/* jshint strict: false */ /* global ArangoClusterComm */ // ////////////////////////////////////////////////////////////////////////////// // / @brief Graph functionality // / // / @file // / // / DISCLAIMER // / // / Copyright 2010-2014 triagens 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 triAGENS GmbH, Cologne, Germany // / // / @author Florian Bartels, Michael Hackstein, Guido Schwab // / @author Copyright 2011-2014, triAGENS GmbH, Cologne, Germany // ////////////////////////////////////////////////////////////////////////////// var arangodb = require('@arangodb'), internal = require('internal'), ArangoCollection = arangodb.ArangoCollection, ArangoError = arangodb.ArangoError, db = arangodb.db, errors = arangodb.errors, _ = require('lodash'); // ////////////////////////////////////////////////////////////////////////////// // / @brief Compatibility functions for 2.8 // / This function registeres user-defined functions that follow the // / same API as the former GRAPH_* functions did. // / Most of these AQL functions can be simply replaced by calls to these. // ////////////////////////////////////////////////////////////////////////////// var registerCompatibilityFunctions = function () { var aqlfunctions = require('@arangodb/aql/functions'); aqlfunctions.register('arangodb::GRAPH_EDGES', function (graphName, vertexExample, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._edges(vertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_VERTICES', function (graphName, vertexExample, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._vertices(vertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_NEIGHBORS', function (graphName, vertexExample, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._neighbors(vertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_COMMON_NEIGHBORS', function (graphName, vertex1Example, vertex2Example, options1, options2) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._commonNeighbors(vertex1Example, vertex2Example, options1, options2); }, false); aqlfunctions.register('arangodb::GRAPH_COMMON_PROPERTIES', function (graphName, vertex1Example, vertex2Example, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._commonProperties(vertex1Example, vertex2Example, options); }, false); aqlfunctions.register('arangodb::GRAPH_PATHS', function (graphName, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._paths(options); }, false); aqlfunctions.register('arangodb::GRAPH_SHORTEST_PATH', function (graphName, startVertexExample, edgeVertexExample, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._shortestPath(startVertexExample, edgeVertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_DISTANCE_TO', function (graphName, startVertexExample, edgeVertexExample, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._distanceTo(startVertexExample, edgeVertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_ABSOLUTE_ECCENTRICITY', function (graphName, vertexExample, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._absoluteEccentricity(vertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_ECCENTRICITY', function (graphName, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._eccentricity(options); }, false); aqlfunctions.register('arangodb::GRAPH_ABSOLUTE_CLOSENESS', function (graphName, vertexExample, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._farness(vertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_CLOSENESS', function (graphName, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._closeness(options); }, false); aqlfunctions.register('arangodb::GRAPH_ABSOLUTE_BETWEENNESS', function (graphName, vertexExample, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._absoluteBetweenness(vertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_BETWEENNESS', function (graphName, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._betweenness(options); }, false); aqlfunctions.register('arangodb::GRAPH_RADIUS', function (graphName, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._radius(options); }, false); aqlfunctions.register('arangodb::GRAPH_DIAMETER', function (graphName, options) { var gm = require('@arangodb/general-graph'); var g = gm._graph(graphName); return g._diameter(options); }, false); }; var fixWeight = function (options) { if (!options.hasOwnProperty('weightAttribute') && options.hasOwnProperty('weight')) { options.weightAttribute = options.weight; } }; // ////////////////////////////////////////////////////////////////////////////// // / @brief transform a string into an array. // ////////////////////////////////////////////////////////////////////////////// var stringToArray = function (x) { if (typeof x === 'string') { return [x]; } return x.slice(); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief checks if a parameter is not defined, an empty string or an empty // array // ////////////////////////////////////////////////////////////////////////////// var isValidCollectionsParameter = function (x) { if (!x) { return false; } if (Array.isArray(x) && x.length === 0) { return false; } if (typeof x !== 'string' && !Array.isArray(x)) { return false; } return true; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief find or create a collection by name // ////////////////////////////////////////////////////////////////////////////// var findOrCreateCollectionByName = function (name, type, noCreate) { var col = db._collection(name), res = false; if (col === null && !noCreate) { if (type === ArangoCollection.TYPE_DOCUMENT) { col = db._create(name); } else { col = db._createEdgeCollection(name); } res = true; } else if (!(col instanceof ArangoCollection)) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_NOT_AN_ARANGO_COLLECTION.code; err.errorMessage = name + arangodb.errors.ERROR_GRAPH_NOT_AN_ARANGO_COLLECTION.message; throw err; } else if (type === ArangoCollection.TYPE_EDGE && col.type() !== type) { var err2 = new ArangoError(); err2.errorNum = arangodb.errors.ERROR_ARANGO_COLLECTION_TYPE_INVALID.code; err2.errorMessage = name + ' cannot be used as relation. It is not an edge collection'; throw err2; } return res; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief find or create a collection by name // ////////////////////////////////////////////////////////////////////////////// var findOrCreateCollectionsByEdgeDefinitions = function (edgeDefinitions, noCreate) { var vertexCollections = {}, edgeCollections = {}; edgeDefinitions.forEach(function (e) { if (!e.hasOwnProperty('collection') || !e.hasOwnProperty('from') || !e.hasOwnProperty('to') || !Array.isArray(e.from) || !Array.isArray(e.to)) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_CREATE_MALFORMED_EDGE_DEFINITION.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_CREATE_MALFORMED_EDGE_DEFINITION.message; throw err; } e.from.concat(e.to).forEach(function (v) { findOrCreateCollectionByName(v, ArangoCollection.TYPE_DOCUMENT, noCreate); vertexCollections[v] = db[v]; }); findOrCreateCollectionByName(e.collection, ArangoCollection.TYPE_EDGE, noCreate); edgeCollections[e.collection] = db[e.collection]; }); return [ vertexCollections, edgeCollections ]; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief internal function to get graphs collection // ////////////////////////////////////////////////////////////////////////////// var getGraphCollection = function () { var gCol = db._graphs; if (gCol === null || gCol === undefined) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_NO_GRAPH_COLLECTION.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_NO_GRAPH_COLLECTION.message; throw err; } return gCol; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief internal function to print edge definitions in _PRINT // ////////////////////////////////////////////////////////////////////////////// var printEdgeDefinitions = function (defs) { return _.map(defs, function (d) { var out = d.collection; out += ': ['; out += d.from.join(', '); out += '] -> ['; out += d.to.join(', '); out += ']'; return out; }); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief internal function to wrap arango collections for overwrite // ////////////////////////////////////////////////////////////////////////////// var wrapCollection = function (col) { var wrapper = {}; _.each(_.functionsIn(col), function (func) { wrapper[func] = function () { return col[func].apply(col, arguments); }; }); return wrapper; }; // Returns either `collection` or UNION(`all collections`) // Does not contain filters or iterator variable // Can be used as for x IN ${startInAllCollections()} return x var startInAllCollections = function (collections) { if (collections.length === 1) { return `${collections[0]}`; } return `UNION(${collections.map(c => `(FOR x IN ${c} RETURN x)`).join(", ")})`; }; var buildEdgeCollectionRestriction = function (collections) { if (!Array.isArray(collections)) { collections = [ collections ]; } return collections.map(collection => '`' + collection + '`').join(','); }; var buildVertexCollectionRestriction = function (collections, varname) { if (!Array.isArray(collections)) { collections = [ collections ]; } var filter = `FILTER ( ${collections.map(e => { return `IS_SAME_COLLECTION(${JSON.stringify(e)},${varname})`; }).join(" OR ")} )`; return `${filter}`; }; var buildFilter = function (examples, bindVars, varname) { var varcount = 0; var foundAllMatch = false; if (!Array.isArray(examples)) { examples = [ examples ]; } var filter = `FILTER ( ${examples.map(e => { if (typeof e === "object") { var keys = Object.keys(e); if (keys.length === 0) { foundAllMatch = true; return ""; } return keys.map(key => { bindVars[varname + "ExVar" + varcount] = key; bindVars[varname + "ExVal" + varcount] = e[key]; return `${varname}[@${varname}ExVar${varcount}] == @${varname}ExVal${varcount++}`; }).join(" AND "); } else if(typeof e === "string") { bindVars[varname + "ExVar" + varcount] = e; return `${varname}._id == @${varname}ExVar${varcount++}`; } else { foundAllMatch = true; return ""; } }).join(") OR (")} )`; if (foundAllMatch) { for (var i = 0; i < varcount; ++i) { delete bindVars[varname + 'ExVar' + varcount]; delete bindVars[varname + 'ExVal' + varcount]; } return ``; } return `${filter}`; }; // Returns FOR IN (...) // So start contains every object in the graph // matching the example(s) var transformExampleToAQL = function (examples, collections, bindVars, varname) { var varcount = 0; var foundAllMatch = false; if (!Array.isArray(examples)) { examples = [ examples ]; } var filter = `FILTER ( ${examples.map(e => { if (typeof e === "object") { var keys = Object.keys(e); if (keys.length === 0) { foundAllMatch = true; return ""; } return keys.map(key => { bindVars[varname + "ExVar" + varcount] = key; bindVars[varname + "ExVal" + varcount] = e[key]; return `${varname}[@${varname}ExVar${varcount}] == @${varname}ExVal${varcount++}`; }).join(" AND "); } else { bindVars[varname + "ExVar" + varcount] = e; return `${varname}._id == @${varname}ExVar${varcount++}`; } }).join(") OR (")} )`; if (foundAllMatch) { for (var i = 0; i < varcount; ++i) { delete bindVars[varname + 'ExVar' + varcount]; delete bindVars[varname + 'ExVal' + varcount]; } return `FOR ${varname} IN ${startInAllCollections(collections)} `; } var query = `FOR ${varname} IN `; if (collections.length === 1) { query += `${collections[0]} ${filter}`; } else { query += `UNION (${collections.map(c => `(FOR ${varname} IN ${c} ${filter} RETURN ${varname})`).join(", ")}) `; } return query; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_relation // ////////////////////////////////////////////////////////////////////////////// var _relation = function ( relationName, fromVertexCollections, toVertexCollections) { var err; if (arguments.length < 3) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_INVALID_NUMBER_OF_ARGUMENTS.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_INVALID_NUMBER_OF_ARGUMENTS.message + '3'; throw err; } if (typeof relationName !== 'string' || relationName === '') { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_INVALID_PARAMETER.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_INVALID_PARAMETER.message + ' arg1 must be non empty string'; throw err; } if (!isValidCollectionsParameter(fromVertexCollections)) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_INVALID_PARAMETER.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_INVALID_PARAMETER.message + ' arg2 must be non empty string or array'; throw err; } if (!isValidCollectionsParameter(toVertexCollections)) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_INVALID_PARAMETER.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_INVALID_PARAMETER.message + ' arg3 must be non empty string or array'; throw err; } return { collection: relationName, from: stringToArray(fromVertexCollections), to: stringToArray(toVertexCollections) }; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_list // ////////////////////////////////////////////////////////////////////////////// var _list = function () { var gdb = getGraphCollection(); return _.map(gdb.toArray(), '_key'); }; var _listObjects = function () { return getGraphCollection().toArray(); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_edge_definitions // ////////////////////////////////////////////////////////////////////////////// var _edgeDefinitions = function () { var res = [], args = arguments; Object.keys(args).forEach(function (x) { res.push(args[x]); }); return res; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_extend_edge_definitions // ////////////////////////////////////////////////////////////////////////////// var _extendEdgeDefinitions = function (edgeDefinition) { var args = arguments, i = 0; Object.keys(args).forEach( function (x) { i++; if (i === 1) { return; } edgeDefinition.push(args[x]); } ); }; // ////////////////////////////////////////////////////////////////////////////// // / internal helper to sort a graph's edge definitions // ////////////////////////////////////////////////////////////////////////////// var sortEdgeDefinition = function (edgeDefinition) { edgeDefinition.from = edgeDefinition.from.sort(); edgeDefinition.to = edgeDefinition.to.sort(); return edgeDefinition; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief create a new graph // ////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_how_to_create // ////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_create // ////////////////////////////////////////////////////////////////////////////// var _create = function (graphName, edgeDefinitions, orphanCollections, options) { if (!Array.isArray(orphanCollections)) { orphanCollections = []; } var gdb = getGraphCollection(), err, graphAlreadyExists = true, collections, result; if (!graphName) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_CREATE_MISSING_NAME.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_CREATE_MISSING_NAME.message; throw err; } edgeDefinitions = edgeDefinitions || []; if (!Array.isArray(edgeDefinitions)) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_CREATE_MALFORMED_EDGE_DEFINITION.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_CREATE_MALFORMED_EDGE_DEFINITION.message; throw err; } // check, if a collection is already used in a different edgeDefinition var tmpCollections = []; var tmpEdgeDefinitions = {}; edgeDefinitions.forEach( function (edgeDefinition) { var col = edgeDefinition.collection; if (tmpCollections.indexOf(col) !== -1) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_COLLECTION_MULTI_USE.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_COLLECTION_MULTI_USE.message; throw err; } tmpCollections.push(col); tmpEdgeDefinitions[col] = edgeDefinition; } ); gdb.toArray().forEach( function (singleGraph) { var sGEDs = singleGraph.edgeDefinitions; sGEDs.forEach( function (sGED) { var col = sGED.collection; if (tmpCollections.indexOf(col) !== -1) { if (JSON.stringify(sGED) !== JSON.stringify(tmpEdgeDefinitions[col])) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS.code; err.errorMessage = col + ' ' + arangodb.errors.ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS.message; throw err; } } } ); } ); try { gdb.document(graphName); } catch (e) { if (e.errorNum !== errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { throw e; } graphAlreadyExists = false; } if (graphAlreadyExists) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_DUPLICATE.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_DUPLICATE.message; throw err; } collections = findOrCreateCollectionsByEdgeDefinitions(edgeDefinitions, false); orphanCollections.forEach( function (oC) { findOrCreateCollectionByName(oC, ArangoCollection.TYPE_DOCUMENT); } ); edgeDefinitions.forEach( function (eD, index) { var tmp = sortEdgeDefinition(eD); edgeDefinitions[index] = tmp; } ); orphanCollections = orphanCollections.sort(); var data = gdb.save({ 'orphanCollections': orphanCollections, 'edgeDefinitions': edgeDefinitions, '_key': graphName }, options); result = new Graph(graphName, edgeDefinitions, collections[0], collections[1], orphanCollections, data._rev , data._id); return result; }; var createHiddenProperty = function (obj, name, value) { Object.defineProperty(obj, name, { enumerable: false, writable: true }); obj[name] = value; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief helper for updating binded collections // ////////////////////////////////////////////////////////////////////////////// var removeEdge = function (graphs, edgeCollection, edgeId, self) { self.__idsToRemove[edgeId] = 1; graphs.forEach( function (graph) { var edgeDefinitions = graph.edgeDefinitions; if (graph.edgeDefinitions) { edgeDefinitions.forEach( function (edgeDefinition) { var from = edgeDefinition.from; var to = edgeDefinition.to; var collection = edgeDefinition.collection; // if collection of edge to be deleted is in from or to if (from.indexOf(edgeCollection) !== -1 || to.indexOf(edgeCollection) !== -1) { // search all edges of the graph var edges = db._collection(collection).edges(edgeId); edges.forEach(function (edge) { // if from is if (!self.__idsToRemove.hasOwnProperty(edge._id)) { self.__collectionsToLock[collection] = 1; removeEdge(graphs, collection, edge._id, self); } }); } } ); } } ); }; var bindEdgeCollections = function (self, edgeCollections) { _.each(edgeCollections, function (key) { var obj = db._collection(key); var wrap = wrapCollection(obj); // save var old_save = wrap.insert; wrap.save = wrap.insert = function (from, to, data) { if (typeof from === 'object' && to === undefined) { data = from; from = data._from; to = data._to; } else if (typeof from === 'string' && typeof to === 'string' && typeof data === 'object') { data._from = from; data._to = to; } if (typeof from !== 'string' || from.indexOf('/') === -1 || typeof to !== 'string' || to.indexOf('/') === -1) { // invalid from or to value var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_ARANGO_DOCUMENT_HANDLE_BAD.code; err.errorMessage = arangodb.errors.ERROR_ARANGO_DOCUMENT_HANDLE_BAD.message; throw err; } // check, if edge is allowed self.__edgeDefinitions.forEach( function (edgeDefinition) { if (edgeDefinition.collection === key) { var fromCollection = from.split('/')[0]; var toCollection = to.split('/')[0]; if (!_.includes(edgeDefinition.from, fromCollection) || !_.includes(edgeDefinition.to, toCollection)) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_INVALID_EDGE.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_INVALID_EDGE.message + ' between ' + from + ' and ' + to + '.'; throw err; } } } ); return old_save(data); }; // remove wrap.remove = function (edgeId, options) { // if _key make _id (only on 1st call) if (edgeId.indexOf('/') === -1) { edgeId = key + '/' + edgeId; } var graphs = getGraphCollection().toArray(); var edgeCollection = edgeId.split('/')[0]; self.__collectionsToLock[edgeCollection] = 1; removeEdge(graphs, edgeCollection, edgeId, self); try { db._executeTransaction({ collections: { write: Object.keys(self.__collectionsToLock) }, embed: true, action: function (params) { var db = require('internal').db; params.ids.forEach( function (edgeId) { if (params.options) { db._remove(edgeId, params.options); } else { db._remove(edgeId); } } ); }, params: { ids: Object.keys(self.__idsToRemove), options: options } }); } catch (e) { self.__idsToRemove = {}; self.__collectionsToLock = {}; throw e; } self.__idsToRemove = {}; self.__collectionsToLock = {}; return true; }; self[key] = wrap; }); }; var bindVertexCollections = function (self, vertexCollections) { _.each(vertexCollections, function (key) { var obj = db._collection(key); var wrap = wrapCollection(obj); wrap.remove = function (vertexId, options) { // delete all edges using the vertex in all graphs var graphs = getGraphCollection().toArray(); var vertexCollectionName = key; if (vertexId.indexOf('/') === -1) { vertexId = key + '/' + vertexId; } self.__collectionsToLock[vertexCollectionName] = 1; graphs.forEach( function (graph) { var edgeDefinitions = graph.edgeDefinitions; if (graph.edgeDefinitions) { edgeDefinitions.forEach( function (edgeDefinition) { var from = edgeDefinition.from; var to = edgeDefinition.to; var collection = edgeDefinition.collection; if (from.indexOf(vertexCollectionName) !== -1 || to.indexOf(vertexCollectionName) !== -1 ) { var edges = db._collection(collection).edges(vertexId); if (edges.length > 0) { self.__collectionsToLock[collection] = 1; edges.forEach(function (edge) { removeEdge(graphs, collection, edge._id, self); }); } } } ); } } ); try { db._executeTransaction({ collections: { write: Object.keys(self.__collectionsToLock) }, embed: true, action: function (params) { var db = require('internal').db; params.ids.forEach( function (edgeId) { if (params.options) { db._remove(edgeId, params.options); } else { db._remove(edgeId); } } ); if (params.options) { db._remove(params.vertexId, params.options); } else { db._remove(params.vertexId); } }, params: { ids: Object.keys(self.__idsToRemove), options: options, vertexId: vertexId } }); } catch (e) { self.__idsToRemove = {}; self.__collectionsToLock = {}; throw e; } self.__idsToRemove = {}; self.__collectionsToLock = {}; return true; }; self[key] = wrap; }); }; var updateBindCollections = function (graph) { // remove all binded collections Object.keys(graph).forEach( function (key) { if (key.substring(0, 1) !== '_') { delete graph[key]; } } ); graph.__edgeDefinitions.forEach( function (edgeDef) { bindEdgeCollections(graph, [edgeDef.collection]); bindVertexCollections(graph, edgeDef.from); bindVertexCollections(graph, edgeDef.to); } ); bindVertexCollections(graph, graph.__orphanCollections); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_vertex_collection_save // ////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_vertex_collection_replace // ////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_vertex_collection_update // ////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_vertex_collection_remove // ////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_edge_collection_save // ////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_edge_collection_replace // ////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_edge_collection_update // ////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_edge_collection_remove // ////////////////////////////////////////////////////////////////////////////// var Graph = function (graphName, edgeDefinitions, vertexCollections, edgeCollections, orphanCollections, revision, id) { edgeDefinitions.forEach( function (eD, index) { var tmp = sortEdgeDefinition(eD); edgeDefinitions[index] = tmp; } ); if (!orphanCollections) { orphanCollections = []; } // we can call the "fast" version of some edge functions if we are // running server-side and are not a coordinator var useBuiltIn = (typeof ArangoClusterComm === 'object'); if (useBuiltIn && require('@arangodb/cluster').isCoordinator()) { useBuiltIn = false; } var self = this; // Create Hidden Properties createHiddenProperty(this, '__useBuiltIn', useBuiltIn); createHiddenProperty(this, '__name', graphName); createHiddenProperty(this, '__vertexCollections', vertexCollections); createHiddenProperty(this, '__edgeCollections', edgeCollections); createHiddenProperty(this, '__edgeDefinitions', edgeDefinitions); createHiddenProperty(this, '__idsToRemove', {}); createHiddenProperty(this, '__collectionsToLock', {}); createHiddenProperty(this, '__id', id); createHiddenProperty(this, '__rev', revision); createHiddenProperty(this, '__orphanCollections', orphanCollections); updateBindCollections(self); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_graph // ////////////////////////////////////////////////////////////////////////////// var _graph = function (graphName) { var gdb = getGraphCollection(), g, collections, orphanCollections; try { g = gdb.document(graphName); } catch (e) { if (e.errorNum !== errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { throw e; } var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_NOT_FOUND.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_NOT_FOUND.message; throw err; } collections = findOrCreateCollectionsByEdgeDefinitions(g.edgeDefinitions, true); orphanCollections = g.orphanCollections; if (!orphanCollections) { orphanCollections = []; } return new Graph(graphName, g.edgeDefinitions, collections[0], collections[1], orphanCollections, g._rev, g._id); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief check if a graph exists. // ////////////////////////////////////////////////////////////////////////////// var _exists = function (graphId) { var gCol = getGraphCollection(); return gCol.exists(graphId); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief rename a collection inside the _graphs collections // ////////////////////////////////////////////////////////////////////////////// var _renameCollection = function (oldName, newName) { db._executeTransaction({ collections: { write: '_graphs' }, action: function (params) { var gdb = getGraphCollection(); if (!gdb) { return; } gdb.toArray().forEach(function (doc) { var c = Object.assign({}, doc), i, j, changed = false; if (c.edgeDefinitions) { for (i = 0; i < c.edgeDefinitions.length; ++i) { var def = c.edgeDefinitions[i]; if (def.collection === params.oldName) { c.edgeDefinitions[i].collection = params.newName; changed = true; } for (j = 0; j < def.from.length; ++j) { if (def.from[j] === params.oldName) { c.edgeDefinitions[i].from[j] = params.newName; changed = true; } } for (j = 0; j < def.to.length; ++j) { if (def.to[j] === params.oldName) { c.edgeDefinitions[i].to[j] = params.newName; changed = true; } } } } for (i = 0; i < c.orphanCollections.length; ++i) { if (c.orphanCollections[i] === params.oldName) { c.orphanCollections[i] = params.newName; changed = true; } } if (changed) { gdb.update(doc._key, c); } }); }, params: { oldName: oldName, newName: newName } }); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief Helper for dropping collections of a graph. // ////////////////////////////////////////////////////////////////////////////// var checkIfMayBeDropped = function (colName, graphName, graphs) { var result = true; graphs.forEach( function (graph) { if (graph._key === graphName) { return; } var edgeDefinitions = graph.edgeDefinitions; if (edgeDefinitions) { edgeDefinitions.forEach( function (edgeDefinition) { var from = edgeDefinition.from; var to = edgeDefinition.to; var collection = edgeDefinition.collection; if (collection === colName || from.indexOf(colName) !== -1 || to.indexOf(colName) !== -1 ) { result = false; } } ); } var orphanCollections = graph.orphanCollections; if (orphanCollections) { if (orphanCollections.indexOf(colName) !== -1) { result = false; } } } ); return result; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_drop // ////////////////////////////////////////////////////////////////////////////// var _drop = function (graphId, dropCollections) { var gdb = getGraphCollection(), graphs; if (!gdb.exists(graphId)) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_NOT_FOUND.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_NOT_FOUND.message; throw err; } if (dropCollections === true) { var graph = gdb.document(graphId); var edgeDefinitions = graph.edgeDefinitions; edgeDefinitions.forEach( function (edgeDefinition) { var from = edgeDefinition.from; var to = edgeDefinition.to; var collection = edgeDefinition.collection; graphs = getGraphCollection().toArray(); if (checkIfMayBeDropped(collection, graph._key, graphs)) { db._drop(collection); } from.forEach( function (col) { if (checkIfMayBeDropped(col, graph._key, graphs)) { db._drop(col); } } ); to.forEach( function (col) { if (checkIfMayBeDropped(col, graph._key, graphs)) { db._drop(col); } } ); } ); // drop orphans graphs = getGraphCollection().toArray(); if (!graph.orphanCollections) { graph.orphanCollections = []; } graph.orphanCollections.forEach( function (oC) { if (checkIfMayBeDropped(oC, graph._key, graphs)) { try { db._drop(oC); } catch (ignore) {} } } ); } gdb.remove(graphId); return true; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief return all edge collections of the graph. // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._edgeCollections = function () { return _.values(this.__edgeCollections); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief return all vertex collections of the graph. // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._vertexCollections = function (excludeOrphans) { if (excludeOrphans) { return this.__vertexCollections; } var orphans = []; _.each(this.__orphanCollections, function (o) { orphans.push(db[o]); }); return _.union(_.values(this.__vertexCollections), orphans); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief _EDGES(vertexId). // ////////////////////////////////////////////////////////////////////////////// // might be needed from AQL itself Graph.prototype._EDGES = function (vertexId) { var err; if (vertexId.indexOf('/') === -1) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_NOT_FOUND.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_NOT_FOUND.message + ': ' + vertexId; throw err; } var result = [], c; for (c in this.__edgeCollections) { if (this.__edgeCollections.hasOwnProperty(c)) { if (this.__useBuiltIn) { result = result.concat(this.__edgeCollections[c].EDGES(vertexId)); } else { result = result.concat(this.__edgeCollections[c].edges(vertexId)); } } } return result; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief INEDGES(vertexId). // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._INEDGES = function (vertexId) { var err; if (vertexId.indexOf('/') === -1) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_NOT_FOUND.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_NOT_FOUND.message + ': ' + vertexId; throw err; } var result = [], c; for (c in this.__edgeCollections) { if (this.__edgeCollections.hasOwnProperty(c)) { if (this.__useBuiltIn) { result = result.concat(this.__edgeCollections[c].INEDGES(vertexId)); } else { result = result.concat(this.__edgeCollections[c].inEdges(vertexId)); } } } return result; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief outEdges(vertexId). // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._OUTEDGES = function (vertexId) { var err; if (vertexId.indexOf('/') === -1) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_NOT_FOUND.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_NOT_FOUND.message + ': ' + vertexId; throw err; } var result = [], c; for (c in this.__edgeCollections) { if (this.__edgeCollections.hasOwnProperty(c)) { if (this.__useBuiltIn) { result = result.concat(this.__edgeCollections[c].OUTEDGES(vertexId)); } else { result = result.concat(this.__edgeCollections[c].outEdges(vertexId)); } } } return result; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_edges // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._edges = function (vertexExample, options) { var bindVars = {}; options = options || {}; if (options.edgeCollectionRestriction) { if (!Array.isArray(options.edgeCollectionRestriction)) { options.edgeCollectionRestriction = [ options.edgeCollectionRestriction ]; } } var query = ` ${transformExampleToAQL(vertexExample, Object.keys(this.__vertexCollections), bindVars, "start")} FOR v, e IN ${options.minDepth || 1}..${options.maxDepth || 1} ${options.direction || "ANY"} start ${Array.isArray(options.edgeCollectionRestriction) && options.edgeCollectionRestriction.length > 0 ? buildEdgeCollectionRestriction(options.edgeCollectionRestriction) : "GRAPH @graphName"} ${buildFilter(options.edgeExamples, bindVars, "e")} RETURN DISTINCT ${options.includeData === true ? "e" : "e._id"}`; if (!Array.isArray(options.edgeCollectionRestriction) || options.edgeCollectionRestriction.length === 0) { bindVars.graphName = this.__name; } return db._query(query, bindVars).toArray(); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_vertices // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._vertices = function (vertexExample, options) { options = options || {}; if (options.vertexCollectionRestriction) { if (!Array.isArray(options.vertexCollectionRestriction)) { options.vertexCollectionRestriction = [ options.vertexCollectionRestriction ]; } } var bindVars = {}; var query = `${transformExampleToAQL({}, Array.isArray(options.vertexCollectionRestriction) && options.vertexCollectionRestriction.length > 0 ? options.vertexCollectionRestriction : Object.keys(this.__vertexCollections), bindVars, "start")} ${buildFilter(vertexExample, bindVars, "start")} RETURN DISTINCT start`; return db._query(query, bindVars).toArray(); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_fromVertex // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._fromVertex = function (edgeId) { if (typeof edgeId !== 'string' || edgeId.indexOf('/') === -1) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_ARANGO_DOCUMENT_HANDLE_BAD.code; err.errorMessage = arangodb.errors.ERROR_ARANGO_DOCUMENT_HANDLE_BAD.message; throw err; } var edgeCollection = this._getEdgeCollectionByName(edgeId.split('/')[0]); var document = edgeCollection.document(edgeId); if (document) { var vertexId = document._from; var vertexCollection = this._getVertexCollectionByName(vertexId.split('/')[0]); return vertexCollection.document(vertexId); } }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_toVertex // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._toVertex = function (edgeId) { if (typeof edgeId !== 'string' || edgeId.indexOf('/') === -1) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_ARANGO_DOCUMENT_HANDLE_BAD.code; err.errorMessage = arangodb.errors.ERROR_ARANGO_DOCUMENT_HANDLE_BAD.message; throw err; } var edgeCollection = this._getEdgeCollectionByName(edgeId.split('/')[0]); var document = edgeCollection.document(edgeId); if (document) { var vertexId = document._to; var vertexCollection = this._getVertexCollectionByName(vertexId.split('/')[0]); return vertexCollection.document(vertexId); } }; // ////////////////////////////////////////////////////////////////////////////// // / @brief get edge collection by name. // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._getEdgeCollectionByName = function (name) { if (this.__edgeCollections[name]) { return this.__edgeCollections[name]; } var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_EDGE_COL_DOES_NOT_EXIST.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_EDGE_COL_DOES_NOT_EXIST.message + ': ' + name; throw err; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief get vertex collection by name. // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._getVertexCollectionByName = function (name) { if (this.__vertexCollections[name]) { return this.__vertexCollections[name]; } var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.message + ': ' + name; throw err; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_neighbors // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._neighbors = function (vertexExample, options) { options = options || {}; if (options.vertexCollectionRestriction) { if (!Array.isArray(options.vertexCollectionRestriction)) { options.vertexCollectionRestriction = [ options.vertexCollectionRestriction ]; } } if (options.edgeCollectionRestriction) { if (!Array.isArray(options.edgeCollectionRestriction)) { options.edgeCollectionRestriction = [ options.edgeCollectionRestriction ]; } } var bindVars = {}; var query = ` ${transformExampleToAQL(vertexExample, Object.keys(this.__vertexCollections), bindVars, "start")} FOR v, e IN ${options.minDepth || 1}..${options.maxDepth || 1} ${options.direction || "ANY"} start ${Array.isArray(options.edgeCollectionRestriction) && options.edgeCollectionRestriction.length > 0 ? buildEdgeCollectionRestriction(options.edgeCollectionRestriction) : "GRAPH @graphName"} OPTIONS {bfs: true} ${buildFilter(options.neighborExamples, bindVars, "v")} ${buildFilter(options.edgeExamples, bindVars, "e")} ${Array.isArray(options.vertexCollectionRestriction) && options.vertexCollectionRestriction.length > 0 ? buildVertexCollectionRestriction(options.vertexCollectionRestriction,"v") : ""} RETURN DISTINCT ${options.includeData === true ? "v" : "v._id"}`; if (!Array.isArray(options.edgeCollectionRestriction) || options.edgeCollectionRestriction.length === 0) { bindVars.graphName = this.__name; } return db._query(query, bindVars).toArray(); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_common_neighbors // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._commonNeighbors = function (vertex1Example, vertex2Example, optionsVertex1, optionsVertex2) { var bindVars = {}; optionsVertex1 = optionsVertex1 || {}; optionsVertex2 = optionsVertex2 || {}; if (optionsVertex1.vertexCollectionRestriction) { if (!Array.isArray(optionsVertex1.vertexCollectionRestriction)) { optionsVertex1.vertexCollectionRestriction = [ optionsVertex1.vertexCollectionRestriction ]; } } if (optionsVertex2.vertexCollectionRestriction) { if (!Array.isArray(optionsVertex2.vertexCollectionRestriction)) { optionsVertex2.vertexCollectionRestriction = [ optionsVertex2.vertexCollectionRestriction ]; } } var query = ` ${transformExampleToAQL(vertex1Example, Object.keys(this.__vertexCollections), bindVars, "left")} LET leftNeighbors = (FOR v IN ${optionsVertex1.minDepth || 1}..${optionsVertex1.maxDepth || 1} ${optionsVertex1.direction || "ANY"} left GRAPH @graphName OPTIONS {bfs: true, uniqueVertices: "global"} ${Array.isArray(optionsVertex1.vertexCollectionRestriction) && optionsVertex1.vertexCollectionRestriction.length > 0 ? buildVertexCollectionRestriction(optionsVertex1.vertexCollectionRestriction,"v") : ""} RETURN v) ${transformExampleToAQL(vertex2Example, Object.keys(this.__vertexCollections), bindVars, "right")} FILTER right != left LET rightNeighbors = (FOR v IN ${optionsVertex2.minDepth || 1}..${optionsVertex2.maxDepth || 1} ${optionsVertex2.direction || "ANY"} right GRAPH @graphName OPTIONS {bfs: true, uniqueVertices: "global"} ${Array.isArray(optionsVertex2.vertexCollectionRestriction) && optionsVertex2.vertexCollectionRestriction.length > 0 ? buildVertexCollectionRestriction(optionsVertex2.vertexCollectionRestriction,"v") : ""} RETURN v) LET neighbors = INTERSECTION(leftNeighbors, rightNeighbors) FILTER LENGTH(neighbors) > 0 `; if (optionsVertex1.includeData === true || optionsVertex2.includeData === true) { query += `RETURN {left : left, right: right, neighbors: neighbors}`; } else { query += `RETURN {left : left._id, right: right._id, neighbors: neighbors[*]._id}`; } bindVars.graphName = this.__name; return db._query(query, bindVars).toArray(); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_count_common_neighbors // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._countCommonNeighbors = function (vertex1Example, vertex2Example, optionsVertex1, optionsVertex2) { var result = this._commonNeighbors(vertex1Example, vertex2Example, optionsVertex1, optionsVertex2), tmp = {}, tmp2 = {}, returnHash = []; result.forEach(function (r) { if (!tmp[r.left]) { tmp[r.left] = []; } tmp2 = {}; tmp2[r.right] = r.neighbors.length; tmp[r.left].push(tmp2); }); Object.keys(tmp).forEach(function (w) { tmp2 = {}; tmp2[w] = tmp[w]; returnHash.push(tmp2); }); return returnHash; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_common_properties // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._commonProperties = function (vertex1Example, vertex2Example, options) { options = options || {}; if (options.hasOwnProperty('ignoreProperties')) { if (!Array.isArray(options.ignoreProperties)) { options.ignoreProperties = [options.ignoreProperties]; } } var bindVars = {}; var query = ` ${transformExampleToAQL(vertex1Example, Object.keys(this.__vertexCollections), bindVars, "left")} SORT left._id LET toZip = ( ${transformExampleToAQL(vertex2Example, Object.keys(this.__vertexCollections), bindVars, "right")} FILTER right != left LET shared = (FOR a IN ATTRIBUTES(left) FILTER a != "_rev" FILTER (${options.hasOwnProperty("ignoreProperties") ? `a NOT IN ${JSON.stringify(options.ignoreProperties)} AND` : ""} left[a] == right[a]) OR a == '_id' RETURN a) FILTER LENGTH(shared) > 1 RETURN KEEP(right, shared) ) FILTER LENGTH(toZip) > 0 RETURN ZIP([left._id], [toZip])`; return db._query(query, bindVars).toArray(); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_count_common_properties // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._countCommonProperties = function (vertex1Example, vertex2Example, options) { options = options || {}; if (options.hasOwnProperty('ignoreProperties')) { if (!Array.isArray(options.ignoreProperties)) { options.ignoreProperties = [options.ignoreProperties]; } } var bindVars = {}; var query = ` ${transformExampleToAQL(vertex1Example, Object.keys(this.__vertexCollections), bindVars, "left")} SORT left._id LET s = SUM( ${transformExampleToAQL(vertex2Example, Object.keys(this.__vertexCollections), bindVars, "right")} FILTER right != left LET shared = (FOR a IN ATTRIBUTES(left) FILTER a != "_rev" FILTER (${options.hasOwnProperty("ignoreProperties") ? `a NOT IN ${JSON.stringify(options.ignoreProperties)} AND` : ""} left[a] == right[a]) OR a == '_id' RETURN a) FILTER LENGTH(shared) > 1 RETURN 1 ) FILTER s > 0 RETURN ZIP([left._id], [s])`; return db._query(query, bindVars).toArray(); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_paths // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._paths = function (options) { options = options || {}; var query = ` FOR source IN ${startInAllCollections(Object.keys(this.__vertexCollections))} FOR v, e, p IN ${options.minLength || 0}..${options.maxLength || 10} ${options.direction || "OUTBOUND"} source GRAPH @graphName `; if (options.followCycles) { query += `OPTIONS {uniqueEdges: "none"} `; } else { query += `OPTIONS {uniqueVertices: "path"} `; } query += `RETURN {source: source, destination: v, edges: p.edges, vertice: p.vertices}`; var bindVars = { 'graphName': this.__name }; return db._query(query, bindVars).toArray(); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_shortest_path // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._shortestPath = function (startVertexExample, endVertexExample, options) { var bindVars = {}; options = options || {}; var query = ` ${transformExampleToAQL(startVertexExample, Object.keys(this.__vertexCollections), bindVars, "start")} ${transformExampleToAQL(endVertexExample, Object.keys(this.__vertexCollections), bindVars, "target")} FILTER target._id != start._id LET p = (FOR v, e IN `; if (options.direction === 'outbound') { query += 'OUTBOUND '; } else if (options.direction === 'inbound') { query += 'INBOUND '; } else { query += 'ANY '; } query += `SHORTEST_PATH start TO target GRAPH @graphName `; fixWeight(options); if (options.hasOwnProperty('weightAttribute') && options.hasOwnProperty('defaultWeight')) { query += `OPTIONS {weightAttribute: @attribute, defaultWeight: @default} RETURN { v: v, e: e, d: IS_NULL(e) ? 0 : (IS_NUMBER(e[@attribute]) ? e[@attribute] : @default) }) `; bindVars.attribute = options.weightAttribute; bindVars.default = options.defaultWeight; } else { query += 'RETURN {v: v, e: e, d: IS_NULL(e) ? 0 : 1}) '; } query += ` FILTER LENGTH(p) > 0`; if (options.stopAtFirstMatch) { query += `SORT SUM(p[*].d) LIMIT 1 `; } query += ` RETURN { vertices: p[*].v._id, edges: p[* FILTER CURRENT.e != null].e, distance: SUM(p[*].d) }`; bindVars.graphName = this.__name; return db._query(query, bindVars).toArray(); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_distance_to // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._distanceTo = function (startVertexExample, endVertexExample, options) { var bindVars = {}; options = options || {}; var query = ` ${transformExampleToAQL(startVertexExample, Object.keys(this.__vertexCollections), bindVars, "start")} ${transformExampleToAQL(endVertexExample, Object.keys(this.__vertexCollections), bindVars, "target")} FILTER target._id != start._id LET p = (FOR v, e IN `; if (options.direction === 'outbound') { query += 'OUTBOUND '; } else if (options.direction === 'inbound') { query += 'INBOUND '; } else { query += 'ANY '; } query += `SHORTEST_PATH start TO target GRAPH @graphName `; fixWeight(options); if (options.hasOwnProperty('weightAttribute') && options.hasOwnProperty('defaultWeight')) { query += `OPTIONS {weightAttribute: @attribute, defaultWeight: @default} FILTER e != null RETURN IS_NUMBER(e[@attribute]) ? e[@attribute] : @default) `; bindVars.attribute = options.weightAttribute; bindVars.default = options.defaultWeight; } else { query += 'FILTER e != null RETURN 1) '; } query += ` FILTER LENGTH(p) > 0 RETURN { startVertex: start._id, vertex: target._id, distance: SUM(p) }`; bindVars.graphName = this.__name; return db._query(query, bindVars).toArray(); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_absolute_eccentricity // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._absoluteEccentricity = function (vertexExample, options) { var bindVars = {}; options = options || {}; var query = transformExampleToAQL(vertexExample, Object.keys(this.__vertexCollections), bindVars, 'start'); query += ` LET lsp = ( FOR target IN ${startInAllCollections(Object.keys(this.__vertexCollections))} FILTER target._id != start._id LET p = (FOR v, e IN `; if (options.direction === 'outbound') { query += 'OUTBOUND '; } else if (options.direction === 'inbound') { query += 'INBOUND '; } else { query += 'ANY '; } query += 'SHORTEST_PATH start TO target GRAPH @graphName '; fixWeight(options); if (options.hasOwnProperty('weightAttribute') && options.hasOwnProperty('defaultWeight')) { query += `OPTIONS {weightAttribute: @attribute, defaultWeight: @default} FILTER e != null RETURN IS_NUMBER(e[@attribute]) ? e[@attribute] : @default) `; bindVars.attribute = options.weightAttribute; bindVars.default = options.defaultWeight; } else { query += 'FILTER e != null RETURN 1) '; } query += `LET k = LENGTH(p) == 0 ? 0 : SUM(p) SORT k DESC LIMIT 1 RETURN k) RETURN [start._id, lsp[0]] `; bindVars.graphName = this.__name; var cursor = db._query(query, bindVars); var result = {}; while (cursor.hasNext()) { var r = cursor.next(); result[r[0]] = r[1]; } return result; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_absolute_closeness // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._farness = Graph.prototype._absoluteCloseness = function (vertexExample, options) { var bindVars = {}; options = options || {}; var query = transformExampleToAQL(vertexExample, Object.keys(this.__vertexCollections), bindVars, 'start'); query += ` LET lsp = ( FOR target IN ${startInAllCollections(Object.keys(this.__vertexCollections))} FILTER target._id != start._id LET p = (FOR v, e IN `; if (options.direction === 'outbound') { query += 'OUTBOUND '; } else if (options.direction === 'inbound') { query += 'INBOUND '; } else { query += 'ANY '; } query += 'SHORTEST_PATH start TO target GRAPH @graphName '; fixWeight(options); if (options.hasOwnProperty('weightAttribute') && options.hasOwnProperty('defaultWeight')) { query += `OPTIONS {weightAttribute: @attribute, defaultWeight: @default} FILTER e != null RETURN IS_NUMBER(e[@attribute]) ? e[@attribute] : @default) `; bindVars.attribute = options.weightAttribute; bindVars.default = options.defaultWeight; } else { query += 'FILTER e != null RETURN 1) '; } query += `LET k = LENGTH(p) == 0 ? 0 : SUM(p) RETURN k) RETURN [start._id, SUM(lsp)] `; bindVars.graphName = this.__name; var cursor = db._query(query, bindVars); var result = {}; while (cursor.hasNext()) { var r = cursor.next(); result[r[0]] = r[1]; } return result; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_eccentricity // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._eccentricity = function (options) { let result = this._absoluteEccentricity({}, options); let min = Number.POSITIVE_INFINITY; for (let k of Object.keys(result)) { if (result[k] !== 0 && result[k] < min) { min = result[k]; } } for (let k of Object.keys(result)) { if (result[k] !== 0) { result[k] = min / result[k]; } } return result; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_closeness // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._closeness = function (options) { var farness = this._farness({}, options); var keys = Object.keys(farness); var min = Number.POSITIVE_INFINITY; for (let t of keys) { if (farness[t] > 0 && farness[t] < min) { min = farness[t]; } } for (let k of keys) { if (farness[k] > 0) { farness[k] = min / farness[k]; } } return farness; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_absolute_betweenness // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._absoluteBetweenness = function (example, options) { var bindVars = {}; options = options || {}; bindVars.graphName = this.__name; var query = ` LET toFind = (${transformExampleToAQL(example, Object.keys(this.__vertexCollections), bindVars, "start")} RETURN start._id) LET paths = ( FOR start IN ${startInAllCollections(Object.keys(this.__vertexCollections))} FOR target IN ${startInAllCollections(Object.keys(this.__vertexCollections))} FILTER start._id != target._id FOR v IN `; if (options.direction === 'outbound') { query += 'OUTBOUND '; } else if (options.direction === 'inbound') { query += 'INBOUND '; } else { query += 'ANY '; } query += 'SHORTEST_PATH start TO target GRAPH @graphName '; fixWeight(options); if (options.hasOwnProperty('weightAttribute') && options.hasOwnProperty('defaultWeight')) { query += `OPTIONS {weightAttribute: @attribute, defaultWeight: @default} `; bindVars.attribute = options.weightAttribute; bindVars.default = options.defaultWeight; } query += ` FILTER v._id != start._id AND v._id != target._id AND v._id IN toFind COLLECT id = v._id WITH COUNT INTO betweenness RETURN [id, betweenness]) RETURN {toFind, paths} `; var res = db._query(query, bindVars).toArray(); var result = {}; var toFind = res[0].toFind; for (let pair of res[0].paths) { if (options.direction !== 'inbound' && options.direction !== 'outbound') { // In any every path is contained twice, once forward once backward. result[pair[0]] = pair[1] / 2; } else { result[pair[0]] = pair[1]; } } // Add all not found values as 0. for (let nf of toFind) { if (!result.hasOwnProperty(nf)) { result[nf] = 0; } } return result; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_betweenness // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._betweenness = function (options) { let result = this._absoluteBetweenness({}, options); let max = 0; for (let k of Object.keys(result)) { if (result[k] > max) { max = result[k]; } } if (max !== 0) { for (let k of Object.keys(result)) { result[k] = result[k] / max; } } return result; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_radius // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._radius = function (options) { var vcs = Object.keys(this.__vertexCollections); var query = ''; var ids; var bindVars = { 'graphName': this.__name }; options = options || {}; if (vcs.length === 1) { ids = vcs[0]; } else { query = `LET ids = UNION(${vcs.map(function(v) {return `(FOR x IN ${v} RETURN x)`;}).join(",")}) `; ids = 'ids'; } query += `FOR s IN ${ids} LET lsp = ( FOR t IN ${ids} FILTER s._id != t._id LET p = (FOR v, e IN `; if (options.direction === 'outbound') { query += 'OUTBOUND '; } else if (options.direction === 'inbound') { query += 'INBOUND '; } else { query += 'ANY '; } query += 'SHORTEST_PATH s TO t GRAPH @graphName '; fixWeight(options); if (options.hasOwnProperty('weightAttribute') && options.hasOwnProperty('defaultWeight')) { query += `OPTIONS {weightAttribute: @attribute, defaultWeight: @default} FILTER e != null RETURN IS_NUMBER(e[@attribute]) ? e[@attribute] : @default) `; bindVars.attribute = options.weightAttribute; bindVars.default = options.defaultWeight; } else { query += 'FILTER e != null RETURN 1) '; } query += `FILTER LENGTH(p) > 0 LET k = SUM(p) SORT k DESC LIMIT 1 RETURN k) FILTER LENGTH(lsp) != 0 SORT lsp[0] ASC LIMIT 1 RETURN lsp[0]`; var res = db._query(query, bindVars).toArray(); if (res.length > 0) { return res[0]; } return res; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_diameter // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._diameter = function (options) { var vcs = Object.keys(this.__vertexCollections); var query; if (vcs.length === 1) { query = `FOR s IN ${vcs[0]} FOR t IN ${vcs[0]} `; } else { query = `LET ids = UNION(${vcs.map(function(v) {return `(FOR x IN ${v} RETURN x)`;}).join(",")}) FOR s IN ids FOR t IN ids `; } options = options || {}; if (options.direction === 'outbound') { query += 'FILTER s._id != t._id LET p = SUM((FOR v, e IN OUTBOUND '; } else if (options.direction === 'inbound') { query += 'FILTER s._id != t._id LET p = SUM((FOR v, e IN INBOUND '; } else { query += 'FILTER s._id < t._id LET p = SUM((FOR v, e IN ANY '; } var bindVars = { 'graphName': this.__name }; query += 'SHORTEST_PATH s TO t GRAPH @graphName '; fixWeight(options); if (options.hasOwnProperty('weightAttribute') && options.hasOwnProperty('defaultWeight')) { query += `OPTIONS {weightAttribute: @attribute, defaultWeight: @default} FILTER e != null RETURN IS_NUMBER(e[@attribute]) ? e[@attribute] : @default)) `; bindVars.attribute = options.weightAttribute; bindVars.default = options.defaultWeight; } else { query += 'RETURN 1)) - 1 '; } query += 'SORT p DESC LIMIT 1 RETURN p'; var result = db._query(query, bindVars).toArray(); if (result.length === 1) { return result[0]; } return result; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph__extendEdgeDefinitions // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._extendEdgeDefinitions = function (edgeDefinition) { edgeDefinition = sortEdgeDefinition(edgeDefinition); var self = this; var err; // check if edgeCollection not already used var eC = edgeDefinition.collection; // ... in same graph if (this.__edgeCollections[eC] !== undefined) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_COLLECTION_MULTI_USE.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_COLLECTION_MULTI_USE.message; throw err; } // in different graph db._graphs.toArray().forEach( function (singleGraph) { var sGEDs = singleGraph.edgeDefinitions; sGEDs.forEach( function (sGED) { var col = sGED.collection; if (col === eC) { if (JSON.stringify(sGED) !== JSON.stringify(edgeDefinition)) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS.code; err.errorMessage = col + ' ' + arangodb.errors.ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS.message; throw err; } } } ); } ); findOrCreateCollectionsByEdgeDefinitions([edgeDefinition]); this.__edgeDefinitions.push(edgeDefinition); db._graphs.update(this.__name, {edgeDefinitions: this.__edgeDefinitions}); this.__edgeCollections[edgeDefinition.collection] = db[edgeDefinition.collection]; edgeDefinition.from.forEach( function (vc) { self[vc] = db[vc]; // remove from __orphanCollections var orphanIndex = self.__orphanCollections.indexOf(vc); if (orphanIndex !== -1) { self.__orphanCollections.splice(orphanIndex, 1); } // push into __vertexCollections if (self.__vertexCollections[vc] === undefined) { self.__vertexCollections[vc] = db[vc]; } } ); edgeDefinition.to.forEach( function (vc) { self[vc] = db[vc]; // remove from __orphanCollections var orphanIndex = self.__orphanCollections.indexOf(vc); if (orphanIndex !== -1) { self.__orphanCollections.splice(orphanIndex, 1); } // push into __vertexCollections if (self.__vertexCollections[vc] === undefined) { self.__vertexCollections[vc] = db[vc]; } } ); updateBindCollections(this); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief internal function for editing edge definitions // ////////////////////////////////////////////////////////////////////////////// var changeEdgeDefinitionsForGraph = function (graph, edgeDefinition, newCollections, possibleOrphans, self) { var graphCollections = []; var graphObj = _graph(graph._key); var eDs = graph.edgeDefinitions; var gotAHit = false; // replace edgeDefintion eDs.forEach( function (eD, id) { if (eD.collection === edgeDefinition.collection) { gotAHit = true; eDs[id].from = edgeDefinition.from; eDs[id].to = edgeDefinition.to; db._graphs.update(graph._key, {edgeDefinitions: eDs}); if (graph._key === self.__name) { self.__edgeDefinitions[id].from = edgeDefinition.from; self.__edgeDefinitions[id].to = edgeDefinition.to; } } else { // collect all used collections graphCollections = _.union(graphCollections, eD.from); graphCollections = _.union(graphCollections, eD.to); } } ); if (!gotAHit) { return; } // remove used collection from orphanage if (graph._key === self.__name) { newCollections.forEach( function (nc) { if (self.__vertexCollections[nc] === undefined) { self.__vertexCollections[nc] = db[nc]; } try { self._removeVertexCollection(nc, false); } catch (ignore) {} } ); possibleOrphans.forEach( function (po) { if (graphCollections.indexOf(po) === -1) { delete self.__vertexCollections[po]; self._addVertexCollection(po); } } ); } else { newCollections.forEach( function (nc) { try { graphObj._removeVertexCollection(nc, false); } catch (ignore) {} } ); possibleOrphans.forEach( function (po) { if (graphCollections.indexOf(po) === -1) { delete graphObj.__vertexCollections[po]; graphObj._addVertexCollection(po); } } ); } // move unused collections to orphanage }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph__editEdgeDefinition // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._editEdgeDefinitions = function (edgeDefinition) { edgeDefinition = sortEdgeDefinition(edgeDefinition); var self = this; // check, if in graphs edge definition if (this.__edgeCollections[edgeDefinition.collection] === undefined) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.message; throw err; } findOrCreateCollectionsByEdgeDefinitions([edgeDefinition]); // evaluate collections to add to orphanage var possibleOrphans = []; var currentEdgeDefinition; this.__edgeDefinitions.forEach( function (ed) { if (edgeDefinition.collection === ed.collection) { currentEdgeDefinition = ed; } } ); var currentCollections = _.union(currentEdgeDefinition.from, currentEdgeDefinition.to); var newCollections = _.union(edgeDefinition.from, edgeDefinition.to); currentCollections.forEach( function (colName) { if (newCollections.indexOf(colName) === -1) { possibleOrphans.push(colName); } } ); // change definition for ALL graphs var graphs = getGraphCollection().toArray(); graphs.forEach( function (graph) { changeEdgeDefinitionsForGraph(graph, edgeDefinition, newCollections, possibleOrphans, self); } ); updateBindCollections(this); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph__deleteEdgeDefinition // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._deleteEdgeDefinition = function (edgeCollection, dropCollection) { // check, if in graphs edge definition if (this.__edgeCollections[edgeCollection] === undefined) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.message; throw err; } var edgeDefinitions = this.__edgeDefinitions, self = this, usedVertexCollections = [], possibleOrphans = [], index; edgeDefinitions.forEach( function (edgeDefinition, idx) { if (edgeDefinition.collection === edgeCollection) { index = idx; possibleOrphans = edgeDefinition.from; possibleOrphans = _.union(possibleOrphans, edgeDefinition.to); } else { usedVertexCollections = _.union(usedVertexCollections, edgeDefinition.from); usedVertexCollections = _.union(usedVertexCollections, edgeDefinition.to); } } ); this.__edgeDefinitions.splice(index, 1); possibleOrphans.forEach( function (po) { if (usedVertexCollections.indexOf(po) === -1) { self.__orphanCollections.push(po); } } ); updateBindCollections(this); db._graphs.update( this.__name, { orphanCollections: this.__orphanCollections, edgeDefinitions: this.__edgeDefinitions } ); if (dropCollection) { db._drop(edgeCollection); } }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph__addVertexCollection // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._addVertexCollection = function (vertexCollectionName, createCollection) { // check edgeCollection var ec = db._collection(vertexCollectionName); var err; if (ec === null) { if (createCollection !== false) { db._create(vertexCollectionName); } else { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.code; err.errorMessage = vertexCollectionName + arangodb.errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.message; throw err; } } else if (ec.type() !== 2) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_WRONG_COLLECTION_TYPE_VERTEX.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_WRONG_COLLECTION_TYPE_VERTEX.message; throw err; } if (this.__vertexCollections[vertexCollectionName] !== undefined) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF.message; throw err; } if (_.includes(this.__orphanCollections, vertexCollectionName)) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS.message; throw err; } this.__orphanCollections.push(vertexCollectionName); updateBindCollections(this); db._graphs.update(this.__name, {orphanCollections: this.__orphanCollections}); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph__orphanCollections // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._orphanCollections = function () { return this.__orphanCollections; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph__removeVertexCollection // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._removeVertexCollection = function (vertexCollectionName, dropCollection) { var err; if (db._collection(vertexCollectionName) === null) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.message; throw err; } var index = this.__orphanCollections.indexOf(vertexCollectionName); if (index === -1) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_NOT_IN_ORPHAN_COLLECTION.code; err.errorMessage = arangodb.errors.ERROR_GRAPH_NOT_IN_ORPHAN_COLLECTION.message; throw err; } this.__orphanCollections.splice(index, 1); delete this[vertexCollectionName]; db._graphs.update(this.__name, {orphanCollections: this.__orphanCollections}); if (dropCollection === true) { var graphs = getGraphCollection().toArray(); if (checkIfMayBeDropped(vertexCollectionName, null, graphs)) { db._drop(vertexCollectionName); } } updateBindCollections(this); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_connectingEdges // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._getConnectingEdges = function (vertexExample1, vertexExample2, options) { options = options || {}; // TODO return []; /* var opts = { includeData: true } if (options.vertex1CollectionRestriction) { opts.startVertexCollectionRestriction = options.vertex1CollectionRestriction } if (options.vertex2CollectionRestriction) { opts.endVertexCollectionRestriction = options.vertex2CollectionRestriction } if (options.edgeCollectionRestriction) { opts.edgeCollectionRestriction = options.edgeCollectionRestriction } if (options.edgeExamples) { opts.edgeExamples = options.edgeExamples } if (vertexExample2) { opts.neighborExamples = vertexExample2 } var query = "RETURN" + " GRAPH_EDGES(@graphName" + ',@vertexExample' + ',@options' + ')' var bindVars = { "graphName": this.__name, "vertexExample": vertexExample1, "options": opts } var result = db._query(query, bindVars).toArray() return result[0] */ }; // ////////////////////////////////////////////////////////////////////////////// // / @brief print basic information for the graph // ////////////////////////////////////////////////////////////////////////////// Graph.prototype._PRINT = function (context) { var name = this.__name; var edgeDefs = printEdgeDefinitions(this.__edgeDefinitions); context.output += '[ Graph '; context.output += name; context.output += ' EdgeDefinitions: '; internal.printRecursive(edgeDefs, context); context.output += ' VertexCollections: '; internal.printRecursive(this.__orphanCollections, context); context.output += ' ]'; }; exports._relation = _relation; exports._graph = _graph; exports._edgeDefinitions = _edgeDefinitions; exports._extendEdgeDefinitions = _extendEdgeDefinitions; exports._create = _create; exports._drop = _drop; exports._exists = _exists; exports._renameCollection = _renameCollection; exports._list = _list; exports._listObjects = _listObjects; exports._registerCompatibilityFunctions = registerCompatibilityFunctions; // ////////////////////////////////////////////////////////////////////////////// // / some more documentation // ////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_create_graph_example1 // ////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph_create_graph_example2 // //////////////////////////////////////////////////////////////////////////////