1
0
Fork 0

Feature/aql traversal prune (#8255)

Allow PRUNE in the AQL Traverser.
This commit is contained in:
Michael Hackstein 2019-02-27 11:43:06 +01:00 committed by GitHub
parent b04cd607f4
commit 6e8d43b2da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 3357 additions and 2229 deletions

View File

@ -1,6 +1,17 @@
devel devel
----- -----
* add "PRUNE <condition>" to AQL Traversals. This allows to early abort searching of
unnecessary branches within a traversal.
PRUNE is only allowed in the Traversal statement and only between the graphdefinition
and the options of the traversal.
e.g.:
FOR v, e, p IN 1..3 OUTBOUND @source GRAPH "myGraph"
PRUNE v.value == "bar"
OPTIONS {} /* These options remain optional */
RETURN v
for more details refer to the documentation chapter.
* fixed a display issues when editing a graph within the web ui * fixed a display issues when editing a graph within the web ui
* fixed some escaping issues within the web ui. * fixed some escaping issues within the web ui.

View File

@ -17,6 +17,7 @@ FOR vertex[, edge[, path]]
IN [min[..max]] IN [min[..max]]
OUTBOUND|INBOUND|ANY startVertex OUTBOUND|INBOUND|ANY startVertex
GRAPH graphName GRAPH graphName
[PRUNE pruneCondition]
[OPTIONS options] [OPTIONS options]
``` ```
- `WITH`: optional for single server instances, but required for - `WITH`: optional for single server instances, but required for
@ -48,6 +49,22 @@ FOR vertex[, edge[, path]]
- `GRAPH` **graphName** (string): the name identifying the named graph. - `GRAPH` **graphName** (string): the name identifying the named graph.
Its vertex and edge collections will be looked up. Note that the graph name Its vertex and edge collections will be looked up. Note that the graph name
is like a regular string, hence it must be enclosed by quote marks. is like a regular string, hence it must be enclosed by quote marks.
- `PRUNE` **condition** (AQL condition, *optional*, (since version 3.4.5)):
A condition, like in a FILTER statement, which will be evaluated in every step
of the traversal, as early as possible. The semantics of this condition is as follows:
* If the condition evaluates to `true` this path will be considered as a result,
it might still be post filtered or ignored due to depth constraints. However
the search will not continue from this path, namely there will be no
result having this path as a prefix.
e.g.: Take the path: `(A) -> (B) -> (C)` starting at `A` and PRUNE on `B`
will result in `(A)` and `(A) -> (B)` beeing valid paths, and `(A) -> (B) -> (C)`
not returned, it got pruned on B.
* If the condition evaluates to `false` we will continue our search beyond
this path.
There is only one `PRUNE` condition possible, but it can contain an arbitrary amount
of `AND` or `OR` statements.
Also note that you can use the output variables of this traversal in the `PRUNE`,
as well as all variables defined before this Traversal statement.
- `OPTIONS` **options** (object, *optional*): used to modify the execution of the - `OPTIONS` **options** (object, *optional*): used to modify the execution of the
traversal. Only the following attributes have an effect, all others are ignored: traversal. Only the following attributes have an effect, all others are ignored:
- **uniqueVertices** (string): optionally ensure vertex uniqueness - **uniqueVertices** (string): optionally ensure vertex uniqueness
@ -79,6 +96,7 @@ FOR vertex[, edge[, path]]
IN [min[..max]] IN [min[..max]]
OUTBOUND|INBOUND|ANY startVertex OUTBOUND|INBOUND|ANY startVertex
edgeCollection1, ..., edgeCollectionN edgeCollection1, ..., edgeCollectionN
[PRUNE pruneCondition]
[OPTIONS options] [OPTIONS options]
``` ```
@ -138,10 +156,62 @@ combined filters cannot.
The following examples are based on the [traversal graph](../../Manual/Graphs/index.html#the-traversal-graph). The following examples are based on the [traversal graph](../../Manual/Graphs/index.html#the-traversal-graph).
### Pruning (since version 3.4.5)
Pruning is the easiest variant to formulate conditions to reduce the amount of data
to be checked during a search. So it allows to improve query performance and reduces
the amount of overhead generated by the query. Pruning can be executed on the
vertex, the edge and the path and any variable defined before.
See examples:
@startDocuBlockInline GRAPHTRAV_graphPruneEdges
@EXAMPLE_AQL{GRAPHTRAV_graphFilterEdges}
@DATASET{traversalGraph}
FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH 'traversalGraph'
PRUNE e.theTruth == true
RETURN { vertices: p.vertices[*]._key, edges: p.edges[*].label }
@END_EXAMPLE_AQL
@endDocuBlock GRAPHTRAV_graphPruneEdges
This will search until it sees an edge having `theTruth == true`.
The path with this edge will be returned, the search will not
continue after this edge.
Namely all responses either have no edge with `theTruth == true`
or the last edge on the path has `theTruth == true`.
@startDocuBlockInline GRAPHTRAV_graphPruneVertices
@EXAMPLE_AQL{GRAPHTRAV_graphFilterEdges}
@DATASET{traversalGraph}
FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH 'traversalGraph'
PRUNE v._key == 'G'
FILTER v._key == 'G'
RETURN { vertices: p.vertices[*]._key, edges: p.edges[*].label }
@END_EXAMPLE_AQL
@endDocuBlock GRAPHTRAV_graphPruneVertices
This will search for all paths from the source `circles/A` to the vertex `circles/G`.
This is done with first the PRUNE which makes sure we stop search as soon as we have found
`G` and we will not go beyond `G` and via a loop return to it.
With the second filter, we remove all paths that do not end in `G` namely
all shorter ones that have not been cut out by prune.
Hence the list of all paths from `A` to `G` are returned.
Note you can also prune as soon as you reach a certain collection with the following
example:
@startDocuBlockInline GRAPHTRAV_graphPruneCollection
@EXAMPLE_AQL{GRAPHTRAV_graphFilterEdges}
@DATASET{traversalGraph}
FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH 'traversalGraph'
PRUNE IS_SAME_COLLECTION('circles', v)
RETURN { vertices: p.vertices[*]._key, edges: p.edges[*].label }
@END_EXAMPLE_AQL
@endDocuBlock GRAPHTRAV_graphPruneCollection
### Filtering on paths ### Filtering on paths
Filtering on paths allows for the most powerful filtering and may have the Filtering on paths allows for the second most powerful filtering and may have the
highest impact on performance. Using the path variable you can filter on second highest impact on performance. Using the path variable you can filter on
specific iteration depths. You can filter for absolute positions in the path specific iteration depths. You can filter for absolute positions in the path
by specifying a positive number (which then qualifies for the optimizations), by specifying a positive number (which then qualifies for the optimizations),
or relative positions to the end of the path by specifying a negative number. or relative positions to the end of the path by specifying a negative number.

View File

@ -1304,129 +1304,65 @@ AstNode* Ast::createNodeCollectionDirection(uint64_t direction, AstNode const* c
return node; return node;
} }
/// @brief create an AST traversal node with only vertex variable /// @brief create an AST traversal node
AstNode* Ast::createNodeTraversal(char const* vertexVarName, size_t vertexVarLength, AstNode* Ast::createNodeTraversal(AstNode const* outVars, AstNode const* graphInfo) {
AstNode const* direction, AstNode const* start, TRI_ASSERT(outVars->type == NODE_TYPE_ARRAY);
AstNode const* graph, AstNode const* options) { TRI_ASSERT(graphInfo->type == NODE_TYPE_ARRAY);
if (vertexVarName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_TRAVERSAL); AstNode* node = createNode(NODE_TYPE_TRAVERSAL);
node->reserve(5); node->reserve(outVars->numMembers() + graphInfo->numMembers());
if (options == nullptr) { TRI_ASSERT(graphInfo->numMembers() == 5);
// no options given. now use default options TRI_ASSERT(outVars->numMembers() > 0);
options = &NopNode; TRI_ASSERT(outVars->numMembers() < 4);
// Add GraphInfo
for (size_t i = 0; i < graphInfo->numMembers(); ++i) {
node->addMember(graphInfo->getMemberUnchecked(i));
} }
node->addMember(direction); // Add variables
node->addMember(start); for (size_t i = 0; i < outVars->numMembers(); ++i) {
node->addMember(graph); node->addMember(outVars->getMemberUnchecked(i));
node->addMember(options); }
TRI_ASSERT(node->numMembers() == graphInfo->numMembers() + outVars->numMembers());
AstNode* vertexVar = createNodeVariable(vertexVarName, vertexVarLength, false);
node->addMember(vertexVar);
TRI_ASSERT(node->numMembers() == 5);
_containsTraversal = true; _containsTraversal = true;
return node; return node;
} }
/// @brief create an AST traversal node with vertex and edge variable /// @brief create an AST shortest path node
AstNode* Ast::createNodeTraversal(char const* vertexVarName, size_t vertexVarLength, AstNode* Ast::createNodeShortestPath(AstNode const* outVars, AstNode const* graphInfo) {
char const* edgeVarName, size_t edgeVarLength, TRI_ASSERT(outVars->type == NODE_TYPE_ARRAY);
AstNode const* direction, AstNode const* start, TRI_ASSERT(graphInfo->type == NODE_TYPE_ARRAY);
AstNode const* graph, AstNode const* options) {
if (edgeVarName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNodeTraversal(vertexVarName, vertexVarLength, direction,
start, graph, options);
AstNode* edgeVar = createNodeVariable(edgeVarName, edgeVarLength, false);
node->addMember(edgeVar);
TRI_ASSERT(node->numMembers() == 6);
_containsTraversal = true;
return node;
}
/// @brief create an AST traversal node with vertex, edge and path variable
AstNode* Ast::createNodeTraversal(char const* vertexVarName, size_t vertexVarLength,
char const* edgeVarName, size_t edgeVarLength,
char const* pathVarName, size_t pathVarLength,
AstNode const* direction, AstNode const* start,
AstNode const* graph, AstNode const* options) {
if (pathVarName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNodeTraversal(vertexVarName, vertexVarLength, edgeVarName,
edgeVarLength, direction, start, graph, options);
AstNode* pathVar = createNodeVariable(pathVarName, pathVarLength, false);
node->addMember(pathVar);
TRI_ASSERT(node->numMembers() == 7);
_containsTraversal = true;
return node;
}
/// @brief create an AST shortest path node with only vertex variable
AstNode* Ast::createNodeShortestPath(char const* vertexVarName,
size_t vertexVarLength, uint64_t direction,
AstNode const* start, AstNode const* target,
AstNode const* graph, AstNode const* options) {
if (vertexVarName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_SHORTEST_PATH); AstNode* node = createNode(NODE_TYPE_SHORTEST_PATH);
node->reserve(outVars->numMembers() + graphInfo->numMembers());
node->reserve(6); TRI_ASSERT(graphInfo->numMembers() == 5);
TRI_ASSERT(outVars->numMembers() > 0);
TRI_ASSERT(outVars->numMembers() < 3);
if (options == nullptr) { // Add GraphInfo
// no options given. now use default options for (size_t i = 0; i < graphInfo->numMembers(); ++i) {
options = &NopNode; node->addMember(graphInfo->getMemberUnchecked(i));
} }
AstNode* dir = createNodeValueInt(direction);
node->addMember(dir);
node->addMember(start);
node->addMember(target);
node->addMember(graph);
node->addMember(options);
AstNode* vertexVar = createNodeVariable(vertexVarName, vertexVarLength, false); // Add variables
node->addMember(vertexVar); for (size_t i = 0; i < outVars->numMembers(); ++i) {
node->addMember(outVars->getMemberUnchecked(i));
}
TRI_ASSERT(node->numMembers() == graphInfo->numMembers() + outVars->numMembers());
TRI_ASSERT(node->numMembers() == 6); _containsTraversal = true;
return node; return node;
} }
/// @brief create an AST shortest path node with vertex and edge variable AstNode const* Ast::createNodeOptions(AstNode const* options) const {
AstNode* Ast::createNodeShortestPath(char const* vertexVarName, if (options != nullptr) {
size_t vertexVarLength, char const* edgeVarName, return options;
size_t edgeVarLength, uint64_t direction,
AstNode const* start, AstNode const* target,
AstNode const* graph, AstNode const* options) {
if (edgeVarName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
} }
return &NopNode;
AstNode* node = createNodeShortestPath(vertexVarName, vertexVarLength, direction,
start, target, graph, options);
AstNode* edgeVar = createNodeVariable(edgeVarName, edgeVarLength, false);
node->addMember(edgeVar);
TRI_ASSERT(node->numMembers() == 7);
return node;
} }
/// @brief create an AST function call node /// @brief create an AST function call node

View File

@ -355,27 +355,16 @@ class Ast {
/// @brief create an AST direction node /// @brief create an AST direction node
AstNode* createNodeCollectionDirection(uint64_t, AstNode const*); AstNode* createNodeCollectionDirection(uint64_t, AstNode const*);
/// @brief create an AST traversal node with only vertex variable /// @brief create an AST options node:
AstNode* createNodeTraversal(char const*, size_t, AstNode const*, // Will either return Noop noed, if the input is nullptr
AstNode const*, AstNode const*, AstNode const*); // Otherwise return the input node.
AstNode const* createNodeOptions(AstNode const*) const;
/// @brief create an AST traversal node with vertex and edge variable /// @brief create an AST traversal node
AstNode* createNodeTraversal(char const*, size_t, char const*, size_t, AstNode const*, AstNode* createNodeTraversal(AstNode const*, AstNode const*);
AstNode const*, AstNode const*, AstNode const*);
/// @brief create an AST traversal node with vertex, edge and path variable /// @brief create an AST shortest path node
AstNode* createNodeTraversal(char const*, size_t, char const*, size_t, AstNode* createNodeShortestPath(AstNode const*, AstNode const*);
char const*, size_t, AstNode const*,
AstNode const*, AstNode const*, AstNode const*);
/// @brief create an AST shortest path node with only vertex variable
AstNode* createNodeShortestPath(char const*, size_t, uint64_t, AstNode const*,
AstNode const*, AstNode const*, AstNode const*);
/// @brief create an AST shortest path node with vertex and edge variable
AstNode* createNodeShortestPath(char const*, size_t, char const*, size_t,
uint64_t, AstNode const*, AstNode const*,
AstNode const*, AstNode const*);
/// @brief create an AST function call node /// @brief create an AST function call node
AstNode* createNodeFunctionCall(char const* functionName, AstNode const* arguments) { AstNode* createNodeFunctionCall(char const* functionName, AstNode const* arguments) {

View File

@ -206,6 +206,13 @@ std::unique_ptr<graph::BaseOptions> createShortestPathOptions(arangodb::aql::Que
return ret; return ret;
} }
std::unique_ptr<Expression> createPruneExpression(ExecutionPlan* plan, Ast* ast, AstNode* node) {
if (node->type == NODE_TYPE_NOP) {
return nullptr;
}
return std::make_unique<Expression>(plan, ast, node);
}
} // namespace } // namespace
/// @brief create the plan /// @brief create the plan
@ -980,11 +987,11 @@ ExecutionNode* ExecutionPlan::fromNodeForView(ExecutionNode* previous, AstNode c
/// @brief create an execution plan element from an AST FOR TRAVERSAL node /// @brief create an execution plan element from an AST FOR TRAVERSAL node
ExecutionNode* ExecutionPlan::fromNodeTraversal(ExecutionNode* previous, AstNode const* node) { ExecutionNode* ExecutionPlan::fromNodeTraversal(ExecutionNode* previous, AstNode const* node) {
TRI_ASSERT(node != nullptr && node->type == NODE_TYPE_TRAVERSAL); TRI_ASSERT(node != nullptr && node->type == NODE_TYPE_TRAVERSAL);
TRI_ASSERT(node->numMembers() >= 5); TRI_ASSERT(node->numMembers() >= 6);
TRI_ASSERT(node->numMembers() <= 7); TRI_ASSERT(node->numMembers() <= 8);
// the first 3 members are used by traversal internally. // the first 5 members are used by traversal internally.
// The members 4-6, where 5 and 6 are optional, are used // The members 6-8, where 5 and 6 are optional, are used
// as out variables. // as out variables.
AstNode const* direction = node->getMember(0); AstNode const* direction = node->getMember(0);
AstNode const* start = node->getMember(1); AstNode const* start = node->getMember(1);
@ -1009,8 +1016,11 @@ ExecutionNode* ExecutionPlan::fromNodeTraversal(ExecutionNode* previous, AstNode
previous = calc; previous = calc;
} }
// Prune Expression
std::unique_ptr<Expression> pruneExpression = createPruneExpression(this, _ast, node->getMember(3));
auto options = auto options =
createTraversalOptions(getAst()->query(), direction, node->getMember(3)); createTraversalOptions(getAst()->query(), direction, node->getMember(4));
TRI_ASSERT(direction->type == NODE_TYPE_DIRECTION); TRI_ASSERT(direction->type == NODE_TYPE_DIRECTION);
TRI_ASSERT(direction->numMembers() == 2); TRI_ASSERT(direction->numMembers() == 2);
@ -1019,24 +1029,25 @@ ExecutionNode* ExecutionPlan::fromNodeTraversal(ExecutionNode* previous, AstNode
// First create the node // First create the node
auto travNode = new TraversalNode(this, nextId(), &(_ast->query()->vocbase()), auto travNode = new TraversalNode(this, nextId(), &(_ast->query()->vocbase()),
direction, start, graph, std::move(options)); direction, start, graph,
std::move(pruneExpression), std::move(options));
auto variable = node->getMember(4); auto variable = node->getMember(5);
TRI_ASSERT(variable->type == NODE_TYPE_VARIABLE); TRI_ASSERT(variable->type == NODE_TYPE_VARIABLE);
auto v = static_cast<Variable*>(variable->getData()); auto v = static_cast<Variable*>(variable->getData());
TRI_ASSERT(v != nullptr); TRI_ASSERT(v != nullptr);
travNode->setVertexOutput(v); travNode->setVertexOutput(v);
if (node->numMembers() > 5) { if (node->numMembers() > 6) {
// return the edge as well // return the edge as well
variable = node->getMember(5); variable = node->getMember(6);
TRI_ASSERT(variable->type == NODE_TYPE_VARIABLE); TRI_ASSERT(variable->type == NODE_TYPE_VARIABLE);
v = static_cast<Variable*>(variable->getData()); v = static_cast<Variable*>(variable->getData());
TRI_ASSERT(v != nullptr); TRI_ASSERT(v != nullptr);
travNode->setEdgeOutput(v); travNode->setEdgeOutput(v);
if (node->numMembers() > 6) { if (node->numMembers() > 7) {
// return the path as well // return the path as well
variable = node->getMember(6); variable = node->getMember(7);
TRI_ASSERT(variable->type == NODE_TYPE_VARIABLE); TRI_ASSERT(variable->type == NODE_TYPE_VARIABLE);
v = static_cast<Variable*>(variable->getData()); v = static_cast<Variable*>(variable->getData());
TRI_ASSERT(v != nullptr); TRI_ASSERT(v != nullptr);

View File

@ -0,0 +1,123 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2019-2019 ArangoDB 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
////////////////////////////////////////////////////////////////////////////////
#include "InAndOutRowExpressionContext.h"
#include "Aql/AqlItemBlock.h"
#include "Aql/AqlValue.h"
#include "Aql/Variable.h"
#include "Basics/Exceptions.h"
#include <limits.h>
using namespace arangodb;
using namespace arangodb::aql;
InAndOutRowExpressionContext::InAndOutRowExpressionContext(
Query* query, std::vector<Variable const*> const&& vars,
std::vector<RegisterId> const&& regs, size_t vertexVarIdx,
size_t edgeVarIdx, size_t pathVarIdx)
: QueryExpressionContext(query),
_input{CreateInvalidInputRowHint()},
_vars(std::move(vars)),
_regs(std::move(regs)),
_vertexVarIdx(vertexVarIdx),
_edgeVarIdx(edgeVarIdx),
_pathVarIdx(pathVarIdx) {
TRI_ASSERT(_vars.size() == _regs.size());
TRI_ASSERT(_vertexVarIdx < _vars.size() ||
_vertexVarIdx == std::numeric_limits<std::size_t>::max());
TRI_ASSERT(_edgeVarIdx < _vars.size() ||
_edgeVarIdx == std::numeric_limits<std::size_t>::max());
TRI_ASSERT(_pathVarIdx < _vars.size() ||
_pathVarIdx == std::numeric_limits<std::size_t>::max());
}
void InAndOutRowExpressionContext::setInputRow(InputAqlItemRow input) {
TRI_ASSERT(input.isInitialized());
_input = input;
}
AqlValue const& InAndOutRowExpressionContext::getRegisterValue(size_t i) const {
TRI_ASSERT(_input.isInitialized());
TRI_ASSERT(i < _regs.size());
if (i == _vertexVarIdx) {
return _vertexValue;
}
if (i == _edgeVarIdx) {
return _edgeValue;
}
if (i == _pathVarIdx) {
return _pathValue;
}
// Search InputRow
RegisterId const& regId = _regs[i];
TRI_ASSERT(regId < _input.getNrRegisters());
return _input.getValue(regId);
}
AqlValue InAndOutRowExpressionContext::getVariableValue(Variable const* variable, bool doCopy,
bool& mustDestroy) const {
TRI_ASSERT(_input.isInitialized());
for (size_t i = 0; i < _vars.size(); ++i) {
auto const& v = _vars[i];
if (v->id == variable->id) {
TRI_ASSERT(i < _regs.size());
if (doCopy) {
mustDestroy = true;
if (i == _vertexVarIdx) {
return _vertexValue.clone();
}
if (i == _edgeVarIdx) {
return _edgeValue.clone();
}
if (i == _pathVarIdx) {
return _pathValue.clone();
}
// Search InputRow
RegisterId const& regId = _regs[i];
TRI_ASSERT(regId < _input.getNrRegisters());
return _input.getValue(regId).clone();
} else {
mustDestroy = false;
if (i == _vertexVarIdx) {
return _vertexValue;
}
if (i == _edgeVarIdx) {
return _edgeValue;
}
if (i == _pathVarIdx) {
return _pathValue;
}
// Search InputRow
RegisterId const& regId = _regs[i];
TRI_ASSERT(regId < _input.getNrRegisters());
return _input.getValue(regId);
}
}
}
std::string msg("variable not found '");
msg.append(variable->name);
// NOTE: PRUNE is the only feature using this context.
msg.append("' in PRUNE statement");
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, msg.c_str());
}

View File

@ -0,0 +1,99 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2019-2019 ArangoDB 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
////////////////////////////////////////////////////////////////////////////////
#ifndef ARANGOD_AQL_IN_AND_OUT_ROW_EXPRESSION_CONTEXT_H
#define ARANGOD_AQL_IN_AND_OUT_ROW_EXPRESSION_CONTEXT_H 1
#include "QueryExpressionContext.h"
#include "Aql/AqlValue.h"
#include "Aql/InputAqlItemRow.h"
namespace arangodb {
namespace aql {
class Query;
/**
* @brief Context for expression evaluation that allows
* to read registers from Input and Output.
* This is useful for expressions that need
* to evaluate variables created by the active block.
* User needs to make sure that outputblock is written to!
*/
class InAndOutRowExpressionContext final : public QueryExpressionContext {
public:
InAndOutRowExpressionContext(Query* query, std::vector<Variable const*> const&& vars,
std::vector<RegisterId> const&& regs, size_t vertexVarIdx,
size_t edgeVarIdx, size_t pathVarIdx);
~InAndOutRowExpressionContext() {}
void setInputRow(InputAqlItemRow input);
size_t numRegisters() const override { return _regs.size(); }
AqlValue const& getRegisterValue(size_t i) const override;
Variable const* getVariable(size_t i) const override { return _vars[i]; }
AqlValue getVariableValue(Variable const* variable, bool doCopy,
bool& mustDestroy) const override;
bool needsVertexValue() const { return _vertexVarIdx < _regs.size(); }
bool needsEdgeValue() const { return _edgeVarIdx < _regs.size(); }
bool needsPathValue() const { return _pathVarIdx < _regs.size(); }
/// @brief inject the result value when asked for the Vertex data
/// This will not copy ownership of slice content. caller needs to make sure
/// that the buffer stays valid until evaluate is called
void setVertexValue(velocypack::Slice v) {
_vertexValue = AqlValue(AqlValueHintDocumentNoCopy(v.begin()));
}
/// @brief inject the result value when asked for the Edge data
/// This will not copy ownership of slice content. caller needs to make sure
/// that the buffer stays valid until evaluate is called
void setEdgeValue(velocypack::Slice e) {
_edgeValue = AqlValue(AqlValueHintDocumentNoCopy(e.begin()));
}
/// @brief inject the result value when asked for the Path data
/// This will not copy ownership of slice content. caller needs to make sure
/// that the buffer stays valid until evaluate is called
void setPathValue(velocypack::Slice p) {
_pathValue = AqlValue(AqlValueHintDocumentNoCopy(p.begin()));
}
private:
InputAqlItemRow _input;
std::vector<Variable const*> const _vars;
std::vector<RegisterId> const _regs;
size_t const _vertexVarIdx;
size_t const _edgeVarIdx;
size_t const _pathVarIdx;
AqlValue _vertexValue;
AqlValue _edgeValue;
AqlValue _pathValue;
};
} // namespace aql
} // namespace arangodb
#endif

View File

@ -22,6 +22,7 @@
/// @author Jan Steemann /// @author Jan Steemann
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include "OptimizerRules.h"
#include "Aql/AqlItemBlock.h" #include "Aql/AqlItemBlock.h"
#include "Aql/ClusterNodes.h" #include "Aql/ClusterNodes.h"
#include "Aql/CollectNode.h" #include "Aql/CollectNode.h"
@ -54,7 +55,6 @@
#include "GeoIndex/Index.h" #include "GeoIndex/Index.h"
#include "Graph/TraverserOptions.h" #include "Graph/TraverserOptions.h"
#include "Indexes/Index.h" #include "Indexes/Index.h"
#include "OptimizerRules.h"
#include "StorageEngine/EngineSelectorFeature.h" #include "StorageEngine/EngineSelectorFeature.h"
#include "StorageEngine/StorageEngine.h" #include "StorageEngine/StorageEngine.h"
#include "Transaction/Methods.h" #include "Transaction/Methods.h"
@ -5884,14 +5884,19 @@ void arangodb::aql::optimizeTraversalsRule(Optimizer* opt,
// yet, as many traversal internals depend on the number of vertices // yet, as many traversal internals depend on the number of vertices
// found/built // found/built
auto outVariable = traversal->edgeOutVariable(); auto outVariable = traversal->edgeOutVariable();
if (outVariable != nullptr && !n->isVarUsedLater(outVariable)) { std::vector<Variable const*> pruneVars;
traversal->getPruneVariables(pruneVars);
if (outVariable != nullptr && !n->isVarUsedLater(outVariable) &&
std::find(pruneVars.begin(), pruneVars.end(), outVariable) == pruneVars.end()) {
// traversal edge outVariable not used later // traversal edge outVariable not used later
traversal->setEdgeOutput(nullptr); traversal->setEdgeOutput(nullptr);
modified = true; modified = true;
} }
outVariable = traversal->pathOutVariable(); outVariable = traversal->pathOutVariable();
if (outVariable != nullptr && !n->isVarUsedLater(outVariable)) { if (outVariable != nullptr && !n->isVarUsedLater(outVariable) &&
std::find(pruneVars.begin(), pruneVars.end(), outVariable) == pruneVars.end()) {
// traversal path outVariable not used later // traversal path outVariable not used later
traversal->setPathOutput(nullptr); traversal->setPathOutput(nullptr);
modified = true; modified = true;
@ -6038,8 +6043,11 @@ void arangodb::aql::removeTraversalPathVariable(Optimizer* opt,
for (auto const& n : tNodes) { for (auto const& n : tNodes) {
TraversalNode* traversal = ExecutionNode::castTo<TraversalNode*>(n); TraversalNode* traversal = ExecutionNode::castTo<TraversalNode*>(n);
std::vector<Variable const*> pruneVars;
traversal->getPruneVariables(pruneVars);
auto outVariable = traversal->pathOutVariable(); auto outVariable = traversal->pathOutVariable();
if (outVariable != nullptr && !n->isVarUsedLater(outVariable)) { if (outVariable != nullptr && !n->isVarUsedLater(outVariable) &&
std::find(pruneVars.begin(), pruneVars.end(), outVariable) == pruneVars.end()) {
// traversal path outVariable not used later // traversal path outVariable not used later
traversal->setPathOutput(nullptr); traversal->setPathOutput(nullptr);
modified = true; modified = true;

View File

@ -0,0 +1,48 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2019-2019 ArangoDB 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
////////////////////////////////////////////////////////////////////////////////
#include "PruneExpressionEvaluator.h"
#include "Aql/AqlValue.h"
#include "Aql/Expression.h"
#include "Transaction/Methods.h"
using namespace arangodb;
using namespace arangodb::aql;
PruneExpressionEvaluator::PruneExpressionEvaluator(
transaction::Methods* trx, Query* query,
std::vector<Variable const*> const&& vars, std::vector<RegisterId> const&& regs,
size_t vertexVarIdx, size_t edgeVarIdx, size_t pathVarIdx, Expression* expr)
: _trx(trx),
_pruneExpression(expr),
_ctx(query, std::move(vars), std::move(regs), vertexVarIdx, edgeVarIdx, pathVarIdx) {}
PruneExpressionEvaluator::~PruneExpressionEvaluator() = default;
bool PruneExpressionEvaluator::evaluate() {
bool mustDestroy = false;
aql::AqlValue res = _pruneExpression->execute(_trx, &_ctx, mustDestroy);
arangodb::aql::AqlValueGuard guard(res, mustDestroy);
TRI_ASSERT(res.isBoolean());
return res.toBoolean();
}

View File

@ -0,0 +1,74 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2019-2019 ArangoDB 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
////////////////////////////////////////////////////////////////////////////////
#ifndef ARANGOD_AQL_PRUNE_EXPRESSION_EVALUATOR_H
#define ARANGOD_AQL_PRUNE_EXPRESSION_EVALUATOR_H 1
#include "Aql/InAndOutRowExpressionContext.h"
namespace arangodb {
namespace velocypack {
class Slice;
}
namespace transaction {
class Methods;
}
namespace aql {
class Expression;
class Query;
class InputAqlItemRow;
class PruneExpressionEvaluator {
public:
PruneExpressionEvaluator(transaction::Methods* trx, Query* query,
std::vector<Variable const*> const&& vars,
std::vector<RegisterId> const&& regs, size_t vertexVarIdx,
size_t edgeVarIdx, size_t pathVarIdx, Expression* expr);
~PruneExpressionEvaluator();
bool evaluate();
void prepareContext(InputAqlItemRow input) { _ctx.setInputRow(input); }
bool needsVertex() const { return _ctx.needsVertexValue(); }
void injectVertex(velocypack::Slice v) { _ctx.setVertexValue(v); }
bool needsEdge() const { return _ctx.needsEdgeValue(); }
void injectEdge(velocypack::Slice e) { _ctx.setEdgeValue(e); }
bool needsPath() const { return _ctx.needsPathValue(); }
void injectPath(velocypack::Slice p) { _ctx.setPathValue(p); }
private:
transaction::Methods* _trx;
/// @brief The condition given in PRUNE (might be empty)
/// The Node keeps responsibility
aql::Expression* _pruneExpression;
/// @brief The context used to inject variables
InAndOutRowExpressionContext _ctx;
};
} // namespace aql
} // namespace arangodb
#endif

View File

@ -22,6 +22,7 @@
#include "TraversalExecutor.h" #include "TraversalExecutor.h"
#include "Aql/OutputAqlItemRow.h" #include "Aql/OutputAqlItemRow.h"
#include "Aql/PruneExpressionEvaluator.h"
#include "Aql/Query.h" #include "Aql/Query.h"
#include "Aql/SingleRowFetcher.h" #include "Aql/SingleRowFetcher.h"
#include "Graph/Traverser.h" #include "Graph/Traverser.h"
@ -149,7 +150,6 @@ std::pair<ExecutionState, TraversalStats> TraversalExecutor::produceRow(OutputAq
s.addScannedIndex(_traverser.getAndResetReadDocuments()); s.addScannedIndex(_traverser.getAndResetReadDocuments());
return {_rowState, s}; return {_rowState, s};
} }
if (!resetTraverser()) { if (!resetTraverser()) {
// Could not start here, (invalid) // Could not start here, (invalid)
// Go to next // Go to next
@ -203,7 +203,11 @@ bool TraversalExecutor::resetTraverser() {
for (auto const& pair : _infos.filterConditionVariables()) { for (auto const& pair : _infos.filterConditionVariables()) {
opts->setVariableValue(pair.first, _input.getValue(pair.second)); opts->setVariableValue(pair.first, _input.getValue(pair.second));
} }
if (opts->usesPrune()) {
auto* evaluator = opts->getPruneEvaluator();
// Replace by inputRow
evaluator->prepareContext(_input);
}
// Now reset the traverser // Now reset the traverser
if (_infos.usesFixedSource()) { if (_infos.usesFixedSource()) {
auto pos = _infos.getFixedSource().find('/'); auto pos = _infos.getFixedSource().find('/');

View File

@ -91,13 +91,15 @@ void TraversalNode::TraversalEdgeConditionBuilder::toVelocyPack(VPackBuilder& bu
TraversalNode::TraversalNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase, TraversalNode::TraversalNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase,
AstNode const* direction, AstNode const* start, AstNode const* direction, AstNode const* start,
AstNode const* graph, std::unique_ptr<BaseOptions> options) AstNode const* graph, std::unique_ptr<Expression> pruneExpression,
std::unique_ptr<BaseOptions> options)
: GraphNode(plan, id, vocbase, direction, graph, std::move(options)), : GraphNode(plan, id, vocbase, direction, graph, std::move(options)),
_pathOutVariable(nullptr), _pathOutVariable(nullptr),
_inVariable(nullptr), _inVariable(nullptr),
_condition(nullptr), _condition(nullptr),
_fromCondition(nullptr), _fromCondition(nullptr),
_toCondition(nullptr) { _toCondition(nullptr),
_pruneExpression(std::move(pruneExpression)) {
TRI_ASSERT(start != nullptr); TRI_ASSERT(start != nullptr);
auto ast = _plan->getAst(); auto ast = _plan->getAst();
@ -143,6 +145,10 @@ TraversalNode::TraversalNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocb
"_id string or an object with _id."); "_id string or an object with _id.");
} }
if (_pruneExpression) {
_pruneExpression->variables(_pruneVariables);
}
#ifdef TRI_ENABLE_MAINTAINER_MODE #ifdef TRI_ENABLE_MAINTAINER_MODE
checkConditionsDefined(); checkConditionsDefined();
#endif #endif
@ -247,6 +253,17 @@ TraversalNode::TraversalNode(ExecutionPlan* plan, arangodb::velocypack::Slice co
} }
} }
list = base.get("expression");
if (!list.isNone()) {
_pruneExpression = std::make_unique<aql::Expression>(plan, plan->getAst(), base);
TRI_ASSERT(base.hasKey("pruneVariables"));
list = base.get("pruneVariables");
TRI_ASSERT(list.isArray());
for (auto const& varinfo : VPackArrayIterator(list)) {
_pruneVariables.emplace(plan->getAst()->variables()->createVariable(varinfo));
}
}
#ifdef TRI_ENABLE_MAINTAINER_MODE #ifdef TRI_ENABLE_MAINTAINER_MODE
checkConditionsDefined(); checkConditionsDefined();
#endif #endif
@ -379,6 +396,17 @@ void TraversalNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) cons
nodes.add(VPackValue("indexes")); nodes.add(VPackValue("indexes"));
_options->toVelocyPackIndexes(nodes); _options->toVelocyPackIndexes(nodes);
if (_pruneExpression != nullptr) {
// The Expression constructor expects only this name
nodes.add(VPackValue("expression"));
_pruneExpression->toVelocyPack(nodes, flags);
nodes.add(VPackValue("pruneVariables"));
nodes.openArray();
for (auto const& var : _pruneVariables) {
var->toVelocyPack(nodes);
}
nodes.close();
}
// And close it: // And close it:
nodes.close(); nodes.close();
} }
@ -428,6 +456,33 @@ std::unique_ptr<ExecutionBlock> TraversalNode::createBlock(
auto opts = static_cast<TraverserOptions*>(options()); auto opts = static_cast<TraverserOptions*>(options());
std::unique_ptr<Traverser> traverser; std::unique_ptr<Traverser> traverser;
auto trx = engine.getQuery()->trx(); auto trx = engine.getQuery()->trx();
if (pruneExpression() != nullptr) {
std::vector<Variable const*> pruneVars;
getPruneVariables(pruneVars);
std::vector<RegisterId> pruneRegs;
// Create List for _pruneVars
pruneRegs.reserve(pruneVars.size());
size_t vertexRegIdx = std::numeric_limits<std::size_t>::max();
size_t edgeRegIdx = std::numeric_limits<std::size_t>::max();
size_t pathRegIdx = std::numeric_limits<std::size_t>::max();
for (auto const v : pruneVars) {
auto it = varInfo.find(v->id);
TRI_ASSERT(it != varInfo.end());
if (v == vertexOutVariable()) {
vertexRegIdx = pruneRegs.size();
} else if (v == edgeOutVariable()) {
edgeRegIdx = pruneRegs.size();
} else if (v == pathOutVariable()) {
pathRegIdx = pruneRegs.size();
}
pruneRegs.emplace_back(it->second.registerId);
}
opts->activatePrune(std::move(pruneVars), std::move(pruneRegs),
vertexRegIdx, edgeRegIdx, pathRegIdx, pruneExpression());
}
if (arangodb::ServerState::instance()->isCoordinator()) { if (arangodb::ServerState::instance()->isCoordinator()) {
#ifdef USE_ENTERPRISE #ifdef USE_ENTERPRISE
if (isSmart()) { if (isSmart()) {
@ -699,6 +754,14 @@ void TraversalNode::getConditionVariables(std::vector<Variable const*>& res) con
} }
} }
void TraversalNode::getPruneVariables(std::vector<Variable const*>& res) const {
if (_pruneExpression) {
for (auto const& it : _pruneVariables) {
res.emplace_back(it);
}
}
}
#ifdef TRI_ENABLE_MAINTAINER_MODE #ifdef TRI_ENABLE_MAINTAINER_MODE
void TraversalNode::checkConditionsDefined() const { void TraversalNode::checkConditionsDefined() const {
TRI_ASSERT(_tmpObjVariable != nullptr); TRI_ASSERT(_tmpObjVariable != nullptr);

View File

@ -78,7 +78,8 @@ class TraversalNode : public GraphNode {
public: public:
TraversalNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase, TraversalNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase,
AstNode const* direction, AstNode const* start, AstNode const* direction, AstNode const* start,
AstNode const* graph, std::unique_ptr<graph::BaseOptions> options); AstNode const* graph, std::unique_ptr<Expression> pruneExpression,
std::unique_ptr<graph::BaseOptions> options);
TraversalNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base); TraversalNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base);
@ -118,6 +119,9 @@ class TraversalNode : public GraphNode {
result.emplace(condVar); result.emplace(condVar);
} }
} }
for (auto const& pruneVar : _pruneVariables) {
result.emplace(pruneVar);
}
if (usesInVariable()) { if (usesInVariable()) {
result.emplace(_inVariable); result.emplace(_inVariable);
} }
@ -178,11 +182,17 @@ class TraversalNode : public GraphNode {
void getConditionVariables(std::vector<Variable const*>&) const override; void getConditionVariables(std::vector<Variable const*>&) const override;
void getPruneVariables(std::vector<Variable const*>&) const;
/// @brief Compute the traversal options containing the expressions /// @brief Compute the traversal options containing the expressions
/// MUST! be called after optimization and before creation /// MUST! be called after optimization and before creation
/// of blocks. /// of blocks.
void prepareOptions() override; void prepareOptions() override;
// @brief Get reference to the Prune expression.
// You are not responsible for it!
Expression* pruneExpression() const { return _pruneExpression.get(); }
private: private:
#ifdef TRI_ENABLE_MAINTAINER_MODE #ifdef TRI_ENABLE_MAINTAINER_MODE
void checkConditionsDefined() const; void checkConditionsDefined() const;
@ -210,6 +220,9 @@ class TraversalNode : public GraphNode {
/// @brief The hard coded condition on _to /// @brief The hard coded condition on _to
AstNode* _toCondition; AstNode* _toCondition;
/// @brief The condition given in PRUNE (might be empty)
std::unique_ptr<Expression> _pruneExpression;
/// @brief The global edge condition. Does not contain /// @brief The global edge condition. Does not contain
/// _from and _to checks /// _from and _to checks
std::vector<AstNode const*> _globalEdgeConditions; std::vector<AstNode const*> _globalEdgeConditions;
@ -222,6 +235,9 @@ class TraversalNode : public GraphNode {
/// @brief List of all depth specific conditions for vertices /// @brief List of all depth specific conditions for vertices
std::unordered_map<uint64_t, AstNode*> _vertexConditions; std::unordered_map<uint64_t, AstNode*> _vertexConditions;
/// @brief the hashSet for variables used in pruning
arangodb::HashSet<Variable const*> _pruneVariables;
}; };
} // namespace aql } // namespace aql

File diff suppressed because it is too large Load Diff

View File

@ -125,7 +125,7 @@ extern int Aqldebug;
union YYSTYPE union YYSTYPE
{ {
#line 35 "Aql/grammar.y" /* yacc.c:1909 */ #line 35 "Aql/grammar.y" /* yacc.c:1915 */
arangodb::aql::AstNode* node; arangodb::aql::AstNode* node;
struct { struct {
@ -135,7 +135,7 @@ union YYSTYPE
bool boolval; bool boolval;
int64_t intval; int64_t intval;
#line 139 "Aql/grammar.hpp" /* yacc.c:1909 */ #line 139 "Aql/grammar.hpp" /* yacc.c:1915 */
}; };
typedef union YYSTYPE YYSTYPE; typedef union YYSTYPE YYSTYPE;

View File

@ -125,7 +125,7 @@ extern int Aqldebug;
union YYSTYPE union YYSTYPE
{ {
#line 35 "Aql/grammar.y" /* yacc.c:1909 */ #line 35 "Aql/grammar.y" /* yacc.c:1915 */
arangodb::aql::AstNode* node; arangodb::aql::AstNode* node;
struct { struct {
@ -135,7 +135,7 @@ union YYSTYPE
bool boolval; bool boolval;
int64_t intval; int64_t intval;
#line 139 "Aql/grammar.hpp" /* yacc.c:1909 */ #line 139 "Aql/grammar.hpp" /* yacc.c:1915 */
}; };
typedef union YYSTYPE YYSTYPE; typedef union YYSTYPE YYSTYPE;

View File

@ -200,6 +200,17 @@ static AstNode const* GetIntoExpression(AstNode const* node) {
return node->getMember(1); return node->getMember(1);
} }
static AstNode* TransformOutputVariables(Parser* parser, AstNode const* names) {
auto wrapperNode = parser->ast()->createNodeArray();
for (size_t i = 0; i < names->numMembers(); ++i) {
AstNode* variableNameNode = names->getMemberUnchecked(i);
TRI_ASSERT(variableNameNode->isStringValue());
AstNode* variableNode = parser->ast()->createNodeVariable(variableNameNode->getStringValue(), variableNameNode->getStringLength(), true);
wrapperNode->addMember(variableNode);
}
return wrapperNode;
}
%} %}
/* define tokens and "nice" token names */ /* define tokens and "nice" token names */
@ -338,6 +349,9 @@ static AstNode const* GetIntoExpression(AstNode const* node) {
%type <node> function_arguments_list; %type <node> function_arguments_list;
%type <node> compound_value; %type <node> compound_value;
%type <node> array; %type <node> array;
%type <node> for_output_variables;
%type <node> traversal_graph_info;
%type <node> shortest_path_graph_info;
%type <node> optional_array_elements; %type <node> optional_array_elements;
%type <node> array_elements_list; %type <node> array_elements_list;
%type <node> for_options; %type <node> for_options;
@ -479,19 +493,145 @@ statement_block_statement:
} }
; ;
more_output_variables:
variable_name {
auto wrapperNode = parser->ast()->createNodeArray();
parser->pushArray(wrapperNode);
// This is guaranteed to be called on the first variable
AstNode* node = parser->ast()->createNodeValueString($1.value, $1.length);
parser->pushArrayElement(node);
}
| more_output_variables T_COMMA variable_name {
AstNode* node = parser->ast()->createNodeValueString($3.value, $3.length);
parser->pushArrayElement(node);
}
;
for_output_variables:
more_output_variables {
$$ = parser->popArray();
}
;
prune_and_options:
/* empty no prune, no options, add two NOPS */ {
auto node = static_cast<AstNode*>(parser->peekStack());
// Prune
node->addMember(parser->ast()->createNodeNop());
// Options
node->addMember(parser->ast()->createNodeNop());
}
| T_STRING expression {
auto node = static_cast<AstNode*>(parser->peekStack());
if (TRI_CaseEqualString($1.value, "PRUNE")) {
/* Only Prune */
if ($2 == nullptr) {
ABORT_OOM
}
// Prune
node->addMember($2);
// Options
node->addMember(parser->ast()->createNodeNop());
} else if (TRI_CaseEqualString($1.value, "OPTIONS")) {
/* Only Options */
if ($2 == nullptr) {
ABORT_OOM
}
if (!$2->isObject()) {
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "traversal 'OPTIONS' have to be an object", $1.value, yylloc.first_line, yylloc.first_column);
}
// Prune
node->addMember(parser->ast()->createNodeNop());
// Options
node->addMember($2);
} else {
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'PRUNE' or 'OPTIONS'", $1.value, yylloc.first_line, yylloc.first_column);
}
}
| T_STRING expression T_STRING object {
/* prune and options */
auto node = static_cast<AstNode*>(parser->peekStack());
if (!TRI_CaseEqualString($1.value, "PRUNE")) {
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'PRUNE'", $1.value, yylloc.first_line, yylloc.first_column);
}
if ($2 == nullptr) {
ABORT_OOM
}
if (!TRI_CaseEqualString($3.value, "OPTIONS")) {
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'OPTIONS'", $3.value, yylloc.first_line, yylloc.first_column);
}
if ($4 == nullptr) {
ABORT_OOM
}
// Prune
node->addMember($2);
// Options
node->addMember($4);
}
;
traversal_graph_info:
graph_direction_steps expression graph_subject {
auto infoNode = parser->ast()->createNodeArray();
// Direction
infoNode->addMember($1);
// Source
infoNode->addMember($2);
// Graph
infoNode->addMember($3);
$$ = infoNode;
}
;
shortest_path_graph_info:
graph_direction T_SHORTEST_PATH expression T_STRING expression graph_subject options {
if (!TRI_CaseEqualString($4.value, "TO")) {
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'TO'", $4.value, yylloc.first_line, yylloc.first_column);
}
auto infoNode = parser->ast()->createNodeArray();
auto dirNode = parser->ast()->createNodeValueInt($1);
// Direction
infoNode->addMember(dirNode);
// Source
infoNode->addMember($3);
// Target
infoNode->addMember($5);
// Graph
infoNode->addMember($6);
// Opts
auto opts = parser->ast()->createNodeOptions($7);
TRI_ASSERT(opts != nullptr);
infoNode->addMember(opts);
$$ = infoNode;
}
;
for_statement: for_statement:
T_FOR variable_name T_IN expression { T_FOR for_output_variables T_IN expression {
// first open a new scope // first open a new scope (after expression is evaluated)
parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR); parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR);
AstNode* variablesNode = static_cast<AstNode*>($2);
TRI_ASSERT(variablesNode != nullptr);
TRI_ASSERT(variablesNode->type == NODE_TYPE_ARRAY);
if (variablesNode->numMembers() != 1) {
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "Collections and Views only have one return variable", yylloc.first_line, yylloc.first_column);
}
// now create an out variable for the FOR statement // now create an out variable for the FOR statement
// this prepares us to handle the optional SEARCH condition, which may // this prepares us to handle the optional SEARCH condition, which may
// or may not refer to the FOR's variable // or may not refer to the FOR's variable
parser->pushStack(parser->ast()->createNodeVariable($2.value, $2.length, true)); AstNode* variableNameNode = variablesNode->getMemberUnchecked(0);
TRI_ASSERT(variableNameNode->isStringValue());
AstNode* variableNode = parser->ast()->createNodeVariable(variableNameNode->getStringValue(), variableNameNode->getStringLength(), true);
parser->pushStack(variableNode);
} for_options { } for_options {
// now we can handle the optional SEARCH condition and OPTIONS. // now we can handle the optional SEARCH condition and OPTIONS.
AstNode* variableNode = static_cast<AstNode*>(parser->popStack()); AstNode* variableNode = static_cast<AstNode*>(parser->popStack());
TRI_ASSERT(variableNode != nullptr);
Variable* variable = static_cast<Variable*>(variableNode->getData()); Variable* variable = static_cast<Variable*>(variableNode->getData());
AstNode* node = nullptr; AstNode* node = nullptr;
@ -528,46 +668,46 @@ for_statement:
parser->ast()->addOperation(node); parser->ast()->addOperation(node);
} }
| T_FOR traversal_statement { | T_FOR for_output_variables T_IN traversal_graph_info {
} // first open a new scope (after expression is evaluated)
| T_FOR shortest_path_statement {
}
;
traversal_statement:
variable_name T_IN graph_direction_steps expression graph_subject options {
parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR); parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR);
auto node = parser->ast()->createNodeTraversal($1.value, $1.length, $3, $4, $5, $6); // Traversal
parser->ast()->addOperation(node); auto variableNamesNode = static_cast<AstNode*>($2);
} TRI_ASSERT(variableNamesNode != nullptr);
| variable_name T_COMMA variable_name T_IN graph_direction_steps expression graph_subject options { TRI_ASSERT(variableNamesNode->type == NODE_TYPE_ARRAY);
parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR); if (variableNamesNode->numMembers() > 3) {
auto node = parser->ast()->createNodeTraversal($1.value, $1.length, $3.value, $3.length, $5, $6, $7, $8); parser->registerParseError(TRI_ERROR_QUERY_PARSE, "Traversals only have one, two or three return variables", yylloc.first_line, yylloc.first_column);
parser->ast()->addOperation(node);
}
| variable_name T_COMMA variable_name T_COMMA variable_name T_IN graph_direction_steps expression graph_subject options {
parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR);
auto node = parser->ast()->createNodeTraversal($1.value, $1.length, $3.value, $3.length, $5.value, $5.length, $7, $8, $9, $10);
parser->ast()->addOperation(node);
}
;
shortest_path_statement:
variable_name T_IN graph_direction T_SHORTEST_PATH expression T_STRING expression graph_subject options {
if (!TRI_CaseEqualString($6.value, "TO")) {
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'TO'", $6.value, yylloc.first_line, yylloc.first_column);
} }
parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR); auto variablesNode = TransformOutputVariables(parser, variableNamesNode);
auto node = parser->ast()->createNodeShortestPath($1.value, $1.length, $3, $5, $7, $8, $9); auto graphInfoNode = static_cast<AstNode*>($4);
TRI_ASSERT(graphInfoNode != nullptr);
TRI_ASSERT(graphInfoNode->type == NODE_TYPE_ARRAY);
parser->pushStack(variablesNode);
parser->pushStack(graphInfoNode);
// This stack push/pop magic is necessary to allow v, e, and p in the prune condition
} prune_and_options {
auto graphInfoNode = static_cast<AstNode*>(parser->popStack());
auto variablesNode = static_cast<AstNode*>(parser->popStack());
auto node = parser->ast()->createNodeTraversal(variablesNode, graphInfoNode);
parser->ast()->addOperation(node); parser->ast()->addOperation(node);
} }
| variable_name T_COMMA variable_name T_IN graph_direction T_SHORTEST_PATH expression T_STRING expression graph_subject options { | T_FOR for_output_variables T_IN shortest_path_graph_info {
if (!TRI_CaseEqualString($8.value, "TO")) { // first open a new scope (after expression is evaluated)
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'TO'", $8.value, yylloc.first_line, yylloc.first_column);
}
parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR); parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR);
auto node = parser->ast()->createNodeShortestPath($1.value, $1.length, $3.value, $3.length, $5, $7, $9, $10, $11); // Shortest Path
auto variableNamesNode = static_cast<AstNode*>($2);
TRI_ASSERT(variableNamesNode != nullptr);
TRI_ASSERT(variableNamesNode->type == NODE_TYPE_ARRAY);
if (variableNamesNode->numMembers() > 2) {
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "ShortestPath only has one or two return variables", yylloc.first_line, yylloc.first_column);
}
auto variablesNode = TransformOutputVariables(parser, variableNamesNode);
auto graphInfoNode = static_cast<AstNode*>($4);
TRI_ASSERT(graphInfoNode != nullptr);
TRI_ASSERT(graphInfoNode->type == NODE_TYPE_ARRAY);
auto node = parser->ast()->createNodeShortestPath(variablesNode, graphInfoNode);
parser->ast()->addOperation(node); parser->ast()->addOperation(node);
} }
; ;

View File

@ -224,6 +224,7 @@ SET(ARANGOD_SOURCES
Aql/Functions.cpp Aql/Functions.cpp
Aql/GraphNode.cpp Aql/GraphNode.cpp
Aql/Graphs.cpp Aql/Graphs.cpp
Aql/InAndOutRowExpressionContext.cpp
Aql/IdExecutor.cpp Aql/IdExecutor.cpp
Aql/IndexExecutor.cpp Aql/IndexExecutor.cpp
Aql/IndexNode.cpp Aql/IndexNode.cpp
@ -242,6 +243,7 @@ SET(ARANGOD_SOURCES
Aql/OutputAqlItemRow.cpp Aql/OutputAqlItemRow.cpp
Aql/Parser.cpp Aql/Parser.cpp
Aql/PlanCache.cpp Aql/PlanCache.cpp
Aql/PruneExpressionEvaluator.cpp
Aql/Quantifier.cpp Aql/Quantifier.cpp
Aql/Query.cpp Aql/Query.cpp
Aql/QueryCache.cpp Aql/QueryCache.cpp

View File

@ -38,6 +38,7 @@ struct AstNode;
class ExecutionPlan; class ExecutionPlan;
class Expression; class Expression;
class Query; class Query;
} // namespace aql } // namespace aql
namespace velocypack { namespace velocypack {

View File

@ -23,6 +23,7 @@
#include "BreadthFirstEnumerator.h" #include "BreadthFirstEnumerator.h"
#include "Aql/PruneExpressionEvaluator.h"
#include "Graph/EdgeCursor.h" #include "Graph/EdgeCursor.h"
#include "Graph/EdgeDocumentToken.h" #include "Graph/EdgeDocumentToken.h"
#include "Graph/Traverser.h" #include "Graph/Traverser.h"
@ -51,12 +52,13 @@ BreadthFirstEnumerator::PathStep::PathStep(PathStep& other)
BreadthFirstEnumerator::BreadthFirstEnumerator(Traverser* traverser, VPackSlice startVertex, BreadthFirstEnumerator::BreadthFirstEnumerator(Traverser* traverser, VPackSlice startVertex,
TraverserOptions* opts) TraverserOptions* opts)
: PathEnumerator(traverser, startVertex.copyString(), opts), : PathEnumerator(traverser, startVertex.copyString(), opts),
_schreierIndex(1), _schreierIndex(0),
_lastReturned(0), _lastReturned(0),
_currentDepth(0), _currentDepth(0),
_toSearchPos(0) { _toSearchPos(0) {
_schreier.reserve(32); _schreier.reserve(32);
arangodb::velocypack::StringRef startVId = _opts->cache()->persistString(arangodb::velocypack::StringRef(startVertex)); arangodb::velocypack::StringRef startVId =
_opts->cache()->persistString(arangodb::velocypack::StringRef(startVertex));
_schreier.emplace_back(std::make_unique<PathStep>(startVId)); _schreier.emplace_back(std::make_unique<PathStep>(startVId));
_toSearch.emplace_back(NextStep(0)); _toSearch.emplace_back(NextStep(0));
@ -67,6 +69,13 @@ BreadthFirstEnumerator::~BreadthFirstEnumerator() {}
bool BreadthFirstEnumerator::next() { bool BreadthFirstEnumerator::next() {
if (_isFirst) { if (_isFirst) {
_isFirst = false; _isFirst = false;
if (shouldPrune()) {
TRI_ASSERT(_toSearch.size() == 1);
// Throw the next one away
_toSearch.clear();
}
// We have faked the 0 position in schreier for pruning
_schreierIndex++;
if (_opts->minDepth == 0) { if (_opts->minDepth == 0) {
return true; return true;
} }
@ -92,22 +101,10 @@ bool BreadthFirstEnumerator::next() {
while (true) { while (true) {
if (_toSearchPos >= _toSearch.size()) { if (_toSearchPos >= _toSearch.size()) {
// This depth is done. GoTo next // This depth is done. GoTo next
if (_nextDepth.empty()) { if (!prepareSearchOnNextDepth()) {
// That's it. we are done. // That's it. we are done.
return false; return false;
} }
// Save copies:
// We clear current
// we swap current and next.
// So now current is filled
// and next is empty.
_toSearch.clear();
_toSearchPos = 0;
_toSearch.swap(_nextDepth);
_currentDepth++;
TRI_ASSERT(_toSearchPos < _toSearch.size());
TRI_ASSERT(_nextDepth.empty());
TRI_ASSERT(_currentDepth < _opts->maxDepth);
} }
// This access is always safe. // This access is always safe.
// If not it should have bailed out before. // If not it should have bailed out before.
@ -152,7 +149,10 @@ bool BreadthFirstEnumerator::next() {
_schreier.emplace_back(std::make_unique<PathStep>(nextIdx, std::move(eid), vId)); _schreier.emplace_back(std::make_unique<PathStep>(nextIdx, std::move(eid), vId));
if (_currentDepth < _opts->maxDepth - 1) { if (_currentDepth < _opts->maxDepth - 1) {
_nextDepth.emplace_back(NextStep(_schreierIndex)); // Prune here
if (!shouldPrune()) {
_nextDepth.emplace_back(NextStep(_schreierIndex));
}
} }
_schreierIndex++; _schreierIndex++;
didInsert = true; didInsert = true;
@ -180,30 +180,41 @@ bool BreadthFirstEnumerator::next() {
// entry. We compute the path to it. // entry. We compute the path to it.
return true; return true;
} }
arangodb::aql::AqlValue BreadthFirstEnumerator::lastVertexToAqlValue() { arangodb::aql::AqlValue BreadthFirstEnumerator::lastVertexToAqlValue() {
TRI_ASSERT(_lastReturned < _schreier.size()); return vertexToAqlValue(_lastReturned);
return _traverser->fetchVertexData(_schreier[_lastReturned]->vertex);
} }
arangodb::aql::AqlValue BreadthFirstEnumerator::lastEdgeToAqlValue() { arangodb::aql::AqlValue BreadthFirstEnumerator::lastEdgeToAqlValue() {
TRI_ASSERT(_lastReturned < _schreier.size()); return edgeToAqlValue(_lastReturned);
if (_lastReturned == 0) {
// This is the first Vertex. No Edge Pointing to it
return arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull());
}
return _opts->cache()->fetchEdgeAqlResult(_schreier[_lastReturned]->edge);
} }
arangodb::aql::AqlValue BreadthFirstEnumerator::pathToAqlValue(arangodb::velocypack::Builder& result) { arangodb::aql::AqlValue BreadthFirstEnumerator::pathToAqlValue(arangodb::velocypack::Builder& result) {
return pathToIndexToAqlValue(result, _lastReturned);
}
arangodb::aql::AqlValue BreadthFirstEnumerator::vertexToAqlValue(size_t index) {
TRI_ASSERT(index < _schreier.size());
return _traverser->fetchVertexData(_schreier[index]->vertex);
}
arangodb::aql::AqlValue BreadthFirstEnumerator::edgeToAqlValue(size_t index) {
TRI_ASSERT(index < _schreier.size());
if (index == 0) {
// This is the first Vertex. No Edge Pointing to it
return arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull());
}
return _opts->cache()->fetchEdgeAqlResult(_schreier[index]->edge);
}
arangodb::aql::AqlValue BreadthFirstEnumerator::pathToIndexToAqlValue(
arangodb::velocypack::Builder& result, size_t index) {
// TODO make deque class variable // TODO make deque class variable
std::deque<size_t> fullPath; std::deque<size_t> fullPath;
size_t cur = _lastReturned; while (index != 0) {
while (cur != 0) {
// Walk backwards through the path and push everything found on the local // Walk backwards through the path and push everything found on the local
// stack // stack
fullPath.emplace_front(cur); fullPath.emplace_front(index);
cur = _schreier[cur]->sourceIdx; index = _schreier[index]->sourceIdx;
} }
result.clear(); result.clear();
@ -226,7 +237,8 @@ arangodb::aql::AqlValue BreadthFirstEnumerator::pathToAqlValue(arangodb::velocyp
return arangodb::aql::AqlValue(result.slice()); return arangodb::aql::AqlValue(result.slice());
} }
bool BreadthFirstEnumerator::pathContainsVertex(size_t index, arangodb::velocypack::StringRef vertex) const { bool BreadthFirstEnumerator::pathContainsVertex(size_t index,
arangodb::velocypack::StringRef vertex) const {
while (true) { while (true) {
TRI_ASSERT(index < _schreier.size()); TRI_ASSERT(index < _schreier.size());
auto const& step = _schreier[index]; auto const& step = _schreier[index];
@ -262,3 +274,42 @@ bool BreadthFirstEnumerator::pathContainsEdge(size_t index,
} }
return false; return false;
} }
bool BreadthFirstEnumerator::prepareSearchOnNextDepth() {
if (_nextDepth.empty()) {
// Nothing left to search
return false;
}
// Save copies:
// We clear current
// we swap current and next.
// So now current is filled
// and next is empty.
_toSearch.clear();
_toSearchPos = 0;
_toSearch.swap(_nextDepth);
_currentDepth++;
TRI_ASSERT(_toSearchPos < _toSearch.size());
TRI_ASSERT(_nextDepth.empty());
TRI_ASSERT(_currentDepth < _opts->maxDepth);
return true;
}
bool BreadthFirstEnumerator::shouldPrune() {
if (_opts->usesPrune()) {
auto* evaluator = _opts->getPruneEvaluator();
if (evaluator->needsVertex()) {
evaluator->injectVertex(vertexToAqlValue(_schreierIndex).slice());
}
if (evaluator->needsEdge()) {
evaluator->injectEdge(edgeToAqlValue(_schreierIndex).slice());
}
transaction::BuilderLeaser builder(_opts->trx());
if (evaluator->needsPath()) {
aql::AqlValue val = pathToIndexToAqlValue(*builder.get(), _schreierIndex);
evaluator->injectPath(val.slice());
}
return evaluator->evaluate();
}
return false;
}

View File

@ -172,6 +172,21 @@ class BreadthFirstEnumerator final : public arangodb::traverser::PathEnumerator
* @return true if the edge is already in the path * @return true if the edge is already in the path
*/ */
bool pathContainsEdge(size_t index, graph::EdgeDocumentToken const& edge) const; bool pathContainsEdge(size_t index, graph::EdgeDocumentToken const& edge) const;
/**
* @brief Reset iterators to search within next depth
* Also honors pruned paths
* @return true if we can continue searching. False if we are done
*/
bool prepareSearchOnNextDepth();
aql::AqlValue vertexToAqlValue(size_t index);
aql::AqlValue edgeToAqlValue(size_t index);
aql::AqlValue pathToIndexToAqlValue(arangodb::velocypack::Builder& result, size_t index);
bool shouldPrune();
}; };
} // namespace graph } // namespace graph
} // namespace arangodb } // namespace arangodb

View File

@ -23,6 +23,7 @@
#include "NeighborsEnumerator.h" #include "NeighborsEnumerator.h"
#include "Aql/PruneExpressionEvaluator.h"
#include "Basics/VelocyPackHelper.h" #include "Basics/VelocyPackHelper.h"
#include "Graph/EdgeCursor.h" #include "Graph/EdgeCursor.h"
#include "Graph/Traverser.h" #include "Graph/Traverser.h"
@ -36,7 +37,8 @@ using namespace arangodb::traverser;
NeighborsEnumerator::NeighborsEnumerator(Traverser* traverser, VPackSlice const& startVertex, NeighborsEnumerator::NeighborsEnumerator(Traverser* traverser, VPackSlice const& startVertex,
TraverserOptions* opts) TraverserOptions* opts)
: PathEnumerator(traverser, startVertex.copyString(), opts), _searchDepth(0) { : PathEnumerator(traverser, startVertex.copyString(), opts), _searchDepth(0) {
arangodb::velocypack::StringRef vId = _traverser->traverserCache()->persistString(arangodb::velocypack::StringRef(startVertex)); arangodb::velocypack::StringRef vId = _traverser->traverserCache()->persistString(
arangodb::velocypack::StringRef(startVertex));
_allFound.insert(vId); _allFound.insert(vId);
_currentDepth.insert(vId); _currentDepth.insert(vId);
_iterator = _currentDepth.begin(); _iterator = _currentDepth.begin();
@ -58,8 +60,7 @@ bool NeighborsEnumerator::next() {
return false; return false;
} }
_lastDepth.swap(_currentDepth); swapLastAndCurrentDepth();
_currentDepth.clear();
for (auto const& nextVertex : _lastDepth) { for (auto const& nextVertex : _lastDepth) {
auto callback = [&](EdgeDocumentToken&& eid, VPackSlice other, size_t cursorId) { auto callback = [&](EdgeDocumentToken&& eid, VPackSlice other, size_t cursorId) {
if (_opts->hasEdgeFilter(_searchDepth, cursorId)) { if (_opts->hasEdgeFilter(_searchDepth, cursorId)) {
@ -90,8 +91,11 @@ bool NeighborsEnumerator::next() {
if (_allFound.find(v) == _allFound.end()) { if (_allFound.find(v) == _allFound.end()) {
if (_traverser->vertexMatchesConditions(v, _searchDepth + 1)) { if (_traverser->vertexMatchesConditions(v, _searchDepth + 1)) {
_currentDepth.emplace(v);
_allFound.emplace(v); _allFound.emplace(v);
if (shouldPrune(v)) {
_toPrune.emplace(v);
}
_currentDepth.emplace(v);
} }
} else { } else {
_opts->cache()->increaseFilterCounter(); _opts->cache()->increaseFilterCounter();
@ -132,3 +136,30 @@ arangodb::aql::AqlValue NeighborsEnumerator::pathToAqlValue(arangodb::velocypack
TRI_ASSERT(false); TRI_ASSERT(false);
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED); THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
} }
void NeighborsEnumerator::swapLastAndCurrentDepth() {
// Filter all in _toPrune
if (!_toPrune.empty()) {
for (auto const& it : _toPrune) {
_currentDepth.erase(it);
}
_toPrune.clear();
}
_lastDepth.swap(_currentDepth);
_currentDepth.clear();
}
bool NeighborsEnumerator::shouldPrune(arangodb::velocypack::StringRef v) {
// Prune here
if (_opts->usesPrune()) {
auto* evaluator = _opts->getPruneEvaluator();
if (evaluator->needsVertex()) {
evaluator->injectVertex(_traverser->fetchVertexData(v).slice());
}
// We cannot support these two here
TRI_ASSERT(!evaluator->needsEdge());
TRI_ASSERT(!evaluator->needsPath());
return evaluator->evaluate();
}
return false;
}

View File

@ -38,6 +38,7 @@ class NeighborsEnumerator final : public arangodb::traverser::PathEnumerator {
std::unordered_set<arangodb::velocypack::StringRef> _currentDepth; std::unordered_set<arangodb::velocypack::StringRef> _currentDepth;
std::unordered_set<arangodb::velocypack::StringRef> _lastDepth; std::unordered_set<arangodb::velocypack::StringRef> _lastDepth;
std::unordered_set<arangodb::velocypack::StringRef>::iterator _iterator; std::unordered_set<arangodb::velocypack::StringRef>::iterator _iterator;
std::unordered_set<arangodb::velocypack::StringRef> _toPrune;
uint64_t _searchDepth; uint64_t _searchDepth;
@ -65,6 +66,11 @@ class NeighborsEnumerator final : public arangodb::traverser::PathEnumerator {
aql::AqlValue lastEdgeToAqlValue() override; aql::AqlValue lastEdgeToAqlValue() override;
aql::AqlValue pathToAqlValue(arangodb::velocypack::Builder& result) override; aql::AqlValue pathToAqlValue(arangodb::velocypack::Builder& result) override;
private:
void swapLastAndCurrentDepth();
bool shouldPrune(arangodb::velocypack::StringRef v);
}; };
} // namespace graph } // namespace graph

View File

@ -22,6 +22,8 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include "PathEnumerator.h" #include "PathEnumerator.h"
#include "Aql/AqlValue.h"
#include "Aql/PruneExpressionEvaluator.h"
#include "Basics/VelocyPackHelper.h" #include "Basics/VelocyPackHelper.h"
#include "Graph/EdgeCursor.h" #include "Graph/EdgeCursor.h"
#include "Graph/Traverser.h" #include "Graph/Traverser.h"
@ -36,7 +38,8 @@ using TraverserOptions = arangodb::traverser::TraverserOptions;
PathEnumerator::PathEnumerator(Traverser* traverser, std::string const& startVertex, PathEnumerator::PathEnumerator(Traverser* traverser, std::string const& startVertex,
TraverserOptions* opts) TraverserOptions* opts)
: _traverser(traverser), _isFirst(true), _opts(opts) { : _traverser(traverser), _isFirst(true), _opts(opts) {
arangodb::velocypack::StringRef svId = _opts->cache()->persistString(arangodb::velocypack::StringRef(startVertex)); arangodb::velocypack::StringRef svId =
_opts->cache()->persistString(arangodb::velocypack::StringRef(startVertex));
// Guarantee that this vertex _id does not run away // Guarantee that this vertex _id does not run away
_enumeratedPath.vertices.push_back(svId); _enumeratedPath.vertices.push_back(svId);
TRI_ASSERT(_enumeratedPath.vertices.size() == 1); TRI_ASSERT(_enumeratedPath.vertices.size() == 1);
@ -44,13 +47,16 @@ PathEnumerator::PathEnumerator(Traverser* traverser, std::string const& startVer
DepthFirstEnumerator::DepthFirstEnumerator(Traverser* traverser, std::string const& startVertex, DepthFirstEnumerator::DepthFirstEnumerator(Traverser* traverser, std::string const& startVertex,
TraverserOptions* opts) TraverserOptions* opts)
: PathEnumerator(traverser, startVertex, opts) {} : PathEnumerator(traverser, startVertex, opts), _pruneNext(false) {}
DepthFirstEnumerator::~DepthFirstEnumerator() {} DepthFirstEnumerator::~DepthFirstEnumerator() {}
bool DepthFirstEnumerator::next() { bool DepthFirstEnumerator::next() {
if (_isFirst) { if (_isFirst) {
_isFirst = false; _isFirst = false;
if (shouldPrune()) {
_pruneNext = true;
}
if (_opts->minDepth == 0) { if (_opts->minDepth == 0) {
return true; return true;
} }
@ -61,11 +67,12 @@ bool DepthFirstEnumerator::next() {
} }
while (true) { while (true) {
if (_enumeratedPath.edges.size() < _opts->maxDepth) { if (_enumeratedPath.edges.size() < _opts->maxDepth && !_pruneNext) {
// We are not done with this path, so // We are not done with this path, so
// we reserve the cursor for next depth // we reserve the cursor for next depth
auto cursor = _opts->nextCursor(_traverser->mmdr(), auto cursor = _opts->nextCursor(_traverser->mmdr(),
arangodb::velocypack::StringRef(_enumeratedPath.vertices.back()), arangodb::velocypack::StringRef(
_enumeratedPath.vertices.back()),
_enumeratedPath.edges.size()); _enumeratedPath.edges.size());
if (cursor != nullptr) { if (cursor != nullptr) {
_edgeCursors.emplace(cursor); _edgeCursors.emplace(cursor);
@ -77,6 +84,7 @@ bool DepthFirstEnumerator::next() {
_enumeratedPath.edges.pop_back(); _enumeratedPath.edges.pop_back();
} }
} }
_pruneNext = false;
bool foundPath = false; bool foundPath = false;
@ -145,6 +153,9 @@ bool DepthFirstEnumerator::next() {
if (cursor->next(callback)) { if (cursor->next(callback)) {
if (foundPath) { if (foundPath) {
if (shouldPrune()) {
_pruneNext = true;
}
if (_enumeratedPath.edges.size() < _opts->minDepth) { if (_enumeratedPath.edges.size() < _opts->minDepth) {
// We have a valid prefix, but do NOT return this path // We have a valid prefix, but do NOT return this path
break; break;
@ -171,7 +182,8 @@ bool DepthFirstEnumerator::next() {
} }
arangodb::aql::AqlValue DepthFirstEnumerator::lastVertexToAqlValue() { arangodb::aql::AqlValue DepthFirstEnumerator::lastVertexToAqlValue() {
return _traverser->fetchVertexData(arangodb::velocypack::StringRef(_enumeratedPath.vertices.back())); return _traverser->fetchVertexData(
arangodb::velocypack::StringRef(_enumeratedPath.vertices.back()));
} }
arangodb::aql::AqlValue DepthFirstEnumerator::lastEdgeToAqlValue() { arangodb::aql::AqlValue DepthFirstEnumerator::lastEdgeToAqlValue() {
@ -202,3 +214,23 @@ arangodb::aql::AqlValue DepthFirstEnumerator::pathToAqlValue(arangodb::velocypac
result.close(); result.close();
return arangodb::aql::AqlValue(result.slice()); return arangodb::aql::AqlValue(result.slice());
} }
bool DepthFirstEnumerator::shouldPrune() {
// We need to call prune here
if (_opts->usesPrune()) {
auto* evaluator = _opts->getPruneEvaluator();
if (evaluator->needsVertex()) {
evaluator->injectVertex(lastVertexToAqlValue().slice());
}
if (evaluator->needsEdge()) {
evaluator->injectEdge(lastEdgeToAqlValue().slice());
}
transaction::BuilderLeaser builder(_opts->trx());
if (evaluator->needsPath()) {
aql::AqlValue val = pathToAqlValue(*builder.get());
evaluator->injectPath(val.slice());
}
return evaluator->evaluate();
}
return false;
}

View File

@ -108,6 +108,11 @@ class DepthFirstEnumerator final : public PathEnumerator {
std::stack<std::unique_ptr<graph::EdgeCursor>> _edgeCursors; std::stack<std::unique_ptr<graph::EdgeCursor>> _edgeCursors;
//////////////////////////////////////////////////////////////////////////////
/// @brief Flag if we need to prune the next path
//////////////////////////////////////////////////////////////////////////////
bool _pruneNext;
public: public:
DepthFirstEnumerator(Traverser* traverser, std::string const& startVertex, DepthFirstEnumerator(Traverser* traverser, std::string const& startVertex,
TraverserOptions* opts); TraverserOptions* opts);
@ -120,16 +125,14 @@ class DepthFirstEnumerator final : public PathEnumerator {
bool next() override; bool next() override;
//////////////////////////////////////////////////////////////////////////////
/// @brief Prunes the current path prefix, the next function should not return
/// any path having this prefix anymore.
//////////////////////////////////////////////////////////////////////////////
aql::AqlValue lastVertexToAqlValue() override; aql::AqlValue lastVertexToAqlValue() override;
aql::AqlValue lastEdgeToAqlValue() override; aql::AqlValue lastEdgeToAqlValue() override;
aql::AqlValue pathToAqlValue(arangodb::velocypack::Builder& result) override; aql::AqlValue pathToAqlValue(arangodb::velocypack::Builder& result) override;
private:
bool shouldPrune();
}; };
} // namespace traverser } // namespace traverser
} // namespace arangodb } // namespace arangodb

View File

@ -38,7 +38,8 @@ using namespace arangodb;
using namespace arangodb::traverser; using namespace arangodb::traverser;
using namespace arangodb::graph; using namespace arangodb::graph;
bool Traverser::VertexGetter::getVertex(VPackSlice edge, std::vector<arangodb::velocypack::StringRef>& result) { bool Traverser::VertexGetter::getVertex(VPackSlice edge,
std::vector<arangodb::velocypack::StringRef>& result) {
VPackSlice res = edge; VPackSlice res = edge;
if (!res.isString()) { if (!res.isString()) {
res = transaction::helpers::extractFromFromDocument(edge); res = transaction::helpers::extractFromFromDocument(edge);
@ -48,15 +49,19 @@ bool Traverser::VertexGetter::getVertex(VPackSlice edge, std::vector<arangodb::v
} }
TRI_ASSERT(res.isString()); TRI_ASSERT(res.isString());
if (!_traverser->vertexMatchesConditions(arangodb::velocypack::StringRef(res), result.size())) { if (!_traverser->vertexMatchesConditions(arangodb::velocypack::StringRef(res),
result.size())) {
return false; return false;
} }
result.emplace_back(_traverser->traverserCache()->persistString(arangodb::velocypack::StringRef(res))); result.emplace_back(_traverser->traverserCache()->persistString(
arangodb::velocypack::StringRef(res)));
return true; return true;
} }
bool Traverser::VertexGetter::getSingleVertex(arangodb::velocypack::Slice edge, arangodb::velocypack::StringRef cmp, bool Traverser::VertexGetter::getSingleVertex(arangodb::velocypack::Slice edge,
uint64_t depth, arangodb::velocypack::StringRef& result) { arangodb::velocypack::StringRef cmp,
uint64_t depth,
arangodb::velocypack::StringRef& result) {
VPackSlice resSlice = edge; VPackSlice resSlice = edge;
if (!resSlice.isString()) { if (!resSlice.isString()) {
VPackSlice from = transaction::helpers::extractFromFromDocument(edge); VPackSlice from = transaction::helpers::extractFromFromDocument(edge);
@ -67,7 +72,8 @@ bool Traverser::VertexGetter::getSingleVertex(arangodb::velocypack::Slice edge,
} }
TRI_ASSERT(resSlice.isString()); TRI_ASSERT(resSlice.isString());
} }
result = _traverser->traverserCache()->persistString(arangodb::velocypack::StringRef(resSlice)); result = _traverser->traverserCache()->persistString(
arangodb::velocypack::StringRef(resSlice));
return _traverser->vertexMatchesConditions(result, depth); return _traverser->vertexMatchesConditions(result, depth);
} }
@ -86,7 +92,8 @@ bool Traverser::UniqueVertexGetter::getVertex(VPackSlice edge,
TRI_ASSERT(toAdd.isString()); TRI_ASSERT(toAdd.isString());
} }
arangodb::velocypack::StringRef toAddStr = _traverser->traverserCache()->persistString(arangodb::velocypack::StringRef(toAdd)); arangodb::velocypack::StringRef toAddStr =
_traverser->traverserCache()->persistString(arangodb::velocypack::StringRef(toAdd));
// First check if we visited it. If not, then mark // First check if we visited it. If not, then mark
if (_returnedVertices.find(toAddStr) != _returnedVertices.end()) { if (_returnedVertices.find(toAddStr) != _returnedVertices.end()) {
// This vertex is not unique. // This vertex is not unique.
@ -104,7 +111,8 @@ bool Traverser::UniqueVertexGetter::getVertex(VPackSlice edge,
} }
bool Traverser::UniqueVertexGetter::getSingleVertex(arangodb::velocypack::Slice edge, bool Traverser::UniqueVertexGetter::getSingleVertex(arangodb::velocypack::Slice edge,
arangodb::velocypack::StringRef cmp, uint64_t depth, arangodb::velocypack::StringRef cmp,
uint64_t depth,
arangodb::velocypack::StringRef& result) { arangodb::velocypack::StringRef& result) {
VPackSlice resSlice = edge; VPackSlice resSlice = edge;
if (!resSlice.isString()) { if (!resSlice.isString()) {
@ -115,7 +123,8 @@ bool Traverser::UniqueVertexGetter::getSingleVertex(arangodb::velocypack::Slice
TRI_ASSERT(resSlice.isString()); TRI_ASSERT(resSlice.isString());
} }
result = _traverser->traverserCache()->persistString(arangodb::velocypack::StringRef(resSlice)); result = _traverser->traverserCache()->persistString(
arangodb::velocypack::StringRef(resSlice));
// First check if we visited it. If not, then mark // First check if we visited it. If not, then mark
if (_returnedVertices.find(result) != _returnedVertices.end()) { if (_returnedVertices.find(result) != _returnedVertices.end()) {
// This vertex is not unique. // This vertex is not unique.
@ -141,7 +150,6 @@ Traverser::Traverser(arangodb::traverser::TraverserOptions* opts, transaction::M
: _trx(trx), : _trx(trx),
_mmdr(new arangodb::ManagedDocumentResult()), _mmdr(new arangodb::ManagedDocumentResult()),
_startIdBuilder(), _startIdBuilder(),
_pruneNext(false),
_done(true), _done(true),
_opts(opts), _opts(opts),
_canUseOptimizedNeighbors(false) { _canUseOptimizedNeighbors(false) {
@ -154,7 +162,8 @@ Traverser::Traverser(arangodb::traverser::TraverserOptions* opts, transaction::M
Traverser::~Traverser() {} Traverser::~Traverser() {}
bool arangodb::traverser::Traverser::edgeMatchesConditions(VPackSlice e, arangodb::velocypack::StringRef vid, bool arangodb::traverser::Traverser::edgeMatchesConditions(VPackSlice e,
arangodb::velocypack::StringRef vid,
uint64_t depth, size_t cursorId) { uint64_t depth, size_t cursorId) {
if (!_opts->evaluateEdgeExpression(e, vid, depth, cursorId)) { if (!_opts->evaluateEdgeExpression(e, vid, depth, cursorId)) {
return false; return false;
@ -162,7 +171,8 @@ bool arangodb::traverser::Traverser::edgeMatchesConditions(VPackSlice e, arangod
return true; return true;
} }
bool arangodb::traverser::Traverser::vertexMatchesConditions(arangodb::velocypack::StringRef v, uint64_t depth) { bool arangodb::traverser::Traverser::vertexMatchesConditions(arangodb::velocypack::StringRef v,
uint64_t depth) {
if (_opts->vertexHasFilter(depth)) { if (_opts->vertexHasFilter(depth)) {
// We always need to destroy this vertex // We always need to destroy this vertex
aql::AqlValue vertex = fetchVertexData(v); aql::AqlValue vertex = fetchVertexData(v);
@ -198,4 +208,4 @@ size_t arangodb::traverser::Traverser::getAndResetFilteredPaths() {
void arangodb::traverser::Traverser::allowOptimizedNeighbors() { void arangodb::traverser::Traverser::allowOptimizedNeighbors() {
_canUseOptimizedNeighbors = true; _canUseOptimizedNeighbors = true;
} }

View File

@ -134,7 +134,8 @@ class Traverser {
virtual ~VertexGetter() = default; virtual ~VertexGetter() = default;
virtual bool getVertex(arangodb::velocypack::Slice, std::vector<arangodb::velocypack::StringRef>&); virtual bool getVertex(arangodb::velocypack::Slice,
std::vector<arangodb::velocypack::StringRef>&);
virtual bool getSingleVertex(arangodb::velocypack::Slice, arangodb::velocypack::StringRef, virtual bool getSingleVertex(arangodb::velocypack::Slice, arangodb::velocypack::StringRef,
uint64_t, arangodb::velocypack::StringRef&); uint64_t, arangodb::velocypack::StringRef&);
@ -156,9 +157,11 @@ class Traverser {
~UniqueVertexGetter() = default; ~UniqueVertexGetter() = default;
bool getVertex(arangodb::velocypack::Slice, std::vector<arangodb::velocypack::StringRef>&) override; bool getVertex(arangodb::velocypack::Slice,
std::vector<arangodb::velocypack::StringRef>&) override;
bool getSingleVertex(arangodb::velocypack::Slice, arangodb::velocypack::StringRef, uint64_t, arangodb::velocypack::StringRef&) override; bool getSingleVertex(arangodb::velocypack::Slice, arangodb::velocypack::StringRef,
uint64_t, arangodb::velocypack::StringRef&) override;
void reset(arangodb::velocypack::StringRef const&) override; void reset(arangodb::velocypack::StringRef const&) override;
@ -218,7 +221,8 @@ class Traverser {
/// @brief Function to load the other sides vertex of an edge /// @brief Function to load the other sides vertex of an edge
/// Returns true if the vertex passes filtering conditions /// Returns true if the vertex passes filtering conditions
virtual bool getSingleVertex(arangodb::velocypack::Slice edge, virtual bool getSingleVertex(arangodb::velocypack::Slice edge,
arangodb::velocypack::StringRef const sourceVertexId, uint64_t depth, arangodb::velocypack::StringRef const sourceVertexId,
uint64_t depth,
arangodb::velocypack::StringRef& targetVertexId) = 0; arangodb::velocypack::StringRef& targetVertexId) = 0;
public: public:
@ -276,7 +280,8 @@ class Traverser {
bool hasMore() { return !_done; } bool hasMore() { return !_done; }
bool edgeMatchesConditions(arangodb::velocypack::Slice edge, arangodb::velocypack::StringRef vid, bool edgeMatchesConditions(arangodb::velocypack::Slice edge,
arangodb::velocypack::StringRef vid,
uint64_t depth, size_t cursorId); uint64_t depth, size_t cursorId);
bool vertexMatchesConditions(arangodb::velocypack::StringRef vid, uint64_t depth); bool vertexMatchesConditions(arangodb::velocypack::StringRef vid, uint64_t depth);
@ -306,9 +311,6 @@ class Traverser {
/// @brief Builder for the start value slice. Leased from transaction /// @brief Builder for the start value slice. Leased from transaction
velocypack::Builder _startIdBuilder; velocypack::Builder _startIdBuilder;
/// @brief toggle if this path should be pruned on next step
bool _pruneNext;
/// @brief indicator if this traversal is done /// @brief indicator if this traversal is done
bool _done; bool _done;
@ -321,7 +323,8 @@ class Traverser {
virtual aql::AqlValue fetchVertexData(arangodb::velocypack::StringRef vid) = 0; virtual aql::AqlValue fetchVertexData(arangodb::velocypack::StringRef vid) = 0;
/// @brief Function to add the real data of a vertex into a velocypack builder /// @brief Function to add the real data of a vertex into a velocypack builder
virtual void addVertexToVelocyPack(arangodb::velocypack::StringRef vid, arangodb::velocypack::Builder&) = 0; virtual void addVertexToVelocyPack(arangodb::velocypack::StringRef vid,
arangodb::velocypack::Builder&) = 0;
}; };
} // namespace traverser } // namespace traverser
} // namespace arangodb } // namespace arangodb

View File

@ -25,6 +25,7 @@
#include "Aql/Ast.h" #include "Aql/Ast.h"
#include "Aql/Expression.h" #include "Aql/Expression.h"
#include "Aql/PruneExpressionEvaluator.h"
#include "Aql/Query.h" #include "Aql/Query.h"
#include "Basics/VelocyPackHelper.h" #include "Basics/VelocyPackHelper.h"
#include "Cluster/ClusterEdgeCursor.h" #include "Cluster/ClusterEdgeCursor.h"
@ -431,8 +432,8 @@ bool TraverserOptions::hasEdgeFilter(int64_t depth, size_t cursorId) const {
} }
bool TraverserOptions::evaluateEdgeExpression(arangodb::velocypack::Slice edge, bool TraverserOptions::evaluateEdgeExpression(arangodb::velocypack::Slice edge,
arangodb::velocypack::StringRef vertexId, uint64_t depth, arangodb::velocypack::StringRef vertexId,
size_t cursorId) const { uint64_t depth, size_t cursorId) const {
arangodb::aql::Expression* expression = nullptr; arangodb::aql::Expression* expression = nullptr;
auto specific = _depthLookupInfo.find(depth); auto specific = _depthLookupInfo.find(depth);
@ -493,9 +494,8 @@ bool TraverserOptions::evaluateVertexExpression(arangodb::velocypack::Slice vert
return evaluateExpression(expression, vertex); return evaluateExpression(expression, vertex);
} }
EdgeCursor* arangodb::traverser::TraverserOptions::nextCursor(ManagedDocumentResult* mmdr, EdgeCursor* arangodb::traverser::TraverserOptions::nextCursor(
arangodb::velocypack::StringRef vid, ManagedDocumentResult* mmdr, arangodb::velocypack::StringRef vid, uint64_t depth) {
uint64_t depth) {
if (_isCoordinator) { if (_isCoordinator) {
return nextCursorCoordinator(vid, depth); return nextCursorCoordinator(vid, depth);
} }
@ -510,7 +510,8 @@ EdgeCursor* arangodb::traverser::TraverserOptions::nextCursor(ManagedDocumentRes
return nextCursorLocal(mmdr, vid, list); return nextCursorLocal(mmdr, vid, list);
} }
EdgeCursor* TraverserOptions::nextCursorCoordinator(arangodb::velocypack::StringRef vid, uint64_t depth) { EdgeCursor* TraverserOptions::nextCursorCoordinator(arangodb::velocypack::StringRef vid,
uint64_t depth) {
TRI_ASSERT(_traverser != nullptr); TRI_ASSERT(_traverser != nullptr);
auto cursor = std::make_unique<ClusterEdgeCursor>(vid, depth, this); auto cursor = std::make_unique<ClusterEdgeCursor>(vid, depth, this);
return cursor.release(); return cursor.release();
@ -548,3 +549,13 @@ double TraverserOptions::estimateCost(size_t& nrItems) const {
nrItems = count; nrItems = count;
return cost; return cost;
} }
void TraverserOptions::activatePrune(std::vector<aql::Variable const*> const&& vars,
std::vector<aql::RegisterId> const&& regs,
size_t vertexVarIdx, size_t edgeVarIdx,
size_t pathVarIdx, aql::Expression* expr) {
_pruneExpression =
std::make_unique<aql::PruneExpressionEvaluator>(_trx, _query, std::move(vars),
std::move(regs), vertexVarIdx,
edgeVarIdx, pathVarIdx, expr);
}

View File

@ -43,8 +43,10 @@ class Slice;
namespace aql { namespace aql {
struct AstNode; struct AstNode;
class Expression; class Expression;
class PruneExpressionEvaluator;
class Query; class Query;
class TraversalNode; class TraversalNode;
struct Variable;
} // namespace aql } // namespace aql
namespace graph { namespace graph {
@ -71,6 +73,10 @@ struct TraverserOptions : public graph::BaseOptions {
arangodb::traverser::ClusterTraverser* _traverser; arangodb::traverser::ClusterTraverser* _traverser;
/// @brief The condition given in PRUNE (might be empty)
/// The Node keeps responsibility
std::unique_ptr<aql::PruneExpressionEvaluator> _pruneExpression;
public: public:
uint64_t minDepth; uint64_t minDepth;
@ -114,17 +120,30 @@ struct TraverserOptions : public graph::BaseOptions {
bool hasEdgeFilter(int64_t, size_t) const; bool hasEdgeFilter(int64_t, size_t) const;
bool evaluateEdgeExpression(arangodb::velocypack::Slice, arangodb::velocypack::StringRef vertexId, bool evaluateEdgeExpression(arangodb::velocypack::Slice,
arangodb::velocypack::StringRef vertexId,
uint64_t, size_t) const; uint64_t, size_t) const;
bool evaluateVertexExpression(arangodb::velocypack::Slice, uint64_t) const; bool evaluateVertexExpression(arangodb::velocypack::Slice, uint64_t) const;
graph::EdgeCursor* nextCursor(ManagedDocumentResult*, arangodb::velocypack::StringRef vid, uint64_t); graph::EdgeCursor* nextCursor(ManagedDocumentResult*,
arangodb::velocypack::StringRef vid, uint64_t);
void linkTraverser(arangodb::traverser::ClusterTraverser*); void linkTraverser(arangodb::traverser::ClusterTraverser*);
double estimateCost(size_t& nrItems) const override; double estimateCost(size_t& nrItems) const override;
void activatePrune(std::vector<aql::Variable const*> const&& vars,
std::vector<aql::RegisterId> const&& regs, size_t vertexVarIdx,
size_t edgeVarIdx, size_t pathVarIdx, aql::Expression* expr);
bool usesPrune() const { return _pruneExpression != nullptr; }
aql::PruneExpressionEvaluator* getPruneEvaluator() {
TRI_ASSERT(usesPrune());
return _pruneExpression.get();
}
private: private:
graph::EdgeCursor* nextCursorCoordinator(arangodb::velocypack::StringRef vid, uint64_t); graph::EdgeCursor* nextCursorCoordinator(arangodb::velocypack::StringRef vid, uint64_t);
}; };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff