1
0
Fork 0

Merge branch 'aql-multi-modify' of https://github.com/arangodb/arangodb into devel

This commit is contained in:
Jan Steemann 2015-12-03 11:38:30 +01:00
commit d572f3ed03
23 changed files with 529 additions and 83 deletions

9
TODO-BRANCH Normal file
View File

@ -0,0 +1,9 @@
fix TODOs in AqlValue.cpp
fix #if 0 in grammar.y
prevent subquery and other optimizations for data-modification queries/subqueries
complete testing
write tests for read-access after modification (via functions, via collection usage etc.)
test with new traversal node
run cluster tests
run Valgrind tests

View File

@ -616,6 +616,7 @@ v8::Handle<v8::Value> AqlValue::toV8 (v8::Isolate* isolate,
}
case EMPTY: {
return v8::Null(isolate); // TODO: FIXME decide if we really want this...---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
return v8::Undefined(isolate);
}
}
@ -692,6 +693,7 @@ Json AqlValue::toJson (triagens::arango::AqlTransaction* trx,
}
case EMPTY: {
return Json(Json::Null); // TODO FIXME: decide if we really want this...--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
return triagens::basics::Json();
}
}

View File

@ -654,6 +654,19 @@ AstNode* Ast::createNodeVariable (char const* name,
}
if (_scopes.existsVariable(name, nameLength)) {
if (! isUserDefined &&
(strcmp(name, Variable::NAME_OLD) == 0 ||
strcmp(name, Variable::NAME_NEW) == 0)) {
// special variable
auto variable = _variables.createVariable(name, nameLength, isUserDefined);
_scopes.replaceVariable(variable);
AstNode* node = createNode(NODE_TYPE_VARIABLE);
node->setData(static_cast<void*>(variable));
return node;
}
_query->registerError(TRI_ERROR_QUERY_VARIABLE_REDECLARED, name);
return nullptr;
}
@ -1584,9 +1597,11 @@ AstNode* Ast::replaceVariableReference (AstNode* node,
void Ast::validateAndOptimize () {
struct TraversalContext {
int64_t stopOptimizationRequests = 0;
bool isInFilter = false;
bool hasSeenWriteNode = false;
std::unordered_set<std::string> writeCollectionsSeen;
int64_t stopOptimizationRequests = 0;
bool isInFilter = false;
bool hasSeenAnyWriteNode = false;
bool hasSeenWriteNodeInCurrentScope = false;
};
auto preVisitor = [&](AstNode const* node, void* data) -> bool {
@ -1605,6 +1620,19 @@ void Ast::validateAndOptimize () {
else if (node->hasFlag(FLAG_BIND_PARAMETER)) {
return false;
}
else if (node->type == NODE_TYPE_REMOVE ||
node->type == NODE_TYPE_INSERT ||
node->type == NODE_TYPE_UPDATE ||
node->type == NODE_TYPE_REPLACE ||
node->type == NODE_TYPE_UPSERT) {
auto c = static_cast<TraversalContext*>(data);
if (c->hasSeenWriteNodeInCurrentScope) {
// no two data-modification nodes are allowed in the same scope
THROW_ARANGO_EXCEPTION(TRI_ERROR_QUERY_MULTI_MODIFY);
}
c->hasSeenWriteNodeInCurrentScope = true;
}
return true;
};
@ -1613,13 +1641,20 @@ void Ast::validateAndOptimize () {
if (node->type == NODE_TYPE_FILTER) {
static_cast<TraversalContext*>(data)->isInFilter = false;
}
else if (node->type == NODE_TYPE_REMOVE ||
node->type == NODE_TYPE_INSERT ||
node->type == NODE_TYPE_UPDATE ||
node->type == NODE_TYPE_REPLACE ||
node->type == NODE_TYPE_UPSERT) {
auto c = static_cast<TraversalContext*>(data);
c->hasSeenAnyWriteNode = true;
if (node->type == NODE_TYPE_REMOVE ||
node->type == NODE_TYPE_INSERT ||
node->type == NODE_TYPE_UPDATE ||
node->type == NODE_TYPE_REPLACE ||
node->type == NODE_TYPE_UPSERT) {
static_cast<TraversalContext*>(data)->hasSeenWriteNode = true;
TRI_ASSERT(c->hasSeenWriteNodeInCurrentScope);
c->hasSeenWriteNodeInCurrentScope = false;
auto collection = node->getMember(1);
auto name = collection->getStringValue();
c->writeCollectionsSeen.emplace(name);
}
else if (node->type == NODE_TYPE_FCALL) {
auto func = static_cast<Function*>(node->getData());
@ -1688,7 +1723,7 @@ void Ast::validateAndOptimize () {
if (node->type == NODE_TYPE_FCALL) {
auto func = static_cast<Function*>(node->getData());
if (static_cast<TraversalContext*>(data)->hasSeenWriteNode &&
if (static_cast<TraversalContext*>(data)->hasSeenAnyWriteNode &&
! func->canRunOnDBServer) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_QUERY_ACCESS_AFTER_MODIFICATION);
}
@ -1728,9 +1763,14 @@ void Ast::validateAndOptimize () {
// collection
if (node->type == NODE_TYPE_COLLECTION) {
if (static_cast<TraversalContext*>(data)->hasSeenWriteNode) {
char const* name = node->getStringValue();
auto c = static_cast<TraversalContext*>(data);
if (c->writeCollectionsSeen.find(name) != c->writeCollectionsSeen.end()) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_QUERY_ACCESS_AFTER_MODIFICATION);
}
return node;
}

View File

@ -173,6 +173,7 @@ ConditionPart::ConditionPart (Variable const* variable,
: ConditionPart(variable, "", operatorNode, side, data) {
TRI_AttributeNamesToString(attributeNames, attributeName, false);
isExpanded = (attributeName.find("[*]") != std::string::npos);
}
ConditionPart::~ConditionPart () {
@ -249,6 +250,16 @@ bool ConditionPart::isCoveredBy (ConditionPart const& other) const {
}
}
}
else if (isExpanded &&
other.isExpanded &&
operatorType == NODE_TYPE_OPERATOR_BINARY_IN &&
other.operatorType == NODE_TYPE_OPERATOR_BINARY_IN &&
other.valueNode->isConstant()) {
if (CompareAstNodes(other.valueNode, valueNode, false) == 0) {
return true;
}
return false;
}
// Results are -1, 0, 1, move to 0, 1, 2 for the lookup:
ConditionPartCompareResult res = ConditionPart::ResultsTable
@ -1326,7 +1337,7 @@ bool Condition::canRemove (ConditionPart const& me,
if (operand->isComparisonOperator()) {
auto lhs = operand->getMember(0);
auto rhs = operand->getMember(1);
if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
std::pair<Variable const*, std::vector<triagens::basics::AttributeName>> result;

View File

@ -1459,6 +1459,30 @@ ExecutionNode* SubqueryNode::clone (ExecutionPlan* plan,
return static_cast<ExecutionNode*>(c);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not the subquery is a data-modification operation
////////////////////////////////////////////////////////////////////////////////
bool SubqueryNode::isModificationQuery () const {
std::vector<ExecutionNode*> stack({ _subquery });
while (! stack.empty()) {
auto current = stack.back();
stack.pop_back();
if (current->isModificationNode()) {
return true;
}
current->addDependencies(stack);
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief replace the out variable, so we can adjust the name.
////////////////////////////////////////////////////////////////////////////////
void SubqueryNode::replaceOutVariable(Variable const* var) {
_outVariable = var;

View File

@ -181,6 +181,15 @@ namespace triagens {
ep->_parents.emplace_back(this);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief add a parent
////////////////////////////////////////////////////////////////////////////////
void addParent (ExecutionNode* ep) {
ep->_dependencies.emplace_back(this);
_parents.emplace_back(ep);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get all dependencies
////////////////////////////////////////////////////////////////////////////////
@ -569,6 +578,15 @@ namespace triagens {
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not the node is a data modification node
////////////////////////////////////////////////////////////////////////////////
virtual bool isModificationNode () const {
// derived classes can change this
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief static analysis, walker class and information collector
////////////////////////////////////////////////////////////////////////////////
@ -1507,6 +1525,12 @@ namespace triagens {
bool withDependencies,
bool withProperties) const override final;
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not the subquery is a data-modification operation
////////////////////////////////////////////////////////////////////////////////
bool isModificationQuery () const;
////////////////////////////////////////////////////////////////////////////////
/// @brief getter for subquery
////////////////////////////////////////////////////////////////////////////////
@ -1519,9 +1543,9 @@ namespace triagens {
/// @brief setter for subquery
////////////////////////////////////////////////////////////////////////////////
void setSubquery (ExecutionNode* subquery) {
void setSubquery (ExecutionNode* subquery, bool forceOverwrite) {
TRI_ASSERT(subquery != nullptr);
TRI_ASSERT(_subquery == nullptr); // do not allow overwriting an existing subquery
TRI_ASSERT((forceOverwrite && _subquery != nullptr) || (! forceOverwrite && _subquery == nullptr));
_subquery = subquery;
}

View File

@ -1743,7 +1743,9 @@ void ExecutionPlan::unlinkNode (ExecutionNode* node,
"Cannot unlink root node of plan");
}
// adjust root node. the caller needs to make sure that a new root node gets inserted
_root = nullptr;
if (node == _root) {
_root = nullptr;
}
}
auto dep = node->getDependencies(); // Intentionally copy the vector!
@ -1855,7 +1857,7 @@ ExecutionNode* ExecutionPlan::fromJson (triagens::basics::Json const& json) {
auto subqueryNode = fromJson(subquery);
// register the just created subquery
static_cast<SubqueryNode*>(ret)->setSubquery(subqueryNode);
static_cast<SubqueryNode*>(ret)->setSubquery(subqueryNode, false);
}
}

View File

@ -185,6 +185,14 @@ namespace triagens {
_outVariableNew = nullptr;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not the node is a data modification node
////////////////////////////////////////////////////////////////////////////////
bool isModificationNode () const override {
return true;
}
// -----------------------------------------------------------------------------
// --SECTION-- protected variables
// -----------------------------------------------------------------------------

View File

@ -1707,10 +1707,16 @@ int triagens::aql::removeUnnecessaryCalculationsRule (Optimizer* opt,
}
else {
auto nn = static_cast<SubqueryNode*>(n);
if (nn->canThrow()) {
// subqueries that can throw must not be optimized away
continue;
}
if (nn->isModificationQuery()) {
// subqueries that modify data must not be optimized away
continue;
}
}
auto outvar = n->getVariablesSetHere();
@ -2316,15 +2322,23 @@ int triagens::aql::scatterInClusterRule (Optimizer* opt,
bool wasModified = false;
if (triagens::arango::ServerState::instance()->isCoordinator()) {
// find subqueries
std::unordered_map<ExecutionNode*, ExecutionNode*> subqueries;
for (auto& it : plan->findNodesOfType(ExecutionNode::SUBQUERY, true)) {
subqueries.emplace(static_cast<SubqueryNode const*>(it)->getSubquery(), it);
}
// we are a coordinator. now look in the plan for nodes of type
// EnumerateCollectionNode and IndexNode
// EnumerateCollectionNode, IndexNode and modification nodes
std::vector<ExecutionNode::NodeType> const types = {
ExecutionNode::ENUMERATE_COLLECTION,
ExecutionNode::INDEX,
ExecutionNode::INSERT,
ExecutionNode::UPDATE,
ExecutionNode::REPLACE,
ExecutionNode::REMOVE
ExecutionNode::REMOVE,
ExecutionNode::UPSERT // TODO: check if ok here
};
std::vector<ExecutionNode*> nodes(std::move(plan->findNodesOfType(types, true)));
@ -2335,13 +2349,15 @@ int triagens::aql::scatterInClusterRule (Optimizer* opt,
auto const& parents = node->getParents();
auto const& deps = node->getDependencies();
TRI_ASSERT(deps.size() == 1);
bool const isRootNode = plan->isRoot(node);
// don't do this if we are already distributing!
if (deps[0]->getType() == ExecutionNode::REMOTE &&
deps[0]->getFirstDependency()->getType() == ExecutionNode::DISTRIBUTE) {
continue;
}
plan->unlinkNode(node, isRootNode);
bool const isRootNode = plan->isRoot(node);
plan->unlinkNode(node, true);
auto const nodeType = node->getType();
@ -2408,6 +2424,13 @@ int triagens::aql::scatterInClusterRule (Optimizer* opt,
parents[0]->replaceDependency(deps[0], gatherNode);
}
// check if the node that we modified was at the end of a subquery
auto it = subqueries.find(node);
if (it != subqueries.end()) {
static_cast<SubqueryNode*>((*it).second)->setSubquery(gatherNode, true);
}
if (isRootNode) {
// if we replaced the root node, set a new root node
plan->root(gatherNode);

View File

@ -73,20 +73,6 @@ Parser::~Parser () {
bool Parser::configureWriteQuery (AstNode const* collectionNode,
AstNode* optionNode) {
// check if we currently are in a subquery
if (_ast->isInSubQuery()) {
// data modification not allowed in sub-queries
_query->registerError(TRI_ERROR_QUERY_MODIFY_IN_SUBQUERY);
return false;
}
// check current query type
if (_isModificationQuery) {
// already a data-modification query, cannot have two data-modification operations in one query
_query->registerError(TRI_ERROR_QUERY_MULTI_MODIFY);
return false;
}
// now track which collection is going to be modified
_ast->addWriteCollection(collectionNode);

View File

@ -61,7 +61,7 @@ Scope::~Scope () {
////////////////////////////////////////////////////////////////////////////////
std::string Scope::typeName () const {
return std::move(typeName(_type));
return typeName(_type);
}
////////////////////////////////////////////////////////////////////////////////
@ -268,6 +268,27 @@ void Scopes::addVariable (Variable* variable) {
_activeScopes.back()->addVariable(variable);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief replaces an existing variable in the current scope
////////////////////////////////////////////////////////////////////////////////
void Scopes::replaceVariable (Variable* variable) {
TRI_ASSERT(! _activeScopes.empty());
TRI_ASSERT(variable != nullptr);
for (auto it = _activeScopes.rbegin(); it != _activeScopes.rend(); ++it) {
auto scope = (*it);
if (scope->existsVariable(variable->name)) {
// replace existing variable
scope->addVariable(variable);
return;
}
}
THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief checks whether a variable exists in any scope
////////////////////////////////////////////////////////////////////////////////

View File

@ -241,6 +241,12 @@ namespace triagens {
void addVariable (Variable*);
////////////////////////////////////////////////////////////////////////////////
/// @brief replaces an existing variable in the current scope
////////////////////////////////////////////////////////////////////////////////
void replaceVariable (Variable*);
////////////////////////////////////////////////////////////////////////////////
/// @brief checks whether a variable exists in any scope
////////////////////////////////////////////////////////////////////////////////

View File

@ -83,6 +83,8 @@ AqlItemBlock* SubqueryBlock::getSome (size_t atLeast,
if (res.get() == nullptr) {
return nullptr;
}
bool const subqueryReturnsData = (_subquery->getPlanNode()->getType() == ExecutionNode::RETURN);
// TODO: constant and deterministic subqueries only need to be executed once
bool const subqueryIsConst = false; // TODO
@ -106,8 +108,20 @@ AqlItemBlock* SubqueryBlock::getSome (size_t atLeast,
// execute the subquery
subqueryResults = executeSubquery();
TRI_ASSERT(subqueryResults != nullptr);
if (! subqueryReturnsData) {
// remove all data from subquery result so only an
// empty array remains
for (auto& x : *subqueryResults) {
delete x;
}
subqueryResults->clear();
res->setValue(i, _outReg, AqlValue(subqueryResults));
continue;
}
try {
TRI_IF_FAILURE("SubqueryBlock::getSome") {
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);

View File

@ -211,34 +211,27 @@ void Aqlerror (YYLTYPE* locp,
%%
query:
optional_statement_block_statements return_statement {
}
| optional_statement_block_statements remove_statement optional_post_modification_block {
}
| optional_statement_block_statements insert_statement optional_post_modification_block {
}
| optional_statement_block_statements update_statement optional_post_modification_block {
}
| optional_statement_block_statements replace_statement optional_post_modification_block {
}
| optional_statement_block_statements upsert_statement optional_post_modification_block {
optional_statement_block_statements final_statement {
}
;
optional_post_modification_lets:
/* empty */ {
final_statement:
return_statement {
}
| optional_post_modification_lets let_statement {
}
;
optional_post_modification_block:
/* empty */ {
// still need to close the scope opened by the data-modification statement
| remove_statement {
parser->ast()->scopes()->endNested();
}
| optional_post_modification_lets return_statement {
// the RETURN statement will close the scope opened by the data-modification statement
| insert_statement {
parser->ast()->scopes()->endNested();
}
| update_statement {
parser->ast()->scopes()->endNested();
}
| replace_statement {
parser->ast()->scopes()->endNested();
}
| upsert_statement {
parser->ast()->scopes()->endNested();
}
;
@ -262,6 +255,16 @@ statement_block_statement:
}
| limit_statement {
}
| remove_statement {
}
| insert_statement {
}
| update_statement {
}
| replace_statement {
}
| upsert_statement {
}
;
for_statement:
@ -885,9 +888,11 @@ expression_or_query:
$$ = $1;
}
| {
#if 0
if (parser->isModificationQuery()) {
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected subquery after data-modification operation", yylloc.first_line, yylloc.first_column);
}
#endif
parser->ast()->scopes()->start(triagens::aql::AQL_SCOPE_SUBQUERY);
parser->ast()->startSubQuery();
} query {
@ -1184,9 +1189,11 @@ reference:
}
}
| T_OPEN {
#if 0
if (parser->isModificationQuery()) {
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected subquery after data-modification operation", yylloc.first_line, yylloc.first_column);
}
#endif
parser->ast()->scopes()->start(triagens::aql::AQL_SCOPE_SUBQUERY);
parser->ast()->startSubQuery();
} query T_CLOSE {

View File

@ -178,7 +178,6 @@
"ERROR_QUERY_FULLTEXT_INDEX_MISSING" : { "code" : 1571, "message" : "no suitable fulltext index found for fulltext query on '%s'" },
"ERROR_QUERY_INVALID_DATE_VALUE" : { "code" : 1572, "message" : "invalid date value" },
"ERROR_QUERY_MULTI_MODIFY" : { "code" : 1573, "message" : "multi-modify query" },
"ERROR_QUERY_MODIFY_IN_SUBQUERY" : { "code" : 1574, "message" : "modify operation in subquery" },
"ERROR_QUERY_COMPILE_TIME_OPTIONS" : { "code" : 1575, "message" : "query options must be readable at query compile time" },
"ERROR_QUERY_EXCEPTION_OPTIONS" : { "code" : 1576, "message" : "query options expected" },
"ERROR_QUERY_COLLECTION_USED_IN_EXPRESSION" : { "code" : 1577, "message" : "collection '%s' used as expression operand" },

View File

@ -178,7 +178,6 @@
"ERROR_QUERY_FULLTEXT_INDEX_MISSING" : { "code" : 1571, "message" : "no suitable fulltext index found for fulltext query on '%s'" },
"ERROR_QUERY_INVALID_DATE_VALUE" : { "code" : 1572, "message" : "invalid date value" },
"ERROR_QUERY_MULTI_MODIFY" : { "code" : 1573, "message" : "multi-modify query" },
"ERROR_QUERY_MODIFY_IN_SUBQUERY" : { "code" : 1574, "message" : "modify operation in subquery" },
"ERROR_QUERY_COMPILE_TIME_OPTIONS" : { "code" : 1575, "message" : "query options must be readable at query compile time" },
"ERROR_QUERY_EXCEPTION_OPTIONS" : { "code" : 1576, "message" : "query options expected" },
"ERROR_QUERY_COLLECTION_USED_IN_EXPRESSION" : { "code" : 1577, "message" : "collection '%s' used as expression operand" },

View File

@ -349,7 +349,7 @@ function printTraversalDetails (traversals) {
function processQuery (query, explain) {
'use strict';
var nodes = { },
parents = { },
parents = { },
rootNode = null,
maxTypeLen = 0,
maxSiteLen = 0,
@ -793,6 +793,8 @@ function processQuery (query, explain) {
};
var postHandle = function (node) {
var isLeafNode = ! parents.hasOwnProperty(node.id);
if ([ "EnumerateCollectionNode",
"EnumerateListNode",
"IndexRangeNode",
@ -800,7 +802,7 @@ function processQuery (query, explain) {
"SubqueryNode" ].indexOf(node.type) !== -1) {
level++;
}
else if (node.type === "ReturnNode" && subqueries.length > 0) {
else if (isLeafNode && subqueries.length > 0) {
level = subqueries.pop();
}
else if (node.type === "SingletonNode") {

View File

@ -27,6 +27,7 @@
/// @author Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var jsunity = require("jsunity");
var db = require("org/arangodb").db;
var internal = require("internal");

View File

@ -0,0 +1,284 @@
/*jshint globalstrict:false, strict:false, sub: true, maxlen: 500 */
/*global assertEqual, assertFalse, assertNull, assertNotNull, assertTrue,
assertNotEqual, assertUndefined, fail, AQL_EXECUTE */
////////////////////////////////////////////////////////////////////////////////
/// @brief tests for multi-modify operations
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
// TODO: test usage of OLD/NEW etc. in queries
var internal = require("internal");
var db = require("org/arangodb").db;
var jsunity = require("jsunity");
var helper = require("org/arangodb/aql-helper");
var getModifyQueryResults = helper.getModifyQueryResults;
var getModifyQueryResultsRaw = helper.getModifyQueryResultsRaw;
var isEqual = helper.isEqual;
var assertQueryError = helper.assertQueryError;
var errors = internal.errors;
var sanitizeStats = function (stats) {
// remove these members from the stats because they don't matter
// for the comparisons
delete stats.scannedFull;
delete stats.scannedIndex;
delete stats.filtered;
return stats;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function ahuacatlMultiModifySuite () {
var errors = internal.errors;
var cn1 = "UnitTestsAhuacatlModify1";
var cn2 = "UnitTestsAhuacatlModify2";
var cn3 = "UnitTestsAhuacatlModify3";
var c1, c2, c3;
return {
////////////////////////////////////////////////////////////////////////////////
/// @brief set up
////////////////////////////////////////////////////////////////////////////////
setUp : function () {
db._drop(cn1);
db._drop(cn2);
db._drop(cn3);
c1 = db._create(cn1);
c2 = db._create(cn2);
c3 = db._create(cn3);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief tear down
////////////////////////////////////////////////////////////////////////////////
tearDown : function () {
db._drop(cn1);
db._drop(cn2);
db._drop(cn3);
c1 = null;
c2 = null;
c3 = null;
},
testMultiInsertSameCollection : function () {
var q = "INSERT { value: 1 } INTO @@cn INSERT { value: 2 } INTO @@cn";
assertQueryError(errors.ERROR_QUERY_ACCESS_AFTER_MODIFICATION.code, q, { "@cn": cn1 });
},
testMultiInsertOtherCollection : function () {
var q = "INSERT { value: 1 } INTO @@cn1 INSERT { value: 2 } INTO @@cn2";
var actual = AQL_EXECUTE(q, { "@cn1": cn1, "@cn2": cn2 });
assertEqual([ ], actual.json);
assertEqual(2, actual.stats.writesExecuted);
assertEqual(1, c1.count());
assertEqual(1, c1.any().value);
assertEqual(1, c2.count());
assertEqual(2, c2.any().value);
},
testMultiInsertLoopSameCollection : function () {
var q = "FOR i IN 1..10 INSERT { value: i } INTO @@cn INSERT { value: i + 1 } INTO @@cn";
assertQueryError(errors.ERROR_QUERY_ACCESS_AFTER_MODIFICATION.code, q, {"@cn": cn1 });
},
testMultiInsertLoopOtherCollection : function () {
var q = "FOR i IN 1..10 INSERT { value: i } INTO @@cn1 INSERT { value: i + 1 } INTO @@cn2";
var actual = AQL_EXECUTE(q, { "@cn1": cn1, "@cn2": cn2 });
assertEqual([ ], actual.json);
assertEqual(20, actual.stats.writesExecuted);
assertEqual(10, c1.count());
assertEqual(10, c2.count());
},
testMultiInsertLoopSubquery : function () {
var q = "FOR i IN 1..10 LET sub = (FOR j IN 1..i INSERT { value: j } INTO @@cn) RETURN 1";
var actual = AQL_EXECUTE(q, { "@cn": cn1 });
assertEqual(10, actual.json.length);
assertEqual(55, actual.stats.writesExecuted);
assertEqual(55, c1.count());
},
testMultiInsertLoopSubquerySameCollection : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { value: i } INTO @@cn", { "@cn" : cn1 });
var q = "FOR i IN @@cn LET sub = (FOR j IN 1..2 INSERT { value: j } INTO @@cn) RETURN 1";
var actual = AQL_EXECUTE(q, { "@cn": cn1 });
assertEqual(10, actual.json.length);
assertEqual(20, actual.stats.writesExecuted);
assertEqual(30, c1.count());
},
testMultiInsertLoopSubqueryOtherCollection : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { value: i } INTO @@cn", { "@cn" : cn1 });
var q = "FOR i IN @@cn1 LET sub = (FOR j IN 1..2 INSERT { value: j } INTO @@cn2) RETURN 1";
var actual = AQL_EXECUTE(q, { "@cn1": cn1, "@cn2" : cn2 });
assertEqual(10, actual.json.length);
assertEqual(20, actual.stats.writesExecuted);
assertEqual(10, c1.count());
assertEqual(20, c2.count());
},
testMultiInsertLoopSubquerySameCollectionIndependent : function () {
// AQL_EXECUTE("FOR i IN 1..10 INSERT { value: i } INTO @@cn", { "@cn" : cn1 });
var q = "FOR i IN @@cn LET sub1 = (FOR j IN 1..2 INSERT { value: j } INTO @@cn) LET sub2 = (FOR j IN 1..2 INSERT { value: j } INTO @@cn) RETURN 1";
assertQueryError(errors.ERROR_QUERY_ACCESS_AFTER_MODIFICATION.code, q, {"@cn": cn1 });
// var actual = AQL_EXECUTE(q, { "@cn": cn1 });
// assertEqual(10, actual.json.length);
// assertEqual(40, actual.stats.writesExecuted);
// assertEqual(50, c1.count());
},
testMultiRemoveSameCollection : function () {
var q = "INSERT { value: 1 } INTO @@cn LET doc = NEW REMOVE doc._key INTO @@cn";
assertQueryError(errors.ERROR_QUERY_ACCESS_AFTER_MODIFICATION.code, q, { "@cn": cn1 });
},
testMultiRemoveOtherCollection : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { _key: CONCAT('test', i) } INTO @@cn", { "@cn" : cn2 });
var q = "INSERT { value: 1 } INTO @@cn1 REMOVE { _key: 'test9' } INTO @@cn2";
var actual = AQL_EXECUTE(q, { "@cn1": cn1, "@cn2": cn2 });
assertEqual([ ], actual.json);
assertEqual(2, actual.stats.writesExecuted);
assertEqual(1, c1.count());
assertEqual(1, c1.any().value);
assertEqual(9, c2.count());
assertFalse(c2.exists('test9'));
assertTrue(c2.exists('test10'));
},
testMultiRemoveLoopSameCollection : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { _key: CONCAT('test', i) } INTO @@cn", { "@cn" : cn1 });
var q = "FOR i IN 1..10 INSERT { value: i } INTO @@cn REMOVE { _key: CONCAT('test', i) } INTO @@cn";
assertQueryError(errors.ERROR_QUERY_ACCESS_AFTER_MODIFICATION.code, q, {"@cn": cn1 });
},
testMultiRemoveLoopOtherCollection : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { _key: CONCAT('test', i) } INTO @@cn", { "@cn" : cn2 });
var q = "FOR i IN 1..10 INSERT { value: i } INTO @@cn1 REMOVE { _key: CONCAT('test', i) } INTO @@cn2";
var actual = AQL_EXECUTE(q, { "@cn1": cn1, "@cn2": cn2 });
assertEqual([ ], actual.json);
assertEqual(20, actual.stats.writesExecuted);
assertEqual(10, c1.count());
assertEqual(0, c2.count());
},
testMultiRemoveLoopSubquery : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { _key: CONCAT('test', i) } INTO @@cn", { "@cn" : cn1 });
var q = "FOR i IN 1..10 LET sub = (REMOVE { _key: CONCAT('test', i) } INTO @@cn) RETURN 1";
var actual = AQL_EXECUTE(q, { "@cn": cn1 });
assertEqual(10, actual.json.length);
assertEqual(10, actual.stats.writesExecuted);
assertEqual(0, c1.count());
},
testMultiRemoveLoopSubquerySameCollection : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { _key: CONCAT('test', i) } INTO @@cn", { "@cn" : cn1 });
var q = "FOR i IN @@cn LET sub = (REMOVE { _key: i._key } INTO @@cn) RETURN 1";
var actual = AQL_EXECUTE(q, { "@cn": cn1 });
assertEqual(10, actual.json.length);
assertEqual(10, actual.stats.writesExecuted);
assertEqual(0, c1.count());
},
testMultiRemoveLoopSubqueryOtherCollection : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { _key: CONCAT('test', i) } INTO @@cn", { "@cn" : cn1 });
AQL_EXECUTE("FOR i IN 1..10 INSERT { _key: CONCAT('test', i) } INTO @@cn", { "@cn" : cn2 });
var q = "FOR i IN @@cn1 LET sub = (REMOVE { _key: i._key } INTO @@cn2) RETURN 1";
var actual = AQL_EXECUTE(q, { "@cn1": cn1, "@cn2" : cn2 });
assertEqual(10, actual.json.length);
assertEqual(10, actual.stats.writesExecuted);
assertEqual(10, c1.count());
assertEqual(0, c2.count());
},
testMultiRemoveLoopSubquerySameCollectionIndependent : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { _key: CONCAT('test', i) } INTO @@cn", { "@cn" : cn1 });
var q = "FOR i IN @@cn LET sub1 = (INSERT { _key: CONCAT('test', i) } INTO @@cn) LET sub2 = (REMOVE { _key: CONCAT('test', i) } INTO @@cn) RETURN 1";
assertQueryError(errors.ERROR_QUERY_ACCESS_AFTER_MODIFICATION.code, q, {"@cn": cn1 });
// var actual = AQL_EXECUTE(q, { "@cn": cn1 });
// assertEqual(10, actual.json.length);
// assertEqual(40, actual.stats.writesExecuted);
// assertEqual(50, c1.count());
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test subquery
////////////////////////////////////////////////////////////////////////////////
/*
testRemoveInSubqueryNoResult : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { value: i } INTO @@cn", { "@cn" : cn1 });
var actual = AQL_EXECUTE("FOR doc IN @@cn SORT doc.value LET f = (REMOVE doc IN @@cn) RETURN f", { "@cn" : cn1 }).json;
var expected = [ ];
for (var i = 1; i <= 10; ++i) {
expected.push([ ]);
}
require("internal").print(actual);
assertEqual(expected, actual);
},
*/
testRemoveInSubqueryReturnKeys : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { value: i } INTO @@cn", { "@cn" : cn1 });
var actual = AQL_EXECUTE("FOR doc IN @@cn SORT doc.value LET f = (REMOVE doc IN @@cn RETURN OLD.value) RETURN f", { "@cn" : cn1 }).json;
var expected = [ ];
for (var i = 1; i <= 10; ++i) {
expected.push([ i ]);
}
assertEqual(expected, actual);
},
testRemoveInSubqueryReturnKeysDoc : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { value: i } INTO @@cn", { "@cn" : cn1 });
var actual = AQL_EXECUTE("FOR doc IN @@cn SORT doc.value LET f = (REMOVE doc IN @@cn RETURN OLD) RETURN f[0].value", { "@cn" : cn1 }).json;
var expected = [ ];
for (var i = 1; i <= 10; ++i) {
expected.push(i);
}
assertEqual(expected, actual);
},
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suites
////////////////////////////////////////////////////////////////////////////////
jsunity.run(ahuacatlMultiModifySuite);
return jsunity.done();
// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
// End:

View File

@ -219,12 +219,11 @@ ERROR_QUERY_GEO_INDEX_MISSING,1570,"no suitable geo index found for geo restrict
ERROR_QUERY_FULLTEXT_INDEX_MISSING,1571,"no suitable fulltext index found for fulltext query on '%s'","Will be raised when a fulltext query is performed on a collection without a suitable fulltext index."
ERROR_QUERY_INVALID_DATE_VALUE,1572,"invalid date value","Will be raised when a value cannot be converted to a date."
ERROR_QUERY_MULTI_MODIFY,1573,"multi-modify query", "Will be raised when an AQL query contains more than one data-modifying operation."
ERROR_QUERY_MODIFY_IN_SUBQUERY,1574,"modify operation in subquery", "Will be raised when an AQL query contains a data-modifying operation inside a subquery."
ERROR_QUERY_COMPILE_TIME_OPTIONS,1575,"query options must be readable at query compile time", "Will be raised when an AQL data-modification query contains options that cannot be figured out at query compile time."
ERROR_QUERY_EXCEPTION_OPTIONS,1576,"query options expected", "Will be raised when an AQL data-modification query contains an invalid options specification."
ERROR_QUERY_COLLECTION_USED_IN_EXPRESSION,1577,"collection '%s' used as expression operand", "Will be raised when a collection is used as an operand in an AQL expression."
ERROR_QUERY_DISALLOWED_DYNAMIC_CALL,1578,"disallowed dynamic call to '%s'", "Will be raised when a dynamic function call is made to a function that cannot be called dynamically."
ERROR_QUERY_ACCESS_AFTER_MODIFICATION,1579,"access after data-modification", "Will be raised when collection data is accessed after a data-modification query part."
ERROR_QUERY_ACCESS_AFTER_MODIFICATION,1579,"access after data-modification", "Will be raised when collection data are accessed after a data-modification operation."
################################################################################
## AQL user functions

View File

@ -174,7 +174,6 @@ void TRI_InitializeErrorMessages () {
REG_ERROR(ERROR_QUERY_FULLTEXT_INDEX_MISSING, "no suitable fulltext index found for fulltext query on '%s'");
REG_ERROR(ERROR_QUERY_INVALID_DATE_VALUE, "invalid date value");
REG_ERROR(ERROR_QUERY_MULTI_MODIFY, "multi-modify query");
REG_ERROR(ERROR_QUERY_MODIFY_IN_SUBQUERY, "modify operation in subquery");
REG_ERROR(ERROR_QUERY_COMPILE_TIME_OPTIONS, "query options must be readable at query compile time");
REG_ERROR(ERROR_QUERY_EXCEPTION_OPTIONS, "query options expected");
REG_ERROR(ERROR_QUERY_COLLECTION_USED_IN_EXPRESSION, "collection '%s' used as expression operand");

View File

@ -429,9 +429,6 @@
/// - 1573: @LIT{multi-modify query}
/// "Will be raised when an AQL query contains more than one data-modifying
/// operation."
/// - 1574: @LIT{modify operation in subquery}
/// "Will be raised when an AQL query contains a data-modifying operation
/// inside a subquery."
/// - 1575: @LIT{query options must be readable at query compile time}
/// "Will be raised when an AQL data-modification query contains options
/// that cannot be figured out at query compile time."
@ -445,8 +442,8 @@
/// "Will be raised when a dynamic function call is made to a function that
/// cannot be called dynamically."
/// - 1579: @LIT{access after data-modification}
/// "Will be raised when collection data is accessed after a
/// data-modification query part."
/// "Will be raised when collection data are accessed after a
/// data-modification operation."
/// - 1580: @LIT{invalid user function name}
/// Will be raised when a user function with an invalid name is registered.
/// - 1581: @LIT{invalid user function code}
@ -2433,17 +2430,6 @@ void TRI_InitializeErrorMessages ();
#define TRI_ERROR_QUERY_MULTI_MODIFY (1573)
////////////////////////////////////////////////////////////////////////////////
/// @brief 1574: ERROR_QUERY_MODIFY_IN_SUBQUERY
///
/// modify operation in subquery
///
/// "Will be raised when an AQL query contains a data-modifying operation
/// inside a subquery."
////////////////////////////////////////////////////////////////////////////////
#define TRI_ERROR_QUERY_MODIFY_IN_SUBQUERY (1574)
////////////////////////////////////////////////////////////////////////////////
/// @brief 1575: ERROR_QUERY_COMPILE_TIME_OPTIONS
///
@ -2493,8 +2479,8 @@ void TRI_InitializeErrorMessages ();
///
/// access after data-modification
///
/// "Will be raised when collection data is accessed after a data-modification
/// query part."
/// "Will be raised when collection data are accessed after a
/// data-modification operation."
////////////////////////////////////////////////////////////////////////////////
#define TRI_ERROR_QUERY_ACCESS_AFTER_MODIFICATION (1579)

View File

@ -678,7 +678,7 @@ std::string TRI_ObjectToString (v8::Handle<v8::Value> const value) {
return "";
}
return std::move(std::string(*utf8Value, utf8Value.length()));
return std::string(*utf8Value, utf8Value.length());
}
////////////////////////////////////////////////////////////////////////////////