diff --git a/arangod/Ahuacatl/ahuacatl-functions.c b/arangod/Ahuacatl/ahuacatl-functions.c index c2e18c5d4b..dc1f1409c6 100644 --- a/arangod/Ahuacatl/ahuacatl-functions.c +++ b/arangod/Ahuacatl/ahuacatl-functions.c @@ -706,7 +706,9 @@ TRI_associative_pointer_t* TRI_CreateFunctionsAql (void) { REGISTER_FUNCTION("GRAPH_PATHS", "GENERAL_GRAPH_PATHS", false, false, "s|s,b,n,n", &OptimisePaths); REGISTER_FUNCTION("SHORTEST_PATH", "GRAPH_SHORTEST_PATH", false, false, "h,h,s,s,s|a", NULL); REGISTER_FUNCTION("TRAVERSAL", "GRAPH_TRAVERSAL", false, false, "h,h,s,s|a", NULL); + REGISTER_FUNCTION("GRAPH_TRAVERSAL", "GENERAL_GRAPH_TRAVERSAL", false, false, "s,s,s|a", NULL); REGISTER_FUNCTION("TRAVERSAL_TREE", "GRAPH_TRAVERSAL_TREE", false, false, "h,h,s,s,s|a", NULL); + REGISTER_FUNCTION("GRAPH_TRAVERSAL_TREE", "GENERAL_GRAPH_TRAVERSAL_TREE", false, false, "s,s,s,s|a", NULL); REGISTER_FUNCTION("EDGES", "GRAPH_EDGES", false, false, "h,s,s|l", NULL); REGISTER_FUNCTION("GRAPH_EDGES", "GENERAL_GRAPH_EDGES", false, false, "s,s,s|lza,ls", NULL); REGISTER_FUNCTION("NEIGHBORS", "GRAPH_NEIGHBORS", false, false, "h,h,s,s|l", NULL); diff --git a/js/server/modules/org/arangodb/ahuacatl.js b/js/server/modules/org/arangodb/ahuacatl.js index a03d13f32b..869bfb06e7 100644 --- a/js/server/modules/org/arangodb/ahuacatl.js +++ b/js/server/modules/org/arangodb/ahuacatl.js @@ -4329,39 +4329,36 @@ function TRAVERSAL_CHECK_EXAMPLES_TYPEWEIGHTS (examples, func) { }); } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tranform key to id +//////////////////////////////////////////////////////////////////////////////// + +function TO_ID (vertex, collection) { + "use strict"; + + if (vertex === 'object' && vertex.hasOwnProperty('_id')) { + return vertex._id; + } + + if (vertex.indexOf('/') === -1 && collection) { + return collection + '/' + vertex; + } + return vertex; +} + + //////////////////////////////////////////////////////////////////////////////// /// @brief traverse a graph //////////////////////////////////////////////////////////////////////////////// function TRAVERSAL_FUNC (func, - vertexCollection, - edgeCollection, + datasource, startVertex, endVertex, direction, params) { "use strict"; - - if (startVertex === 'object' && startVertex.hasOwnProperty('_id')) { - startVertex = startVertex._id; - } - - if (startVertex.indexOf('/') === -1) { - startVertex = vertexCollection + '/' + startVertex; - } - - if (endVertex !== undefined) { - if (endVertex === 'object' && endVertex.hasOwnProperty('_id')) { - endVertex = endVertex._id; - } - - if (endVertex.indexOf('/') === -1) { - endVertex = vertexCollection + '/' + endVertex; - } - } - - vertexCollection = COLLECTION(vertexCollection); - edgeCollection = COLLECTION(edgeCollection); if (params === undefined) { params = { }; @@ -4388,7 +4385,7 @@ function TRAVERSAL_FUNC (func, var config = { distance: params.distance, connect: params.connect, - datasource: TRAVERSAL.collectionDatasourceFactory(edgeCollection), + datasource: datasource, trackPaths: params.paths || false, visitor: params.visitor, maxDepth: params.maxDepth, @@ -4491,11 +4488,10 @@ function GRAPH_SHORTEST_PATH (vertexCollection, params.distance = undefined; } - return TRAVERSAL_FUNC("SHORTEST_PATH", - vertexCollection, - edgeCollection, - startVertex, - endVertex, + return TRAVERSAL_FUNC("SHORTEST_PATH", + TRAVERSAL.collectionDatasourceFactory(COLLECTION(edgeCollection)), + TO_ID(startVertex, vertexCollection), + TO_ID(endVertex, vertexCollection), direction, params); } @@ -4517,15 +4513,40 @@ function GRAPH_TRAVERSAL (vertexCollection, params.visitor = TRAVERSAL_VISITOR; - return TRAVERSAL_FUNC("TRAVERSAL", - vertexCollection, - edgeCollection, - startVertex, - undefined, + return TRAVERSAL_FUNC("TRAVERSAL", + TRAVERSAL.collectionDatasourceFactory(COLLECTION(edgeCollection)), + TO_ID(startVertex, vertexCollection), + undefined, direction, params); } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief traverse a graph +//////////////////////////////////////////////////////////////////////////////// + +function GENERAL_GRAPH_TRAVERSAL (graphName, + startVertex, + direction, + params) { + "use strict"; + + if (params === undefined) { + params = { }; + } + + params.visitor = TRAVERSAL_VISITOR; + + return TRAVERSAL_FUNC("TRAVERSAL", + TRAVERSAL.generalGraphDatasourceFactory(graphName), + TO_ID(startVertex), + undefined, + direction, + params); +} + + //////////////////////////////////////////////////////////////////////////////// /// @brief traverse a graph and create a hierarchical result /// this function uses the same setup as the TRAVERSE() function but will use @@ -4551,10 +4572,9 @@ function GRAPH_TRAVERSAL_TREE (vertexCollection, params.visitor = TRAVERSAL_TREE_VISITOR; params.connect = connectName; - var result = TRAVERSAL_FUNC("TRAVERSAL_TREE", - vertexCollection, - edgeCollection, - startVertex, + var result = TRAVERSAL_FUNC("TRAVERSAL_TREE", + TRAVERSAL.collectionDatasourceFactory(COLLECTION(edgeCollection)), + TO_ID(startVertex, vertexCollection), undefined, direction, params); @@ -4565,6 +4585,44 @@ function GRAPH_TRAVERSAL_TREE (vertexCollection, return [ result[0][params.connect] ]; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief traverse a graph and create a hierarchical result +/// this function uses the same setup as the TRAVERSE() function but will use +/// a different visitor to create the result +//////////////////////////////////////////////////////////////////////////////// + +function GENERAL_GRAPH_TRAVERSAL_TREE (graphName, + startVertex, + direction, + connectName, + params) { + "use strict"; + + if (connectName === "") { + THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "TRAVERSAL_TREE"); + } + + if (params === undefined) { + params = { }; + } + + params.visitor = TRAVERSAL_TREE_VISITOR; + params.connect = connectName; + + var result = TRAVERSAL_FUNC("TRAVERSAL_TREE", + TRAVERSAL.generalGraphDatasourceFactory(graphName), + TO_ID(startVertex), + undefined, + direction, + params); + + if (result.length === 0) { + return [ ]; + } + return [ result[0][params.connect] ]; +} + + //////////////////////////////////////////////////////////////////////////////// /// @brief return connected edges //////////////////////////////////////////////////////////////////////////////// @@ -4836,6 +4894,8 @@ exports.GRAPH_PATHS = GRAPH_PATHS; exports.GRAPH_SHORTEST_PATH = GRAPH_SHORTEST_PATH; exports.GRAPH_TRAVERSAL = GRAPH_TRAVERSAL; exports.GRAPH_TRAVERSAL_TREE = GRAPH_TRAVERSAL_TREE; +exports.GENERAL_GRAPH_TRAVERSAL = GENERAL_GRAPH_TRAVERSAL; +exports.GENERAL_GRAPH_TRAVERSAL_TREE = GENERAL_GRAPH_TRAVERSAL_TREE; exports.GRAPH_EDGES = GRAPH_EDGES; exports.GENERAL_GRAPH_EDGES = GENERAL_GRAPH_EDGES; exports.GENERAL_GRAPH_PATHS = GENERAL_GRAPH_PATHS; diff --git a/js/server/tests/ahuacatl-general-graph.js b/js/server/tests/ahuacatl-general-graph.js index d38f48430b..afcca8d36d 100644 --- a/js/server/tests/ahuacatl-general-graph.js +++ b/js/server/tests/ahuacatl-general-graph.js @@ -262,7 +262,7 @@ function ahuacatlQueryGeneralPathsTestSuite() { }, //////////////////////////////////////////////////////////////////////////////// -/// @brief checks EDGES() +/// @brief checks GRAPH_PATHS() //////////////////////////////////////////////////////////////////////////////// testPaths: function () { @@ -352,11 +352,161 @@ function ahuacatlQueryGeneralPathsTestSuite() { } +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite for GRAPH_TRAVERSAL() function +//////////////////////////////////////////////////////////////////////////////// + +function ahuacatlQueryGeneralTraversalTestSuite() { + var vertex = null; + var edge = null; + + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set up +//////////////////////////////////////////////////////////////////////////////// + + setUp: function () { + db._drop("UnitTests_Berliner"); + db._drop("UnitTests_Hamburger"); + db._drop("UnitTests_Frankfurter"); + db._drop("UnitTests_Leipziger"); + db._drop("UnitTests_KenntAnderenBerliner"); + db._drop("UnitTests_KenntAnderen"); + + KenntAnderenBerliner = "UnitTests_KenntAnderenBerliner"; + KenntAnderen = "UnitTests_KenntAnderen"; + + Berlin = db._create("UnitTests_Berliner"); + Hamburg = db._create("UnitTests_Hamburger"); + Frankfurt = db._create("UnitTests_Frankfurter"); + Leipzig = db._create("UnitTests_Leipziger"); + db._createEdgeCollection(KenntAnderenBerliner); + db._createEdgeCollection(KenntAnderen); + + var Anton = Berlin.save({ _key: "Anton" , gender : "male"}); + var Berta = Berlin.save({ _key: "Berta" , gender : "female"}); + var Caesar = Hamburg.save({ _key: "Caesar" , gender : "male"}); + var Dieter = Hamburg.save({ _key: "Dieter" , gender : "male"}); + var Emil = Frankfurt.save({ _key: "Emil" , gender : "male"}); + var Fritz = Frankfurt.save({ _key: "Fritz" , gender : "male"}); + var Gerda = Leipzig.save({ _key: "Gerda" , gender : "female"}); + + try { + db._collection("_graphs").remove("_graphs/werKenntWen") + } catch (err) { + } + var g = graph._create( + "werKenntWen", + graph.edgeDefinitions( + graph._undirectedRelationDefinition(KenntAnderenBerliner, "UnitTests_Berliner"), + graph._directedRelationDefinition(KenntAnderen, + ["UnitTests_Hamburger", "UnitTests_Frankfurter", "UnitTests_Berliner", "UnitTests_Leipziger"], + ["UnitTests_Hamburger", "UnitTests_Frankfurter", "UnitTests_Berliner", "UnitTests_Leipziger"] + ) + ) + ); + function makeEdge(from, to, collection) { + collection.save(from, to, { what: from.split("/")[1] + "->" + to.split("/")[1] }); + } + makeEdge(Berta._id, Anton._id, g[KenntAnderenBerliner]); + makeEdge(Caesar._id, Anton._id, g[KenntAnderen]); + makeEdge(Caesar._id, Berta._id, g[KenntAnderen]); + makeEdge(Berta._id, Gerda._id, g[KenntAnderen]); + makeEdge(Gerda._id, Dieter._id, g[KenntAnderen]); + makeEdge(Dieter._id, Emil._id, g[KenntAnderen]); + makeEdge(Emil._id, Fritz._id, g[KenntAnderen]); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tear down +//////////////////////////////////////////////////////////////////////////////// + + tearDown: function () { + db._drop("UnitTests_Berliner"); + db._drop("UnitTests_Hamburger"); + db._drop("UnitTests_Frankfurter"); + db._drop("UnitTests_Leipziger"); + db._drop("UnitTests_KenntAnderenBerliner"); + db._drop("UnitTests_KenntAnderen"); + db._collection("_graphs").remove("_graphs/werKenntWen"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief checks GRAPH_TRAVERSAL() +//////////////////////////////////////////////////////////////////////////////// + + testGRAPH_TRAVERSALs: function () { + var actual, result= []; + + actual = getQueryResults("FOR e IN GRAPH_TRAVERSAL('werKenntWen', 'UnitTests_Hamburger/Caesar', 'outbound') RETURN e"); + //require("internal").print(actual); + actual.forEach(function (s) { + result.push(s.vertex._key); + }); + //require("internal").print(result) + assertEqual(result, [ + "Caesar", + "Anton", + "Berta", + "Anton", + "Gerda", + "Dieter", + "Emil", + "Fritz" + ]); + }, + + testGENERAL_GRAPH_TRAVERSAL_TREE: function () { + var actual, start, middle; + + actual = getQueryResults("FOR e IN GRAPH_TRAVERSAL_TREE('werKenntWen', 'UnitTests_Hamburger/Caesar', 'outbound', 'connected') RETURN e"); + start = actual[0][0]; + + assertEqual(start._key, "Caesar"); + assertTrue(start.hasOwnProperty("connected")); + assertTrue(start.connected.length === 2); + assertEqual(start.connected[0]._key, "Anton"); + assertEqual(start.connected[1]._key, "Berta"); + + assertTrue(!start.connected[0].hasOwnProperty("connected")); + assertTrue(start.connected[1].hasOwnProperty("connected")); + + middle = start.connected[1]; + + assertTrue(middle.connected.length === 2); + assertEqual(middle.connected[0]._key, "Anton"); + assertEqual(middle.connected[1]._key, "Gerda"); + + assertTrue(!middle.connected[0].hasOwnProperty("connected")); + assertTrue(middle.connected[1].hasOwnProperty("connected")); + + middle = middle.connected[1]; + assertTrue(middle.connected.length === 1); + assertEqual(middle.connected[0]._key, "Dieter"); + + middle = middle.connected[0]; + + assertTrue(middle.connected.length === 1); + assertEqual(middle.connected[0]._key, "Emil"); + + middle = middle.connected[0]; + assertTrue(middle.connected.length === 1); + assertEqual(middle.connected[0]._key, "Fritz"); + + } + } +} + + + + //////////////////////////////////////////////////////////////////////////////// /// @brief executes the test suite //////////////////////////////////////////////////////////////////////////////// +jsunity.run(ahuacatlQueryGeneralTraversalTestSuite); jsunity.run(ahuacatlQueryGeneralEdgesTestSuite); jsunity.run(ahuacatlQueryGeneralPathsTestSuite);