I also needed to know the answer to this question, so here is a solution that works.
I am sure that the code should be customized for you and can make some improvements, please comment accordingly if this is suitable to answer this sample.
The solution is to use Foxx Microservice, which supports recursion and creates a tree. The problem I have is related to cycle loops, but I have fulfilled the maximum depth limit that stops this, hardcoded to 10 in the example below.
To create a Microservice Foxx:
- Create a new folder (e.g. a recursive tree)
- Creating Directory Scripts
- Place the
manifest.json and index.js in the root directory - Place the
setup.js file in the script directory - Then create a new zip file with these three files in it (e.g.
Foxx.zip ) - Go to the ArangoDB admin console
- Click "Service" | Add service
- Enter the appropriate mount point, e.g. / My / tree
- Click the Zip tab
- Drag and drop the
Foxx.zip file that you created, it should create without problems. - If you receive an error message, make sure that the
myItems and myConnections collections myItems not exist, and the myGraph graph myGraph not exist, since it will try to create them with sample data. - Then go to the ArangoDB admin console, Services | / My / tree
- Click API
- Expand / tree / {rootId}
- Specify the ItemI rootId parameter and click "Try"
- You should see the result from the root id provided.
If the rootId does not exist, it returns nothing. If the rootId has no children, it returns an empty array for 'contains'. If the rootId is โcontainsโ, it returns nesting to the limit of depth, I want a cleaner way to stop this.
Here are three files: setup.js (located in the script folder):
'use strict'; const db = require('@arangodb').db; const graph_module = require("org/arangodb/general-graph"); const itemCollectionName = 'myItems'; const edgeCollectionName = 'myConnections'; const graphName = 'myGraph'; if (!db._collection(itemCollectionName)) { const itemCollection = db._createDocumentCollection(itemCollectionName); itemCollection.save({_key: "ItemA" }); itemCollection.save({_key: "ItemB" }); itemCollection.save({_key: "ItemC" }); itemCollection.save({_key: "ItemD" }); itemCollection.save({_key: "ItemE" }); if (!db._collection(edgeCollectionName)) { const edgeCollection = db._createEdgeCollection(edgeCollectionName); edgeCollection.save({_from: itemCollectionName + '/ItemA', _to: itemCollectionName + '/ItemB'}); edgeCollection.save({_from: itemCollectionName + '/ItemB', _to: itemCollectionName + '/ItemC'}); edgeCollection.save({_from: itemCollectionName + '/ItemB', _to: itemCollectionName + '/ItemD'}); edgeCollection.save({_from: itemCollectionName + '/ItemD', _to: itemCollectionName + '/ItemE'}); } const graphDefinition = [ { "collection": edgeCollectionName, "from":[itemCollectionName], "to":[itemCollectionName] } ]; const graph = graph_module._create(graphName, graphDefinition); }
mainfest.json (located in the root folder):
{ "engines": { "arangodb": "^3.0.0" }, "main": "index.js", "scripts": { "setup": "scripts/setup.js" } }
index.js (located in the root folder):
'use strict'; const createRouter = require('@arangodb/foxx/router'); const router = createRouter(); const joi = require('joi'); const db = require('@arangodb').db; const aql = require('@arangodb').aql; const recursionQuery = function(itemId, tree, depth) { const result = db._query(aql` FOR d IN myItems FILTER d._id == ${itemId} LET contains = ( FOR c IN 1..1 OUTBOUND ${itemId} GRAPH 'myGraph' RETURN { "_id": c._id } ) RETURN MERGE({"_id": d._id}, {"contains": contains}) `); tree = result._documents[0]; if (depth < 10) { if ((result._documents[0]) && (result._documents[0].contains) && (result._documents[0].contains.length > 0)) { for (var i = 0; i < result._documents[0].contains.length; i++) { tree.contains[i] = recursionQuery(result._documents[0].contains[i]._id, tree.contains[i], depth + 1); } } } return tree; } router.get('/tree/:rootId', function(req, res) { let myResult = recursionQuery('myItems/' + req.pathParams.rootId, {}, 0); res.send(myResult); }) .response(joi.object().required(), 'Tree of child nodes.') .summary('Tree of child nodes') .description('Tree of child nodes underneath the provided node.'); module.context.use(router);
Now you can call the Foxx Microservice API endpoint, indicating that rootId will return the full tree. It's very fast.
An example of the output of this parameter for ItemA:
{ "_id": "myItems/ItemA", "contains": [ { "_id": "myItems/ItemB", "contains": [ { "_id": "myItems/ItemC", "contains": [] }, { "_id": "myItems/ItemD", "contains": [ { "_id": "myItems/ItemE", "contains": [] } ] } ] } ] }
You can see that item B contains two child items ItemC and ItemD, and then ItemD also contains ItemE.
I canโt wait for ArangoDB AQL to improve the processing of variable depth paths in FOR v, e, p IN 1..100 OUTBOUND 'abc/def' GRAPH 'someGraph' style queries FOR v, e, p IN 1..100 OUTBOUND 'abc/def' GRAPH 'someGraph' . User visitors were not recommended for use in 3.x, but in fact they were not replaced by something powerful for processing wild card requests at the top of the path or processing prune or exclude style commands on the VTP path.
I would like to have comments / feedback if this can be simplified.