diff --git a/arangod/Aql/ExecutionNode.cpp b/arangod/Aql/ExecutionNode.cpp index 916b27ef0d..3a752a618d 100644 --- a/arangod/Aql/ExecutionNode.cpp +++ b/arangod/Aql/ExecutionNode.cpp @@ -406,10 +406,8 @@ Json ExecutionNode::toJsonHelperGeneric (triagens::basics::Json& nodes, json("parents", parents); } json("id", Json(static_cast(id()))); + json("estimatedCost", Json(_estimatedCost)); - if (_estimatedCost != 0.0) { - json("estimatedCost", Json(_estimatedCost)); - } return json; } diff --git a/arangod/Aql/Query.cpp b/arangod/Aql/Query.cpp index 6865389eeb..48a820733e 100644 --- a/arangod/Aql/Query.cpp +++ b/arangod/Aql/Query.cpp @@ -353,7 +353,7 @@ QueryResult Query::parse () { /// @brief explain an AQL query //////////////////////////////////////////////////////////////////////////////// -QueryResult Query::explain (bool returnAllPlans) { +QueryResult Query::explain () { try { ExecutionPlan* plan; Parser parser(this); @@ -393,14 +393,14 @@ QueryResult Query::explain (bool returnAllPlans) { QueryResult result(TRI_ERROR_NO_ERROR); - if (returnAllPlans) { + if (allPlans()) { triagens::basics::Json out(triagens::basics::Json::List); auto plans = opt.getPlans(); for (auto it : plans) { TRI_ASSERT(it != nullptr); - out.add(it->toJson(TRI_UNKNOWN_MEM_ZONE, true)); + out.add(it->toJson(TRI_UNKNOWN_MEM_ZONE, verbosePlans())); } result.json = out.steal(); @@ -410,7 +410,7 @@ QueryResult Query::explain (bool returnAllPlans) { plan = opt.stealBest(); // Now we own the best one again TRI_ASSERT(plan != nullptr); - result.json = plan->toJson(TRI_UNKNOWN_MEM_ZONE, false).steal(); + result.json = plan->toJson(TRI_UNKNOWN_MEM_ZONE, verbosePlans()).steal(); delete plan; } @@ -502,14 +502,30 @@ char* Query::registerString (std::string const& p, // --SECTION-- private methods // ----------------------------------------------------------------------------- +//////////////////////////////////////////////////////////////////////////////// +/// @brief fetch a boolean value from the options +//////////////////////////////////////////////////////////////////////////////// + +bool Query::getBooleanOption (char const* option, bool defaultValue) const { + if (! TRI_IsArrayJson(_options)) { + return defaultValue; + } + + TRI_json_t const* valueJson = TRI_LookupArrayJson(_options, option); + if (! TRI_IsBooleanJson(valueJson)) { + return defaultValue; + } + + return valueJson->_value._boolean; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief neatly format transaction errors to the user. //////////////////////////////////////////////////////////////////////////////// -QueryResult Query::transactionError (int errorCode, AQL_TRANSACTION_V8 const& trx) +QueryResult Query::transactionError (int errorCode, AQL_TRANSACTION_V8 const& trx) const { - std::string err; - err += std::string(TRI_errno_string(errorCode)); + std::string err(TRI_errno_string(errorCode)); auto detail = trx.getErrorData(); if (detail.size() > 0) { @@ -521,7 +537,6 @@ QueryResult Query::transactionError (int errorCode, AQL_TRANSACTION_V8 const& tr return QueryResult(errorCode, err); } - //////////////////////////////////////////////////////////////////////////////// /// @brief read the "optimizer.rules" section from the options //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/Query.h b/arangod/Aql/Query.h index e687a9a9cc..bcff98011d 100644 --- a/arangod/Aql/Query.h +++ b/arangod/Aql/Query.h @@ -155,6 +155,22 @@ namespace triagens { return _queryLength; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief should we return verbose plans? +//////////////////////////////////////////////////////////////////////////////// + + bool verbosePlans () const { + return getBooleanOption("verbosePlans", false); + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief should we return all plans? +//////////////////////////////////////////////////////////////////////////////// + + bool allPlans () const { + return getBooleanOption("allPlans", false); + } + //////////////////////////////////////////////////////////////////////////////// /// @brief extract a region from the query //////////////////////////////////////////////////////////////////////////////// @@ -185,7 +201,7 @@ namespace triagens { /// @brief explain an AQL query //////////////////////////////////////////////////////////////////////////////// - QueryResult explain (bool); + QueryResult explain (); //////////////////////////////////////////////////////////////////////////////// /// @brief get v8 executor @@ -214,6 +230,13 @@ namespace triagens { // --SECTION-- private methods // ----------------------------------------------------------------------------- +//////////////////////////////////////////////////////////////////////////////// +/// @brief fetch a boolean value from the options +//////////////////////////////////////////////////////////////////////////////// + + bool getBooleanOption (char const*, + bool) const; + //////////////////////////////////////////////////////////////////////////////// /// @brief read the "optimizer.rules" section from the options //////////////////////////////////////////////////////////////////////////////// @@ -224,7 +247,7 @@ namespace triagens { /// @brief neatly format transaction errors to the user. //////////////////////////////////////////////////////////////////////////////// - QueryResult transactionError (int errorCode, AQL_TRANSACTION_V8 const& trx); + QueryResult transactionError (int errorCode, AQL_TRANSACTION_V8 const& trx) const; // ----------------------------------------------------------------------------- // --SECTION-- private variables diff --git a/arangod/V8Server/v8-vocbase.cpp b/arangod/V8Server/v8-vocbase.cpp index 4ffc4afaf6..289cadfe88 100644 --- a/arangod/V8Server/v8-vocbase.cpp +++ b/arangod/V8Server/v8-vocbase.cpp @@ -889,7 +889,6 @@ static v8::Handle JS_ExplainAql (v8::Arguments const& argv) { } } - bool returnAllPlans = false; TRI_json_t* options = nullptr; if (argv.Length() > 2) { @@ -901,18 +900,13 @@ static v8::Handle JS_ExplainAql (v8::Arguments const& argv) { TRI_V8_TYPE_ERROR(scope, "expecting object for "); } - if (argv[2]->ToObject()->Has(TRI_V8_STRING("allPlans"))) { - // should we return all plans? - returnAllPlans = TRI_ObjectToBoolean(argv[2]->ToObject()->Get(TRI_V8_STRING("allPlans"))); - } - options = TRI_ObjectToJson(argv[2]); } // bind parameters will be freed by the query later triagens::aql::Query query(vocbase, queryString.c_str(), queryString.size(), parameters, options); - auto queryResult = query.explain(returnAllPlans); + auto queryResult = query.explain(); if (queryResult.code != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION_FULL(scope, queryResult.code, queryResult.details); @@ -920,7 +914,7 @@ static v8::Handle JS_ExplainAql (v8::Arguments const& argv) { v8::Handle result = v8::Object::New(); if (queryResult.json != nullptr) { - if (returnAllPlans) { + if (query.allPlans()) { result->Set(TRI_V8_STRING("plans"), TRI_ObjectJson(queryResult.json)); } else { diff --git a/js/server/tests/aql-explain.js b/js/server/tests/aql-explain.js new file mode 100644 index 0000000000..ac62af786e --- /dev/null +++ b/js/server/tests/aql-explain.js @@ -0,0 +1,294 @@ +/*global require, exports, assertTrue, assertEqual, AQL_EXECUTE, AQL_EXPLAIN */ +//////////////////////////////////////////////////////////////////////////////// +/// @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 errors = require("internal").errors; +var internal = require("internal"); +var errors = internal.errors; +var db = require("org/arangodb").db; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite +//////////////////////////////////////////////////////////////////////////////// + +function explainSuite () { + var cn = "UnitTestsAhuacatlExplain"; + var c; + + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set up +//////////////////////////////////////////////////////////////////////////////// + + setUp : function () { + db._drop(cn); + c = db._create(cn); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tear down +//////////////////////////////////////////////////////////////////////////////// + + tearDown : function () { + db._drop(cn); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test bind parameters +//////////////////////////////////////////////////////////////////////////////// + + testExplainBindMissing : function () { + var actual; + var query = "RETURN @foo"; + + try { + actual = AQL_EXPLAIN(query); + fail(); + } + catch (err) { + assertEqual(err.errorNum, errors.ERROR_QUERY_BIND_PARAMETER_MISSING.code); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test bind parameters +//////////////////////////////////////////////////////////////////////////////// + + testExplainBindPresent : function () { + var actual; + var query = "RETURN @foo"; + + actual = AQL_EXPLAIN(query, { foo: "bar" }); + assertEqual(3, actual.plan.nodes.length); + assertEqual("SingletonNode", actual.plan.nodes[0].type); + assertEqual("CalculationNode", actual.plan.nodes[1].type); + assertEqual("ReturnNode", actual.plan.nodes[2].type); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test verbosity w/ single plan +//////////////////////////////////////////////////////////////////////////////// + + testExplainVerbosity : function () { + var actual; + var query = "FOR i IN " + cn + " FOR j IN " + cn + " RETURN i"; + + // single plan, no options + actual = AQL_EXPLAIN(query); + assertTrue(actual.hasOwnProperty("plan")); + assertFalse(Array.isArray(actual.plan)); + assertTrue(actual.plan.hasOwnProperty("nodes")); + assertTrue(Array.isArray(actual.plan.nodes)); + assertTrue(actual.plan.hasOwnProperty("rules")); + assertTrue(Array.isArray(actual.plan.rules)); + assertFalse(actual.plan.hasOwnProperty("estimatedCost")); + + actual.plan.nodes.forEach(function(node) { + assertTrue(node.hasOwnProperty("type")); + assertFalse(node.hasOwnProperty("typeID")); // deactivated if not verbose + assertTrue(node.hasOwnProperty("dependencies")); + assertTrue(Array.isArray(node.dependencies)); + assertFalse(node.hasOwnProperty("parents")); // deactivated if not verbose + assertTrue(node.hasOwnProperty("id")); + assertTrue(node.hasOwnProperty("estimatedCost")); + }); + + // single plan, verbose options + actual = AQL_EXPLAIN(query, { }, { verbosePlans: true }); + assertTrue(actual.hasOwnProperty("plan")); + assertFalse(Array.isArray(actual.plan)); + assertTrue(actual.plan.hasOwnProperty("nodes")); + assertTrue(Array.isArray(actual.plan.nodes)); + assertTrue(actual.plan.hasOwnProperty("rules")); + assertTrue(Array.isArray(actual.plan.rules)); + + actual.plan.nodes.forEach(function(node) { + assertTrue(node.hasOwnProperty("type")); + assertTrue(node.hasOwnProperty("typeID")); + assertTrue(node.hasOwnProperty("dependencies")); + assertTrue(Array.isArray(node.dependencies)); + assertTrue(node.hasOwnProperty("parents")); + assertTrue(Array.isArray(node.parents)); + assertTrue(node.hasOwnProperty("id")); + assertTrue(node.hasOwnProperty("estimatedCost")); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test explain w/ a signle plan vs. all plans +//////////////////////////////////////////////////////////////////////////////// + + testExplainAllPlansVsSingle : function () { + var actual; + var query = "FOR i IN " + cn + " FOR j IN " + cn + " RETURN i"; + + // single plan + actual = AQL_EXPLAIN(query, { }, { verbosePlans: true }); + assertTrue(actual.hasOwnProperty("plan")); + assertFalse(actual.hasOwnProperty("plans")); + assertFalse(Array.isArray(actual.plan)); + + assertTrue(actual.plan.hasOwnProperty("nodes")); + assertTrue(Array.isArray(actual.plan.nodes)); + + actual.plan.nodes.forEach(function(node) { + assertTrue(node.hasOwnProperty("type")); + assertTrue(node.hasOwnProperty("typeID")); + assertTrue(node.hasOwnProperty("dependencies")); + assertTrue(Array.isArray(node.dependencies)); + assertTrue(node.hasOwnProperty("parents")); + assertTrue(node.hasOwnProperty("id")); + assertTrue(node.hasOwnProperty("estimatedCost")); + }); + + assertTrue(actual.plan.hasOwnProperty("rules")); + assertTrue(Array.isArray(actual.plan.rules)); + + + // multiple plans + actual = AQL_EXPLAIN(query, { }, { allPlans: true, verbosePlans: true }); + assertFalse(actual.hasOwnProperty("plan")); + assertTrue(actual.hasOwnProperty("plans")); + assertTrue(Array.isArray(actual.plans)); + + actual.plans.forEach(function (plan) { + assertTrue(plan.hasOwnProperty("nodes")); + assertTrue(Array.isArray(plan.nodes)); + + plan.nodes.forEach(function(node) { + assertTrue(node.hasOwnProperty("type")); + assertTrue(node.hasOwnProperty("typeID")); + assertTrue(node.hasOwnProperty("dependencies")); + assertTrue(Array.isArray(node.dependencies)); + assertTrue(node.hasOwnProperty("parents")); + assertTrue(node.hasOwnProperty("id")); + assertTrue(node.hasOwnProperty("estimatedCost")); + }); + + assertTrue(plan.hasOwnProperty("rules")); + assertTrue(Array.isArray(plan.rules)); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test nodes in plan +//////////////////////////////////////////////////////////////////////////////// + + testNodes : function () { + var actual; + var query = "FOR i IN " + cn + " FILTER i.value > 1 LET a = i.value / 2 SORT a DESC COLLECT x = a INTO g RETURN x"; + + actual = AQL_EXPLAIN(query, null, { optimizer: { rules: [ "-all" ] } }); + var nodes = actual.plan.nodes, node; + + node = nodes[0]; + assertEqual("SingletonNode", node.type); + assertEqual([ ], node.dependencies); + assertEqual(1, node.id); + assertEqual(1, node.estimatedCost); + + node = nodes[1]; + assertEqual("EnumerateCollectionNode", node.type); + assertEqual([ 1 ], node.dependencies); + assertEqual(2, node.id); + assertEqual(0, node.estimatedCost); + assertEqual("_system", node.database); + assertEqual(cn, node.collection); + assertEqual("i", node.outVariable.name); + + node = nodes[2]; + assertEqual("CalculationNode", node.type); + assertEqual([ 2 ], node.dependencies); + assertEqual(3, node.id); + assertTrue(node.hasOwnProperty("expression")); + assertFalse(node.canThrow); + var out = node.outVariable.name; + + node = nodes[3]; + assertEqual("FilterNode", node.type); + assertEqual([ 3 ], node.dependencies); + assertEqual(4, node.id); + assertEqual(0, node.estimatedCost); + assertEqual(out, node.inVariable.name); + + node = nodes[4]; + assertEqual("CalculationNode", node.type); + assertEqual([ 4 ], node.dependencies); + assertEqual(5, node.id); + assertTrue(node.hasOwnProperty("expression")); + assertEqual("a", node.outVariable.name); + assertTrue(node.canThrow); + + node = nodes[5]; + assertEqual("SortNode", node.type); + assertEqual([ 5 ], node.dependencies); + assertEqual(6, node.id); + assertEqual(1, node.elements.length); + assertEqual("a", node.elements[0].inVariable.name); + assertFalse(node.stable); + + node = nodes[6]; + assertEqual("SortNode", node.type); + assertEqual([ 6 ], node.dependencies); + assertEqual(7, node.id); + assertEqual(1, node.elements.length); + assertEqual("a", node.elements[0].inVariable.name); + assertTrue(node.stable); + + node = nodes[7]; + assertEqual("AggregateNode", node.type); + assertEqual([ 7 ], node.dependencies); + assertEqual(8, node.id); + assertEqual(1, node.aggregates.length); + assertEqual("a", node.aggregates[0].inVariable.name); + assertEqual("x", node.aggregates[0].outVariable.name); + assertEqual("g", node.outVariable.name); + + node = nodes[8]; + assertEqual("ReturnNode", node.type); + assertEqual([ 8 ], node.dependencies); + assertEqual(9, node.id); + assertEqual("x", node.inVariable.name); + } + + }; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief executes the test suite +//////////////////////////////////////////////////////////////////////////////// + +jsunity.run(explainSuite); + +return jsunity.done(); + +// Local Variables: +// mode: outline-minor +// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" +// End: diff --git a/js/server/tests/aql-optimizer-rule-interchange-adjacent-enumerations.js b/js/server/tests/aql-optimizer-rule-interchange-adjacent-enumerations.js index 6ccdefddbe..8ad2a1a9f7 100644 --- a/js/server/tests/aql-optimizer-rule-interchange-adjacent-enumerations.js +++ b/js/server/tests/aql-optimizer-rule-interchange-adjacent-enumerations.js @@ -85,6 +85,7 @@ function optimizerRuleTestSuite () { var opts = _.clone(paramNone); opts.allPlans = true; + opts.verbosePlans = true; queries.forEach(function(query) { var result = AQL_EXPLAIN(query, { }, opts); @@ -109,6 +110,7 @@ function optimizerRuleTestSuite () { var opts = _.clone(paramEnabled); opts.allPlans = true; + opts.verbosePlans = true; queries.forEach(function(query) { var result = AQL_EXPLAIN(query, { }, opts); @@ -135,6 +137,7 @@ function optimizerRuleTestSuite () { var opts = _.clone(paramEnabled); opts.allPlans = true; + opts.verbosePlans = true; queries.forEach(function(query) { var withRule = 0; @@ -171,6 +174,7 @@ function optimizerRuleTestSuite () { var opts = _.clone(paramEnabled); opts.allPlans = true; + opts.verbosePlans = true; queries.forEach(function(query) { var planDisabled = AQL_EXPLAIN(query[0], { }, paramDisabled);