mirror of https://gitee.com/bigwinds/arangodb
1722 lines
50 KiB
JavaScript
1722 lines
50 KiB
JavaScript
/* jshint strict: false, unused: true */
|
|
/* global ArangoClusterComm */
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief Traversal "classes"
|
|
// /
|
|
// / @file
|
|
// /
|
|
// / DISCLAIMER
|
|
// /
|
|
// / Copyright 2011-2013 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 Jan Steemann
|
|
// / @author Michael Hackstein
|
|
// / @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
var generalGraph = require('@arangodb/general-graph');
|
|
var arangodb = require('@arangodb');
|
|
var BinaryHeap = require('@arangodb/heap').BinaryHeap;
|
|
var ArangoError = arangodb.ArangoError;
|
|
|
|
var db = arangodb.db;
|
|
|
|
var ArangoTraverser;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief clone any object
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function clone (obj) {
|
|
if (obj === null || typeof obj !== 'object') {
|
|
return obj;
|
|
}
|
|
|
|
var copy;
|
|
if (Array.isArray(obj)) {
|
|
copy = [];
|
|
obj.forEach(function (i) {
|
|
copy.push(clone(i));
|
|
});
|
|
} else if (obj instanceof Object) {
|
|
copy = { };
|
|
Object.keys(obj).forEach(function (k) {
|
|
copy[k] = clone(obj[k]);
|
|
});
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief test if object is empty
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function isEmpty (obj) {
|
|
for (var key in obj) {
|
|
if (obj.hasOwnProperty(key)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief traversal abortion exception
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
var abortedException = function (message, options) {
|
|
'use strict';
|
|
this.message = message || 'traversal intentionally aborted by user';
|
|
this.options = options || { };
|
|
this._intentionallyAborted = true;
|
|
};
|
|
|
|
abortedException.prototype = new Error();
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief default ArangoCollection datasource
|
|
// /
|
|
// / This is a factory function that creates a datasource that operates on the
|
|
// / specified edge collection. The vertices and edges are the documents in the
|
|
// / corresponding collections.
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function collectionDatasourceFactory (edgeCollection) {
|
|
var c = edgeCollection;
|
|
if (typeof c === 'string') {
|
|
c = db._collection(c);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
return {
|
|
edgeCollection: c,
|
|
useBuiltIn: useBuiltIn,
|
|
|
|
getVertexId: function (vertex) {
|
|
return vertex._id;
|
|
},
|
|
|
|
getPeerVertex: function (edge, vertex) {
|
|
if (edge._from === vertex._id) {
|
|
return db._document(edge._to);
|
|
}
|
|
|
|
if (edge._to === vertex._id) {
|
|
return db._document(edge._from);
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
getInVertex: function (edge) {
|
|
return db._document(edge._to);
|
|
},
|
|
|
|
getOutVertex: function (edge) {
|
|
return db._document(edge._from);
|
|
},
|
|
|
|
getEdgeId: function (edge) {
|
|
return edge._id;
|
|
},
|
|
|
|
getEdgeFrom: function (edge) {
|
|
return edge._from;
|
|
},
|
|
|
|
getEdgeTo: function (edge) {
|
|
return edge._to;
|
|
},
|
|
|
|
getLabel: function (edge) {
|
|
return edge.$label;
|
|
},
|
|
|
|
getAllEdges: function (vertex) {
|
|
if (this.useBuiltIn) {
|
|
return this.edgeCollection.EDGES(vertex._id);
|
|
}
|
|
return this.edgeCollection.edges(vertex._id);
|
|
},
|
|
|
|
getInEdges: function (vertex) {
|
|
if (this.useBuiltIn) {
|
|
return this.edgeCollection.INEDGES(vertex._id);
|
|
}
|
|
return this.edgeCollection.inEdges(vertex._id);
|
|
},
|
|
|
|
getOutEdges: function (vertex) {
|
|
if (this.useBuiltIn) {
|
|
return this.edgeCollection.OUTEDGES(vertex._id);
|
|
}
|
|
return this.edgeCollection.outEdges(vertex._id);
|
|
}
|
|
};
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief general graph datasource
|
|
// /
|
|
// / This is a factory function that creates a datasource that operates on the
|
|
// / specified general graph. The vertices and edges are delivered by the
|
|
// / the general-graph module.
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function generalGraphDatasourceFactory (graph) {
|
|
var g = graph;
|
|
if (typeof g === 'string') {
|
|
g = generalGraph._graph(g);
|
|
}
|
|
|
|
return {
|
|
graph: g,
|
|
|
|
getVertexId: function (vertex) {
|
|
return vertex._id;
|
|
},
|
|
|
|
getPeerVertex: function (edge, vertex) {
|
|
if (edge._from === vertex._id) {
|
|
return db._document(edge._to);
|
|
}
|
|
|
|
if (edge._to === vertex._id) {
|
|
return db._document(edge._from);
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
getInVertex: function (edge) {
|
|
return db._document(edge._to);
|
|
},
|
|
|
|
getOutVertex: function (edge) {
|
|
return db._document(edge._from);
|
|
},
|
|
|
|
getEdgeId: function (edge) {
|
|
return edge._id;
|
|
},
|
|
|
|
getEdgeFrom: function (edge) {
|
|
return edge._from;
|
|
},
|
|
|
|
getEdgeTo: function (edge) {
|
|
return edge._to;
|
|
},
|
|
|
|
getLabel: function (edge) {
|
|
return edge.$label;
|
|
},
|
|
|
|
getAllEdges: function (vertex) {
|
|
return this.graph._EDGES(vertex._id);
|
|
},
|
|
|
|
getInEdges: function (vertex) {
|
|
return this.graph._INEDGES(vertex._id);
|
|
},
|
|
|
|
getOutEdges: function (vertex) {
|
|
return this.graph._OUTEDGES(vertex._id);
|
|
}
|
|
};
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief default outbound expander function
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function outboundExpander (config, vertex, path) {
|
|
var datasource = config.datasource;
|
|
var connections = [];
|
|
var outEdges = datasource.getOutEdges(vertex);
|
|
var edgeIterator;
|
|
|
|
if (outEdges.length > 1 && config.sort) {
|
|
outEdges.sort(config.sort);
|
|
}
|
|
|
|
if (config.buildVertices) {
|
|
if (!config.expandFilter) {
|
|
edgeIterator = function (edge) {
|
|
try {
|
|
var v = datasource.getInVertex(edge);
|
|
connections.push({ edge: edge, vertex: v });
|
|
} catch (e) {
|
|
// continue even in the face of non-existing documents
|
|
}
|
|
};
|
|
} else {
|
|
edgeIterator = function (edge) {
|
|
try {
|
|
var v = datasource.getInVertex(edge);
|
|
if (config.expandFilter(config, v, edge, path)) {
|
|
connections.push({ edge: edge, vertex: v });
|
|
}
|
|
} catch (e) {
|
|
// continue even in the face of non-existing documents
|
|
}
|
|
};
|
|
}
|
|
} else {
|
|
if (!config.expandFilter) {
|
|
edgeIterator = function (edge) {
|
|
var id = datasource.getEdgeTo(edge);
|
|
var v = { _id: id, _key: id.substr(id.indexOf('/') + 1)};
|
|
connections.push({ edge: edge, vertex: v });
|
|
};
|
|
} else {
|
|
edgeIterator = function (edge) {
|
|
var id = datasource.getEdgeTo(edge);
|
|
var v = { _id: id, _key: id.substr(id.indexOf('/') + 1)};
|
|
if (config.expandFilter(config, v, edge, path)) {
|
|
connections.push({ edge: edge, vertex: v });
|
|
}
|
|
};
|
|
}
|
|
}
|
|
outEdges.forEach(edgeIterator);
|
|
return connections;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief default inbound expander function
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function inboundExpander (config, vertex, path) {
|
|
var datasource = config.datasource;
|
|
var connections = [];
|
|
|
|
var inEdges = datasource.getInEdges(vertex);
|
|
|
|
if (inEdges.length > 1 && config.sort) {
|
|
inEdges.sort(config.sort);
|
|
}
|
|
var edgeIterator;
|
|
|
|
if (config.buildVertices) {
|
|
if (!config.expandFilter) {
|
|
edgeIterator = function (edge) {
|
|
try {
|
|
var v = datasource.getOutVertex(edge);
|
|
connections.push({ edge: edge, vertex: v });
|
|
} catch (e) {
|
|
// continue even in the face of non-existing documents
|
|
}
|
|
};
|
|
} else {
|
|
edgeIterator = function (edge) {
|
|
try {
|
|
var v = datasource.getOutVertex(edge);
|
|
if (config.expandFilter(config, v, edge, path)) {
|
|
connections.push({ edge: edge, vertex: v });
|
|
}
|
|
} catch (e) {
|
|
// continue even in the face of non-existing documents
|
|
}
|
|
};
|
|
}
|
|
} else {
|
|
if (!config.expandFilter) {
|
|
edgeIterator = function (edge) {
|
|
var id = datasource.getEdgeFrom(edge);
|
|
var v = { _id: id, _key: id.substr(id.indexOf('/') + 1)};
|
|
connections.push({ edge: edge, vertex: v });
|
|
};
|
|
} else {
|
|
edgeIterator = function (edge) {
|
|
var id = datasource.getEdgeFrom(edge);
|
|
var v = { _id: id, _key: id.substr(id.indexOf('/') + 1)};
|
|
if (config.expandFilter(config, v, edge, path)) {
|
|
connections.push({ edge: edge, vertex: v });
|
|
}
|
|
};
|
|
}
|
|
}
|
|
inEdges.forEach(edgeIterator);
|
|
|
|
return connections;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief default "any" expander function
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function anyExpander (config, vertex, path) {
|
|
var datasource = config.datasource;
|
|
var connections = [];
|
|
var edges = datasource.getAllEdges(vertex);
|
|
|
|
if (edges.length > 1 && config.sort) {
|
|
edges.sort(config.sort);
|
|
}
|
|
|
|
var edgeIterator;
|
|
if (config.buildVertices) {
|
|
if (!config.expandFilter) {
|
|
edgeIterator = function (edge) {
|
|
try {
|
|
var v = datasource.getPeerVertex(edge, vertex);
|
|
connections.push({ edge: edge, vertex: v });
|
|
} catch (e) {
|
|
// continue even in the face of non-existing documents
|
|
}
|
|
};
|
|
} else {
|
|
edgeIterator = function (edge) {
|
|
try {
|
|
var v = datasource.getPeerVertex(edge, vertex);
|
|
if (config.expandFilter(config, v, edge, path)) {
|
|
connections.push({ edge: edge, vertex: v });
|
|
}
|
|
} catch (e) {
|
|
// continue even in the face of non-existing documents
|
|
}
|
|
};
|
|
}
|
|
} else {
|
|
if (!config.expandFilter) {
|
|
edgeIterator = function (edge) {
|
|
var id = datasource.getEdgeFrom(edge);
|
|
if (id === vertex._id) {
|
|
id = datasource.getEdgeTo(edge);
|
|
}
|
|
var v = { _id: id, _key: id.substr(id.indexOf('/') + 1)};
|
|
connections.push({ edge: edge, vertex: v });
|
|
};
|
|
} else {
|
|
edgeIterator = function (edge) {
|
|
var id = datasource.getEdgeFrom(edge);
|
|
if (id === vertex._id) {
|
|
id = datasource.getEdgeTo(edge);
|
|
}
|
|
var v = { _id: id, _key: id.substr(id.indexOf('/') + 1)};
|
|
if (config.expandFilter(config, v, edge, path)) {
|
|
connections.push({ edge: edge, vertex: v });
|
|
}
|
|
};
|
|
}
|
|
}
|
|
edges.forEach(edgeIterator);
|
|
return connections;
|
|
}
|
|
|
|
// /////////////////////////////////////////////////////////////////////////////////////////
|
|
// / @brief expands all outbound edges labeled with at least one label in config.labels
|
|
// /////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function expandOutEdgesWithLabels (config, vertex) {
|
|
var datasource = config.datasource;
|
|
var result = [];
|
|
var i;
|
|
|
|
if (!Array.isArray(config.labels)) {
|
|
config.labels = [config.labels];
|
|
}
|
|
|
|
var edgesList = datasource.getOutEdges(vertex);
|
|
|
|
if (edgesList !== undefined) {
|
|
for (i = 0; i < edgesList.length; ++i) {
|
|
var edge = edgesList[i];
|
|
var label = datasource.getLabel(edge);
|
|
|
|
if (config.labels.indexOf(label) >= 0) {
|
|
result.push({ edge: edge, vertex: datasource.getInVertex(edge) });
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// /////////////////////////////////////////////////////////////////////////////////////////
|
|
// / @brief expands all inbound edges labeled with at least one label in config.labels
|
|
// /////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function expandInEdgesWithLabels (config, vertex) {
|
|
var datasource = config.datasource;
|
|
var result = [];
|
|
var i;
|
|
|
|
if (!Array.isArray(config.labels)) {
|
|
config.labels = [config.labels];
|
|
}
|
|
|
|
var edgesList = config.datasource.getInEdges(vertex);
|
|
|
|
if (edgesList !== undefined) {
|
|
for (i = 0; i < edgesList.length; ++i) {
|
|
var edge = edgesList[i];
|
|
var label = datasource.getLabel(edge);
|
|
|
|
if (config.labels.indexOf(label) >= 0) {
|
|
result.push({ edge: edge, vertex: datasource.getOutVertex(edge) });
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// /////////////////////////////////////////////////////////////////////////////////////////
|
|
// / @brief expands all edges labeled with at least one label in config.labels
|
|
// /////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function expandEdgesWithLabels (config, vertex) {
|
|
var datasource = config.datasource;
|
|
var result = [];
|
|
var i;
|
|
|
|
if (!Array.isArray(config.labels)) {
|
|
config.labels = [config.labels];
|
|
}
|
|
|
|
var edgesList = config.datasource.getAllEdges(vertex);
|
|
|
|
if (edgesList !== undefined) {
|
|
for (i = 0; i < edgesList.length; ++i) {
|
|
var edge = edgesList[i];
|
|
var label = datasource.getLabel(edge);
|
|
|
|
if (config.labels.indexOf(label) >= 0) {
|
|
result.push({ edge: edge, vertex: datasource.getPeerVertex(edge, vertex) });
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief default visitor that just tracks every visit
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function trackingVisitor (config, result, vertex, path) {
|
|
if (!result || !result.visited) {
|
|
return;
|
|
}
|
|
|
|
if (result.visited.vertices) {
|
|
result.visited.vertices.push(clone(vertex));
|
|
}
|
|
|
|
if (result.visited.paths) {
|
|
result.visited.paths.push(clone(path));
|
|
}
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief a visitor that counts the number of nodes visited
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function countingVisitor (config, result) {
|
|
if (!result) {
|
|
return;
|
|
}
|
|
|
|
if (result.hasOwnProperty('count')) {
|
|
++result.count;
|
|
} else {
|
|
result.count = 1;
|
|
}
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief a visitor that does nothing - can be used to quickly traverse a
|
|
// / graph, e.g. for performance comparisons etc.
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function doNothingVisitor () {
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief default filter to visit & expand all vertices
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function visitAllFilter () {
|
|
return '';
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief filter to visit & expand all vertices up to a given depth
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function maxDepthFilter (config, vertex, path) {
|
|
if (path && path.vertices && path.vertices.length > config.maxDepth) {
|
|
return ArangoTraverser.PRUNE;
|
|
}
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief exclude all vertices up to a given depth
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function minDepthFilter (config, vertex, path) {
|
|
if (path && path.vertices && path.vertices.length <= config.minDepth) {
|
|
return ArangoTraverser.EXCLUDE;
|
|
}
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief include all vertices matching one of the given attribute sets
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function includeMatchingAttributesFilter (config, vertex) {
|
|
if (!Array.isArray(config.matchingAttributes)) {
|
|
config.matchingAttributes = [config.matchingAttributes];
|
|
}
|
|
|
|
var include = false;
|
|
|
|
config.matchingAttributes.forEach(function (example) {
|
|
var count = 0;
|
|
var keys = Object.keys(example);
|
|
|
|
keys.forEach(function (key) {
|
|
if (vertex[key] && vertex[key] === example[key]) {
|
|
count++;
|
|
}
|
|
});
|
|
|
|
if (count > 0 && count === keys.length) {
|
|
include = true;
|
|
}
|
|
});
|
|
|
|
var result;
|
|
|
|
if (!include) {
|
|
result = 'exclude';
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief combine an array of filters
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function combineFilters (filters, config, vertex, path) {
|
|
var result = [];
|
|
|
|
filters.forEach(function (f) {
|
|
var tmp = f(config, vertex, path);
|
|
|
|
if (!Array.isArray(tmp)) {
|
|
tmp = [ tmp ];
|
|
}
|
|
|
|
result = result.concat(tmp);
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief parse a filter result
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function parseFilterResult (args) {
|
|
var result = {
|
|
visit: true,
|
|
expand: true
|
|
};
|
|
|
|
function processArgument (arg) {
|
|
if (arg === undefined || arg === null) {
|
|
return;
|
|
}
|
|
|
|
var finish = false;
|
|
|
|
if (typeof (arg) === 'string') {
|
|
if (arg === ArangoTraverser.EXCLUDE) {
|
|
result.visit = false;
|
|
finish = true;
|
|
} else if (arg === ArangoTraverser.PRUNE) {
|
|
result.expand = false;
|
|
finish = true;
|
|
} else if (arg === '') {
|
|
finish = true;
|
|
}
|
|
} else if (Array.isArray(arg)) {
|
|
var i;
|
|
for (i = 0; i < arg.length; ++i) {
|
|
processArgument(arg[i]);
|
|
}
|
|
finish = true;
|
|
}
|
|
|
|
if (finish) {
|
|
return;
|
|
}
|
|
|
|
var err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_INVALID_FILTER_RESULT.code;
|
|
err.errorMessage = arangodb.errors.ERROR_GRAPH_INVALID_FILTER_RESULT.message;
|
|
throw err;
|
|
}
|
|
|
|
processArgument(args);
|
|
|
|
return result;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief apply the uniqueness checks
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function checkUniqueness (config, visited, vertex, edge) {
|
|
var uniqueness = config.uniqueness;
|
|
var datasource = config.datasource;
|
|
var id;
|
|
|
|
if (uniqueness.vertices !== ArangoTraverser.UNIQUE_NONE) {
|
|
id = datasource.getVertexId(vertex);
|
|
|
|
if (visited.vertices[id] === true) {
|
|
return false;
|
|
}
|
|
|
|
visited.vertices[id] = true;
|
|
}
|
|
|
|
if (edge !== null && uniqueness.edges !== ArangoTraverser.UNIQUE_NONE) {
|
|
id = datasource.getEdgeId(edge);
|
|
|
|
if (visited.edges[id] === true) {
|
|
return false;
|
|
}
|
|
|
|
visited.edges[id] = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief check if we must process items in reverse order
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function checkReverse (config) {
|
|
var result = false;
|
|
|
|
if (config.order === ArangoTraverser.POST_ORDER) {
|
|
// post order
|
|
if (config.itemOrder === ArangoTraverser.FORWARD) {
|
|
result = true;
|
|
}
|
|
} else if (config.order === ArangoTraverser.PRE_ORDER ||
|
|
config.order === ArangoTraverser.PRE_ORDER_EXPANDER) {
|
|
// pre order
|
|
if (config.itemOrder === ArangoTraverser.BACKWARD &&
|
|
config.strategy === ArangoTraverser.BREADTH_FIRST) {
|
|
result = true;
|
|
} else if (config.itemOrder === ArangoTraverser.FORWARD &&
|
|
config.strategy === ArangoTraverser.DEPTH_FIRST) {
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief implementation details for breadth-first strategy
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function breadthFirstSearch () {
|
|
return {
|
|
requiresEndVertex: function () {
|
|
return false;
|
|
},
|
|
|
|
getPathItems: function (id, items) {
|
|
var visited = { };
|
|
var ignore = items.length - 1;
|
|
|
|
items.forEach(function (item, i) {
|
|
if (i !== ignore) {
|
|
visited[id(item)] = true;
|
|
}
|
|
});
|
|
|
|
return visited;
|
|
},
|
|
|
|
createPath: function (items, idx) {
|
|
var path = { edges: [], vertices: [] };
|
|
var pathItem = items[idx];
|
|
|
|
while (true) {
|
|
if (pathItem.edge !== null) {
|
|
path.edges.unshift(pathItem.edge);
|
|
}
|
|
path.vertices.unshift(pathItem.vertex);
|
|
idx = pathItem.parentIndex;
|
|
if (idx < 0) {
|
|
break;
|
|
}
|
|
pathItem = items[idx];
|
|
}
|
|
|
|
return path;
|
|
},
|
|
|
|
run: function (config, result, startVertex) {
|
|
var maxIterations = config.maxIterations, visitCounter = 0;
|
|
var toVisit = [ { edge: null, vertex: startVertex, parentIndex: -1 } ];
|
|
var visited = { edges: { }, vertices: { } };
|
|
|
|
var index = 0;
|
|
var step = 1;
|
|
var reverse = checkReverse(config);
|
|
|
|
while ((step === 1 && index < toVisit.length) ||
|
|
(step === -1 && index >= 0)) {
|
|
var current = toVisit[index];
|
|
var vertex = current.vertex;
|
|
var edge = current.edge;
|
|
var path;
|
|
|
|
if (visitCounter++ > maxIterations) {
|
|
var err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.code;
|
|
err.errorMessage = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.message;
|
|
throw err;
|
|
}
|
|
|
|
if (current.visit === null || current.visit === undefined) {
|
|
current.visit = false;
|
|
|
|
path = this.createPath(toVisit, index);
|
|
|
|
// first apply uniqueness check
|
|
if (config.uniqueness.vertices === ArangoTraverser.UNIQUE_PATH) {
|
|
visited.vertices = this.getPathItems(config.datasource.getVertexId, path.vertices);
|
|
}
|
|
if (config.uniqueness.edges === ArangoTraverser.UNIQUE_PATH) {
|
|
visited.edges = this.getPathItems(config.datasource.getEdgeId, path.edges);
|
|
}
|
|
|
|
if (!checkUniqueness(config, visited, vertex, edge)) {
|
|
if (index < toVisit.length - 1) {
|
|
index += step;
|
|
} else {
|
|
step = -1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
var filterResult = parseFilterResult(config.filter(config, vertex, path));
|
|
if (config.order === ArangoTraverser.PRE_ORDER && filterResult.visit) {
|
|
// preorder
|
|
config.visitor(config, result, vertex, path);
|
|
} else {
|
|
// postorder
|
|
current.visit = filterResult.visit || false;
|
|
}
|
|
|
|
if (filterResult.expand) {
|
|
var connected = config.expander(config, vertex, path), i;
|
|
|
|
if (reverse) {
|
|
connected.reverse();
|
|
}
|
|
|
|
if (config.order === ArangoTraverser.PRE_ORDER_EXPANDER && filterResult.visit) {
|
|
config.visitor(config, result, vertex, path, connected);
|
|
}
|
|
|
|
for (i = 0; i < connected.length; ++i) {
|
|
connected[i].parentIndex = index;
|
|
toVisit.push(connected[i]);
|
|
}
|
|
} else if (config.order === ArangoTraverser.PRE_ORDER_EXPANDER && filterResult.visit) {
|
|
config.visitor(config, result, vertex, path, []);
|
|
}
|
|
|
|
if (config.order === ArangoTraverser.POST_ORDER) {
|
|
if (index < toVisit.length - 1) {
|
|
index += step;
|
|
} else {
|
|
step = -1;
|
|
}
|
|
}
|
|
} else {
|
|
if (config.order === ArangoTraverser.POST_ORDER && current.visit) {
|
|
path = this.createPath(toVisit, index);
|
|
config.visitor(config, result, vertex, path);
|
|
}
|
|
index += step;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief implementation details for depth-first strategy
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function depthFirstSearch () {
|
|
return {
|
|
requiresEndVertex: function () {
|
|
return false;
|
|
},
|
|
|
|
getPathItems: function (id, items) {
|
|
var visited = { };
|
|
items.forEach(function (item) {
|
|
visited[id(item)] = true;
|
|
});
|
|
return visited;
|
|
},
|
|
|
|
run: function (config, result, startVertex) {
|
|
var maxIterations = config.maxIterations, visitCounter = 0;
|
|
var toVisit = [ { edge: null, vertex: startVertex, visit: null } ];
|
|
var path = { edges: [], vertices: [] };
|
|
var visited = { edges: { }, vertices: { } };
|
|
var reverse = checkReverse(config);
|
|
var uniqueness = config.uniqueness;
|
|
var haveUniqueness = ((uniqueness.vertices !== ArangoTraverser.UNIQUE_NONE) ||
|
|
(uniqueness.edges !== ArangoTraverser.UNIQUE_NONE));
|
|
|
|
while (toVisit.length > 0) {
|
|
if (visitCounter++ > maxIterations) {
|
|
var err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.code;
|
|
err.errorMessage = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.message;
|
|
throw err;
|
|
}
|
|
|
|
// peek at the top of the stack
|
|
var current = toVisit[toVisit.length - 1];
|
|
var vertex = current.vertex;
|
|
var edge = current.edge;
|
|
|
|
// check if we visit the element for the first time
|
|
if (current.visit === null || current.visit === undefined) {
|
|
current.visit = false;
|
|
|
|
if (haveUniqueness) {
|
|
// first apply uniqueness check
|
|
if (uniqueness.vertices === ArangoTraverser.UNIQUE_PATH) {
|
|
visited.vertices = this.getPathItems(config.datasource.getVertexId, path.vertices);
|
|
}
|
|
|
|
if (uniqueness.edges === ArangoTraverser.UNIQUE_PATH) {
|
|
visited.edges = this.getPathItems(config.datasource.getEdgeId, path.edges);
|
|
}
|
|
|
|
if (!checkUniqueness(config, visited, vertex, edge)) {
|
|
// skip element if not unique
|
|
toVisit.pop();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// push the current element onto the path stack
|
|
if (edge !== null) {
|
|
path.edges.push(edge);
|
|
}
|
|
|
|
path.vertices.push(vertex);
|
|
|
|
var filterResult = parseFilterResult(config.filter(config, vertex, path));
|
|
if (config.order === ArangoTraverser.PRE_ORDER && filterResult.visit) {
|
|
// preorder visit
|
|
config.visitor(config, result, vertex, path);
|
|
} else {
|
|
// postorder. mark the element visitation flag because we'll got to check it later
|
|
current.visit = filterResult.visit || false;
|
|
}
|
|
|
|
// expand the element's children?
|
|
if (filterResult.expand) {
|
|
var connected = config.expander(config, vertex, path), i;
|
|
|
|
if (reverse) {
|
|
connected.reverse();
|
|
}
|
|
|
|
if (config.order === ArangoTraverser.PRE_ORDER_EXPANDER && filterResult.visit) {
|
|
config.visitor(config, result, vertex, path, connected);
|
|
}
|
|
|
|
for (i = 0; i < connected.length; ++i) {
|
|
connected[i].visit = null;
|
|
toVisit.push(connected[i]);
|
|
}
|
|
} else if (config.order === ArangoTraverser.PRE_ORDER_EXPANDER && filterResult.visit) {
|
|
config.visitor(config, result, vertex, path, []);
|
|
}
|
|
} else {
|
|
// we have already seen this element
|
|
if (config.order === ArangoTraverser.POST_ORDER && current.visit) {
|
|
// postorder visitation
|
|
config.visitor(config, result, vertex, path);
|
|
}
|
|
|
|
// pop the element from the stack
|
|
toVisit.pop();
|
|
if (path.edges.length > 0) {
|
|
path.edges.pop();
|
|
}
|
|
path.vertices.pop();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief implementation details for dijkstra shortest path strategy
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function dijkstraSearch () {
|
|
return {
|
|
nodes: { },
|
|
|
|
requiresEndVertex: function () {
|
|
return true;
|
|
},
|
|
|
|
makeNode: function (vertex) {
|
|
var id = vertex._id;
|
|
if (!this.nodes.hasOwnProperty(id)) {
|
|
this.nodes[id] = { vertex: vertex, dist: Infinity };
|
|
}
|
|
|
|
return this.nodes[id];
|
|
},
|
|
|
|
vertexList: function (vertex) {
|
|
var result = [];
|
|
while (vertex) {
|
|
result.push(vertex);
|
|
vertex = vertex.parent;
|
|
}
|
|
return result;
|
|
},
|
|
|
|
buildPath: function (vertex) {
|
|
var path = { vertices: [ vertex.vertex ], edges: [] };
|
|
var v = vertex;
|
|
|
|
while (v.parent) {
|
|
path.vertices.unshift(v.parent.vertex);
|
|
path.edges.unshift(v.parentEdge);
|
|
v = v.parent;
|
|
}
|
|
return path;
|
|
},
|
|
|
|
run: function (config, result, startVertex, endVertex) {
|
|
var maxIterations = config.maxIterations, visitCounter = 0;
|
|
|
|
var heap = new BinaryHeap(function (node) {
|
|
return node.dist;
|
|
});
|
|
|
|
var startNode = this.makeNode(startVertex);
|
|
startNode.dist = 0;
|
|
heap.push(startNode);
|
|
|
|
while (heap.size() > 0) {
|
|
if (visitCounter++ > maxIterations) {
|
|
var err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.code;
|
|
err.errorMessage = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.message;
|
|
throw err;
|
|
}
|
|
|
|
var currentNode = heap.pop();
|
|
var i, n;
|
|
|
|
if (currentNode.vertex._id === endVertex._id) {
|
|
var vertices = this.vertexList(currentNode).reverse();
|
|
|
|
n = vertices.length;
|
|
for (i = 0; i < n; ++i) {
|
|
if (!vertices[i].hide) {
|
|
config.visitor(config, result, vertices[i].vertex, this.buildPath(vertices[i]));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (currentNode.visited) {
|
|
continue;
|
|
}
|
|
|
|
if (currentNode.dist === Infinity) {
|
|
break;
|
|
}
|
|
|
|
currentNode.visited = true;
|
|
|
|
var path = this.buildPath(currentNode);
|
|
var filterResult = parseFilterResult(config.filter(config, currentNode.vertex, path));
|
|
|
|
if (!filterResult.visit) {
|
|
currentNode.hide = true;
|
|
}
|
|
|
|
if (!filterResult.expand) {
|
|
continue;
|
|
}
|
|
|
|
var dist = currentNode.dist;
|
|
var connected = config.expander(config, currentNode.vertex, path);
|
|
n = connected.length;
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
var neighbor = this.makeNode(connected[i].vertex);
|
|
|
|
if (neighbor.visited) {
|
|
continue;
|
|
}
|
|
|
|
var edge = connected[i].edge;
|
|
var weight = 1;
|
|
if (config.distance) {
|
|
weight = config.distance(config, currentNode.vertex, neighbor.vertex, edge);
|
|
} else if (config.weight) {
|
|
if (typeof edge[config.weight] === 'number') {
|
|
weight = edge[config.weight];
|
|
} else if (config.defaultWeight) {
|
|
weight = config.defaultWeight;
|
|
} else {
|
|
weight = Infinity;
|
|
}
|
|
}
|
|
|
|
var alt = dist + weight;
|
|
if (alt < neighbor.dist) {
|
|
neighbor.dist = alt;
|
|
neighbor.parent = currentNode;
|
|
neighbor.parentEdge = edge;
|
|
heap.push(neighbor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
function dijkstraSearchMulti () {
|
|
return {
|
|
nodes: { },
|
|
|
|
requiresEndVertex: function () {
|
|
return true;
|
|
},
|
|
|
|
makeNode: function (vertex) {
|
|
var id = vertex._id;
|
|
if (!this.nodes.hasOwnProperty(id)) {
|
|
this.nodes[id] = { vertex: vertex, dist: Infinity };
|
|
}
|
|
|
|
return this.nodes[id];
|
|
},
|
|
|
|
vertexList: function (vertex) {
|
|
var result = [];
|
|
while (vertex) {
|
|
result.push(vertex);
|
|
vertex = vertex.parent;
|
|
}
|
|
return result;
|
|
},
|
|
|
|
buildPath: function (vertex) {
|
|
var path = { vertices: [ vertex.vertex ], edges: [] };
|
|
var v = vertex;
|
|
|
|
while (v.parent) {
|
|
path.vertices.unshift(v.parent.vertex);
|
|
path.edges.unshift(v.parentEdge);
|
|
v = v.parent;
|
|
}
|
|
return path;
|
|
},
|
|
|
|
run: function (config, result, startVertex, endVertex) {
|
|
var maxIterations = config.maxIterations, visitCounter = 0;
|
|
|
|
var heap = new BinaryHeap(function (node) {
|
|
return node.dist;
|
|
});
|
|
|
|
var startNode = this.makeNode(startVertex);
|
|
startNode.dist = 0;
|
|
heap.push(startNode);
|
|
|
|
while (heap.size() > 0) {
|
|
if (visitCounter++ > maxIterations) {
|
|
var err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.code;
|
|
err.errorMessage = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.message;
|
|
throw err;
|
|
}
|
|
|
|
var currentNode = heap.pop();
|
|
var i, n;
|
|
|
|
if (endVertex.hasOwnProperty(currentNode.vertex._id)) {
|
|
delete endVertex[currentNode.vertex._id];
|
|
config.visitor(config, result, currentNode, this.buildPath(currentNode));
|
|
if (isEmpty(endVertex)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (currentNode.visited) {
|
|
continue;
|
|
}
|
|
|
|
if (currentNode.dist === Infinity) {
|
|
break;
|
|
}
|
|
|
|
currentNode.visited = true;
|
|
|
|
var path = this.buildPath(currentNode);
|
|
var filterResult = parseFilterResult(config.filter(config, currentNode.vertex, path));
|
|
|
|
if (!filterResult.visit) {
|
|
currentNode.hide = true;
|
|
}
|
|
|
|
if (!filterResult.expand) {
|
|
continue;
|
|
}
|
|
|
|
var dist = currentNode.dist;
|
|
var connected = config.expander(config, currentNode.vertex, path);
|
|
n = connected.length;
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
var neighbor = this.makeNode(connected[i].vertex);
|
|
|
|
if (neighbor.visited) {
|
|
continue;
|
|
}
|
|
|
|
var edge = connected[i].edge;
|
|
var weight = 1;
|
|
if (config.distance) {
|
|
weight = config.distance(config, currentNode.vertex, neighbor.vertex, edge);
|
|
} else if (config.weight) {
|
|
if (typeof edge[config.weight] === 'number') {
|
|
weight = edge[config.weight];
|
|
} else if (config.defaultWeight) {
|
|
weight = config.defaultWeight;
|
|
} else {
|
|
weight = Infinity;
|
|
}
|
|
}
|
|
|
|
var alt = dist + weight;
|
|
if (alt < neighbor.dist) {
|
|
neighbor.dist = alt;
|
|
neighbor.parent = currentNode;
|
|
neighbor.parentEdge = edge;
|
|
heap.push(neighbor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief implementation details for a* shortest path strategy
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function astarSearch () {
|
|
return {
|
|
nodes: { },
|
|
|
|
requiresEndVertex: function () {
|
|
return true;
|
|
},
|
|
|
|
makeNode: function (vertex) {
|
|
var id = vertex._id;
|
|
if (!this.nodes.hasOwnProperty(id)) {
|
|
this.nodes[id] = { vertex: vertex, f: 0, g: 0, h: 0 };
|
|
}
|
|
|
|
return this.nodes[id];
|
|
},
|
|
|
|
vertexList: function (vertex) {
|
|
var result = [];
|
|
while (vertex) {
|
|
result.push(vertex);
|
|
vertex = vertex.parent;
|
|
}
|
|
return result;
|
|
},
|
|
|
|
buildPath: function (vertex) {
|
|
var path = { vertices: [ vertex.vertex ], edges: [] };
|
|
var v = vertex;
|
|
|
|
while (v.parent) {
|
|
path.vertices.unshift(v.parent.vertex);
|
|
path.edges.unshift(v.parentEdge);
|
|
v = v.parent;
|
|
}
|
|
return path;
|
|
},
|
|
|
|
run: function (config, result, startVertex, endVertex) {
|
|
var maxIterations = config.maxIterations, visitCounter = 0;
|
|
|
|
var heap = new BinaryHeap(function (node) {
|
|
return node.f;
|
|
});
|
|
|
|
heap.push(this.makeNode(startVertex));
|
|
|
|
while (heap.size() > 0) {
|
|
if (visitCounter++ > maxIterations) {
|
|
var err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.code;
|
|
err.errorMessage = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.message;
|
|
throw err;
|
|
}
|
|
|
|
var currentNode = heap.pop();
|
|
var i, n;
|
|
|
|
if (currentNode.vertex._id === endVertex._id) {
|
|
var vertices = this.vertexList(currentNode);
|
|
if (config.order !== ArangoTraverser.PRE_ORDER) {
|
|
vertices.reverse();
|
|
}
|
|
|
|
n = vertices.length;
|
|
for (i = 0; i < n; ++i) {
|
|
config.visitor(config, result, vertices[i].vertex, this.buildPath(vertices[i]));
|
|
}
|
|
return;
|
|
}
|
|
|
|
currentNode.closed = true;
|
|
|
|
var path = this.buildPath(currentNode);
|
|
var connected = config.expander(config, currentNode.vertex, path);
|
|
n = connected.length;
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
var neighbor = this.makeNode(connected[i].vertex);
|
|
|
|
if (neighbor.closed) {
|
|
continue;
|
|
}
|
|
|
|
var gScore = currentNode.g + 1; // + neighbor.cost
|
|
var beenVisited = neighbor.visited;
|
|
|
|
if (!beenVisited || gScore < neighbor.g) {
|
|
var edge = connected[i].edge;
|
|
neighbor.visited = true;
|
|
neighbor.parent = currentNode;
|
|
neighbor.parentEdge = edge;
|
|
neighbor.h = 1;
|
|
if (config.distance && !neighbor.h) {
|
|
neighbor.h = config.distance(config, neighbor.vertex, endVertex, edge);
|
|
}
|
|
neighbor.g = gScore;
|
|
neighbor.f = neighbor.g + neighbor.h;
|
|
|
|
if (!beenVisited) {
|
|
heap.push(neighbor);
|
|
} else {
|
|
heap.rescoreElement(neighbor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief traversal constructor
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser = function (config) {
|
|
var defaults = {
|
|
order: ArangoTraverser.PRE_ORDER,
|
|
itemOrder: ArangoTraverser.FORWARD,
|
|
strategy: ArangoTraverser.DEPTH_FIRST,
|
|
uniqueness: {
|
|
vertices: ArangoTraverser.UNIQUE_NONE,
|
|
edges: ArangoTraverser.UNIQUE_PATH
|
|
},
|
|
visitor: trackingVisitor,
|
|
filter: null,
|
|
expander: outboundExpander,
|
|
datasource: null,
|
|
maxIterations: 10000000,
|
|
minDepth: 0,
|
|
maxDepth: 256,
|
|
buildVertices: true
|
|
}, d;
|
|
|
|
var err;
|
|
|
|
if (typeof config !== 'object') {
|
|
err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
|
|
err.errorMessage = arangodb.errors.ERROR_BAD_PARAMETER.message;
|
|
throw err;
|
|
}
|
|
|
|
// apply defaults
|
|
for (d in defaults) {
|
|
if (defaults.hasOwnProperty(d)) {
|
|
if (!config.hasOwnProperty(d) || config[d] === undefined) {
|
|
config[d] = defaults[d];
|
|
}
|
|
}
|
|
}
|
|
|
|
function validate (value, map, param) {
|
|
var m;
|
|
|
|
if (value === null || value === undefined) {
|
|
// use first key from map
|
|
for (m in map) {
|
|
if (map.hasOwnProperty(m)) {
|
|
value = m;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (typeof value === 'string') {
|
|
value = value.toLowerCase().replace(/-/, '');
|
|
if (map[value] !== null && map[value] !== undefined) {
|
|
return map[value];
|
|
}
|
|
}
|
|
for (m in map) {
|
|
if (map.hasOwnProperty(m)) {
|
|
if (map[m] === value) {
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
|
|
err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
|
|
err.errorMessage = 'invalid value for ' + param;
|
|
throw err;
|
|
}
|
|
|
|
config.uniqueness = {
|
|
vertices: validate(config.uniqueness && config.uniqueness.vertices, {
|
|
none: ArangoTraverser.UNIQUE_NONE,
|
|
global: ArangoTraverser.UNIQUE_GLOBAL,
|
|
path: ArangoTraverser.UNIQUE_PATH
|
|
}, 'uniqueness.vertices'),
|
|
edges: validate(config.uniqueness && config.uniqueness.edges, {
|
|
path: ArangoTraverser.UNIQUE_PATH,
|
|
none: ArangoTraverser.UNIQUE_NONE,
|
|
global: ArangoTraverser.UNIQUE_GLOBAL
|
|
}, 'uniqueness.edges')
|
|
};
|
|
|
|
config.strategy = validate(config.strategy, {
|
|
depthfirst: ArangoTraverser.DEPTH_FIRST,
|
|
breadthfirst: ArangoTraverser.BREADTH_FIRST,
|
|
astar: ArangoTraverser.ASTAR_SEARCH,
|
|
dijkstra: ArangoTraverser.DIJKSTRA_SEARCH,
|
|
dijkstramulti: ArangoTraverser.DIJKSTRA_SEARCH_MULTI
|
|
}, 'strategy');
|
|
|
|
config.order = validate(config.order, {
|
|
preorder: ArangoTraverser.PRE_ORDER,
|
|
postorder: ArangoTraverser.POST_ORDER,
|
|
preorderexpander: ArangoTraverser.PRE_ORDER_EXPANDER
|
|
}, 'order');
|
|
|
|
config.itemOrder = validate(config.itemOrder, {
|
|
forward: ArangoTraverser.FORWARD,
|
|
backward: ArangoTraverser.BACKWARD
|
|
}, 'itemOrder');
|
|
|
|
if (typeof config.visitor !== 'function') {
|
|
err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
|
|
err.errorMessage = 'invalid visitor function';
|
|
throw err;
|
|
}
|
|
|
|
// prepare an array of filters
|
|
var filters = [];
|
|
if (config.minDepth !== undefined &&
|
|
config.minDepth !== null &&
|
|
config.minDepth > 0) {
|
|
filters.push(minDepthFilter);
|
|
}
|
|
if (config.maxDepth !== undefined &&
|
|
config.maxDepth !== null &&
|
|
config.maxDepth > 0) {
|
|
filters.push(maxDepthFilter);
|
|
}
|
|
|
|
if (!Array.isArray(config.filter)) {
|
|
if (typeof config.filter === 'function') {
|
|
config.filter = [ config.filter ];
|
|
} else {
|
|
config.filter = [];
|
|
}
|
|
}
|
|
|
|
config.filter.forEach(function (f) {
|
|
if (typeof f !== 'function') {
|
|
err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
|
|
err.errorMessage = 'invalid filter function';
|
|
throw err;
|
|
}
|
|
|
|
filters.push(f);
|
|
});
|
|
|
|
if (filters.length > 1) {
|
|
// more than one filter. combine their results
|
|
config.filter = function (config, vertex, path) {
|
|
return combineFilters(filters, config, vertex, path);
|
|
};
|
|
} else if (filters.length === 1) {
|
|
// exactly one filter
|
|
config.filter = filters[0];
|
|
} else {
|
|
config.filter = visitAllFilter;
|
|
}
|
|
|
|
if (typeof config.expander !== 'function') {
|
|
config.expander = validate(config.expander, {
|
|
outbound: outboundExpander,
|
|
inbound: inboundExpander,
|
|
any: anyExpander
|
|
}, 'expander');
|
|
}
|
|
|
|
if (typeof config.expander !== 'function') {
|
|
err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
|
|
err.errorMessage = 'invalid expander function';
|
|
throw err;
|
|
}
|
|
|
|
if (typeof config.datasource !== 'object') {
|
|
err = new ArangoError();
|
|
err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
|
|
err.errorMessage = 'invalid datasource';
|
|
throw err;
|
|
}
|
|
|
|
this.config = config;
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief execute the traversal
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.prototype.traverse = function (result, startVertex, endVertex) {
|
|
// get the traversal strategy
|
|
var strategy;
|
|
|
|
if (this.config.strategy === ArangoTraverser.ASTAR_SEARCH) {
|
|
strategy = astarSearch();
|
|
} else if (this.config.strategy === ArangoTraverser.DIJKSTRA_SEARCH) {
|
|
strategy = dijkstraSearch();
|
|
} else if (this.config.strategy === ArangoTraverser.DIJKSTRA_SEARCH_MULTI) {
|
|
strategy = dijkstraSearchMulti();
|
|
} else if (this.config.strategy === ArangoTraverser.BREADTH_FIRST) {
|
|
strategy = breadthFirstSearch();
|
|
} else {
|
|
strategy = depthFirstSearch();
|
|
}
|
|
|
|
// check the start vertex
|
|
if (startVertex === undefined ||
|
|
startVertex === null ||
|
|
typeof startVertex !== 'object') {
|
|
var err1 = new ArangoError();
|
|
err1.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
|
|
err1.errorMessage = arangodb.errors.ERROR_BAD_PARAMETER.message +
|
|
': invalid startVertex specified for traversal';
|
|
throw err1;
|
|
}
|
|
|
|
if (strategy.requiresEndVertex() &&
|
|
(endVertex === undefined ||
|
|
endVertex === null ||
|
|
typeof endVertex !== 'object')) {
|
|
var err2 = new ArangoError();
|
|
err2.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
|
|
err2.errorMessage = arangodb.errors.ERROR_BAD_PARAMETER.message +
|
|
': invalid endVertex specified for traversal';
|
|
throw err2;
|
|
}
|
|
|
|
// run the traversal
|
|
try {
|
|
strategy.run(this.config, result, startVertex, endVertex);
|
|
} catch (err3) {
|
|
if (typeof err3 !== 'object' || !err3._intentionallyAborted) {
|
|
throw err3;
|
|
}
|
|
}
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief every element can be revisited
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.UNIQUE_NONE = 0;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief element can only be revisited if not already in current path
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.UNIQUE_PATH = 1;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief element can only be revisited if not already visited
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.UNIQUE_GLOBAL = 2;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief visitation strategy breadth first
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.BREADTH_FIRST = 0;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief visitation strategy depth first
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.DEPTH_FIRST = 1;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief astar search
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.ASTAR_SEARCH = 2;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief dijkstra search
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.DIJKSTRA_SEARCH = 3;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief dijkstra search with multiple end vertices
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.DIJKSTRA_SEARCH_MULTI = 4;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief pre-order traversal, visitor called before expander
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.PRE_ORDER = 0;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief post-order traversal
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.POST_ORDER = 1;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief pre-order traversal, visitor called at expander
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.PRE_ORDER_EXPANDER = 2;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief forward item processing order
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.FORWARD = 0;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief backward item processing order
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.BACKWARD = 1;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief prune "constant"
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.PRUNE = 'prune';
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief exclude "constant"
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
ArangoTraverser.EXCLUDE = 'exclude';
|
|
|
|
exports.collectionDatasourceFactory = collectionDatasourceFactory;
|
|
exports.generalGraphDatasourceFactory = generalGraphDatasourceFactory;
|
|
|
|
exports.outboundExpander = outboundExpander;
|
|
exports.inboundExpander = inboundExpander;
|
|
exports.anyExpander = anyExpander;
|
|
exports.expandOutEdgesWithLabels = expandOutEdgesWithLabels;
|
|
exports.expandInEdgesWithLabels = expandInEdgesWithLabels;
|
|
exports.expandEdgesWithLabels = expandEdgesWithLabels;
|
|
|
|
exports.trackingVisitor = trackingVisitor;
|
|
exports.countingVisitor = countingVisitor;
|
|
exports.doNothingVisitor = doNothingVisitor;
|
|
|
|
exports.visitAllFilter = visitAllFilter;
|
|
exports.maxDepthFilter = maxDepthFilter;
|
|
exports.minDepthFilter = minDepthFilter;
|
|
exports.includeMatchingAttributesFilter = includeMatchingAttributesFilter;
|
|
exports.abortedException = abortedException;
|
|
|
|
exports.Traverser = ArangoTraverser;
|