diff --git a/Documentation/Books/Users/Aql/DataModification.mdpp b/Documentation/Books/Users/Aql/DataModification.mdpp index 578294cd39..fdd89ff872 100644 --- a/Documentation/Books/Users/Aql/DataModification.mdpp +++ b/Documentation/Books/Users/Aql/DataModification.mdpp @@ -19,7 +19,7 @@ Let's start with an example that modifies existing documents in a collection FILTER u.status == 'not active' UPDATE u WITH { status: 'inactive' } IN users -Note there is no need to combine a data-modification query with other +Note that there is no need to combine a data-modification query with other AQL operations such as *FOR* and *FILTER*. For example, the following stripped-down *update* query will work, too. It will *update* one document (with key *foo*) in collection *users*: @@ -34,13 +34,42 @@ Now, let's copy the contents of the collection *users* into the collection As a final example, let's find some documents in collection *users* and remove them from collection *backup*. The link between the documents in both -collections is establish via the documents' keys: +collections is established via the documents' keys: FOR u IN users FILTER u.status == 'deleted' REMOVE u IN backup +!SUBSECTION Returning documents + +To return documents from a data-modification query, the `INSERT`, `REMOVE`, +`UPDATE` or `REPLACE` statement must be immediately followed by a `LET` +statement that assigns either the pseudo-value `NEW` or `OLD` to a user-defined +variable. The `LET` statement must be followed by a `RETURN` statement that +returns the variable introduced by `LET`: + + FOR i IN 1..100 + INSERT { value: i } IN test LET inserted = NEW RETURN inserted + + FOR u IN users + FILTER u.status == 'deleted' + REMOVE u IN users LET removed = OLD RETURN removed + + FOR u IN users + FILTER u.status == 'not active' + UPDATE u WITH { status: 'inactive' } IN users LET updated = NEW RETURN updated + +`NEW` refers to the inserted or modified document revision, and `OLD` refers +to the document revision before update or removal. `INSERT` statements can +only refer to the `NEW` pseudo-value, and `REMOVE` operations only to `OLD`. +`UPDATE` and `REPLACE` can refer to either. + +In all cases the full documents will be returned with all their attributes, +including the potentially auto-generated attributes such as `_id`, `_key`, or `_rev` +and the attributes not specified in the update expression of a partial update. + + !SUBSECTION Restrictions The name of the modified collection (*users* and *backup* in the above cases) diff --git a/Documentation/Books/Users/Aql/Operations.mdpp b/Documentation/Books/Users/Aql/Operations.mdpp index 2341874f4c..c58a0a9c2a 100644 --- a/Documentation/Books/Users/Aql/Operations.mdpp +++ b/Documentation/Books/Users/Aql/Operations.mdpp @@ -186,7 +186,11 @@ repeated computations of the same value at multiple parts of a query. ``` FOR u IN users LET numRecommendations = LENGTH(u.recommendations) - RETURN { "user" : u, "numRecommendations" : numRecommendations, "isPowerUser" : numRecommendations >= 10 } + RETURN { + "user" : u, + "numRecommendations" : numRecommendations, + "isPowerUser" : numRecommendations >= 10 + } ``` In the above example, the computation of the number of recommendations is @@ -446,20 +450,22 @@ do not need to be identical to the ones produced by a preceding *FOR* statement: ``` FOR i IN 1..1000 - REMOVE { _key: CONCAT('test', TO_STRING(i)) } IN users + REMOVE { _key: CONCAT('test', i) } IN users FOR u IN users FILTER u.active == false REMOVE { _key: u._key } IN backup ``` +!SUBSUBSECTION Setting query options + *options* can be used to suppress query errors that may occur when trying to remove non-existing documents. For example, the following query will fail if one of the to-be-deleted documents does not exist: ``` FOR i IN 1..1000 - REMOVE { _key: CONCAT('test', TO_STRING(i)) } IN users + REMOVE { _key: CONCAT('test', i) } IN users ``` By specifying the *ignoreErrors* query option, these errors can be suppressed so @@ -467,7 +473,7 @@ the query completes: ``` FOR i IN 1..1000 - REMOVE { _key: CONCAT('test', TO_STRING(i)) } IN users OPTIONS { ignoreErrors: true } + REMOVE { _key: CONCAT('test', i) } IN users OPTIONS { ignoreErrors: true } ``` To make sure data has been written to disk when a query returns, there is the *waitForSync* @@ -475,7 +481,26 @@ query option: ``` FOR i IN 1..1000 - REMOVE { _key: CONCAT('test', TO_STRING(i)) } IN users OPTIONS { waitForSync: true } + REMOVE { _key: CONCAT('test', i) } IN users OPTIONS { waitForSync: true } +``` + +!SUBSUBSECTION Returning the removed documents + +The removed documents can also be returned by the query. In this case, the `REMOVE` +statement must be directly followed by a special `LET` statement and a `RETURN` +statement. The `LET` statement is limited to assigning the pseudo-value `OLD` +to a variable, and the `RETURN` statement is limited to returning this variable: + +``` +REMOVE key-expression IN collection options LET variable-name = OLD RETURN variable-name +``` + +Following is an example using a variable named `removed` to return the removed +documents: + +``` +FOR u IN users + REMOVE u IN users LET removed = OLD RETURN removed ``` !SUBSECTION UPDATE @@ -535,19 +560,21 @@ to the ones produced by a preceding *FOR* statement: ``` FOR i IN 1..1000 - UPDATE CONCAT('test', TO_STRING(i)) WITH { foobar: true } IN users + UPDATE CONCAT('test', i) WITH { foobar: true } IN users FOR u IN users FILTER u.active == false UPDATE u WITH { status: 'inactive' } IN backup ``` +!SUBSUBSECTION Setting query options + *options* can be used to suppress query errors that may occur when trying to update non-existing documents or violating unique key constraints: ``` FOR i IN 1..1000 - UPDATE { _key: CONCAT('test', TO_STRING(i)) } WITH { foobar: true } IN users OPTIONS { ignoreErrors: true } + UPDATE { _key: CONCAT('test', i) } WITH { foobar: true } IN users OPTIONS { ignoreErrors: true } ``` An update operation will only update the attributes specified in *document* and @@ -603,6 +630,42 @@ FOR u IN users UPDATE u WITH { foobar: true } IN users OPTIONS { waitForSync: true } ``` +!SUBSUBSECTION Returning the modified documents + +The modified documents can also be returned by the query. In this case, the `UPDATE` +statement must be directly followed by a special `LET` statement and a `RETURN` +statement. The `LET` statement is limited to assigning either the pseudo-value `OLD` +or `NEW` to a variable, and the `RETURN` statement is limited to returning this variable. +The `OLD` pseudo-value refers to the document revisions before the update, and `NEW` +refers to document revisions after the update. + +``` +UPDATE document IN collection options LET variable-name = OLD RETURN variable-name +UPDATE document IN collection options LET variable-name = NEW RETURN variable-name +UPDATE key-expression WITH document IN collection options LET variable-name = OLD RETURN variable-name +UPDATE key-expression WITH document IN collection options LET variable-name = NEW RETURN variable-name +``` + +Following is an example using a variable named `previous` to return the original +documents before modification: + +``` +FOR u IN users + UPDATE u WITH { value: "test" } LET previous = OLD RETURN previous +``` + +The following query uses the `NEW` pseudo-value to return the updated +documents: + +``` +FOR u IN users + UPDATE u WITH { value: "test" } LET updated = NEW RETURN updated +``` + +In both cases, the returned documents will contain all attributes, even +those not specified in the update expression. + + !SUBSECTION REPLACE The *REPLACE* keyword can be used to completely replace documents in a collection. On a @@ -666,19 +729,21 @@ to the ones produced by a preceding *FOR* statement: ``` FOR i IN 1..1000 - REPLACE CONCAT('test', TO_STRING(i)) WITH { foobar: true } IN users + REPLACE CONCAT('test', i) WITH { foobar: true } IN users FOR u IN users FILTER u.active == false REPLACE u WITH { status: 'inactive', name: u.name } IN backup ``` +!SUBSUBSECTION Setting query options + *options* can be used to suppress query errors that may occur when trying to replace non-existing documents or when violating unique key constraints: ``` FOR i IN 1..1000 - REPLACE { _key: CONCAT('test', TO_STRING(i)) } WITH { foobar: true } IN users OPTIONS { ignoreErrors: true } + REPLACE { _key: CONCAT('test', i) } WITH { foobar: true } IN users OPTIONS { ignoreErrors: true } ``` To make sure data are durable when a replace query returns, there is the *waitForSync* @@ -686,9 +751,45 @@ query option: ``` FOR i IN 1..1000 - REPLACE { _key: CONCAT('test', TO_STRING(i)) } WITH { foobar: true } IN users OPTIONS { waitForSync: true } + REPLACE { _key: CONCAT('test', i) } WITH { foobar: true } IN users OPTIONS { waitForSync: true } ``` +!SUBSUBSECTION Returning the modified documents + +The modified documents can also be returned by the query. In this case, the `REPLACE` +statement must be directly followed by a special `LET` statement and a `RETURN` +statement. The `LET` statement is limited to assigning either the pseudo-value `OLD` +or `NEW` to a variable, and the `RETURN` statement is limited to returning this variable. +The `OLD` pseudo-value refers to the document revisions before the update, and `NEW` +refers to document revisions after the update. + +``` +REPLACE document IN collection options LET variable-name = OLD RETURN variable-name +REPLACE document IN collection options LET variable-name = NEW RETURN variable-name +REPLACE key-expression WITH document IN collection options LET variable-name = OLD RETURN variable-name +REPLACE key-expression WITH document IN collection options LET variable-name = NEW RETURN variable-name +``` + +Following is an example using a variable named `previous` to return the original +documents before modification: + +``` +FOR u IN users + REPLACE u WITH { value: "test" } LET previous = OLD RETURN previous +``` + +The following query uses the `NEW` pseudo-value to return the replaced +documents: + +``` +FOR u IN users + REPLACE u WITH { value: "test" } LET replaced = NEW RETURN replaced +``` + +In both cases, the returned documents will contain all attributes, even +those not specified in the replace expression. + + !SUBSECTION INSERT The *INSERT* keyword can be used to insert new documents into a collection. On a @@ -728,12 +829,14 @@ FOR u IN users INSERT { _from: u._id, _to: p._id } IN recommendations ``` +!SUBSUBSECTION Setting query options + *options* can be used to suppress query errors that may occur when violating unique key constraints: ``` FOR i IN 1..1000 - INSERT { _key: CONCAT('test', TO_STRING(i)), name: "test" } WITH { foobar: true } IN users OPTIONS { ignoreErrors: true } + INSERT { _key: CONCAT('test', i), name: "test" } WITH { foobar: true } IN users OPTIONS { ignoreErrors: true } ``` To make sure data are durable when an insert query returns, there is the *waitForSync* @@ -741,5 +844,27 @@ query option: ``` FOR i IN 1..1000 - INSERT { _key: CONCAT('test', TO_STRING(i)), name: "test" } WITH { foobar: true } IN users OPTIONS { waitForSync: true } + INSERT { _key: CONCAT('test', i), name: "test" } WITH { foobar: true } IN users OPTIONS { waitForSync: true } ``` + +!SUBSUBSECTION Returning the inserted documents + +The inserted documents can also be returned by the query. In this case, the `INSERT` +statement must be directly followed by a special `LET` statement and a `RETURN` +statement. The `LET` statement is limited to assigning the pseudo-value `NEW` +to a variable, and the `RETURN` statement is limited to returning this variable: + +``` +INSERT document IN collection options LET variable-name = OLD RETURN variable-name +``` + +Following is an example using a variable named `inserted` to return the inserted +documents: + +``` +FOR i IN 1..100 + INSERT { value: i } LET inserted = NEW RETURN inserted +``` + +The returned documents will contain all attributes, even those auto-generated by +the database (e.g. `_id`, `_key`, `_rev`, `_from`, and `_to`). diff --git a/Documentation/Books/Users/Installing/Compiling.mdpp b/Documentation/Books/Users/Installing/Compiling.mdpp index b5fd3e3f69..a7e7efcf39 100644 --- a/Documentation/Books/Users/Installing/Compiling.mdpp +++ b/Documentation/Books/Users/Installing/Compiling.mdpp @@ -167,7 +167,7 @@ is available that describes how to compile ArangoDB from source on Ubuntu. Verify that your system contains * the GNU C/C++ compilers "gcc" and "g++" and the standard C/C++ libraries, with support - for C++11. You will need version gcc number 4.8.1 or higher. For "clang" and "clang++", + for C++11. You will need version gcc 4.8.1 or higher. For "clang" and "clang++", you will need at least version 3.4. * the GNU autotools (autoconf, automake) * GNU make @@ -278,8 +278,12 @@ the code. This option activates additional code in the server that intentionally makes the server crash or misbehave (e.g. by pretending the system ran out of memory). This -option is useful to test the recovery after a crash and also several edge cases. +option is useful for writing tests. +`--enable-v8-debug` + +Builds a debug version of the V8 library. This is useful only when working on the +V8 integration inside ArangoDB. !SUBSECTION Compiling Go diff --git a/Documentation/Books/Users/NewFeatures/NewFeatures24.mdpp b/Documentation/Books/Users/NewFeatures/NewFeatures24.mdpp index 7b94e94ae5..738b797527 100644 --- a/Documentation/Books/Users/NewFeatures/NewFeatures24.mdpp +++ b/Documentation/Books/Users/NewFeatures/NewFeatures24.mdpp @@ -99,6 +99,39 @@ statement and move the random iteration into the appropriate `EnumerateCollectio This is usually more efficient than individually enumerating and sorting. +!SUBSECTION Data-modification queries returning documents + +`INSERT`, `REMOVE`, `UPDATE` or `REPLACE` queries now can optionally return +the documents inserted, removed, updated, or replaced. This is helpful for tracking +the auto-generated attributes (e.g. `_key`, `_rev`) created by an `INSERT` and in +a lot of other situations. + +In order to return documents from a data-modification query, the statement must +immediately be immediately followed by a `LET` statement that assigns either the +pseudo-value `NEW` or `OLD` to a variable. This `LET` statement must be followed +by a `RETURN` statement that returns the variable introduced by `LET`: + + FOR i IN 1..100 + INSERT { value: i } IN test LET inserted = NEW RETURN inserted + + FOR u IN users + FILTER u.status == 'deleted' + REMOVE u IN users LET removed = OLD RETURN removed + + FOR u IN users + FILTER u.status == 'not active' + UPDATE u WITH { status: 'inactive' } IN users LET updated = NEW RETURN updated + +`NEW` refers to the inserted or modified document revision, and `OLD` refers +to the document revision before update or removal. `INSERT` statements can +only refer to the `NEW` pseudo-value, and `REMOVE` operations only to `OLD`. +`UPDATE` and `REPLACE` can refer to either. + +In all cases the full documents will be returned with all their attributes, +including the potentially auto-generated attributes such as `_id`, `_key`, or `_rev` +and the attributes not specified in the update expression of a partial update. + + !SUBSECTION Language improvements !SUBSUBSECTION `COUNT` clause diff --git a/arangod/Aql/Parser.cpp b/arangod/Aql/Parser.cpp index 95c6b4f4c1..8584742a32 100644 --- a/arangod/Aql/Parser.cpp +++ b/arangod/Aql/Parser.cpp @@ -109,7 +109,7 @@ bool Parser::configureWriteQuery (QueryType type, case AQL_QUERY_REMOVE: if (newOld != nullptr) { if (! TRI_CaseEqualString(newOld, "OLD")) { - _query->registerError(TRI_ERROR_QUERY_PARSE, "OLD expected"); + _query->registerError(TRI_ERROR_QUERY_PARSE, "remove operations can only refer to 'OLD' value"); return false; } } @@ -118,7 +118,7 @@ bool Parser::configureWriteQuery (QueryType type, case AQL_QUERY_INSERT: if (newOld != nullptr) { if (! TRI_CaseEqualString(newOld, "NEW")) { - _query->registerError(TRI_ERROR_QUERY_PARSE, "NEW expected"); + _query->registerError(TRI_ERROR_QUERY_PARSE, "insert operations can only refer to 'NEW' value"); return false; } } @@ -128,7 +128,7 @@ bool Parser::configureWriteQuery (QueryType type, case AQL_QUERY_REPLACE: if (newOld != nullptr) { if (! TRI_CaseEqualString(newOld, "OLD") && ! TRI_CaseEqualString(newOld, "NEW")) { - _query->registerError(TRI_ERROR_QUERY_PARSE, "NEW or OLD expected"); + _query->registerError(TRI_ERROR_QUERY_PARSE, "update/replace operations can only refer to 'NEW' or 'OLD' values"); return false; } } @@ -137,7 +137,7 @@ bool Parser::configureWriteQuery (QueryType type, if (varInto != nullptr && varReturn != nullptr) { if (! TRI_CaseEqualString(varInto, varReturn)) { - _query->registerError(TRI_ERROR_QUERY_PARSE, "invalid variable used in data-modification operation"); + _query->registerError(TRI_ERROR_QUERY_PARSE, "invalid variable used in data-modification operation return value"); return false; } } diff --git a/js/server/tests/aql-modify-noncluster.js b/js/server/tests/aql-modify-noncluster.js index 65043dab2e..2f7f38cea1 100644 --- a/js/server/tests/aql-modify-noncluster.js +++ b/js/server/tests/aql-modify-noncluster.js @@ -91,7 +91,7 @@ var validateDeleteGone = function (collection, results) { assertEqual(collection.document(results[index]._key), {}); fail(); } - catch(e) { + catch (e) { assertTrue(e.errorNum !== undefined, "unexpected error format while calling checking for deleted entry"); assertEqual(errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, e.errorNum, "unexpected error code (" + e.errorMessage + "): "); } @@ -264,6 +264,14 @@ function ahuacatlModifySuite () { assertQueryError(errors.ERROR_QUERY_PARSE.code, "UPDATE 'abc' WITH { } IN @@cn LET updated = NEW RETURN foo", { "@cn": cn1 }); }, +//////////////////////////////////////////////////////////////////////////////// +/// @brief test variable names +//////////////////////////////////////////////////////////////////////////////// + + testInvalidVariableNames3 : function () { + assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN 1..1 UPDATE 'abc' WITH { } IN @@cn LET updated = NEW RETURN i", { "@cn": cn1 }); + }, + //////////////////////////////////////////////////////////////////////////////// /// @brief test empty results ////////////////////////////////////////////////////////////////////////////////