mirror of https://gitee.com/bigwinds/arangodb
parent
b04cd607f4
commit
6e8d43b2da
11
CHANGELOG
11
CHANGELOG
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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
|
|
@ -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('/');
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue