1
0
Fork 0

Bug fix/internal issue 2658 (#5760)

This commit is contained in:
Michael Hackstein 2018-07-04 21:51:24 +02:00 committed by Jan
parent 7fc4be14e1
commit 131114292b
11 changed files with 800 additions and 8 deletions

View File

@ -1,6 +1,11 @@
v3.3.12 (XXXX-XX-XX)
--------------------
* fixed internal issue #2658: AQL modification queries did not allow `_rev`
checking. There is now a new option `ignoreRevs` which can be set to `false`
in order to force AQL modification queries to match revision ids before
doing any modifications
* fixed issue #5679: Replication applier restrictions will crash synchronisation
after initial sync

View File

@ -86,6 +86,16 @@ FOR i IN 1..1000
REMOVE { _key: CONCAT('test', i) } IN users OPTIONS { waitForSync: true }
```
In order to not accidentially remove documents that have been updated since you last fetched
them, you can use the option *ignoreRevs* to either let ArangoDB compare the `_rev` values and
only succeed if they still match, or let ArangoDB ignore them (default):
```
FOR i IN 1..1000
REMOVE { _key: CONCAT('test', i), _rev: "1287623" } IN users OPTIONS { ignoreRevs: false }
```
### Returning the removed documents
The removed documents can also be returned by the query. In this case, the `REMOVE`

View File

@ -100,6 +100,15 @@ FOR i IN 1..1000
REPLACE { _key: CONCAT('test', i) } WITH { foobar: true } IN users OPTIONS { waitForSync: true }
```
In order to not accidentially overwrite documents that have been updated since you last fetched
them, you can use the option *ignoreRevs* to either let ArangoDB compare the `_rev` value and only
succeed if they still match, or let ArangoDB ignore them (default):
```
FOR i IN 1..1000
REPLACE { _key: CONCAT('test', i), _rev: "1287623" } WITH { foobar: true } IN users OPTIONS { ignoreRevs: false }
```
### Returning the modified documents
The modified documents can also be returned by the query. In this case, the `REPLACE`

View File

@ -207,6 +207,18 @@ FOR u IN users
} IN users OPTIONS { waitForSync: true }
```
In order to not accidentially overwrite documents that have been updated since you last fetched
them, you can use the option *ignoreRevs* to either let ArangoDB compare the `_rev` value and
only succeed if they still match, or let ArangoDB ignore them (default):
```js
FOR i IN 1..1000
UPDATE { _key: CONCAT('test', i), _rev: "1287623" }
WITH { foobar: true } IN users
OPTIONS { ignoreRevs: false }
```
### Returning the modified documents
The modified documents can also be returned by the query. In this case, the `UPDATE`

View File

@ -81,6 +81,22 @@ explicitly.
To make sure data are durable when an update query returns, there is the *waitForSync*
query option.
In order to not accidentially update documents that have been written and updated since
you last fetched them you can use the option *ignoreRevs* to either let ArangoDB compare
the `_rev` value and only succeed if they still match, or let ArangoDB ignore them (default):
```
FOR i IN 1..1000
UPSERT { _key: CONCAT('test', i)}
INSERT {foobar: false}
UPDATE {_rev: "1287623", foobar: true }
IN users OPTIONS { ignoreRevs: false }
```
*NOTE*: You need to add the `_rev` value in the updateExpression, it will not be used within
the searchExpression. Even worse, if you use an outdated `_rev` in the searchExpression
UPSERT will trigger the INSERT path instead of the UPDATE path, because it has not found a document
exactly matching the searchExpression.
### Returning documents

View File

@ -516,6 +516,8 @@ ModificationOptions ExecutionPlan::createModificationOptions(
options.nullMeansRemove = value->isFalse();
} else if (name == "mergeObjects") {
options.mergeObjects = value->isTrue();
} else if (name == "ignoreRevs") {
options.ignoreRevs = value->isTrue();
}
}
}

View File

@ -179,6 +179,33 @@ int ModificationBlock::extractKey(AqlValue const& value,
return TRI_ERROR_ARANGO_DOCUMENT_KEY_MISSING;
}
int ModificationBlock::extractKeyAndRev(AqlValue const& value,
std::string& key,
std::string& rev) {
if (value.isObject()) {
bool mustDestroy;
AqlValue sub = value.get(_trx, StaticStrings::KeyString, mustDestroy, false);
AqlValueGuard guard(sub, mustDestroy);
if (sub.isString()) {
key.assign(sub.slice().copyString());
bool mustDestroyToo;
AqlValue subTwo = value.get(_trx, StaticStrings::RevString, mustDestroyToo, false);
AqlValueGuard guard(subTwo, mustDestroyToo);
if (subTwo.isString()) {
rev.assign(subTwo.slice().copyString());
}
return TRI_ERROR_NO_ERROR;
}
} else if (value.isString()) {
key.assign(value.slice().copyString());
return TRI_ERROR_NO_ERROR;
}
return TRI_ERROR_ARANGO_DOCUMENT_KEY_MISSING;
}
/// @brief process the result of a data-modification operation
void ModificationBlock::handleResult(int code, bool ignoreErrors,
std::string const* errorMessage) {
@ -290,7 +317,7 @@ AqlItemBlock* RemoveBlock::work(std::vector<AqlItemBlock*>& blocks) {
OperationOptions options;
options.silent = !producesOutput;
options.waitForSync = ep->_options.waitForSync;
options.ignoreRevs = true;
options.ignoreRevs = ep->getOptions().ignoreRevs;
options.returnOld = producesOutput;
options.isRestore = ep->getOptions().useIsRestore;
@ -310,6 +337,7 @@ AqlItemBlock* RemoveBlock::work(std::vector<AqlItemBlock*>& blocks) {
}
std::string key;
std::string rev;
int errorCode = TRI_ERROR_NO_ERROR;
// loop over the complete block
// build the request block
@ -324,9 +352,15 @@ AqlItemBlock* RemoveBlock::work(std::vector<AqlItemBlock*>& blocks) {
if (!ep->_options.consultAqlWriteFilter ||
!_collection->getCollection()->skipForAqlWrite(a.slice(), "")) {
if (a.isObject()) {
// value is an object. now extract the _key attribute
key.clear();
errorCode = extractKey(a, key);
if (!options.ignoreRevs) {
rev.clear();
// value is an object. now extract the _key and _rev attribute
errorCode = extractKeyAndRev(a, key, rev);
} else {
// value is an object. now extract the _key attribute
errorCode = extractKey(a, key);
}
} else if (a.isString()) {
// value is a string
key = a.slice().copyString();
@ -339,6 +373,9 @@ AqlItemBlock* RemoveBlock::work(std::vector<AqlItemBlock*>& blocks) {
// create a slice for the key
keyBuilder.openObject();
keyBuilder.add(StaticStrings::KeyString, VPackValue(key));
if (!options.ignoreRevs && !rev.empty()) {
keyBuilder.add(StaticStrings::RevString, VPackValue(rev));
}
keyBuilder.close();
} else {
// We have an error, handle it
@ -605,7 +642,7 @@ AqlItemBlock* UpdateBlock::work(std::vector<AqlItemBlock*>& blocks) {
options.keepNull = !ep->_options.nullMeansRemove;
options.returnOld = (producesOutput && ep->_outVariableOld != nullptr);
options.returnNew = (producesOutput && ep->_outVariableNew != nullptr);
options.ignoreRevs = true;
options.ignoreRevs = ep->getOptions().ignoreRevs;
options.isRestore = ep->getOptions().useIsRestore;
// loop over all blocks
@ -813,7 +850,7 @@ AqlItemBlock* UpsertBlock::work(std::vector<AqlItemBlock*>& blocks) {
options.mergeObjects = ep->_options.mergeObjects;
options.keepNull = !ep->_options.nullMeansRemove;
options.returnNew = producesOutput;
options.ignoreRevs = true;
options.ignoreRevs = ep->getOptions().ignoreRevs;
options.isRestore = ep->getOptions().useIsRestore;
VPackBuilder keyBuilder;
@ -1073,7 +1110,7 @@ AqlItemBlock* ReplaceBlock::work(std::vector<AqlItemBlock*>& blocks) {
options.keepNull = !ep->_options.nullMeansRemove;
options.returnOld = (producesOutput && ep->_outVariableOld != nullptr);
options.returnNew = (producesOutput && ep->_outVariableNew != nullptr);
options.ignoreRevs = true;
options.ignoreRevs = ep->getOptions().ignoreRevs;
options.isRestore = ep->getOptions().useIsRestore;
// loop over all blocks

View File

@ -49,7 +49,10 @@ class ModificationBlock : public ExecutionBlock {
virtual AqlItemBlock* work(std::vector<AqlItemBlock*>&) = 0;
/// @brief extract a key from the AqlValue passed
int extractKey(AqlValue const&, std::string&);
int extractKey(AqlValue const&, std::string& key);
/// @brief extract a key and rev from the AqlValue passed
int extractKeyAndRev(AqlValue const&, std::string& key, std::string& rev);
/// @brief process the result of a data-modification operation
void handleResult(int, bool, std::string const* errorMessage = nullptr);

View File

@ -44,6 +44,8 @@ ModificationOptions::ModificationOptions(VPackSlice const& slice) {
basics::VelocyPackHelper::getBooleanValue(obj, "useIsRestore", false);
consultAqlWriteFilter =
basics::VelocyPackHelper::getBooleanValue(obj, "consultAqlWriteFilter", false);
ignoreRevs =
basics::VelocyPackHelper::getBooleanValue(obj, "ignoreRevs", true);
}
void ModificationOptions::toVelocyPack(VPackBuilder& builder) const {
@ -56,4 +58,5 @@ void ModificationOptions::toVelocyPack(VPackBuilder& builder) const {
builder.add("readCompleteInput", VPackValue(readCompleteInput));
builder.add("useIsRestore", VPackValue(useIsRestore));
builder.add("consultAqlWriteFilter", VPackValue(consultAqlWriteFilter));
builder.add("ignoreRevs", VPackValue(ignoreRevs));
}

View File

@ -45,7 +45,8 @@ struct ModificationOptions {
ignoreDocumentNotFound(false),
readCompleteInput(true),
useIsRestore(false),
consultAqlWriteFilter(false) {}
consultAqlWriteFilter(false),
ignoreRevs(true) {}
void toVelocyPack(arangodb::velocypack::Builder&) const;
@ -57,6 +58,7 @@ struct ModificationOptions {
bool readCompleteInput;
bool useIsRestore;
bool consultAqlWriteFilter;
bool ignoreRevs;
};
} // namespace arangodb::aql

View File

@ -0,0 +1,693 @@
/*jshint globalstrict:false, strict:false, maxlen: 500 */
/*global assertEqual, assertNotEqual, assertTrue, assertFalse, assertNull, assertMatch, fail, AQL_EXECUTE */
////////////////////////////////////////////////////////////////////////////////
/// @brief tests for query language, modification blocks
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2018-2018 ArangoDB 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 ArangoDB GmbH, Cologne, Germany
///
/// @author Michael Hackstein
/// @author Copyright 2018, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
const internal = require("internal");
const db = require("@arangodb").db;
const jsunity = require("jsunity");
const helper = require("@arangodb/aql-helper");
const errors = internal.errors;
const collectionName = "UnitTestAqlModify";
let col;
////////////////////////////////////////////////////////////////////////////////
/// @brief helper functions
////////////////////////////////////////////////////////////////////////////////
let invCounter = 0;
const genInvalidValue = function () {
++invCounter;
return `invalid${invCounter}`;
};
const setUp = function () {
tearDown();
col = db._create(collectionName, {numberOfShards: 3});
let list = [];
for (let i = 0; i < 2000; ++i) {
list.push({val: i});
}
col.save(list);
assertEqual(2000, col.count());
};
const tearDown = function () {
db._drop(collectionName);
col = null;
};
const buildSetOfDocs = function (count = 0) {
if (count <= 0 || count >= 2000) {
return new Set(col.toArray());
}
const query = `FOR d IN ${collectionName} LIMIT ${count} RETURN d`;
return new Set(db._query(query).toArray());
};
const validateDocsAreUpdated = function (docs, invalid, areUpdated) {
for (let d of docs) {
const nowStored = col.document(d._key);
if (areUpdated) {
assertEqual(invalid, nowStored.val, `Did not updated ${JSON.stringify(d)} using wrong _rev value`);
} else {
assertNotEqual(invalid, nowStored.val, `Updated ${JSON.stringify(d)} using wrong _rev value`);
}
}
};
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function aqlUpdateOptionsSuite () {
return {
setUp, tearDown,
testUpdateSingleWithInvalidRev : function () {
const invalid = genInvalidValue();
let q = `UPDATE {_key: @key, _rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
try {
db._query(q, {key: d._key});
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
}
validateDocsAreUpdated(docs, invalid, false);
},
testUpdateManyWithInvalidRev : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs UPDATE {_key: doc._key, _rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs(10);
try {
db._query(q, {docs: [...docs]});
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
validateDocsAreUpdated(docs, invalid, false);
},
testUpdateEnumerationWithInvalidRev : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
UPDATE {_key: doc._key, _rev: "invalid", val: "${invalid}"}
IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs();
try {
db._query(q);
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
validateDocsAreUpdated(docs, invalid, false);
},
testUpdateSingleWithInvalidRevIgnore : function () {
const invalid = genInvalidValue();
let q = `UPDATE {_key: @key, _rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
db._query(q, {key: d._key});
}
validateDocsAreUpdated(docs, invalid, true);
},
testUpdateManyWithInvalidRevIgnore : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs UPDATE {_key: doc._key, _rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs(10);
db._query(q, {docs: [...docs]});
validateDocsAreUpdated(docs, invalid, true);
},
testUpdateEnumerationWithInvalidRevIgnore : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
UPDATE {_key: doc._key, _rev: "invalid", val: "${invalid}"}
IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs();
db._query(q);
validateDocsAreUpdated(docs, invalid, true);
},
testUpdateSingleWithInvalidRevDefault : function () {
const invalid = genInvalidValue();
let q = `UPDATE {_key: @key, _rev: "invalid", val: "${invalid}"} IN ${collectionName}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
db._query(q, {key: d._key});
}
validateDocsAreUpdated(docs, invalid, true);
},
testUpdateManyWithInvalidRevDefault : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs UPDATE {_key: doc._key, _rev: "invalid", val: "${invalid}"} IN ${collectionName}`;
let docs = buildSetOfDocs(10);
db._query(q, {docs: [...docs]});
validateDocsAreUpdated(docs, invalid, true);
},
testUpdateEnumerationWithInvalidRevDefault : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
UPDATE {_key: doc._key, _rev: "invalid", val: "${invalid}"}
IN ${collectionName}`;
let docs = buildSetOfDocs();
db._query(q);
validateDocsAreUpdated(docs, invalid, true);
}
};
};
function aqlReplaceOptionsSuite () {
return {
setUp, tearDown,
testReplaceSingleWithInvalidRev : function () {
const invalid = genInvalidValue();
let q = `REPLACE {_key: @key, _rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
try {
db._query(q, {key: d._key});
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
}
validateDocsAreUpdated(docs, invalid, false);
},
testReplaceManyWithInvalidRev : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs REPLACE {_key: doc._key, _rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs(10);
try {
db._query(q, {docs: [...docs]});
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
validateDocsAreUpdated(docs, invalid, false);
},
testReplaceEnumerationWithInvalidRev : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
REPLACE {_key: doc._key, _rev: "invalid", val: "${invalid}"}
IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs();
try {
db._query(q);
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
validateDocsAreUpdated(docs, invalid, false);
},
testReplaceSingleWithInvalidRevIgnore : function () {
const invalid = genInvalidValue();
let q = `REPLACE {_key: @key, _rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
db._query(q, {key: d._key});
}
validateDocsAreUpdated(docs, invalid, true);
},
testReplaceManyWithInvalidRevIgnore : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs REPLACE {_key: doc._key, _rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs(10);
db._query(q, {docs: [...docs]});
validateDocsAreUpdated(docs, invalid, true);
},
testReplaceEnumerationWithInvalidRevIgnore : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
REPLACE {_key: doc._key, _rev: "invalid", val: "${invalid}"}
IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs();
db._query(q);
validateDocsAreUpdated(docs, invalid, true);
},
testReplaceSingleWithInvalidRevDefault : function () {
const invalid = genInvalidValue();
let q = `REPLACE {_key: @key, _rev: "invalid", val: "${invalid}"} IN ${collectionName}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
db._query(q, {key: d._key});
}
validateDocsAreUpdated(docs, invalid, true);
},
testReplaceManyWithInvalidRevDefault : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs REPLACE {_key: doc._key, _rev: "invalid", val: "${invalid}"} IN ${collectionName}`;
let docs = buildSetOfDocs(10);
db._query(q, {docs: [...docs]});
validateDocsAreUpdated(docs, invalid, true);
},
testReplaceEnumerationWithInvalidRevDefault : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
REPLACE {_key: doc._key, _rev: "invalid", val: "${invalid}"}
IN ${collectionName}`;
let docs = buildSetOfDocs();
db._query(q);
validateDocsAreUpdated(docs, invalid, true);
}
};
};
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function aqlRemoveOptionsSuite () {
return {
setUp, tearDown,
testRemoveSingleWithInvalidRev : function () {
const invalid = genInvalidValue();
let q = `REMOVE {_key: @key, _rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
try {
db._query(q, {key: d._key});
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
}
assertEqual(2000, col.count(), `We removed the document`);
},
testRemoveManyWithInvalidRev : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs REMOVE {_key: doc._key, _rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs(10);
try {
db._query(q, {docs: [...docs]});
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
assertEqual(2000, col.count(), `We removed the document`);
},
testRemoveEnumerationWithInvalidRev : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
REMOVE {_key: doc._key, _rev: "invalid", val: "${invalid}"}
IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs();
try {
db._query(q);
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
assertEqual(2000, col.count(), `We removed the document`);
},
testRemoveSingleWithInvalidRevIgnore : function () {
const invalid = genInvalidValue();
let q = `REMOVE {_key: @key, _rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
db._query(q, {key: d._key});
}
assertEqual(2000 - 1, col.count(), `We did not remove the document`);
},
testRemoveManyWithInvalidRevIgnore : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs REMOVE {_key: doc._key, _rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs(10);
db._query(q, {docs: [...docs]});
assertEqual(2000 - 10, col.count(), `We did not remove the document`);
},
testRemoveEnumerationWithInvalidRevIgnore : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
REMOVE {_key: doc._key, _rev: "invalid", val: "${invalid}"}
IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs();
db._query(q);
assertEqual(0, col.count(), `We did not remove the document`);
},
testRemoveSingleWithInvalidRevDefault : function () {
const invalid = genInvalidValue();
let q = `REMOVE {_key: @key, _rev: "invalid", val: "${invalid}"} IN ${collectionName}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
db._query(q, {key: d._key});
}
assertEqual(2000 - 1, col.count(), `We did not remove the document`);
},
testRemoveManyWithInvalidRevDefault : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs REMOVE {_key: doc._key, _rev: "invalid", val: "${invalid}"} IN ${collectionName}`;
let docs = buildSetOfDocs(10);
db._query(q, {docs: [...docs]});
assertEqual(2000 - 10, col.count(), `We did not remove the document`);
},
testRemoveEnumerationWithInvalidRevDefault : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
REMOVE {_key: doc._key, _rev: "invalid", val: "${invalid}"}
IN ${collectionName}`;
let docs = buildSetOfDocs();
db._query(q);
assertEqual(0, col.count(), `We did not remove the document`);
}
};
};
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function aqlUpsertOptionsSuite () {
return {
setUp, tearDown,
testUpsertSingleWithInvalidRev : function () {
const invalid = genInvalidValue();
let q = `UPSERT {_key: @key} INSERT {} UPDATE {_rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
try {
db._query(q, {key: d._key});
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
}
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, false);
},
testUpsertManyWithInvalidRev : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs UPSERT {_key: doc._key} INSERT {} UPDATE {_rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs(10);
try {
db._query(q, {docs: [...docs]});
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, false);
},
testUpsertEnumerationWithInvalidRev : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
UPSERT {_key: doc._key} INSERT {}
UPDATE {_rev: "invalid", val: "${invalid}"}
IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs();
try {
db._query(q);
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, false);
},
testUpsertSingleWithInvalidRevIgnore : function () {
const invalid = genInvalidValue();
let q = `UPSERT {_key: @key} INSERT {}
UPDATE {_rev: "invalid", val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
db._query(q, {key: d._key});
}
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, true);
},
testUpsertManyWithInvalidRevIgnore : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs UPSERT {_key: doc._key} INSERT {}
UPDATE {_rev: "invalid", val: "${invalid}"} IN ${collectionName}
OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs(10);
db._query(q, {docs: [...docs]});
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, true);
},
testUpsertEnumerationWithInvalidRevIgnore : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
UPSERT {_key: doc._key} INSERT {}
UPDATE {_rev: "invalid", val: "${invalid}"}
IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs();
db._query(q);
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, true);
},
testUpsertSingleWithInvalidRevDefault : function () {
const invalid = genInvalidValue();
let q = `UPSERT {_key: @key} INSERT {} UPDATE {_rev: "invalid", val: "${invalid}"} IN ${collectionName}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
db._query(q, {key: d._key});
}
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, true);
},
testUpsertManyWithInvalidRevDefault : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs UPSERT {_key: doc._key} INSERT {}
UPDATE {_rev: "invalid", val: "${invalid}"} IN ${collectionName}`;
let docs = buildSetOfDocs(10);
db._query(q, {docs: [...docs]});
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, true);
},
testUpsertEnumerationWithInvalidRevDefault : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
UPSERT {_key: doc._key} INSERT {}
UPDATE {_rev: "invalid", val: "${invalid}"}
IN ${collectionName}`;
let docs = buildSetOfDocs();
db._query(q);
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, true);
},
/* We cannot yet solve this. If you need to ensure _rev value checks put them in the UPDATE {} clause
testUpsertSingleWithInvalidRevInMatch : function () {
const invalid = genInvalidValue();
let q = `UPSERT {_key: @key, _rev: "invalid"} INSERT {} UPDATE {val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: false}`;
db._explain(q, {key: "1"});
let docs = buildSetOfDocs(1);
for (let d of docs) {
try {
db._query(q, {key: d._key});
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
}
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, false);
},
testUpsertManyWithInvalidRevInMatch : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs UPSERT {_key: doc._key, _rev: "invalid"} INSERT {} UPDATE {val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs(10);
try {
db._query(q, {docs: [...docs]});
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, false);
},
testUpsertEnumerationWithInvalidRevInMatch : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
UPSERT {_key: doc._key, _rev: "invalid"} INSERT {}
UPDATE {val: "${invalid}"}
IN ${collectionName} OPTIONS {ignoreRevs: false}`;
let docs = buildSetOfDocs();
try {
db._query(q);
fail();
} catch (err) {
assertEqual(err.errorNum, errors.ERROR_ARANGO_CONFLICT.code);
}
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, false);
},
testUpsertSingleWithInvalidRevIgnoreInMatch : function () {
const invalid = genInvalidValue();
let q = `UPSERT {_key: @key, _rev: "invalid"} INSERT {}
UPDATE {val: "${invalid}"} IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
db._query(q, {key: d._key});
}
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, true);
},
testUpsertManyWithInvalidRevIgnoreInMatch : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs UPSERT {_key: doc._key, _rev: "invalid"} INSERT {}
UPDATE {val: "${invalid}"} IN ${collectionName}
OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs(10);
db._query(q, {docs: [...docs]});
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, true);
},
testUpsertEnumerationWithInvalidRevIgnoreInMatch : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
UPSERT {_key: doc._key, _rev: "invalid"} INSERT {}
UPDATE {val: "${invalid}"}
IN ${collectionName} OPTIONS {ignoreRevs: true}`;
let docs = buildSetOfDocs();
db._query(q);
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, true);
},
testUpsertSingleWithInvalidRevDefaultInMatch : function () {
const invalid = genInvalidValue();
let q = `UPSERT {_key: @key, _rev: "invalid"} INSERT {} UPDATE {val: "${invalid}"} IN ${collectionName}`;
let docs = buildSetOfDocs(1);
for (let d of docs) {
db._query(q, {key: d._key});
}
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, true);
},
testUpsertManyWithInvalidRevDefaultInMatch : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN @docs UPSERT {_key: doc._key, _rev: "invalid"} INSERT {}
UPDATE {val: "${invalid}"} IN ${collectionName}`;
let docs = buildSetOfDocs(10);
db._query(q, {docs: [...docs]});
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, true);
},
testUpsertEnumerationWithInvalidRevDefaultInMatch : function () {
const invalid = genInvalidValue();
let q = `FOR doc IN ${collectionName}
UPSERT {_key: doc._key, _rev: "invalid"} INSERT {}
UPDATE {val: "${invalid}"}
IN ${collectionName}`;
let docs = buildSetOfDocs();
db._query(q);
assertEqual(2000, col.count(), `We falsly triggered an INSERT`);
validateDocsAreUpdated(docs, invalid, true);
},
*/
};
};
////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suites
////////////////////////////////////////////////////////////////////////////////
jsunity.run(aqlUpdateOptionsSuite);
jsunity.run(aqlReplaceOptionsSuite);
jsunity.run(aqlRemoveOptionsSuite);
jsunity.run(aqlUpsertOptionsSuite);
return jsunity.done();