/// @brief Implementation of Shortest Path Execution Node /// /// @file arangod/Aql/ShortestPathNode.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 "ShortestPathNode.h" #include "Aql/Ast.h" #include "Aql/Collection.h" #include "Aql/ExecutionPlan.h" #include "Aql/ShortestPathBlock.h" #include "Aql/Query.h" #include "Graph/ShortestPathOptions.h" #include "Indexes/Index.h" #include "Utils/CollectionNameResolver.h" #include "VocBase/LogicalCollection.h" #include #include using namespace arangodb; using namespace arangodb::basics; using namespace arangodb::aql; using namespace arangodb::graph; static void parseNodeInput(AstNode const* node, std::string& id, Variable const*& variable) { switch (node->type) { case NODE_TYPE_REFERENCE: variable = static_cast(node->getData()); id = ""; break; case NODE_TYPE_VALUE: if (node->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."); } variable = nullptr; id = node->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."); } } ShortestPathNode::ShortestPathNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase, AstNode const* direction, AstNode const* start, AstNode const* target, AstNode const* graph, std::unique_ptr options) : GraphNode(plan, id, vocbase, direction, graph, std::move(options)), _inStartVariable(nullptr), _inTargetVariable(nullptr), _fromCondition(nullptr), _toCondition(nullptr) { TRI_ASSERT(start != nullptr); TRI_ASSERT(target != nullptr); TRI_ASSERT(graph != nullptr); auto ast = _plan->getAst(); // Let us build the conditions on _from and _to. Just in case we need them. { auto const* access = ast->createNodeAttributeAccess( getTemporaryRefNode(), StaticStrings::FromString.c_str(), StaticStrings::FromString.length()); auto const* cond = ast->createNodeBinaryOperator( NODE_TYPE_OPERATOR_BINARY_EQ, access, _tmpIdNode); _fromCondition = ast->createNodeNaryOperator(NODE_TYPE_OPERATOR_NARY_AND); _fromCondition->addMember(cond); } TRI_ASSERT(_fromCondition != nullptr); { auto const* access = ast->createNodeAttributeAccess( getTemporaryRefNode(), StaticStrings::ToString.c_str(), StaticStrings::ToString.length()); auto const* cond = ast->createNodeBinaryOperator( NODE_TYPE_OPERATOR_BINARY_EQ, access, _tmpIdNode); _toCondition = ast->createNodeNaryOperator(NODE_TYPE_OPERATOR_NARY_AND); _toCondition->addMember(cond); } TRI_ASSERT(_toCondition != nullptr); parseNodeInput(start, _startVertexId, _inStartVariable); parseNodeInput(target, _targetVertexId, _inTargetVariable); } /// @brief Internal constructor to clone the node. ShortestPathNode::ShortestPathNode( ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase, std::vector> const& edgeColls, std::vector> const& vertexColls, std::vector const& directions, Variable const* inStartVariable, std::string const& startVertexId, Variable const* inTargetVariable, std::string const& targetVertexId, std::unique_ptr options) : GraphNode(plan, id, vocbase, edgeColls, vertexColls, directions, std::move(options)), _inStartVariable(inStartVariable), _startVertexId(startVertexId), _inTargetVariable(inTargetVariable), _targetVertexId(targetVertexId), _fromCondition(nullptr), _toCondition(nullptr) {} ShortestPathNode::~ShortestPathNode() {} ShortestPathNode::ShortestPathNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base) : GraphNode(plan, base), _inStartVariable(nullptr), _inTargetVariable(nullptr), _fromCondition(nullptr), _toCondition(nullptr) { // Start Vertex if (base.hasKey("startInVariable")) { _inStartVariable = Variable::varFromVPack(plan->getAst(), base, "startInVariable"); } else { VPackSlice v = base.get("startVertexId"); if (!v.isString()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN, "start vertex must be a string"); } _startVertexId = v.copyString(); if (_startVertexId.empty()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN, "start vertex mustn't be empty"); } } // Target Vertex if (base.hasKey("targetInVariable")) { _inTargetVariable = Variable::varFromVPack(plan->getAst(), base, "targetInVariable"); } else { VPackSlice v = base.get("targetVertexId"); if (!v.isString()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN, "target vertex must be a string"); } _targetVertexId = v.copyString(); if (_targetVertexId.empty()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN, "target vertex mustn't be empty"); } } // TODO SP Difference is here: /* 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); } auto const& eColls = _graphObj->edgeCollections(); for (auto const& it : eColls) { _edgeColls.push_back(it); // if there are twice as many directions as collections, this means we // have a shortest path with direction ANY. we must add each collection // twice then if (_directions.size() == 2 * eColls.size()) { // add collection again _edgeColls.push_back(it); } } } 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 of edge collection names for (auto const& it : VPackArrayIterator(_graphInfo.slice())) { if (!it.isString()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN, "graph has to be an array of strings."); } _edgeColls.emplace_back(it.copyString()); } if (_edgeColls.empty()) { THROW_ARANGO_EXCEPTION_MESSAGE( TRI_ERROR_QUERY_BAD_JSON_PLAN, "graph has to be a non empty array of strings."); } } */ // 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")); } void ShortestPathNode::toVelocyPackHelper(VPackBuilder& nodes, bool verbose) const { GraphNode::toVelocyPackHelper(nodes, verbose); // call base class method // In variables if (usesStartInVariable()) { nodes.add(VPackValue("startInVariable")); startInVariable()->toVelocyPack(nodes); } else { nodes.add("startVertexId", VPackValue(_startVertexId)); } if (usesTargetInVariable()) { nodes.add(VPackValue("targetInVariable")); targetInVariable()->toVelocyPack(nodes); } else { nodes.add("targetVertexId", VPackValue(_targetVertexId)); } // Filter Conditions TRI_ASSERT(_fromCondition != nullptr); nodes.add(VPackValue("fromCondition")); _fromCondition->toVelocyPack(nodes, verbose); TRI_ASSERT(_toCondition != nullptr); nodes.add(VPackValue("toCondition")); _toCondition->toVelocyPack(nodes, verbose); // And close it: nodes.close(); } /// @brief creates corresponding ExecutionBlock std::unique_ptr ShortestPathNode::createBlock( ExecutionEngine& engine, std::unordered_map const&, std::unordered_set const& ) const { return std::make_unique(&engine, this); } ExecutionNode* ShortestPathNode::clone(ExecutionPlan* plan, bool withDependencies, bool withProperties) const { TRI_ASSERT(!_optionsBuilt); auto oldOpts = static_cast(options()); std::unique_ptr tmp = std::make_unique(*oldOpts); auto c = new ShortestPathNode(plan, _id, _vocbase, _edgeColls, _vertexColls, _directions, _inStartVariable, _startVertexId, _inTargetVariable, _targetVertexId, std::move(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); } // 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()); cloneHelper(c, withDependencies, withProperties); return ExecutionNode::castTo(c); } double ShortestPathNode::estimateCost(size_t& nrItems) const { size_t incoming = 0; double depCost = _dependencies.at(0)->getCost(incoming); return depCost + (incoming * _options->estimateCost(nrItems)); } void ShortestPathNode::prepareOptions() { if (_optionsBuilt) { return; } TRI_ASSERT(!_optionsBuilt); size_t numEdgeColls = _edgeColls.size(); Ast* ast = _plan->getAst(); auto opts = static_cast(options()); opts->setVariable(getTemporaryVariable()); // Compute Indexes. for (size_t i = 0; i < numEdgeColls; ++i) { auto dir = _directions[i]; switch (dir) { case TRI_EDGE_IN: opts->addLookupInfo(_plan, _edgeColls[i]->getName(), StaticStrings::ToString, _toCondition->clone(ast)); opts->addReverseLookupInfo(_plan, _edgeColls[i]->getName(), StaticStrings::FromString, _fromCondition->clone(ast)); break; case TRI_EDGE_OUT: opts->addLookupInfo(_plan, _edgeColls[i]->getName(), StaticStrings::FromString, _fromCondition->clone(ast)); opts->addReverseLookupInfo(_plan, _edgeColls[i]->getName(), StaticStrings::ToString, _toCondition->clone(ast)); break; case TRI_EDGE_ANY: TRI_ASSERT(false); break; } } // If we use the path output the cache should activate document // caching otherwise it is not worth it. if (ServerState::instance()->isCoordinator()) { _options->activateCache(false, engines()); } else { _options->activateCache(false, nullptr); } _optionsBuilt = true; }