diff --git a/arangod/Aql/AqlItemBlock.cpp b/arangod/Aql/AqlItemBlock.cpp index cfc16b8cf2..91fc1a7474 100644 --- a/arangod/Aql/AqlItemBlock.cpp +++ b/arangod/Aql/AqlItemBlock.cpp @@ -110,7 +110,21 @@ void AqlItemBlock::shrink (size_t nrItems) { // erase all stored values in the region that we freed for (size_t i = nrItems; i < _nrItems; ++i) { for (RegisterId j = 0; j < _nrRegs; ++j) { - eraseValue(i, j); + AqlValue& a(_data[_nrRegs * i + j]); + if (! a.isEmpty()) { + auto it = _valueCount.find(a); + if (it != _valueCount.end()) { + if (--it->second == 0) { + a.destroy(); + try { + _valueCount.erase(it); + } + catch (...) { + } + } + } + a.erase(); + } } } @@ -131,12 +145,11 @@ void AqlItemBlock::clearRegisters (std::unordered_set& toClear) { auto it = _valueCount.find(a); if (it != _valueCount.end()) { if (--it->second == 0) { + a.destroy(); try { _valueCount.erase(it); } catch (...) { - it->second++; - throw; } } } diff --git a/arangod/Aql/AqlItemBlock.h b/arangod/Aql/AqlItemBlock.h index 62b0071795..136e531c6a 100644 --- a/arangod/Aql/AqlItemBlock.h +++ b/arangod/Aql/AqlItemBlock.h @@ -130,8 +130,6 @@ namespace triagens { _valueCount.erase(it); } catch (...) { - it->second++; - throw; } } } diff --git a/arangod/Aql/AqlValue.cpp b/arangod/Aql/AqlValue.cpp index f5cf576069..efa9cb2576 100644 --- a/arangod/Aql/AqlValue.cpp +++ b/arangod/Aql/AqlValue.cpp @@ -444,7 +444,7 @@ Json AqlValue::toJson (AQL_TRANSACTION_V8* trx, //////////////////////////////////////////////////////////////////////////////// /// @brief extract an attribute value from the AqlValue -/// this will return null if the value is not an array +/// this will return an empty Json if the value is not an array //////////////////////////////////////////////////////////////////////////////// Json AqlValue::extractArrayMember (AQL_TRANSACTION_V8* trx, diff --git a/arangod/Aql/ExecutionBlock.cpp b/arangod/Aql/ExecutionBlock.cpp index a67bfc6eb2..f7301fbfec 100644 --- a/arangod/Aql/ExecutionBlock.cpp +++ b/arangod/Aql/ExecutionBlock.cpp @@ -1023,7 +1023,7 @@ void IndexRangeBlock::readSkiplistIndex () { skiplistOperator = TRI_CreateIndexOperator(TRI_EQ_INDEX_OPERATOR, nullptr, nullptr, parameters.copy().steal(), shaper, nullptr, i, nullptr); } - if (range._low._undefined) { + if (!range._low._undefined) { auto op = range._low.toIndexOperator(false, parameters.copy(), shaper); if (skiplistOperator != nullptr) { skiplistOperator = TRI_CreateIndexOperator(TRI_AND_INDEX_OPERATOR, @@ -1033,7 +1033,7 @@ void IndexRangeBlock::readSkiplistIndex () { skiplistOperator = op; } } - if (range._high._undefined) { + if (!range._high._undefined) { auto op = range._high.toIndexOperator(true, parameters.copy(), shaper); if (skiplistOperator != nullptr) { skiplistOperator = TRI_CreateIndexOperator(TRI_AND_INDEX_OPERATOR, diff --git a/arangod/Aql/ExecutionNode.cpp b/arangod/Aql/ExecutionNode.cpp index eb796943a0..b6dfd4e919 100644 --- a/arangod/Aql/ExecutionNode.cpp +++ b/arangod/Aql/ExecutionNode.cpp @@ -263,7 +263,7 @@ ExecutionNode::CompareIndex (TRI_index_t* idx, return match; } - match.fullmatch = idx->_fields._length == attrs.size(); + match.fullmatch = idx->_fields._length >= attrs.size(); size_t interestingCount = 0; size_t j = 0; @@ -583,11 +583,12 @@ void IndexRangeNode::toJsonHelper (triagens::basics::Json& nodes, TRI_json_t* idxJson = _index->json(_index); if (idxJson != nullptr) { try { - json.set("index", Json(TRI_UNKNOWN_MEM_ZONE, TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, idxJson))); + TRI_json_t* copy = TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, idxJson); + json.set("index", Json(TRI_UNKNOWN_MEM_ZONE, copy)); } catch (...) { } - TRI_Free(TRI_CORE_MEM_ZONE, idxJson); + TRI_FreeJson(TRI_CORE_MEM_ZONE, idxJson); } // And add it: diff --git a/arangod/Aql/ExecutionNode.h b/arangod/Aql/ExecutionNode.h index 0540e79818..697d1341b7 100644 --- a/arangod/Aql/ExecutionNode.h +++ b/arangod/Aql/ExecutionNode.h @@ -107,6 +107,7 @@ namespace triagens { ExecutionNode (size_t id) : _id(id), _estimatedCost(0.0), + _estimatedCostSet(false), _varUsageValid(false) { } @@ -353,8 +354,9 @@ namespace triagens { //////////////////////////////////////////////////////////////////////////////// double getCost () { - if (_estimatedCost == 0.0) { + if (! _estimatedCostSet) { _estimatedCost = estimateCost(); + _estimatedCostSet = true; TRI_ASSERT(_estimatedCost >= 0.0); } return _estimatedCost; @@ -517,10 +519,12 @@ namespace triagens { //////////////////////////////////////////////////////////////////////////////// /// @brief _estimatedCost = 0 if uninitialised and otherwise stores the result -/// of estimateCost() +/// of estimateCost(), the bool indicates if the cost has been set, it starts +/// out as false //////////////////////////////////////////////////////////////////////////////// double _estimatedCost; + bool _estimatedCostSet; //////////////////////////////////////////////////////////////////////////////// /// @brief _varsUsedLater and _varsValid, the former contains those @@ -1424,23 +1428,31 @@ namespace triagens { //////////////////////////////////////////////////////////////////////////////// struct SortInformation { + + enum Match { + unequal, + weSupersede, + otherSupersedes, + allEqual + }; + std::vector> criteria; bool isValid = true; bool isComplex = false; - bool isCoveredBy (SortInformation const& other) { + Match isCoveredBy (SortInformation const& other) { if (! isValid || ! other.isValid) { - return false; + return unequal; } if (isComplex) { - return false; + return unequal; } size_t const n = criteria.size(); for (size_t i = 0; i < n; ++i) { - if (other.criteria.size() < i) { - return false; + if (other.criteria.size() <= i) { + return otherSupersedes; } auto ours = criteria[i]; @@ -1448,16 +1460,18 @@ namespace triagens { if (std::get<2>(ours) != std::get<2>(theirs)) { // sort order is different - return false; + return unequal; } if (std::get<1>(ours) != std::get<1>(theirs)) { // sort criterion is different - return false; + return unequal; } } - - return true; + if (other.criteria.size() > n) + return weSupersede; + else + return allEqual; } }; @@ -1525,7 +1539,12 @@ namespace triagens { double estimateCost () { double depCost = _dependencies.at(0)->getCost(); - return log(depCost) * depCost; + if (depCost <= 2.0) { + return depCost; + } + else { + return log(depCost) * depCost; + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/Expression.cpp b/arangod/Aql/Expression.cpp index 9fb0dbd1bc..31be5fb456 100644 --- a/arangod/Aql/Expression.cpp +++ b/arangod/Aql/Expression.cpp @@ -184,7 +184,8 @@ void Expression::analyzeExpression () { } //////////////////////////////////////////////////////////////////////////////// -/// @brief execute an expression of type SIMPLE +/// @brief execute an expression of type SIMPLE, the convention is that +/// the resulting AqlValue will be destroyed outside eventually //////////////////////////////////////////////////////////////////////////////// AqlValue Expression::executeSimpleExpression (AstNode const* node, @@ -207,6 +208,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, AqlValue result = executeSimpleExpression(member, &myCollection, trx, docColls, argv, startPos, vars, regs); auto j = result.extractArrayMember(trx, myCollection, name); + result.destroy(); return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal())); } @@ -228,6 +230,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, if (result.isList()) { if (index->isNumericValue()) { auto j = result.extractListMember(trx, myCollection, index->getIntValue()); + result.destroy(); return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal())); } else if (index->isStringValue()) { @@ -238,6 +241,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, // stoll() might throw an exception if the string is not a number int64_t position = static_cast(std::stoll(p)); auto j = result.extractListMember(trx, myCollection, position); + result.destroy(); return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal())); } catch (...) { @@ -250,6 +254,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, if (index->isNumericValue()) { std::string const indexString = std::to_string(index->getIntValue()); auto j = result.extractArrayMember(trx, myCollection, indexString.c_str()); + result.destroy(); return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal())); } else if (index->isStringValue()) { @@ -257,10 +262,12 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, TRI_ASSERT(p != nullptr); auto j = result.extractArrayMember(trx, myCollection, p); + result.destroy(); return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal())); } // fall-through to returning null } + result.destroy(); return AqlValue(new Json(Json::Null)); } @@ -276,6 +283,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, AqlValue result = executeSimpleExpression(member, &myCollection, trx, docColls, argv, startPos, vars, regs); list->add(result.toJson(trx, myCollection)); + result.destroy(); } return AqlValue(list); } @@ -300,6 +308,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, AqlValue result = executeSimpleExpression(member, &myCollection, trx, docColls, argv, startPos, vars, regs); resultArray->set(key, result.toJson(trx, myCollection)); + result.destroy(); } return AqlValue(resultArray); } @@ -319,7 +328,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, // save the collection info *collection = docColls[regs[i]]; - return argv[startPos + regs[i]]; + return argv[startPos + regs[i]].clone(); } } // fall-through to exception @@ -344,7 +353,9 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, auto member = node->getMember(0); AqlValue result = executeSimpleExpression(member, &myCollection, trx, docColls, argv, startPos, vars, regs); - return func->implementation(trx, myCollection, result); + auto res2 = func->implementation(trx, myCollection, result); + result.destroy(); + return res2; } THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unhandled type in simple expression"); diff --git a/arangod/Aql/OptimizerRules.cpp b/arangod/Aql/OptimizerRules.cpp index e429c07ed2..d85e82569b 100644 --- a/arangod/Aql/OptimizerRules.cpp +++ b/arangod/Aql/OptimizerRules.cpp @@ -69,7 +69,14 @@ int triagens::aql::removeRedundantSorts (Optimizer* opt, // we found another sort. now check if they are compatible! auto other = static_cast(current)->getSortInformation(plan); - if (sortInfo.isCoveredBy(other)) { + switch (sortInfo.isCoveredBy(other)) { + case triagens::aql::SortInformation::unequal: + break; + case triagens::aql::SortInformation::otherSupersedes: + toUnlink.insert(current); + break; + case triagens::aql::SortInformation::weSupersede: + case triagens::aql::SortInformation::allEqual: // the sort at the start of the pipeline makes the sort at the end // superfluous, so we'll remove it toUnlink.insert(n); @@ -645,6 +652,12 @@ class FilterToEnumCollFinder : public WalkerWorker { buildRangeInfo(node->getMember(0), enumCollVar, attr); buildRangeInfo(node->getMember(1), enumCollVar, attr); } + /* TODO: or isn't implemented yet. + if (node->type == NODE_TYPE_OPERATOR_BINARY_OR) { + buildRangeInfo(node->getMember(0), enumCollVar, attr); + buildRangeInfo(node->getMember(1), enumCollVar, attr); + } + */ attr = ""; enumCollVar = ""; return; @@ -787,13 +800,13 @@ public: /// @brief removes the sortNode and its referenced Calculationnodes from the plan. //////////////////////////////////////////////////////////////////////////////// void removeSortNodeFromPlan (ExecutionPlan *newPlan) { - newPlan->unlinkNode(newPlan->getNodeById(sortNodeID)); - for (auto idToRemove = _sortNodeData.begin(); idToRemove != _sortNodeData.end(); ++idToRemove) { newPlan->unlinkNode(newPlan->getNodeById((*idToRemove)->calculationNodeID)); } + + newPlan->unlinkNode(newPlan->getNodeById(sortNodeID)); } }; @@ -817,7 +830,7 @@ class sortToIndexNode : public WalkerWorker { _plan(plan), _sortNode(Node), _level(level) { - planModified = true; + planModified = false; } //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/Parser.cpp b/arangod/Aql/Parser.cpp index e062f71338..31810a3354 100644 --- a/arangod/Aql/Parser.cpp +++ b/arangod/Aql/Parser.cpp @@ -178,15 +178,35 @@ void Parser::registerParseError (int errorCode, // extract the query string part where the error happened std::string const region(_query->extractRegion(line, column)); + // create a neat pointer to the location of the error. + auto arrowpointer = (char*) malloc (sizeof(char) * (column + 10) ); + size_t i; + for (i = 0; i < (size_t) column; i++) { + arrowpointer[i] = ' '; + } + if (i > 0) { + i --; + arrowpointer[i++] = '^'; + } + + arrowpointer[i++] = '^'; + arrowpointer[i++] = '^'; + arrowpointer[i++] = '\0'; + + // note: line numbers reported by bison/flex start at 1, columns start at 0 char buffer[512]; snprintf(buffer, sizeof(buffer), - "%s near '%s' at position %d:%d", + "%s near '%s' at position %d:%d:\n%s\n%s\n", data, region.c_str(), line, - column + 1); + column + 1, + _query->queryString(), + arrowpointer); + + free(arrowpointer); registerError(errorCode, buffer); } diff --git a/arangod/Aql/RangeInfo.h b/arangod/Aql/RangeInfo.h index 5c0e05d79f..4f314be8a0 100644 --- a/arangod/Aql/RangeInfo.h +++ b/arangod/Aql/RangeInfo.h @@ -84,11 +84,13 @@ namespace triagens { void assign (AstNode const* bound, bool include) { _include = include; _bound = Json(TRI_UNKNOWN_MEM_ZONE, bound->toJson(TRI_UNKNOWN_MEM_ZONE, true)); + _undefined = false; } void assign (RangeInfoBound copy) { _include = copy._include; _bound = copy._bound; + _undefined = false; } Json toJson () const { @@ -154,7 +156,7 @@ namespace triagens { RangeInfo () : _valid(false), _undefined(true) {} RangeInfo (basics::Json const& json) : - _var(basics::JsonHelper::checkAndGetStringValue(json.json(), "var")), + _var(basics::JsonHelper::checkAndGetStringValue(json.json(), "variable")), _attr(basics::JsonHelper::checkAndGetStringValue(json.json(), "attr")), _valid(basics::JsonHelper::checkAndGetBooleanValue(json.json(), "valid")), _undefined(false) { @@ -178,7 +180,7 @@ namespace triagens { Json toJson () { Json item(basics::Json::Array); - item("var", Json(_var))("attr", Json(_attr)); + item("variable", Json(_var))("attr", Json(_attr)); if(!_low._undefined){ item("low", _low.toJson()); } @@ -254,7 +256,7 @@ namespace triagens { for (auto x : _ranges) { for (auto y: x.second){ Json item(Json::Array); - item("var", Json(x.first)) + item("variable", Json(x.first)) ("attribute name", Json(y.first)) ("range info", y.second.toJson()); list(item); diff --git a/js/server/modules/org/arangodb/aql-helper.js b/js/server/modules/org/arangodb/aql-helper.js index 32429d24ca..453896fc28 100644 --- a/js/server/modules/org/arangodb/aql-helper.js +++ b/js/server/modules/org/arangodb/aql-helper.js @@ -425,10 +425,51 @@ function getCompactPlan (explainResult) { return out; } +function findExecutionNodes(plan, nodetype) { + var matches = []; + plan.plan.nodes.forEach(function(node) { + if (node.type === nodetype) { + + matches.push(node); + } + else if (node.type === "SubqueryNode") { + var subPlan = {"plan" : node.subquery}; + matches = matches.concat(findExecutionNodes(subPlan, nodetype)); + } + }); + return matches; +} + +function findReferencedNodes(plan, testNode) { + var matches = []; + if (testNode.elements) { + testNode.elements.forEach(function(element) { + plan.plan.nodes.forEach(function(node) { + if (node.hasOwnProperty("outVariable") && + node.outVariable.id === + element.inVariable.id) { + matches.push(node); + } + }); + }); + } + else { + plan.plan.nodes.forEach(function(node) { + if (node.outVariable.id === testNode.inVariable.id) { + matches.push(node); + } + }); + } + + return matches; +} + + // ----------------------------------------------------------------------------- // --SECTION-- module exports // ----------------------------------------------------------------------------- +exports.isEqual = isEqual; exports.getParseResults = getParseResults; exports.assertParseError = assertParseError; exports.getQueryExplanation = getQueryExplanation; @@ -441,6 +482,8 @@ exports.assertQueryError = assertQueryError; exports.assertQueryError2 = assertQueryError2; exports.getLinearizedPlan = getLinearizedPlan; exports.getCompactPlan = getCompactPlan; +exports.findExecutionNodes = findExecutionNodes; +exports.findReferencedNodes = findReferencedNodes; // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE diff --git a/js/server/tests/aql-optimizer-rule-move-calculations-up.js b/js/server/tests/aql-optimizer-rule-move-calculations-up.js index 10defb7f8b..58e5bb0fa6 100644 --- a/js/server/tests/aql-optimizer-rule-move-calculations-up.js +++ b/js/server/tests/aql-optimizer-rule-move-calculations-up.js @@ -37,7 +37,10 @@ var assertQueryError = helper.assertQueryError2; function optimizerRuleTestSuite () { var ruleName = "move-calculations-up"; - + // various choices to control the optimizer: + var paramNone = { optimizer: { rules: [ "-all" ] } }; + var paramMCU = { optimizer: { rules: [ "-all", "+" + ruleName ] } }; + var paramNoMCU = { optimizer: { rules: [ "+all", "-" + ruleName ] } }; return { //////////////////////////////////////////////////////////////////////////////// @@ -66,7 +69,7 @@ function optimizerRuleTestSuite () { ]; queries.forEach(function(query) { - var result = AQL_EXPLAIN(query, { }, { optimizer: { rules: [ "-all" ] } }); + var result = AQL_EXPLAIN(query, { }, paramNone); assertEqual([ ], result.plan.rules); }); }, @@ -84,7 +87,7 @@ function optimizerRuleTestSuite () { ]; queries.forEach(function(query) { - var result = AQL_EXPLAIN(query, { }, { optimizer: { rules: [ "-all", "+" + ruleName ] } }); + var result = AQL_EXPLAIN(query, { }, paramMCU); assertEqual([ ], result.plan.rules, query); }); }, @@ -104,7 +107,7 @@ function optimizerRuleTestSuite () { ]; queries.forEach(function(query) { - var result = AQL_EXPLAIN(query, { }, { optimizer: { rules: [ "-all", "+" + ruleName ] } }); + var result = AQL_EXPLAIN(query, { }, paramMCU); assertEqual([ ruleName ], result.plan.rules); }); }, @@ -120,7 +123,7 @@ function optimizerRuleTestSuite () { ]; plans.forEach(function(plan) { - var result = AQL_EXPLAIN(plan[0], { }, { optimizer: { rules: [ "-all", "+" + ruleName ] } }); + var result = AQL_EXPLAIN(plan[0], { }, paramMCU); assertEqual([ ruleName ], result.plan.rules, plan[0]); assertEqual(plan[1], helper.getCompactPlan(result).map(function(node) { return node.type; }), plan[0]); }); @@ -139,11 +142,11 @@ function optimizerRuleTestSuite () { ]; queries.forEach(function(query) { - var planDisabled = AQL_EXPLAIN(query[0], { }, { optimizer: { rules: [ "+all", "-" + ruleName ] } }); - var planEnabled = AQL_EXPLAIN(query[0], { }, { optimizer: { rules: [ "-all", "+" + ruleName ] } }); + var planDisabled = AQL_EXPLAIN(query[0], { }, paramNoMCU); + var planEnabled = AQL_EXPLAIN(query[0], { }, paramMCU); - var resultDisabled = AQL_EXECUTE(query[0], { }, { optimizer: { rules: [ "+all", "-" + ruleName ] } }); - var resultEnabled = AQL_EXECUTE(query[0], { }, { optimizer: { rules: [ "-all", "+" + ruleName ] } }); + var resultDisabled = AQL_EXECUTE(query[0], { }, paramNoMCU); + var resultEnabled = AQL_EXECUTE(query[0], { }, paramMCU); assertTrue(planDisabled.plan.rules.indexOf(ruleName) === -1, query[0]); assertTrue(planEnabled.plan.rules.indexOf(ruleName) !== -1, query[0]); diff --git a/js/server/tests/aql-optimizer-rule-remove-unnecessary-calculations.js b/js/server/tests/aql-optimizer-rule-remove-unnecessary-calculations.js new file mode 100644 index 0000000000..0ee6b2a1d2 --- /dev/null +++ b/js/server/tests/aql-optimizer-rule-remove-unnecessary-calculations.js @@ -0,0 +1,392 @@ +/*jslint indent: 2, nomen: true, maxlen: 200, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, assertTrue, assertEqual, AQL_EXECUTE, AQL_EXPLAIN, fail, loopmax */ +//////////////////////////////////////////////////////////////////////////////// +/// @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 helper = require("org/arangodb/aql-helper"); +var getQueryResults = helper.getQueryResults2; +var assertQueryError = helper.assertQueryError2; +var isEqual = helper.isEqual; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite +//////////////////////////////////////////////////////////////////////////////// + +function optimizerRuleTestSuite () { + var ruleName = "remove-unnecessary-calculations"; + // various choices to control the optimizer: + var paramNone = { optimizer: { rules: [ "-all" ] } }; + var paramMCU = { optimizer: { rules: [ "-all", "+" + ruleName ] } }; + var paramNoMCU = { 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 a IN 1 RETURN a + 1", + "FOR a IN 1 RETURN a + 0", + "FOR a IN 1 RETURN a - 1", + "FOR a IN 1 RETURN a * 2", + "FOR a IN 4 RETURN a / 2", + "FOR a IN 4 RETURN a % 3", + "FOR a IN 4 RETURN a == 8", + "FOR a IN 4 RETURN 2", + "FOR a IN [1, 2, 3, 4, 5, 6] RETURN SLICE(a, 4, 1)", + "FOR a IN 17.33 RETURN FLOOR(a)", + "FOR a IN 17.33 RETURN CEIL(a)", + "FOR a IN 17.33 RETURN FLOOR(a + 3)", + "FOR a IN 17.33 RETURN CEIL(a + 3)", + "FOR a IN 17.33 RETURN FLOOR(a / 77)", + "FOR a IN 17.33 RETURN CEIL(a / 77)", + "FOR a IN -12 RETURN TO_BOOL(a)", + "FOR a IN \"-12\" RETURN TO_NUMBER(a)", + "FOR a IN \"-12\" RETURN IS_NUMBER(a)", + "FOR a IN \"-12\" RETURN TO_LIST(a)", + "FOR a IN { \"a\" : null, \"b\" : -63, \"c\" : [ 1, 2 ], \"d\": { \"a\" : \"b\" } } RETURN TO_LIST(a)", + "FOR a IN -12 RETURN ABS(a)", + "FOR a IN -12 RETURN ABS(a + 17)", + "FOR a IN 17.33 RETURN ROUND(a)", + "FOR a IN 17.33 RETURN SQRT(a)", + "FOR a IN -17.33 RETURN SQRT(a)", + "FOR a IN CHAR_LENGTH('äöボカド名üÄÖÜß') return a + 1", + "FOR a IN 7 return a..12", + "FOR a IN [1, 7, 3, 12] RETURN AVERAGE(a)", + "FOR a IN [1, 7, 3, 12] RETURN MEDIAN(a)", + "FOR a IN [1, 7, 3, 12] RETURN MAX(a)", + "FOR a IN [1, 7, 3, 12] RETURN SUM(a)", + "FOR a IN [1, 7, 3, 12, null] RETURN NOT_NULL(a)", + "FOR a IN [1, 7, 3, 12, null] RETURN FIRST_LIST(a)", + "FOR a IN [null, \"not a doc!\"] RETURN FIRST_DOCUMENT(a)", + "FOR a IN [0.75, 0.8] RETURN VARIANCE_SAMPLE(a)", + "FOR a IN [0.75, 0.8] RETURN STDDEV_POPULATION(a)", + "FOR a IN 5 RETURN DATE_DAYOFWEEK(a)", + "FOR a IN 5 RETURN DATE_MONTH(a)", + "FOR a IN 5 RETURN DATE_DAY(a)", + "FOR a IN 5 RETURN DATE_HOUR(a)", + "FOR a IN 5 RETURN DATE_MINUTE(a)", + "FOR a IN 5 RETURN DATE_SECOND(a)", + "FOR a IN 5 RETURN DATE_MILLISECOND(a)", + "FOR a IN 5 RETURN DATE_TIMESTAMP(a)", + "FOR a IN 5 RETURN DATE_ISO8601(a)", + "FOR a IN 1975 RETURN DATE_YEAR(a)", + "LET a = SLEEP(2) RETURN 1" + ]; + + 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 a IN 1 RETURN a + 1", + "FOR a IN 1 RETURN a + 0", + "FOR a IN 1 RETURN a - 1", + "FOR a IN 1 RETURN a * 2", + "FOR a IN 4 RETURN a / 2", + "FOR a IN 4 RETURN a % 3", + "FOR a IN 4 RETURN a == 8", + "FOR a IN 4 RETURN 2", + "FOR a IN [1, 2, 3, 4, 5, 6] RETURN SLICE(a, 4, 1)", + "FOR a IN 17.33 RETURN FLOOR(a)", + "FOR a IN 17.33 RETURN CEIL(a)", + "FOR a IN 17.33 RETURN FLOOR(a + 3)", + "FOR a IN 17.33 RETURN CEIL(a + 3)", + "FOR a IN 17.33 RETURN FLOOR(a / 77)", + "FOR a IN 17.33 RETURN CEIL(a / 77)", + "FOR a IN -12 RETURN TO_BOOL(a)", + "FOR a IN \"-12\" RETURN TO_NUMBER(a)", + "FOR a IN \"-12\" RETURN IS_NUMBER(a)", + "FOR a IN \"-12\" RETURN TO_LIST(a)", + "FOR a IN { \"a\" : null, \"b\" : -63, \"c\" : [ 1, 2 ], \"d\": { \"a\" : \"b\" } } RETURN TO_LIST(a)", + "FOR a IN -12 RETURN ABS(a)", + "FOR a IN -12 RETURN ABS(a + 17)", + "FOR a IN 17.33 RETURN ROUND(a)", + "FOR a IN 17.33 RETURN SQRT(a)", + "FOR a IN -17.33 RETURN SQRT(a)", + "FOR a IN CHAR_LENGTH('äöボカド名üÄÖÜß') return a + 1", + "FOR a IN 7 return a..12", + "FOR a IN [1, 7, 3, 12] RETURN AVERAGE(a)", + "FOR a IN [1, 7, 3, 12] RETURN MEDIAN(a)", + "FOR a IN [1, 7, 3, 12] RETURN MAX(a)", + "FOR a IN [1, 7, 3, 12] RETURN SUM(a)", + "FOR a IN [1, 7, 3, 12, null] RETURN NOT_NULL(a)", + "FOR a IN [1, 7, 3, 12, null] RETURN FIRST_LIST(a)", + "FOR a IN [null, \"not a doc!\"] RETURN FIRST_DOCUMENT(a)", + "FOR a IN [0.75, 0.8] RETURN VARIANCE_SAMPLE(a)", + "FOR a IN [0.75, 0.8] RETURN STDDEV_POPULATION(a)", + "FOR a IN 5 RETURN DATE_DAYOFWEEK(a)", + "FOR a IN 5 RETURN DATE_MONTH(a)", + "FOR a IN 5 RETURN DATE_DAY(a)", + "FOR a IN 5 RETURN DATE_HOUR(a)", + "FOR a IN 5 RETURN DATE_MINUTE(a)", + "FOR a IN 5 RETURN DATE_SECOND(a)", + "FOR a IN 5 RETURN DATE_MILLISECOND(a)", + "FOR a IN 5 RETURN DATE_TIMESTAMP(a)", + "FOR a IN 5 RETURN DATE_ISO8601(a)", + "FOR a IN 1975 RETURN DATE_YEAR(a)", + "LET a = SLEEP(2) RETURN 1" + ]; + + queries.forEach(function(query) { + var result = AQL_EXPLAIN(query, { }, paramMCU); + assertEqual([ ], result.plan.rules, query); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test that rule has an effect +//////////////////////////////////////////////////////////////////////////////// + + testRuleHasEffect : function () { + var queries = [ + "LET a = 1 RETURN a + 1", + "LET a = 1 RETURN a + 0", + "LET a = 1 RETURN a - 1", + "LET a = 1 RETURN a * 2", + "LET a = 4 RETURN a / 2", + "LET a = 4 RETURN a % 3", + "LET a = 4 RETURN a == 8", + "LET a = 4 RETURN 2", + "LET a = [1, 2, 3, 4, 5, 6] RETURN SLICE(a, 4, 1)", + "LET a = 17.33 RETURN FLOOR(a)", + "LET a = 17.33 RETURN CEIL(a)", + "LET a = 17.33 RETURN FLOOR(a + 3)", + "LET a = 17.33 RETURN CEIL(a + 3)", + "LET a = 17.33 RETURN FLOOR(a / 77)", + "LET a = 17.33 RETURN CEIL(a / 77)", + "LET a = -12 RETURN TO_BOOL(a)", + "LET a = \"-12\" RETURN TO_NUMBER(a)", + "LET a = \"-12\" RETURN IS_NUMBER(a)", + "LET a = \"-12\" RETURN TO_LIST(a)", + "LET a = { \"a\" : null, \"b\" : -63, \"c\" : [ 1, 2 ], \"d\": { \"a\" : \"b\" } } RETURN TO_LIST(a)", + "LET a = -12 RETURN ABS(a)", + "LET a = -12 RETURN ABS(a + 17)", + "LET a = 17.33 RETURN ROUND(a)", + "LET a = 17.33 RETURN SQRT(a)", + "LET a = -17.33 RETURN SQRT(a)", + "LET a = CHAR_LENGTH('äöボカド名üÄÖÜß') return a + 1", + "LET a = 7 return a..12", + "LET a = 1 LET b = 2 RETURN [ a, b ]", + "LET a = [1, 7, 3, 12] RETURN AVERAGE(a)", + "LET a = [1, 7, 3, 12] RETURN MEDIAN(a)", + "LET a = [1, 7, 3, 12] RETURN MAX(a)", + "LET a = [1, 7, 3, 12] RETURN SUM(a)", + "LET a = [1, 7, 3, 12, null] RETURN NOT_NULL(a)", + "LET a = [1, 7, 3, 12, null] RETURN FIRST_LIST(a)", + "LET a = [null, \"not a doc!\"] RETURN FIRST_DOCUMENT(a)", + "LET a = [0.75, 0.8] RETURN VARIANCE_SAMPLE(a)", + "LET a = [0.75, 0.8] RETURN STDDEV_POPULATION(a)", + "LET a = 5 RETURN DATE_DAYOFWEEK(a)", + "LET a = 5 RETURN DATE_MONTH(a)", + "LET a = 5 RETURN DATE_DAY(a)", + "LET a = 5 RETURN DATE_HOUR(a)", + "LET a = 5 RETURN DATE_MINUTE(a)", + "LET a = 5 RETURN DATE_SECOND(a)", + "LET a = 5 RETURN DATE_MILLISECOND(a)", + "LET a = 5 RETURN DATE_TIMESTAMP(a)", + "LET a = 5 RETURN DATE_ISO8601(a)", + "LET a = 1975 RETURN DATE_YEAR(a)", + "FOR i IN 1..10 LET a = 1 FILTER i == a RETURN i" +// "FOR i IN 1..10 LET a = i + 1 FILTER i != a RETURN i" + ]; + + queries.forEach(function(query) { + var result = AQL_EXPLAIN(query, { }, paramMCU); + //require("internal").print(result); + assertEqual([ ruleName ], result.plan.rules); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test generated plans +//////////////////////////////////////////////////////////////////////////////// + + + testPlans : function () { + var plans = [ + ["LET a = 1 RETURN a + 1", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 1 RETURN a + 0", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 1 RETURN a - 1", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 1 RETURN a * 2", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 4 RETURN a / 2", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 4 RETURN a % 3", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 4 RETURN a == 8", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 4 RETURN 2", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 2, 3, 4, 5, 6] RETURN SLICE(a, 4, 1)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN FLOOR(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN CEIL(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN FLOOR(a + 3)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN CEIL(a + 3)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN FLOOR(a / 77)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN CEIL(a / 77)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = -12 RETURN TO_BOOL(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = \"-12\" RETURN TO_NUMBER(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = \"-12\" RETURN IS_NUMBER(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = \"-12\" RETURN TO_LIST(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = { \"a\" : null, \"b\" : -63, \"c\" : [ 1, 2 ], \"d\": { \"a\" : \"b\" } } RETURN TO_LIST(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = -12 RETURN ABS(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = -12 RETURN ABS(a + 17)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN ROUND(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN SQRT(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = -17.33 RETURN SQRT(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = CHAR_LENGTH('äöボカド名üÄÖÜß') return a + 1", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 7 return a..12", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 1 LET b = 2 RETURN [ a, b ]", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 7, 3, 12] RETURN AVERAGE(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 7, 3, 12] RETURN MEDIAN(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 7, 3, 12] RETURN MAX(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 7, 3, 12] RETURN SUM(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 7, 3, 12, null] RETURN NOT_NULL(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 7, 3, 12, null] RETURN FIRST_LIST(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [null, \"not a doc!\"] RETURN FIRST_DOCUMENT(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [0.75, 0.8] RETURN VARIANCE_SAMPLE(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [0.75, 0.8] RETURN STDDEV_POPULATION(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_DAYOFWEEK(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_MONTH(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_DAY(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_HOUR(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_MINUTE(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_SECOND(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_MILLISECOND(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_TIMESTAMP(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_ISO8601(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 1975 RETURN DATE_YEAR(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["FOR i IN 1..10 LET a = 1 FILTER i == a RETURN i", ["SingletonNode", "CalculationNode", "EnumerateListNode", "CalculationNode", "FilterNode", "ReturnNode" ]] + ]; + + plans.forEach(function(plan) { + var result = AQL_EXPLAIN(plan[0], { }, paramMCU); + assertEqual([ ruleName ], result.plan.rules, plan[0]); + //require("internal").print(helper.getCompactPlan(result).map(function(node) { return node.type; })); + assertEqual(plan[1], helper.getCompactPlan(result).map(function(node) { return node.type; }), plan[0]); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test results +//////////////////////////////////////////////////////////////////////////////// + + testResults : function () { + var queries = [ + ["LET a = 1 RETURN a + 1", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 1 RETURN a + 0", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 1 RETURN a - 1", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 1 RETURN a * 2", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 4 RETURN a / 2", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 4 RETURN a % 3", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 4 RETURN a == 8", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 4 RETURN 2", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 2, 3, 4, 5, 6] RETURN SLICE(a, 4, 1)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN FLOOR(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN CEIL(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN FLOOR(a + 3)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN CEIL(a + 3)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN FLOOR(a / 77)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN CEIL(a / 77)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = -12 RETURN TO_BOOL(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = \"-12\" RETURN TO_NUMBER(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = \"-12\" RETURN IS_NUMBER(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = \"-12\" RETURN TO_LIST(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = { \"a\" : null, \"b\" : -63, \"c\" : [ 1, 2 ], \"d\": { \"a\" : \"b\" } } RETURN TO_LIST(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = -12 RETURN ABS(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = -12 RETURN ABS(a + 17)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN ROUND(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 17.33 RETURN SQRT(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = -17.33 RETURN SQRT(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = CHAR_LENGTH('äöボカド名üÄÖÜß') return a + 1", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 7 return a..12", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 1 LET b = 2 RETURN [ a, b ]", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 7, 3, 12] RETURN AVERAGE(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 7, 3, 12] RETURN MEDIAN(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 7, 3, 12] RETURN MAX(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 7, 3, 12] RETURN SUM(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 7, 3, 12, null] RETURN NOT_NULL(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [1, 7, 3, 12, null] RETURN FIRST_LIST(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [null, \"not a doc!\"] RETURN FIRST_DOCUMENT(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [0.75, 0.8] RETURN VARIANCE_SAMPLE(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = [0.75, 0.8] RETURN STDDEV_POPULATION(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_DAYOFWEEK(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_MONTH(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_DAY(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_HOUR(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_MINUTE(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_SECOND(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_MILLISECOND(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_TIMESTAMP(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 5 RETURN DATE_ISO8601(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["LET a = 1975 RETURN DATE_YEAR(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]], + ["FOR i IN 1..10 LET a = 1 FILTER i == a RETURN i", ["SingletonNode", "CalculationNode", "EnumerateListNode", "CalculationNode", "FilterNode", "ReturnNode" ]] + ]; + + queries.forEach(function(query) { + var resultDisabled = AQL_EXECUTE(query[0], { }, paramNoMCU).json; + var resultEnabled = AQL_EXECUTE(query[0], { }, paramMCU).json; + + assertTrue(isEqual(resultDisabled, resultEnabled), query[0]); + }); + } + + }; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief executes the test suite +//////////////////////////////////////////////////////////////////////////////// + +jsunity.run(optimizerRuleTestSuite); + +return jsunity.done(); + +// Local Variables: +// mode: outline-minor +// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" +// End: diff --git a/js/server/tests/aql-optimizer-rule-use-index-for-sort.js b/js/server/tests/aql-optimizer-rule-use-index-for-sort.js new file mode 100644 index 0000000000..92401c0fc0 --- /dev/null +++ b/js/server/tests/aql-optimizer-rule-use-index-for-sort.js @@ -0,0 +1,760 @@ +/*jslint indent: 2, nomen: true, maxlen: 200, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, assertTrue, assertEqual, AQL_EXECUTE, AQL_EXPLAIN, fail, loopmax */ + +//////////////////////////////////////////////////////////////////////////////// +/// @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 internal = require("internal"); +var jsunity = require("jsunity"); +var errors = require("internal").errors; +var helper = require("org/arangodb/aql-helper"); +var getQueryResults = helper.getQueryResults2; +var assertQueryError = helper.assertQueryError2; +var isEqual = helper.isEqual; +var findExecutionNodes = helper.findExecutionNodes; +var findReferencedNodes = helper.findReferencedNodes; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite +//////////////////////////////////////////////////////////////////////////////// + +function optimizerRuleTestSuite() { + var ruleName = "use-index-for-sort"; + var secondRuleName = "use-index-range"; + var thirdRuleName = "remove-redundant-sorts"; + var colName = "UnitTestsAqlOptimizer" + ruleName.replace(/-/g, "_"); + var colNameOther = colName + "_XX"; + + // various choices to control the optimizer: + var paramNone = { optimizer: { rules: [ "-all" ] } }; + var paramIFS = { optimizer: { rules: [ "-all", "+" + ruleName ] } }; + var paramIR = { optimizer: { rules: [ "-all", "+" + secondRuleName ] } }; + var paramRS = { optimizer: { rules: [ "-all", "+" + thirdRuleName ] } }; + var paramBoth = { optimizer: { rules: [ "-all", "+" + ruleName, "+" + secondRuleName ] } }; + + var skiplist; + var skiplist2; + var sortArray = function (l, r) { + if (l[0] !== r[0]) { + return l[0] < r[0] ? -1 : 1; + } + if (l[1] !== r[1]) { + return l[1] < r[1] ? -1 : 1; + } + return 0; + }; + var hasSortNode = function (plan) { + assertEqual(findExecutionNodes(plan, "SortNode").length, 1, "Has SortNode"); + }; + var hasNoSortNode = function (plan) { + assertEqual(findExecutionNodes(plan, "SortNode").length, 0, "Has NO SortNode"); + }; + var hasCalculationNodes = function (plan, countXPect) { + assertEqual(findExecutionNodes(plan, "CalculationNode").length, + countXPect, + "Has " + countXPect + "CalculationNode"); + }; + var hasNoCalculationNode = function (plan) { + assertEqual(findExecutionNodes(plan, "CalculationNode").length, 0, "Has NO CalculationNode"); + }; + var hasIndexRangeNode_WithRanges = function (plan, haveRanges) { + var rn = findExecutionNodes(plan, "IndexRangeNode"); + assertEqual(rn.length, 1, "Has IndexRangeNode"); + if (haveRanges) { + assertTrue(rn[0].ranges.length > 0, "Have IndexRangeNode with ranges"); + } + else { + assertEqual(rn[0].ranges.length, 0, "Have IndexRangeNode with NO ranges"); + } + }; + var getRangeAttributes = function (plan) { + var rn = findExecutionNodes(plan, "IndexRangeNode"); + assertEqual(rn.length, 1, "Has IndexRangeNode"); + assertTrue(rn[0].ranges.length > 0, "Have IndexRangeNode with ranges"); + return rn[0].ranges; + }; + var getRangeAttribute = function (rangeAttributes, varcmp, attrcmp, getNth) { + var ret = {}; + rangeAttributes.forEach(function compare(oneRA) { + if ( (oneRA.variable === varcmp) && + (oneRA.attr === attrcmp)) { + getNth --; + if (getNth === 0) { + ret = oneRA; + } + } + + }); + return ret; + }; + var isNodeType = function(node, type) { + assertEqual(node.type, type, "check whether this node is of type "+type); + }; + + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set up +// Datastructure: +// - double index on (a,b)/(f,g) for tests with these +// - single column index on d/j to test sort behaviour without sub-columns +// - non-indexed columns c/h to sort without indices. +// - non-skiplist indexed columns e/j to check whether its not selecting them. +// - join column 'joinme' to intersect both tables. +//////////////////////////////////////////////////////////////////////////////// + + setUp : function () { + var loopto; + if (typeof loopmax === 'undefined') { + loopto = 10; + } + else { + loopto = loopmax; + } + /// require("internal").print("loopto: " + loopto + "\n"); + + internal.db._drop(colName); + skiplist = internal.db._create(colName); + var i, j; + for (j = 1; j <= loopto; ++j) { + for (i = 1; i <= loopto; ++i) { + skiplist.save({ "a" : i, "b": j , "c": j, "d": i, "e": i, "joinme" : "aoeu " + j}); + } + skiplist.save( { "a" : i, "c": j, "d": i, "e": i, "joinme" : "aoeu " + j}); + skiplist.save( { "c": j, "joinme" : "aoeu " + j}); + } + + skiplist.ensureSkiplist("a", "b"); + skiplist.ensureSkiplist("d"); + skiplist.ensureIndex({ type: "hash", fields: [ "c" ], unique: false }); + + skiplist2 = internal.db._create(colNameOther); + for (j = 1; j <= loopto; ++j) { + for (i = 1; i <= loopto; ++i) { + skiplist2.save({ "f" : i, "g": j , "h": j, "i": i, "j": i, "joinme" : "aoeu " + j}); + } + skiplist2.save( { "f" : i, "g": j, "i": i, "j": i, "joinme" : "aoeu " + j}); + skiplist2.save( { "h": j, "joinme" : "aoeu " + j}); + } + skiplist2.ensureSkiplist("f", "g"); + skiplist2.ensureSkiplist("i"); + skiplist2.ensureIndex({ type: "hash", fields: [ "h" ], unique: false }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tear down +//////////////////////////////////////////////////////////////////////////////// + + tearDown : function () { + internal.db._drop(colName); + internal.db._drop(colNameOther); + skiplist = null; + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test that rule has no effect when explicitly disabled +//////////////////////////////////////////////////////////////////////////////// + + testRuleDisabled : function () { +/* + var queries = [ + "LET a = 1 FOR i IN 1..10 FILTER a == 1 RETURN i", + "FOR i IN 1..10 LET a = 25 RETURN i", + "FOR i IN 1..10 LET a = 1 LET b = 2 LET c = 3 FILTER i > 3 LET d = 1 RETURN i" + ]; + + queries.forEach(function(query) { + var result = AQL_EXPLAIN(query, { }, { optimizer: { rules: [ "-all" ] } }); + assertEqual([ ], result.plan.rules); + }); +*/ + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test that rule has no effect +//////////////////////////////////////////////////////////////////////////////// + + testRuleNoEffect : function () { +/* + var queries = [ + + "FOR v IN " + colName + " SORT v.c RETURN [v.a, v.b]", +// todo: we use an index anyways right now. "FOR v IN " + colName + " SORT v.a DESC RETURN [v.a, v.b]",// currently only ASC supported. + "FOR v IN " + colName + " SORT v.b, v.a RETURN [v.a, v.b]", + "FOR v IN " + colName + " SORT v.c RETURN [v.a, v.b]", + "FOR v IN " + colName + " SORT v.a + 1 RETURN [v.a, v.b]", + "FOR v IN " + colName + " SORT CONCAT(TO_STRING(v.a), \"lol\") RETURN [v.a, v.b]", + "FOR v IN " + colName + " FILTER v.a > 2 LIMIT 3 SORT v.a RETURN [v.a, v.b] ", // TODO: limit blocks sort atm. + "FOR v IN " + colName + " FOR w IN " + colNameOther + " SORT v.a RETURN [v.a, v.b]" + ]; + + queries.forEach(function(query) { + +// require("internal").print(query); + var result = AQL_EXPLAIN(query, { }, paramIFS); +// require("internal").print(result); + assertEqual([], result.plan.rules, query); + }); +*/ + }, + +/* +//////////////////////////////////////////////////////////////////////////////// +/// @brief test that rule has an effect +//////////////////////////////////////////////////////////////////////////////// + testRuleHasEffect : function () { + + var queries = [ + + "FOR v IN " + colName + " SORT v.d DESC RETURN [v.d]",// currently only ASC supported, but we use the index range anyways. todo: this may change. + "FOR v IN " + colName + " SORT v.d FILTER v.a > 2 LIMIT 3 RETURN [v.d] ", + "FOR v IN " + colName + " FOR w IN 1..10 SORT v.d RETURN [v.d]", + + "FOR v IN " + colName + " LET x = (FOR w IN " + colNameOther + " RETURN w.f ) SORT v.a RETURN [v.a]" + ]; + var QResults = []; + var i = 0; + queries.forEach(function(query) { + + //require("internal").print(query); + var result = AQL_EXPLAIN(query, { }, paramIFS); + assertEqual([ ruleName ], result.plan.rules, query); + QResults[0] = AQL_EXECUTE(query, { }, paramNone).json; + QResults[1] = AQL_EXECUTE(query, { }, paramIFS ).json; + // require("internal").print(result); + + assertTrue(isEqual(QResults[0], QResults[1]), "Result " + i + " is Equal?"); + i++; + }); + + }, + + + testRuleHasEffectButSortsStill : function () { + + var queries = [ + "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a, v.c RETURN [v.a, v.b, v.c]", + "FOR v IN " + colName + " LET x = (FOR w IN " + colNameOther + " SORT w.j, w.h RETURN w.f ) SORT v.a RETURN [v.a]" + ]; + var QResults = []; + var i = 0; + queries.forEach(function(query) { +// require("internal").print(query); + var result = AQL_EXPLAIN(query, { }, paramIFS); +// require("internal").print(result); + assertEqual([ ruleName ], result.plan.rules); + hasIndexRangeNode_WithRanges(result, false); + hasSortNode(result); + QResults[0] = AQL_EXECUTE(query, { }, paramNone).json; + QResults[1] = AQL_EXECUTE(query, { }, paramIFS ).json; + assertTrue(isEqual(QResults[0], QResults[1]), "Result " + i + " is Equal?"); + i++; + }); + + }, +*/ + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test generated plans +//////////////////////////////////////////////////////////////////////////////// +/* + testPlans : function () { + var plans = [ + + [ "FOR i IN 1..10 LET a = 1 RETURN i", [ "SingletonNode", "CalculationNode", "CalculationNode", "EnumerateListNode", "ReturnNode" ] ], + [ "FOR i IN 1..10 FILTER i == 1 LET a = 1 RETURN i", [ "SingletonNode", "CalculationNode", "CalculationNode", "EnumerateListNode", "CalculationNode", "FilterNode", "ReturnNode" ] ] + ]; + + plans.forEach(function(plan) { + var result = AQL_EXPLAIN(plan[0], { }, paramIFS); + assertEqual([ ruleName ], result.plan.rules, 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 LET a = 1 RETURN i", [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] ], + [ "FOR i IN 1..10 LET a = 1 RETURN a", [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] ], + [ "FOR i IN 1..10 FILTER i == 1 LET a = 1 RETURN i", [ 1 ] ], + [ "FOR i IN 1..10 FILTER i == 1 LET a = 1 RETURN a", [ 1 ] ], + ]; + + queries.forEach(function(query) { + var planDisabled = AQL_EXPLAIN(query[0], { }, { optimizer: { rules: [ "+all", "-" + ruleName ] } }); + var planEnabled = AQL_EXPLAIN(query[0], { }, paramIFS); + + var resultDisabled = AQL_EXECUTE(query[0], { }, { optimizer: { rules: [ "+all", "-" + ruleName ] } }); + var resultEnabled = AQL_EXECUTE(query[0], { }, paramIFS); + + assertTrue(planDisabled.plan.rules.indexOf(ruleName) === -1, query[0]); + assertTrue(planEnabled.plan.rules.indexOf(ruleName) !== -1, query[0]); + + assertEqual(resultDisabled.json, query[1], query[0]); + assertEqual(resultEnabled.json, query[1], query[0]); + }); + }, +*/ +/* +// ----------------------------------------------------------------------------- +// --SECTION-- sortToIndexRange +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief this sort is replaceable by an index. +//////////////////////////////////////////////////////////////////////////////// + testSortIndexable: function () { + + var query = "FOR v IN " + colName + " SORT v.a RETURN [v.a, v.b]"; + + var XPresult; + var QResults=[]; + + // we have to re-sort here, because of the index has one more sort criteria. + QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray); + + // -> use-index-for-sort alone. + XPresult = AQL_EXPLAIN(query, { }, paramNone); + + XPresult = AQL_EXPLAIN(query, { }, paramIFS); + QResults[1] = AQL_EXECUTE(query, { }, paramIFS).json; + // our rule should have been applied. + assertEqual([ ruleName ], XPresult.plan.rules); + // The sortnode and its calculation node should have been removed. + hasNoSortNode(XPresult); + hasCalculationNodes(XPresult, 1); + // The IndexRangeNode created by this rule is simple; it shouldn't have ranges. + hasIndexRangeNode_WithRanges(XPresult, false); + + assertTrue(isEqual(QResults[0], QResults[1]), "Query results are equal?"); + + }, + + + + testSortMoreThanIndexed: function () { + + var query = "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a, v.c RETURN [v.a, v.b, v.c]"; + // no index can be used for v.c -> sort has to remain in place! + var XPresult; + var QResults=[]; + var i; + + // the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity. + QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray); + + + // -> use-index-for-sort alone. + QResults[1] = AQL_EXECUTE(query, { }, paramIFS).json; + XPresult = AQL_EXPLAIN(query, { }, paramIFS); + // our rule should be there. + assertEqual([ ruleName ], XPresult.plan.rules); + // The sortnode and its calculation node should have been removed. +// require("internal").print(XPresult); + + hasSortNode(XPresult); + hasCalculationNodes(XPresult, 4); + // The IndexRangeNode created by this rule is simple; it shouldn't have ranges. + hasIndexRangeNode_WithRanges(XPresult, false); + + // -> combined use-index-for-sort and use-index-range + // use-index-range superseedes use-index-for-sort + QResults[2] = AQL_EXECUTE(query, { }, paramBoth).json; + XPresult = AQL_EXPLAIN(query, { }, paramBoth); + + assertEqual([ secondRuleName ], XPresult.plan.rules.sort()); + // The sortnode and its calculation node should not have been removed. + hasSortNode(XPresult); + hasCalculationNodes(XPresult, 4); + // The IndexRangeNode created by this rule should be more clever, it knows the ranges. + hasIndexRangeNode_WithRanges(XPresult, true); + + // -> use-index-range alone. + QResults[3] = AQL_EXECUTE(query, { }, paramIR).json; + XPresult = AQL_EXPLAIN(query, { }, paramIR); + assertEqual([ secondRuleName ], XPresult.plan.rules); + // the sortnode and its calculation node should be there. + + hasSortNode(XPresult); + hasCalculationNodes(XPresult,4); + // we should be able to find exactly one sortnode property - its a Calculation node. + var sortProperty = findReferencedNodes(XPresult, findExecutionNodes(XPresult, "SortNode")[0]); + assertEqual(sortProperty.length, 2); + assertEqual(sortProperty[0].type, "CalculationNode"); + assertEqual(sortProperty[1].type, "CalculationNode"); + // The IndexRangeNode created by this rule should be more clever, it knows the ranges. + hasIndexRangeNode_WithRanges(XPresult, true); + + for (i = 1; i < 4; i++) { + assertTrue(isEqual(QResults[0], QResults[i]), "Result " + i + " is Equal?"); + } + }, + + testRangeSuperseedsSort: function () { + + var query = "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a RETURN [v.a, v.b, v.c]"; + + var XPresult; + var QResults=[]; + var i; + + // the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity. + QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray); + + // -> use-index-for-sort alone. + QResults[1] = AQL_EXECUTE(query, { }, paramIFS).json; + XPresult = AQL_EXPLAIN(query, { }, paramIFS); + // our rule should be there. + assertEqual([ ruleName ], XPresult.plan.rules); + // The sortnode and its calculation node should have been removed. + hasNoSortNode(XPresult); + hasCalculationNodes(XPresult, 2); + // The IndexRangeNode created by this rule is simple; it shouldn't have ranges. + hasIndexRangeNode_WithRanges(XPresult, false); + + // -> combined use-index-for-sort and use-index-range + QResults[2] = AQL_EXECUTE(query, { }, paramBoth).json; + XPresult = AQL_EXPLAIN(query, { }, paramBoth); + assertEqual([ secondRuleName, ruleName ].sort(), XPresult.plan.rules.sort()); + // The sortnode and its calculation node should have been removed. + hasNoSortNode(XPresult); + hasCalculationNodes(XPresult, 2); + // The IndexRangeNode created by this rule should be more clever, it knows the ranges. + hasIndexRangeNode_WithRanges(XPresult, true); + + // -> use-index-range alone. + QResults[3] = AQL_EXECUTE(query, { }, paramIR).json; + XPresult = AQL_EXPLAIN(query, { }, paramIR); + assertEqual([ secondRuleName ], XPresult.plan.rules); + // the sortnode and its calculation node should be there. + hasSortNode(XPresult); + hasCalculationNodes(XPresult, 3); + // we should be able to find exactly one sortnode property - its a Calculation node. + var sortProperty = findReferencedNodes(XPresult, findExecutionNodes(XPresult, "SortNode")[0]); + assertEqual(sortProperty.length, 1); + isNodeType(sortProperty[0], "CalculationNode"); + // The IndexRangeNode created by this rule should be more clever, it knows the ranges. + hasIndexRangeNode_WithRanges(XPresult, true); + + + for (i = 1; i < 4; i++) { + assertTrue(isEqual(QResults[0], QResults[i]), "Result " + i + " is Equal?"); + } + + }, + +*/ +// ----------------------------------------------------------------------------- +// --SECTION-- toIndexRange +// ----------------------------------------------------------------------------- +/* + testRangeEquals: function () { + + var query = "FOR v IN " + colName + " FILTER v.a == 1 RETURN [v.a, v.b, v.c]"; + + var XPresult; + var QResults=[]; + var i; + + // the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity. + QResults[0] = AQL_EXECUTE(query, { }, paramNone).json; + + // -> use-index-range alone. + QResults[1] = AQL_EXECUTE(query, { }, paramIR).json; + XPresult = AQL_EXPLAIN(query, { }, paramIR); + assertEqual([ secondRuleName ], XPresult.plan.rules); + // the sortnode and its calculation node should be there. + hasCalculationNodes(XPresult, 2); + + // The IndexRangeNode created by this rule should be more clever, it knows the ranges. + var RAs = getRangeAttributes(XPresult); + //require("internal").print(RAs); + var first = getRangeAttribute(RAs, "v", "a", 1); + + //require("internal").print(first); + assertEqual(first.low.bound.vType, "int", "Type is int"); + assertEqual(first.high.bound.vType, "int", "Type is int"); + assertEqual(first.low.bound.value, first.high.bound.value); + + for (i = 1; i < 2; i++) { + assertTrue(isEqual(QResults[0].sort(sortArray), QResults[i]), "Result " + i + " is Equal?"); + } + + }, + + + testRangeLessThen: function () { + var query = "FOR v IN " + colName + " FILTER v.a < 5 RETURN [v.a, v.b]"; + + var XPresult; + var QResults=[]; + + // the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity. + QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray); + + // -> use-index-range alone. + QResults[1] = AQL_EXECUTE(query, { }, paramIR).json; + + XPresult = AQL_EXPLAIN(query, { }, paramIR); + assertEqual([ secondRuleName ], XPresult.plan.rules); + // the sortnode and its calculation node should be there. + hasCalculationNodes(XPresult, 2); + + // The IndexRangeNode created by this rule should be more clever, it knows the ranges. + var RAs = getRangeAttributes(XPresult); + var first = getRangeAttribute(RAs, "v", "a", 1); + assertEqual(first.high.bound.vType, "int", "Type is int"); + assertEqual(first.high.bound.value, 5, "proper value was set"); + + assertTrue(isEqual(QResults[0], QResults[1]), "Results are Equal?"); + }, +*/ + testRangeGreaterThen: function () { +/* + TODO: can skiplist do a range? + var query = "FOR v IN " + colName + " FILTER v.a < 5 RETURN [v.a, v.b, v.c]"; + var query = "FOR v IN " + colName + " FILTER v.a > 1 && v.a < 5 RETURN [v.a, v.b, v.c]"; + var query = "FOR v IN " + colName + " FILTER v.c > 1 && v.c < 5 RETURN [v.a, v.b, v.c]"; +*/ + var query = "FOR v IN " + colName + " FILTER v.a > 5 RETURN [v.a, v.b]"; +/* + var XPresult; + var QResults=[]; + + // the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity. + QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray); + + // -> use-index-range alone. + QResults[1] = AQL_EXECUTE(query, { }, paramIR).json; + + XPresult = AQL_EXPLAIN(query, { }, paramIR); + assertEqual([ secondRuleName ], XPresult.plan.rules); + // the sortnode and its calculation node should be there. + hasCalculationNodes(XPresult, 2); + + // The IndexRangeNode created by this rule should be more clever, it knows the ranges. + var RAs = getRangeAttributes(XPresult); +// require("internal").print(RAs); + var first = getRangeAttribute(RAs, "v", "a", 1); + +// require("internal").print(first); + assertEqual(first.low.bound.vType, "int", "Type is int"); + assertEqual(first.low.bound.value, 5, "proper value was set"); + + assertTrue(isEqual(QResults[0], QResults[1]), "Results are Equal?"); +*/ + }, + + testRangeBandpass: function () { + var query = "FOR v IN " + colName + " FILTER v.a > 4 && v.a < 10 RETURN [v.a, v.b]"; +/* + var XPresult; + var QResults=[]; + + // the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity. + QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray); + + // -> use-index-range alone. + QResults[1] = AQL_EXECUTE(query, { }, paramIR).json; + + XPresult = AQL_EXPLAIN(query, { }, paramIR); + assertEqual([ secondRuleName ], XPresult.plan.rules); + // the sortnode and its calculation node should be there. + hasCalculationNodes(XPresult, 2); + + // The IndexRangeNode created by this rule should be more clever, it knows the ranges. + var RAs = getRangeAttributes(XPresult); +// require("internal").print(RAs); + var first = getRangeAttribute(RAs, "v", "a", 1); + +// require("internal").print(first); + assertEqual(first.low.bound.vType, "int", "Type is int"); + assertEqual(first.low.bound.value, 4, "proper value was set"); + assertEqual(first.high.bound.vType, "int", "Type is int"); + assertEqual(first.high.bound.value, 10, "proper value was set"); + + assertTrue(isEqual(QResults[0], QResults[1]), "Results are Equal?"); + */ + }, + + testRangeBandpassInvalid: function () { +/* TODO: this doesn't do anything. should it simply flush that range since its empty? or even raise? + var query = "FOR v IN " + colName + " FILTER v.a > 7 && v.a < 4 RETURN [v.a, v.b]"; + + var XPresult; + var QResults=[]; + + // the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity. + QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray); + + // -> use-index-range alone. + QResults[1] = AQL_EXECUTE(query, { }, paramIR).json; + + + XPresult = AQL_EXPLAIN(query, { }, paramIR); + require("internal").print(XPresult); + assertEqual([ secondRuleName ], XPresult.plan.rules); + // the sortnode and its calculation node should be there. + hasCalculationNodes(XPresult, 2); + + // The IndexRangeNode created by this rule should be more clever, it knows the ranges. + var RAs = getRangeAttributes(XPresult); +// require("internal").print(RAs); + var first = getRangeAttribute(RAs, "v", "a", 1); + + require("internal").print(first); +// require("internal").print(first); + assertEqual(first.low.bound.vType, "int", "Type is int"); + assertEqual(first.low.bound.value, 4, "proper value was set"); + assertEqual(first.high.bound.vType, "int", "Type is int"); + assertEqual(first.high.bound.value, 10, "proper value was set"); + + assertTrue(isEqual(QResults[0], QResults[1]), "Results are Equal?"); +*/ + }, + + + testRangeBandstop: function () { +/* TODO: OR isn't implemented + var query = "FOR v IN " + colName + " FILTER v.a < 5 || v.a > 10 RETURN [v.a, v.b]"; + + var XPresult; + var QResults=[]; + + // the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity. + QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray); + + // -> use-index-range alone. + QResults[1] = AQL_EXECUTE(query, { }, paramIR).json; + + XPresult = AQL_EXPLAIN(query, { }, paramIR); + assertEqual([ secondRuleName ], XPresult.plan.rules); + // the sortnode and its calculation node should be there. + hasCalculationNodes(XPresult, 2); + + // The IndexRangeNode created by this rule should be more clever, it knows the ranges. + var RAs = getRangeAttributes(XPresult); + require("internal").print(RAs); + var first = getRangeAttribute(RAs, "v", "a", 1); + + require("internal").print(first); + assertEqual(first.low.bound.vType, "int", "Type is int"); + assertEqual(first.low.bound.value, 5, "proper value was set"); + + require("internal").print(QResults[0]); + require("internal").print(QResults[1]); + assertTrue(isEqual(QResults[0], QResults[1]), "Results are Equal?"); + */ + }, + + testMultiRangeBandpass: function () { +/* TODO: OR isn't implemented + var query = "FOR v IN " + colName + " FILTER ((v.a > 3 && v.a < 5) || (v.a > 4 && v.a < 7)) RETURN [v.a, v.b]"; + + var XPresult; + var QResults=[]; + + // the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity. + QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray); + + // -> use-index-range alone. + QResults[1] = AQL_EXECUTE(query, { }, paramIR).json; + + XPresult = AQL_EXPLAIN(query, { }, paramIR); + assertEqual([ secondRuleName ], XPresult.plan.rules); + // the sortnode and its calculation node should be there. + hasCalculationNodes(XPresult, 2); + + // The IndexRangeNode created by this rule should be more clever, it knows the ranges. + var RAs = getRangeAttributes(XPresult); +// require("internal").print(RAs); + var first = getRangeAttribute(RAs, "v", "a", 1); + +// require("internal").print(first); + assertEqual(first.low.bound.vType, "int", "Type is int"); + assertEqual(first.low.bound.value, 4, "proper value was set"); + assertEqual(first.high.bound.vType, "int", "Type is int"); + assertEqual(first.high.bound.value, 10, "proper value was set"); + + assertTrue(isEqual(QResults[0], QResults[1]), "Results are Equal?"); +*/ + }, + +// ----------------------------------------------------------------------------- +// --SECTION-- toIndexRange +// ----------------------------------------------------------------------------- + testDSRuleHasEffect : function () { + + var queries = [ + + "FOR v IN " + colName + " SORT v.c SORT v.c RETURN [v.a, v.b]", + "FOR v IN " + colName + " SORT v.c SORT v.c , v.d RETURN [v.a, v.b]", + "FOR v IN " + colName + " SORT v.c, v.d SORT v.c RETURN [v.a, v.b]", + "FOR v IN " + colName + " SORT v.c SORT v.c, v.d SORT v.c RETURN [v.a, v.b]", + "FOR v IN " + colName + " SORT v.c, v.d SORT v.c SORT v.c, v.d SORT v.c RETURN [v.a, v.b]", + + "FOR v IN " + colName + " SORT v.c FILTER v.c > 3 SORT v.c RETURN [v.a, v.b]" +/* +// todo: we use an index anyways right now. "FOR v IN " + colName + " SORT v.a DESC RETURN [v.a, v.b]",// currently only ASC supported. + "FOR v IN " + colName + " SORT v.b, v.a RETURN [v.a, v.b]", + "FOR v IN " + colName + " SORT v.c RETURN [v.a, v.b]", + "FOR v IN " + colName + " SORT v.a + 1 RETURN [v.a, v.b]", + "FOR v IN " + colName + " SORT CONCAT(TO_STRING(v.a), \"lol\") RETURN [v.a, v.b]", + "FOR v IN " + colName + " FILTER v.a > 2 LIMIT 3 SORT v.a RETURN [v.a, v.b] ", // TODO: limit blocks sort atm. + "FOR v IN " + colName + " FOR w IN " + colNameOther + " SORT v.a RETURN [v.a, v.b]" +*/ + ]; + + queries.forEach(function(query) { + +// require("internal").print(query); + var result = AQL_EXPLAIN(query, { }, paramRS); +// require("internal").print(result); + assertEqual([thirdRuleName], result.plan.rules, query); + }); + } +//*/ + }; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief executes the test suite +//////////////////////////////////////////////////////////////////////////////// + +jsunity.run(optimizerRuleTestSuite); + +return jsunity.done(); + +// Local Variables: +// mode: outline-minor +// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" +// End: diff --git a/lib/Utilities/ReadlineShell.cpp b/lib/Utilities/ReadlineShell.cpp index c88a39e5ef..2b42a3d8c8 100644 --- a/lib/Utilities/ReadlineShell.cpp +++ b/lib/Utilities/ReadlineShell.cpp @@ -186,6 +186,10 @@ bool ReadlineShell::close() { bool res = writeHistory(); + clear_history(); + HIST_ENTRY** hist = history_list(); + free(hist); + #ifndef __APPLE__ // reset state of the terminal to what it was before readline() rl_cleanup_after_signal(); @@ -225,7 +229,8 @@ void ReadlineShell::addHistory(char const* str) { if (current_history()) { do { if (strcmp(current_history()->line, str) == 0) { - remove_history(where_history()); + HIST_ENTRY* e = remove_history(where_history()); + free_history_entry(e); break; } }