1
0
Fork 0

Solved conflict

This commit is contained in:
Michael Hackstein 2013-06-13 09:48:46 +02:00
commit fc5f10b3ed
16 changed files with 370 additions and 174 deletions

View File

@ -1,5 +1,18 @@
v1.4 v1.4
------ ----
* changed the HTTP return code from 400 to 404 for some cases when there is a referral
to a non-existing collection or document.
* introduced error code 1909 `too many iterations` that is thrown when graph traversals
hit the `maxIterations` threshold.
* optionally limit traversals to a certain number of iterations
the limitation can be achieved via the traversal API by setting the `maxIterations`
attribute, and also via the AQL `TRAVERSAL` and `TRAVERSAL_TREE` functions by setting
the same attribute. If traversals are not limited by the end user, a server-defined
limit for `maxIterations` may be used to prevent server-side traversals from running
endlessly.
* added graph traversal API at `/_api/traversal` * added graph traversal API at `/_api/traversal`

View File

@ -1277,6 +1277,10 @@ Example calls:
- `minDepth`: Minimum path depths for vertices to be included. This can be used to - `minDepth`: Minimum path depths for vertices to be included. This can be used to
include only vertices in the result that are found after a certain minimum depth. include only vertices in the result that are found after a certain minimum depth.
Defaults to 0. Defaults to 0.
- `maxIterations`: Maximum number of iterations in each traversal. This number can be
set to prevent endless loops in traversal of cyclic graphs. When a traversal performs
as many iterations as the `maxIterations` value, the traversal will abort with an
error. If `maxIterations` is not set, a server-defined value may be used.
- `maxDepth`: Maximum path depth for sub-edges expansion. This can be used to - `maxDepth`: Maximum path depth for sub-edges expansion. This can be used to
limit the depth of the traversal to a sensible amount. This should especially be used limit the depth of the traversal to a sensible amount. This should especially be used
for big graphs to limit the traversal to some sensible amount, and for graphs for big graphs to limit the traversal to some sensible amount, and for graphs

View File

@ -33,10 +33,10 @@ describe ArangoDB do
body = "{ \"query\" : \"FOR u IN unknowncollection LIMIT 2 RETURN u.n\", \"count\" : true, \"bindVars\" : {}, \"batchSize\" : 2 }" body = "{ \"query\" : \"FOR u IN unknowncollection LIMIT 2 RETURN u.n\", \"count\" : true, \"bindVars\" : {}, \"batchSize\" : 2 }"
doc = ArangoDB.log_post("#{prefix}-unknown-collection", cmd, :body => body) doc = ArangoDB.log_post("#{prefix}-unknown-collection", cmd, :body => body)
doc.code.should eq(400) doc.code.should eq(404)
doc.headers['content-type'].should eq("application/json; charset=utf-8") doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true) doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400) doc.parsed_response['code'].should eq(404)
doc.parsed_response['errorNum'].should eq(1203) doc.parsed_response['errorNum'].should eq(1203)
end end

View File

@ -29,10 +29,10 @@ describe ArangoDB do
body = "{ \"query\" : \"FOR u IN unknowncollection LIMIT 2 RETURN u.n\" }" body = "{ \"query\" : \"FOR u IN unknowncollection LIMIT 2 RETURN u.n\" }"
doc = ArangoDB.log_post("#{prefix}-unknown-collection", cmd, :body => body) doc = ArangoDB.log_post("#{prefix}-unknown-collection", cmd, :body => body)
doc.code.should eq(400) doc.code.should eq(404)
doc.headers['content-type'].should eq("application/json; charset=utf-8") doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true) doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400) doc.parsed_response['code'].should eq(404)
doc.parsed_response['errorNum'].should eq(1203) doc.parsed_response['errorNum'].should eq(1203)
end end

View File

@ -180,10 +180,10 @@ describe ArangoDB do
body = "{ \"collections\" : { \"write\": \"_meow\" }, \"action\" : \"function () { return 1; }\" }" body = "{ \"collections\" : { \"write\": \"_meow\" }, \"action\" : \"function () { return 1; }\" }"
doc = ArangoDB.log_post("#{prefix}-non-existing-collection", cmd, :body => body) doc = ArangoDB.log_post("#{prefix}-non-existing-collection", cmd, :body => body)
doc.code.should eq(400) doc.code.should eq(404)
doc.headers['content-type'].should eq("application/json; charset=utf-8") doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true) doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400) doc.parsed_response['code'].should eq(404)
doc.parsed_response['errorNum'].should eq(1203) doc.parsed_response['errorNum'].should eq(1203)
end end

View File

@ -22,7 +22,7 @@ describe ArangoDB do
cmd = "/_api/document?collection=#{@cv}" cmd = "/_api/document?collection=#{@cv}"
[ [
"World", "Nothing", "Europe", "Asia", "America", "Australia", "Antarctica", "Africa", "Blackhole", "World", "Nothing", "Europe", "Asia", "America", "Australia", "Antarctica", "Africa", "Blackhole", "Blackhole2",
"DE", "FR", "GB", "IE", "CN", "JP", "TW", "US", "MX", "AU", "EG", "ZA", "AN", "DE", "FR", "GB", "IE", "CN", "JP", "TW", "US", "MX", "AU", "EG", "ZA", "AN",
"London", "Paris", "Lyon", "Cologne","Dusseldorf", "Beijing", "Shanghai", "Tokyo", "Kyoto", "Taipeh", "Perth", "Sydney" "London", "Paris", "Lyon", "Cologne","Dusseldorf", "Beijing", "Shanghai", "Tokyo", "Kyoto", "Taipeh", "Perth", "Sydney"
].each do|loc| ].each do|loc|
@ -44,7 +44,9 @@ describe ArangoDB do
["Asia", "TW"], ["Asia", "TW"],
["America", "US"], ["America", "US"],
["America", "MX"], ["America", "MX"],
["Australia", "AU"] ["Australia", "AU"],
["Blackhole", "Blackhole2"],
["Blackhole2", "Blackhole"]
].each do|pair| ].each do|pair|
from = pair[0] from = pair[0]
to = pair[1] to = pair[1]
@ -176,6 +178,34 @@ describe ArangoDB do
doc.parsed_response['errorNum'].should eq(500) doc.parsed_response['errorNum'].should eq(500)
end end
################################################################################
## traversal abortion
################################################################################
it "traversal abortion, few iterations" do
body = "{ \"edgeCollection\" : \"#{@ce}\", \"startVertex\" : \"#{@cv}/Blackhole\", \"direction\" : \"outbound\", \"uniqueness\" : { \"vertices\" : \"none\", \"edges\" : \"none\" }, \"maxIterations\" : 5 }"
doc = ArangoDB.log_post("#{prefix}-visit-traversal-abort1", api, :body => body)
doc.code.should eq(500)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(500)
doc.parsed_response['errorNum'].should eq(1909)
end
it "traversal abortion, many iterations" do
body = "{ \"edgeCollection\" : \"#{@ce}\", \"startVertex\" : \"#{@cv}/Blackhole\", \"direction\" : \"outbound\", \"uniqueness\" : { \"vertices\" : \"none\", \"edges\" : \"none\" }, \"maxIterations\" : 5000, \"maxDepth\" : 999999, \"visitor\" : \"\" }"
doc = ArangoDB.log_post("#{prefix}-visit-traversal-abort2", api, :body => body)
doc.code.should eq(500)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(500)
doc.parsed_response['errorNum'].should eq(1909)
end
end end
################################################################################ ################################################################################

View File

@ -144,6 +144,7 @@
"ERROR_GRAPH_INVALID_EDGE" : { "code" : 1906, "message" : "invalid edge" }, "ERROR_GRAPH_INVALID_EDGE" : { "code" : 1906, "message" : "invalid edge" },
"ERROR_GRAPH_COULD_NOT_CREATE_EDGE" : { "code" : 1907, "message" : "could not create edge" }, "ERROR_GRAPH_COULD_NOT_CREATE_EDGE" : { "code" : 1907, "message" : "could not create edge" },
"ERROR_GRAPH_COULD_NOT_CHANGE_EDGE" : { "code" : 1908, "message" : "could not change edge" }, "ERROR_GRAPH_COULD_NOT_CHANGE_EDGE" : { "code" : 1908, "message" : "could not change edge" },
"ERROR_GRAPH_TOO_MANY_ITERATIONS" : { "code" : 1909, "message" : "too many iterations" },
"ERROR_SESSION_INVALID_SESSION" : { "code" : 1951, "message" : "invalid session" }, "ERROR_SESSION_INVALID_SESSION" : { "code" : 1951, "message" : "invalid session" },
"ERROR_SESSION_COULD_NOT_CREATE_SESSION" : { "code" : 1952, "message" : "could not create session" }, "ERROR_SESSION_COULD_NOT_CREATE_SESSION" : { "code" : 1952, "message" : "could not create session" },
"ERROR_SESSION_COULD_NOT_CHANGE_SESSION" : { "code" : 1953, "message" : "could not change session" }, "ERROR_SESSION_COULD_NOT_CHANGE_SESSION" : { "code" : 1953, "message" : "could not change session" },

View File

@ -31,6 +31,7 @@ module.define("org/arangodb/graph/traversal", function(exports, module) {
var graph = require("org/arangodb/graph"); var graph = require("org/arangodb/graph");
var arangodb = require("org/arangodb"); var arangodb = require("org/arangodb");
var ArangoError = arangodb.ArangoError;
var db = arangodb.db; var db = arangodb.db;
@ -661,6 +662,7 @@ function breadthFirstSearch () {
}, },
run: function (config, result, startVertex) { run: function (config, result, startVertex) {
var maxIterations = config.maxIterations, visitCounter = 0;
var toVisit = [ { edge: null, vertex: startVertex, parentIndex: -1 } ]; var toVisit = [ { edge: null, vertex: startVertex, parentIndex: -1 } ];
var visited = { edges: { }, vertices: { } }; var visited = { edges: { }, vertices: { } };
@ -675,6 +677,13 @@ function breadthFirstSearch () {
var edge = current.edge; var edge = current.edge;
var path; 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) { if (current.visit === null || current.visit === undefined) {
current.visit = false; current.visit = false;
@ -757,12 +766,20 @@ function depthFirstSearch () {
}, },
run: function (config, result, startVertex) { run: function (config, result, startVertex) {
var maxIterations = config.maxIterations, visitCounter = 0;
var toVisit = [ { edge: null, vertex: startVertex, visit: null } ]; var toVisit = [ { edge: null, vertex: startVertex, visit: null } ];
var path = { edges: [ ], vertices: [ ] }; var path = { edges: [ ], vertices: [ ] };
var visited = { edges: { }, vertices: { } }; var visited = { edges: { }, vertices: { } };
var reverse = checkReverse(config); var reverse = checkReverse(config);
while (toVisit.length > 0) { 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 // peek at the top of the stack
var current = toVisit[toVisit.length - 1]; var current = toVisit[toVisit.length - 1];
var vertex = current.vertex; var vertex = current.vertex;
@ -871,52 +888,150 @@ ArangoTraverser = function (config) {
visitor: trackingVisitor, visitor: trackingVisitor,
filter: visitAllFilter, filter: visitAllFilter,
expander: outboundExpander, expander: outboundExpander,
datasource: null datasource: null,
maxIterations: 10000,
minDepth: 0,
maxDepth: 256
}, d; }, d;
var err;
if (typeof config !== "object") { if (typeof config !== "object") {
throw "invalid configuration"; err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
err.errorMessage = arangodb.errors.ERROR_BAD_PARAMETER.message;
throw err;
} }
// apply defaults // apply defaults
for (d in defaults) { for (d in defaults) {
if (defaults.hasOwnProperty(d)) { if (defaults.hasOwnProperty(d)) {
if (! config.hasOwnProperty(d)) { if (! config.hasOwnProperty(d) || config[d] === undefined) {
config[d] = defaults[d]; config[d] = defaults[d];
} }
} }
} }
if (typeof config.visitor !== "function") { function validate (value, map, param) {
throw "invalid visitor"; 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) {
return map[value];
}
}
for (m in map) {
if (map.hasOwnProperty(m)) {
if (map[m] === value) {
return value;
}
}
} }
if (Array.isArray(config.filter)) { err = new ArangoError();
config.filter.forEach( function (f) { err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
if (typeof f !== "function") { err.errorMessage = "invalid value for " + param;
throw "invalid filter"; throw err;
} }
});
var innerFilters = config.filter.slice(); config.uniqueness = {
vertices: validate(config.uniqueness && config.uniqueness.vertices, {
var combinedFilter = function (config, vertex, path) { global: ArangoTraverser.UNIQUE_GLOBAL,
return combineFilters(innerFilters, config, vertex, path); none: ArangoTraverser.UNIQUE_NONE,
path: ArangoTraverser.UNIQUE_PATH
}, "uniqueness.vertices"),
edges: validate(config.uniqueness && config.uniqueness.edges, {
global: ArangoTraverser.UNIQUE_GLOBAL,
none: ArangoTraverser.UNIQUE_NONE,
path: ArangoTraverser.UNIQUE_PATH
}, "uniqueness.edges")
}; };
config.filter = combinedFilter; config.strategy = validate(config.strategy, {
depthfirst: ArangoTraverser.DEPTH_FIRST,
breadthfirst: ArangoTraverser.BREADTH_FIRST
}, "strategy");
config.order = validate(config.order, {
preorder: ArangoTraverser.PRE_ORDER,
postorder: ArangoTraverser.POST_ORDER
}, "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;
} }
if (typeof config.filter !== "function") { // prepare an array of filters
throw "invalid filter"; var filters = [ ];
if (config.minDepth !== undefined && config.minDepth >= 0) {
filters.push(minDepthFilter);
}
if (config.maxDepth !== undefined && config.maxDepth > 0) {
filters.push(maxDepthFilter);
}
if (! Array.isArray(config.filter)) {
config.filter = [ 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 === 0) {
filters.push(visitAllFilter);
}
config.filter = function (config, vertex, path) {
return combineFilters(filters, config, vertex, path);
};
if (typeof config.expander !== "function") {
config.expander = validate(config.expander, {
outbound: outboundExpander,
inbound: inboundExpander,
any: anyExpander
}, "expander");
} }
if (typeof config.expander !== "function") { if (typeof config.expander !== "function") {
throw "invalid expander"; err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
err.errorMessage = "invalid expander function";
throw err;
} }
if (typeof config.datasource !== "object") { if (typeof config.datasource !== "object") {
throw "invalid datasource"; err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
err.errorMessage = "invalid datasource";
throw err;
} }
this.config = config; this.config = config;

View File

@ -65,32 +65,6 @@ function notFound (req, res, code, message) {
message); message);
} }
////////////////////////////////////////////////////////////////////////////////
/// @brief validate and translate the argument given in value
////////////////////////////////////////////////////////////////////////////////
function validateArg (value, map) {
if (value === null || value === undefined) {
var m;
// 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) {
return map[value];
}
}
return undefined;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief execute a server-side traversal /// @brief execute a server-side traversal
/// ///
@ -683,7 +657,8 @@ function post_api_traversal(req, res) {
catch (err2) { catch (err2) {
} }
if (edgeCollection === undefined || edgeCollection == null) { if (edgeCollection === undefined ||
edgeCollection === null) {
return notFound(req, res, arangodb.ERROR_ARANGO_COLLECTION_NOT_FOUND, "invalid edgeCollection"); return notFound(req, res, arangodb.ERROR_ARANGO_COLLECTION_NOT_FOUND, "invalid edgeCollection");
} }
@ -734,11 +709,7 @@ function post_api_traversal(req, res) {
var expander; var expander;
if (json.direction !== undefined) { if (json.direction !== undefined) {
expander = validateArg(json.direction, { expander = json.direction;
'outbound': traversal.outboundExpander,
'inbound': traversal.inboundExpander,
'any': traversal.anyExpander
});
} }
else if (json.expander !== undefined) { else if (json.expander !== undefined) {
try { try {
@ -761,35 +732,16 @@ function post_api_traversal(req, res) {
params: json, params: json,
edgeCollection: edgeCollection, edgeCollection: edgeCollection,
datasource: traversal.collectionDatasourceFactory(edgeCollection), datasource: traversal.collectionDatasourceFactory(edgeCollection),
strategy: validateArg(json.strategy, { strategy: json.strategy,
'depthfirst': Traverser.DEPTH_FIRST, order: json.order,
'breadthfirst': Traverser.BREADTH_FIRST itemOrder: json.itemOrder,
}),
order: validateArg(json.order, {
'preorder': Traverser.PRE_ORDER,
'postorder': Traverser.POST_ORDER
}),
itemOrder: validateArg(json.itemOrder, {
'forward': Traverser.FORWARD,
'backward': Traverser.BACKWARD
}),
expander: expander, expander: expander,
visitor: visitor, visitor: visitor,
filter: filters, filter: filters,
minDepth: json.minDepth || 0, minDepth: json.minDepth,
maxDepth: json.maxDepth, maxDepth: json.maxDepth,
uniqueness: { maxIterations: json.maxIterations,
vertices: validateArg(json.uniqueness && json.uniqueness.vertices, { uniqueness: json.uniqueness
'global': Traverser.UNIQUE_GLOBAL,
'none': Traverser.UNIQUE_NONE,
'path': Traverser.UNIQUE_PATH
}),
edges: validateArg(json.uniqueness && json.uniqueness.edges, {
'global': Traverser.UNIQUE_GLOBAL,
'none': Traverser.UNIQUE_NONE,
'path': Traverser.UNIQUE_PATH
})
}
}; };
// assemble result object // assemble result object
@ -815,12 +767,17 @@ function post_api_traversal(req, res) {
// run the traversal // run the traversal
// ----------------------------------------- // -----------------------------------------
var traverser = new Traverser(config); var traverser;
try { try {
traverser = new Traverser(config);
traverser.traverse(result, doc); traverser.traverse(result, doc);
actions.resultOk(req, res, actions.HTTP_OK, { result : result }); actions.resultOk(req, res, actions.HTTP_OK, { result : result });
} }
catch (err7) { catch (err7) {
if (traverser === undefined) {
// error during traversal setup
return badParam(req, res, err7);
}
actions.resultException(req, res, err7, undefined, false); actions.resultException(req, res, err7, undefined, false);
} }
} }

View File

@ -144,6 +144,7 @@
"ERROR_GRAPH_INVALID_EDGE" : { "code" : 1906, "message" : "invalid edge" }, "ERROR_GRAPH_INVALID_EDGE" : { "code" : 1906, "message" : "invalid edge" },
"ERROR_GRAPH_COULD_NOT_CREATE_EDGE" : { "code" : 1907, "message" : "could not create edge" }, "ERROR_GRAPH_COULD_NOT_CREATE_EDGE" : { "code" : 1907, "message" : "could not create edge" },
"ERROR_GRAPH_COULD_NOT_CHANGE_EDGE" : { "code" : 1908, "message" : "could not change edge" }, "ERROR_GRAPH_COULD_NOT_CHANGE_EDGE" : { "code" : 1908, "message" : "could not change edge" },
"ERROR_GRAPH_TOO_MANY_ITERATIONS" : { "code" : 1909, "message" : "too many iterations" },
"ERROR_SESSION_INVALID_SESSION" : { "code" : 1951, "message" : "invalid session" }, "ERROR_SESSION_INVALID_SESSION" : { "code" : 1951, "message" : "invalid session" },
"ERROR_SESSION_COULD_NOT_CREATE_SESSION" : { "code" : 1952, "message" : "could not create session" }, "ERROR_SESSION_COULD_NOT_CREATE_SESSION" : { "code" : 1952, "message" : "could not create session" },
"ERROR_SESSION_COULD_NOT_CHANGE_SESSION" : { "code" : 1953, "message" : "could not change session" }, "ERROR_SESSION_COULD_NOT_CHANGE_SESSION" : { "code" : 1953, "message" : "could not change session" },

View File

@ -30,6 +30,7 @@
var graph = require("org/arangodb/graph"); var graph = require("org/arangodb/graph");
var arangodb = require("org/arangodb"); var arangodb = require("org/arangodb");
var ArangoError = arangodb.ArangoError;
var db = arangodb.db; var db = arangodb.db;
@ -660,6 +661,7 @@ function breadthFirstSearch () {
}, },
run: function (config, result, startVertex) { run: function (config, result, startVertex) {
var maxIterations = config.maxIterations, visitCounter = 0;
var toVisit = [ { edge: null, vertex: startVertex, parentIndex: -1 } ]; var toVisit = [ { edge: null, vertex: startVertex, parentIndex: -1 } ];
var visited = { edges: { }, vertices: { } }; var visited = { edges: { }, vertices: { } };
@ -674,6 +676,13 @@ function breadthFirstSearch () {
var edge = current.edge; var edge = current.edge;
var path; 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) { if (current.visit === null || current.visit === undefined) {
current.visit = false; current.visit = false;
@ -756,12 +765,20 @@ function depthFirstSearch () {
}, },
run: function (config, result, startVertex) { run: function (config, result, startVertex) {
var maxIterations = config.maxIterations, visitCounter = 0;
var toVisit = [ { edge: null, vertex: startVertex, visit: null } ]; var toVisit = [ { edge: null, vertex: startVertex, visit: null } ];
var path = { edges: [ ], vertices: [ ] }; var path = { edges: [ ], vertices: [ ] };
var visited = { edges: { }, vertices: { } }; var visited = { edges: { }, vertices: { } };
var reverse = checkReverse(config); var reverse = checkReverse(config);
while (toVisit.length > 0) { 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 // peek at the top of the stack
var current = toVisit[toVisit.length - 1]; var current = toVisit[toVisit.length - 1];
var vertex = current.vertex; var vertex = current.vertex;
@ -870,52 +887,150 @@ ArangoTraverser = function (config) {
visitor: trackingVisitor, visitor: trackingVisitor,
filter: visitAllFilter, filter: visitAllFilter,
expander: outboundExpander, expander: outboundExpander,
datasource: null datasource: null,
maxIterations: 10000,
minDepth: 0,
maxDepth: 256
}, d; }, d;
var err;
if (typeof config !== "object") { if (typeof config !== "object") {
throw "invalid configuration"; err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
err.errorMessage = arangodb.errors.ERROR_BAD_PARAMETER.message;
throw err;
} }
// apply defaults // apply defaults
for (d in defaults) { for (d in defaults) {
if (defaults.hasOwnProperty(d)) { if (defaults.hasOwnProperty(d)) {
if (! config.hasOwnProperty(d)) { if (! config.hasOwnProperty(d) || config[d] === undefined) {
config[d] = defaults[d]; config[d] = defaults[d];
} }
} }
} }
if (typeof config.visitor !== "function") { function validate (value, map, param) {
throw "invalid visitor"; 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) {
return map[value];
}
}
for (m in map) {
if (map.hasOwnProperty(m)) {
if (map[m] === value) {
return value;
}
}
} }
if (Array.isArray(config.filter)) { err = new ArangoError();
config.filter.forEach( function (f) { err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
if (typeof f !== "function") { err.errorMessage = "invalid value for " + param;
throw "invalid filter"; throw err;
} }
});
var innerFilters = config.filter.slice(); config.uniqueness = {
vertices: validate(config.uniqueness && config.uniqueness.vertices, {
var combinedFilter = function (config, vertex, path) { global: ArangoTraverser.UNIQUE_GLOBAL,
return combineFilters(innerFilters, config, vertex, path); none: ArangoTraverser.UNIQUE_NONE,
path: ArangoTraverser.UNIQUE_PATH
}, "uniqueness.vertices"),
edges: validate(config.uniqueness && config.uniqueness.edges, {
global: ArangoTraverser.UNIQUE_GLOBAL,
none: ArangoTraverser.UNIQUE_NONE,
path: ArangoTraverser.UNIQUE_PATH
}, "uniqueness.edges")
}; };
config.filter = combinedFilter; config.strategy = validate(config.strategy, {
depthfirst: ArangoTraverser.DEPTH_FIRST,
breadthfirst: ArangoTraverser.BREADTH_FIRST
}, "strategy");
config.order = validate(config.order, {
preorder: ArangoTraverser.PRE_ORDER,
postorder: ArangoTraverser.POST_ORDER
}, "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;
} }
if (typeof config.filter !== "function") { // prepare an array of filters
throw "invalid filter"; var filters = [ ];
if (config.minDepth !== undefined && config.minDepth >= 0) {
filters.push(minDepthFilter);
}
if (config.maxDepth !== undefined && config.maxDepth > 0) {
filters.push(maxDepthFilter);
}
if (! Array.isArray(config.filter)) {
config.filter = [ 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 === 0) {
filters.push(visitAllFilter);
}
config.filter = function (config, vertex, path) {
return combineFilters(filters, config, vertex, path);
};
if (typeof config.expander !== "function") {
config.expander = validate(config.expander, {
outbound: outboundExpander,
inbound: inboundExpander,
any: anyExpander
}, "expander");
} }
if (typeof config.expander !== "function") { if (typeof config.expander !== "function") {
throw "invalid expander"; err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
err.errorMessage = "invalid expander function";
throw err;
} }
if (typeof config.datasource !== "object") { if (typeof config.datasource !== "object") {
throw "invalid datasource"; err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
err.errorMessage = "invalid datasource";
throw err;
} }
this.config = config; this.config = config;

View File

@ -1853,9 +1853,15 @@ function resultException (req, res, err, headers, verbose) {
switch (num) { switch (num) {
case arangodb.ERROR_INTERNAL: case arangodb.ERROR_INTERNAL:
case arangodb.ERROR_OUT_OF_MEMORY: case arangodb.ERROR_OUT_OF_MEMORY:
case arangodb.ERROR_GRAPH_TOO_MANY_ITERATIONS:
code = exports.HTTP_SERVER_ERROR; code = exports.HTTP_SERVER_ERROR;
break; break;
case arangodb.ERROR_ARANGO_COLLECTION_NOT_FOUND:
case arangodb.ERROR_ARANGO_DOCUMENT_NOT_FOUND:
code = exports.HTTP_NOT_FOUND;
break;
case arangodb.ERROR_ARANGO_DUPLICATE_NAME: case arangodb.ERROR_ARANGO_DUPLICATE_NAME:
case arangodb.ERROR_ARANGO_DUPLICATE_IDENTIFIER: case arangodb.ERROR_ARANGO_DUPLICATE_IDENTIFIER:
code = exports.HTTP_CONFLICT; code = exports.HTTP_CONFLICT;

View File

@ -3478,27 +3478,6 @@ function TRAVERSAL_FUNC (func, vertexCollection, edgeCollection, startVertex, di
}); });
} }
function validate (value, map) {
if (value === null || value === undefined) {
var m;
// 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) {
return map[value];
}
}
THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, func);
}
if (typeof params.visitor !== "function") { if (typeof params.visitor !== "function") {
THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, func); THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, func);
} }
@ -3508,55 +3487,16 @@ function TRAVERSAL_FUNC (func, vertexCollection, edgeCollection, startVertex, di
params.maxDepth = 256; params.maxDepth = 256;
} }
// prepare an array of filters
var filters = [ ];
if (params.minDepth !== undefined) {
filters.push(TRAVERSAL.minDepthFilter);
}
if (params.maxDepth !== undefined) {
filters.push(TRAVERSAL.maxDepthFilter);
}
if (filters.length === 0) {
filters.push(TRAVERSAL.visitAllFilter);
}
var config = { var config = {
connect: params.connect, connect: params.connect,
datasource: TRAVERSAL.collectionDatasourceFactory(edgeCollection), datasource: TRAVERSAL.collectionDatasourceFactory(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, trackPaths: params.paths || false,
visitor: params.visitor, visitor: params.visitor,
maxDepth: params.maxDepth, maxDepth: params.maxDepth,
minDepth: params.minDepth, minDepth: params.minDepth,
filter: filters, maxIterations: params.maxIterations,
uniqueness: { uniqueness: params.uniqueness,
vertices: validate(params.uniqueness && params.uniqueness.vertices, { expander: direction
'global': TRAVERSAL.Traverser.UNIQUE_GLOBAL,
'none': TRAVERSAL.Traverser.UNIQUE_NONE,
'path': TRAVERSAL.Traverser.UNIQUE_PATH
}),
edges: validate(params.uniqueness && params.uniqueness.edges, {
'global': TRAVERSAL.Traverser.UNIQUE_GLOBAL,
'none': TRAVERSAL.Traverser.UNIQUE_NONE,
'path': TRAVERSAL.Traverser.UNIQUE_PATH
})
},
expander: validate(direction, {
'outbound': TRAVERSAL.outboundExpander,
'inbound': TRAVERSAL.inboundExpander,
'any': TRAVERSAL.anyExpander
})
}; };
if (params.followEdges) { if (params.followEdges) {

View File

@ -198,7 +198,7 @@ ERROR_KEYVALUE_KEY_NOT_REMOVED,1805,"key value not removed","Will be raised when
ERROR_KEYVALUE_NO_VALUE,1806,"missing value","Will be raised when the value is missing" ERROR_KEYVALUE_NO_VALUE,1806,"missing value","Will be raised when the value is missing"
################################################################################ ################################################################################
## Graph errors ## Graph / traversal errors
################################################################################ ################################################################################
ERROR_GRAPH_INVALID_GRAPH,1901,"invalid graph","Will be raised when an invalid name is passed to the server" ERROR_GRAPH_INVALID_GRAPH,1901,"invalid graph","Will be raised when an invalid name is passed to the server"
@ -209,6 +209,7 @@ ERROR_GRAPH_COULD_NOT_CHANGE_VERTEX,1905,"could not change vertex","Will be rais
ERROR_GRAPH_INVALID_EDGE,1906,"invalid edge","Will be raised when an invalid edge id is passed to the server" ERROR_GRAPH_INVALID_EDGE,1906,"invalid edge","Will be raised when an invalid edge id is passed to the server"
ERROR_GRAPH_COULD_NOT_CREATE_EDGE,1907,"could not create edge","Will be raised when the edge could not be created" ERROR_GRAPH_COULD_NOT_CREATE_EDGE,1907,"could not create edge","Will be raised when the edge could not be created"
ERROR_GRAPH_COULD_NOT_CHANGE_EDGE,1908,"could not change edge","Will be raised when the edge could not be changed" ERROR_GRAPH_COULD_NOT_CHANGE_EDGE,1908,"could not change edge","Will be raised when the edge could not be changed"
ERROR_GRAPH_TOO_MANY_ITERATIONS,1909,"too many iterations","Will be raised when too many iterations are done in a graph traversal"
################################################################################ ################################################################################
## Session errors ## Session errors

View File

@ -140,6 +140,7 @@ void TRI_InitialiseErrorMessages (void) {
REG_ERROR(ERROR_GRAPH_INVALID_EDGE, "invalid edge"); REG_ERROR(ERROR_GRAPH_INVALID_EDGE, "invalid edge");
REG_ERROR(ERROR_GRAPH_COULD_NOT_CREATE_EDGE, "could not create edge"); REG_ERROR(ERROR_GRAPH_COULD_NOT_CREATE_EDGE, "could not create edge");
REG_ERROR(ERROR_GRAPH_COULD_NOT_CHANGE_EDGE, "could not change edge"); REG_ERROR(ERROR_GRAPH_COULD_NOT_CHANGE_EDGE, "could not change edge");
REG_ERROR(ERROR_GRAPH_TOO_MANY_ITERATIONS, "too many iterations");
REG_ERROR(ERROR_SESSION_INVALID_SESSION, "invalid session"); REG_ERROR(ERROR_SESSION_INVALID_SESSION, "invalid session");
REG_ERROR(ERROR_SESSION_COULD_NOT_CREATE_SESSION, "could not create session"); REG_ERROR(ERROR_SESSION_COULD_NOT_CREATE_SESSION, "could not create session");
REG_ERROR(ERROR_SESSION_COULD_NOT_CHANGE_SESSION, "could not change session"); REG_ERROR(ERROR_SESSION_COULD_NOT_CHANGE_SESSION, "could not change session");

View File

@ -306,6 +306,8 @@ extern "C" {
/// Will be raised when the edge could not be created /// Will be raised when the edge could not be created
/// - 1908: @LIT{could not change edge} /// - 1908: @LIT{could not change edge}
/// Will be raised when the edge could not be changed /// Will be raised when the edge could not be changed
/// - 1909: @LIT{too many iterations}
/// Will be raised when too many iterations are done in a graph traversal
/// - 1951: @LIT{invalid session} /// - 1951: @LIT{invalid session}
/// Will be raised when an invalid session id is passed to the server /// Will be raised when an invalid session id is passed to the server
/// - 1952: @LIT{could not create session} /// - 1952: @LIT{could not create session}
@ -1751,6 +1753,16 @@ void TRI_InitialiseErrorMessages (void);
#define TRI_ERROR_GRAPH_COULD_NOT_CHANGE_EDGE (1908) #define TRI_ERROR_GRAPH_COULD_NOT_CHANGE_EDGE (1908)
////////////////////////////////////////////////////////////////////////////////
/// @brief 1909: ERROR_GRAPH_TOO_MANY_ITERATIONS
///
/// too many iterations
///
/// Will be raised when too many iterations are done in a graph traversal
////////////////////////////////////////////////////////////////////////////////
#define TRI_ERROR_GRAPH_TOO_MANY_ITERATIONS (1909)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief 1951: ERROR_SESSION_INVALID_SESSION /// @brief 1951: ERROR_SESSION_INVALID_SESSION
/// ///