diff --git a/arangod/Aql/ExecutionBlock.cpp b/arangod/Aql/ExecutionBlock.cpp index d92747f84a..2d0ed66436 100644 --- a/arangod/Aql/ExecutionBlock.cpp +++ b/arangod/Aql/ExecutionBlock.cpp @@ -3369,7 +3369,7 @@ void UpdateBlock::work (std::vector& blocks) { TRI_json_t* old = TRI_JsonShapedJson(_collection->documentCollection()->getShaper(), &shapedJson); if (old != nullptr) { - TRI_json_t* patchedJson = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, old, json.json(), ep->_options.nullMeansRemove); + TRI_json_t* patchedJson = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, old, json.json(), ep->_options.nullMeansRemove, ep->_options.mergeArrays); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, old); if (patchedJson != nullptr) { diff --git a/arangod/Aql/ExecutionPlan.cpp b/arangod/Aql/ExecutionPlan.cpp index 5bbde93402..f4fe8b5bd2 100644 --- a/arangod/Aql/ExecutionPlan.cpp +++ b/arangod/Aql/ExecutionPlan.cpp @@ -251,6 +251,9 @@ ModificationOptions ExecutionPlan::createOptions (AstNode const* node) { // nullMeansRemove is the opposite of keepNull options.nullMeansRemove = value->isFalse(); } + else if (strcmp(name, "mergeArrays") == 0) { + options.mergeArrays = value->isTrue(); + } } } } diff --git a/arangod/Aql/ModificationOptions.cpp b/arangod/Aql/ModificationOptions.cpp index 84ae4ce8bc..4ab6953497 100644 --- a/arangod/Aql/ModificationOptions.cpp +++ b/arangod/Aql/ModificationOptions.cpp @@ -36,6 +36,7 @@ ModificationOptions::ModificationOptions (Json const& json) { ignoreErrors = JsonHelper::getBooleanValue(array.json(), "ignoreErrors", false); waitForSync = JsonHelper::getBooleanValue(array.json(), "waitForSync", false); nullMeansRemove = JsonHelper::getBooleanValue(array.json(), "nullMeansRemove", false); + mergeArrays = JsonHelper::getBooleanValue(array.json(), "mergeArrays", false); } void ModificationOptions::toJson (triagens::basics::Json& json, @@ -44,7 +45,8 @@ void ModificationOptions::toJson (triagens::basics::Json& json, flags = Json(Json::Array, 3) ("ignoreErrors", Json(ignoreErrors)) ("waitForSync", Json(waitForSync)) - ("nullMeansRemove", Json(nullMeansRemove)); + ("nullMeansRemove", Json(nullMeansRemove)) + ("mergeArrays", Json(mergeArrays)); json ("modificationFlags", flags); } diff --git a/arangod/Aql/ModificationOptions.h b/arangod/Aql/ModificationOptions.h index a2ff88806a..802184e605 100644 --- a/arangod/Aql/ModificationOptions.h +++ b/arangod/Aql/ModificationOptions.h @@ -53,7 +53,8 @@ namespace triagens { ModificationOptions () : ignoreErrors(false), waitForSync(false), - nullMeansRemove(false) { + nullMeansRemove(false), + mergeArrays(false) { } void toJson (triagens::basics::Json& json, TRI_memory_zone_t* zone) const; @@ -65,6 +66,7 @@ namespace triagens { bool ignoreErrors; bool waitForSync; bool nullMeansRemove; + bool mergeArrays; }; diff --git a/arangod/Cluster/ClusterMethods.cpp b/arangod/Cluster/ClusterMethods.cpp index 39ad9c54a8..1385a6501f 100644 --- a/arangod/Cluster/ClusterMethods.cpp +++ b/arangod/Cluster/ClusterMethods.cpp @@ -1051,6 +1051,7 @@ int modifyDocumentOnCoordinator ( bool waitForSync, bool isPatch, bool keepNull, // only counts for isPatch == true + bool mergeArrays, // only counts for isPatch == true TRI_json_t* json, map const& headers, triagens::rest::HttpResponse::HttpResponseCode& responseCode, @@ -1115,6 +1116,12 @@ int modifyDocumentOnCoordinator ( if (! keepNull) { revstr += "&keepNull=false"; } + if (mergeArrays) { + revstr += "&mergeArrays=true"; + } + else { + revstr += "&mergeArrays=false"; + } } else { reqType = triagens::rest::HttpRequest::HTTP_REQUEST_PUT; diff --git a/arangod/Cluster/ClusterMethods.h b/arangod/Cluster/ClusterMethods.h index 269e5a1bc1..569c6578eb 100644 --- a/arangod/Cluster/ClusterMethods.h +++ b/arangod/Cluster/ClusterMethods.h @@ -177,6 +177,7 @@ namespace triagens { bool waitForSync, bool isPatch, bool keepNull, // only counts for isPatch == true + bool mergeArrays, // only counts for isPatch == true TRI_json_t* json, std::map const& headers, triagens::rest::HttpResponse::HttpResponseCode& responseCode, diff --git a/arangod/RestHandler/RestDocumentHandler.cpp b/arangod/RestHandler/RestDocumentHandler.cpp index dbfbe79210..390affb4ee 100644 --- a/arangod/RestHandler/RestDocumentHandler.cpp +++ b/arangod/RestHandler/RestDocumentHandler.cpp @@ -1202,6 +1202,12 @@ bool RestDocumentHandler::replaceDocument () { /// from the existing document that are contained in the patch document with an /// attribute value of *null*. /// +/// @RESTQUERYPARAM{mergeArrays,boolean,optional} +/// Controls whether arrays (not lists) will be merged if present in both the +/// existing and the patch document. If set to *false*, the value in the +/// patch document will overwrite the existing document's value. If set to +/// *true*, arrays will be merged. The default is *true*. +/// /// @RESTQUERYPARAM{waitForSync,boolean,optional} /// Wait until document has been synced to disk. /// @@ -1410,6 +1416,7 @@ bool RestDocumentHandler::modifyDocument (bool isPatch) { if (isPatch) { // patching an existing document bool nullMeansRemove; + bool mergeArrays; bool found; char const* valueStr = _request->value("keepNull", found); if (! found || StringUtils::boolean(valueStr)) { @@ -1421,6 +1428,15 @@ bool RestDocumentHandler::modifyDocument (bool isPatch) { nullMeansRemove = true; } + valueStr = _request->value("mergeArrays", found); + if (! found || StringUtils::boolean(valueStr)) { + // the default is true + mergeArrays = true; + } + else { + mergeArrays = false; + } + // read the existing document TRI_doc_mptr_copy_t oldDocument; @@ -1471,7 +1487,7 @@ bool RestDocumentHandler::modifyDocument (bool isPatch) { } } - TRI_json_t* patchedJson = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, old, json, nullMeansRemove); + TRI_json_t* patchedJson = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, old, json, nullMeansRemove, mergeArrays); TRI_FreeJson(shaper->_memoryZone, old); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); @@ -1574,13 +1590,17 @@ bool RestDocumentHandler::modifyDocumentCoordinator ( string resultBody; bool keepNull = true; - if (! strcmp(_request->value("keepNull"),"false")) { + if (! strcmp(_request->value("keepNull"), "false")) { keepNull = false; } + bool mergeArrays = true; + if (TRI_EqualString(_request->value("mergeArrays"), "false")) { + mergeArrays = false; + } int error = triagens::arango::modifyDocumentOnCoordinator( dbname, collname, key, rev, policy, waitForSync, isPatch, - keepNull, json, headers, responseCode, resultHeaders, resultBody); + keepNull, mergeArrays, json, headers, responseCode, resultHeaders, resultBody); if (error != TRI_ERROR_NO_ERROR) { generateTransactionError(collname, error); diff --git a/arangod/V8Server/v8-collection.cpp b/arangod/V8Server/v8-collection.cpp index c9cc99fad2..4b5ce88d0e 100644 --- a/arangod/V8Server/v8-collection.cpp +++ b/arangod/V8Server/v8-collection.cpp @@ -91,6 +91,7 @@ struct InsertOptions { struct UpdateOptions { bool overwrite = false; bool keepNull = true; + bool mergeArrays = true; bool waitForSync = false; bool silent = false; }; @@ -701,6 +702,7 @@ static v8::Handle ModifyVocbaseColCoordinator ( bool waitForSync, bool isPatch, bool keepNull, // only counts if isPatch==true + bool mergeArrays, // only counts if isPatch==true bool silent, v8::Arguments const& argv) { v8::HandleScope scope; @@ -734,7 +736,7 @@ static v8::Handle ModifyVocbaseColCoordinator ( error = triagens::arango::modifyDocumentOnCoordinator( dbname, collname, key, rev, policy, waitForSync, isPatch, - keepNull, json, headers, responseCode, resultHeaders, resultBody); + keepNull, mergeArrays, json, headers, responseCode, resultHeaders, resultBody); // Note that the json has been freed inside! if (error != TRI_ERROR_NO_ERROR) { @@ -875,6 +877,7 @@ static v8::Handle ReplaceVocbaseCol (bool useCollection, options.waitForSync, false, // isPatch true, // keepNull, does not matter + false, // mergeArrays, does not matter options.silent, argv)); } @@ -1081,7 +1084,7 @@ static v8::Handle UpdateVocbaseCol (bool useCollection, TRI_v8_global_t* v8g = static_cast(v8::Isolate::GetCurrent()->GetData()); if (argLength < 2 || argLength > 5) { - TRI_V8_EXCEPTION_USAGE(scope, "update(, , {overwrite: booleanValue, keepNull: booleanValue, waitForSync: booleanValue})"); + TRI_V8_EXCEPTION_USAGE(scope, "update(, , {overwrite: booleanValue, keepNull: booleanValue, mergeArrays: booleanValue, waitForSync: booleanValue})"); } if (argLength > 2) { @@ -1094,6 +1097,9 @@ static v8::Handle UpdateVocbaseCol (bool useCollection, if (optionsObject->Has(v8g->KeepNullKey)) { options.keepNull = TRI_ObjectToBoolean(optionsObject->Get(v8g->KeepNullKey)); } + if (optionsObject->Has(v8g->MergeArraysKey)) { + options.mergeArrays = TRI_ObjectToBoolean(optionsObject->Get(v8g->MergeArraysKey)); + } if (optionsObject->Has(v8g->WaitForSyncKey)) { options.waitForSync = TRI_ObjectToBoolean(optionsObject->Get(v8g->WaitForSyncKey)); } @@ -1160,6 +1166,7 @@ static v8::Handle UpdateVocbaseCol (bool useCollection, options.waitForSync, true, // isPatch options.keepNull, + options.mergeArrays, options.silent, argv)); } @@ -1226,7 +1233,7 @@ static v8::Handle UpdateVocbaseCol (bool useCollection, } } - TRI_json_t* patchedJson = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, old, json, ! options.keepNull); + TRI_json_t* patchedJson = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, old, json, ! options.keepNull, options.mergeArrays); TRI_FreeJson(zone, old); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); diff --git a/arangod/V8Server/v8-user-structures.cpp b/arangod/V8Server/v8-user-structures.cpp index 5208911479..6652634da1 100644 --- a/arangod/V8Server/v8-user-structures.cpp +++ b/arangod/V8Server/v8-user-structures.cpp @@ -786,7 +786,7 @@ class KeySpace { TRI_V8_EXCEPTION(scope, TRI_ERROR_OUT_OF_MEMORY); } - TRI_json_t* merged = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, found->json, other, nullMeansRemove); + TRI_json_t* merged = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, found->json, other, nullMeansRemove, false); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, other); if (merged == nullptr) { diff --git a/js/apps/system/aardvark/frontend/js/modules/org/arangodb/arango-collection.js b/js/apps/system/aardvark/frontend/js/modules/org/arangodb/arango-collection.js index c2dd22754d..9b5643ac32 100644 --- a/js/apps/system/aardvark/frontend/js/modules/org/arangodb/arango-collection.js +++ b/js/apps/system/aardvark/frontend/js/modules/org/arangodb/arango-collection.js @@ -1174,6 +1174,7 @@ ArangoCollection.prototype.replace = function (id, data, overwrite, waitForSync) /// @param id the id of the document /// @param overwrite (optional) a boolean value or a json object /// @param keepNull (optional) determines if null values should saved or not +/// @param mergeArrays (optional) whether or not array values should be merged /// @param waitForSync (optional) a boolean value . /// @example update("example/996280832675", { a : 1, c : 2} ) /// @example update("example/996280832675", { a : 1, c : 2, x: null}, true, true, true) @@ -1212,6 +1213,11 @@ ArangoCollection.prototype.update = function (id, data, overwrite, keepNull, wai } params = "?keepNull=" + options.keepNull; + if (! options.hasOwnProperty("mergeArrays")) { + options.mergeArrays = true; + } + params += "&mergeArrays=" + options.mergeArrays; + if (options.hasOwnProperty("overwrite") && options.overwrite) { params += "&policy=last"; } diff --git a/js/apps/system/aardvark/frontend/js/modules/org/arangodb/arango-database.js b/js/apps/system/aardvark/frontend/js/modules/org/arangodb/arango-database.js index 2ade9ebb3a..66bfdd84de 100644 --- a/js/apps/system/aardvark/frontend/js/modules/org/arangodb/arango-database.js +++ b/js/apps/system/aardvark/frontend/js/modules/org/arangodb/arango-database.js @@ -758,6 +758,10 @@ ArangoDatabase.prototype._update = function (id, data, overwrite, keepNull, wait options.keepNull = true; } params = "?keepNull=" + options.keepNull; + if (! options.hasOwnProperty("mergeArrays")) { + options.mergeArrays = true; + } + params += "&mergeArrays=" + options.mergeArrays; if (options.hasOwnProperty("overwrite") && options.overwrite) { params += "&policy=last"; diff --git a/js/client/modules/org/arangodb/arango-collection.js b/js/client/modules/org/arangodb/arango-collection.js index bcba5e135f..1a0111b15b 100644 --- a/js/client/modules/org/arangodb/arango-collection.js +++ b/js/client/modules/org/arangodb/arango-collection.js @@ -1173,6 +1173,7 @@ ArangoCollection.prototype.replace = function (id, data, overwrite, waitForSync) /// @param id the id of the document /// @param overwrite (optional) a boolean value or a json object /// @param keepNull (optional) determines if null values should saved or not +/// @param mergeArrays (optional) whether or not array values should be merged /// @param waitForSync (optional) a boolean value . /// @example update("example/996280832675", { a : 1, c : 2} ) /// @example update("example/996280832675", { a : 1, c : 2, x: null}, true, true, true) @@ -1211,6 +1212,11 @@ ArangoCollection.prototype.update = function (id, data, overwrite, keepNull, wai } params = "?keepNull=" + options.keepNull; + if (! options.hasOwnProperty("mergeArrays")) { + options.mergeArrays = true; + } + params += "&mergeArrays=" + options.mergeArrays; + if (options.hasOwnProperty("overwrite") && options.overwrite) { params += "&policy=last"; } diff --git a/js/client/modules/org/arangodb/arango-database.js b/js/client/modules/org/arangodb/arango-database.js index dd1f2c83e4..33186bd804 100644 --- a/js/client/modules/org/arangodb/arango-database.js +++ b/js/client/modules/org/arangodb/arango-database.js @@ -757,6 +757,10 @@ ArangoDatabase.prototype._update = function (id, data, overwrite, keepNull, wait options.keepNull = true; } params = "?keepNull=" + options.keepNull; + if (! options.hasOwnProperty("mergeArrays")) { + options.mergeArrays = true; + } + params += "&mergeArrays=" + options.mergeArrays; if (options.hasOwnProperty("overwrite") && options.overwrite) { params += "&policy=last"; diff --git a/lib/Basics/json-utilities.cpp b/lib/Basics/json-utilities.cpp index d4f4a8cad6..b1fcab336e 100644 --- a/lib/Basics/json-utilities.cpp +++ b/lib/Basics/json-utilities.cpp @@ -40,7 +40,8 @@ static TRI_json_t* MergeRecursive (TRI_memory_zone_t* zone, TRI_json_t const* lhs, TRI_json_t const* rhs, - bool nullMeansRemove) { + bool nullMeansRemove, + bool mergeArrays) { TRI_json_t* result = TRI_CopyJson(zone, lhs); if (result == nullptr) { @@ -65,7 +66,7 @@ static TRI_json_t* MergeRecursive (TRI_memory_zone_t* zone, // existing array does not have the attribute => append new attribute if (value->_type == TRI_JSON_ARRAY) { TRI_json_t* empty = TRI_CreateArrayJson(zone); - TRI_json_t* merged = MergeRecursive(zone, empty, value, nullMeansRemove); + TRI_json_t* merged = MergeRecursive(zone, empty, value, nullMeansRemove, mergeArrays); TRI_Insert3ArrayJson(zone, result, key->_value._string.data, merged); TRI_FreeJson(zone, empty); @@ -76,8 +77,8 @@ static TRI_json_t* MergeRecursive (TRI_memory_zone_t* zone, } else { // existing array already has the attribute => replace attribute - if (lhsValue->_type == TRI_JSON_ARRAY && value->_type == TRI_JSON_ARRAY) { - TRI_json_t* merged = MergeRecursive(zone, lhsValue, value, nullMeansRemove); + if (lhsValue->_type == TRI_JSON_ARRAY && value->_type == TRI_JSON_ARRAY && mergeArrays) { + TRI_json_t* merged = MergeRecursive(zone, lhsValue, value, nullMeansRemove, mergeArrays); TRI_ReplaceArrayJson(zone, result, key->_value._string.data, merged); TRI_FreeJson(zone, merged); } @@ -732,13 +733,14 @@ bool TRI_HasDuplicateKeyJson (TRI_json_t const* object) { TRI_json_t* TRI_MergeJson (TRI_memory_zone_t* zone, TRI_json_t const* lhs, TRI_json_t const* rhs, - bool nullMeansRemove) { + bool nullMeansRemove, + bool mergeArrays) { TRI_json_t* result; TRI_ASSERT(lhs->_type == TRI_JSON_ARRAY); TRI_ASSERT(rhs->_type == TRI_JSON_ARRAY); - result = MergeRecursive(zone, lhs, rhs, nullMeansRemove); + result = MergeRecursive(zone, lhs, rhs, nullMeansRemove, mergeArrays); return result; } diff --git a/lib/Basics/json-utilities.h b/lib/Basics/json-utilities.h index 918caece9a..5c7fdf6076 100644 --- a/lib/Basics/json-utilities.h +++ b/lib/Basics/json-utilities.h @@ -137,6 +137,7 @@ bool TRI_HasDuplicateKeyJson (TRI_json_t const*); TRI_json_t* TRI_MergeJson (TRI_memory_zone_t*, TRI_json_t const*, TRI_json_t const*, + bool, bool); //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/V8/v8-globals.cpp b/lib/V8/v8-globals.cpp index 6ae8e2e745..127959b432 100644 --- a/lib/V8/v8-globals.cpp +++ b/lib/V8/v8-globals.cpp @@ -92,6 +92,7 @@ TRI_v8_global_s::TRI_v8_global_s (v8::Isolate* isolate) KeyOptionsKey(), LengthKey(), LifeTimeKey(), + MergeArraysKey(), NameKey(), OperationIDKey(), ParametersKey(), @@ -176,6 +177,7 @@ TRI_v8_global_s::TRI_v8_global_s (v8::Isolate* isolate) KeyOptionsKey = v8::Persistent::New(isolate, TRI_V8_SYMBOL("keyOptions")); LengthKey = v8::Persistent::New(isolate, TRI_V8_SYMBOL("length")); LifeTimeKey = v8::Persistent::New(isolate, TRI_V8_SYMBOL("lifeTime")); + MergeArraysKey = v8::Persistent::New(isolate, TRI_V8_SYMBOL("mergeArrays")); NameKey = v8::Persistent::New(isolate, TRI_V8_SYMBOL("name")); OperationIDKey = v8::Persistent::New(isolate, TRI_V8_SYMBOL("operationID")); OverwriteKey = v8::Persistent::New(isolate, TRI_V8_SYMBOL("overwrite")); diff --git a/lib/V8/v8-globals.h b/lib/V8/v8-globals.h index 4a0a55b78f..972ec77249 100644 --- a/lib/V8/v8-globals.h +++ b/lib/V8/v8-globals.h @@ -555,6 +555,12 @@ typedef struct TRI_v8_global_s { v8::Persistent LifeTimeKey; +//////////////////////////////////////////////////////////////////////////////// +/// @brief "mergeArrays" key name +//////////////////////////////////////////////////////////////////////////////// + + v8::Persistent MergeArraysKey; + //////////////////////////////////////////////////////////////////////////////// /// @brief "name" key ////////////////////////////////////////////////////////////////////////////////