1
0
Fork 0

Merge branch 'aql-feature-optimize-or' of https://github.com/triAGENS/ArangoDB into devel

This commit is contained in:
Jan Steemann 2014-11-05 11:37:10 +01:00
commit 7137647608
6 changed files with 733 additions and 29 deletions

View File

@ -554,6 +554,7 @@ SHELL_SERVER_AQL = @top_srcdir@/js/server/tests/aql-arithmetic.js \
@top_srcdir@/js/server/tests/aql-optimizer-rule-remove-unnecessary-calculations.js \
@top_srcdir@/js/server/tests/aql-optimizer-rule-remove-unnecessary-filters.js \
@top_srcdir@/js/server/tests/aql-optimizer-rule-use-index-for-sort.js \
@top_srcdir@/js/server/tests/aql-optimizer-rule-replace-or-with-in.js \
@top_srcdir@/js/server/tests/aql-parse.js \
@top_srcdir@/js/server/tests/aql-primary-index-noncluster.js \
@top_srcdir@/js/server/tests/aql-queries-collection.js \

View File

@ -420,7 +420,7 @@ void Optimizer::setupRules () {
moveFiltersUpRule,
moveFiltersUpRule_pass4,
true);
//////////////////////////////////////////////////////////////////////////////
/// "Pass 5": try to remove redundant or unnecessary nodes (second try)
/// use levels between 601 and 699 for this
@ -450,6 +450,12 @@ void Optimizer::setupRules () {
/// "Pass 6": use indexes if possible for FILTER and/or SORT nodes
/// use levels between 701 and 799 for this
//////////////////////////////////////////////////////////////////////////////
// try to replace simple OR conditions with IN
registerRule("replace-OR-with-IN",
replaceORwithIN,
replaceORwithIN_pass6,
true);
// try to find a filter after an enumerate collection and find an index . . .
registerRule("use-index-range",

View File

@ -103,6 +103,7 @@ namespace triagens {
// move filters up the dependency chain (to make result sets as small
// as possible as early as possible)
moveFiltersUpRule_pass4 = 620,
//////////////////////////////////////////////////////////////////////////////
/// "Pass 5": try to remove redundant or unnecessary nodes (second try)
@ -125,11 +126,15 @@ namespace triagens {
//////////////////////////////////////////////////////////////////////////////
pass6 = 800,
// replace simple OR conditions with IN
replaceORwithIN_pass6 = 810,
// try to find a filter after an enumerate collection and find an index . . .
useIndexRange_pass6 = 810,
useIndexRange_pass6 = 820,
// try to find sort blocks which are superseeded by indexes
useIndexForSort_pass6 = 820,
useIndexForSort_pass6 = 830,
//////////////////////////////////////////////////////////////////////////////
/// "Pass 10": final transformations for the cluster

View File

@ -35,6 +35,14 @@ using namespace triagens::aql;
using Json = triagens::basics::Json;
using EN = triagens::aql::ExecutionNode;
//#if 0
#define ENTER_BLOCK try { (void) 0;
#define LEAVE_BLOCK } catch (...) { std::cout << "caught an exception in " << __FUNCTION__ << ", " << __FILE__ << ":" << __LINE__ << "!\n"; throw; }
//#else
//#define ENTER_BLOCK
//#define LEAVE_BLOCK
//#endif
// -----------------------------------------------------------------------------
// --SECTION-- rules for the optimizer
// -----------------------------------------------------------------------------
@ -48,7 +56,7 @@ using EN = triagens::aql::ExecutionNode;
int triagens::aql::removeRedundantSorts (Optimizer* opt,
ExecutionPlan* plan,
Optimizer::Rule const* rule) {
std::vector<ExecutionNode*> nodes = plan->findNodesOfType(triagens::aql::ExecutionNode::SORT, true);
std::vector<ExecutionNode*> nodes = plan->findNodesOfType(EN::SORT, true);
std::unordered_set<ExecutionNode*> toUnlink;
triagens::basics::StringBuffer buffer(TRI_UNKNOWN_MEM_ZONE);
@ -77,7 +85,7 @@ int triagens::aql::removeRedundantSorts (Optimizer* opt,
auto current = stack.back();
stack.pop_back();
if (current->getType() == triagens::aql::ExecutionNode::SORT) {
if (current->getType() == EN::SORT) {
// we found another sort. now check if they are compatible!
auto other = static_cast<SortNode*>(current)->getSortInformation(plan, &buffer);
@ -126,17 +134,17 @@ int triagens::aql::removeRedundantSorts (Optimizer* opt,
}
}
}
else if (current->getType() == triagens::aql::ExecutionNode::FILTER) {
else if (current->getType() == EN::FILTER) {
// ok: a filter does not depend on sort order
}
else if (current->getType() == triagens::aql::ExecutionNode::CALCULATION) {
else if (current->getType() == EN::CALCULATION) {
// ok: a filter does not depend on sort order only if it does not throw
if (current->canThrow()) {
++nodesRelyingOnSort;
}
}
else if (current->getType() == triagens::aql::ExecutionNode::ENUMERATE_LIST ||
current->getType() == triagens::aql::ExecutionNode::ENUMERATE_COLLECTION) {
else if (current->getType() == EN::ENUMERATE_LIST ||
current->getType() == EN::ENUMERATE_COLLECTION) {
// ok, but we cannot remove two different sorts if one of these node types is between them
// example: in the following query, the one sort will be optimized away:
// FOR i IN [ { a: 1 }, { a: 2 } , { a: 3 } ] SORT i.a ASC SORT i.a DESC RETURN i
@ -188,7 +196,7 @@ int triagens::aql::removeUnnecessaryFiltersRule (Optimizer* opt,
bool modified = false;
std::unordered_set<ExecutionNode*> toUnlink;
// should we enter subqueries??
std::vector<ExecutionNode*> nodes = plan->findNodesOfType(triagens::aql::ExecutionNode::FILTER, true);
std::vector<ExecutionNode*> nodes = plan->findNodesOfType(EN::FILTER, true);
for (auto n : nodes) {
// filter nodes always have one input variable
@ -200,7 +208,7 @@ int triagens::aql::removeUnnecessaryFiltersRule (Optimizer* opt,
auto setter = plan->getVarSetBy(variable->id);
if (setter == nullptr ||
setter->getType() != triagens::aql::ExecutionNode::CALCULATION) {
setter->getType() != EN::CALCULATION) {
// filter variable was not introduced by a calculation.
continue;
}
@ -254,7 +262,7 @@ int triagens::aql::removeUnnecessaryFiltersRule (Optimizer* opt,
int triagens::aql::moveCalculationsUpRule (Optimizer* opt,
ExecutionPlan* plan,
Optimizer::Rule const* rule) {
std::vector<ExecutionNode*> nodes = plan->findNodesOfType(triagens::aql::ExecutionNode::CALCULATION, true);
std::vector<ExecutionNode*> nodes = plan->findNodesOfType(EN::CALCULATION, true);
bool modified = false;
for (auto n : nodes) {
@ -333,7 +341,7 @@ int triagens::aql::moveCalculationsUpRule (Optimizer* opt,
int triagens::aql::moveFiltersUpRule (Optimizer* opt,
ExecutionPlan* plan,
Optimizer::Rule const* rule) {
std::vector<ExecutionNode*> nodes = plan->findNodesOfType(triagens::aql::ExecutionNode::FILTER, true);
std::vector<ExecutionNode*> nodes = plan->findNodesOfType(EN::FILTER, true);
bool modified = false;
for (auto n : nodes) {
@ -349,7 +357,7 @@ int triagens::aql::moveFiltersUpRule (Optimizer* opt,
auto current = stack.back();
stack.pop_back();
if (current->getType() == triagens::aql::ExecutionNode::LIMIT) {
if (current->getType() == EN::LIMIT) {
// cannot push a filter beyond a LIMIT node
break;
}
@ -500,7 +508,7 @@ int triagens::aql::removeRedundantCalculationsRule (Optimizer* opt,
std::unordered_map<VariableId, Variable const*> replacements;
std::vector<ExecutionNode*> nodes
= plan->findNodesOfType(triagens::aql::ExecutionNode::CALCULATION, true);
= plan->findNodesOfType(EN::CALCULATION, true);
for (auto n : nodes) {
auto nn = static_cast<CalculationNode*>(n);
@ -535,7 +543,7 @@ int triagens::aql::removeRedundantCalculationsRule (Optimizer* opt,
auto current = stack.back();
stack.pop_back();
if (current->getType() == triagens::aql::ExecutionNode::CALCULATION) {
if (current->getType() == EN::CALCULATION) {
try {
static_cast<CalculationNode*>(current)->expression()->stringify(&buffer);
}
@ -584,7 +592,7 @@ int triagens::aql::removeRedundantCalculationsRule (Optimizer* opt,
}
}
if (current->getType() == triagens::aql::ExecutionNode::AGGREGATE) {
if (current->getType() == EN::AGGREGATE) {
if (static_cast<AggregateNode*>(current)->hasOutVariable()) {
// COLLECT ... INTO is evil (tm): it needs to keep all already defined variables
// we need to abort optimization here
@ -632,14 +640,14 @@ int triagens::aql::removeUnnecessaryCalculationsRule (Optimizer* opt,
ExecutionPlan* plan,
Optimizer::Rule const* rule) {
std::vector<ExecutionNode::NodeType> const types = {
triagens::aql::ExecutionNode::CALCULATION,
triagens::aql::ExecutionNode::SUBQUERY
EN::CALCULATION,
EN::SUBQUERY
};
std::vector<ExecutionNode*> nodes = plan->findNodesOfType(types, true);
std::unordered_set<ExecutionNode*> toUnlink;
for (auto n : nodes) {
if (n->getType() == triagens::aql::ExecutionNode::CALCULATION) {
if (n->getType() == EN::CALCULATION) {
auto nn = static_cast<CalculationNode*>(n);
if (nn->canThrow() ||
@ -983,7 +991,7 @@ class FilterToEnumCollFinder : public WalkerWorker<ExecutionNode> {
auto x = static_cast<Variable*>(node->getData());
auto setter = _plan->getVarSetBy(x->id);
if (setter != nullptr &&
setter->getType() == triagens::aql::ExecutionNode::ENUMERATE_COLLECTION) {
setter->getType() == EN::ENUMERATE_COLLECTION) {
enumCollVar = x;
}
return;
@ -1132,7 +1140,7 @@ int triagens::aql::useIndexRange (Optimizer* opt,
Optimizer::Rule const* rule) {
std::vector<ExecutionNode*> nodes
= plan->findNodesOfType(triagens::aql::ExecutionNode::FILTER, true);
= plan->findNodesOfType(EN::FILTER, true);
for (auto n : nodes) {
auto nn = static_cast<FilterNode*>(n);
@ -1441,7 +1449,7 @@ int triagens::aql::useIndexForSort (Optimizer* opt,
Optimizer::Rule const* rule) {
bool planModified = false;
std::vector<ExecutionNode*> nodes
= plan->findNodesOfType(triagens::aql::ExecutionNode::SORT, true);
= plan->findNodesOfType(EN::SORT, true);
for (auto n : nodes) {
auto thisSortNode = static_cast<SortNode*>(n);
SortAnalysis node(thisSortNode);
@ -1503,7 +1511,7 @@ int triagens::aql::interchangeAdjacentEnumerations (Optimizer* opt,
ExecutionPlan* plan,
Optimizer::Rule const* rule) {
std::vector<ExecutionNode*> nodes
= plan->findNodesOfType(triagens::aql::ExecutionNode::ENUMERATE_COLLECTION,
= plan->findNodesOfType(EN::ENUMERATE_COLLECTION,
true);
std::unordered_set<ExecutionNode*> nodesSet;
for (auto n : nodes) {
@ -1532,7 +1540,7 @@ int triagens::aql::interchangeAdjacentEnumerations (Optimizer* opt,
break;
}
if (deps[0]->getType() !=
triagens::aql::ExecutionNode::ENUMERATE_COLLECTION) {
EN::ENUMERATE_COLLECTION) {
break;
}
nwalker = deps[0];
@ -1818,7 +1826,7 @@ int triagens::aql::distributeFilternCalcToCluster (Optimizer* opt,
bool modified = false;
std::vector<ExecutionNode*> nodes
= plan->findNodesOfType(triagens::aql::ExecutionNode::GATHER, true);
= plan->findNodesOfType(EN::GATHER, true);
for (auto n : nodes) {
@ -1911,7 +1919,7 @@ int triagens::aql::distributeSortToCluster (Optimizer* opt,
bool modified = false;
std::vector<ExecutionNode*> nodes
= plan->findNodesOfType(triagens::aql::ExecutionNode::GATHER, true);
= plan->findNodesOfType(EN::GATHER, true);
for (auto n : nodes) {
auto remoteNodeList = n->getDependencies();
@ -1993,7 +2001,7 @@ int triagens::aql::removeUnnecessaryRemoteScatter (Optimizer* opt,
ExecutionPlan* plan,
Optimizer::Rule const* rule) {
std::vector<ExecutionNode*> nodes
= plan->findNodesOfType(triagens::aql::ExecutionNode::REMOTE, true);
= plan->findNodesOfType(EN::REMOTE, true);
std::unordered_set<ExecutionNode*> toUnlink;
for (auto n : nodes) {
@ -2237,7 +2245,7 @@ int triagens::aql::undistributeRemoveAfterEnumColl (Optimizer* opt,
ExecutionPlan* plan,
Optimizer::Rule const* rule) {
std::vector<ExecutionNode*> nodes
= plan->findNodesOfType(triagens::aql::ExecutionNode::REMOVE, true);
= plan->findNodesOfType(EN::REMOVE, true);
std::unordered_set<ExecutionNode*> toUnlink;
for (auto n : nodes) {
@ -2257,6 +2265,220 @@ int triagens::aql::undistributeRemoveAfterEnumColl (Optimizer* opt,
return TRI_ERROR_NO_ERROR;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief
////////////////////////////////////////////////////////////////////////////////
struct OrToInConverter {
AstNode const* variableNode;
std::string variableName;
std::vector<AstNode const*> valueNodes;
std::vector<AstNode const*> possibleNodes;
std::vector<AstNode const*> orConditions;
std::string getString (AstNode const* node) {
triagens::basics::StringBuffer buffer(TRI_UNKNOWN_MEM_ZONE);
node->append(&buffer, false);
return std::string(buffer.c_str(), buffer.length());
}
AstNode* buildInExpression (Ast* ast) {
// the list of comparison values
auto list = ast->createNodeList();
for (auto x : valueNodes) {
list->addMember(x);
}
// return a new IN operator node
return ast->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_IN,
variableNode->clone(ast),
list);
}
bool flattenOr (AstNode const* node) {
if (node->type == NODE_TYPE_OPERATOR_BINARY_OR) {
return (flattenOr(node->getMember(0)) && flattenOr(node->getMember(1)));
}
if (node->type == NODE_TYPE_OPERATOR_BINARY_EQ) {
orConditions.push_back(node);
return true;
}
if (node->type == NODE_TYPE_VALUE) {
return true;
}
return false;
}
bool findCommonNode (AstNode const* node) {
if (! flattenOr(node)) {
return false;
}
TRI_ASSERT(orConditions.size() > 1);
for (AstNode const* n: orConditions) {
auto lhs = n->getMember(0);
auto rhs = n->getMember(1);
if (lhs->isConstant()) {
variableNode = rhs;
return true;
}
if (rhs->isConstant()) {
variableNode = lhs;
return true;
}
if (rhs->type == NODE_TYPE_FCALL || rhs->type == NODE_TYPE_FCALL_USER) {
variableNode = lhs;
return true;
}
if (lhs->type == NODE_TYPE_FCALL || lhs->type == NODE_TYPE_FCALL_USER) {
variableNode = rhs;
return true;
}
if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS ||
lhs->type == NODE_TYPE_INDEXED_ACCESS) {
if (possibleNodes.size() == 2) {
for (size_t i = 0; i < 2; i++) {
if (getString(lhs) == getString(possibleNodes[i])) {
variableNode = possibleNodes[i];
variableName = getString(variableNode);
return true;
}
}
}
else {
possibleNodes.push_back(lhs);
}
}
if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS ||
rhs->type == NODE_TYPE_INDEXED_ACCESS) {
if (possibleNodes.size() == 2) {
for (size_t i = 0; i < 2; i++) {
if (getString(rhs) == getString(possibleNodes[i])) {
variableNode = possibleNodes[i];
variableName = getString(variableNode);
return true;
}
}
return false;
}
else {
possibleNodes.push_back(rhs);
}
}
}
return false;
}
bool canConvertExpression (AstNode const* node) {
if (node->type == NODE_TYPE_OPERATOR_BINARY_OR) {
return (canConvertExpression(node->getMember(0)) &&
canConvertExpression(node->getMember(1)));
}
if (node->type == NODE_TYPE_OPERATOR_BINARY_EQ) {
auto lhs = node->getMember(0);
auto rhs = node->getMember(1);
if (canConvertExpression(rhs) && ! canConvertExpression(lhs)) {
valueNodes.push_back(lhs);
return true;
}
if (canConvertExpression(lhs) && ! canConvertExpression(rhs)) {
valueNodes.push_back(rhs);
return true;
}
// if canConvertExpression(lhs) and canConvertExpression(rhs), then one of
// the equalities in the OR statement is of the form x == x
// fall-through intentional
}
else if (node->type == NODE_TYPE_REFERENCE ||
node->type == NODE_TYPE_ATTRIBUTE_ACCESS ||
node->type == NODE_TYPE_INDEXED_ACCESS) {
// get a string representation of the node for comparisons
std::string nodeString = getString(node);
return nodeString == getString(variableNode);
} else if (node->isBoolValue()) {
return true;
}
return false;
}
};
int triagens::aql::replaceORwithIN (Optimizer* opt,
ExecutionPlan* plan,
Optimizer::Rule const* rule) {
ENTER_BLOCK;
std::vector<ExecutionNode*> nodes
= plan->findNodesOfType(EN::FILTER, true);
bool modified = false;
for (auto n : nodes) {
auto deps = n->getDependencies();
TRI_ASSERT(deps.size() == 1);
if (deps[0]->getType() != EN::CALCULATION) {
continue;
}
auto fn = static_cast<FilterNode*>(n);
auto cn = static_cast<CalculationNode*>(deps[0]);
auto inVar = fn->getVariablesUsedHere();
auto outVar = cn->getVariablesSetHere();
if (outVar.size() != 1 || outVar[0]->id != inVar[0]->id) {
continue;
}
if (cn->expression()->node()->type != NODE_TYPE_OPERATOR_BINARY_OR) {
continue;
}
OrToInConverter converter;
if (converter.findCommonNode(cn->expression()->node())
&& converter.canConvertExpression(cn->expression()->node())) {
Expression* expr = nullptr;
ExecutionNode* newNode = nullptr;
auto inNode = converter.buildInExpression(plan->getAst());
try {
expr = new Expression(plan->getAst(), inNode);
}
catch (...) {
delete inNode;
throw;
}
try {
newNode = new CalculationNode(plan, plan->nextId(), expr, outVar[0]);
}
catch (...) {
delete expr;
throw;
}
plan->registerNode(newNode);
plan->replaceNode(cn, newNode);
modified = true;
}
}
if (modified) {
plan->findVarUsage();
}
opt->addPlan(plan, rule->level, modified);
return TRI_ERROR_NO_ERROR;
LEAVE_BLOCK;
}
// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|// --SECTION--\\|/// @\\}\\)"

View File

@ -164,6 +164,17 @@ namespace triagens {
////////////////////////////////////////////////////////////////////////////////
int undistributeRemoveAfterEnumColl (Optimizer*, ExecutionPlan*, Optimizer::Rule const*);
////////////////////////////////////////////////////////////////////////////////
/// @brief this rule replaces expressions of the type:
/// x.val == 1 || x.val == 2 || x.val == 3
// with
// x.val IN [1,2,3]
// when the OR conditions are present in the same FILTER node, and refer to the
// same (single) attribute.
////////////////////////////////////////////////////////////////////////////////
int replaceORwithIN (Optimizer*, ExecutionPlan*, Optimizer::Rule const*);
} // namespace aql
} // namespace triagens

View File

@ -0,0 +1,459 @@
/*jshint strict: false, maxlen: 500 */
/*global require, assertEqual, assertTrue, AQL_EXPLAIN, AQL_EXECUTE */
////////////////////////////////////////////////////////////////////////////////
/// @brief tests for Ahuacatl, skiplist index queries
///
/// @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
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var internal = require("internal");
var jsunity = require("jsunity");
var helper = require("org/arangodb/aql-helper");
var getQueryResults = helper.getQueryResults;
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function NewAqlReplaceORWithINTestSuite () {
var replace;
var ruleName = "replace-OR-with-IN";
var isRuleUsed = function (query, params) {
var result = AQL_EXPLAIN(query, params, { optimizer: { rules: [ "-all", "+" + ruleName ] } });
assertTrue(result.plan.rules.indexOf(ruleName) !== -1, query);
result = AQL_EXPLAIN(query, params, { optimizer: { rules: [ "-all" ] } });
assertTrue(result.plan.rules.indexOf(ruleName) === -1, query);
};
var ruleIsNotUsed = function (query, params) {
var result = AQL_EXPLAIN(query, params, { optimizer: { rules: [ "-all", "+" + ruleName ] } });
assertTrue(result.plan.rules.indexOf(ruleName) === -1, query);
};
var executeWithRule = function (query, params) {
return AQL_EXECUTE(query, params, { optimizer: { rules: [ "-all", "+" + ruleName ] } }).json;
};
var executeWithoutRule = function (query, params) {
return AQL_EXECUTE(query, params, { optimizer: { rules: [ "-all" ] } }).json;
};
return {
////////////////////////////////////////////////////////////////////////////////
/// @brief set up
////////////////////////////////////////////////////////////////////////////////
setUp : function () {
internal.db._drop("UnitTestsNewAqlReplaceORWithINTestSuite");
replace = internal.db._create("UnitTestsNewAqlReplaceORWithINTestSuite");
for (var i = 1; i <= 10; ++i) {
replace.save({ "value" : i, x: [i]});
replace.save({"a" : {"b" : i}});
replace.save({"value": i + 10, "bb": i, "cc": 10 - i });
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief tear down
////////////////////////////////////////////////////////////////////////////////
tearDown : function () {
internal.db._drop("UnitTestsNewAqlReplaceORWithINTestSuite");
replace = null;
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test the rule fires for actual values
////////////////////////////////////////////////////////////////////////////////
testFires2Values1 : function () {
var query = "FOR x IN " + replace.name() +
" FILTER x.value == 1 || x.value == 2 SORT x.value RETURN x.value";
isRuleUsed(query, {});
var expected = [ 1, 2 ];
var actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFires2Values2 : function () {
var query = "FOR x IN " + replace.name() +
" FILTER x.a.b == 1 || x.a.b == 2 SORT x.a.b RETURN x.a.b";
isRuleUsed(query, {});
var expected = [ 1, 2 ];
var actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFires4Values1 : function () {
var query = "FOR x IN " + replace.name() + " FILTER x.value == 1 " +
"|| x.value == 2 || x.value == 3 || x.value == 4 SORT x.value RETURN x.value";
isRuleUsed(query, {});
var expected = [ 1, 2, 3, 4];
var actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFiresNoAttributeAccess : function () {
var query =
"FOR x IN " + replace.name() + " FILTER x == 1 || x == 2 RETURN x";
isRuleUsed(query, {});
var expected = [ ];
var actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFiresNoCollection : function () {
var query =
"FOR x in 1..10 FILTER x == 1 || x == 2 SORT x RETURN x";
isRuleUsed(query, {});
var expected = [ 1, 2 ];
var actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFiresBind : function () {
var query =
"FOR v IN " + replace.name()
+ " FILTER v.value == @a || v.value == @b SORT v.value RETURN v.value";
var params = {"a": 1, "b": 2};
isRuleUsed(query, params);
var expected = [ 1, 2 ];
var actual = getQueryResults(query, params);
assertEqual(expected, actual);
assertEqual(executeWithRule(query, params), executeWithoutRule(query, params));
},
testFiresVariables : function () {
var query =
"LET x = 1 LET y = 2 FOR v IN " + replace.name()
+ " FILTER v.value == x || v.value == y SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 1, 2 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFires2AttributeAccesses1 : function () {
var query =
"LET x = {a:1,b:2} FOR v IN " + replace.name()
+ " FILTER v.value == x.a || v.value == x.b SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 1, 2 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFires2AttributeAccesses2 : function () {
var query =
"LET x = {a:1,b:2} FOR v IN " + replace.name()
+ " FILTER x.a == v.value || v.value == x.b SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 1, 2 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFires2AttributeAccesses3 : function () {
var query =
"LET x = {a:1,b:2} FOR v IN " + replace.name()
+ " FILTER x.a == v.value || x.b == v.value SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 1, 2 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFiresMixed1 : function () {
var query =
"LET x = [1,2] FOR v IN " + replace.name()
+ " FILTER x[0] == v.value || x[1] == v.value SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 1, 2 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFiresMixed2 : function () {
var query =
"LET x = [1,2] LET y = {b:2} FOR v IN " + replace.name()
+ " FILTER x[0] == v.value || y.b == v.value SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 1, 2 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFiresSelfReference1 : function () {
var query =
"FOR v IN " + replace.name()
+ " FILTER v.value == v.a.b || v.value == 10 || v.value == 7 SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 7, 10 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFiresSelfReference2 : function () {
var query =
"FOR v IN " + replace.name()
+ " FILTER v.a.b == v.value || v.value == 10 || 7 == v.value SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 7, 10 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFiresAttributeIsList1 : function () {
var query =
"LET x = {a:1,b:2} FOR v IN " + replace.name()
+ " FILTER v.x[0] == x.a || v.x[0] == 3 SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 1, 3 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFiresAttributeIsList2 : function () {
var query =
"LET x = {a:1,b:2} FOR v IN " + replace.name()
+ " FILTER x.a == v.x[0] || v.x[0] == 3 SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 1, 3 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFiresAttributeIsList3 : function () {
var query =
"LET x = {a:1,b:2} FOR v IN " + replace.name()
+ " FILTER x.a == v.x[0] || 3 == v.x[0] SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 1, 3 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFiresAttributeIsList4 : function () {
var query =
"LET x = {a:1,b:2} FOR v IN " + replace.name()
+ " FILTER v.x[0] == x.a || 3 == v.x[0] SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 1, 3 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testFires2Loops: function () {
var query =
"FOR x IN " + replace.name()
+ " FOR y IN " + replace.name()
+ " FILTER x.value == y.bb || x.value == y.cc"
+ " FILTER x.value != null SORT x.value RETURN x.value";
isRuleUsed(query, {});
var expected = [ 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
assertEqual(expected, executeWithoutRule(query, {}));
},
testFiresNonsense1: function () {
var query =
"FOR v in " + replace.name()
+ " FILTER 1 == 2 || v.value == 2 || v.value == 3 SORT v.value RETURN v.value" ;
isRuleUsed(query, {});
var expected = [ 2, 3 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
assertEqual(expected, executeWithoutRule(query, {}));
},
testFiresNonsense2: function () {
var query = "FOR v in " + replace.name() +
" FILTER 1 == 2 || 2 == v.value || v.value == 3 SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 2, 3 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
assertEqual(expected, executeWithoutRule(query, {}));
},
testFiresNonsense3: function () {
var query =
"FOR v in " + replace.name()
+ " FILTER v.value == 2 || 3 == v.value || 1 == 2 SORT v.value RETURN v.value";
isRuleUsed(query, {});
var expected = [ 2, 3 ];
var actual = getQueryResults(query, {});
assertEqual(expected, actual);
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
assertEqual(expected, executeWithoutRule(query, {}));
},
testDudAlwaysTrue: function () {
var query =
"FOR x IN " + replace.name()
+ " FILTER x.value == x.value || x.value == 2 || x.value == 3 SORT x.value RETURN x.value";
ruleIsNotUsed(query, {});
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
},
testDudDifferentAttributes1 : function () {
var query =
"FOR x IN " + replace.name() + " FILTER x.val1 == 1 || x.val2 == 2 RETURN x";
ruleIsNotUsed(query, {});
},
testDudDifferentVariables : function () {
var query =
"FOR y IN " + replace.name() + " FOR x IN " + replace.name()
+ " FILTER x.val1 == 1 || y.val1 == 2 RETURN x";
ruleIsNotUsed(query, {});
},
testDudDifferentAttributes2: function () {
var query =
"FOR x IN " + replace.name() + " FILTER x.val1 == 1 || x == 2 RETURN x";
ruleIsNotUsed(query, {});
},
testDudNoOR1: function () {
var query =
"FOR x IN " + replace.name() + " FILTER x.val1 == 1 RETURN x";
ruleIsNotUsed(query, {});
},
testDudNoOR2: function () {
var query =
"FOR x IN " + replace.name()
+ " FILTER x.val1 == 1 && x.val2 == 2 RETURN x";
ruleIsNotUsed(query, {});
},
testDudNonEquality1: function () {
var query =
"FOR x IN " + replace.name()
+ " FILTER x.val1 > 1 || x.val1 == 2 RETURN x";
ruleIsNotUsed(query, {});
},
testDudNonEquality2: function () {
var query =
"FOR x IN " + replace.name()
+ " FILTER x.val1 == 1 || 2 < x.val1 RETURN x";
ruleIsNotUsed(query, {});
},
testDudAttributeIsList: function () {
var query =
"LET x = {a:1,b:2} FOR v IN " + replace.name()
+ " FILTER v.x[0] == x.a || v.x[1] == 3 SORT v.value RETURN v.value";
ruleIsNotUsed(query, {});
}
};
}
jsunity.run(NewAqlReplaceORWithINTestSuite);
return jsunity.done();