//////////////////////////////////////////////////////////////////////////////// /// @brief Implementation of Traversal Execution Node /// /// @file arangod/Aql/TraversalNode.cpp /// /// DISCLAIMER /// /// Copyright 2010-2014 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 ArangoDB GmbH, Cologne, Germany /// /// @author Michael Hackstein /// @author Copyright 2015, ArangoDB GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include "TraversalNode.h" #include "Aql/Ast.h" #include "Aql/ExecutionPlan.h" #include "Aql/Query.h" #include "Aql/SortCondition.h" #include "Cluster/ClusterComm.h" #include "Indexes/Index.h" #include "Utils/CollectionNameResolver.h" #include "VocBase/ticks.h" #include "VocBase/TraverserOptions.h" #include #include using namespace arangodb::basics; using namespace arangodb::aql; using namespace arangodb::traverser; TraversalNode::TraversalEdgeConditionBuilder::TraversalEdgeConditionBuilder( TraversalNode const* tn) : EdgeConditionBuilder(tn->_plan->getAst()->createNodeNaryOperator( NODE_TYPE_OPERATOR_NARY_AND)), _tn(tn) {} TraversalNode::TraversalEdgeConditionBuilder::TraversalEdgeConditionBuilder( TraversalNode const* tn, arangodb::velocypack::Slice const& condition) : EdgeConditionBuilder(new AstNode(tn->_plan->getAst(), condition)), _tn(tn) { } TraversalNode::TraversalEdgeConditionBuilder::TraversalEdgeConditionBuilder( TraversalNode const* tn, TraversalEdgeConditionBuilder const* other) : EdgeConditionBuilder(other->_modCondition), _tn(tn) { _fromCondition = other->_fromCondition; _toCondition = other->_toCondition; _containsCondition = other->_containsCondition; } void TraversalNode::TraversalEdgeConditionBuilder::buildFromCondition() { // TODO Move computation in here. _fromCondition = _tn->_fromCondition; } void TraversalNode::TraversalEdgeConditionBuilder::buildToCondition() { // TODO Move computation in here. _toCondition = _tn->_toCondition; } void TraversalNode::TraversalEdgeConditionBuilder::toVelocyPack( VPackBuilder& builder, bool verbose) { if (_containsCondition) { _modCondition->removeMemberUnchecked(_modCondition->numMembers() - 1); _containsCondition = false; } _modCondition->toVelocyPack(builder, verbose); } static TRI_edge_direction_e parseDirection (AstNode const* node) { TRI_ASSERT(node->isIntValue()); auto dirNum = node->getIntValue(); switch (dirNum) { case 0: return TRI_EDGE_ANY; case 1: return TRI_EDGE_IN; case 2: return TRI_EDGE_OUT; default: THROW_ARANGO_EXCEPTION_MESSAGE( TRI_ERROR_QUERY_PARSE, "direction can only be INBOUND, OUTBOUND or ANY"); } } TraversalNode::TraversalNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase, AstNode const* direction, AstNode const* start, AstNode const* graph, std::unique_ptr& options) : ExecutionNode(plan, id), _vocbase(vocbase), _vertexOutVariable(nullptr), _edgeOutVariable(nullptr), _pathOutVariable(nullptr), _inVariable(nullptr), _graphObj(nullptr), _condition(nullptr), _tmpObjVariable(_plan->getAst()->variables()->createTemporaryVariable()), _tmpObjVarNode(_plan->getAst()->createNodeReference(_tmpObjVariable)), _tmpIdNode(_plan->getAst()->createNodeValueString("", 0)), _fromCondition(nullptr), _toCondition(nullptr), _optionsBuild(false), _isSmart(false) { TRI_ASSERT(_vocbase != nullptr); TRI_ASSERT(direction != nullptr); TRI_ASSERT(start != nullptr); TRI_ASSERT(graph != nullptr); _options.swap(options); auto ast = _plan->getAst(); // Let us build the conditions on _from and _to. Just in case we need them. { auto const* access = ast->createNodeAttributeAccess( _tmpObjVarNode, StaticStrings::FromString.c_str(), StaticStrings::FromString.length()); _fromCondition = ast->createNodeBinaryOperator( NODE_TYPE_OPERATOR_BINARY_EQ, access, _tmpIdNode); } TRI_ASSERT(_fromCondition != nullptr); TRI_ASSERT(_fromCondition->type == NODE_TYPE_OPERATOR_BINARY_EQ); { auto const* access = ast->createNodeAttributeAccess( _tmpObjVarNode, StaticStrings::ToString.c_str(), StaticStrings::ToString.length()); _toCondition = ast->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_EQ, access, _tmpIdNode); } TRI_ASSERT(_toCondition != nullptr); TRI_ASSERT(_toCondition->type == NODE_TYPE_OPERATOR_BINARY_EQ); auto resolver = std::make_unique(vocbase); // Parse Steps and direction TRI_ASSERT(direction->type == NODE_TYPE_DIRECTION); TRI_ASSERT(direction->numMembers() == 2); // Member 0 is the direction. Already the correct Integer. // Is not inserted by user but by enum. TRI_edge_direction_e baseDirection = parseDirection(direction->getMember(0)); std::unordered_map seenCollections; auto addEdgeColl = [&](std::string const& n, TRI_edge_direction_e dir) -> void { if (_isSmart) { if (n.compare(0, 6, "_from_") == 0) { if (dir != TRI_EDGE_IN) { _directions.emplace_back(TRI_EDGE_OUT); _edgeColls.emplace_back(std::make_unique( n, _vocbase, AccessMode::Type::READ)); } return; } else if (n.compare(0, 4, "_to_") == 0) { if (dir != TRI_EDGE_OUT) { _directions.emplace_back(TRI_EDGE_IN); _edgeColls.emplace_back(std::make_unique( n, _vocbase, AccessMode::Type::READ)); } return; } } if (dir == TRI_EDGE_ANY) { _directions.emplace_back(TRI_EDGE_OUT); _edgeColls.emplace_back(std::make_unique( n, _vocbase, AccessMode::Type::READ)); _directions.emplace_back(TRI_EDGE_IN); _edgeColls.emplace_back(std::make_unique( n, _vocbase, AccessMode::Type::READ)); } else { _directions.emplace_back(dir); _edgeColls.emplace_back(std::make_unique( n, _vocbase, AccessMode::Type::READ)); } }; if (graph->type == NODE_TYPE_COLLECTION_LIST) { size_t edgeCollectionCount = graph->numMembers(); _graphInfo.openArray(); _edgeColls.reserve(edgeCollectionCount); _directions.reserve(edgeCollectionCount); // First determine whether all edge collections are smart and sharded // like a common collection: auto ci = ClusterInfo::instance(); if (ServerState::instance()->isRunningInCluster()) { _isSmart = true; std::string distributeShardsLike; for (size_t i = 0; i < edgeCollectionCount; ++i) { auto col = graph->getMember(i); if (col->type == NODE_TYPE_DIRECTION) { col = col->getMember(1); // The first member always is the collection } std::string n = col->getString(); auto c = ci->getCollection(_vocbase->name(), n); if (!c->isSmart() || c->distributeShardsLike().empty()) { _isSmart = false; break; } if (distributeShardsLike.empty()) { distributeShardsLike = c->distributeShardsLike(); } else if (distributeShardsLike != c->distributeShardsLike()) { _isSmart = false; break; } } } // List of edge collection names for (size_t i = 0; i < edgeCollectionCount; ++i) { auto col = graph->getMember(i); TRI_edge_direction_e dir = TRI_EDGE_ANY; if (col->type == NODE_TYPE_DIRECTION) { // We have a collection with special direction. dir = parseDirection(col->getMember(0)); col = col->getMember(1); } else { dir = baseDirection; } std::string eColName = col->getString(); // now do some uniqueness checks for the specified collections auto it = seenCollections.find(eColName); if (it != seenCollections.end()) { if ((*it).second != dir) { std::string msg("conflicting directions specified for collection '" + std::string(eColName)); THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_ARANGO_COLLECTION_TYPE_INVALID, msg); } // do not re-add the same collection! continue; } seenCollections.emplace(eColName, dir); if (resolver->getCollectionTypeCluster(eColName) != TRI_COL_TYPE_EDGE) { std::string msg("collection type invalid for collection '" + std::string(eColName) + ": expecting collection type 'edge'"); THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_ARANGO_COLLECTION_TYPE_INVALID, msg); } _graphInfo.add(VPackValue(eColName)); if (ServerState::instance()->isRunningInCluster()) { auto c = ci->getCollection(_vocbase->name(), eColName); if (!c->isSmart()) { addEdgeColl(eColName, dir); } else { std::vector names; if (_isSmart) { names = c->realNames(); } else { names = c->realNamesForRead(); } for (auto const& name : names) { addEdgeColl(name, dir); } } } else { addEdgeColl(eColName, dir); } } _graphInfo.close(); } else { if (_edgeColls.empty()) { if (graph->isStringValue()) { std::string graphName = graph->getString(); _graphInfo.add(VPackValue(graphName)); _graphObj = plan->getAst()->query()->lookupGraphByName(graphName); if (_graphObj == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_GRAPH_NOT_FOUND); } auto eColls = _graphObj->edgeCollections(); size_t length = eColls.size(); if (length == 0) { THROW_ARANGO_EXCEPTION(TRI_ERROR_GRAPH_EMPTY); } // First determine whether all edge collections are smart and sharded // like a common collection: auto ci = ClusterInfo::instance(); if (ServerState::instance()->isRunningInCluster()) { _isSmart = true; std::string distributeShardsLike; for (auto const& n : eColls) { auto c = ci->getCollection(_vocbase->name(), n); if (!c->isSmart() || c->distributeShardsLike().empty()) { _isSmart = false; break; } if (distributeShardsLike.empty()) { distributeShardsLike = c->distributeShardsLike(); } else if (distributeShardsLike != c->distributeShardsLike()) { _isSmart = false; break; } } } for (const auto& n : eColls) { if (ServerState::instance()->isRunningInCluster()) { auto c = ci->getCollection(_vocbase->name(), n); if (!c->isSmart()) { addEdgeColl(n, baseDirection); } else { std::vector names; if (_isSmart) { names = c->realNames(); } else { names = c->realNamesForRead(); } for (auto const& name : names) { addEdgeColl(name, baseDirection); } } } else { addEdgeColl(n, baseDirection); } } auto vColls = _graphObj->vertexCollections(); length = vColls.size(); if (length == 0) { THROW_ARANGO_EXCEPTION(TRI_ERROR_GRAPH_EMPTY); } _vertexColls.reserve(length); for (auto const& v : vColls) { _vertexColls.emplace_back(std::make_unique( v, _vocbase, AccessMode::Type::READ)); } } } } // Parse start node switch (start->type) { case NODE_TYPE_REFERENCE: _inVariable = static_cast(start->getData()); _vertexId = ""; break; case NODE_TYPE_VALUE: if (start->value.type != VALUE_TYPE_STRING) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_PARSE, "invalid start vertex. Must either be " "an _id string or an object with _id."); } _inVariable = nullptr; _vertexId = start->getString(); break; default: THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_PARSE, "invalid start vertex. Must either be an " "_id string or an object with _id."); } // Parse options node #ifdef TRI_ENABLE_MAINTAINER_MODE checkConditionsDefined(); #endif } /// @brief Internal constructor to clone the node. TraversalNode::TraversalNode( ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase, std::vector> const& edgeColls, std::vector> const& vertexColls, Variable const* inVariable, std::string const& vertexId, std::vector const& directions, std::unique_ptr& options) : ExecutionNode(plan, id), _vocbase(vocbase), _vertexOutVariable(nullptr), _edgeOutVariable(nullptr), _pathOutVariable(nullptr), _inVariable(inVariable), _vertexId(vertexId), _directions(directions), _graphObj(nullptr), _condition(nullptr), _tmpObjVariable(nullptr), _tmpObjVarNode(nullptr), _tmpIdNode(nullptr), _fromCondition(nullptr), _toCondition(nullptr), _optionsBuild(false), _isSmart(false) { _options.swap(options); _graphInfo.openArray(); for (auto& it : edgeColls) { // Collections cannot be copied. So we need to create new ones to prevent leaks _edgeColls.emplace_back(std::make_unique( it->getName(), _vocbase, AccessMode::Type::READ)); _graphInfo.add(VPackValue(it->getName())); } for (auto& it : vertexColls) { // Collections cannot be copied. So we need to create new ones to prevent leaks _vertexColls.emplace_back(std::make_unique( it->getName(), _vocbase, AccessMode::Type::READ)); } _graphInfo.close(); } TraversalNode::TraversalNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base) : ExecutionNode(plan, base), _vocbase(plan->getAst()->query()->vocbase()), _vertexOutVariable(nullptr), _edgeOutVariable(nullptr), _pathOutVariable(nullptr), _inVariable(nullptr), _graphObj(nullptr), _condition(nullptr), _options(std::make_unique( _plan->getAst()->query()->trx(), base)), _tmpObjVariable(nullptr), _tmpObjVarNode(nullptr), _tmpIdNode(nullptr), _fromCondition(nullptr), _toCondition(nullptr), _optionsBuild(false), _isSmart(false) { VPackSlice dirList = base.get("directions"); for (auto const& it : VPackArrayIterator(dirList)) { uint64_t dir = arangodb::basics::VelocyPackHelper::stringUInt64(it); TRI_edge_direction_e d; switch (dir) { case 0: TRI_ASSERT(false); break; case 1: d = TRI_EDGE_IN; break; case 2: d = TRI_EDGE_OUT; break; default: THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER, "Invalid direction value"); break; } _directions.emplace_back(d); } // In Vertex if (base.hasKey("inVariable")) { _inVariable = varFromVPack(plan->getAst(), base, "inVariable"); } else { VPackSlice v = base.get("vertexId"); if (!v.isString()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN, "start vertex must be a string"); } _vertexId = v.copyString(); if (_vertexId.empty()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN, "start vertex mustn't be empty"); } } if (base.hasKey("condition")) { VPackSlice condition = base.get("condition"); if (!condition.isObject()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN, "condition must be an object"); } _condition = Condition::fromVPack(plan, condition); } auto list = base.get("conditionVariables"); if (list.isArray()) { for (auto const& v : VPackArrayIterator(list)) { _conditionVariables.emplace( _plan->getAst()->variables()->createVariable(v)); } } // TODO: Can we remove this? std::string graphName; if (base.hasKey("graph") && (base.get("graph").isString())) { graphName = base.get("graph").copyString(); if (base.hasKey("graphDefinition")) { _graphObj = plan->getAst()->query()->lookupGraphByName(graphName); if (_graphObj == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_GRAPH_NOT_FOUND); } } else { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN, "missing graphDefinition."); } } else { _graphInfo.add(base.get("graph")); if (!_graphInfo.slice().isArray()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN, "graph has to be an array."); } } list = base.get("edgeCollections"); if (!list.isArray()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN, "traverser needs an array of edge collections."); } for (auto const& it : VPackArrayIterator(list)) { std::string e = arangodb::basics::VelocyPackHelper::getStringValue(it, ""); _edgeColls.emplace_back( std::make_unique(e, _vocbase, AccessMode::Type::READ)); } list = base.get("vertexCollections"); if (!list.isArray()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN, "traverser needs an array of vertex collections."); } for (auto const& it : VPackArrayIterator(list)) { std::string v = arangodb::basics::VelocyPackHelper::getStringValue(it, ""); _vertexColls.emplace_back( std::make_unique(v, _vocbase, AccessMode::Type::READ)); } // Out variables if (base.hasKey("vertexOutVariable")) { _vertexOutVariable = varFromVPack(plan->getAst(), base, "vertexOutVariable"); } if (base.hasKey("edgeOutVariable")) { _edgeOutVariable = varFromVPack(plan->getAst(), base, "edgeOutVariable"); } if (base.hasKey("pathOutVariable")) { _pathOutVariable = varFromVPack(plan->getAst(), base, "pathOutVariable"); } // Temporary Filter Objects TRI_ASSERT(base.hasKey("tmpObjVariable")); _tmpObjVariable = varFromVPack(plan->getAst(), base, "tmpObjVariable"); TRI_ASSERT(base.hasKey("tmpObjVarNode")); _tmpObjVarNode = new AstNode(plan->getAst(), base.get("tmpObjVarNode")); TRI_ASSERT(base.hasKey("tmpIdNode")); _tmpIdNode = new AstNode(plan->getAst(), base.get("tmpIdNode")); // Filter Condition Parts TRI_ASSERT(base.hasKey("fromCondition")); _fromCondition = new AstNode(plan->getAst(), base.get("fromCondition")); TRI_ASSERT(base.hasKey("toCondition")); _toCondition = new AstNode(plan->getAst(), base.get("toCondition")); list = base.get("globalEdgeConditions"); if (list.isArray()) { for (auto const& cond : VPackArrayIterator(list)) { _globalEdgeConditions.emplace_back(new AstNode(plan->getAst(), cond)); } } list = base.get("globalVertexConditions"); if (list.isArray()) { for (auto const& cond : VPackArrayIterator(list)) { _globalVertexConditions.emplace_back(new AstNode(plan->getAst(), cond)); } } list = base.get("vertexConditions"); if (list.isObject()) { for (auto const& cond : VPackObjectIterator(list)) { std::string key = cond.key.copyString(); _vertexConditions.emplace(StringUtils::uint64(key), new AstNode(plan->getAst(), cond.value)); } } list = base.get("edgeConditions"); if (list.isObject()) { for (auto const& cond : VPackObjectIterator(list)) { std::string key = cond.key.copyString(); auto ecbuilder = std::make_unique(this, cond.value); _edgeConditions.emplace(StringUtils::uint64(key), std::move(ecbuilder)); } } #ifdef TRI_ENABLE_MAINTAINER_MODE checkConditionsDefined(); #endif } TraversalNode::~TraversalNode() { if (_condition != nullptr) { delete _condition; } } int TraversalNode::checkIsOutVariable(size_t variableId) const { if (_vertexOutVariable != nullptr && _vertexOutVariable->id == variableId) { return 0; } if (_edgeOutVariable != nullptr && _edgeOutVariable->id == variableId) { return 1; } if (_pathOutVariable != nullptr && _pathOutVariable->id == variableId) { return 2; } return -1; } /// @brief check whether an access is inside the specified range bool TraversalNode::isInRange(uint64_t depth, bool isEdge) const { if (isEdge) { return (depth < _options->maxDepth); } return (depth <= _options->maxDepth); } /// @brief check if all directions are equal bool TraversalNode::allDirectionsEqual() const { if (_directions.empty()) { // no directions! return false; } size_t const n = _directions.size(); TRI_edge_direction_e const expected = _directions[0]; for (size_t i = 1; i < n; ++i) { if (_directions[i] != expected) { return false; } } return true; } void TraversalNode::toVelocyPackHelper(arangodb::velocypack::Builder& nodes, bool verbose) const { ExecutionNode::toVelocyPackHelperGeneric(nodes, verbose); // call base class method nodes.add("database", VPackValue(_vocbase->name())); nodes.add("graph", _graphInfo.slice()); nodes.add(VPackValue("directions")); { VPackArrayBuilder guard(&nodes); for (auto const& d : _directions) { nodes.add(VPackValue(d)); } } nodes.add(VPackValue("edgeCollections")); { VPackArrayBuilder guard(&nodes); for (auto const& e : _edgeColls) { nodes.add(VPackValue(e->getName())); } } nodes.add(VPackValue("vertexCollections")); { VPackArrayBuilder guard(&nodes); for (auto const& v : _vertexColls) { nodes.add(VPackValue(v->getName())); } } // In variable if (usesInVariable()) { nodes.add(VPackValue("inVariable")); inVariable()->toVelocyPack(nodes); } else { nodes.add("vertexId", VPackValue(_vertexId)); } if (_condition != nullptr) { nodes.add(VPackValue("condition")); _condition->toVelocyPack(nodes, verbose); } if (!_conditionVariables.empty()) { nodes.add(VPackValue("conditionVariables")); nodes.openArray(); for (auto const& it : _conditionVariables) { it->toVelocyPack(nodes); } nodes.close(); } if (_graphObj != nullptr) { nodes.add(VPackValue("graphDefinition")); _graphObj->toVelocyPack(nodes, verbose); } // Out variables if (usesVertexOutVariable()) { nodes.add(VPackValue("vertexOutVariable")); vertexOutVariable()->toVelocyPack(nodes); } if (usesEdgeOutVariable()) { nodes.add(VPackValue("edgeOutVariable")); edgeOutVariable()->toVelocyPack(nodes); } if (usesPathOutVariable()) { nodes.add(VPackValue("pathOutVariable")); pathOutVariable()->toVelocyPack(nodes); } nodes.add(VPackValue("traversalFlags")); _options->toVelocyPack(nodes); // Traversal Filter Conditions TRI_ASSERT(_tmpObjVariable != nullptr); nodes.add(VPackValue("tmpObjVariable")); _tmpObjVariable->toVelocyPack(nodes); TRI_ASSERT(_tmpObjVarNode != nullptr); nodes.add(VPackValue("tmpObjVarNode")); _tmpObjVarNode->toVelocyPack(nodes, verbose); TRI_ASSERT(_tmpIdNode != nullptr); nodes.add(VPackValue("tmpIdNode")); _tmpIdNode->toVelocyPack(nodes, verbose); TRI_ASSERT(_fromCondition != nullptr); nodes.add(VPackValue("fromCondition")); _fromCondition->toVelocyPack(nodes, verbose); TRI_ASSERT(_toCondition != nullptr); nodes.add(VPackValue("toCondition")); _toCondition->toVelocyPack(nodes, verbose); if (!_globalEdgeConditions.empty()) { nodes.add(VPackValue("globalEdgeConditions")); nodes.openArray(); for (auto const& it : _globalEdgeConditions) { it->toVelocyPack(nodes, verbose); } nodes.close(); } if (!_globalVertexConditions.empty()) { nodes.add(VPackValue("globalVertexConditions")); nodes.openArray(); for (auto const& it : _globalVertexConditions) { it->toVelocyPack(nodes, verbose); } nodes.close(); } if (!_vertexConditions.empty()) { nodes.add(VPackValue("vertexConditions")); nodes.openObject(); for (auto const& it : _vertexConditions) { nodes.add(VPackValue(basics::StringUtils::itoa(it.first))); it.second->toVelocyPack(nodes, verbose); } nodes.close(); } if (!_edgeConditions.empty()) { nodes.add(VPackValue("edgeConditions")); nodes.openObject(); for (auto& it : _edgeConditions) { nodes.add(VPackValue(basics::StringUtils::itoa(it.first))); it.second->toVelocyPack(nodes, verbose); } nodes.close(); } nodes.add(VPackValue("indexes")); _options->toVelocyPackIndexes(nodes); // And close it: nodes.close(); } /// @brief clone ExecutionNode recursively ExecutionNode* TraversalNode::clone(ExecutionPlan* plan, bool withDependencies, bool withProperties) const { TRI_ASSERT(!_optionsBuild); auto tmp = std::make_unique(*_options.get()); auto c = new TraversalNode(plan, _id, _vocbase, _edgeColls, _vertexColls, _inVariable, _vertexId, _directions, tmp); if (usesVertexOutVariable()) { auto vertexOutVariable = _vertexOutVariable; if (withProperties) { vertexOutVariable = plan->getAst()->variables()->createVariable(vertexOutVariable); } TRI_ASSERT(vertexOutVariable != nullptr); c->setVertexOutput(vertexOutVariable); } if (usesEdgeOutVariable()) { auto edgeOutVariable = _edgeOutVariable; if (withProperties) { edgeOutVariable = plan->getAst()->variables()->createVariable(edgeOutVariable); } TRI_ASSERT(edgeOutVariable != nullptr); c->setEdgeOutput(edgeOutVariable); } if (usesPathOutVariable()) { auto pathOutVariable = _pathOutVariable; if (withProperties) { pathOutVariable = plan->getAst()->variables()->createVariable(pathOutVariable); } TRI_ASSERT(pathOutVariable != nullptr); c->setPathOutput(pathOutVariable); } c->_conditionVariables.reserve(_conditionVariables.size()); for (auto const& it: _conditionVariables) { c->_conditionVariables.emplace(it->clone()); } #ifdef TRI_ENABLE_MAINTAINER_MODE checkConditionsDefined(); #endif // Temporary Filter Objects c->_tmpObjVariable = _tmpObjVariable; c->_tmpObjVarNode = _tmpObjVarNode; c->_tmpIdNode = _tmpIdNode; // Filter Condition Parts c->_fromCondition = _fromCondition->clone(_plan->getAst()); c->_toCondition = _toCondition->clone(_plan->getAst()); c->_globalEdgeConditions.insert(c->_globalEdgeConditions.end(), _globalEdgeConditions.begin(), _globalEdgeConditions.end()); c->_globalVertexConditions.insert(c->_globalVertexConditions.end(), _globalVertexConditions.begin(), _globalVertexConditions.end()); for (auto const& it : _edgeConditions) { // Copy the builder auto ecBuilder = std::make_unique(this, it.second.get()); c->_edgeConditions.emplace(it.first, std::move(ecBuilder)); } for (auto const& it : _vertexConditions) { c->_vertexConditions.emplace(it.first, it.second->clone(_plan->getAst())); } #ifdef TRI_ENABLE_MAINTAINER_MODE c->checkConditionsDefined(); #endif cloneHelper(c, plan, withDependencies, withProperties); return static_cast(c); } /// @brief the cost of a traversal node double TraversalNode::estimateCost(size_t& nrItems) const { return _options->estimateCost(nrItems); } void TraversalNode::prepareOptions() { if (_optionsBuild) { return; } TRI_ASSERT(!_optionsBuild); _options->_tmpVar = _tmpObjVariable; size_t numEdgeColls = _edgeColls.size(); TraversalEdgeConditionBuilder globalEdgeConditionBuilder(this); for (auto& it : _globalEdgeConditions) { globalEdgeConditionBuilder.addConditionPart(it); } Ast* ast = _plan->getAst(); // Compute Edge Indexes. First default indexes: for (size_t i = 0; i < numEdgeColls; ++i) { auto dir = _directions[i]; switch (dir) { case TRI_EDGE_IN: _options->addLookupInfo( ast, _edgeColls[i]->getName(), StaticStrings::ToString, globalEdgeConditionBuilder.getInboundCondition()->clone(ast)); break; case TRI_EDGE_OUT: _options->addLookupInfo( ast, _edgeColls[i]->getName(), StaticStrings::FromString, globalEdgeConditionBuilder.getOutboundCondition()->clone(ast)); break; case TRI_EDGE_ANY: TRI_ASSERT(false); break; } } for (auto& it : _edgeConditions) { uint64_t depth = it.first; // We probably have to adopt minDepth. We cannot fulfill a condition of larger depth anyway auto& builder = it.second; for (auto& it : _globalEdgeConditions) { builder->addConditionPart(it); } for (size_t i = 0; i < numEdgeColls; ++i) { auto dir = _directions[i]; // TODO we can optimize here. indexCondition and Expression could be // made non-overlapping. switch (dir) { case TRI_EDGE_IN: _options->addDepthLookupInfo( ast, _edgeColls[i]->getName(), StaticStrings::ToString, builder->getInboundCondition()->clone(ast), depth); break; case TRI_EDGE_OUT: _options->addDepthLookupInfo( ast, _edgeColls[i]->getName(), StaticStrings::FromString, builder->getOutboundCondition()->clone(ast), depth); break; case TRI_EDGE_ANY: TRI_ASSERT(false); break; } } } for (auto& it : _vertexConditions) { // We inject the base conditions as well here. for (auto const& jt : _globalVertexConditions) { it.second->addMember(jt); } _options->_vertexExpressions.emplace(it.first, new Expression(ast, it.second)); TRI_ASSERT(!_options->_vertexExpressions[it.first]->isV8()); } if (!_globalVertexConditions.empty()) { auto cond = _plan->getAst()->createNodeNaryOperator(NODE_TYPE_OPERATOR_NARY_AND); for (auto const& it : _globalVertexConditions) { cond->addMember(it); } _options->_baseVertexExpression = new Expression(ast, cond); TRI_ASSERT(!_options->_baseVertexExpression->isV8()); } // If we use the path output the cache should activate document // caching otherwise it is not worth it. _options->activateCache(false); _optionsBuild = true; } void TraversalNode::addEngine(TraverserEngineID const& engine, arangodb::ServerID const& server) { TRI_ASSERT(arangodb::ServerState::instance()->isCoordinator()); _engines.emplace(server, engine); } /// @brief remember the condition to execute for early traversal abortion. void TraversalNode::setCondition(arangodb::aql::Condition* condition) { std::unordered_set varsUsedByCondition; Ast::getReferencedVariables(condition->root(), varsUsedByCondition); for (auto const& oneVar : varsUsedByCondition) { if ((_vertexOutVariable == nullptr || oneVar->id != _vertexOutVariable->id) && (_edgeOutVariable == nullptr || oneVar->id != _edgeOutVariable->id) && (_pathOutVariable == nullptr || oneVar->id != _pathOutVariable->id) && (_inVariable == nullptr || oneVar->id != _inVariable->id)) { _conditionVariables.emplace(oneVar); } } _condition = condition; } void TraversalNode::registerCondition(bool isConditionOnEdge, uint64_t conditionLevel, AstNode const* condition) { Ast::getReferencedVariables(condition, _conditionVariables); if (isConditionOnEdge) { auto const& it = _edgeConditions.find(conditionLevel); if (it == _edgeConditions.end()) { auto builder = std::make_unique(this); builder->addConditionPart(condition); _edgeConditions.emplace(conditionLevel, std::move(builder)); } else { it->second->addConditionPart(condition); } } else { auto const& it = _vertexConditions.find(conditionLevel); if (it == _vertexConditions.end()) { auto cond = _plan->getAst()->createNodeNaryOperator(NODE_TYPE_OPERATOR_NARY_AND); cond->addMember(condition); _vertexConditions.emplace(conditionLevel, cond); } else { it->second->addMember(condition); } } } void TraversalNode::registerGlobalCondition(bool isConditionOnEdge, AstNode const* condition) { Ast::getReferencedVariables(condition, _conditionVariables); if (isConditionOnEdge) { _globalEdgeConditions.emplace_back(condition); } else { _globalVertexConditions.emplace_back(condition); } } arangodb::traverser::TraverserOptions* TraversalNode::options() const { return _options.get(); } AstNode* TraversalNode::getTemporaryRefNode() const { return _tmpObjVarNode; } Variable const* TraversalNode::getTemporaryVariable() const { return _tmpObjVariable; } void TraversalNode::getConditionVariables( std::vector& res) const { for (auto const& it : _conditionVariables) { if (it != _tmpObjVariable) { res.emplace_back(it); } } } #ifndef USE_ENTERPRISE void TraversalNode::enhanceEngineInfo(VPackBuilder& builder) const { if (_graphObj != nullptr) { _graphObj->enhanceEngineInfo(builder); } else { // TODO enhance the Info based on EdgeCollections. } } #endif #ifdef TRI_ENABLE_MAINTAINER_MODE void TraversalNode::checkConditionsDefined() const { TRI_ASSERT(_tmpObjVariable != nullptr); TRI_ASSERT(_tmpObjVarNode != nullptr); TRI_ASSERT(_tmpIdNode != nullptr); TRI_ASSERT(_fromCondition != nullptr); TRI_ASSERT(_fromCondition->type == NODE_TYPE_OPERATOR_BINARY_EQ); TRI_ASSERT(_toCondition != nullptr); TRI_ASSERT(_toCondition->type == NODE_TYPE_OPERATOR_BINARY_EQ); } #endif