From 41ab3aa4d6dd25d5223ac841c165ddba18bfe08e Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Tue, 2 Dec 2014 17:03:03 +0100 Subject: [PATCH 1/6] Renamed mergeArrays->mergeObjects to make API less confusing. --- arangod/Aql/ExecutionBlock.cpp | 2 +- arangod/Aql/ExecutionPlan.cpp | 4 +- arangod/Aql/ModificationOptions.cpp | 4 +- arangod/Aql/ModificationOptions.h | 4 +- arangod/Cluster/ClusterMethods.cpp | 8 +- arangod/Cluster/ClusterMethods.h | 2 +- arangod/RestHandler/RestDocumentHandler.cpp | 24 ++-- arangod/V8Server/v8-collection.cpp | 18 +-- .../aardvark/frontend/js/lib/joi.browser.js | 112 +++++++++--------- .../modules/org/arangodb/arango-collection.js | 8 +- .../modules/org/arangodb/arango-database.js | 6 +- .../modules/org/arangodb/arango-collection.js | 8 +- .../modules/org/arangodb/arango-database.js | 6 +- lib/Basics/json-utilities.cpp | 12 +- lib/V8/v8-globals.cpp | 4 +- lib/V8/v8-globals.h | 4 +- 16 files changed, 113 insertions(+), 113 deletions(-) diff --git a/arangod/Aql/ExecutionBlock.cpp b/arangod/Aql/ExecutionBlock.cpp index 9ec159e1df..2319c54df5 100644 --- a/arangod/Aql/ExecutionBlock.cpp +++ b/arangod/Aql/ExecutionBlock.cpp @@ -3395,7 +3395,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, ep->_options.mergeArrays); + TRI_json_t* patchedJson = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, old, json.json(), ep->_options.nullMeansRemove, ep->_options.mergeObjects); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, old); if (patchedJson != nullptr) { diff --git a/arangod/Aql/ExecutionPlan.cpp b/arangod/Aql/ExecutionPlan.cpp index 85ad758be9..cafe2e4ee1 100644 --- a/arangod/Aql/ExecutionPlan.cpp +++ b/arangod/Aql/ExecutionPlan.cpp @@ -251,8 +251,8 @@ 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(); + else if (strcmp(name, "mergeObjects") == 0) { + options.mergeObjects = value->isTrue(); } } } diff --git a/arangod/Aql/ModificationOptions.cpp b/arangod/Aql/ModificationOptions.cpp index 4ab6953497..2848cc5847 100644 --- a/arangod/Aql/ModificationOptions.cpp +++ b/arangod/Aql/ModificationOptions.cpp @@ -36,7 +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); + mergeObjects = JsonHelper::getBooleanValue(array.json(), "mergeObjects", false); } void ModificationOptions::toJson (triagens::basics::Json& json, @@ -46,7 +46,7 @@ void ModificationOptions::toJson (triagens::basics::Json& json, ("ignoreErrors", Json(ignoreErrors)) ("waitForSync", Json(waitForSync)) ("nullMeansRemove", Json(nullMeansRemove)) - ("mergeArrays", Json(mergeArrays)); + ("mergeObjects", Json(mergeObjects)); json ("modificationFlags", flags); } diff --git a/arangod/Aql/ModificationOptions.h b/arangod/Aql/ModificationOptions.h index 802184e605..d60769be42 100644 --- a/arangod/Aql/ModificationOptions.h +++ b/arangod/Aql/ModificationOptions.h @@ -54,7 +54,7 @@ namespace triagens { : ignoreErrors(false), waitForSync(false), nullMeansRemove(false), - mergeArrays(false) { + mergeObjects(false) { } void toJson (triagens::basics::Json& json, TRI_memory_zone_t* zone) const; @@ -66,7 +66,7 @@ namespace triagens { bool ignoreErrors; bool waitForSync; bool nullMeansRemove; - bool mergeArrays; + bool mergeObjects; }; diff --git a/arangod/Cluster/ClusterMethods.cpp b/arangod/Cluster/ClusterMethods.cpp index 1385a6501f..b5abe0c3e8 100644 --- a/arangod/Cluster/ClusterMethods.cpp +++ b/arangod/Cluster/ClusterMethods.cpp @@ -1051,7 +1051,7 @@ int modifyDocumentOnCoordinator ( bool waitForSync, bool isPatch, bool keepNull, // only counts for isPatch == true - bool mergeArrays, // only counts for isPatch == true + bool mergeObjects, // only counts for isPatch == true TRI_json_t* json, map const& headers, triagens::rest::HttpResponse::HttpResponseCode& responseCode, @@ -1116,11 +1116,11 @@ int modifyDocumentOnCoordinator ( if (! keepNull) { revstr += "&keepNull=false"; } - if (mergeArrays) { - revstr += "&mergeArrays=true"; + if (mergeObjects) { + revstr += "&mergeObjects=true"; } else { - revstr += "&mergeArrays=false"; + revstr += "&mergeObjects=false"; } } else { diff --git a/arangod/Cluster/ClusterMethods.h b/arangod/Cluster/ClusterMethods.h index 569c6578eb..74b05a4069 100644 --- a/arangod/Cluster/ClusterMethods.h +++ b/arangod/Cluster/ClusterMethods.h @@ -177,7 +177,7 @@ namespace triagens { bool waitForSync, bool isPatch, bool keepNull, // only counts for isPatch == true - bool mergeArrays, // only counts for isPatch == true + bool mergeObjects, // 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 390affb4ee..4d29ff256e 100644 --- a/arangod/RestHandler/RestDocumentHandler.cpp +++ b/arangod/RestHandler/RestDocumentHandler.cpp @@ -1202,11 +1202,11 @@ 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 +/// @RESTQUERYPARAM{mergeObjects,boolean,optional} +/// Controls whether objects (not arrays) 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*. +/// *true*, objects will be merged. The default is *true*. /// /// @RESTQUERYPARAM{waitForSync,boolean,optional} /// Wait until document has been synced to disk. @@ -1416,7 +1416,7 @@ bool RestDocumentHandler::modifyDocument (bool isPatch) { if (isPatch) { // patching an existing document bool nullMeansRemove; - bool mergeArrays; + bool mergeObjects; bool found; char const* valueStr = _request->value("keepNull", found); if (! found || StringUtils::boolean(valueStr)) { @@ -1428,13 +1428,13 @@ bool RestDocumentHandler::modifyDocument (bool isPatch) { nullMeansRemove = true; } - valueStr = _request->value("mergeArrays", found); + valueStr = _request->value("mergeObjects", found); if (! found || StringUtils::boolean(valueStr)) { // the default is true - mergeArrays = true; + mergeObjects = true; } else { - mergeArrays = false; + mergeObjects = false; } // read the existing document @@ -1487,7 +1487,7 @@ bool RestDocumentHandler::modifyDocument (bool isPatch) { } } - TRI_json_t* patchedJson = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, old, json, nullMeansRemove, mergeArrays); + TRI_json_t* patchedJson = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, old, json, nullMeansRemove, mergeObjects); TRI_FreeJson(shaper->_memoryZone, old); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); @@ -1593,14 +1593,14 @@ bool RestDocumentHandler::modifyDocumentCoordinator ( if (! strcmp(_request->value("keepNull"), "false")) { keepNull = false; } - bool mergeArrays = true; - if (TRI_EqualString(_request->value("mergeArrays"), "false")) { - mergeArrays = false; + bool mergeObjects = true; + if (TRI_EqualString(_request->value("mergeObjects"), "false")) { + mergeObjects = false; } int error = triagens::arango::modifyDocumentOnCoordinator( dbname, collname, key, rev, policy, waitForSync, isPatch, - keepNull, mergeArrays, json, headers, responseCode, resultHeaders, resultBody); + keepNull, mergeObjects, 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 18eacb9208..f05a7f089d 100644 --- a/arangod/V8Server/v8-collection.cpp +++ b/arangod/V8Server/v8-collection.cpp @@ -91,7 +91,7 @@ struct InsertOptions { struct UpdateOptions { bool overwrite = false; bool keepNull = true; - bool mergeArrays = true; + bool mergeObjects = true; bool waitForSync = false; bool silent = false; }; @@ -702,7 +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 mergeObjects, // only counts if isPatch==true bool silent, v8::Arguments const& argv) { v8::HandleScope scope; @@ -736,7 +736,7 @@ static v8::Handle ModifyVocbaseColCoordinator ( error = triagens::arango::modifyDocumentOnCoordinator( dbname, collname, key, rev, policy, waitForSync, isPatch, - keepNull, mergeArrays, json, headers, responseCode, resultHeaders, resultBody); + keepNull, mergeObjects, json, headers, responseCode, resultHeaders, resultBody); // Note that the json has been freed inside! if (error != TRI_ERROR_NO_ERROR) { @@ -877,7 +877,7 @@ static v8::Handle ReplaceVocbaseCol (bool useCollection, options.waitForSync, false, // isPatch true, // keepNull, does not matter - false, // mergeArrays, does not matter + false, // mergeObjects, does not matter options.silent, argv)); } @@ -1084,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, mergeArrays: booleanValue, waitForSync: booleanValue})"); + TRI_V8_EXCEPTION_USAGE(scope, "update(, , {overwrite: booleanValue, keepNull: booleanValue, mergeObjects: booleanValue, waitForSync: booleanValue})"); } if (argLength > 2) { @@ -1097,8 +1097,8 @@ 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->MergeObjectsKey)) { + options.mergeObjects = TRI_ObjectToBoolean(optionsObject->Get(v8g->MergeObjectsKey)); } if (optionsObject->Has(v8g->WaitForSyncKey)) { options.waitForSync = TRI_ObjectToBoolean(optionsObject->Get(v8g->WaitForSyncKey)); @@ -1166,7 +1166,7 @@ static v8::Handle UpdateVocbaseCol (bool useCollection, options.waitForSync, true, // isPatch options.keepNull, - options.mergeArrays, + options.mergeObjects, options.silent, argv)); } @@ -1233,7 +1233,7 @@ static v8::Handle UpdateVocbaseCol (bool useCollection, } } - TRI_json_t* patchedJson = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, old, json, ! options.keepNull, options.mergeArrays); + TRI_json_t* patchedJson = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, old, json, ! options.keepNull, options.mergeObjects); TRI_FreeJson(zone, old); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); diff --git a/js/apps/system/aardvark/frontend/js/lib/joi.browser.js b/js/apps/system/aardvark/frontend/js/lib/joi.browser.js index 9c446249b8..87b123e644 100644 --- a/js/apps/system/aardvark/frontend/js/lib/joi.browser.js +++ b/js/apps/system/aardvark/frontend/js/lib/joi.browser.js @@ -2896,7 +2896,7 @@ exports.clone = function (obj, seen) { // Merge all the properties of source into target, source wins in conflict, and by default null and undefined from source are applied -exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) { +exports.merge = function (target, source, isNullOverride /* = true */, isMergeObjects /* = true */) { exports.assert(target && typeof target === 'object', 'Invalid target value: must be an object'); exports.assert(source === null || source === undefined || typeof source === 'object', 'Invalid source value: must be null, undefined, or an object'); @@ -2907,7 +2907,7 @@ exports.merge = function (target, source, isNullOverride /* = true */, isMergeAr if (Array.isArray(source)) { exports.assert(Array.isArray(target), 'Cannot merge array onto an object'); - if (isMergeArrays === false) { // isMergeArrays defaults to true + if (isMergeObjects === false) { // isMergeObjects defaults to true target.length = 0; // Must not change target assignment } @@ -2935,7 +2935,7 @@ exports.merge = function (target, source, isNullOverride /* = true */, isMergeAr target[key] = exports.clone(value); } else { - exports.merge(target[key], value, isNullOverride, isMergeArrays); + exports.merge(target[key], value, isNullOverride, isMergeObjects); } } else { @@ -4692,14 +4692,14 @@ var Hoek = require('hoek'); var internals = {}; -exports = module.exports = internals.Topo = function () { +exports = module.exports = internals.Topo = function () { this._items = []; - this.nodes = []; + this.nodes = []; }; -internals.Topo.prototype.add = function (nodes, options) { +internals.Topo.prototype.add = function (nodes, options) { var self = this; @@ -4716,17 +4716,17 @@ internals.Topo.prototype.add = function (nodes, options) { Hoek.assert(after.indexOf(group) === -1, 'Item cannot come after itself:', group); Hoek.assert(after.indexOf('?') === -1, 'Item cannot come after unassociated items'); - ([].concat(nodes)).forEach(function (node, i) { + ([].concat(nodes)).forEach(function (node, i) { - var item = { + var item = { seq: self._items.length, before: before, after: after, group: group, - node: node + node: node }; - self._items.push(item); + self._items.push(item); }); // Insert event @@ -4734,7 +4734,7 @@ internals.Topo.prototype.add = function (nodes, options) { var error = this._sort(); Hoek.assert(!error, 'item', (group !== '?' ? 'added into group ' + group : ''), 'created a dependencies error'); - return this.nodes; + return this.nodes; }; @@ -4746,7 +4746,7 @@ internals.Topo.prototype._sort = function () { var graph = {}; var graphAfters = {}; - for (var i = 0, il = this._items.length; i < il; ++i) { + for (var i = 0, il = this._items.length; i < il; ++i) { var item = this._items[i]; var seq = item.seq; // Unique across all items var group = item.group; @@ -4763,55 +4763,55 @@ internals.Topo.prototype._sort = function () { // Build second intermediary graph with 'after' var after = item.after; - for (var j = 0, jl = after.length; j < jl; ++j) { - graphAfters[after[j]] = (graphAfters[after[j]] || []).concat(seq); - } + for (var j = 0, jl = after.length; j < jl; ++j) { + graphAfters[after[j]] = (graphAfters[after[j]] || []).concat(seq); + } } // Expand intermediary graph var graphNodes = Object.keys(graph); - for (i = 0, il = graphNodes.length; i < il; ++i) { + for (i = 0, il = graphNodes.length; i < il; ++i) { var node = graphNodes[i]; var expandedGroups = []; var graphNodeItems = Object.keys(graph[node]); - for (j = 0, jl = graphNodeItems.length; j < jl; ++j) { + for (j = 0, jl = graphNodeItems.length; j < jl; ++j) { var group = graph[node][graphNodeItems[j]]; groups[group] = groups[group] || []; - groups[group].forEach(function (d) { + groups[group].forEach(function (d) { - expandedGroups.push(d); - }); + expandedGroups.push(d); + }); } - graph[node] = expandedGroups; + graph[node] = expandedGroups; } // Merge intermediary graph using graphAfters into final graph var afterNodes = Object.keys(graphAfters); - for (i = 0, il = afterNodes.length; i < il; ++i) { + for (i = 0, il = afterNodes.length; i < il; ++i) { var group = afterNodes[i]; - if (groups[group]) { - for (j = 0, jl = groups[group].length; j < jl; ++j) { + if (groups[group]) { + for (j = 0, jl = groups[group].length; j < jl; ++j) { var node = groups[group][j]; - graph[node] = graph[node].concat(graphAfters[group]); - } - } + graph[node] = graph[node].concat(graphAfters[group]); + } + } } // Compile ancestors var ancestors = {}; graphNodes = Object.keys(graph); - for (i = 0, il = graphNodes.length; i < il; ++i) { + for (i = 0, il = graphNodes.length; i < il; ++i) { var node = graphNodes[i]; var children = graph[node]; - for (j = 0, jl = children.length; j < jl; ++j) { - ancestors[children[j]] = (ancestors[children[j]] || []).concat(node); - } + for (j = 0, jl = children.length; j < jl; ++j) { + ancestors[children[j]] = (ancestors[children[j]] || []).concat(node); + } } // Topo sort @@ -4819,61 +4819,61 @@ internals.Topo.prototype._sort = function () { var visited = {}; var sorted = []; - for (i = 0, il = this._items.length; i < il; ++i) { + for (i = 0, il = this._items.length; i < il; ++i) { var next = i; - if (ancestors[i]) { + if (ancestors[i]) { next = null; - for (j = 0, jl = this._items.length; j < jl; ++j) { - if (visited[j] === true) { - continue; + for (j = 0, jl = this._items.length; j < jl; ++j) { + if (visited[j] === true) { + continue; } - if (!ancestors[j]) { - ancestors[j] = []; + if (!ancestors[j]) { + ancestors[j] = []; } var shouldSeeCount = ancestors[j].length; var seenCount = 0; - for (var l = 0, ll = shouldSeeCount; l < ll; ++l) { - if (sorted.indexOf(ancestors[j][l]) >= 0) { - ++seenCount; - } + for (var l = 0, ll = shouldSeeCount; l < ll; ++l) { + if (sorted.indexOf(ancestors[j][l]) >= 0) { + ++seenCount; + } } - if (seenCount === shouldSeeCount) { + if (seenCount === shouldSeeCount) { next = j; - break; - } - } + break; + } + } } - if (next !== null) { + if (next !== null) { next = next.toString(); // Normalize to string TODO: replace with seq visited[next] = true; - sorted.push(next); - } + sorted.push(next); + } } - if (sorted.length !== this._items.length) { - return new Error('Invalid dependencies'); + if (sorted.length !== this._items.length) { + return new Error('Invalid dependencies'); } var seqIndex = {}; - this._items.forEach(function (item) { + this._items.forEach(function (item) { - seqIndex[item.seq] = item; + seqIndex[item.seq] = item; }); var sortedNodes = []; - this._items = sorted.map(function (value) { + this._items = sorted.map(function (value) { var item = seqIndex[value]; sortedNodes.push(item.node); - return item; + return item; }); - this.nodes = sortedNodes; + this.nodes = sortedNodes; }; },{"hoek":16}],23:[function(require,module,exports){ 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 9b5643ac32..9063045bf7 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,7 +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 mergeObjects (optional) whether or not object 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) @@ -1213,10 +1213,10 @@ ArangoCollection.prototype.update = function (id, data, overwrite, keepNull, wai } params = "?keepNull=" + options.keepNull; - if (! options.hasOwnProperty("mergeArrays")) { - options.mergeArrays = true; + if (! options.hasOwnProperty("mergeObjects")) { + options.mergeObjects = true; } - params += "&mergeArrays=" + options.mergeArrays; + params += "&mergeObjects=" + options.mergeObjects; 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 66bfdd84de..5a059e562a 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,10 +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; + if (! options.hasOwnProperty("mergeObjects")) { + options.mergeObjects = true; } - params += "&mergeArrays=" + options.mergeArrays; + params += "&mergeObjects=" + options.mergeObjects; 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 1a0111b15b..7dbb5718f7 100644 --- a/js/client/modules/org/arangodb/arango-collection.js +++ b/js/client/modules/org/arangodb/arango-collection.js @@ -1173,7 +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 mergeObjects (optional) whether or not object 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,10 +1212,10 @@ ArangoCollection.prototype.update = function (id, data, overwrite, keepNull, wai } params = "?keepNull=" + options.keepNull; - if (! options.hasOwnProperty("mergeArrays")) { - options.mergeArrays = true; + if (! options.hasOwnProperty("mergeObjects")) { + options.mergeObjects = true; } - params += "&mergeArrays=" + options.mergeArrays; + params += "&mergeObjects=" + options.mergeObjects; 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 33186bd804..c269318cb3 100644 --- a/js/client/modules/org/arangodb/arango-database.js +++ b/js/client/modules/org/arangodb/arango-database.js @@ -757,10 +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; + if (! options.hasOwnProperty("mergeObjects")) { + options.mergeObjects = true; } - params += "&mergeArrays=" + options.mergeArrays; + params += "&mergeObjects=" + options.mergeObjects; if (options.hasOwnProperty("overwrite") && options.overwrite) { params += "&policy=last"; diff --git a/lib/Basics/json-utilities.cpp b/lib/Basics/json-utilities.cpp index b1fcab336e..0978b5a5e2 100644 --- a/lib/Basics/json-utilities.cpp +++ b/lib/Basics/json-utilities.cpp @@ -41,7 +41,7 @@ static TRI_json_t* MergeRecursive (TRI_memory_zone_t* zone, TRI_json_t const* lhs, TRI_json_t const* rhs, bool nullMeansRemove, - bool mergeArrays) { + bool mergeObjects) { TRI_json_t* result = TRI_CopyJson(zone, lhs); if (result == nullptr) { @@ -66,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, mergeArrays); + TRI_json_t* merged = MergeRecursive(zone, empty, value, nullMeansRemove, mergeObjects); TRI_Insert3ArrayJson(zone, result, key->_value._string.data, merged); TRI_FreeJson(zone, empty); @@ -77,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 && mergeArrays) { - TRI_json_t* merged = MergeRecursive(zone, lhsValue, value, nullMeansRemove, mergeArrays); + if (lhsValue->_type == TRI_JSON_ARRAY && value->_type == TRI_JSON_ARRAY && mergeObjects) { + TRI_json_t* merged = MergeRecursive(zone, lhsValue, value, nullMeansRemove, mergeObjects); TRI_ReplaceArrayJson(zone, result, key->_value._string.data, merged); TRI_FreeJson(zone, merged); } @@ -734,13 +734,13 @@ TRI_json_t* TRI_MergeJson (TRI_memory_zone_t* zone, TRI_json_t const* lhs, TRI_json_t const* rhs, bool nullMeansRemove, - bool mergeArrays) { + bool mergeObjects) { TRI_json_t* result; TRI_ASSERT(lhs->_type == TRI_JSON_ARRAY); TRI_ASSERT(rhs->_type == TRI_JSON_ARRAY); - result = MergeRecursive(zone, lhs, rhs, nullMeansRemove, mergeArrays); + result = MergeRecursive(zone, lhs, rhs, nullMeansRemove, mergeObjects); return result; } diff --git a/lib/V8/v8-globals.cpp b/lib/V8/v8-globals.cpp index 127959b432..ce7c29ec8c 100644 --- a/lib/V8/v8-globals.cpp +++ b/lib/V8/v8-globals.cpp @@ -92,7 +92,7 @@ TRI_v8_global_s::TRI_v8_global_s (v8::Isolate* isolate) KeyOptionsKey(), LengthKey(), LifeTimeKey(), - MergeArraysKey(), + MergeObjectsKey(), NameKey(), OperationIDKey(), ParametersKey(), @@ -177,7 +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")); + MergeObjectsKey = v8::Persistent::New(isolate, TRI_V8_SYMBOL("mergeObjects")); 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 972ec77249..3fb8cd08a8 100644 --- a/lib/V8/v8-globals.h +++ b/lib/V8/v8-globals.h @@ -556,10 +556,10 @@ typedef struct TRI_v8_global_s { v8::Persistent LifeTimeKey; //////////////////////////////////////////////////////////////////////////////// -/// @brief "mergeArrays" key name +/// @brief "mergeObjects" key name //////////////////////////////////////////////////////////////////////////////// - v8::Persistent MergeArraysKey; + v8::Persistent MergeObjectsKey; //////////////////////////////////////////////////////////////////////////////// /// @brief "name" key From 741bf1e4d5fc93ae0fd1455238a9fd378e543e44 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Tue, 2 Dec 2014 18:25:36 +0100 Subject: [PATCH 2/6] Support positional arguments in Foxx queries. --- .../Books/Users/Foxx/FoxxQueries.mdpp | 44 ++++++++++++++++++- .../Books/Users/Foxx/FoxxRepository.mdpp | 25 +++++------ js/server/modules/org/arangodb/foxx/query.js | 25 +++++++++-- 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/Documentation/Books/Users/Foxx/FoxxQueries.mdpp b/Documentation/Books/Users/Foxx/FoxxQueries.mdpp index 46ca157f33..ab0f811a67 100644 --- a/Documentation/Books/Users/Foxx/FoxxQueries.mdpp +++ b/Documentation/Books/Users/Foxx/FoxxQueries.mdpp @@ -27,12 +27,13 @@ console.log('usernames:', usernames); Creates a query function that performs the given query and returns the result. -The returned query function optionally takes an object as its argument. If an object is provided, its properties will be used as the query's bind parameters. Note that collection bind parameters need to be prefixed with an at-sign, e.g. `{'@myCollectionVar': 'my_collection_name'}`. +The returned query function optionally takes an object as its argument. If an object is provided, its properties will be used as the query's bind parameters. Any additional arguments will be passed to the transform function (or dropped if no transform function is defined). *Parameter* * *cfg*: an object with the following properties: * *query*: an AQL query string or an ArangoDB Query Builder query object. + * *params* (optional): an array of parameter names. * *context* (optional): an *applicationContext*. * *model* (optional): a *Foxx.Model* that will be applied to the query results. * *defaults* (optional): default values for the query's bind parameters. These can be overridden by passing a value for the same name to the query function. @@ -42,7 +43,9 @@ If *cfg* is a string, it will be used as the value of *cfg.query* instead. If a *context* is specified, the values of all collection bind parameters will be passed through the context's *collectionName* method. -Note that collection bind parameters in AQL need to be referenced with two at-signs instead of one, e.g. `@@myCollectionVar`. +Note that collection bind parameters in AQL need to be referenced with two at-signs instead of one, e.g. `@@myCollectionVar` and their parameter name needs to be prefixed with an at-sign as well, e.g. `{'@myCollectionVar': 'collection_name'}`. + +If *params* is provided, the query function will accept positional arguments instead of an object. If *params* is a string, it will be treated as an array containing that string. If both *model* and *transform* are provided, the *transform* function will be applied to the result array _after_ the results have been converted into model instances. The *transform* function is always passed the entire result array and its return value will be returned by the query function. @@ -62,6 +65,16 @@ var query = Foxx.createQuery('FOR u IN _users RETURN u[@propName]'); var usernames = query({propName: 'user'}); ``` +Using named bind parameters: + +```js +var query = Foxx.createQuery({ + query: 'FOR u IN _users RETURN u[@propName]', + params: ['propName'] +); +var usernames = query('user'); +``` + Using models: ```js @@ -93,3 +106,30 @@ var query = Foxx.createQuery({ }); var user = query(); // first user by username ``` + +Using a transformation with extra arguments: + +```js +var query = Foxx.createQuery({ + query: 'FOR u IN _users SORT u.user ASC RETURN u[@propName]', + transform: function (results, uppercase) { + return uppercase ? results[0].toUpperCase() : results[0].toLowerCase(); + } +}); +query({propName: 'user'}, true); // username of first user in uppercase +query({propName: 'user'}, false); // username of first user in lowercase +``` + +Using a transformation with extra arguments (using positional arguments): + +```js +var query = Foxx.createQuery({ + query: 'FOR u IN _users SORT u.user ASC RETURN u[@propName]', + params: ['propName'], + transform: function (results, uppercase) { + return uppercase ? results[0].toUpperCase() : results[0].toLowerCase(); + } +}); +query('user', true); // username of first user in uppercase +query('user', false); // username of first user in lowercase +``` diff --git a/Documentation/Books/Users/Foxx/FoxxRepository.mdpp b/Documentation/Books/Users/Foxx/FoxxRepository.mdpp index 0a5c2f73ca..afa45efbc1 100644 --- a/Documentation/Books/Users/Foxx/FoxxRepository.mdpp +++ b/Documentation/Books/Users/Foxx/FoxxRepository.mdpp @@ -23,7 +23,7 @@ You can define custom query methods using Foxx.createQuery and Foxx.Repository.e Making a simple query in the repository and using it from the controller: -```javascript +```js // in the repository var Foxx = require("org/arangodb/foxx"); @@ -41,29 +41,31 @@ ctrl.get("/", function(req, res) { It is also possible to supply parameters to the query: -```javascript +```js // in the repository -getPendingItemById: Foxx.createQuery( - 'FOR todo IN my_todos FILTER todo.completed == false FILTER todo._key == @id RETURN todo' -) +getPendingItemById: Foxx.createQuery({ + query: 'FOR todo IN my_todos FILTER todo.completed == false FILTER todo._key == @id RETURN todo', + params: ['id'] +}) // in the controller ctrl.get("/:id", function(req, res) { var id = req.params("id"); - var rv = todosRepository.getPendingItemById({ id: id }); + var rv = todosRepository.getPendingItemById(id); res.json(rv); }); ``` The list of results can also be transformed before returning it from the repository: -```javascript +```js // in the repository getPendingItemById: Foxx.createQuery({ query: 'FOR todo IN my_todos FILTER todo.completed == false FILTER todo._key == @id RETURN todo', - transform: function(results, args) { + params: ['id'], + transform: function(results, extra) { for (var i = 0; i < results.length; i++) { - results[i].extraProperty = args.extra; + results[i].extraProperty = extra; } } }) @@ -72,10 +74,7 @@ getPendingItemById: Foxx.createQuery({ ctrl.get("/:id", function(req, res) { var id = req.params("id"); var extra = req.params("extra"); - var rv = todosRepository.getPendingItemById( - { id: id }, - { extra: extra } - ); + var rv = todosRepository.getPendingItemById(id, extra); res.json(rv); }); ``` diff --git a/js/server/modules/org/arangodb/foxx/query.js b/js/server/modules/org/arangodb/foxx/query.js index 018b9aaa79..f2739e188a 100644 --- a/js/server/modules/org/arangodb/foxx/query.js +++ b/js/server/modules/org/arangodb/foxx/query.js @@ -40,11 +40,20 @@ exports.createQuery = function createQuery (cfg) { } var query = cfg.query, + params = cfg.params, context = cfg.context, Model = cfg.model, defaults = cfg.defaults, transform = cfg.transform; + if (params && !Array.isArray(params)) { + params = [params]; + } + + if (params && !params.each(function (v) {return typeof v === 'string';})) { + throw new Error('Argument names must be a string or an array of strings.'); + } + if (!query || (typeof query !== 'string' && typeof query.toAQL !== 'function')) { throw new Error('Expected query to be a string or a QueryBuilder instance.'); } @@ -61,7 +70,17 @@ exports.createQuery = function createQuery (cfg) { throw new Error('Expected transform to be a function.'); } - return function query(vars, trArgs) { + return function query() { + var args = Array.prototype.slice.call(arguments); + var vars; + if (params) { + vars = {}; + params.forEach(function (name) { + vars[name] = args.shift(); + }); + } else { + vars = args.shift(); + } vars = _.extend({}, defaults, vars); if (context) { _.each(vars, function (value, key) { @@ -76,7 +95,7 @@ exports.createQuery = function createQuery (cfg) { return new Model(data); }); } - - return transform ? transform(result, trArgs) : result; + args.unshift(result); + return transform ? transform.apply(null, args) : result; }; }; From 382747add9cb4aad412b23bab4511ab1214f3ff5 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Wed, 3 Dec 2014 10:49:13 +0100 Subject: [PATCH 3/6] Added example with no query params. --- Documentation/Books/Users/Foxx/FoxxQueries.mdpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/Books/Users/Foxx/FoxxQueries.mdpp b/Documentation/Books/Users/Foxx/FoxxQueries.mdpp index ab0f811a67..8f84f27421 100644 --- a/Documentation/Books/Users/Foxx/FoxxQueries.mdpp +++ b/Documentation/Books/Users/Foxx/FoxxQueries.mdpp @@ -133,3 +133,17 @@ var query = Foxx.createQuery({ query('user', true); // username of first user in uppercase query('user', false); // username of first user in lowercase ``` + +Using a transformation with extra arguments (and no query parameters): + +```js +var query = Foxx.createQuery({ + query: 'FOR u IN _users SORT u.user ASC RETURN u.user', + params: [], + transform: function (results, uppercase) { + return uppercase ? results[0].toUpperCase() : results[0].toLowerCase(); + } +}); +query(true); // username of first user in uppercase +query(false); // username of first user in lowercase +``` From 7a4076da8fecc77493242c31a42c464addf714ac Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Wed, 3 Dec 2014 10:52:47 +0100 Subject: [PATCH 4/6] Also accept `false` instead of an empty array. --- Documentation/Books/Users/Foxx/FoxxQueries.mdpp | 2 +- js/server/modules/org/arangodb/foxx/query.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Documentation/Books/Users/Foxx/FoxxQueries.mdpp b/Documentation/Books/Users/Foxx/FoxxQueries.mdpp index 8f84f27421..f7e7e8a95a 100644 --- a/Documentation/Books/Users/Foxx/FoxxQueries.mdpp +++ b/Documentation/Books/Users/Foxx/FoxxQueries.mdpp @@ -139,7 +139,7 @@ Using a transformation with extra arguments (and no query parameters): ```js var query = Foxx.createQuery({ query: 'FOR u IN _users SORT u.user ASC RETURN u.user', - params: [], + params: false, // an empty array would work, too transform: function (results, uppercase) { return uppercase ? results[0].toUpperCase() : results[0].toLowerCase(); } diff --git a/js/server/modules/org/arangodb/foxx/query.js b/js/server/modules/org/arangodb/foxx/query.js index f2739e188a..7715004368 100644 --- a/js/server/modules/org/arangodb/foxx/query.js +++ b/js/server/modules/org/arangodb/foxx/query.js @@ -46,12 +46,14 @@ exports.createQuery = function createQuery (cfg) { defaults = cfg.defaults, transform = cfg.transform; - if (params && !Array.isArray(params)) { + if (params === false) { + params = []; + } else if (params && !Array.isArray(params)) { params = [params]; } if (params && !params.each(function (v) {return typeof v === 'string';})) { - throw new Error('Argument names must be a string or an array of strings.'); + throw new Error('Argument names must be a string, an array of strings or false.'); } if (!query || (typeof query !== 'string' && typeof query.toAQL !== 'function')) { From 4f39b9e3585ae1c1887fda1d517dee08d9f2a9e2 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Wed, 3 Dec 2014 13:49:03 +0100 Subject: [PATCH 5/6] optimize away `INTO` of `COLLECT` if unused --- Documentation/Books/Users/Aql/Optimizer.mdpp | 2 + UnitTests/Makefile.unittests | 1 + arangod/Aql/ExecutionNode.h | 17 ++ arangod/Aql/Optimizer.cpp | 58 +++--- arangod/Aql/Optimizer.h | 63 ++++--- arangod/Aql/OptimizerRules.cpp | 121 ++++++++---- arangod/Aql/OptimizerRules.h | 32 ++-- .../aql-optimizer-rule-remove-collect-into.js | 172 ++++++++++++++++++ 8 files changed, 358 insertions(+), 108 deletions(-) create mode 100644 js/server/tests/aql-optimizer-rule-remove-collect-into.js diff --git a/Documentation/Books/Users/Aql/Optimizer.mdpp b/Documentation/Books/Users/Aql/Optimizer.mdpp index 31278a6e33..1644b263e8 100644 --- a/Documentation/Books/Users/Aql/Optimizer.mdpp +++ b/Documentation/Books/Users/Aql/Optimizer.mdpp @@ -363,6 +363,8 @@ The following optimizer rules may appear in the `rules` attribute of a plan: optimizations). * `remove-redundant-sorts`: will appear if multiple *SORT* statements can be merged into fewer sorts. +* `remove-collect-into`: will appear if an *INTO* clause was removed from a *COLLECT* + statement because the result of *INTO* is not used. * `interchange-adjacent-enumerations`: will appear if a query contains multiple *FOR* statements whose order were permuted. Permutation of *FOR* statements is performed because it may enable further optimizations by other rules. diff --git a/UnitTests/Makefile.unittests b/UnitTests/Makefile.unittests index 0b6819c7f0..0094040bda 100755 --- a/UnitTests/Makefile.unittests +++ b/UnitTests/Makefile.unittests @@ -560,6 +560,7 @@ SHELL_SERVER_AQL = @top_srcdir@/js/server/tests/aql-arithmetic.js \ @top_srcdir@/js/server/tests/aql-optimizer-rule-interchange-adjacent-enumerations-noncluster.js \ @top_srcdir@/js/server/tests/aql-optimizer-rule-move-calculations-up.js \ @top_srcdir@/js/server/tests/aql-optimizer-rule-move-filters-up.js \ + @top_srcdir@/js/server/tests/aql-optimizer-rule-remove-collect-into.js \ @top_srcdir@/js/server/tests/aql-optimizer-rule-remove-redundant-calculations.js \ @top_srcdir@/js/server/tests/aql-optimizer-rule-remove-redundant-or.js \ @top_srcdir@/js/server/tests/aql-optimizer-rule-remove-redundant-sorts.js \ diff --git a/arangod/Aql/ExecutionNode.h b/arangod/Aql/ExecutionNode.h index 869b8d16bd..597d1e17ba 100644 --- a/arangod/Aql/ExecutionNode.h +++ b/arangod/Aql/ExecutionNode.h @@ -1967,6 +1967,23 @@ namespace triagens { return _outVariable != nullptr; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the out variable +//////////////////////////////////////////////////////////////////////////////// + + Variable const* outVariable () const { + return _outVariable; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief clear the out variable +//////////////////////////////////////////////////////////////////////////////// + + void clearOutVariable () { + TRI_ASSERT(_outVariable != nullptr); + _outVariable = nullptr; + } + //////////////////////////////////////////////////////////////////////////////// /// @brief getVariablesUsedHere //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/Optimizer.cpp b/arangod/Aql/Optimizer.cpp index 1054a4edab..f59829cbbe 100644 --- a/arangod/Aql/Optimizer.cpp +++ b/arangod/Aql/Optimizer.cpp @@ -397,8 +397,8 @@ void Optimizer::setupRules () { // remove redundant sort blocks registerRule("remove-redundant-sorts", - removeRedundantSorts, - removeRedundantSorts_pass2, + removeRedundantSortsRule, + removeRedundantSortsRule_pass2, true); ////////////////////////////////////////////////////////////////////////////// @@ -408,8 +408,8 @@ void Optimizer::setupRules () { ////////////////////////////////////////////////////////////////////////////// registerRule("interchange-adjacent-enumerations", - interchangeAdjacentEnumerations, - interchangeAdjacentEnumerations_pass3, + interchangeAdjacentEnumerationsRule, + interchangeAdjacentEnumerationsRule_pass3, true); ////////////////////////////////////////////////////////////////////////////// @@ -452,8 +452,14 @@ void Optimizer::setupRules () { // remove redundant sort blocks registerRule("remove-redundant-sorts-2", - removeRedundantSorts, - removeRedundantSorts_pass5, + removeRedundantSortsRule, + removeRedundantSortsRule_pass5, + true); + + // remove INTO from COLLECT + registerRule("remove-collect-into", + removeCollectIntoRule, + removeCollectIntoRule_pass5, true); ////////////////////////////////////////////////////////////////////////////// @@ -463,26 +469,26 @@ void Optimizer::setupRules () { // try to replace simple OR conditions with IN registerRule("replace-or-with-in", - replaceOrWithIn, - replaceOrWithIn_pass6, + replaceOrWithInRule, + replaceOrWithInRule_pass6, true); // try to remove redundant OR conditions registerRule("remove-redundant-or", - removeRedundantOr, - removeRedundantOr_pass6, + removeRedundantOrRule, + removeRedundantOrRule_pass6, true); // try to find a filter after an enumerate collection and find an index . . . registerRule("use-index-range", - useIndexRange, - useIndexRange_pass6, + useIndexRangeRule, + useIndexRangeRule_pass6, true); // try to find sort blocks which are superseeded by indexes registerRule("use-index-for-sort", - useIndexForSort, - useIndexForSort_pass6, + useIndexForSortRule, + useIndexForSortRule_pass6, true); #if 0 @@ -497,34 +503,34 @@ void Optimizer::setupRules () { if (ExecutionEngine::isCoordinator()) { // distribute operations in cluster registerRule("scatter-in-cluster", - scatterInCluster, - scatterInCluster_pass10, + scatterInClusterRule, + scatterInClusterRule_pass10, false); registerRule("distribute-in-cluster", - distributeInCluster, - distributeInCluster_pass10, + distributeInClusterRule, + distributeInClusterRule_pass10, false); // distribute operations in cluster registerRule("distribute-filtercalc-to-cluster", - distributeFilternCalcToCluster, - distributeFilternCalcToCluster_pass10, + distributeFilternCalcToClusterRule, + distributeFilternCalcToClusterRule_pass10, true); registerRule("distribute-sort-to-cluster", - distributeSortToCluster, - distributeSortToCluster_pass10, + distributeSortToClusterRule, + distributeSortToClusterRule_pass10, true); registerRule("remove-unnecessary-remote-scatter", - removeUnnecessaryRemoteScatter, - removeUnnecessaryRemoteScatter_pass10, + removeUnnecessaryRemoteScatterRule, + removeUnnecessaryRemoteScatterRule_pass10, true); registerRule("undistribute-remove-after-enum-coll", - undistributeRemoveAfterEnumColl, - undistributeRemoveAfterEnumColl_pass10, + undistributeRemoveAfterEnumCollRule, + undistributeRemoveAfterEnumCollRule_pass10, true); } diff --git a/arangod/Aql/Optimizer.h b/arangod/Aql/Optimizer.h index 454f94dfcd..03daaa5821 100644 --- a/arangod/Aql/Optimizer.h +++ b/arangod/Aql/Optimizer.h @@ -81,37 +81,37 @@ namespace triagens { // "Pass 1": moving nodes "up" (potentially outside loops): ////////////////////////////////////////////////////////////////////////////// - pass1 = 100, + pass1 = 100, // split and-combined filters into multiple smaller filters - splitFiltersRule_pass1 = 110, + splitFiltersRule_pass1 = 110, // move calculations up the dependency chain (to pull them out of // inner loops etc.) - moveCalculationsUpRule_pass1 = 120, + moveCalculationsUpRule_pass1 = 120, // move filters up the dependency chain (to make result sets as small // as possible as early as possible) - moveFiltersUpRule_pass1 = 130, + moveFiltersUpRule_pass1 = 130, // remove calculations that are repeatedly used in a query - removeRedundantCalculationsRule_pass1 = 140, + removeRedundantCalculationsRule_pass1 = 140, ////////////////////////////////////////////////////////////////////////////// /// "Pass 2": try to remove redundant or unnecessary nodes ////////////////////////////////////////////////////////////////////////////// - pass2 = 200, + pass2 = 200, // remove filters from the query that are not necessary at all // filters that are always true will be removed entirely // filters that are always false will be replaced with a NoResults node - removeUnnecessaryFiltersRule_pass2 = 210, + removeUnnecessaryFiltersRule_pass2 = 210, // remove calculations that are never necessary - removeUnnecessaryCalculationsRule_pass2 = 220, + removeUnnecessaryCalculationsRule_pass2 = 220, // remove redundant sort blocks - removeRedundantSorts_pass2 = 230, + removeRedundantSortsRule_pass2 = 230, ////////////////////////////////////////////////////////////////////////////// /// "Pass 3": interchange EnumerateCollection nodes in all possible ways @@ -119,21 +119,21 @@ namespace triagens { /// levels go back to this or lower levels! ////////////////////////////////////////////////////////////////////////////// - pass3 = 500, - interchangeAdjacentEnumerations_pass3 = 510, + pass3 = 500, + interchangeAdjacentEnumerationsRule_pass3 = 510, ////////////////////////////////////////////////////////////////////////////// // "Pass 4": moving nodes "up" (potentially outside loops) (second try): ////////////////////////////////////////////////////////////////////////////// - pass4 = 600, + pass4 = 600, // move calculations up the dependency chain (to pull them out of // inner loops etc.) - moveCalculationsUpRule_pass4 = 610, + moveCalculationsUpRule_pass4 = 610, // move filters up the dependency chain (to make result sets as small // as possible as early as possible) - moveFiltersUpRule_pass4 = 620, + moveFiltersUpRule_pass4 = 620, ////////////////////////////////////////////////////////////////////////////// @@ -143,61 +143,64 @@ namespace triagens { // remove filters from the query that are not necessary at all // filters that are always true will be removed entirely // filters that are always false will be replaced with a NoResults node - pass5 = 700, - removeUnnecessaryFiltersRule_pass5 = 710, + pass5 = 700, + removeUnnecessaryFiltersRule_pass5 = 710, // remove calculations that are never necessary - removeUnnecessaryCalculationsRule_pass5 = 720, + removeUnnecessaryCalculationsRule_pass5 = 720, // remove redundant sort blocks - removeRedundantSorts_pass5 = 730, + removeRedundantSortsRule_pass5 = 730, + + // remove INTO for COLLECT if appropriate + removeCollectIntoRule_pass5 = 740, ////////////////////////////////////////////////////////////////////////////// /// "Pass 6": use indexes if possible for FILTER and/or SORT nodes ////////////////////////////////////////////////////////////////////////////// - pass6 = 800, + pass6 = 800, // replace simple OR conditions with IN - replaceOrWithIn_pass6 = 810, + replaceOrWithInRule_pass6 = 810, // remove redundant OR conditions - removeRedundantOr_pass6 = 820, + removeRedundantOrRule_pass6 = 820, // try to find a filter after an enumerate collection and find an index . . . - useIndexRange_pass6 = 830, + useIndexRangeRule_pass6 = 830, // try to find sort blocks which are superseeded by indexes - useIndexForSort_pass6 = 840, + useIndexForSortRule_pass6 = 840, // try to remove filters covered by index ranges - removeFiltersCoveredByIndex_pass6 = 850, + removeFiltersCoveredByIndexRule_pass6 = 850, ////////////////////////////////////////////////////////////////////////////// /// "Pass 10": final transformations for the cluster ////////////////////////////////////////////////////////////////////////////// // make operations on sharded collections use distribute - distributeInCluster_pass10 = 1000, + distributeInClusterRule_pass10 = 1000, // make operations on sharded collections use scatter / gather / remote - scatterInCluster_pass10 = 1010, + scatterInClusterRule_pass10 = 1010, // move FilterNodes & Calculation nodes inbetween // scatter(remote) <-> gather(remote) so they're // distributed to the cluster nodes. - distributeFilternCalcToCluster_pass10 = 1020, + distributeFilternCalcToClusterRule_pass10 = 1020, // move SortNodes into the distribution. // adjust gathernode to also contain the sort criterions. - distributeSortToCluster_pass10 = 1030, + distributeSortToClusterRule_pass10 = 1030, // try to get rid of a RemoteNode->ScatterNode combination which has // only a SingletonNode and possibly some CalculationNodes as dependencies - removeUnnecessaryRemoteScatter_pass10 = 1040, + removeUnnecessaryRemoteScatterRule_pass10 = 1040, //recognise that a RemoveNode can be moved to the shards - undistributeRemoveAfterEnumColl_pass10 = 1050 + undistributeRemoveAfterEnumCollRule_pass10 = 1050 }; public: diff --git a/arangod/Aql/OptimizerRules.cpp b/arangod/Aql/OptimizerRules.cpp index f2f64e805a..ec824aabf0 100644 --- a/arangod/Aql/OptimizerRules.cpp +++ b/arangod/Aql/OptimizerRules.cpp @@ -53,9 +53,9 @@ using EN = triagens::aql::ExecutionNode; /// - sorts that are covered by earlier sorts will be removed //////////////////////////////////////////////////////////////////////////////// -int triagens::aql::removeRedundantSorts (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::removeRedundantSortsRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { std::vector nodes = plan->findNodesOfType(EN::SORT, true); std::unordered_set toUnlink; @@ -254,6 +254,49 @@ int triagens::aql::removeUnnecessaryFiltersRule (Optimizer* opt, return TRI_ERROR_NO_ERROR; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief remove INTO of a COLLECT if not used +//////////////////////////////////////////////////////////////////////////////// + +int triagens::aql::removeCollectIntoRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { + bool modified = false; + std::unordered_set toUnlink; + // should we enter subqueries?? + std::vector nodes = plan->findNodesOfType(EN::AGGREGATE, true); + + for (auto n : nodes) { + auto collectNode = static_cast(n); + TRI_ASSERT(collectNode != nullptr); + + auto outVariable = collectNode->outVariable(); + + if (outVariable == nullptr) { + // no out variable. nothing to do + continue; + } + + auto varsUsedLater = n->getVarsUsedLater(); + if (varsUsedLater.find(outVariable) != varsUsedLater.end()) { + // outVariable is used later + continue; + } + + // outVariable is not used later. remove it! + collectNode->clearOutVariable(); + modified = true; + } + + if (modified) { + plan->findVarUsage(); + } + + opt->addPlan(plan, rule->level, modified); + + return TRI_ERROR_NO_ERROR; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief move calculations up in the plan /// this rule modifies the plan in place @@ -1220,9 +1263,9 @@ class FilterToEnumCollFinder : public WalkerWorker { /// @brief useIndexRange, try to use an index for filtering //////////////////////////////////////////////////////////////////////////////// -int triagens::aql::useIndexRange (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::useIndexRangeRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { bool modified = false; @@ -1534,9 +1577,9 @@ class SortToIndexNode : public WalkerWorker { } }; -int triagens::aql::useIndexForSort (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::useIndexForSortRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { bool planModified = false; std::vector nodes = plan->findNodesOfType(EN::SORT, true); @@ -1731,9 +1774,9 @@ struct FilterCondition { /// @brief try to remove filters which are covered by indexes //////////////////////////////////////////////////////////////////////////////// -int triagens::aql::removeFiltersCoveredByIndex (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::removeFiltersCoveredByIndexRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { std::unordered_set toUnlink; std::vector&& nodes= plan->findNodesOfType(EN::FILTER, true); @@ -1843,9 +1886,9 @@ static bool nextPermutationTuple (std::vector& data, /// @brief interchange adjacent EnumerateCollectionNodes in all possible ways //////////////////////////////////////////////////////////////////////////////// -int triagens::aql::interchangeAdjacentEnumerations (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::interchangeAdjacentEnumerationsRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { std::vector&& nodes = plan->findNodesOfType(EN::ENUMERATE_COLLECTION, true); @@ -1963,9 +2006,9 @@ int triagens::aql::interchangeAdjacentEnumerations (Optimizer* opt, /// it will change plans in place //////////////////////////////////////////////////////////////////////////////// -int triagens::aql::scatterInCluster (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::scatterInClusterRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { bool wasModified = false; if (ExecutionEngine::isCoordinator()) { @@ -2075,9 +2118,9 @@ int triagens::aql::scatterInCluster (Optimizer* opt, /// it will change plans in place //////////////////////////////////////////////////////////////////////////////// -int triagens::aql::distributeInCluster (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::distributeInClusterRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { bool wasModified = false; if (ExecutionEngine::isCoordinator()) { @@ -2156,9 +2199,9 @@ int triagens::aql::distributeInCluster (Optimizer* opt, /// as small as possible as early as possible //////////////////////////////////////////////////////////////////////////////// -int triagens::aql::distributeFilternCalcToCluster (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::distributeFilternCalcToClusterRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { bool modified = false; std::vector nodes @@ -2249,9 +2292,9 @@ int triagens::aql::distributeFilternCalcToCluster (Optimizer* opt, /// filters are not pushed beyond limits //////////////////////////////////////////////////////////////////////////////// -int triagens::aql::distributeSortToCluster (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::distributeSortToClusterRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { bool modified = false; std::vector nodes @@ -2333,9 +2376,9 @@ int triagens::aql::distributeSortToCluster (Optimizer* opt, /// only a SingletonNode and possibly some CalculationNodes as dependencies //////////////////////////////////////////////////////////////////////////////// -int triagens::aql::removeUnnecessaryRemoteScatter (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::removeUnnecessaryRemoteScatterRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { std::vector nodes = plan->findNodesOfType(EN::REMOTE, true); std::unordered_set toUnlink; @@ -2577,9 +2620,9 @@ class RemoveToEnumCollFinder: public WalkerWorker { /// @brief recognises that a RemoveNode can be moved to the shards. //////////////////////////////////////////////////////////////////////////////// -int triagens::aql::undistributeRemoveAfterEnumColl (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::undistributeRemoveAfterEnumCollRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { std::vector nodes = plan->findNodesOfType(EN::REMOVE, true); std::unordered_set toUnlink; @@ -2780,9 +2823,9 @@ struct OrToInConverter { // same (single) attribute. //////////////////////////////////////////////////////////////////////////////// -int triagens::aql::replaceOrWithIn (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::replaceOrWithInRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { ENTER_BLOCK; std::vector nodes = plan->findNodesOfType(EN::FILTER, true); @@ -2979,9 +3022,9 @@ struct RemoveRedundantOr { } }; -int triagens::aql::removeRedundantOr (Optimizer* opt, - ExecutionPlan* plan, - Optimizer::Rule const* rule) { +int triagens::aql::removeRedundantOrRule (Optimizer* opt, + ExecutionPlan* plan, + Optimizer::Rule const* rule) { ENTER_BLOCK; std::vector nodes = plan->findNodesOfType(EN::FILTER, true); diff --git a/arangod/Aql/OptimizerRules.h b/arangod/Aql/OptimizerRules.h index 6f12d2cb49..ca3c8f0da6 100644 --- a/arangod/Aql/OptimizerRules.h +++ b/arangod/Aql/OptimizerRules.h @@ -45,7 +45,7 @@ namespace triagens { /// - sorts that are covered by earlier sorts will be removed //////////////////////////////////////////////////////////////////////////////// - int removeRedundantSorts (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int removeRedundantSortsRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); //////////////////////////////////////////////////////////////////////////////// /// @brief remove all unnecessary filters @@ -56,6 +56,12 @@ namespace triagens { int removeUnnecessaryFiltersRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); +//////////////////////////////////////////////////////////////////////////////// +/// @brief remove INTO of a COLLECT if not used +//////////////////////////////////////////////////////////////////////////////// + + int removeCollectIntoRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + //////////////////////////////////////////////////////////////////////////////// /// @brief move calculations up in the plan /// this rule modifies the plan in place @@ -97,32 +103,32 @@ namespace triagens { /// @brief prefer IndexRange nodes over EnumerateCollection nodes //////////////////////////////////////////////////////////////////////////////// - int useIndexRange (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int useIndexRangeRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); //////////////////////////////////////////////////////////////////////////////// /// @brief try to use the index for sorting //////////////////////////////////////////////////////////////////////////////// - int useIndexForSort (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int useIndexForSortRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); //////////////////////////////////////////////////////////////////////////////// /// @brief try to remove filters which are covered by indexes //////////////////////////////////////////////////////////////////////////////// - int removeFiltersCoveredByIndex (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int removeFiltersCoveredByIndexRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); //////////////////////////////////////////////////////////////////////////////// /// @brief interchange adjacent EnumerateCollectionNodes in all possible ways //////////////////////////////////////////////////////////////////////////////// - int interchangeAdjacentEnumerations (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int interchangeAdjacentEnumerationsRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); //////////////////////////////////////////////////////////////////////////////// /// @brief scatter operations in cluster - send all incoming rows to all remote /// clients //////////////////////////////////////////////////////////////////////////////// - int scatterInCluster (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int scatterInClusterRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); //////////////////////////////////////////////////////////////////////////////// /// @brief distribute operations in cluster - send each incoming row to every @@ -134,18 +140,18 @@ namespace triagens { /// The collections coll1 and coll2 do not have to be distinct for this. //////////////////////////////////////////////////////////////////////////////// - int distributeInCluster (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int distributeInClusterRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); - int distributeFilternCalcToCluster (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int distributeFilternCalcToClusterRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); - int distributeSortToCluster (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int distributeSortToClusterRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); //////////////////////////////////////////////////////////////////////////////// /// @brief try to get rid of a RemoteNode->ScatterNode combination which has /// only a SingletonNode and possibly some CalculationNodes as dependencies //////////////////////////////////////////////////////////////////////////////// - int removeUnnecessaryRemoteScatter (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int removeUnnecessaryRemoteScatterRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); //////////////////////////////////////////////////////////////////////////////// /// @brief this rule removes Remote-Gather-Scatter/Distribute-Remote nodes from @@ -175,7 +181,7 @@ namespace triagens { /// //////////////////////////////////////////////////////////////////////////////// - int undistributeRemoveAfterEnumColl (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int undistributeRemoveAfterEnumCollRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); //////////////////////////////////////////////////////////////////////////////// /// @brief this rule replaces expressions of the type: @@ -186,9 +192,9 @@ namespace triagens { // same (single) attribute. //////////////////////////////////////////////////////////////////////////////// - int replaceOrWithIn (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int replaceOrWithInRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); - int removeRedundantOr (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); + int removeRedundantOrRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*); } // namespace aql } // namespace triagens diff --git a/js/server/tests/aql-optimizer-rule-remove-collect-into.js b/js/server/tests/aql-optimizer-rule-remove-collect-into.js new file mode 100644 index 0000000000..7fe096d2fd --- /dev/null +++ b/js/server/tests/aql-optimizer-rule-remove-collect-into.js @@ -0,0 +1,172 @@ +/*jshint strict: false, maxlen: 500 */ +/*global require, assertEqual, assertTrue, assertNotEqual, AQL_EXPLAIN, AQL_EXECUTE */ + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tests for optimizer rules +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2010-2012 triagens GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// +/// @author Jan Steemann +/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +var jsunity = require("jsunity"); +var helper = require("org/arangodb/aql-helper"); +var isEqual = helper.isEqual; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite +//////////////////////////////////////////////////////////////////////////////// + +function optimizerRuleTestSuite () { + var ruleName = "remove-collect-into"; + // various choices to control the optimizer: + var paramNone = { optimizer: { rules: [ "-all" ] } }; + var paramEnabled = { optimizer: { rules: [ "-all", "+remove-collect-into", "+" + ruleName ] } }; + var paramDisabled = { optimizer: { rules: [ "+all", "-" + ruleName ] } }; + + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set up +//////////////////////////////////////////////////////////////////////////////// + + setUp : function () { + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tear down +//////////////////////////////////////////////////////////////////////////////// + + tearDown : function () { + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test that rule has no effect when explicitly disabled +//////////////////////////////////////////////////////////////////////////////// + + testRuleDisabled : function () { + var queries = [ + "FOR i IN 1..10 COLLECT a = i INTO group RETURN a", + "FOR i IN 1..10 FOR j IN 1..10 COLLECT a = i, b = j INTO group RETURN a", + "FOR i IN 1..10 FOR j IN 1..10 COLLECT a = i, b = j INTO group RETURN { a: a, b : b }" + ]; + + queries.forEach(function(query) { + var result = AQL_EXPLAIN(query, { }, paramNone); + assertEqual([ ], result.plan.rules); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test that rule has no effect +//////////////////////////////////////////////////////////////////////////////// + + testRuleNoEffect : function () { + var queries = [ + "FOR i IN 1..10 COLLECT a = i RETURN a", + "FOR i IN 1..10 FOR j IN 1..10 COLLECT a = i, b = j RETURN a", + "FOR i IN 1..10 FOR j IN 1..10 COLLECT a = i, b = j INTO group RETURN { a: a, b : b, group: group }" + ]; + + queries.forEach(function(query) { + var result = AQL_EXPLAIN(query, { }, paramEnabled); + assertTrue(result.plan.rules.indexOf(ruleName) === -1, query); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test that rule has an effect +//////////////////////////////////////////////////////////////////////////////// + + testRuleHasEffect : function () { + var queries = [ + "FOR i IN 1..10 COLLECT a = i INTO group RETURN a", + "FOR i IN 1..10 FOR j IN 1..10 COLLECT a = i, b = j INTO group RETURN a", + "FOR i IN 1..10 FOR j IN 1..10 COLLECT a = i, b = j INTO group RETURN { a: a, b : b }" + ]; + + queries.forEach(function(query) { + var result = AQL_EXPLAIN(query, { }, paramEnabled); + assertNotEqual(-1, result.plan.rules.indexOf(ruleName), query); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test generated plans +//////////////////////////////////////////////////////////////////////////////// + + + testPlans : function () { + var plans = [ + [ "FOR i IN 1..10 COLLECT a = i INTO group RETURN a", [ "SingletonNode", "CalculationNode", "EnumerateListNode", "SortNode", "AggregateNode", "ReturnNode" ] ], + [ "FOR i IN 1..10 FOR j IN 1..10 COLLECT a = i, b = j INTO group RETURN a", [ "SingletonNode", "CalculationNode", "EnumerateListNode", "CalculationNode", "EnumerateListNode", "SortNode", "AggregateNode", "ReturnNode" ] ], + [ "FOR i IN 1..10 FOR j IN 1..10 COLLECT a = i, b = j INTO group RETURN { a: a, b : b }", [ "SingletonNode", "CalculationNode", "EnumerateListNode", "CalculationNode", "EnumerateListNode", "SortNode", "AggregateNode", "CalculationNode", "ReturnNode" ] ] + ]; + + plans.forEach(function(plan) { + var result = AQL_EXPLAIN(plan[0], { }, paramEnabled); + assertNotEqual(-1, result.plan.rules.indexOf(ruleName), plan[0]); + assertEqual(plan[1], helper.getCompactPlan(result).map(function(node) { return node.type; }), plan[0]); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test results +//////////////////////////////////////////////////////////////////////////////// + + testResults : function () { + var queries = [ + [ "FOR i IN 1..10 COLLECT a = i INTO group RETURN a", [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] ], + [ "FOR i IN 1..2 FOR j IN 1..2 COLLECT a = i, b = j INTO group RETURN [ a, b ]", [ [ 1, 1 ], [ 1, 2 ], [ 2, 1 ], [ 2, 2 ] ] ] + ]; + + queries.forEach(function(query) { + var planDisabled = AQL_EXPLAIN(query[0], { }, paramDisabled); + var planEnabled = AQL_EXPLAIN(query[0], { }, paramEnabled); + var resultDisabled = AQL_EXECUTE(query[0], { }, paramDisabled).json; + var resultEnabled = AQL_EXECUTE(query[0], { }, paramEnabled).json; + + assertTrue(isEqual(resultDisabled, resultEnabled), query[0]); + + assertEqual(-1, planDisabled.plan.rules.indexOf(ruleName), query[0]); + assertNotEqual(-1, planEnabled.plan.rules.indexOf(ruleName), query[0]); + + assertEqual(resultDisabled, query[1]); + assertEqual(resultEnabled, query[1]); + }); + } + + }; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief executes the test suite +//////////////////////////////////////////////////////////////////////////////// + +jsunity.run(optimizerRuleTestSuite); + +return jsunity.done(); + +// Local Variables: +// mode: outline-minor +// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" +// End: From ac4597b802871160450117fc7b18aa6d15ab9b92 Mon Sep 17 00:00:00 2001 From: Thomas Schmidts Date: Wed, 3 Dec 2014 14:45:09 +0100 Subject: [PATCH 6/6] Fixed #1150. Added Queries to the Summary --- Documentation/Books/Users/Foxx/FoxxRepository.mdpp | 2 ++ Documentation/Books/Users/SUMMARY.md | 1 + 2 files changed, 3 insertions(+) diff --git a/Documentation/Books/Users/Foxx/FoxxRepository.mdpp b/Documentation/Books/Users/Foxx/FoxxRepository.mdpp index 0a5c2f73ca..acb9a39ff5 100644 --- a/Documentation/Books/Users/Foxx/FoxxRepository.mdpp +++ b/Documentation/Books/Users/Foxx/FoxxRepository.mdpp @@ -19,6 +19,8 @@ exports.repository = TodosRepository; You can define custom query methods using Foxx.createQuery and Foxx.Repository.extend. +For more details see the chapter on [Foxx Queries](../Foxx/FoxxQueries.md). + *Examples* Making a simple query in the repository and using it from the controller: diff --git a/Documentation/Books/Users/SUMMARY.md b/Documentation/Books/Users/SUMMARY.md index bc10d32421..fa612f9628 100644 --- a/Documentation/Books/Users/SUMMARY.md +++ b/Documentation/Books/Users/SUMMARY.md @@ -108,6 +108,7 @@ * [FoxxController](Foxx/FoxxController.md) * [FoxxModel](Foxx/FoxxModel.md) * [FoxxRepository](Foxx/FoxxRepository.md) + * [Foxx Queries](Foxx/FoxxQueries.md) * [Deploying Applications](Foxx/DeployingAnApplication.md) * [Developing Applications](Foxx/DevelopingAnApplication.md) * [Dependency Injection](Foxx/FoxxInjection.md)