1
0
Fork 0

Merge branch 'devel' of github.com:triAGENS/ArangoDB into devel

This commit is contained in:
Lucas Dohmen 2013-01-13 23:43:09 +01:00
commit a29f2f7370
15 changed files with 1680 additions and 374 deletions

View File

@ -0,0 +1,8 @@
TRAVERSE(friends, friendrelations, "friends/john", "outbound", {
strategy: "depthfirst",
order: "postorder",
itemOrder: "backward",
maxDepth: 6,
trackPaths: true
})

View File

@ -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!

View File

@ -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:

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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),

View File

@ -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
////////////////////////////////////////////////////////////////////////////////

View File

@ -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;

View File

@ -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;
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

View File

@ -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

View File

@ -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()) {

View File

@ -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;
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

View File

@ -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();