diff --git a/arangod/Aql/GraphNode.cpp b/arangod/Aql/GraphNode.cpp index 34d34a9ffb..b4e656d6f0 100644 --- a/arangod/Aql/GraphNode.cpp +++ b/arangod/Aql/GraphNode.cpp @@ -81,7 +81,7 @@ GraphNode::GraphNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase, // Direction is already the correct Integer. // Is not inserted by user but by enum. - TRI_edge_direction_e baseDirection = parseDirection(direction); + _defaultDirection = parseDirection(direction); std::unordered_map seenCollections; @@ -130,7 +130,7 @@ GraphNode::GraphNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase, dir = parseDirection(col->getMember(0)); col = col->getMember(1); } else { - dir = baseDirection; + dir = _defaultDirection; } std::string eColName = col->getString(); @@ -219,7 +219,7 @@ GraphNode::GraphNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase, if (ServerState::instance()->isRunningInCluster()) { auto c = ci->getCollection(_vocbase->name(), n); if (!c->isSmart()) { - addEdgeCollection(n, baseDirection); + addEdgeCollection(n, _defaultDirection); } else { std::vector names; if (_isSmart) { @@ -228,11 +228,11 @@ GraphNode::GraphNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase, names = c->realNamesForRead(); } for (auto const& name : names) { - addEdgeCollection(name, baseDirection); + addEdgeCollection(name, _defaultDirection); } } } else { - addEdgeCollection(n, baseDirection); + addEdgeCollection(n, _defaultDirection); } } @@ -263,25 +263,30 @@ GraphNode::GraphNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& bas _options(nullptr), _optionsBuilt(false), _isSmart(false) { + + auto uint64ToDir = [](uint64_t d) -> TRI_edge_direction_e { + switch (d) { + case 1: + return TRI_EDGE_IN; + case 2: + return TRI_EDGE_OUT; + case 0: + TRI_ASSERT(false); + default: + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER, + "Invalid direction value"); + break; + } + }; + + uint64_t dir = arangodb::basics::VelocyPackHelper::stringUInt64(base.get("defaultDirection")); + _defaultDirection = uint64ToDir(dir); + // Directions 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 1: - d = TRI_EDGE_IN; - break; - case 2: - d = TRI_EDGE_OUT; - break; - case 0: - TRI_ASSERT(false); - default: - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER, - "Invalid direction value"); - break; - } + TRI_edge_direction_e d = uint64ToDir(dir); _directions.emplace_back(d); } @@ -418,6 +423,9 @@ void GraphNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) const { _graphObj->toVelocyPack(nodes); } + // Default Direction + nodes.add("defaultDirection", VPackValue(_defaultDirection)); + // Directions nodes.add(VPackValue("directions")); { diff --git a/arangod/Aql/GraphNode.h b/arangod/Aql/GraphNode.h index 1e9e388e27..64e453a2a8 100644 --- a/arangod/Aql/GraphNode.h +++ b/arangod/Aql/GraphNode.h @@ -163,6 +163,9 @@ class GraphNode : public ExecutionNode { /// @brief the vertex collection names std::vector> _vertexColls; + /// @brief The default direction given in the query + TRI_edge_direction_e _defaultDirection; + /// @brief The directions edges are followed std::vector _directions; diff --git a/js/common/modules/@arangodb/aql/explainer.js b/js/common/modules/@arangodb/aql/explainer.js index 6cd4a5e4ad..008ca191cd 100644 --- a/js/common/modules/@arangodb/aql/explainer.js +++ b/js/common/modules/@arangodb/aql/explainer.js @@ -1260,7 +1260,7 @@ function processQuery(query, explain, planIndex) { parts.push(variableName(node.edgeOutVariable) + ' ' + annotation('/* edge */')); } translate = ['ANY', 'INBOUND', 'OUTBOUND']; - let defaultDirection = node.directions[0]; + let defaultDirection = node.defaultDirection; rc = `${keyword("FOR")} ${parts.join(", ")} ${keyword("IN")} ${keyword(translate[defaultDirection])} ${keyword("SHORTEST_PATH")} `; if (node.hasOwnProperty('startVertexId')) { rc += `'${value(node.startVertexId)}'`; @@ -1277,13 +1277,25 @@ function processQuery(query, explain, planIndex) { rc += ` ${annotation("/* targetnode */")} `; if (Array.isArray(node.graph)) { - rc += node.graph.map(function (g, index) { + var directions = [], d; + for (var i = 0; i < node.edgeCollections.length; ++i) { + var isLast = (i + 1 === node.edgeCollections.length); + d = node.directions[i]; + if (!isLast && node.edgeCollections[i] === node.edgeCollections[i + 1]) { + // direction ANY is represented by two traversals: an INBOUND and an OUTBOUND traversal + // on the same collection + d = 0; // ANY + ++i; + } + directions.push({ collection: node.edgeCollections[i], direction: d }); + } + rc += directions.map(function (g, index) { var tmp = ''; - if (node.directions[index] !== defaultDirection) { - tmp += keyword(translate[node.directions[index]]); + if (g.direction !== defaultDirection) { + tmp += keyword(translate[g.direction]); tmp += ' '; } - return tmp + collection(g); + return tmp + collection(g.collection); }).join(', '); } else { rc += keyword('GRAPH') + " '" + value(node.graph) + "'"; @@ -1320,7 +1332,7 @@ function processQuery(query, explain, planIndex) { parts.push(variableName(node.pathOutVariable) + ' ' + annotation('/* path */')); } translate = ['ANY', 'INBOUND', 'OUTBOUND']; - let defaultDirection = node.directions[0]; + let defaultDirection = node.defaultDirection; rc = `${keyword("FOR")} ${parts.join(", ")} ${keyword("IN")} ${keyword(translate[defaultDirection])} ${keyword("K_SHORTEST_PATHS")} `; if (node.hasOwnProperty('startVertexId')) { rc += `'${value(node.startVertexId)}'`; @@ -1337,13 +1349,25 @@ function processQuery(query, explain, planIndex) { rc += ` ${annotation("/* targetnode */")} `; if (Array.isArray(node.graph)) { - rc += node.graph.map(function (g, index) { + var directions = [], d; + for (var i = 0; i < node.edgeCollections.length; ++i) { + var isLast = (i + 1 === node.edgeCollections.length); + d = node.directions[i]; + if (!isLast && node.edgeCollections[i] === node.edgeCollections[i + 1]) { + // direction ANY is represented by two traversals: an INBOUND and an OUTBOUND traversal + // on the same collection + d = 0; // ANY + ++i; + } + directions.push({ collection: node.edgeCollections[i], direction: d }); + } + rc += directions.map(function (g, index) { var tmp = ''; - if (node.directions[index] !== defaultDirection) { - tmp += keyword(translate[node.directions[index]]); + if (g.direction !== defaultDirection) { + tmp += keyword(translate[g.direction]); tmp += ' '; } - return tmp + collection(g); + return tmp + collection(g.collection); }).join(', '); } else { rc += keyword('GRAPH') + " '" + value(node.graph) + "'";