mirror of https://gitee.com/bigwinds/arangodb
Bug fix/internal issue 2658 (#5760)
This commit is contained in:
parent
7fc4be14e1
commit
131114292b
|
@ -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
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
Loading…
Reference in New Issue