diff --git a/arangod/Aql/Ast.cpp b/arangod/Aql/Ast.cpp index 19ab95009a..0a04312fe0 100644 --- a/arangod/Aql/Ast.cpp +++ b/arangod/Aql/Ast.cpp @@ -1634,6 +1634,7 @@ void Ast::validateAndOptimize() { struct TraversalContext { std::unordered_set writeCollectionsSeen; std::unordered_map collectionsFirstSeen; + std::unordered_map variableDefinitions; int64_t stopOptimizationRequests = 0; int64_t nestingLevel = 0; bool isInFilter = false; @@ -1769,7 +1770,7 @@ void Ast::validateAndOptimize() { // attribute access if (node->type == NODE_TYPE_ATTRIBUTE_ACCESS) { - return this->optimizeAttributeAccess(node); + return this->optimizeAttributeAccess(node, static_cast(data)->variableDefinitions); } // passthru node @@ -1813,6 +1814,22 @@ void Ast::validateAndOptimize() { // LET if (node->type == NODE_TYPE_LET) { + // remember variable assignments + TRI_ASSERT(node->numMembers() == 2); + auto context = static_cast(data); + Variable const* variable = static_cast(node->getMember(0)->getData()); + AstNode const* definition = node->getMember(1); + // recursively process assignments so we can track LET a = b LET c = b + + while (definition->type == NODE_TYPE_REFERENCE) { + auto it = context->variableDefinitions.find(static_cast(definition->getData())); + if (it == context->variableDefinitions.end()) { + break; + } + definition = (*it).second; + } + + context->variableDefinitions.emplace(variable, definition); return this->optimizeLet(node); } @@ -2669,12 +2686,21 @@ AstNode* Ast::optimizeTernaryOperator(AstNode* node) { } /// @brief optimizes an attribute access -AstNode* Ast::optimizeAttributeAccess(AstNode* node) { +AstNode* Ast::optimizeAttributeAccess(AstNode* node, std::unordered_map const& variableDefinitions) { TRI_ASSERT(node != nullptr); TRI_ASSERT(node->type == NODE_TYPE_ATTRIBUTE_ACCESS); TRI_ASSERT(node->numMembers() == 1); - AstNode* what = node->getMember(0); + AstNode const* what = node->getMember(0); + + if (what->type == NODE_TYPE_REFERENCE) { + // check if the access value is a variable and if it is an alias + auto it = variableDefinitions.find(static_cast(what->getData())); + + if (it != variableDefinitions.end()) { + what = (*it).second; + } + } if (!what->isConstant()) { return node; @@ -2689,6 +2715,7 @@ AstNode* Ast::optimizeAttributeAccess(AstNode* node) { for (size_t i = 0; i < n; ++i) { AstNode const* member = what->getMember(0); + if (member->type == NODE_TYPE_OBJECT_ELEMENT && member->getStringLength() == length && memcmp(name, member->getStringValue(), length) == 0) { @@ -2942,6 +2969,8 @@ AstNode* Ast::nodeFromVPack(VPackSlice const& slice, bool copyStringValues) { for (auto const& it : VPackArrayIterator(slice)) { node->addMember(nodeFromVPack(it, copyStringValues)); } + + node->setFlag(DETERMINED_CONSTANT, VALUE_CONSTANT); return node; } @@ -2963,6 +2992,8 @@ AstNode* Ast::nodeFromVPack(VPackSlice const& slice, bool copyStringValues) { node->addMember(createNodeObjectElement( attributeName, static_cast(nameLength), nodeFromVPack(it.value, copyStringValues))); } + + node->setFlag(DETERMINED_CONSTANT, VALUE_CONSTANT); return node; } @@ -2970,6 +3001,61 @@ AstNode* Ast::nodeFromVPack(VPackSlice const& slice, bool copyStringValues) { return createNodeValueNull(); } +/// @brief resolve an attribute access +AstNode const* Ast::resolveConstAttributeAccess(AstNode const* node) { + TRI_ASSERT(node != nullptr); + TRI_ASSERT(node->type == NODE_TYPE_ATTRIBUTE_ACCESS); + + std::vector attributeNames; + + while (node->type == NODE_TYPE_ATTRIBUTE_ACCESS) { + attributeNames.emplace_back(node->getString()); + node = node->getMember(0); + } + + size_t which = attributeNames.size(); + TRI_ASSERT(which > 0); + + while (which > 0) { + TRI_ASSERT(node->type == NODE_TYPE_VALUE || node->type == NODE_TYPE_ARRAY || + node->type == NODE_TYPE_OBJECT); + + bool found = false; + + if (node->type == NODE_TYPE_OBJECT) { + TRI_ASSERT(which > 0); + std::string const& attributeName = attributeNames[which - 1]; + --which; + + size_t const n = node->numMembers(); + for (size_t i = 0; i < n; ++i) { + auto member = node->getMember(i); + + if (member->type == NODE_TYPE_OBJECT_ELEMENT && + member->getString() == attributeName) { + // found the attribute + node = member->getMember(0); + if (which == 0) { + // we found what we looked for + return node; + } + // we found the correct attribute but there is now an attribute + // access on the result + found = true; + break; + } + } + } + + if (!found) { + break; + } + } + + // attribute not found or non-array + return createNodeValueNull(); +} + /// @brief traverse the AST, using pre- and post-order visitors AstNode* Ast::traverseAndModify( AstNode* node, std::function preVisitor, diff --git a/arangod/Aql/Ast.h b/arangod/Aql/Ast.h index 267e3fe5f7..7595ef303f 100644 --- a/arangod/Aql/Ast.h +++ b/arangod/Aql/Ast.h @@ -412,6 +412,9 @@ class Ast { /// @brief create an AST node from vpack AstNode* nodeFromVPack(arangodb::velocypack::Slice const&, bool); + + /// @brief resolve an attribute access + static AstNode const* resolveConstAttributeAccess(AstNode const*); /// @brief traverse the AST using a depth-first visitor static AstNode* traverseAndModify(AstNode*, @@ -457,7 +460,7 @@ class Ast { AstNode* optimizeTernaryOperator(AstNode*); /// @brief optimizes an attribute access - AstNode* optimizeAttributeAccess(AstNode*); + AstNode* optimizeAttributeAccess(AstNode*, std::unordered_map const&); /// @brief optimizes a call to a built-in function AstNode* optimizeFunctionCall(AstNode*); diff --git a/arangod/Aql/AstNode.cpp b/arangod/Aql/AstNode.cpp index 96e20b8154..c785aabe8c 100644 --- a/arangod/Aql/AstNode.cpp +++ b/arangod/Aql/AstNode.cpp @@ -187,61 +187,6 @@ std::unordered_map const AstNode::ValueTypeNames{ namespace { -/// @brief resolve an attribute access -static AstNode const* ResolveAttribute(AstNode const* node) { - TRI_ASSERT(node != nullptr); - TRI_ASSERT(node->type == NODE_TYPE_ATTRIBUTE_ACCESS); - - std::vector attributeNames; - - while (node->type == NODE_TYPE_ATTRIBUTE_ACCESS) { - attributeNames.emplace_back(node->getString()); - node = node->getMember(0); - } - - size_t which = attributeNames.size(); - TRI_ASSERT(which > 0); - - while (which > 0) { - TRI_ASSERT(node->type == NODE_TYPE_VALUE || node->type == NODE_TYPE_ARRAY || - node->type == NODE_TYPE_OBJECT); - - bool found = false; - - if (node->type == NODE_TYPE_OBJECT) { - TRI_ASSERT(which > 0); - std::string const& attributeName = attributeNames[which - 1]; - --which; - - size_t const n = node->numMembers(); - for (size_t i = 0; i < n; ++i) { - auto member = node->getMember(i); - - if (member->type == NODE_TYPE_OBJECT_ELEMENT && - member->getString() == attributeName) { - // found the attribute - node = member->getMember(0); - if (which == 0) { - // we found what we looked for - return node; - } - // we found the correct attribute but there is now an attribute - // access on the result - found = true; - break; - } - } - } - - if (!found) { - break; - } - } - - // attribute not found or non-array - return Ast::createNodeValueNull(); -} - /// @brief get the node type for inter-node comparisons static VPackValueType GetNodeCompareType(AstNode const* node) { TRI_ASSERT(node != nullptr); @@ -276,10 +221,10 @@ int arangodb::aql::CompareAstNodes(AstNode const* lhs, AstNode const* rhs, TRI_ASSERT(rhs != nullptr); if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) { - lhs = ResolveAttribute(lhs); + lhs = Ast::resolveConstAttributeAccess(lhs); } if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) { - rhs = ResolveAttribute(rhs); + rhs = Ast::resolveConstAttributeAccess(rhs); } auto lType = GetNodeCompareType(lhs); @@ -419,6 +364,7 @@ AstNode::AstNode(bool v, AstNodeValueType valueType) value.value._bool = v; TRI_ASSERT(flags == 0); TRI_ASSERT(computedValue == nullptr); + setFlag(DETERMINED_CONSTANT, VALUE_CONSTANT); } /// @brief create an int node, with defining a value @@ -428,6 +374,7 @@ AstNode::AstNode(int64_t v, AstNodeValueType valueType) value.value._int = v; TRI_ASSERT(flags == 0); TRI_ASSERT(computedValue == nullptr); + setFlag(DETERMINED_CONSTANT, VALUE_CONSTANT); } /// @brief create a string node, with defining a value @@ -437,6 +384,7 @@ AstNode::AstNode(char const* v, size_t length, AstNodeValueType valueType) setStringValue(v, length); TRI_ASSERT(flags == 0); TRI_ASSERT(computedValue == nullptr); + setFlag(DETERMINED_CONSTANT, VALUE_CONSTANT); } /// @brief create the node from VPack diff --git a/arangod/Aql/Condition.cpp b/arangod/Aql/Condition.cpp index 2a186a6919..7af12546d6 100644 --- a/arangod/Aql/Condition.cpp +++ b/arangod/Aql/Condition.cpp @@ -722,14 +722,20 @@ void Condition::optimize(ExecutionPlan* plan) { auto operand = andNode->getMemberUnchecked(j); if (operand->isComparisonOperator()) { - auto lhs = operand->getMember(0); - auto rhs = operand->getMember(1); + AstNode const* lhs = operand->getMember(0); + AstNode const* rhs = operand->getMember(1); if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) { + if (lhs->isConstant()) { + lhs = Ast::resolveConstAttributeAccess(lhs); + } storeAttributeAccess(varAccess, variableUsage, lhs, j, ATTRIBUTE_LEFT); } if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS || rhs->type == NODE_TYPE_EXPANSION) { + if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS && rhs->isConstant()) { + rhs = Ast::resolveConstAttributeAccess(rhs); + } storeAttributeAccess(varAccess, variableUsage, rhs, j, ATTRIBUTE_RIGHT); } } diff --git a/arangod/Aql/IndexBlock.cpp b/arangod/Aql/IndexBlock.cpp index 7a54317ba3..5dddd72cc9 100644 --- a/arangod/Aql/IndexBlock.cpp +++ b/arangod/Aql/IndexBlock.cpp @@ -53,6 +53,30 @@ IndexBlock::IndexBlock(ExecutionEngine* engine, IndexNode const* en) _hasV8Expression(false) { _mmdr.reset(new ManagedDocumentResult(transaction())); + + if (_condition != nullptr) { + // fix const attribute accesses, e.g. { "a": 1 }.a + for (size_t i = 0; i < _condition->numMembers(); ++i) { + auto andCond = _condition->getMemberUnchecked(i); + for (size_t j = 0; j < andCond->numMembers(); ++j) { + auto leaf = andCond->getMemberUnchecked(j); + + // We only support binary conditions + TRI_ASSERT(leaf->numMembers() == 2); + AstNode* lhs = leaf->getMember(0); + AstNode* rhs = leaf->getMember(1); + + if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS && lhs->isConstant()) { + lhs = const_cast(Ast::resolveConstAttributeAccess(lhs)); + leaf->changeMember(0, lhs); + } + if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS && rhs->isConstant()) { + rhs = const_cast(Ast::resolveConstAttributeAccess(rhs)); + leaf->changeMember(1, rhs); + } + } + } + } } IndexBlock::~IndexBlock() { @@ -126,7 +150,7 @@ int IndexBlock::initialize() { auto en = static_cast(getPlanNode()); auto ast = en->_plan->getAst(); - + // instantiate expressions: auto instantiateExpression = [&](size_t i, size_t j, size_t k, AstNode* a) -> void { @@ -176,8 +200,8 @@ int IndexBlock::initialize() { // We only support binary conditions TRI_ASSERT(leaf->numMembers() == 2); - auto lhs = leaf->getMember(0); - auto rhs = leaf->getMember(1); + AstNode* lhs = leaf->getMember(0); + AstNode* rhs = leaf->getMember(1); if (lhs->isAttributeAccessForVariable(outVariable, false)) { // Index is responsible for the left side, check if right side has to be