mirror of https://gitee.com/bigwinds/arangodb
Merge branch 'devel' of github.com:triAGENS/ArangoDB into devel
This commit is contained in:
commit
a29f2f7370
|
@ -0,0 +1,8 @@
|
|||
TRAVERSE(friends, friendrelations, "friends/john", "outbound", {
|
||||
strategy: "depthfirst",
|
||||
order: "postorder",
|
||||
itemOrder: "backward",
|
||||
maxDepth: 6,
|
||||
trackPaths: true
|
||||
})
|
||||
|
|
@ -327,6 +327,49 @@ static bool EqualName (TRI_associative_pointer_t* array,
|
|||
/// @{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief check if we have a matching restriction we can use to optimise
|
||||
/// a PATHS query
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static bool CheckPathRestriction (TRI_aql_field_access_t* fieldAccess,
|
||||
TRI_aql_context_t* const context,
|
||||
TRI_aql_node_t* vertexCollection,
|
||||
const char* lookFor,
|
||||
char* name,
|
||||
const size_t n) {
|
||||
size_t len;
|
||||
|
||||
assert(fieldAccess);
|
||||
assert(lookFor);
|
||||
|
||||
len = strlen(lookFor);
|
||||
if (len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (n > fieldAccess->_variableNameLength + len &&
|
||||
memcmp((void*) lookFor, (void*) name, len) == 0) {
|
||||
// we'll now patch the collection hint
|
||||
TRI_aql_collection_hint_t* hint;
|
||||
|
||||
// field name is collection.source.XXX, e.g. users.source._id
|
||||
LOG_DEBUG("optimising PATHS() field access %s", fieldAccess->_fullName);
|
||||
|
||||
// we can now modify this fieldaccess in place to collection.XXX, e.g. users._id
|
||||
// copy trailing \0 byte as well
|
||||
memmove(name, name + len - 1, n - fieldAccess->_variableNameLength - len + 2);
|
||||
|
||||
// attach the modified fieldaccess to the collection
|
||||
hint = (TRI_aql_collection_hint_t*) (TRI_AQL_NODE_DATA(vertexCollection));
|
||||
hint->_ranges = TRI_AddAccessAql(context, hint->_ranges, fieldAccess);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief optimise callback function for PATHS() AQL function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -340,8 +383,6 @@ static void OptimisePaths (const TRI_aql_node_t* const fcallNode,
|
|||
TRI_aql_node_t* direction;
|
||||
char* directionValue;
|
||||
char* name;
|
||||
const char* lookFor;
|
||||
size_t len;
|
||||
size_t n;
|
||||
|
||||
args = TRI_AQL_NODE_MEMBER(fcallNode, 0);
|
||||
|
@ -366,37 +407,17 @@ static void OptimisePaths (const TRI_aql_node_t* const fcallNode,
|
|||
|
||||
// try to optimise the vertex collection access
|
||||
if (TRI_EqualString(directionValue, "outbound")) {
|
||||
lookFor = ".source.";
|
||||
len = strlen(lookFor);
|
||||
CheckPathRestriction(fieldAccess, context, vertexCollection, ".source.", name, n);
|
||||
}
|
||||
else if (TRI_EqualString(directionValue, "inbound")) {
|
||||
lookFor = ".destination.";
|
||||
len = strlen(lookFor);
|
||||
CheckPathRestriction(fieldAccess, context, vertexCollection, ".destination.", name, n);
|
||||
}
|
||||
else {
|
||||
// "any" will not be optimised
|
||||
lookFor = NULL;
|
||||
len = 0;
|
||||
}
|
||||
|
||||
if (len > 0 &&
|
||||
n > fieldAccess->_variableNameLength + len &&
|
||||
memcmp((void*) lookFor, (void*) name, len) == 0) {
|
||||
// we'll now patch the collection hint
|
||||
TRI_aql_collection_hint_t* hint;
|
||||
|
||||
// field name is collection.source.XXX, e.g. users.source._id
|
||||
LOG_DEBUG("optimising PATHS() field access %s", fieldAccess->_fullName);
|
||||
|
||||
// we can now modify this fieldaccess in place to collection.XXX, e.g. users._id
|
||||
// copy trailing \0 byte as well
|
||||
memmove(name, name + len - 1, n - fieldAccess->_variableNameLength - len + 2);
|
||||
|
||||
// attach the modified fieldaccess to the collection
|
||||
hint = (TRI_aql_collection_hint_t*) (TRI_AQL_NODE_DATA(vertexCollection));
|
||||
hint->_ranges = TRI_AddAccessAql(context, hint->_ranges, fieldAccess);
|
||||
else if (TRI_EqualString(directionValue, "any")) {
|
||||
CheckPathRestriction(fieldAccess, context, vertexCollection, ".source.", name, n);
|
||||
CheckPathRestriction(fieldAccess, context, vertexCollection, ".destination.", name, n);
|
||||
}
|
||||
|
||||
// check if we have a filter on LENGTH(edges)
|
||||
if (args->_members._length <= 4 &&
|
||||
TRI_EqualString(name, ".edges.LENGTH()")) {
|
||||
// length restriction, can only be applied if length parameters are not already set
|
||||
|
@ -603,6 +624,7 @@ TRI_associative_pointer_t* TRI_InitialiseFunctionsAql (void) {
|
|||
|
||||
// graph functions
|
||||
REGISTER_FUNCTION("PATHS", "GRAPH_PATHS", false, false, "c,h|s,b", &OptimisePaths);
|
||||
REGISTER_FUNCTION("TRAVERSE", "GRAPH_TRAVERSE", false, false, "h,h,s,s,a", NULL);
|
||||
|
||||
// misc functions
|
||||
REGISTER_FUNCTION("FAIL", "FAIL", false, false, "|s", NULL); // FAIL is non-deterministic, otherwise query optimisation will fail!
|
||||
|
|
|
@ -959,7 +959,7 @@
|
|||
/// that contain a word starting with the prefix @LIT{cent} and that also contain a word
|
||||
/// starting with the prefix @LIT{subst}.
|
||||
///
|
||||
/// If multiple search words (or prefixes) are given, then by default the results will
|
||||
/// If multiple search words (or prefixes) are given, then by default the results will be
|
||||
/// AND-combined, meaning only the logical intersection of all searches will be returned.
|
||||
/// It is also possible to combine partial results with a logical OR, and with a logical NOT:
|
||||
///
|
||||
|
@ -997,15 +997,55 @@
|
|||
///
|
||||
/// The result of the function is a list of paths. Paths of length 0 will also be returned. Each
|
||||
/// path is a document consisting of the following attributes:
|
||||
/// - @FA{vertices}: list of vertices visited along the path
|
||||
/// - @FA{edges}: list of edges visited along the path (might be empty)
|
||||
/// - @FA{source}: start vertex of path
|
||||
/// - @FA{destination}: destination vertex of path
|
||||
/// - @LIT{vertices}: list of vertices visited along the path
|
||||
/// - @LIT{edges}: list of edges visited along the path (might be empty)
|
||||
/// - @LIT{source}: start vertex of path
|
||||
/// - @LIT{destination}: destination vertex of path
|
||||
///
|
||||
/// Example calls:
|
||||
///
|
||||
/// @verbinclude aqlpaths
|
||||
///
|
||||
/// - @FN{TRAVERSE(@FA{vertexcollection}\, @FA{edgecollection}\, @FA{startVertex}\, @FA{direction}\, @FA{options})}:
|
||||
/// traverses the graph described by @FA{vertexcollection} and @FA{edgecollection},
|
||||
/// starting at the vertex identified by id @FA{startVertex}. Vertex connectivity is
|
||||
/// specified by the @FA{direction} parameter:
|
||||
/// - @LIT{outbound}: vertices are connected in @LIT{_from} to @LIT{_to} order
|
||||
/// - @LIT{inbound}: vertices are connected in @LIT{_to} to @LIT{_from} order
|
||||
///
|
||||
/// Additional options for the traversal can be provided via the @FA{options} document:
|
||||
/// - @LIT{strategy}: defines the traversal strategy. Possible values are @LIT{depthfirst}
|
||||
/// and @LIT{breadthfirst}. Defaults to @LIT{depthfirst}
|
||||
/// - @LIT{order}: defines the traversal order: Possible values are @LIT{preorder} and
|
||||
/// @LIT{postorder}. Defaults to @LIT{preorder}
|
||||
/// - @LIT{itemOrder}: Defines the level item order. Can be @LIT{forward} or
|
||||
/// @LIT{backward}. Defaults to @LIT{forward}
|
||||
/// - @LIT{maxDepth}: Maximum traversal depth. Defaults to unbounded
|
||||
/// - @LIT{trackPaths}: if @LIT{true}, the paths encountered during the traversal will
|
||||
/// also be returned along with each traversed vertex. If @LIT{false}, only the
|
||||
/// encountered vertices will be returned.
|
||||
/// - @LIT[uniqueness}: an optional document with the following properties:
|
||||
/// - @LIT{vertices}:
|
||||
/// - @LIT{none}: no vertex uniqueness is enforced
|
||||
/// - @LIT{global}: a vertex may be visited at most once
|
||||
/// - @LIT{path}: a vertex is visited only if not already contained in the current
|
||||
/// traversal path
|
||||
/// - @LIT{edges}:
|
||||
/// - @LIT{none}: no edge uniqueness is enforced
|
||||
/// - @LIT{global}: an edge may be visited at most once
|
||||
/// - @LIT{path}: an edge is visited only if not already contained in the current
|
||||
/// traversal path
|
||||
///
|
||||
/// The result of the TRAVERSE function is a list of traversed points. Each point is a
|
||||
/// document consisting of the following properties:
|
||||
/// - @LIT{vertex}: the vertex at the traversal point
|
||||
/// - @LIT{path}: The path history for the traversal point. The path is a document with the
|
||||
/// properties @LIT{vertices} and @LIT{edges}, which are both lists.
|
||||
///
|
||||
/// Example calls:
|
||||
///
|
||||
/// @verbinclude aqltraversal
|
||||
///
|
||||
/// @subsubsection AqlFunctionsControl Control flow functions
|
||||
///
|
||||
/// AQL offers the following functions to let the user control the flow of operations:
|
||||
|
|
|
@ -2895,7 +2895,7 @@ static v8::Handle<v8::Value> JS_UpgradeVocbaseCol (v8::Arguments const& argv) {
|
|||
|
||||
memset(&newMarker, 0, newMarkerSize);
|
||||
|
||||
sprintf(didBuffer,"%d", (unsigned int) oldMarker->_did);
|
||||
sprintf(didBuffer,"%llu", (unsigned long long) oldMarker->_did);
|
||||
keySize = strlen(didBuffer) + 1;
|
||||
keyBodySize = TRI_DF_ALIGN_BLOCK(keySize);
|
||||
keyBody = (char*) TRI_Allocate(TRI_CORE_MEM_ZONE, keyBodySize, true);
|
||||
|
@ -2949,9 +2949,9 @@ static v8::Handle<v8::Value> JS_UpgradeVocbaseCol (v8::Arguments const& argv) {
|
|||
|
||||
memset(&newMarker, 0, newMarkerSize);
|
||||
|
||||
sprintf(didBuffer,"%d", (unsigned int) oldMarker->base._did);
|
||||
sprintf(toDidBuffer,"%d", (unsigned int) oldMarker->_toDid);
|
||||
sprintf(fromDidBuffer,"%d", (unsigned int) oldMarker->_fromDid);
|
||||
sprintf(didBuffer,"%llu", (unsigned long long) oldMarker->base._did);
|
||||
sprintf(toDidBuffer,"%llu", (unsigned long long) oldMarker->_toDid);
|
||||
sprintf(fromDidBuffer,"%llu", (unsigned long long) oldMarker->_fromDid);
|
||||
|
||||
keySize = strlen(didBuffer) + 1;
|
||||
toSize = strlen(toDidBuffer) + 1;
|
||||
|
@ -3008,7 +3008,7 @@ static v8::Handle<v8::Value> JS_UpgradeVocbaseCol (v8::Arguments const& argv) {
|
|||
|
||||
memset(&newMarker, 0, newMarkerSize);
|
||||
|
||||
sprintf(didBuffer,"%d", (unsigned int) oldMarker->_did);
|
||||
sprintf(didBuffer,"%llu", (unsigned long long) oldMarker->_did);
|
||||
keySize = strlen(didBuffer) + 1;
|
||||
keyBodySize = TRI_DF_ALIGN_BLOCK(keySize);
|
||||
keyBody = (char*) TRI_Allocate(TRI_CORE_MEM_ZONE, keyBodySize, true);
|
||||
|
|
|
@ -678,10 +678,10 @@ static TRI_datafile_t* OpenDatafile (char const* filename, bool ignoreErrors) {
|
|||
|
||||
// check the maximal size
|
||||
if (size > header._maximalSize) {
|
||||
LOG_WARNING("datafile '%s' has size '%u', but maximal size is '%u'",
|
||||
filename,
|
||||
(unsigned int) size,
|
||||
(unsigned int) header._maximalSize);
|
||||
LOG_DEBUG("datafile '%s' has size '%u', but maximal size is '%u'",
|
||||
filename,
|
||||
(unsigned int) size,
|
||||
(unsigned int) header._maximalSize);
|
||||
}
|
||||
|
||||
// map datafile into memory
|
||||
|
|
|
@ -217,7 +217,7 @@ static int RevisionKey (TRI_key_generator_t* const generator,
|
|||
current += TRI_StringUInt64InPlace(revision, current);
|
||||
}
|
||||
else {
|
||||
char numBuffer[22];
|
||||
char numBuffer[22]; // a uint64 cannot be longer than this
|
||||
size_t length;
|
||||
|
||||
length = TRI_StringUInt64InPlace(revision, (char*) &numBuffer);
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
/*jslint indent: 2,
|
||||
nomen: true,
|
||||
maxlen: 100,
|
||||
sloppy: true,
|
||||
vars: true,
|
||||
white: true,
|
||||
plusplus: true */
|
||||
/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
|
||||
/*global require, arango, db, edges, Module */
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1109,6 +1103,11 @@ function ArangoCollection (database, data) {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ArangoCollection.prototype._documenturl = function (id) {
|
||||
var s = id.split("/");
|
||||
|
||||
if (s.length == 1) {
|
||||
return this._database._documenturl(this.name() + "/" + id, this.name());
|
||||
}
|
||||
return this._database._documenturl(id, this.name());
|
||||
};
|
||||
|
||||
|
@ -1615,6 +1614,78 @@ function ArangoCollection (database, data) {
|
|||
this._type = requestResult['type'];
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief iterators over some elements of a collection
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ArangoCollection.prototype.iterate = function (iterator, options) {
|
||||
var probability = 1.0;
|
||||
var limit = null;
|
||||
var stmt;
|
||||
var cursor;
|
||||
var pos;
|
||||
|
||||
if (options !== undefined) {
|
||||
if (options.hasOwnProperty("probability")) {
|
||||
probability = options.probability;
|
||||
}
|
||||
|
||||
if (options.hasOwnProperty("limit")) {
|
||||
limit = options.limit;
|
||||
}
|
||||
}
|
||||
|
||||
if (limit === null) {
|
||||
if (probability >= 1.0) {
|
||||
cursor = this.all();
|
||||
}
|
||||
else {
|
||||
stmt = internal.sprintf("FOR d IN %s FILTER rand() >= @prob RETURN d", this.name());
|
||||
stmt = internal.db._createStatement({ query: stmt });
|
||||
|
||||
if (probability < 1.0) {
|
||||
stmt.bind("prob", probability);
|
||||
}
|
||||
|
||||
cursor = stmt.execute();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (typeof limit !== "number") {
|
||||
var error = new ArangoError();
|
||||
error.errorNum = internal.errors.ERROR_ILLEGAL_NUMBER.code;
|
||||
error.errorMessage = "expecting a number, got " + String(limit);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (probability >= 1.0) {
|
||||
cursor = this.all().limit(limit);
|
||||
}
|
||||
else {
|
||||
stmt = internal.sprintf("FOR d IN %s FILTER rand() >= @prob LIMIT %d RETURN d",
|
||||
this.name(), limit);
|
||||
stmt = internal.db._createStatement({ query: stmt });
|
||||
|
||||
if (probability < 1.0) {
|
||||
stmt.bind("prob", probability);
|
||||
}
|
||||
|
||||
cursor = stmt.execute();
|
||||
}
|
||||
}
|
||||
|
||||
pos = 0;
|
||||
|
||||
while (cursor.hasNext()) {
|
||||
var document = cursor.next();
|
||||
|
||||
iterator(document, pos);
|
||||
|
||||
pos++;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1657,7 +1728,7 @@ function ArangoCollection (database, data) {
|
|||
}
|
||||
|
||||
if (rev === null) {
|
||||
requestResult = this._database._connection.GET(this._documenturl(this._name + "/" + id));
|
||||
requestResult = this._database._connection.GET(this._documenturl(id));
|
||||
}
|
||||
else {
|
||||
requestResult = this._database._connection.GET(this._documenturl(id),
|
||||
|
|
|
@ -542,6 +542,15 @@
|
|||
internal.print = internal.printBrowser;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief global print
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
internal.printf = function () {
|
||||
var text = internal.sprintf.apply(internal.springf, arguments);
|
||||
internal.output(text);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief start pager
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/*jslint indent: 2,
|
||||
nomen: true,
|
||||
maxlen: 100,
|
||||
sloppy: true,
|
||||
plusplus: true */
|
||||
/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, plusplus: true */
|
||||
/*global require, WeakDictionary, exports */
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -104,7 +100,7 @@ findOrCreateEdgeCollectionByName = function (name) {
|
|||
/// @brief constructs a new edge object
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function Edge(graph, id) {
|
||||
function Edge (graph, id) {
|
||||
var properties = graph._edges.document(id);
|
||||
|
||||
this._graph = graph;
|
||||
|
|
|
@ -40,59 +40,61 @@ var internal = require("internal");
|
|||
/// @brief traversal constructor
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function ArangoTraverser (edgeCollection, properties) {
|
||||
// check edgeCollection
|
||||
if (edgeCollection == undefined || edgeCollection == null) {
|
||||
throw "invalid edgeCollection";
|
||||
}
|
||||
|
||||
this._edgeCollection = edgeCollection;
|
||||
|
||||
if (typeof properties !== "object") {
|
||||
throw "invalid properties";
|
||||
}
|
||||
|
||||
this._order = properties.visitOrder || ArangoTraverser.PRE_ORDER;
|
||||
this._itemOrder = properties.itemOrder || ArangoTraverser.FORWARD;
|
||||
|
||||
// check the visitation strategy
|
||||
if (properties.strategy !== ArangoTraverser.BREADTH_FIRST &&
|
||||
properties.strategy !== ArangoTraverser.DEPTH_FIRST) {
|
||||
throw "invalid strategy";
|
||||
}
|
||||
this._strategy = properties.strategy;
|
||||
|
||||
// initialise uniqueness check attributes
|
||||
this._uniqueness = {
|
||||
vertices: properties.uniqueness.vertices || ArangoTraverser.UNIQUE_NONE,
|
||||
edges: properties.uniqueness.edges || ArangoTraverser.UNIQUE_NONE
|
||||
function ArangoTraverser (config) {
|
||||
var defaults = {
|
||||
order: ArangoTraverser.PRE_ORDER,
|
||||
itemOrder: ArangoTraverser.FORWARD,
|
||||
strategy: ArangoTraverser.DEPTH_FIRST,
|
||||
uniqueness: {
|
||||
vertices: ArangoTraverser.UNIQUE_NONE,
|
||||
edges: ArangoTraverser.UNIQUE_NONE
|
||||
},
|
||||
visitor: TrackingVisitor,
|
||||
filter: VisitAllFilter,
|
||||
expander: CollectionOutboundExpander
|
||||
};
|
||||
|
||||
// callbacks
|
||||
// visitor
|
||||
this._visitor = properties.visitor;
|
||||
if (typeof config !== "object") {
|
||||
throw "invalid configuration";
|
||||
}
|
||||
|
||||
// filter
|
||||
this._filter = properties.filter || function () {
|
||||
return {
|
||||
visit: true,
|
||||
expand: true
|
||||
};
|
||||
};
|
||||
// apply defaults
|
||||
for (var d in defaults) {
|
||||
if (! defaults.hasOwnProperty(d)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._expander = properties.expander || OutboundExpander;
|
||||
if (! config.hasOwnProperty(d)) {
|
||||
config[d] = defaults[d];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof this._visitor !== "function") {
|
||||
if (typeof config.visitor !== "function") {
|
||||
throw "invalid visitor";
|
||||
}
|
||||
|
||||
if (typeof this._filter !== "function") {
|
||||
|
||||
if (Array.isArray(config.filter)) {
|
||||
config.filter.forEach( function (f) {
|
||||
if (typeof f !== "function") {
|
||||
throw "invalid filter";
|
||||
}
|
||||
});
|
||||
var innerFilters = config.filter.slice()
|
||||
var combinedFilter = function(config, vertex, path) {
|
||||
return CombineFilters(innerFilters, config, vertex, path);
|
||||
};
|
||||
config.filter = combinedFilter;
|
||||
}
|
||||
|
||||
if (typeof config.filter !== "function") {
|
||||
throw "invalid filter";
|
||||
}
|
||||
|
||||
if (typeof this._expander !== "function") {
|
||||
if (typeof config.expander !== "function") {
|
||||
throw "invalid expander";
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -112,25 +114,23 @@ function ArangoTraverser (edgeCollection, properties) {
|
|||
/// @brief execute the traversal
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ArangoTraverser.prototype.traverse = function (startVertex, context) {
|
||||
ArangoTraverser.prototype.traverse = function (result, startVertex) {
|
||||
// check the start vertex
|
||||
if (startVertex == undefined || startVertex == null) {
|
||||
throw "invalid startVertex specified for traversal";
|
||||
}
|
||||
|
||||
// set user defined context
|
||||
this._context = context;
|
||||
|
||||
// run the traversal
|
||||
// get the traversal strategy
|
||||
var strategy;
|
||||
if (this._strategy === ArangoTraverser.BREADTH_FIRST) {
|
||||
strategy = BreadthFirstSearch;
|
||||
if (this.config.strategy === ArangoTraverser.BREADTH_FIRST) {
|
||||
strategy = BreadthFirstSearch();
|
||||
}
|
||||
else {
|
||||
strategy = DepthFirstSearch;
|
||||
strategy = DepthFirstSearch();
|
||||
}
|
||||
|
||||
strategy().run(this, startVertex);
|
||||
|
||||
// run the traversal
|
||||
strategy.run(this.config, result, startVertex);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -146,6 +146,49 @@ ArangoTraverser.prototype.traverse = function (startVertex, context) {
|
|||
/// @{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief parse a filter result
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function ParseFilterResult (args) {
|
||||
var result = {
|
||||
visit: true,
|
||||
expand: true
|
||||
};
|
||||
|
||||
function processArgument (arg) {
|
||||
if (arg == undefined || arg == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof(arg) === 'string') {
|
||||
if (arg === ArangoTraverser.EXCLUDE) {
|
||||
result.visit = false;
|
||||
return;
|
||||
}
|
||||
else if (arg === ArangoTraverser.PRUNE) {
|
||||
result.expand = false;
|
||||
return;
|
||||
}
|
||||
else if (arg === '') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (Array.isArray(arg)) {
|
||||
for (var i = 0; i < arg.length; ++i) {
|
||||
processArgument(arg[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
throw "invalid filter result";
|
||||
}
|
||||
|
||||
processArgument(args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief apply the uniqueness checks
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -174,19 +217,21 @@ function CheckUniqueness (uniqueness, visited, vertex, edge) {
|
|||
/// @brief check if we must process items in reverse order
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function CheckReverse (traverser) {
|
||||
if (traverser._order === ArangoTraverser.POST_ORDER) {
|
||||
function CheckReverse (config) {
|
||||
if (config.order === ArangoTraverser.POST_ORDER) {
|
||||
// post order
|
||||
if (traverser._itemOrder === ArangoTraverser.FORWARD) {
|
||||
if (config.itemOrder === ArangoTraverser.FORWARD) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (traverser._order === ArangoTraverser.PRE_ORDER) {
|
||||
else if (config.order === ArangoTraverser.PRE_ORDER) {
|
||||
// pre order
|
||||
if (traverser._itemOrder === ArangoTraverser.BACKWARD && traverser._strategy === ArangoTraverser.BREADTH_FIRST) {
|
||||
if (config.itemOrder === ArangoTraverser.BACKWARD &&
|
||||
config.strategy === ArangoTraverser.BREADTH_FIRST) {
|
||||
return true;
|
||||
}
|
||||
if (traverser._itemOrder === ArangoTraverser.FORWARD && traverser._strategy === ArangoTraverser.DEPTH_FIRST) {
|
||||
if (config.itemOrder === ArangoTraverser.FORWARD &&
|
||||
config.strategy === ArangoTraverser.DEPTH_FIRST) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -200,6 +245,18 @@ function CheckReverse (traverser) {
|
|||
|
||||
function BreadthFirstSearch () {
|
||||
return {
|
||||
getPathItems: function (items) {
|
||||
var visited = { };
|
||||
var ignore = items.length - 1;
|
||||
|
||||
items.forEach(function (item, i) {
|
||||
if (i != ignore) {
|
||||
visited[item._id] = true;
|
||||
}
|
||||
});
|
||||
|
||||
return visited;
|
||||
},
|
||||
|
||||
createPath: function (items, idx) {
|
||||
var path = { edges: [ ], vertices: [ ] };
|
||||
|
@ -220,13 +277,13 @@ function BreadthFirstSearch () {
|
|||
return path;
|
||||
},
|
||||
|
||||
run: function (traverser, startVertex) {
|
||||
run: function (config, result, startVertex) {
|
||||
var toVisit = [ { edge: null, vertex: startVertex, parentIndex: -1 } ];
|
||||
var visited = { edges: { }, vertices: { } };
|
||||
|
||||
var index = 0;
|
||||
var step = 1;
|
||||
var reverse = CheckReverse(traverser);
|
||||
var reverse = CheckReverse(config);
|
||||
|
||||
while ((step == 1 && index < toVisit.length) ||
|
||||
(step == -1 && index >= 0)) {
|
||||
|
@ -237,8 +294,17 @@ function BreadthFirstSearch () {
|
|||
if (current.visit == null) {
|
||||
current.visit = false;
|
||||
|
||||
var path = this.createPath(toVisit, index);
|
||||
|
||||
// first apply uniqueness check
|
||||
if (! CheckUniqueness(traverser._uniqueness, visited, vertex, edge)) {
|
||||
if (config.uniqueness.vertices === ArangoTraverser.UNIQUE_PATH) {
|
||||
visited.vertices = this.getPathItems(path.vertices);
|
||||
}
|
||||
if (config.uniqueness.edges === ArangoTraverser.UNIQUE_PATH) {
|
||||
visited.edges = this.getPathItems(path.edges);
|
||||
}
|
||||
|
||||
if (! CheckUniqueness(config.uniqueness, visited, vertex, edge)) {
|
||||
if (index < toVisit.length - 1) {
|
||||
index += step;
|
||||
}
|
||||
|
@ -248,12 +314,10 @@ function BreadthFirstSearch () {
|
|||
continue;
|
||||
}
|
||||
|
||||
var path = this.createPath(toVisit, index);
|
||||
|
||||
var filterResult = traverser._filter(traverser, vertex, path);
|
||||
if (traverser._order === ArangoTraverser.PRE_ORDER && filterResult.visit) {
|
||||
var filterResult = ParseFilterResult(config.filter(config, vertex, path));
|
||||
if (config.order === ArangoTraverser.PRE_ORDER && filterResult.visit) {
|
||||
// preorder
|
||||
traverser._visitor(traverser, vertex, path);
|
||||
config.visitor(config, result, vertex, path);
|
||||
}
|
||||
else {
|
||||
// postorder
|
||||
|
@ -261,7 +325,7 @@ function BreadthFirstSearch () {
|
|||
}
|
||||
|
||||
if (filterResult.expand) {
|
||||
var connected = traverser._expander(traverser, vertex, path);
|
||||
var connected = config.expander(config, vertex, path);
|
||||
if (connected.length > 0) {
|
||||
reverse && connected.reverse();
|
||||
for (var i = 0; i < connected.length; ++i) {
|
||||
|
@ -271,7 +335,7 @@ function BreadthFirstSearch () {
|
|||
}
|
||||
}
|
||||
|
||||
if (traverser._order === ArangoTraverser.POST_ORDER) {
|
||||
if (config.order === ArangoTraverser.POST_ORDER) {
|
||||
if (index < toVisit.length - 1) {
|
||||
index += step;
|
||||
}
|
||||
|
@ -281,9 +345,9 @@ function BreadthFirstSearch () {
|
|||
}
|
||||
}
|
||||
else {
|
||||
if (traverser._order === ArangoTraverser.POST_ORDER && current.visit) {
|
||||
if (config.order === ArangoTraverser.POST_ORDER && current.visit) {
|
||||
var path = this.createPath(toVisit, index);
|
||||
traverser._visitor(traverser, vertex, path);
|
||||
config.visitor(config, result, vertex, path);
|
||||
}
|
||||
index += step;
|
||||
|
||||
|
@ -301,12 +365,19 @@ function BreadthFirstSearch () {
|
|||
|
||||
function DepthFirstSearch () {
|
||||
return {
|
||||
getPathItems: function (items) {
|
||||
var visited = { };
|
||||
items.forEach(function (item) {
|
||||
visited[item._id] = true;
|
||||
});
|
||||
return visited;
|
||||
},
|
||||
|
||||
run: function (traverser, startVertex) {
|
||||
run: function (config, result, startVertex) {
|
||||
var toVisit = [ { edge: null, vertex: startVertex, visit: null } ];
|
||||
var path = { edges: [ ], vertices: [ ] };
|
||||
var visited = { edges: { }, vertices: { } };
|
||||
var reverse = CheckReverse(traverser);
|
||||
var reverse = CheckReverse(config);
|
||||
|
||||
while (toVisit.length > 0) {
|
||||
// peek at the top of the stack
|
||||
|
@ -319,7 +390,14 @@ function DepthFirstSearch () {
|
|||
current.visit = false;
|
||||
|
||||
// first apply uniqueness check
|
||||
if (! CheckUniqueness(traverser._uniqueness, visited, vertex, edge)) {
|
||||
if (config.uniqueness.vertices === ArangoTraverser.UNIQUE_PATH) {
|
||||
visited.vertices = this.getPathItems(path.vertices);
|
||||
}
|
||||
if (config.uniqueness.edges === ArangoTraverser.UNIQUE_PATH) {
|
||||
visited.edges = this.getPathItems(path.edges);
|
||||
}
|
||||
|
||||
if (! CheckUniqueness(config.uniqueness, visited, vertex, edge)) {
|
||||
// skip element if not unique
|
||||
toVisit.pop();
|
||||
continue;
|
||||
|
@ -331,11 +409,10 @@ function DepthFirstSearch () {
|
|||
}
|
||||
path.vertices.push(vertex);
|
||||
|
||||
|
||||
var filterResult = traverser._filter(traverser, vertex, path);
|
||||
if (traverser._order === ArangoTraverser.PRE_ORDER && filterResult.visit) {
|
||||
var filterResult = ParseFilterResult(config.filter(config, vertex, path));
|
||||
if (config.order === ArangoTraverser.PRE_ORDER && filterResult.visit) {
|
||||
// preorder visit
|
||||
traverser._visitor(traverser, vertex, path);
|
||||
config.visitor(config, result, vertex, path);
|
||||
}
|
||||
else {
|
||||
// postorder. mark the element visitation flag because we'll got to check it later
|
||||
|
@ -344,7 +421,7 @@ function DepthFirstSearch () {
|
|||
|
||||
// expand the element's children?
|
||||
if (filterResult.expand) {
|
||||
var connected = traverser._expander(traverser, vertex, path);
|
||||
var connected = config.expander(config, vertex, path);
|
||||
if (connected.length > 0) {
|
||||
reverse && connected.reverse();
|
||||
for (var i = 0; i < connected.length; ++i) {
|
||||
|
@ -356,9 +433,9 @@ function DepthFirstSearch () {
|
|||
}
|
||||
else {
|
||||
// we have already seen this element
|
||||
if (traverser._order === ArangoTraverser.POST_ORDER && current.visit) {
|
||||
if (config.order === ArangoTraverser.POST_ORDER && current.visit) {
|
||||
// postorder visitation
|
||||
traverser._visitor(traverser, vertex, path);
|
||||
config.visitor(config, result, vertex, path);
|
||||
}
|
||||
|
||||
// pop the element from the stack
|
||||
|
@ -379,32 +456,181 @@ function DepthFirstSearch () {
|
|||
/// @brief default outbound expander function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function OutboundExpander (traverser, vertex, path) {
|
||||
function CollectionOutboundExpander (config, vertex, path) {
|
||||
var connections = [ ];
|
||||
var outEdges = config.edgeCollection.outEdges(vertex._id);
|
||||
|
||||
if (outEdges.length > 1 && config.sort) {
|
||||
outEdges.sort(config.sort);
|
||||
}
|
||||
|
||||
this._edgeCollection.outEdges(vertex._id).forEach(function (edge) {
|
||||
var vertex = internal.db._document(edge._to);
|
||||
connections.push({ edge: edge, vertex: vertex });
|
||||
outEdges.forEach(function (edge) {
|
||||
try {
|
||||
var vertex = internal.db._document(edge._to);
|
||||
connections.push({ edge: edge, vertex: vertex });
|
||||
}
|
||||
catch (e) {
|
||||
// continue even in the face of non-existing documents
|
||||
}
|
||||
});
|
||||
|
||||
return connections;
|
||||
};
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief default inbound expander function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function InboundExpander (traverser, vertex, path) {
|
||||
function CollectionInboundExpander (config, vertex, path) {
|
||||
var connections = [ ];
|
||||
var inEdges = config.edgeCollection.inEdges(vertex._id);
|
||||
|
||||
if (inEdges.length > 1 && config.sort) {
|
||||
inEdges.sort(config.sort);
|
||||
}
|
||||
|
||||
this._edgeCollection.inEdges(vertex._id).forEach(function (edge) {
|
||||
var vertex = internal.db._document(edge._from);
|
||||
connections.push({ edge: edge, vertex: vertex });
|
||||
inEdges.forEach(function (edge) {
|
||||
try {
|
||||
var vertex = internal.db._document(edge._from);
|
||||
connections.push({ edge: edge, vertex: vertex });
|
||||
}
|
||||
catch (e) {
|
||||
// continue even in the face of non-existing documents
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return connections;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief default "any" expander function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function CollectionAnyExpander (config, vertex, path) {
|
||||
var connections = [ ];
|
||||
var edges = config.edgeCollection.edges(vertex._id);
|
||||
|
||||
if (edges.length > 1 && config.sort) {
|
||||
edges.sort(config.sort);
|
||||
}
|
||||
|
||||
edges.forEach(function (edge) {
|
||||
try {
|
||||
var vertex = internal.db._document(edge._from);
|
||||
connections.push({ edge: edge, vertex: vertex });
|
||||
}
|
||||
catch (e) {
|
||||
// continue even in the face of non-existing documents
|
||||
}
|
||||
});
|
||||
|
||||
return connections;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief default expander that expands all edges labeled with one label in config.labels
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var ExpandEdgesWithLabels = function (config, vertex, path) {
|
||||
var result = [ ];
|
||||
if (!Array.isArray(config.labels)) {
|
||||
config.labels = [config.labels];
|
||||
}
|
||||
var edgesList = edges[vertex._id];
|
||||
if (edgesList != undefined) {
|
||||
for (i = 0; i < edgesList.length; ++i) {
|
||||
if (!!~config.labels.indexOf(edgesList[i].label)) {
|
||||
result.push({ edge: edgesList[i], vertex: vertices[edgesList[i]._to] });
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief default visitor that just tracks every visit
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function TrackingVisitor (config, result, vertex, path) {
|
||||
if (! result || ! result.visited) {
|
||||
return;
|
||||
}
|
||||
|
||||
function clone (obj) {
|
||||
if (obj == null || typeof(obj) !== "object") {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
var copy = [];
|
||||
for (var i = 0; i < obj.length; ++i) {
|
||||
copy[i] = clone(obj[i]);
|
||||
}
|
||||
}
|
||||
else if (obj instanceof Object) {
|
||||
var copy = {};
|
||||
for (var attr in obj) {
|
||||
if (obj.hasOwnProperty(attr)) {
|
||||
copy[attr] = clone(obj[attr]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
if (result.visited.vertices) {
|
||||
result.visited.vertices.push(clone(vertex));
|
||||
}
|
||||
if (result.visited.paths) {
|
||||
result.visited.paths.push(clone(path));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief default filter to visit & expand all vertices
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function VisitAllFilter () {
|
||||
return "";
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief default filter to visit & expand all vertices up to a given depth
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function MaxDepthFilter (config, vertex, path) {
|
||||
if (path.vertices.length > config.maxDepth) {
|
||||
return ArangoTraverser.PRUNE;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief default filter to exclude all vertices up to a given depth
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function MinDepthFilter (config, vertex, path) {
|
||||
if (path.vertices.length <= config.minDepth) {
|
||||
return ArangoTraverser.EXCLUDE;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -472,6 +698,18 @@ ArangoTraverser.FORWARD = 0;
|
|||
|
||||
ArangoTraverser.BACKWARD = 1;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief prune "constant"
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ArangoTraverser.PRUNE = 'prune';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief exclude "constant"
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ArangoTraverser.EXCLUDE = 'exclude';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -485,10 +723,16 @@ ArangoTraverser.BACKWARD = 1;
|
|||
/// @{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
exports.Traverser = ArangoTraverser;
|
||||
exports.OutboundExpander = OutboundExpander;
|
||||
exports.InboundExpander = InboundExpander;
|
||||
|
||||
exports.Traverser = ArangoTraverser;
|
||||
exports.CollectionOutboundExpander = CollectionOutboundExpander;
|
||||
exports.CollectionInboundExpander = CollectionInboundExpander;
|
||||
exports.CollectionAnyExpander = CollectionAnyExpander;
|
||||
exports.VisitAllFilter = VisitAllFilter;
|
||||
exports.TrackingVisitor = TrackingVisitor;
|
||||
exports.MinDepthFilter = MinDepthFilter;
|
||||
exports.MaxDepthFilter = MaxDepthFilter;
|
||||
exports.ExpandEdgesWithLabels = ExpandEdgesWithLabels;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -84,10 +84,6 @@ ArangoStatement.prototype.bind = function (key, value) {
|
|||
this._bindVars = key;
|
||||
}
|
||||
else if (typeof(key) === "string") {
|
||||
if (this._bindVars[key] !== undefined) {
|
||||
throw "redeclaration of bind parameter";
|
||||
}
|
||||
|
||||
this._bindVars[key] = value;
|
||||
}
|
||||
else if (typeof(key) === "number") {
|
||||
|
@ -97,10 +93,6 @@ ArangoStatement.prototype.bind = function (key, value) {
|
|||
throw "invalid bind parameter declaration";
|
||||
}
|
||||
|
||||
if (this._bindVars[strKey] !== undefined) {
|
||||
throw "redeclaration of bind parameter";
|
||||
}
|
||||
|
||||
this._bindVars[strKey] = value;
|
||||
}
|
||||
else {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -242,10 +242,17 @@
|
|||
|
||||
if (limit === null) {
|
||||
if (probability >= 1.0) {
|
||||
stmt = internal.sprintf("FOR d IN %s RETURN d", this.name());
|
||||
cursor = this.all();
|
||||
}
|
||||
else {
|
||||
stmt = internal.sprintf("FOR d IN %s FILTER rand() >= @prob RETURN d", this.name());
|
||||
stmt = internal.db._createStatement({ query: stmt });
|
||||
|
||||
if (probability < 1.0) {
|
||||
stmt.bind("prob", probability);
|
||||
}
|
||||
|
||||
cursor = stmt.execute();
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -258,21 +265,21 @@
|
|||
}
|
||||
|
||||
if (probability >= 1.0) {
|
||||
stmt = internal.sprintf("FOR d IN %s LIMIT %d RETURN d", this.name(), limit);
|
||||
cursor = this.all().limit(limit);
|
||||
}
|
||||
else {
|
||||
stmt = internal.sprintf("FOR d IN %s FILTER rand() >= @prob LIMIT %d RETURN d",
|
||||
this.name(), limit);
|
||||
stmt = internal.db._createStatement({ query: stmt });
|
||||
|
||||
if (probability < 1.0) {
|
||||
stmt.bind("prob", probability);
|
||||
}
|
||||
|
||||
cursor = stmt.execute();
|
||||
}
|
||||
}
|
||||
|
||||
stmt = internal.db._createStatement({ query: stmt });
|
||||
|
||||
if (probability < 1.0) {
|
||||
stmt.bind("prob", probability);
|
||||
}
|
||||
|
||||
cursor = stmt.execute();
|
||||
pos = 0;
|
||||
|
||||
while (cursor.hasNext()) {
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var internal = require("internal");
|
||||
var traversal = require("org/arangodb/graph/traversal");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief type weight used for sorting and comparing
|
||||
|
@ -2209,8 +2210,6 @@ function AHUACATL_GRAPH_PATHS () {
|
|||
followCycles : followCycles
|
||||
};
|
||||
|
||||
// TODO: restrict allEdges to edges with certain _from values etc.
|
||||
|
||||
var result = [ ];
|
||||
var n = vertices.length;
|
||||
for (var i = 0; i < n; ++i) {
|
||||
|
@ -2304,6 +2303,94 @@ function AHUACATL_GRAPH_SUBNODES (searchAttributes, vertexId, visited, edges, ve
|
|||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief visitor callback function for traversal
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function AHUACATL_TRAVERSE_VISITOR (config, result, vertex, path) {
|
||||
if (config.trackPaths) {
|
||||
result.push(AHUACATL_CLONE({ vertex: vertex, path: path }));
|
||||
}
|
||||
else {
|
||||
result.push(AHUACATL_CLONE({ vertex: vertex }));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief traverse a graph
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function AHUACATL_GRAPH_TRAVERSE () {
|
||||
var vertexCollection = AHUACATL_COLLECTION(arguments[0]);
|
||||
var edgeCollection = AHUACATL_COLLECTION(arguments[1]);
|
||||
var startVertex = arguments[2];
|
||||
var direction = arguments[3];
|
||||
var params = arguments[4];
|
||||
|
||||
function validate (value, map) {
|
||||
if (value == null || value == undefined) {
|
||||
// use first key from map
|
||||
for (var m in map) {
|
||||
if (map.hasOwnProperty(m)) {
|
||||
value = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
value = value.toLowerCase().replace(/-/, "");
|
||||
if (map[value] != null) {
|
||||
return map[value];
|
||||
}
|
||||
}
|
||||
|
||||
AHUACATL_THROW(internal.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "TRAVERSE");
|
||||
}
|
||||
|
||||
var config = {
|
||||
edgeCollection: edgeCollection,
|
||||
strategy: validate(params.strategy, {
|
||||
'depthfirst': traversal.Traverser.DEPTH_FIRST,
|
||||
'breadthfirst': traversal.Traverser.BREADTH_FIRST
|
||||
}),
|
||||
order: validate(params.order, {
|
||||
'preorder': traversal.Traverser.PRE_ORDER,
|
||||
'postorder': traversal.Traverser.POST_ORDER
|
||||
}),
|
||||
itemOrder: validate(params.itemOrder, {
|
||||
'forward': traversal.Traverser.FORWARD,
|
||||
'backward': traversal.Traverser.BACKWARD
|
||||
}),
|
||||
trackPaths: params.paths || false,
|
||||
visitor: AHUACATL_TRAVERSE_VISITOR,
|
||||
maxDepth: params.maxDepth,
|
||||
filter: params.maxDepth != undefined ? traversal.MaxDepthFilter : VisitAllFilter,
|
||||
uniqueness: {
|
||||
vertices: validate(params.uniqueness && params.uniqueness.vertices, {
|
||||
'none': traversal.Traverser.UNIQUE_NONE,
|
||||
'global': traversal.Traverser.UNIQUE_GLOBAL,
|
||||
'path': traversal.Traverser.UNIQUE_PATH
|
||||
}),
|
||||
edges: validate(params.uniqueness && params.uniqueness.edges, {
|
||||
'none': traversal.Traverser.UNIQUE_NONE,
|
||||
'global': traversal.Traverser.UNIQUE_GLOBAL,
|
||||
'path': traversal.Traverser.UNIQUE_PATH
|
||||
}),
|
||||
},
|
||||
expander: validate(direction, {
|
||||
'outbound': traversal.CollectionOutboundExpander,
|
||||
'inbound': traversal.CollectionInboundExpander,
|
||||
'any': traversal.CollectionAnyExpander
|
||||
})
|
||||
};
|
||||
|
||||
var result = [ ];
|
||||
var traverser = new traversal.Traverser(config);
|
||||
traverser.traverse(result, vertexCollection.document(startVertex));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -273,11 +273,89 @@ function ahuacatlQueryPathsTestSuite () {
|
|||
};
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test suite
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function ahuacatlQueryTraverseTestSuite () {
|
||||
var vn = "UnitTestsTraverseVertices";
|
||||
var en = "UnitTestsTraverseEdges";
|
||||
|
||||
var vertices;
|
||||
var edges;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief execute a given query
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function executeQuery (query, params) {
|
||||
var cursor = AHUACATL_RUN(query, params);
|
||||
if (cursor instanceof ArangoError) {
|
||||
print(query, cursor.errorMessage);
|
||||
}
|
||||
assertFalse(cursor instanceof ArangoError);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief set up
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
setUp : function () {
|
||||
db._drop(vn);
|
||||
db._drop(en);
|
||||
|
||||
vertexCollection = db._create(vn);
|
||||
edgeCollection = db._createEdgeCollection(en);
|
||||
|
||||
[ "A", "B", "C", "D" ].forEach(function (item) {
|
||||
vertexCollection.save({ _key: item, name: item });
|
||||
});
|
||||
|
||||
[ [ "A", "B" ], [ "B", "C" ], [ "A", "D" ], [ "D", "C" ], [ "C", "A" ] ].forEach(function (item) {
|
||||
var l = item[0];
|
||||
var r = item[1];
|
||||
edgeCollection.save(vn + "/" + l, vn + "/" + r, { _key: l + r, what : l + "->" + r });
|
||||
});
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief tear down
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
tearDown : function () {
|
||||
db._drop(vn);
|
||||
db._drop(en);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief tear down
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testTraversalDepthFirst : function () {
|
||||
var config = {
|
||||
strategy: "depthfirst",
|
||||
order: "preorder",
|
||||
itemOrder: "forward",
|
||||
maxDepth: 2
|
||||
};
|
||||
|
||||
var actual = executeQuery("FOR p IN TRAVERSE(@@v, @@e, '" + vn + "/A', 'outbound', " + JSON.stringify(config) + ") RETURN p.vertex._key", { "@v" : vn, "@e" : en }).getRows();
|
||||
|
||||
assertEqual([ "A", "B", "C", "D", "C" ], actual);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief executes the test suite
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
jsunity.run(ahuacatlQueryPathsTestSuite);
|
||||
jsunity.run(ahuacatlQueryTraverseTestSuite);
|
||||
|
||||
return jsunity.done();
|
||||
|
||||
|
|
Loading…
Reference in New Issue