diff --git a/arangod/Aql/AstNode.h b/arangod/Aql/AstNode.h index 516523d8a9..8f190d9c0b 100644 --- a/arangod/Aql/AstNode.h +++ b/arangod/Aql/AstNode.h @@ -667,7 +667,10 @@ namespace triagens { //////////////////////////////////////////////////////////////////////////////// inline AstNode* getMemberUnchecked (size_t i) const throw() { - return members.at(i); +#ifdef TRI_ENABLE_MAINTAINER_MODE + TRI_ASSERT(i < members.size()); +#endif + return members[i]; } //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/Condition.cpp b/arangod/Aql/Condition.cpp index 73789b1c1b..d06a454001 100644 --- a/arangod/Aql/Condition.cpp +++ b/arangod/Aql/Condition.cpp @@ -426,50 +426,58 @@ ConditionPart::ConditionPartCompareResult const ConditionPart::ResultsTable[3][7 } }; -void Condition::optimize (ExecutionPlan* plan) { - typedef std::vector> UsagePositionType; - typedef std::unordered_map AttributeUsageType; - typedef std::unordered_map VariableUsageType; - - auto storeAttributeAccess = [] (VariableUsageType& variableUsage, AstNode const* node, size_t position, AttributeSideType side) { - std::pair> result; - - if (! node->isAttributeAccessForVariable(result)) { - return; - } - auto variable = result.first; +//////////////////////////////////////////////////////////////////////////////// +/// @brief registers an attribute access for a particular (collection) variable +//////////////////////////////////////////////////////////////////////////////// - if (variable != nullptr) { - auto it = variableUsage.find(variable); +void Condition::storeAttributeAccess (VariableUsageType& variableUsage, + AstNode const* node, + size_t position, + AttributeSideType side) { - if (it == variableUsage.end()) { - // nothing recorded yet for variable - it = variableUsage.emplace(variable, AttributeUsageType()).first; - } - - std::string attributeName; - TRI_AttributeNamesToString(result.second, attributeName, false); - - auto it2 = (*it).second.find(attributeName); + std::pair> result; - if (it2 == (*it).second.end()) { - // nothing recorded yet for attribute name in this variable - it2 = (*it).second.emplace(attributeName, UsagePositionType()).first; - } - - auto& dst = (*it2).second; + if (! node->isAttributeAccessForVariable(result)) { + return; + } + auto variable = result.first; - if (! dst.empty() && dst.back().first == position) { - // already have this attribute for this variable. can happen in case a condition refers to itself (e.g. a.x == a.x) - // in this case, we won't optimize it - dst.erase(dst.begin() + dst.size() - 1); - } - else { - dst.emplace_back(position, side); - } + if (variable != nullptr) { + auto it = variableUsage.find(variable); + + if (it == variableUsage.end()) { + // nothing recorded yet for variable + it = variableUsage.emplace(variable, AttributeUsageType()).first; } - }; - + + std::string attributeName; + TRI_AttributeNamesToString(result.second, attributeName, false); + + auto it2 = (*it).second.find(attributeName); + + if (it2 == (*it).second.end()) { + // nothing recorded yet for attribute name in this variable + it2 = (*it).second.emplace(attributeName, UsagePositionType()).first; + } + + auto& dst = (*it2).second; + + if (! dst.empty() && dst.back().first == position) { + // already have this attribute for this variable. can happen in case a condition refers to itself (e.g. a.x == a.x) + // in this case, we won't optimize it + dst.erase(dst.begin() + dst.size() - 1); + } + else { + dst.emplace_back(position, side); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief optimize the condition expression tree +//////////////////////////////////////////////////////////////////////////////// + +void Condition::optimize (ExecutionPlan* plan) { if (_root == nullptr) { return; } @@ -478,13 +486,15 @@ void Condition::optimize (ExecutionPlan* plan) { TRI_ASSERT(_root->type == NODE_TYPE_OPERATOR_NARY_OR); // handle sub nodes or top-level OR node - size_t const n = _root->numMembers(); + size_t n = _root->numMembers(); + size_t r = 0; - for (size_t i = 0; i < n; ++i) { // foreach OR-Node - auto andNode = _root->getMemberUnchecked(i); + while (r < n) { // foreach OR-Node + bool retry = false; + auto andNode = _root->getMemberUnchecked(r); TRI_ASSERT(andNode->type == NODE_TYPE_OPERATOR_NARY_AND); - restartThisOrItem: +restartThisOrItem: size_t const andNumMembers = andNode->numMembers(); // deduplicate all IN arrays @@ -492,6 +502,14 @@ void Condition::optimize (ExecutionPlan* plan) { deduplicateInOperation(andNode->getMemberUnchecked(j)); } + if (andNumMembers <= 1) { + // simple AND item with 0 or 1 members. nothing to do + ++r; + continue; + } + + TRI_ASSERT(andNumMembers > 1); + // move IN operation to the front to make comparison code below simpler andNode->sortMembers([] (AstNode const* lhs, AstNode const* rhs) -> bool { if (lhs->type == NODE_TYPE_OPERATOR_BINARY_IN) { @@ -506,173 +524,178 @@ void Condition::optimize (ExecutionPlan* plan) { return (lhs < rhs); // compare pointers to have a deterministic order }); - if (andNumMembers > 1) { - // optimization is only necessary if an AND node has multiple members - VariableUsageType variableUsage; + // optimization is only necessary if an AND node has multiple members + VariableUsageType variableUsage; - for (size_t j = 0; j < andNumMembers; ++j) { - auto operand = andNode->getMemberUnchecked(j); + for (size_t j = 0; j < andNumMembers; ++j) { + auto operand = andNode->getMemberUnchecked(j); - if (operand->isComparisonOperator()) { - auto lhs = operand->getMember(0); - auto rhs = operand->getMember(1); + if (operand->isComparisonOperator()) { + auto lhs = operand->getMember(0); + auto rhs = operand->getMember(1); - if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) { - storeAttributeAccess(variableUsage, lhs, j, ATTRIBUTE_LEFT); - } - if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS || - rhs->type == NODE_TYPE_EXPANSION) { - storeAttributeAccess(variableUsage, rhs, j, ATTRIBUTE_RIGHT); - } + if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) { + storeAttributeAccess(variableUsage, lhs, j, ATTRIBUTE_LEFT); + } + if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS || + rhs->type == NODE_TYPE_EXPANSION) { + storeAttributeAccess(variableUsage, rhs, j, ATTRIBUTE_RIGHT); } } + } - // now find the variables and attributes for which there are multiple conditions - for (auto const& it : variableUsage) { // foreach sub-and-node - auto variable = it.first; + // now find the variables and attributes for which there are multiple conditions + for (auto const& it : variableUsage) { // foreach sub-and-node + auto variable = it.first; - for (auto const& it2 : it.second) { // cross compare sub-and-nodes - auto const& attributeName = it2.first; - auto const& positions = it2.second; + for (auto const& it2 : it.second) { // cross compare sub-and-nodes + auto const& attributeName = it2.first; + auto const& positions = it2.second; - if (positions.size() <= 1) { - // none or only one occurence of the attribute + if (positions.size() <= 1) { + // none or only one occurence of the attribute + continue; + } + + // multiple occurrences of the same attribute + auto leftNode = andNode->getMemberUnchecked(positions[0].first); + + ConditionPart current(variable, attributeName, 0, leftNode, positions[0].second); + + if (! current.valueNode->isConstant()) { + continue; + } + + // current.dump(); + size_t j = 1; + + while (j < positions.size()) { + auto rightNode = andNode->getMemberUnchecked(positions[j].first); + + ConditionPart other(variable, attributeName, j, rightNode, positions[j].second); + + if (! other.valueNode->isConstant()) { + ++j; continue; } - // multiple occurrences of the same attribute - auto leftNode = andNode->getMemberUnchecked(positions[0].first); + // IN-merging + if (leftNode->type == NODE_TYPE_OPERATOR_BINARY_IN && + leftNode->getMemberUnchecked(1)->isConstant()) { + TRI_ASSERT(leftNode->numMembers() == 2); - ConditionPart current(variable, attributeName, 0, leftNode, positions[0].second); + if (rightNode->type == NODE_TYPE_OPERATOR_BINARY_IN && + rightNode->getMemberUnchecked(1)->isConstant()) { + // merge IN with IN on same attribute + TRI_ASSERT(rightNode->numMembers() == 2); - if (! current.valueNode->isConstant()) { - continue; - } - - // current.dump(); - size_t j = 1; - - while (j < positions.size()) { - auto rightNode = andNode->getMemberUnchecked(positions[j].first); - - ConditionPart other(variable, attributeName, j, rightNode, positions[j].second); - - if (! other.valueNode->isConstant()) { - ++j; - continue; - } - - // IN-merging - if (leftNode->type == NODE_TYPE_OPERATOR_BINARY_IN && - leftNode->getMemberUnchecked(1)->isConstant()) { - TRI_ASSERT(leftNode->numMembers() == 2); - - if (rightNode->type == NODE_TYPE_OPERATOR_BINARY_IN && - rightNode->getMemberUnchecked(1)->isConstant()) { - // merge IN with IN on same attribute - TRI_ASSERT(rightNode->numMembers() == 2); - - auto merged = _ast->createNodeBinaryOperator( + auto merged = _ast->createNodeBinaryOperator( NODE_TYPE_OPERATOR_BINARY_IN, leftNode->getMemberUnchecked(0), mergeInOperations(leftNode, rightNode) - ); - andNode->removeMemberUnchecked(positions[j].first); - andNode->changeMember(positions[0].first, merged); - goto restartThisOrItem; - } - else if (rightNode->isSimpleComparisonOperator()) { - // merge other comparison operator with IN - TRI_ASSERT(rightNode->numMembers() == 2); - - auto inNode = _ast->createNodeArray(); - auto values = leftNode->getMemberUnchecked(1); - - for (size_t k = 0; k < values->numMembers(); ++k) { - auto value = values->getMemberUnchecked(k); - ConditionPart::ConditionPartCompareResult res = ConditionPart::ResultsTable - [CompareAstNodes(value, other.valueNode, false) + 1] - [0 /*NODE_TYPE_OPERATOR_BINARY_EQ*/] - [other.whichCompareOperation()]; - - bool const keep = (res == CompareResult::OTHER_CONTAINED_IN_SELF || res == CompareResult::CONVERT_EQUAL); - if (keep) { - inNode->addMember(value); - } - } - - if (inNode->numMembers() == 0) { - // no values left after merging -> IMPOSSIBLE - _root->removeMemberUnchecked(i); - goto fastForwardToNextOrItem; - } - - // use the new array of values - andNode->getMemberUnchecked(positions[0].first)->changeMember(1, inNode); - // remove the other operator - andNode->removeMemberUnchecked(positions[j].first); - goto restartThisOrItem; - } + ); + andNode->removeMemberUnchecked(positions[j].first); + andNode->changeMember(positions[0].first, merged); + goto restartThisOrItem; } - // end of IN-merging + else if (rightNode->isSimpleComparisonOperator()) { + // merge other comparison operator with IN + TRI_ASSERT(rightNode->numMembers() == 2); - // Results are -1, 0, 1, move to 0, 1, 2 for the lookup: - ConditionPart::ConditionPartCompareResult res = ConditionPart::ResultsTable - [CompareAstNodes(current.valueNode, other.valueNode, false) + 1] - [current.whichCompareOperation()] - [other.whichCompareOperation()]; + auto inNode = _ast->createNodeArray(); + auto values = leftNode->getMemberUnchecked(1); - switch (res) { - case CompareResult::IMPOSSIBLE: { - // impossible condition - // j = positions.size(); - // we remove this one, so fast forward the loops to their end: - _root->removeMemberUnchecked(i); - /// i -= 1; <- wenn wir das ohne goto machen... + for (size_t k = 0; k < values->numMembers(); ++k) { + auto value = values->getMemberUnchecked(k); + ConditionPart::ConditionPartCompareResult res = ConditionPart::ResultsTable + [CompareAstNodes(value, other.valueNode, false) + 1] + [0 /*NODE_TYPE_OPERATOR_BINARY_EQ*/] + [other.whichCompareOperation()]; + + bool const keep = (res == CompareResult::OTHER_CONTAINED_IN_SELF || res == CompareResult::CONVERT_EQUAL); + if (keep) { + inNode->addMember(value); + } + } + + if (inNode->numMembers() == 0) { + // no values left after merging -> IMPOSSIBLE + _root->removeMemberUnchecked(r); + retry = true; goto fastForwardToNextOrItem; } - case CompareResult::SELF_CONTAINED_IN_OTHER: { - std::cout << "SELF IS CONTAINED IN OTHER\n"; - andNode->removeMemberUnchecked(positions[0].first); - goto restartThisOrItem; - } - case CompareResult::OTHER_CONTAINED_IN_SELF: { - std::cout << "OTHER IS CONTAINED IN SELF\n"; - andNode->removeMemberUnchecked(positions[j].first); - goto restartThisOrItem; - } - case CompareResult::CONVERT_EQUAL: { /// beide gehen, werden umgeformt zu a == x (== y) - andNode->removeMemberUnchecked(positions[j].first); - auto origNode = andNode->getMemberUnchecked(positions[0].first); - auto newNode = plan->getAst()->createNode(NODE_TYPE_OPERATOR_BINARY_EQ); - for (size_t iMemb = 0; iMemb < origNode->numMembers(); iMemb++) { - newNode->addMember(origNode->getMemberUnchecked(iMemb)); - } - andNode->changeMember(positions[0].first, newNode); - std::cout << "RESULT equals X/Y\n"; - break; - } - case CompareResult::DISJOINT: { - break; - } - case CompareResult::UNKNOWN: { - std::cout << "UNKNOWN\n"; - break; - } + // use the new array of values + andNode->getMemberUnchecked(positions[0].first)->changeMember(1, inNode); + // remove the other operator + andNode->removeMemberUnchecked(positions[j].first); + goto restartThisOrItem; } - - ++j; } - } // cross compare sub-and-nodes - } // foreach sub-and-node + // end of IN-merging - fastForwardToNextOrItem: - continue; + // Results are -1, 0, 1, move to 0, 1, 2 for the lookup: + ConditionPart::ConditionPartCompareResult res = ConditionPart::ResultsTable + [CompareAstNodes(current.valueNode, other.valueNode, false) + 1] + [current.whichCompareOperation()] + [other.whichCompareOperation()]; + + switch (res) { + case CompareResult::IMPOSSIBLE: { + // impossible condition + // j = positions.size(); + // we remove this one, so fast forward the loops to their end: + _root->removeMemberUnchecked(r); + retry = true; + goto fastForwardToNextOrItem; + } + case CompareResult::SELF_CONTAINED_IN_OTHER: { + std::cout << "SELF IS CONTAINED IN OTHER\n"; + andNode->removeMemberUnchecked(positions[0].first); + goto restartThisOrItem; + } + case CompareResult::OTHER_CONTAINED_IN_SELF: { + std::cout << "OTHER IS CONTAINED IN SELF\n"; + andNode->removeMemberUnchecked(positions[j].first); + goto restartThisOrItem; + } + case CompareResult::CONVERT_EQUAL: { /// beide gehen, werden umgeformt zu a == x (== y) + andNode->removeMemberUnchecked(positions[j].first); + auto origNode = andNode->getMemberUnchecked(positions[0].first); + auto newNode = plan->getAst()->createNode(NODE_TYPE_OPERATOR_BINARY_EQ); + for (size_t iMemb = 0; iMemb < origNode->numMembers(); iMemb++) { + newNode->addMember(origNode->getMemberUnchecked(iMemb)); + } + + andNode->changeMember(positions[0].first, newNode); + std::cout << "RESULT equals X/Y\n"; + break; + } + case CompareResult::DISJOINT: { + break; + } + case CompareResult::UNKNOWN: { + std::cout << "UNKNOWN\n"; + break; + } + } + + ++j; + } + } // cross compare sub-and-nodes + } // foreach sub-and-node + +fastForwardToNextOrItem: + if (retry) { + // number of root sub-nodes has probably changed. + // now recalculate the number and don't modify r! + n = _root->numMembers(); + } + else { + // root nodes hasn't changed. go to next sub-node! + ++r; } - } - } //////////////////////////////////////////////////////////////////////////////// @@ -853,7 +876,7 @@ AstNode* Condition::transformNode (AstNode* node) { node->changeMember(0, transformNode(sub)); // fallthrough intentional } - + return node; } diff --git a/arangod/Aql/Condition.h b/arangod/Aql/Condition.h index 77bcc60ada..6e31f66362 100644 --- a/arangod/Aql/Condition.h +++ b/arangod/Aql/Condition.h @@ -125,6 +125,16 @@ namespace triagens { class Condition { +// ----------------------------------------------------------------------------- +// --SECTION-- private typedefs +// ----------------------------------------------------------------------------- + + private: + + typedef std::vector> UsagePositionType; + typedef std::unordered_map AttributeUsageType; + typedef std::unordered_map VariableUsageType; + // ----------------------------------------------------------------------------- // --SECTION-- constructors / destructors // ----------------------------------------------------------------------------- @@ -211,6 +221,15 @@ namespace triagens { void normalize (ExecutionPlan* plan); +//////////////////////////////////////////////////////////////////////////////// +/// @brief registers an attribute access for a particular (collection) variable +//////////////////////////////////////////////////////////////////////////////// + + void storeAttributeAccess (VariableUsageType&, + AstNode const*, + size_t, + AttributeSideType); + //////////////////////////////////////////////////////////////////////////////// /// @brief optimize the condition expression tree //////////////////////////////////////////////////////////////////////////////// diff --git a/js/apps/system/_admin/aardvark/APP/frontend/js/modules/org/arangodb/aql/explainer.js b/js/apps/system/_admin/aardvark/APP/frontend/js/modules/org/arangodb/aql/explainer.js index ebad581298..c144752f46 100644 --- a/js/apps/system/_admin/aardvark/APP/frontend/js/modules/org/arangodb/aql/explainer.js +++ b/js/apps/system/_admin/aardvark/APP/frontend/js/modules/org/arangodb/aql/explainer.js @@ -191,7 +191,7 @@ function printIndexes (indexes) { stringBuilder.appendLine(" " + value("none")); } else { - var maxIdLen = String("Id").length; + var maxIdLen = String("By").length; var maxCollectionLen = String("Collection").length; var maxUniqueLen = String("Unique").length; var maxSparseLen = String("Sparse").length; @@ -216,7 +216,7 @@ function printIndexes (indexes) { maxCollectionLen = l; } }); - var line = " " + pad(1 + maxIdLen - String("Id").length) + header("Id") + " " + + var line = " " + pad(1 + maxIdLen - String("By").length) + header("By") + " " + header("Type") + pad(1 + maxTypeLen - "Type".length) + " " + header("Collection") + pad(1 + maxCollectionLen - "Collection".length) + " " + header("Unique") + pad(1 + maxUniqueLen - "Unique".length) + " " + @@ -232,7 +232,13 @@ function printIndexes (indexes) { var sparsity = (indexes[i].hasOwnProperty("sparse") ? (indexes[i].sparse ? "true" : "false") : "n/a"); var fields = indexes[i].fields.map(attribute).join(", "); var fieldsLen = indexes[i].fields.map(attributeUncolored).join(", ").length; - var ranges = "[ " + indexes[i].ranges + " ]"; + var ranges; + if (indexes[i].hasOwnProperty("condition")) { + ranges = indexes[i].condition; + } + else { + ranges = "[ " + indexes[i].ranges + " ]"; + } var selectivity = (indexes[i].hasOwnProperty("selectivityEstimate") ? (indexes[i].selectivityEstimate * 100).toFixed(2) + " %" : "n/a" @@ -378,8 +384,6 @@ function processQuery (query, explain) { references[node.subNodes[0].subNodes[0].name] = node.subNodes[0].subNodes[1]; } return buildExpression(node.subNodes[1]); - case "verticalizer": - return buildExpression(node.subNodes[0]); case "user function call": return func(node.name) + "(" + ((node.subNodes && node.subNodes[0].subNodes) || [ ]).map(buildExpression).join(", ") + ")" + " " + annotation("/* user-defined function */"); case "function call": @@ -416,6 +420,10 @@ function processQuery (query, explain) { return buildExpression(node.subNodes[0]) + " && " + buildExpression(node.subNodes[1]); case "ternary": return buildExpression(node.subNodes[0]) + " ? " + buildExpression(node.subNodes[1]) + " : " + buildExpression(node.subNodes[2]); + case "n-ary or": + return node.subNodes.map(function(sub) { return buildExpression(sub); }).join(" || "); + case "n-ary and": + return node.subNodes.map(function(sub) { return buildExpression(sub); }).join(" && "); default: return "unhandled node type (" + node.type + ")"; } @@ -486,11 +494,14 @@ function processQuery (query, explain) { case "IndexNode": collectionVariables[node.outVariable.id] = node.collection; var types = [ ]; - node.indexes.forEach(function (idx) { + node.indexes.forEach(function (idx, i) { types.push((idx.reverse ? "reverse " : "") + idx.type + " index scan"); idx.collection = node.collection; idx.node = node.id; - idx.ranges = [ ]; + var condition = ""; + if (node.condition.type === 'n-ary or') { + idx.condition = buildExpression(node.condition.subNodes[i]); + } indexes.push(idx); }); return keyword("FOR") + " " + variableName(node.outVariable) + " " + keyword("IN") + " " + collection(node.collection) + " " + annotation("/* " + types.join(", ") + " */"); diff --git a/js/common/modules/org/arangodb/aql/explainer.js b/js/common/modules/org/arangodb/aql/explainer.js index 0d3faa7b95..98715b62ce 100644 --- a/js/common/modules/org/arangodb/aql/explainer.js +++ b/js/common/modules/org/arangodb/aql/explainer.js @@ -190,7 +190,7 @@ function printIndexes (indexes) { stringBuilder.appendLine(" " + value("none")); } else { - var maxIdLen = String("Id").length; + var maxIdLen = String("By").length; var maxCollectionLen = String("Collection").length; var maxUniqueLen = String("Unique").length; var maxSparseLen = String("Sparse").length; @@ -215,7 +215,7 @@ function printIndexes (indexes) { maxCollectionLen = l; } }); - var line = " " + pad(1 + maxIdLen - String("Id").length) + header("Id") + " " + + var line = " " + pad(1 + maxIdLen - String("By").length) + header("By") + " " + header("Type") + pad(1 + maxTypeLen - "Type".length) + " " + header("Collection") + pad(1 + maxCollectionLen - "Collection".length) + " " + header("Unique") + pad(1 + maxUniqueLen - "Unique".length) + " " + @@ -231,7 +231,13 @@ function printIndexes (indexes) { var sparsity = (indexes[i].hasOwnProperty("sparse") ? (indexes[i].sparse ? "true" : "false") : "n/a"); var fields = indexes[i].fields.map(attribute).join(", "); var fieldsLen = indexes[i].fields.map(attributeUncolored).join(", ").length; - var ranges = "[ " + indexes[i].ranges + " ]"; + var ranges; + if (indexes[i].hasOwnProperty("condition")) { + ranges = indexes[i].condition; + } + else { + ranges = "[ " + indexes[i].ranges + " ]"; + } var selectivity = (indexes[i].hasOwnProperty("selectivityEstimate") ? (indexes[i].selectivityEstimate * 100).toFixed(2) + " %" : "n/a" @@ -377,8 +383,6 @@ function processQuery (query, explain) { references[node.subNodes[0].subNodes[0].name] = node.subNodes[0].subNodes[1]; } return buildExpression(node.subNodes[1]); - case "verticalizer": - return buildExpression(node.subNodes[0]); case "user function call": return func(node.name) + "(" + ((node.subNodes && node.subNodes[0].subNodes) || [ ]).map(buildExpression).join(", ") + ")" + " " + annotation("/* user-defined function */"); case "function call": @@ -415,6 +419,10 @@ function processQuery (query, explain) { return buildExpression(node.subNodes[0]) + " && " + buildExpression(node.subNodes[1]); case "ternary": return buildExpression(node.subNodes[0]) + " ? " + buildExpression(node.subNodes[1]) + " : " + buildExpression(node.subNodes[2]); + case "n-ary or": + return node.subNodes.map(function(sub) { return buildExpression(sub); }).join(" || "); + case "n-ary and": + return node.subNodes.map(function(sub) { return buildExpression(sub); }).join(" && "); default: return "unhandled node type (" + node.type + ")"; } @@ -485,11 +493,16 @@ function processQuery (query, explain) { case "IndexNode": collectionVariables[node.outVariable.id] = node.collection; var types = [ ]; - node.indexes.forEach(function (idx) { + node.indexes.forEach(function (idx, i) { types.push((idx.reverse ? "reverse " : "") + idx.type + " index scan"); idx.collection = node.collection; idx.node = node.id; - idx.ranges = [ ]; + if (node.condition.type === 'n-ary or') { + idx.condition = buildExpression(node.condition.subNodes[i]); + } + else { + idx.conditionn = ""; + } indexes.push(idx); }); return keyword("FOR") + " " + variableName(node.outVariable) + " " + keyword("IN") + " " + collection(node.collection) + " " + annotation("/* " + types.join(", ") + " */");