diff --git a/arangod/Aql/Ast.cpp b/arangod/Aql/Ast.cpp index 0942d7db71..0b46ef5439 100644 --- a/arangod/Aql/Ast.cpp +++ b/arangod/Aql/Ast.cpp @@ -123,7 +123,8 @@ Ast::Ast (Query* query) _root(nullptr), _queries(), _writeCollections(), - _functionsMayAccessDocuments(false) { + _functionsMayAccessDocuments(false), + _containsTraversal(false) { TRI_ASSERT(_query != nullptr); @@ -1230,6 +1231,8 @@ AstNode* Ast::createNodeTraversal (char const* vertexVarName, TRI_ASSERT(node->numMembers() == 4); + _containsTraversal = true; + return node; } @@ -1253,6 +1256,8 @@ AstNode* Ast::createNodeTraversal (char const* vertexVarName, node->addMember(edgeVar); TRI_ASSERT(node->numMembers() == 5); + + _containsTraversal = true; return node; } @@ -1280,6 +1285,8 @@ AstNode* Ast::createNodeTraversal (char const* vertexVarName, node->addMember(pathVar); TRI_ASSERT(node->numMembers() == 6); + + _containsTraversal = true; return node; } diff --git a/arangod/Aql/Ast.h b/arangod/Aql/Ast.h index 28199a9ad0..ece021b579 100644 --- a/arangod/Aql/Ast.h +++ b/arangod/Aql/Ast.h @@ -202,6 +202,14 @@ namespace triagens { return _functionsMayAccessDocuments; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not the query contains a traversal +//////////////////////////////////////////////////////////////////////////////// + + bool containsTraversal () const { + return _containsTraversal; + } + //////////////////////////////////////////////////////////////////////////////// /// @brief convert the AST into JSON /// the caller is responsible for freeing the JSON later @@ -998,6 +1006,12 @@ namespace triagens { bool _functionsMayAccessDocuments; +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not the query contains a traversal +//////////////////////////////////////////////////////////////////////////////// + + bool _containsTraversal; + //////////////////////////////////////////////////////////////////////////////// /// @brief a singleton no-op node instance //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/ExecutionPlan.cpp b/arangod/Aql/ExecutionPlan.cpp index 193eaeae10..95a1a5aa1f 100644 --- a/arangod/Aql/ExecutionPlan.cpp +++ b/arangod/Aql/ExecutionPlan.cpp @@ -471,12 +471,18 @@ ModificationOptions ExecutionPlan::createModificationOptions (AstNode const* nod // no functions in the query can access document data... bool isReadWrite = false; - auto const collections = _ast->query()->collections(); + if (_ast->containsTraversal()) { + // its unclear which collections the traversal will access + isReadWrite = true; + } + else { + auto const collections = _ast->query()->collections(); - for (auto const& it : *(collections->collections())) { - if (it.second->isReadWrite) { - isReadWrite = true; - break; + for (auto const& it : *(collections->collections())) { + if (it.second->isReadWrite) { + isReadWrite = true; + break; + } } } diff --git a/arangod/Aql/OptimizerRules.cpp b/arangod/Aql/OptimizerRules.cpp index 76ae61832d..f3ff93980e 100644 --- a/arangod/Aql/OptimizerRules.cpp +++ b/arangod/Aql/OptimizerRules.cpp @@ -3686,6 +3686,12 @@ int triagens::aql::patchUpdateStatementsRule (Optimizer* opt, modified = true; } } + + if (type == EN::TRAVERSAL) { + // unclear what will be read by the traversal + modified = false; + break; + } dep = dep->getFirstDependency(); } diff --git a/js/common/modules/org/arangodb/aql/explainer.js b/js/common/modules/org/arangodb/aql/explainer.js index d9e857270a..a4348ecdc7 100644 --- a/js/common/modules/org/arangodb/aql/explainer.js +++ b/js/common/modules/org/arangodb/aql/explainer.js @@ -683,10 +683,16 @@ function processQuery (query, explain) { } rc += " " + keyword("IN") + " " + - node.minMaxDepth + " " + annotation("/* min..maxPathDepth */") + " " + + value(node.minMaxDepth) + " " + annotation("/* min..maxPathDepth */") + " " + keyword("OUTBOUND") + - " '" + node.vertexId + "' " + annotation("/* Startnode */") + " " + - keyword("GRAPH") + " '" + node.graph + "'"; + " '" + value(node.vertexId) + "' " + annotation("/* startnode */") + " "; + + if (Array.isArray(node.graph)) { + rc += node.graph.map(function(g) { return collection(g); }).join(", "); + } + else { + rc += keyword("GRAPH") + " '" + value(node.graph) + "'"; + } traversalDetails.push(node); if (node.hasOwnProperty('simpleExpressions')) { diff --git a/js/server/tests/aql-multi-modify.js b/js/server/tests/aql-multi-modify.js index 08c46a222c..0af1d82c09 100644 --- a/js/server/tests/aql-multi-modify.js +++ b/js/server/tests/aql-multi-modify.js @@ -73,6 +73,43 @@ function ahuacatlMultiModifySuite () { c3 = null; }, + testTraversalAndModification : function () { + c1.insert({ _key: "1" }); + c1.insert({ _key: "2" }); + c1.insert({ _key: "3" }); + c1.insert({ _key: "4" }); + c3.insert(cn1 + "/1", cn1 + "/2", { }); + c3.insert(cn1 + "/2", cn1 + "/3", { }); + + var q = "FOR v IN 1..99 OUTBOUND '" + cn1 + "/1' @@e REMOVE v._key IN @@cn"; + var actual = AQL_EXECUTE(q, { "@cn": cn1, "@e": cn3 }); + assertEqual([ ], actual.json); + assertEqual(2, actual.stats.writesExecuted); + assertEqual(2, c1.count()); + assertTrue(c1.exists("1")); + assertTrue(c1.exists("4")); + assertEqual(2, c3.count()); + }, + + testTraversalAndModificationBig : function () { + var i; + for (i = 1; i <= 2010; ++i) { + c1.insert({ _key: String(i) }); + if (i !== 2010) { + c3.insert(cn1 + "/" + String(i), cn1 + "/" + String(i + 1), { }); + } + } + + var q = "FOR v IN 1..2010 OUTBOUND '" + cn1 + "/1' @@e REMOVE v._key IN @@cn"; + var actual = AQL_EXECUTE(q, { "@cn": cn1, "@e": cn3 }); + + assertEqual([ ], actual.json); + assertEqual(2009, actual.stats.writesExecuted); + assertEqual(1, c1.count()); + assertTrue(c1.exists("1")); + assertEqual(2009, c3.count()); + }, + testTraversalAfterModification : function () { var q = "INSERT { _key: '1', foo: 'bar' } INTO @@cn FOR doc IN OUTBOUND 'v/1' @@e RETURN doc"; assertQueryError(errors.ERROR_QUERY_ACCESS_AFTER_MODIFICATION.code, q, { "@cn": cn1, "@e": cn3 });