mirror of https://gitee.com/bigwinds/arangodb
Merge branch 'aql-feature-optimize-or' of https://github.com/triAGENS/ArangoDB into devel
This commit is contained in:
commit
7137647608
|
@ -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 \
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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--\\|/// @\\}\\)"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
Loading…
Reference in New Issue