diff --git a/CHANGELOG b/CHANGELOG index aacb26315d..ccbcebdb19 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -103,6 +103,12 @@ v1.3.alpha1 (2013-04-05) v1.2.3 (XXXX-XX-XX) ------------------- +* added optional parameter `edgexamples` for AQL function EDGES() and NEIGHBORS() + +* added AQL function NEIGHBORS() + +* added freebsd support + * fixed firstExample() query with `_id` and `_key` attributes * issue triAGENS/ArangoDB-PHP#55: AQL optimiser may have mis-optimised duplicate diff --git a/Documentation/UserManual/Aql.md b/Documentation/UserManual/Aql.md index d217a4ee26..11e30411d7 100644 --- a/Documentation/UserManual/Aql.md +++ b/Documentation/UserManual/Aql.md @@ -1007,7 +1007,8 @@ AQL supports the following functions to operate on document values: continue the comparison with the next example until there are no more examples left. The @FA{examples} must be a list of 1..n example documents, with any number of attributes - each. + each. Note: specifying an empty list of examples is not allowed. + Example usage: RETURN MATCHES({ "test" : 1 }, [ @@ -1287,16 +1288,39 @@ 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 time and memory for the result set. -- @FN{EDGES(@FA{edgecollection}\, @FA{startvertex}\, @FA{direction})}: +- @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 - direction are: + @FA{direction} are: + - `outbound`: return all outbound edges + - `inbound`: return all inbound edges + - `any`: return outbound and inbound edges + + The @FA{edgeexamples} parameter can optionally be used to restrict the results to specific + edge connections only. The matching is then done via the @LIT{MATCHES} function. + To not restrict the result to specific connections, @FA{edgeexamples} should be left + unspecified. + +Example calls: + + EDGES(friendrelations, "friends/john", "outbound") + EDGES(friendrelations, "friends/john", "any", [ { "$label": "knows" } ]) + +- @FN{NEIGHBORS(@FA{vertexcollection}\, @FA{edgecollection}\, @FA{startvertex}\, @FA{direction}, @FA{edgeexamples})}: + return all neighbors that are directly connected to the vertex @FA{startvertex} as a list. + The possible values for @FA{direction} are: - `outbound`: return all outbound edges - `inbound`: return all inbound edges - `any`: return outbound and inbound edges + The @FA{edgeexamples} parameter can optionally be used to restrict the results to specific + edge connections only. The matching is then done via the @LIT{MATCHES} function. + To not restrict the result to specific connections, @FA{edgeexamples} should be left + unspecified. + Example calls: - EDGES(friendrelations, "friends/john", "outgoing") + NEIGHBORS(friends, friendrelations, "friends/john", "outbound") + NEIGHBORS(users, usersrelations, "users/john", "any", [ { "$label": "recommends" } ] ) @subsubsection AqlFunctionsControl Control flow functions diff --git a/arangod/Ahuacatl/ahuacatl-functions.c b/arangod/Ahuacatl/ahuacatl-functions.c index cc1470434d..e991a921e7 100644 --- a/arangod/Ahuacatl/ahuacatl-functions.c +++ b/arangod/Ahuacatl/ahuacatl-functions.c @@ -653,7 +653,8 @@ TRI_associative_pointer_t* TRI_InitialiseFunctionsAql (void) { 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("TRAVERSAL_TREE", "GRAPH_TRAVERSAL_TREE", false, false, "h,h,s,s,s,a", NULL); - REGISTER_FUNCTION("EDGES", "GRAPH_EDGES", false, false, "h,s,s", 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); // misc functions REGISTER_FUNCTION("FAIL", "FAIL", false, false, "|s", NULL); // FAIL is non-deterministic, otherwise query optimisation will fail! diff --git a/js/server/modules/org/arangodb/ahuacatl.js b/js/server/modules/org/arangodb/ahuacatl.js index c7479a0794..ad24dc8982 100644 --- a/js/server/modules/org/arangodb/ahuacatl.js +++ b/js/server/modules/org/arangodb/ahuacatl.js @@ -1,5 +1,5 @@ /*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true, continue: true */ -/*global require, exports, COMPARE_STRING */ +/*global require, exports, COMPARE_STRING, MATCHES */ //////////////////////////////////////////////////////////////////////////////// /// @brief Ahuacatl, internal query functions @@ -79,6 +79,29 @@ var TYPEWEIGHT_DOCUMENT = 16; /// @{ //////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/// @brief filter using a list of examples +//////////////////////////////////////////////////////////////////////////////// + +function FILTER (list, + examples) { + var result = [ ], i; + + if (examples === undefined || examples === null) { + return list; + } + + for (i = 0; i < list.length; ++i) { + var element = list[i]; + + if (MATCHES(element, examples, false)) { + result.push(element); + } + } + + return result; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief throw a runtime exception //////////////////////////////////////////////////////////////////////////////// @@ -2803,6 +2826,7 @@ function MATCHES (element, examples, returnIndex) { if (! Array.isArray(examples)) { examples = [ examples ]; } + if (examples.length === 0) { THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "MATCHES"); } @@ -3258,7 +3282,8 @@ function GRAPH_TRAVERSAL_TREE (vertexCollection, function GRAPH_EDGES (edgeCollection, vertex, - direction) { + direction, + examples) { var c = COLLECTION(edgeCollection), result; // validate arguments @@ -3275,6 +3300,55 @@ function GRAPH_EDGES (edgeCollection, THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "EDGES"); } + return FILTER(result, examples); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return connected neighbors +//////////////////////////////////////////////////////////////////////////////// + +function GRAPH_NEIGHBORS (vertexCollection, + edgeCollection, + vertex, + direction, + examples) { + var c = COLLECTION(vertexCollection); + + if (vertex.indexOf('/') === -1) { + vertex = vertexCollection + '/' + vertex; + } + + var edges = GRAPH_EDGES(edgeCollection, vertex, direction); + var result = [ ]; + + FILTER(edges, examples).forEach (function (e) { + var key; + + if (direction === "inbound") { + key = e._from; + } + else if (direction === "outbound") { + key = e._to; + } + else if (direction === "any") { + key = e._from; + if (key === vertex) { + key = e._to; + } + } + + if (key === vertex) { + // do not return the start vertex itself + return; + } + + try { + result.push({ edge: CLONE(e), vertex: c.document(key) }); + } + catch (err) { + } + }); + return result; } @@ -3446,6 +3520,7 @@ exports.GRAPH_PATHS = GRAPH_PATHS; exports.GRAPH_TRAVERSAL = GRAPH_TRAVERSAL; exports.GRAPH_TRAVERSAL_TREE = GRAPH_TRAVERSAL_TREE; exports.GRAPH_EDGES = GRAPH_EDGES; +exports.GRAPH_NEIGHBORS = GRAPH_NEIGHBORS; exports.NOT_NULL = NOT_NULL; exports.FIRST_LIST = FIRST_LIST; exports.FIRST_DOCUMENT = FIRST_DOCUMENT; diff --git a/js/server/tests/ahuacatl-graph.js b/js/server/tests/ahuacatl-graph.js index 07f71560ac..f4391e3ad7 100644 --- a/js/server/tests/ahuacatl-graph.js +++ b/js/server/tests/ahuacatl-graph.js @@ -206,6 +206,72 @@ function ahuacatlQueryEdgesTestSuite () { } }, +//////////////////////////////////////////////////////////////////////////////// +/// @brief checks NEIGHBORS() +//////////////////////////////////////////////////////////////////////////////// + + testNeighborsAny : function () { + var actual; + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v1', 'any') SORT n.vertex._key RETURN [ n.vertex._key, n.edge.what ]", true); + assertEqual(actual, [ [ "v2", "v1->v2" ], [ "v3", "v1->v3" ] ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v2', 'any') SORT n.vertex._key RETURN [ n.vertex._key, n.edge.what ]", true); + assertEqual(actual, [ [ "v1", "v1->v2" ], [ "v3", "v2->v3" ], [ "v4", "v4->v2" ] ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v3', 'any') SORT n.vertex._key, n.edge.what RETURN [ n.vertex._key, n.edge.what ]", true); + assertEqual(actual, [ [ "v1", "v1->v3"], [ "v2", "v2->v3" ], [ "v4", "v3->v4" ], [ "v6", "v3->v6" ], [ "v6", "v6->v3"], [ "v7", "v3->v7" ], [ "v7", "v7->v3" ] ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v8', 'any') SORT n.vertex._key RETURN n.vertex._key", true); + assertEqual(actual, [ ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v5', 'any') SORT n.vertex._key RETURN n.vertex._key", true); + assertEqual(actual, [ ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/thefox', 'any') SORT n.vertex._key RETURN n.vertex._key", true); + assertEqual(actual, [ ]); + + try { + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'thefox/thefox', 'any') SORT n.vertex._key RETURN n.vertex._key", true); + } + catch (err) { + assertEqual(errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.code, err.errorNum); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief checks NEIGHBORS() +//////////////////////////////////////////////////////////////////////////////// + + testNeighborsIn : function () { + var actual; + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v1', 'inbound') SORT n.vertex._key RETURN [ n.vertex._key, n.edge.what ]", true); + assertEqual(actual, [ ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v2', 'inbound') SORT n.vertex._key RETURN [ n.vertex._key, n.edge.what ]", true); + assertEqual(actual, [ [ "v1", "v1->v2" ], [ "v4", "v4->v2" ] ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v3', 'inbound') SORT n.vertex._key RETURN [ n.vertex._key, n.edge.what ]", true); + assertEqual(actual, [ [ "v1", "v1->v3"], [ "v2", "v2->v3" ], [ "v6", "v6->v3"], [ "v7", "v7->v3" ] ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v8', 'inbound') SORT n.vertex._key RETURN n.vertex._key", true); + assertEqual(actual, [ ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v5', 'inbound') SORT n.vertex._key RETURN n.vertex._key", true); + assertEqual(actual, [ ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/thefox', 'inbound') SORT n.vertex._key RETURN n.vertex._key", true); + assertEqual(actual, [ ]); + + try { + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'thefox/thefox', 'inbound') SORT n.vertex._key RETURN n.vertex._key", true); + } + catch (err) { + assertEqual(errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.code, err.errorNum); + } + }, + //////////////////////////////////////////////////////////////////////////////// /// @brief checks EDGES() //////////////////////////////////////////////////////////////////////////////// @@ -222,9 +288,6 @@ function ahuacatlQueryEdgesTestSuite () { actual = getQueryResults("FOR e IN EDGES(UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v3', 'outbound') SORT e.what RETURN e.what", true); assertEqual(actual, [ "v3->v4", "v3->v6", "v3->v7" ]); - actual = getQueryResults("FOR e IN EDGES(UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v3', 'outbound') SORT e.what RETURN e.what", true); - assertEqual(actual, [ "v3->v4", "v3->v6", "v3->v7" ]); - actual = getQueryResults("FOR e IN EDGES(UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v8', 'outbound') SORT e.what RETURN e.what", true); assertEqual(actual, [ ]); @@ -240,6 +303,39 @@ function ahuacatlQueryEdgesTestSuite () { catch (err) { assertEqual(errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.code, err.errorNum); } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief checks NEIGHBORS() +//////////////////////////////////////////////////////////////////////////////// + + testNeighborsOut : function () { + var actual; + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v1', 'outbound') SORT n.vertex._key RETURN [ n.vertex._key, n.edge.what ]", true); + assertEqual(actual, [ [ "v2", "v1->v2" ], [ "v3", "v1->v3" ] ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v2', 'outbound') SORT n.vertex._key RETURN [ n.vertex._key, n.edge.what ]", true); + assertEqual(actual, [ [ "v3", "v2->v3" ] ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v3', 'outbound') SORT n.vertex._key RETURN n.vertex._key", true); + assertEqual(actual, [ "v4", "v6", "v7" ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v8', 'outbound') SORT n.vertex._key RETURN n.vertex._key", true); + assertEqual(actual, [ ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/v5', 'outbound') SORT n.vertex._key RETURN n.vertex._key", true); + assertEqual(actual, [ ]); + + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'UnitTestsAhuacatlVertex/thefox', 'outbound') SORT n.vertex._key RETURN n.vertex._key", true); + assertEqual(actual, [ ]); + + try { + actual = getQueryResults("FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, 'thefox/thefox', 'outbound') SORT n.vertex._key RETURN n.vertex._key", true); + } + catch (err) { + assertEqual(errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.code, err.errorNum); + } } };