mirror of https://gitee.com/bigwinds/arangodb
2114 lines
77 KiB
JavaScript
2114 lines
77 KiB
JavaScript
/* 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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
const arangodb = require('@arangodb');
|
|
const internal = require('internal');
|
|
const ArangoCollection = arangodb.ArangoCollection;
|
|
const ArangoError = arangodb.ArangoError;
|
|
const db = arangodb.db;
|
|
const errors = arangodb.errors;
|
|
const users = require('@arangodb/users');
|
|
const _ = require('lodash');
|
|
|
|
let 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, options) {
|
|
let col = db._collection(name);
|
|
let res = false;
|
|
if (col === null && !noCreate) {
|
|
if (type === ArangoCollection.TYPE_DOCUMENT) {
|
|
col = db._create(name, options);
|
|
} else {
|
|
col = db._createEdgeCollection(name, options);
|
|
}
|
|
res = true;
|
|
} else if (!(col instanceof ArangoCollection)) {
|
|
var err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_ARANGO_DATA_SOURCE_NOT_FOUND.code;
|
|
err.errorMessage = name + arangodb.errors.ERROR_ARANGO_DATA_SOURCE_NOT_FOUND.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;
|
|
}
|
|
checkROPermission(name);
|
|
return res;
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief find or create a collection by name
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
var findOrCreateCollectionsByEdgeDefinitions = function (edgeDefinitions, noCreate, options) {
|
|
let vertexCollections = {};
|
|
let 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.forEach(function (v) {
|
|
findOrCreateCollectionByName(v, ArangoCollection.TYPE_DOCUMENT, noCreate, options);
|
|
vertexCollections[v] = db[v];
|
|
});
|
|
e.to.forEach(function (v) {
|
|
findOrCreateCollectionByName(v, ArangoCollection.TYPE_DOCUMENT, noCreate, options);
|
|
vertexCollections[v] = db[v];
|
|
});
|
|
|
|
findOrCreateCollectionByName(e.collection, ArangoCollection.TYPE_EDGE, noCreate, options);
|
|
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, bindVars, graph) {
|
|
if (typeof collections === 'string') {
|
|
collections = [collections];
|
|
}
|
|
if (!Array.isArray(collections) || collections.length === 0) {
|
|
bindVars.graphName = graph.__name;
|
|
return 'GRAPH @graphName';
|
|
}
|
|
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 WITH v1, ... vn if we do not use the graph name.
|
|
var generateWithStatement = function (graph, options) {
|
|
if (!options.hasOwnProperty('edgeCollectionRestriction') ||
|
|
!Array.isArray(options.edgeCollectionRestriction) ||
|
|
options.edgeCollectionRestriction.length === 0) {
|
|
return '';
|
|
}
|
|
return 'WITH ' + Object.keys(graph.__vertexCollections).join(', ');
|
|
};
|
|
|
|
// Returns FOR <varname> 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;
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / internal helper to sort a graph's edge definitions
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
var sortEdgeDefinitionInplace = function (edgeDefinition) {
|
|
edgeDefinition.from.sort();
|
|
edgeDefinition.to.sort();
|
|
return edgeDefinition;
|
|
};
|
|
|
|
var sortEdgeDefinition = function (edgeDefinition) {
|
|
return {
|
|
collection: edgeDefinition.collection,
|
|
from: edgeDefinition.from.slice().sort(),
|
|
to: edgeDefinition.to.slice().sort(),
|
|
};
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief create a new graph
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief was docuBlock JSF_general_graph_how_to_create
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
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 oldSave = wrap.insert;
|
|
wrap.save = wrap.insert = function (from, to, data) {
|
|
var options = {};
|
|
if (typeof from === 'object' && to === undefined) {
|
|
data = from;
|
|
from = data._from;
|
|
to = data._to;
|
|
} else if (typeof from === 'object' && typeof to === 'object' && data === undefined) {
|
|
data = from;
|
|
options = to;
|
|
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 + '. Doesn\'t conform to any edge definition';
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
return oldSave(data, options);
|
|
};
|
|
|
|
// remove
|
|
wrap.remove = function (edgeId, options) {
|
|
// if _key make _id (only on 1st call)
|
|
if (edgeId.indexOf('/') === -1) {
|
|
edgeId = key + '/' + edgeId;
|
|
}
|
|
var graphs = exports._listObjects();
|
|
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 = exports._listObjects();
|
|
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);
|
|
};
|
|
|
|
var checkRWPermission = function (c) {
|
|
if (!users.isAuthActive()) {
|
|
return;
|
|
}
|
|
|
|
let user = users.currentUser();
|
|
if (user) {
|
|
let p = users.permission(user, db._name(), c);
|
|
if (p !== 'rw') {
|
|
var err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_FORBIDDEN.code;
|
|
err.errorMessage = arangodb.errors.ERROR_FORBIDDEN.message;
|
|
throw err;
|
|
}
|
|
}
|
|
};
|
|
|
|
var checkROPermission = function(c) {
|
|
if (!users.isAuthActive()) {
|
|
return;
|
|
}
|
|
|
|
let user = users.currentUser();
|
|
if (user) {
|
|
let p = users.permission(user, db._name(), c);
|
|
var err = new ArangoError();
|
|
if (p === 'none') {
|
|
err.errorNum = arangodb.errors.ERROR_FORBIDDEN.code;
|
|
err.errorMessage = arangodb.errors.ERROR_FORBIDDEN.message;
|
|
throw err;
|
|
}
|
|
}
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief internal function for editing edge definitions
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
var changeEdgeDefinitionsForGraph = function (graph, edgeDefinition, newCollections, possibleOrphans, self) {
|
|
var graphCollections = [];
|
|
var graphObj = exports._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 Helper for dropping collections of a graph.
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
var checkIfMayBeDropped = function (colName, graphName, graphs) {
|
|
var result = true;
|
|
|
|
graphs.forEach(
|
|
function (graph) {
|
|
if (result === false) {
|
|
// Short circuit
|
|
return;
|
|
}
|
|
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;
|
|
};
|
|
|
|
const edgeDefinitionsEqual = function (leftEdgeDef, rightEdgeDef) {
|
|
leftEdgeDef = sortEdgeDefinition(leftEdgeDef);
|
|
rightEdgeDef = sortEdgeDefinition(rightEdgeDef);
|
|
const stringify = obj => JSON.stringify(obj, Object.keys(obj).sort());
|
|
return stringify(leftEdgeDef) === stringify(rightEdgeDef);
|
|
};
|
|
|
|
// @brief Class Graph. Defines a graph in the Database.
|
|
class Graph {
|
|
constructor (info) {
|
|
// We assume well-formedness of the input.
|
|
// User cannot directly call this constructor.
|
|
let vertexCollections = {};
|
|
let edgeCollections = {};
|
|
|
|
info.edgeDefinitions.forEach((e) => {
|
|
// Link the edge collection
|
|
edgeCollections[e.collection] = db[e.collection];
|
|
// Link from collections
|
|
e.from.forEach((v) => {
|
|
vertexCollections[v] = db[v];
|
|
});
|
|
// Link to collections
|
|
e.to.forEach((v) => {
|
|
vertexCollections[v] = db[v];
|
|
});
|
|
});
|
|
|
|
// 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', info._key || info.name);
|
|
createHiddenProperty(this, '__vertexCollections', vertexCollections);
|
|
createHiddenProperty(this, '__edgeCollections', edgeCollections);
|
|
createHiddenProperty(this, '__edgeDefinitions', info.edgeDefinitions);
|
|
createHiddenProperty(this, '__idsToRemove', {});
|
|
createHiddenProperty(this, '__collectionsToLock', {});
|
|
createHiddenProperty(this, '__id', info._id);
|
|
createHiddenProperty(this, '__rev', info._rev);
|
|
createHiddenProperty(this, '__orphanCollections', info.orphanCollections || []);
|
|
|
|
if (info.numberOfShards) {
|
|
createHiddenProperty(this, '__numberOfShards', info.numberOfShards);
|
|
}
|
|
createHiddenProperty(this, '__replicationFactor', info.replicationFactor || 1);
|
|
|
|
// Create Hidden Functions
|
|
createHiddenProperty(this, '__updateBindCollections', updateBindCollections);
|
|
createHiddenProperty(this, '__sortEdgeDefinition', sortEdgeDefinitionInplace);
|
|
updateBindCollections(self);
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief return all edge collections of the graph.
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_edgeCollections () {
|
|
return _.values(this.__edgeCollections);
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief return all vertex collections of the graph.
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_vertexCollections (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
|
|
_EDGES (vertexId) {
|
|
if (vertexId.indexOf('/') === -1) {
|
|
let err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_NOT_FOUND.code;
|
|
err.errorMessage = arangodb.errors.ERROR_GRAPH_NOT_FOUND.message + ': ' + vertexId;
|
|
throw err;
|
|
}
|
|
|
|
let result = [];
|
|
for (let 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).
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_INEDGES (vertexId) {
|
|
if (vertexId.indexOf('/') === -1) {
|
|
let err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_NOT_FOUND.code;
|
|
err.errorMessage = arangodb.errors.ERROR_GRAPH_NOT_FOUND.message + ': ' + vertexId;
|
|
throw err;
|
|
}
|
|
|
|
let result = [];
|
|
for (let 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).
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_OUTEDGES (vertexId) {
|
|
if (vertexId.indexOf('/') === -1) {
|
|
let err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_NOT_FOUND.code;
|
|
err.errorMessage = arangodb.errors.ERROR_GRAPH_NOT_FOUND.message + ': ' + vertexId;
|
|
throw err;
|
|
}
|
|
|
|
let result = [];
|
|
for (let 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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_edges (vertexExample, options) {
|
|
var bindVars = {};
|
|
options = options || {};
|
|
var query = `
|
|
${transformExampleToAQL(vertexExample, Object.keys(this.__vertexCollections), bindVars, 'start')}
|
|
FOR v, e IN ${options.minDepth || 1}..${options.maxDepth || 1} ${options.direction || 'ANY'} start
|
|
${buildEdgeCollectionRestriction(options.edgeCollectionRestriction, bindVars, this)}
|
|
${buildFilter(options.edgeExamples, bindVars, 'e')}
|
|
RETURN DISTINCT ${options.includeData === true ? 'e' : 'e._id'}`;
|
|
return db._query(query, bindVars).toArray();
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief was docuBlock JSF_general_graph_vertices
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_vertices (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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_fromVertex (edgeId) {
|
|
if (typeof edgeId !== 'string' ||
|
|
edgeId.indexOf('/') === -1) {
|
|
let 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;
|
|
}
|
|
let edgeCollection = this._getEdgeCollectionByName(edgeId.split('/')[0]);
|
|
let document = edgeCollection.document(edgeId);
|
|
if (document) {
|
|
let vertexId = document._from;
|
|
let vertexCollection = this._getVertexCollectionByName(vertexId.split('/')[0]);
|
|
return vertexCollection.document(vertexId);
|
|
}
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief was docuBlock JSF_general_graph_toVertex
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_toVertex (edgeId) {
|
|
if (typeof edgeId !== 'string' ||
|
|
edgeId.indexOf('/') === -1) {
|
|
let 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;
|
|
}
|
|
let edgeCollection = this._getEdgeCollectionByName(edgeId.split('/')[0]);
|
|
let document = edgeCollection.document(edgeId);
|
|
if (document) {
|
|
let vertexId = document._to;
|
|
let vertexCollection = this._getVertexCollectionByName(vertexId.split('/')[0]);
|
|
return vertexCollection.document(vertexId);
|
|
}
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief get edge collection by name.
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_getEdgeCollectionByName (name) {
|
|
if (this.__edgeCollections[name]) {
|
|
return this.__edgeCollections[name];
|
|
}
|
|
let 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.
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_getVertexCollectionByName (name) {
|
|
if (this.__vertexCollections[name]) {
|
|
return this.__vertexCollections[name];
|
|
}
|
|
let 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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_neighbors (vertexExample, options) {
|
|
options = options || {};
|
|
if (options.vertexCollectionRestriction) {
|
|
if (!Array.isArray(options.vertexCollectionRestriction)) {
|
|
options.vertexCollectionRestriction = [ options.vertexCollectionRestriction ];
|
|
}
|
|
}
|
|
|
|
var bindVars = {};
|
|
var query = `
|
|
${generateWithStatement(this, options)}
|
|
${transformExampleToAQL(vertexExample, Object.keys(this.__vertexCollections), bindVars, 'start')}
|
|
FOR v, e IN ${options.minDepth || 1}..${options.maxDepth || 1} ${options.direction || 'ANY'} start
|
|
${buildEdgeCollectionRestriction(options.edgeCollectionRestriction, bindVars, this)}
|
|
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'}`;
|
|
return db._query(query, bindVars).toArray();
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief was docuBlock JSF_general_graph_common_neighbors
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_commonNeighbors (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 = `
|
|
${generateWithStatement(this, optionsVertex1.hasOwnProperty('edgeCollectionRestriction') ? optionsVertex1 : optionsVertex2)}
|
|
${transformExampleToAQL(vertex1Example, Object.keys(this.__vertexCollections), bindVars, 'left')}
|
|
LET leftNeighbors = (FOR v IN ${optionsVertex1.minDepth || 1}..${optionsVertex1.maxDepth || 1} ${optionsVertex1.direction || 'ANY'} left
|
|
${buildEdgeCollectionRestriction(optionsVertex1.edgeCollectionRestriction, bindVars, this)}
|
|
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
|
|
${buildEdgeCollectionRestriction(optionsVertex2.edgeCollectionRestriction, bindVars, this)}
|
|
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._id, right: right._id, neighbors: neighbors}`;
|
|
} else {
|
|
query += `RETURN {left : left._id, right: right._id, neighbors: neighbors[*]._id}`;
|
|
}
|
|
return db._query(query, bindVars).toArray();
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief was docuBlock JSF_general_graph_count_common_neighbors
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_countCommonNeighbors (vertex1Example, vertex2Example, optionsVertex1, optionsVertex2) {
|
|
let result = this._commonNeighbors(vertex1Example, vertex2Example, optionsVertex1, optionsVertex2);
|
|
let tmp = {};
|
|
let tmp2 = {};
|
|
let 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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_commonProperties (vertex1Example, vertex2Example, options) {
|
|
options = options || {};
|
|
if (options.hasOwnProperty('ignoreProperties')) {
|
|
if (!Array.isArray(options.ignoreProperties)) {
|
|
options.ignoreProperties = [options.ignoreProperties];
|
|
}
|
|
}
|
|
var bindVars = {};
|
|
var query = `
|
|
${generateWithStatement(this, options)}
|
|
${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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_countCommonProperties (vertex1Example, vertex2Example, options) {
|
|
options = options || {};
|
|
if (options.hasOwnProperty('ignoreProperties')) {
|
|
if (!Array.isArray(options.ignoreProperties)) {
|
|
options.ignoreProperties = [options.ignoreProperties];
|
|
}
|
|
}
|
|
var bindVars = {};
|
|
var query = `
|
|
${generateWithStatement(this, options)}
|
|
${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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_paths (options) {
|
|
options = options || {};
|
|
|
|
var query = `
|
|
${generateWithStatement(this, options)}
|
|
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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_shortestPath (startVertexExample, endVertexExample, options) {
|
|
var bindVars = {};
|
|
options = options || {};
|
|
var query = `
|
|
${generateWithStatement(this, options)}
|
|
${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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_distanceTo (startVertexExample, endVertexExample, options) {
|
|
var bindVars = {};
|
|
options = options || {};
|
|
var query = `
|
|
${generateWithStatement(this, options)}
|
|
${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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_absoluteEccentricity (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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_farness (vertexExample, options) {
|
|
return this._absoluteCloseness(vertexExample, options);
|
|
}
|
|
|
|
_absoluteCloseness (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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_eccentricity (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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_closeness (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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
_absoluteBetweenness (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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_betweenness (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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_radius (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((v) => `(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
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
_diameter (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((v) => `(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__orphanCollections
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_orphanCollections () {
|
|
return this.__orphanCollections;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief was docuBlock JSF_general_graph_connectingEdges
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
_getConnectingEdges (vertexExample1, vertexExample2, options) {
|
|
options = options || {};
|
|
if (options.vertex1CollectionRestriction) {
|
|
if (!Array.isArray(options.vertex1CollectionRestriction)) {
|
|
options.vertex1CollectionRestriction = [ options.vertex1CollectionRestriction ];
|
|
}
|
|
}
|
|
if (options.vertex2CollectionRestriction) {
|
|
if (!Array.isArray(options.vertex2CollectionRestriction)) {
|
|
options.vertex2CollectionRestriction = [ options.vertex2CollectionRestriction ];
|
|
}
|
|
}
|
|
|
|
/*var query = `
|
|
${generateWithStatement(this, optionsVertex1.hasOwnProperty('edgeCollectionRestriction') ? optionsVertex1 : optionsVertex2)}
|
|
${transformExampleToAQL(vertex1Example, Object.keys(this.__vertexCollections), bindVars, 'left')}
|
|
LET leftNeighbors = (FOR v IN ${optionsVertex1.minDepth || 1}..${optionsVertex1.maxDepth || 1} ${optionsVertex1.direction || 'ANY'} left
|
|
${buildEdgeCollectionRestriction(optionsVertex1.edgeCollectionRestriction, bindVars, this)}
|
|
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
|
|
${buildEdgeCollectionRestriction(optionsVertex2.edgeCollectionRestriction, bindVars, this)}
|
|
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}`;
|
|
}
|
|
return db._query(query, bindVars).toArray();*/
|
|
|
|
|
|
// TODO
|
|
return [];
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief print basic information for the graph
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
_PRINT (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 += ' ]';
|
|
}
|
|
*/
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief was docuBlock JSF_general_graph_relation
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports._relation = function (relationName, fromVertexCollections, toVertexCollections) {
|
|
if (arguments.length < 3) {
|
|
let 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 === '') {
|
|
let 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)) {
|
|
let 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)) {
|
|
let 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_graph
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports._graph = function (graphName) {
|
|
let gdb = getGraphCollection();
|
|
let g;
|
|
let collections;
|
|
let orphanCollections;
|
|
|
|
try {
|
|
g = gdb.document(graphName);
|
|
} catch (e) {
|
|
if (e.errorNum !== errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) {
|
|
throw e;
|
|
}
|
|
let err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_NOT_FOUND.code;
|
|
err.errorMessage = arangodb.errors.ERROR_GRAPH_NOT_FOUND.message;
|
|
throw err;
|
|
}
|
|
if (g.isSmart) {
|
|
let err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_INVALID_GRAPH.code;
|
|
err.errorMessage = 'The graph you requested is a SmartGraph (Enterprise Only)';
|
|
throw err;
|
|
}
|
|
|
|
findOrCreateCollectionsByEdgeDefinitions(g.edgeDefinitions, true);
|
|
return new Graph(g);
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief was docuBlock JSF_general_graph_edge_definitions
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports._edgeDefinitions = function () {
|
|
let res = [];
|
|
let args = arguments;
|
|
Object.keys(args).forEach(function (x) {
|
|
res.push(args[x]);
|
|
});
|
|
|
|
return res;
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief was docuBlock JSF_general_graph_extend_edge_definitions
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports._extendEdgeDefinitions = function (edgeDefinition) {
|
|
let args = arguments;
|
|
let i = 0;
|
|
|
|
Object.keys(args).forEach(
|
|
function (x) {
|
|
i++;
|
|
if (i === 1) {
|
|
return;
|
|
}
|
|
edgeDefinition.push(args[x]);
|
|
}
|
|
);
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief was docuBlock JSF_general_graph_create
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports._create = function (graphName, edgeDefinitions, orphanCollections, options) {
|
|
if (!Array.isArray(orphanCollections)) {
|
|
orphanCollections = [];
|
|
}
|
|
options = options || {};
|
|
let gdb = getGraphCollection();
|
|
let graphAlreadyExists = true;
|
|
if (!graphName) {
|
|
let 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)) {
|
|
let 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;
|
|
}
|
|
|
|
edgeDefinitions = _.cloneDeep(edgeDefinitions);
|
|
|
|
// check, if a collection is already used in a different edgeDefinition
|
|
let tmpCollections = [];
|
|
let tmpEdgeDefinitions = {};
|
|
edgeDefinitions.forEach(
|
|
(edgeDefinition) => {
|
|
let col = edgeDefinition.collection;
|
|
if (tmpCollections.indexOf(col) !== -1) {
|
|
let 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(
|
|
(singleGraph) => {
|
|
var sGEDs = singleGraph.edgeDefinitions;
|
|
if (!Array.isArray(sGEDs)) {
|
|
return;
|
|
}
|
|
sGEDs.forEach(
|
|
(sGED) => {
|
|
var col = sGED.collection;
|
|
if (tmpCollections.indexOf(col) !== -1) {
|
|
if (!edgeDefinitionsEqual(sGED, tmpEdgeDefinitions[col])) {
|
|
let 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) {
|
|
let err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_DUPLICATE.code;
|
|
err.errorMessage = arangodb.errors.ERROR_GRAPH_DUPLICATE.message;
|
|
throw err;
|
|
}
|
|
|
|
db._flushCache();
|
|
let collections = findOrCreateCollectionsByEdgeDefinitions(edgeDefinitions, false, options);
|
|
orphanCollections.forEach(
|
|
(oC) => {
|
|
findOrCreateCollectionByName(oC, ArangoCollection.TYPE_DOCUMENT, false, options);
|
|
}
|
|
);
|
|
|
|
edgeDefinitions.forEach(sortEdgeDefinitionInplace);
|
|
orphanCollections = orphanCollections.sort();
|
|
|
|
var data = gdb.save({
|
|
'orphanCollections': orphanCollections,
|
|
'edgeDefinitions': edgeDefinitions,
|
|
'_key': graphName,
|
|
'numberOfShards': options.numberOfShards || 1,
|
|
'replicationFactor': options.replicationFactor || 1,
|
|
}, options);
|
|
data.orphanCollections = orphanCollections;
|
|
data.edgeDefinitions = edgeDefinitions;
|
|
return new Graph(data);
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief was docuBlock JSF_general_graph_drop
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports._drop = function (graphId, dropCollections) {
|
|
let gdb = getGraphCollection();
|
|
let graphs;
|
|
|
|
if (!gdb.exists(graphId)) {
|
|
let err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_NOT_FOUND.code;
|
|
err.errorMessage = arangodb.errors.ERROR_GRAPH_NOT_FOUND.message;
|
|
throw err;
|
|
}
|
|
|
|
checkRWPermission("_graphs");
|
|
var graph = gdb.document(graphId);
|
|
if (dropCollections === true) {
|
|
graphs = exports._listObjects();
|
|
// Here we collect all collections
|
|
// that are leading for distribution
|
|
let leaderCollections = new Set();
|
|
let followerCollections = new Set();
|
|
let dropColCB = (name) => {
|
|
if (checkIfMayBeDropped(name, graph._key, graphs)) {
|
|
checkRWPermission(name);
|
|
let colObj = db[name];
|
|
if (colObj !== undefined) {
|
|
// If it is undefined the collection is gone already
|
|
if (colObj.properties().distributeShardsLike === undefined) {
|
|
leaderCollections.add(name);
|
|
} else {
|
|
followerCollections.add(name);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
// drop orphans
|
|
if (!graph.orphanCollections) {
|
|
graph.orphanCollections = [];
|
|
}
|
|
graph.orphanCollections.forEach(dropColCB);
|
|
var edgeDefinitions = graph.edgeDefinitions;
|
|
edgeDefinitions.forEach(
|
|
function (edgeDefinition) {
|
|
var from = edgeDefinition.from;
|
|
var to = edgeDefinition.to;
|
|
var collection = edgeDefinition.collection;
|
|
dropColCB(edgeDefinition.collection);
|
|
from.forEach(dropColCB);
|
|
to.forEach(dropColCB);
|
|
}
|
|
);
|
|
let dropColl = (c) => {
|
|
try {
|
|
db._drop(c);
|
|
} catch (e) {
|
|
internal.print("Failed to Drop: '" + c + "' reason: " + e.message);
|
|
}
|
|
};
|
|
followerCollections.forEach(dropColl);
|
|
leaderCollections.forEach(dropColl);
|
|
}
|
|
|
|
gdb.remove(graphId);
|
|
db._flushCache();
|
|
|
|
return true;
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief check if a graph exists.
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports._exists = function (graphId) {
|
|
var gCol = getGraphCollection();
|
|
return gCol.exists(graphId);
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief rename a collection inside the _graphs collections
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports._renameCollection = function (oldName, newName) {
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: '_graphs'
|
|
},
|
|
action: function (params) {
|
|
var gdb = getGraphCollection();
|
|
if (!gdb) {
|
|
return;
|
|
}
|
|
gdb.toArray().forEach(function (doc) {
|
|
if (doc.isSmart) {
|
|
return;
|
|
}
|
|
let c = Object.assign({}, doc);
|
|
let changed = false;
|
|
if (c.edgeDefinitions) {
|
|
for (let 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 (let j = 0; j < def.from.length; ++j) {
|
|
if (def.from[j] === params.oldName) {
|
|
c.edgeDefinitions[i].from[j] = params.newName;
|
|
changed = true;
|
|
}
|
|
}
|
|
for (let j = 0; j < def.to.length; ++j) {
|
|
if (def.to[j] === params.oldName) {
|
|
c.edgeDefinitions[i].to[j] = params.newName;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (let 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 was docuBlock JSF_general_graph_list
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports._list = function () {
|
|
return db._query(`FOR x IN _graphs RETURN x._key`).toArray();
|
|
};
|
|
|
|
exports._listObjects = function () {
|
|
return db._query(`FOR x IN _graphs RETURN x`).toArray();
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @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.
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports._registerCompatibilityFunctions = function () {
|
|
const aqlfunctions = require('@arangodb/aql/functions');
|
|
aqlfunctions.register('arangodb::GRAPH_EDGES', function (graphName, vertexExample, options) {
|
|
var gm = require('@arangodb/general-graph-common');
|
|
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-common');
|
|
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-common');
|
|
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-common');
|
|
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-common');
|
|
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-common');
|
|
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-common');
|
|
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-common');
|
|
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-common');
|
|
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-common');
|
|
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-common');
|
|
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-common');
|
|
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-common');
|
|
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-common');
|
|
var g = gm._graph(graphName);
|
|
return g._betweenness(options);
|
|
}, false);
|
|
aqlfunctions.register('arangodb::GRAPH_RADIUS', function (graphName, options) {
|
|
var gm = require('@arangodb/general-graph-common');
|
|
var g = gm._graph(graphName);
|
|
return g._radius(options);
|
|
}, false);
|
|
aqlfunctions.register('arangodb::GRAPH_DIAMETER', function (graphName, options) {
|
|
var gm = require('@arangodb/general-graph-common');
|
|
var g = gm._graph(graphName);
|
|
return g._diameter(options);
|
|
}, false);
|
|
};
|
|
|
|
exports.__GraphClass = Graph;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @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
|
|
// //////////////////////////////////////////////////////////////////////////////
|