1
0
Fork 0

Merge branch 'devel' of github.com:triAGENS/ArangoDB into devel

This commit is contained in:
Michael Hackstein 2014-01-28 09:48:04 +01:00
commit dbcdd72da4
9 changed files with 706 additions and 56 deletions

View File

@ -1,6 +1,50 @@
v1.5.0 (XXXX-XX-XX) v1.5.0 (XXXX-XX-XX)
------------------- -------------------
* allow vertex and edge filtering with user-defined functions in TRAVERSAL,
TRAVERSAL_TREE and SHORTEST_PATH AQL functions:
// using user-defined AQL functions for edge and vertex filtering
RETURN TRAVERSAL(friends, friendrelations, "friends/john", "outbound", {
followEdges: "myfunctions::checkedge",
filterVertices: "myfunctions::checkvertex"
})
// using the following custom filter functions
var aqlfunctions = require("org/arangodb/aql/functions");
aqlfunctions.register("myfunctions::checkedge", function (config, vertex, edge, path) {
return (edge.type !== 'dislikes'); // don't follow these edges
}, false);
aqlfunctions.register("myfunctions::checkvertex", function (config, vertex, path) {
if (vertex.isDeleted || ! vertex.isActive) {
return [ "prune", "exclude" ]; // exclude these and don't follow them
}
return [ ]; // include everything else
}, false);
* added SHORTEST_PATH AQL function
this calculates the shortest paths between two vertices, using the Dijkstra
algorithm, employing a min-heap
By default, ArangoDB does not know the distance between any two vertices and
will use a default distance of 1. A custom distance function can be registered
as an AQL user function to make the distance calculation use any document
attributes or custom logic:
RETURN SHORTEST_PATH(cities, motorways, "cities/CGN", "cities/MUC", "outbound", {
paths: true,
distance: "myfunctions::citydistance"
})
// using the following custom distance function
var aqlfunctions = require("org/arangodb/aql/functions");
aqlfunctions.register("myfunctions::distance", function (config, vertex1, vertex2, edge) {
return Math.sqrt(Math.pow(vertex1.x - vertex2.x) + Math.pow(vertex1.y - vertex2.y));
}, false);
* issue #751: Create database through API should return HTTP status code 201 * issue #751: Create database through API should return HTTP status code 201
By default, the server now returns HTTP 201 (created) when creating a new By default, the server now returns HTTP 201 (created) when creating a new

View File

@ -1420,7 +1420,7 @@ Example calls:
- `paths`: if `true`, the paths encountered during the traversal will - `paths`: if `true`, the paths encountered during the traversal will
also be returned along with each traversed vertex. If `false`, only the also be returned along with each traversed vertex. If `false`, only the
encountered vertices will be returned. encountered vertices will be returned.
- `uniqueness`: an optional document with the following properties: - `uniqueness`: an optional document with the following attributes:
- `vertices`: - `vertices`:
- `none`: no vertex uniqueness is enforced - `none`: no vertex uniqueness is enforced
- `global`: a vertex may be visited at most once. This is the default. - `global`: a vertex may be visited at most once. This is the default.
@ -1434,22 +1434,44 @@ Example calls:
- `followEdges`: an optional list of example edge documents that the traversal will - `followEdges`: an optional list of example edge documents that the traversal will
expand into. If no examples are given, the traversal will follow all edges. If one expand into. If no examples are given, the traversal will follow all edges. If one
or many edge examples are given, the traversal will only follow an edge if it matches or many edge examples are given, the traversal will only follow an edge if it matches
at least one of the specified examples. at least one of the specified examples. `followEdges` can also be a string with the
name of an AQL user-defined function that should be responsible for checking if an
edge should be followed. In this case, the AQL function will is expected to have the
following signature:
function (config, vertex, edge, path)
The function is expected to return a boolean value. If ìt returns `true`, the edge
will be followed. If `false` is returned, the edge will be ignored.
- `filterVertices`: an optional list of example vertex documents that the traversal will - `filterVertices`: an optional list of example vertex documents that the traversal will
treat specially. If no examples are given, the traversal will handle all encountered treat specially. If no examples are given, the traversal will handle all encountered
vertices equally. If one or many vertex examples are given, the traversal will exclude vertices equally. If one or many vertex examples are given, the traversal will exclude
the vertex from the result and/or descend into it. the vertex from the result and/or not descend into it. Optionally, `filterVertices` can
- `vertexFilterMethod`: only useful in conjunction with `filterVertices`. If specified, contain the name of a user-defined AQL function that should be responsible for filtering.
it will influence how vertices are handled that don't match the examples in `filterVertices`: If so, the AQL function is expected to have the following signature:
function (config, vertex, path)
If a custom AQL function is used, it is expected to return one of the following values:
- `[ ]`: include the vertex in the result and descend into its connected edges
- `[ "prune" ]`: will include the vertex in the result but not descend into its connected edges
- `[ "exclude" ]`: will not include the vertex in the result but descend into its connected edges
- `[ "prune", "exclude" ]`: will completely ignore the vertex and its connected edges
- `vertexFilterMethod`: only useful in conjunction with `filterVertices` and if no user-defined
AQL function is used.. If specified, it will influence how vertices are handled that don't match
the examples in `filterVertices`:
- `[ "prune" ]`: will include non-matching vertices in the result but not descend into them - `[ "prune" ]`: will include non-matching vertices in the result but not descend into them
- `[ "exclude" ]`: will not include non-matching vertices in the result but descend into them - `[ "exclude" ]`: will not include non-matching vertices in the result but descend into them
- `[ "prune", "exclude" ]`: will neither include non-matching vertices in the result nor descend into them - `[ "prune", "exclude" ]`: will neither include non-matching vertices in the result nor descend into them
The result of the TRAVERSAL function is a list of traversed points. Each point is a The result of the TRAVERSAL function is a list of traversed points. Each point is a
document consisting of the following properties: document consisting of the following attributes:
- `vertex`: the vertex at the traversal point - `vertex`: the vertex at the traversal point
- `path`: The path history for the traversal point. The path is a document with the - `path`: The path history for the traversal point. The path is a document with the
properties `vertices` and `edges`, which are both lists. attributes `vertices` and `edges`, which are both lists. Note that `path` is only present
in the result if the `paths` attribute is set in the @FA{options}.
Example calls: Example calls:
@ -1458,9 +1480,10 @@ Example calls:
order: "postorder", order: "postorder",
itemOrder: "backward", itemOrder: "backward",
maxDepth: 6, maxDepth: 6,
trackPaths: true paths: true
}) })
// filtering on specific edges (by specifying example edges)
TRAVERSAL(friends, friendrelations, "friends/john", "outbound", { TRAVERSAL(friends, friendrelations, "friends/john", "outbound", {
strategy: "breadthfirst", strategy: "breadthfirst",
order: "preorder", order: "preorder",
@ -1468,15 +1491,38 @@ Example calls:
followEdges: [ { type: "knows" }, { state: "FL" } ] followEdges: [ { type: "knows" }, { state: "FL" } ]
}) })
// filtering on specific edges and vertices
TRAVERSAL(friends, friendrelations, "friends/john", "outbound", { TRAVERSAL(friends, friendrelations, "friends/john", "outbound", {
strategy: "breadthfirst", strategy: "breadthfirst",
order: "preorder", order: "preorder",
itemOrder: "forward", itemOrder: "forward",
followEdges: [ { type: "knows" }, { state: "FL" } ], followEdges: [ { type: "knows" }, { state: "FL" } ],
filterVertices: [ { isActive: true } ], filterVertices: [ { isActive: true }, { isDeleted: false } ],
vertexFilterMethod: [ "prune", "exclude" ] vertexFilterMethod: [ "prune", "exclude" ]
}) })
// using user-defined AQL functions for edge and vertex filtering
TRAVERSAL(friends, friendrelations, "friends/john", "outbound", {
followEdges: "myfunctions::checkedge",
filterVertices: "myfunctions::checkvertex"
})
// to register the custom AQL functions, execute something in the fashion of the
// following commands in arangosh once:
var aqlfunctions = require("org/arangodb/aql/functions");
// these are the actual filter functions
aqlfunctions.register("myfunctions::checkedge", function (config, vertex, edge, path) {
return (edge.type !== 'dislikes'); // don't follow these edges
}, false);
aqlfunctions.register("myfunctions::checkvertex", function (config, vertex, path) {
if (vertex.isDeleted || ! vertex.isActive) {
return [ "prune", "exclude" ]; // exclude these and don't follow them
}
return [ ]; // include everything else
}, false);
- @FN{TRAVERSAL_TREE(@FA{vertexcollection}, @FA{edgecollection}, @FA{startVertex}, @FA{direction}, @FA{connectName}, @FA{options})}: - @FN{TRAVERSAL_TREE(@FA{vertexcollection}, @FA{edgecollection}, @FA{startVertex}, @FA{direction}, @FA{connectName}, @FA{options})}:
traverses the graph described by @FA{vertexcollection} and @FA{edgecollection}, traverses the graph described by @FA{vertexcollection} and @FA{edgecollection},
starting at the vertex identified by id @FA{startVertex} and creates a hierchical result. starting at the vertex identified by id @FA{startVertex} and creates a hierchical result.
@ -1490,7 +1536,7 @@ Example calls:
Example calls: Example calls:
TREE(friends, friendrelations, "friends/john", "outbound", "likes", { TRAVERSAL_TREE(friends, friendrelations, "friends/john", "outbound", "likes", {
itemOrder: "forward" itemOrder: "forward"
}) })
@ -1501,6 +1547,106 @@ If no bounds are set, a traversal might run into an endless loop in a cyclic gra
and even in a non-cyclic graph, traversing far into the graph might consume a lot of processing and even in a non-cyclic graph, traversing far into the graph might consume a lot of processing
time and memory for the result set. time and memory for the result set.
- @FN{SHORTEST_PATH(@FA{vertexcollection}, @FA{edgecollection}, @FA{startVertex}, @FA{endVertex}, @FA{direction}, @FA{options})}:
determines the first shortest path from the @FA{startVertex} to the @FA{endVertex}.
Both vertices must be present in the vertex collection specified in @FA{vertexcollection},
and any connecting edges must be present in the collection specified by @FA{edgecollection}.
Vertex connectivity is specified by the @FA{direction} parameter:
- `"outbound"`: vertices are connected in `_from` to `_to` order
- `"inbound"`: vertices are connected in `_to` to `_from` order
- `"any"`: vertices are connected in both `_to` to `_from` and in
`_from` to `_to` order
The search is aborted when a shortest path is found. Only the first shortest path will be
returned. Any vertex will be visited at most once by the search.
Additional options for the traversal can be provided via the @FA{options} document:
- `maxIterations`: Maximum number of iterations in the search. This number can be
set to bound long-running searches. When a search performs as many iterations as the
`maxIterations` value, the search will abort with an error. If `maxIterations` is not
set, a server-defined value may be used.
- `paths`: if `true`, the result will not only contain the vertices along the shortest
path, but also the connecting edges. If `false`, only the encountered vertices will
be returned.
- `distance`: an optional custom function to be used when calculating the distance
between a vertex and a neighboring vertex. The expected function signature is:
function (config, vertex1, vertex2, edge)
Both vertices and the connecting edge will be passed into the function. The function
is expected to return a numeric value that expresses the distance between the two
vertices. Higher values will mean higher distances, giving the connection a lower
priority in further analysis.
If no custom distance function is specified, all vertices are assumed to have the
same distance (1) to each other. If a function name is specified, it must have been
registered as a regular user-defined AQL function.
- `followEdges`: an optional list of example edge documents that the search will
expand into. If no examples are given, the search will follow all edges. If one
or many edge examples are given, the search will only follow an edge if it matches
at least one of the specified examples. `followEdges` can also be a string with the
name of an AQL user-defined function that should be responsible for checking if an
edge should be followed. In this case, the AQL function will is expected to have the
following signature:
function (config, vertex, edge, path)
The function is expected to return a boolean value. If ìt returns `true`, the edge
will be followed. If `false` is returned, the edge will be ignored.
- `filterVertices`: an optional list of example vertex documents that the search will
treat specially. If no examples are given, the search will handle all encountered
vertices equally. If one or many vertex examples are given, the search will exclude
the vertex from the result and/or not descend into it. Optionally, `filterVertices` can
contain the name of a user-defined AQL function that should be responsible for filtering.
If so, the AQL function is expected to have the following signature:
function (config, vertex, path)
If a custom AQL function is used, it is expected to return one of the following values:
- `[ ]`: include the vertex in the result and descend into its connected edges
- `[ "prune" ]`: will include the vertex in the result but not descend into its connected edges
- `[ "exclude" ]`: will not include the vertex in the result but descend into its connected edges
- `[ "prune", "exclude" ]`: will completely ignore the vertex and its connected edges
The result of the SHORTEST_PATH function is a list with the components of the shortest
path. Each component is a document consisting of the following attributes:
- `vertex`: the vertex at the traversal point
- `path`: The path history for the traversal point. The path is a document with the
attributes `vertices` and `edges`, which are both lists. Note that `path` is only present
in the result if the `paths` attribute is set in the @FA{options}.
Example calls:
SHORTEST_PATH(cities, motorways, "cities/CGN", "cities/MUC", "outbound", {
paths: true
})
// using a user-defined distance function
SHORTEST_PATH(cities, motorways, "cities/CGN", "cities/MUC", "outbound", {
paths: true,
distance: "myfunctions::citydistance"
})
// using a user-defined function to filter edges
SHORTEST_PATH(cities, motorways, "cities/CGN", "cities/MUC", "outbound", {
paths: true,
followEdges: "myfunctions::checkedge"
})
// to register a custom AQL distance function, execute something in the fashion of the
// following commands in arangosh once:
var aqlfunctions = require("org/arangodb/aql/functions");
// this is the actual distance function
aqlfunctions.register("myfunctions::distance", function (config, vertex1, vertex2, edge) {
return Math.sqrt(Math.pow(vertex1.x - vertex2.x) + Math.pow(vertex1.y - vertex2.y));
}, false);
// this is the filter function for the edges
aqlfunctions.register("myfunctions::checkedge", function (config, vertex, edge, path) {
return (edge.underConstruction === false); // don't follow these edges
}, false);
- @FN{EDGES(@FA{edgecollection}, @FA{startvertex}, @FA{direction}, @FA{edgeexamples})}: - @FN{EDGES(@FA{edgecollection}, @FA{startvertex}, @FA{direction}, @FA{edgeexamples})}:
return all edges connected to the vertex @FA{startvertex} as a list. The possible values for return all edges connected to the vertex @FA{startvertex} as a list. The possible values for
@FA{direction} are: @FA{direction} are:

View File

@ -701,8 +701,9 @@ TRI_associative_pointer_t* TRI_CreateFunctionsAql (void) {
// graph functions // graph functions
REGISTER_FUNCTION("PATHS", "GRAPH_PATHS", false, false, "c,h|s,b", &OptimisePaths); REGISTER_FUNCTION("PATHS", "GRAPH_PATHS", false, false, "c,h|s,b", &OptimisePaths);
REGISTER_FUNCTION("TRAVERSAL", "GRAPH_TRAVERSAL", false, false, "h,h,s,s,a", NULL); REGISTER_FUNCTION("SHORTEST_PATH", "GRAPH_SHORTEST_PATH", false, false, "h,h,s,s,s|a", NULL);
REGISTER_FUNCTION("TRAVERSAL_TREE", "GRAPH_TRAVERSAL_TREE", false, false, "h,h,s,s,s,a", NULL); REGISTER_FUNCTION("TRAVERSAL", "GRAPH_TRAVERSAL", false, false, "h,h,s,s|a", NULL);
REGISTER_FUNCTION("TRAVERSAL_TREE", "GRAPH_TRAVERSAL_TREE", false, false, "h,h,s,s,s|a", NULL);
REGISTER_FUNCTION("EDGES", "GRAPH_EDGES", false, false, "h,s,s|l", NULL); REGISTER_FUNCTION("EDGES", "GRAPH_EDGES", false, false, "h,s,s|l", NULL);
REGISTER_FUNCTION("NEIGHBORS", "GRAPH_NEIGHBORS", false, false, "h,h,s,s|l", NULL); REGISTER_FUNCTION("NEIGHBORS", "GRAPH_NEIGHBORS", false, false, "h,h,s,s|l", NULL);

View File

@ -127,6 +127,10 @@ var actions = require("org/arangodb/actions");
/// If the transaction specification is either missing or malformed, the server /// If the transaction specification is either missing or malformed, the server
/// will respond with `HTTP 400`. /// will respond with `HTTP 400`.
/// ///
/// @RESTRETURNCODE{404}
/// If the transaction specification contains an unknown collection, the server
/// will respond with `HTTP 404`.
///
/// @RESTRETURNCODE{500} /// @RESTRETURNCODE{500}
/// Exceptions thrown by users will make the server respond with a return code of /// Exceptions thrown by users will make the server respond with a return code of
/// `HTTP 500` /// `HTTP 500`

View File

@ -105,8 +105,13 @@ abortedException.prototype = new Error();
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function collectionDatasourceFactory (edgeCollection) { function collectionDatasourceFactory (edgeCollection) {
var c = edgeCollection;
if (typeof c === 'string') {
c = db._collection(c);
}
return { return {
edgeCollection: edgeCollection, edgeCollection: c,
getVertexId: function (vertex) { getVertexId: function (vertex) {
return vertex._id; return vertex._id;
@ -947,14 +952,13 @@ function dijkstraSearch () {
var i, n; var i, n;
if (currentNode.vertex._id === endVertex._id) { if (currentNode.vertex._id === endVertex._id) {
var vertices = this.vertexList(currentNode); var vertices = this.vertexList(currentNode).reverse();
if (config.order !== ArangoTraverser.PRE_ORDER) {
vertices.reverse();
}
n = vertices.length; n = vertices.length;
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
config.visitor(config, result, vertices[i].vertex, this.buildPath(vertices[i])); if (! vertices[i].hide) {
config.visitor(config, result, vertices[i].vertex, this.buildPath(vertices[i]));
}
} }
return; return;
} }
@ -968,9 +972,19 @@ function dijkstraSearch () {
} }
currentNode.visited = true; currentNode.visited = true;
var dist = currentNode.dist;
var path = this.buildPath(currentNode); var path = this.buildPath(currentNode);
var filterResult = parseFilterResult(config.filter(config, currentNode.vertex, path));
if (! filterResult.visit) {
currentNode.hide = true;
}
if (! filterResult.expand) {
continue;
}
var dist = currentNode.dist;
var connected = config.expander(config, currentNode.vertex, path); var connected = config.expander(config, currentNode.vertex, path);
n = connected.length; n = connected.length;

View File

@ -104,8 +104,13 @@ abortedException.prototype = new Error();
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function collectionDatasourceFactory (edgeCollection) { function collectionDatasourceFactory (edgeCollection) {
var c = edgeCollection;
if (typeof c === 'string') {
c = db._collection(c);
}
return { return {
edgeCollection: edgeCollection, edgeCollection: c,
getVertexId: function (vertex) { getVertexId: function (vertex) {
return vertex._id; return vertex._id;
@ -946,14 +951,13 @@ function dijkstraSearch () {
var i, n; var i, n;
if (currentNode.vertex._id === endVertex._id) { if (currentNode.vertex._id === endVertex._id) {
var vertices = this.vertexList(currentNode); var vertices = this.vertexList(currentNode).reverse();
if (config.order !== ArangoTraverser.PRE_ORDER) {
vertices.reverse();
}
n = vertices.length; n = vertices.length;
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
config.visitor(config, result, vertices[i].vertex, this.buildPath(vertices[i])); if (! vertices[i].hide) {
config.visitor(config, result, vertices[i].vertex, this.buildPath(vertices[i]));
}
} }
return; return;
} }
@ -967,9 +971,19 @@ function dijkstraSearch () {
} }
currentNode.visited = true; currentNode.visited = true;
var dist = currentNode.dist;
var path = this.buildPath(currentNode); var path = this.buildPath(currentNode);
var filterResult = parseFilterResult(config.filter(config, currentNode.vertex, path));
if (! filterResult.visit) {
currentNode.hide = true;
}
if (! filterResult.expand) {
continue;
}
var dist = currentNode.dist;
var connected = config.expander(config, currentNode.vertex, path); var connected = config.expander(config, currentNode.vertex, path);
n = connected.length; n = connected.length;

View File

@ -3751,7 +3751,7 @@ function TRAVERSAL_TREE_VISITOR (config, result, vertex, path) {
/// @brief expander callback function for traversal /// @brief expander callback function for traversal
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_FILTER (config, vertex, edge, path) { function TRAVERSAL_EDGE_EXAMPLE_FILTER (config, vertex, edge, path) {
"use strict"; "use strict";
return MATCHES(edge, config.expandEdgeExamples); return MATCHES(edge, config.expandEdgeExamples);
@ -3764,7 +3764,7 @@ function TRAVERSAL_FILTER (config, vertex, edge, path) {
function TRAVERSAL_VERTEX_FILTER (config, vertex, path) { function TRAVERSAL_VERTEX_FILTER (config, vertex, path) {
"use strict"; "use strict";
if (!MATCHES(vertex, config.filterVertexExamples)) { if (! MATCHES(vertex, config.filterVertexExamples)) {
return config.vertexFilterMethod; return config.vertexFilterMethod;
} }
} }
@ -3775,6 +3775,11 @@ function TRAVERSAL_VERTEX_FILTER (config, vertex, path) {
function TRAVERSAL_CHECK_EXAMPLES_TYPEWEIGHTS (examples, func) { function TRAVERSAL_CHECK_EXAMPLES_TYPEWEIGHTS (examples, func) {
"use strict"; "use strict";
if (TYPEWEIGHT(examples) === TYPEWEIGHT_STRING) {
// a callback function was supplied. this is considered valid
return;
}
if (TYPEWEIGHT(examples) !== TYPEWEIGHT_LIST) { if (TYPEWEIGHT(examples) !== TYPEWEIGHT_LIST) {
THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, func); THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, func);
@ -3793,15 +3798,29 @@ function TRAVERSAL_CHECK_EXAMPLES_TYPEWEIGHTS (examples, func) {
/// @brief traverse a graph /// @brief traverse a graph
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_FUNC (func, vertexCollection, edgeCollection, startVertex, direction, params) { function TRAVERSAL_FUNC (func,
vertexCollection,
edgeCollection,
startVertex,
endVertex,
direction,
params) {
"use strict"; "use strict";
if (startVertex.indexOf('/') === -1) { if (startVertex.indexOf('/') === -1) {
startVertex = vertexCollection + '/' + startVertex; startVertex = vertexCollection + '/' + startVertex;
} }
if (endVertex !== undefined && endVertex.indexOf('/') === -1) {
endVertex = vertexCollection + '/' + endVertex;
}
vertexCollection = COLLECTION(vertexCollection); vertexCollection = COLLECTION(vertexCollection);
edgeCollection = COLLECTION(edgeCollection); edgeCollection = COLLECTION(edgeCollection);
if (params === undefined) {
params = { };
}
// check followEdges property // check followEdges property
if (params.followEdges) { if (params.followEdges) {
@ -3822,6 +3841,7 @@ function TRAVERSAL_FUNC (func, vertexCollection, edgeCollection, startVertex, di
} }
var config = { var config = {
distance: params.distance,
connect: params.connect, connect: params.connect,
datasource: TRAVERSAL.collectionDatasourceFactory(edgeCollection), datasource: TRAVERSAL.collectionDatasourceFactory(edgeCollection),
trackPaths: params.paths || false, trackPaths: params.paths || false,
@ -3830,39 +3850,104 @@ function TRAVERSAL_FUNC (func, vertexCollection, edgeCollection, startVertex, di
minDepth: params.minDepth, minDepth: params.minDepth,
maxIterations: params.maxIterations, maxIterations: params.maxIterations,
uniqueness: params.uniqueness, uniqueness: params.uniqueness,
expander: direction expander: direction,
strategy: params.strategy
}; };
if (params.followEdges) { if (params.followEdges) {
config.expandFilter = TRAVERSAL_FILTER; if (typeof params.followEdges === 'string') {
config.expandEdgeExamples = params.followEdges; var f1 = params.followEdges.toUpperCase();
config.expandFilter = function (config, vertex, edge, path) {
return FCALL_USER(f1, [ config, vertex, edge, path ]);
};
}
else {
config.expandFilter = TRAVERSAL_EDGE_EXAMPLE_FILTER;
config.expandEdgeExamples = params.followEdges;
}
} }
if (params.filterVertices) { if (params.filterVertices) {
config.filter = TRAVERSAL_VERTEX_FILTER; if (typeof params.filterVertices === 'string') {
config.filterVertexExamples = params.filterVertices; var f2 = params.filterVertices.toUpperCase();
config.vertexFilterMethod = params.vertexFilterMethod || ["prune","exclude"]; config.filter = function (config, vertex, edge, path) {
return FCALL_USER(f2, [ config, vertex, path ]);
};
}
else {
config.filter = TRAVERSAL_VERTEX_FILTER;
config.filterVertexExamples = params.filterVertices;
config.vertexFilterMethod = params.vertexFilterMethod || ["prune", "exclude"];
}
} }
if (params._sort) { if (params._sort) {
config.sort = function (l, r) { return l._key < r._key ? -1 : 1; }; config.sort = function (l, r) { return l._key < r._key ? -1 : 1; };
} }
// start vertex
var v = null; var v = null;
var result = [ ];
try { try {
v = INTERNAL.db._document(startVertex); v = INTERNAL.db._document(startVertex);
} }
catch (err) { catch (err1) {
} }
// end vertex
var e;
if (endVertex !== undefined) {
try {
e = INTERNAL.db._document(endVertex);
}
catch (err2) {
}
}
var result = [ ];
if (v !== null) { if (v !== null) {
var traverser = new TRAVERSAL.Traverser(config); var traverser = new TRAVERSAL.Traverser(config);
traverser.traverse(result, v); traverser.traverse(result, v, e);
} }
return result; return result;
} }
////////////////////////////////////////////////////////////////////////////////
/// @brief shortest path algorithm
////////////////////////////////////////////////////////////////////////////////
function GRAPH_SHORTEST_PATH (vertexCollection,
edgeCollection,
startVertex,
endVertex,
direction,
params) {
"use strict";
params.strategy = "dijkstra";
params.itemorder = "forward";
params.order = "forward";
params.visitor = TRAVERSAL_VISITOR;
if (typeof params.distance === "string") {
var name = params.distance.toUpperCase();
params.distance = function (config, vertex1, vertex2, edge) {
return FCALL_USER(name, [ config, vertex1, vertex2, edge ]);
};
}
else {
params.distance = undefined;
}
return TRAVERSAL_FUNC("SHORTEST_PATH",
vertexCollection,
edgeCollection,
startVertex,
endVertex,
direction,
params);
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief traverse a graph /// @brief traverse a graph
@ -3880,7 +3965,8 @@ function GRAPH_TRAVERSAL (vertexCollection,
return TRAVERSAL_FUNC("TRAVERSAL", return TRAVERSAL_FUNC("TRAVERSAL",
vertexCollection, vertexCollection,
edgeCollection, edgeCollection,
startVertex, startVertex,
undefined,
direction, direction,
params); params);
} }
@ -3903,8 +3989,10 @@ function GRAPH_TRAVERSAL_TREE (vertexCollection,
THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "TRAVERSAL_TREE"); THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "TRAVERSAL_TREE");
} }
params.strategy = "depthfirst"; if (params === undefined) {
params.order = "preorder"; params = { };
}
params.visitor = TRAVERSAL_TREE_VISITOR; params.visitor = TRAVERSAL_TREE_VISITOR;
params.connect = connectName; params.connect = connectName;
@ -3912,6 +4000,7 @@ function GRAPH_TRAVERSAL_TREE (vertexCollection,
vertexCollection, vertexCollection,
edgeCollection, edgeCollection,
startVertex, startVertex,
undefined,
direction, direction,
params); params);
@ -4103,6 +4192,7 @@ exports.GEO_NEAR = GEO_NEAR;
exports.GEO_WITHIN = GEO_WITHIN; exports.GEO_WITHIN = GEO_WITHIN;
exports.FULLTEXT = FULLTEXT; exports.FULLTEXT = FULLTEXT;
exports.GRAPH_PATHS = GRAPH_PATHS; exports.GRAPH_PATHS = GRAPH_PATHS;
exports.GRAPH_SHORTEST_PATH = GRAPH_SHORTEST_PATH;
exports.GRAPH_TRAVERSAL = GRAPH_TRAVERSAL; exports.GRAPH_TRAVERSAL = GRAPH_TRAVERSAL;
exports.GRAPH_TRAVERSAL_TREE = GRAPH_TRAVERSAL_TREE; exports.GRAPH_TRAVERSAL_TREE = GRAPH_TRAVERSAL_TREE;
exports.GRAPH_EDGES = GRAPH_EDGES; exports.GRAPH_EDGES = GRAPH_EDGES;

View File

@ -187,7 +187,7 @@ extend(RequestContext.prototype, {
constraint[paramName] = this.typeToRegex[attributes.type]; constraint[paramName] = this.typeToRegex[attributes.type];
if (!constraint[paramName]) { if (!constraint[paramName]) {
throw new Error("Illegal attribute type: " + attributes.type); throw new Error("Illegal attribute type: " + attributes.type);
} }
this.route.url = internal.constructUrlObject(url.match, constraint, url.methods[0]); this.route.url = internal.constructUrlObject(url.match, constraint, url.methods[0]);
this.docs.addPathParam(paramName, attributes.description, attributes.type); this.docs.addPathParam(paramName, attributes.description, attributes.type);

View File

@ -473,13 +473,239 @@ function ahuacatlQueryPathsTestSuite () {
}; };
} }
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite for SHORTEST_PATH() function
////////////////////////////////////////////////////////////////////////////////
function ahuacatlQueryShortestPathTestSuite () {
var vn = "UnitTestsTraversalVertices";
var en = "UnitTestsTraversalEdges";
var vertexCollection;
var edgeCollection;
var aqlfunctions = require("org/arangodb/aql/functions");
return {
////////////////////////////////////////////////////////////////////////////////
/// @brief set up
////////////////////////////////////////////////////////////////////////////////
setUp : function () {
db._drop(vn);
db._drop(en);
vertexCollection = db._create(vn);
edgeCollection = db._createEdgeCollection(en);
[ "A", "B", "C", "D", "E", "F", "G", "H" ].forEach(function (item) {
vertexCollection.save({ _key: item, name: item });
});
[ [ "A", "B", 1 ], [ "B", "C", 5 ], [ "C", "D", 1 ], [ "A", "D", 12 ], [ "D", "C", 3 ], [ "C", "B", 2 ], [ "D", "E", 6 ], [ "B", "F", 1 ], [ "E", "G", 5 ], [ "G", "H", 2 ] ].forEach(function (item) {
var l = item[0];
var r = item[1];
var w = item[2];
edgeCollection.save(vn + "/" + l, vn + "/" + r, { _key: l + r, what : l + "->" + r, weight: w });
});
try {
aqlfunctions.unregister("UnitTests::distance");
}
catch (err) {
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief tear down
////////////////////////////////////////////////////////////////////////////////
tearDown : function () {
db._drop(vn);
db._drop(en);
vertexCollection = null;
edgeCollection = null;
try {
aqlfunctions.unregister("UnitTests::distance");
}
catch (err) {
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief shortest path using dijkstra
////////////////////////////////////////////////////////////////////////////////
testShortestPathDijkstraOutbound : function () {
var config = {
paths: true,
_sort: true
};
var actual = getQueryResults("FOR p IN SHORTEST_PATH(@@v, @@e, '" + vn + "/A', '" + vn + "/H', 'outbound', " + JSON.stringify(config) + ") RETURN [ p.vertex._key, p.path.vertices[*]._key, p.path.edges[*]._key ]", { "@v" : vn, "@e" : en });
assertEqual([
[ "A", [ "A" ], [ ] ],
[ "D", [ "A", "D" ], [ "AD" ] ],
[ "E", [ "A", "D", "E" ], [ "AD", "DE" ] ],
[ "G", [ "A", "D", "E", "G" ], [ "AD", "DE", "EG" ] ],
[ "H", [ "A", "D", "E", "G", "H" ], [ "AD", "DE", "EG", "GH" ] ]
], actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief shortest path using dijkstra
////////////////////////////////////////////////////////////////////////////////
testShortestPathDijkstraInbound : function () {
var config = {
_sort: true
};
var actual = getQueryResults("FOR p IN SHORTEST_PATH(@@v, @@e, '" + vn + "/H', '" + vn + "/A', 'inbound', " + JSON.stringify(config) + ") RETURN p.vertex._key", { "@v" : vn, "@e" : en });
assertEqual([ "H", "G", "E", "D", "A" ], actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief shortest path with custom distance function
////////////////////////////////////////////////////////////////////////////////
testShortestPathDijkstraDistance : function () {
aqlfunctions.register("UnitTests::distance", function (config, vertex1, vertex2, edge) {
return edge.weight;
}, false);
var config = {
distance: "UnitTests::distance",
_sort: true
};
var actual = getQueryResults("FOR p IN SHORTEST_PATH(@@v, @@e, '" + vn + "/A', '" + vn + "/H', 'outbound', " + JSON.stringify(config) + ") RETURN p.vertex._key", { "@v" : vn, "@e" : en });
assertEqual([ "A", "B", "C", "D", "E", "G", "H" ], actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief shortest path with vertex filter function
////////////////////////////////////////////////////////////////////////////////
testShortestPathDijkstraVertexFilter1 : function () {
aqlfunctions.register("UnitTests::distance", function (config, vertex, path) {
if (vertex._key === 'B' || vertex._key === 'C') {
return [ 'exclude', 'prune' ];
}
}, false);
var config = {
filterVertices: "UnitTests::distance",
_sort: true
};
var actual = getQueryResults("FOR p IN SHORTEST_PATH(@@v, @@e, '" + vn + "/A', '" + vn + "/H', 'outbound', " + JSON.stringify(config) + ") RETURN p.vertex._key", { "@v" : vn, "@e" : en });
assertEqual([ "A", "D", "E", "G", "H" ], actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief shortest path with vertex filter function
////////////////////////////////////////////////////////////////////////////////
testShortestPathDijkstraVertexFilter2 : function () {
aqlfunctions.register("UnitTests::distance", function (config, vertex, path) {
return [ 'exclude', 'prune' ];
}, false);
var config = {
filterVertices: "UnitTests::distance",
_sort: true
};
var actual = getQueryResults("FOR p IN SHORTEST_PATH(@@v, @@e, '" + vn + "/A', '" + vn + "/H', 'outbound', " + JSON.stringify(config) + ") RETURN p.vertex._key", { "@v" : vn, "@e" : en });
assertEqual([ ], actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief shortest path with edge filter function
////////////////////////////////////////////////////////////////////////////////
testShortestPathDijkstraEdgeFilter : function () {
aqlfunctions.register("UnitTests::distance", function (config, vertex, edge, path) {
return edge.weight <= 6;
}, false);
var config = {
followEdges: "UnitTests::distance",
_sort: true
};
var actual = getQueryResults("FOR p IN SHORTEST_PATH(@@v, @@e, '" + vn + "/A', '" + vn + "/H', 'outbound', " + JSON.stringify(config) + ") RETURN p.vertex._key", { "@v" : vn, "@e" : en });
assertEqual([ "A", "B", "C", "D", "E", "G", "H" ], actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief shortest path, with cycles
////////////////////////////////////////////////////////////////////////////////
testShortestPathDijkstraCycles : function () {
[ [ "B", "A" ], [ "C", "A" ], [ "D", "B" ] ].forEach(function (item) {
var l = item[0];
var r = item[1];
edgeCollection.save(vn + "/" + l, vn + "/" + r, { _key: l + r, what : l + "->" + r });
});
var config = {
paths: true,
_sort: true
};
var actual = getQueryResults("FOR p IN SHORTEST_PATH(@@v, @@e, '" + vn + "/A', '" + vn + "/H', 'outbound', " + JSON.stringify(config) + ") RETURN [ p.vertex._key, p.path.vertices[*]._key, p.path.edges[*]._key ]", { "@v" : vn, "@e" : en });
assertEqual([
[ "A", [ "A" ], [ ] ],
[ "D", [ "A", "D" ], [ "AD" ] ],
[ "E", [ "A", "D", "E" ], [ "AD", "DE" ] ],
[ "G", [ "A", "D", "E", "G" ], [ "AD", "DE", "EG" ] ],
[ "H", [ "A", "D", "E", "G", "H" ], [ "AD", "DE", "EG", "GH" ] ]
], actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief shortest path, non-connected vertices
////////////////////////////////////////////////////////////////////////////////
testShortestPathDijkstraNotConnected : function () {
// this item is not connected to any other
vertexCollection.save({ _key: "J", name: "J" });
var config = {
paths: true,
_sort: true
};
var actual = getQueryResults("FOR p IN SHORTEST_PATH(@@v, @@e, '" + vn + "/A', '" + vn + "/J', 'outbound', " + JSON.stringify(config) + ") RETURN p.vertex._key", { "@v" : vn, "@e" : en });
assertEqual([ ], actual);
}
};
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief test suite for TRAVERSAL() filter function /// @brief test suite for TRAVERSAL() filter function
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function ahuacatlQueryTraversalFilterTestSuite () { function ahuacatlQueryTraversalFilterTestSuite () {
var vn = "UnitTestsTraverseVertices"; var vn = "UnitTestsTraversalVertices";
var en = "UnitTestsTraverseEdges"; var en = "UnitTestsTraversalEdges";
var vertexCollection;
var edgeCollection;
var aqlfunctions = require("org/arangodb/aql/functions");
return { return {
@ -514,6 +740,12 @@ function ahuacatlQueryTraversalFilterTestSuite () {
var r = item[1]; var r = item[1];
edgeCollection.save(vn + "/" + l, vn + "/" + r, { _key: l + r, what : l + "->" + r }); edgeCollection.save(vn + "/" + l, vn + "/" + r, { _key: l + r, what : l + "->" + r });
}); });
try {
aqlfunctions.unregister("UnitTests::filter");
}
catch (err) {
}
}, },
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -523,6 +755,41 @@ function ahuacatlQueryTraversalFilterTestSuite () {
tearDown : function () { tearDown : function () {
db._drop(vn); db._drop(vn);
db._drop(en); db._drop(en);
try {
aqlfunctions.unregister("UnitTests::filter");
}
catch (err) {
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test vertex filter with custom function
////////////////////////////////////////////////////////////////////////////////
testTraversalVertexFilterCallback : function () {
aqlfunctions.register("UnitTests::filter", function (config, vertex, path) {
if (vertex._key.substr(0, 3) === '1-1') {
return;
}
return 'exclude';
});
var config = {
strategy: "depthfirst",
order: "preorder",
itemOrder: "forward",
uniqueness: {
vertices: "global",
edges: "none"
},
_sort: true,
filterVertices: "UnitTests::filter"
};
var actual = getQueryResults("FOR p IN TRAVERSAL(@@v, @@e, '" + vn + "/1', 'outbound', " + JSON.stringify(config) + ") RETURN p.vertex._key", { "@v" : vn, "@e" : en });
assertEqual([ "1-1", "1-1-1", "1-1-2" ], actual);
}, },
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -644,6 +911,32 @@ function ahuacatlQueryTraversalFilterTestSuite () {
var actual = getQueryResults("FOR p IN TRAVERSAL(@@v, @@e, '" + vn + "/1', 'outbound', " + JSON.stringify(config) + ") RETURN p.vertex._key", { "@v" : vn, "@e" : en }); var actual = getQueryResults("FOR p IN TRAVERSAL(@@v, @@e, '" + vn + "/1', 'outbound', " + JSON.stringify(config) + ") RETURN p.vertex._key", { "@v" : vn, "@e" : en });
assertEqual([ "1", "1-1", "1-1-2", "1-2-1", "1-3", "1-3-2" ], actual); assertEqual([ "1", "1-1", "1-1-2", "1-2-1", "1-3", "1-3-2" ], actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test edge filter with custom function
////////////////////////////////////////////////////////////////////////////////
testTraversalEdgeFilterCallback : function () {
aqlfunctions.register("UnitTests::filter", function (config, vertex, edge, path) {
return ! vertex.isDeleted;
});
var config = {
strategy: "depthfirst",
order: "preorder",
itemOrder: "forward",
uniqueness: {
vertices: "global",
edges: "none"
},
_sort: true,
followEdges: "UnitTests::filter"
};
var actual = getQueryResults("FOR p IN TRAVERSAL(@@v, @@e, '" + vn + "/1', 'outbound', " + JSON.stringify(config) + ") RETURN p.vertex._key", { "@v" : vn, "@e" : en });
assertEqual([ "1", "1-1", "1-1-2", "1-3", "1-3-2" ], actual);
} }
}; };
@ -654,11 +947,10 @@ function ahuacatlQueryTraversalFilterTestSuite () {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function ahuacatlQueryTraversalTestSuite () { function ahuacatlQueryTraversalTestSuite () {
var vn = "UnitTestsTraverseVertices"; var vn = "UnitTestsTraversalVertices";
var en = "UnitTestsTraverseEdges"; var en = "UnitTestsTraversalEdges";
var vertexCollection;
var vertices; var edgeCollection;
var edges;
return { return {
@ -693,6 +985,51 @@ function ahuacatlQueryTraversalTestSuite () {
db._drop(en); db._drop(en);
}, },
////////////////////////////////////////////////////////////////////////////////
/// @brief test min-depth filtering
////////////////////////////////////////////////////////////////////////////////
testTraversalBreadthFirst : function () {
var config = {
strategy: "breadthfirst",
order: "preorder",
itemOrder: "forward",
uniqueness: {
vertices: "none",
edges: "path"
},
_sort: true
};
var actual = getQueryResults("FOR p IN TRAVERSAL(@@v, @@e, '" + vn + "/A', 'outbound', " + JSON.stringify(config) + ") RETURN p.vertex._key", { "@v" : vn, "@e" : en });
assertEqual([ "A", "B", "D", "C", "C", "A", "A", "D", "B", "C", "C" ], actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test min-depth filtering, track paths
////////////////////////////////////////////////////////////////////////////////
testTraversalTrackPaths : function () {
var config = {
paths: true,
minDepth: 1,
uniqueness: {
vertices: "global",
edges: "none"
},
_sort: true
};
var actual = getQueryResults("FOR p IN TRAVERSAL(@@v, @@e, '" + vn + "/A', 'outbound', " + JSON.stringify(config) + ") RETURN [ p.vertex._key, p.path.vertices[*]._key, p.path.edges[*]._key ]", { "@v" : vn, "@e" : en });
assertEqual([
[ "B", [ "A", "B" ], [ "AB" ] ],
[ "C", [ "A", "B", "C" ], [ "AB", "BC" ] ],
[ "D", [ "A", "D" ], [ "AD" ] ]
], actual);
},
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief test min-depth filtering /// @brief test min-depth filtering
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -910,11 +1247,10 @@ function ahuacatlQueryTraversalTestSuite () {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function ahuacatlQueryTraversalTreeTestSuite () { function ahuacatlQueryTraversalTreeTestSuite () {
var vn = "UnitTestsTraverseVertices"; var vn = "UnitTestsTraversalVertices";
var en = "UnitTestsTraverseEdges"; var en = "UnitTestsTraversalEdges";
var vertexCollection;
var vertices; var edgeCollection;
var edges;
return { return {
@ -994,6 +1330,7 @@ function ahuacatlQueryTraversalTreeTestSuite () {
jsunity.run(ahuacatlQueryEdgesTestSuite); jsunity.run(ahuacatlQueryEdgesTestSuite);
jsunity.run(ahuacatlQueryPathsTestSuite); jsunity.run(ahuacatlQueryPathsTestSuite);
jsunity.run(ahuacatlQueryShortestPathTestSuite);
jsunity.run(ahuacatlQueryTraversalFilterTestSuite); jsunity.run(ahuacatlQueryTraversalFilterTestSuite);
jsunity.run(ahuacatlQueryTraversalTestSuite); jsunity.run(ahuacatlQueryTraversalTestSuite);
jsunity.run(ahuacatlQueryTraversalTreeTestSuite); jsunity.run(ahuacatlQueryTraversalTreeTestSuite);