sql/json_diff.cc (189 lines of code) (raw):

/* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "json_diff.h" #include "field.h" // Field_json #include "json_dom.h" // Json_dom, Json_wrapper #include "json_path.h" // Json_path #include "my_dbug.h" // DBUG_ASSERT #include "sql_class.h" // THD #include "sql_string.h" // StringBuffer Json_diff::Json_diff(const Json_seekable_path &path, enum_json_diff_operation operation, Json_dom *value) : m_path(), m_operation(operation), m_value(value) { for (size_t i= 0; i < path.leg_count(); ++i) m_path.append(*path.get_leg_at(i)); } Json_wrapper Json_diff::value() const { Json_wrapper result(m_value.get()); result.set_alias(); return result; } /** Find the value at the specified path in a JSON DOM. The path should not contain any wildcard or ellipsis, only simple array cells or member names. Auto-wrapping is not performed. @param dom the root of the DOM @param path the path to seek for @param path_legs the number of path legs to use @return the JSON DOM at the given path, or `nullptr` if the path is not found */ static Json_dom *seek_exact_path(Json_dom *dom, const Json_seekable_path &path, size_t path_legs) { for (size_t i= 0; i < path_legs; ++i) { const auto leg= path.get_leg_at(i); const auto leg_type= leg->get_type(); DBUG_ASSERT(leg_type == jpl_member || leg_type == jpl_array_cell); switch (dom->json_type()) { case enum_json_type::J_ARRAY: { const auto array= down_cast<Json_array*>(dom); if (leg_type != jpl_array_cell) return nullptr; Json_array_index idx= leg->first_array_index(array->size()); if (!idx.within_bounds()) return nullptr; dom= (*array)[idx.position()]; continue; } case enum_json_type::J_OBJECT: { const auto object= down_cast<Json_object*>(dom); if (leg_type != jpl_member) return nullptr; dom= object->get(leg->get_member_name()); if (dom == nullptr) return nullptr; continue; } default: return nullptr; } } return dom; } enum_json_diff_status apply_json_diffs(Field_json *field, const Json_diff_vector *diffs) { // Cannot apply a diff to NULL. if (field->is_null()) return enum_json_diff_status::REJECTED; Json_wrapper doc; if (field->val_json(&doc)) return enum_json_diff_status::ERROR; /* purecov: inspected */ // Should we collect logical diffs while applying them? const bool collect_logical_diffs= field->table->is_logical_diff_enabled(field); // Should we try to perform the update in place using binary diffs? bool binary_inplace_update= field->table->is_binary_diff_enabled(field); StringBuffer<STRING_BUFFER_USUAL_SIZE> buffer; const THD *thd= field->table->in_use; for (const Json_diff &diff : *diffs) { Json_wrapper val= diff.value(); auto &path= diff.path(); if (path.leg_count() == 0) { /* Cannot replace the root (then a full update will be used instead of creating a diff), or insert the root, or remove the root, so reject this diff. */ return enum_json_diff_status::REJECTED; } if (collect_logical_diffs) field->table->add_logical_diff(field, path, diff.operation(), &val); if (binary_inplace_update) { if (diff.operation() == enum_json_diff_operation::REPLACE) { bool partially_updated= false; bool replaced_path= false; if (doc.attempt_binary_update(field, path, &val, false, &buffer, &partially_updated, &replaced_path)) return enum_json_diff_status::ERROR; /* purecov: inspected */ if (partially_updated) { if (!replaced_path) return enum_json_diff_status::REJECTED; continue; } } else if (diff.operation() == enum_json_diff_operation::REMOVE) { Json_wrapper_vector hits(key_memory_JSON); bool found_path= false; if (doc.binary_remove(field, path, &buffer, &found_path)) return enum_json_diff_status::ERROR; /* purecov: inspected */ if (!found_path) return enum_json_diff_status::REJECTED; continue; } // Couldn't update in place, so try full update. binary_inplace_update= false; field->table->disable_binary_diffs_for_current_row(field); } Json_dom *dom= doc.to_dom(thd); if (doc.to_dom(thd) == nullptr) return enum_json_diff_status::ERROR; /* purecov: inspected */ switch (diff.operation()) { case enum_json_diff_operation::REPLACE: { DBUG_ASSERT(path.leg_count() > 0); Json_dom *old= seek_exact_path(dom, path, path.leg_count()); if (old == nullptr) return enum_json_diff_status::REJECTED; DBUG_ASSERT(old->parent() != nullptr); old->parent()->replace_dom_in_container(old, val.clone_dom(thd)); continue; } case enum_json_diff_operation::INSERT: { DBUG_ASSERT(path.leg_count() > 0); Json_dom *parent= seek_exact_path(dom, path, path.leg_count() - 1); if (parent == nullptr) return enum_json_diff_status::REJECTED; const Json_path_leg *last_leg= path.get_leg_at(path.leg_count() - 1); if (parent->json_type() == enum_json_type::J_OBJECT && last_leg->get_type() == jpl_member) { auto obj= down_cast<Json_object*>(parent); if (obj->get(last_leg->get_member_name()) != nullptr) return enum_json_diff_status::REJECTED; if (obj->add_alias(last_leg->get_member_name(), val.clone_dom(thd))) return enum_json_diff_status::ERROR; /* purecov: inspected */ continue; } if (parent->json_type() == enum_json_type::J_ARRAY && last_leg->get_type() == jpl_array_cell) { auto array= down_cast<Json_array*>(parent); Json_array_index idx= last_leg->first_array_index(array->size()); if (array->insert_alias(idx.position(), val.clone_dom(thd))) return enum_json_diff_status::ERROR; /* purecov: inspected */ continue; } return enum_json_diff_status::REJECTED; } case enum_json_diff_operation::REMOVE: { DBUG_ASSERT(path.leg_count() > 0); Json_dom *parent= seek_exact_path(dom, path, path.leg_count() - 1); if (parent == nullptr) return enum_json_diff_status::REJECTED; const Json_path_leg *last_leg= path.get_leg_at(path.leg_count() - 1); if (parent->json_type() == enum_json_type::J_OBJECT) { auto object= down_cast<Json_object*>(parent); if (last_leg->get_type() != jpl_member || !object->remove(last_leg->get_member_name())) return enum_json_diff_status::REJECTED; } else if (parent->json_type() == enum_json_type::J_ARRAY) { if (last_leg->get_type() != jpl_array_cell) return enum_json_diff_status::REJECTED; auto array= down_cast<Json_array*>(parent); Json_array_index idx= last_leg->first_array_index(array->size()); if (!idx.within_bounds() || !array->remove(idx.position())) return enum_json_diff_status::REJECTED; } else { return enum_json_diff_status::REJECTED; } continue; } } DBUG_ASSERT(false); /* purecov: deadcode */ } if (field->store_json(&doc) != TYPE_OK) return enum_json_diff_status::ERROR; /* purecov: inspected */ return enum_json_diff_status::SUCCESS; }