diff --git a/site/content/3.10/aql/high-level-operations/upsert.md b/site/content/3.10/aql/high-level-operations/upsert.md
index aa0e23633e..caf694d0be 100644
--- a/site/content/3.10/aql/high-level-operations/upsert.md
+++ b/site/content/3.10/aql/high-level-operations/upsert.md
@@ -3,10 +3,16 @@ title: '`UPSERT` operation in AQL'
menuTitle: UPSERT
weight: 70
description: >-
- The `UPSERT` operations either modifies an existing document, or creates a new
+ An `UPSERT` operation either modifies an existing document, or creates a new
document if it does not exist
archetype: default
---
+`UPSERT` looks up a single document that matches the provided example.
+If there is no match, an insert operation is executed to create a
+document. If a document is found, you can either update or replace the document.
+These subtypes are called **upsert** (update or insert) and **repsert**
+(replace or insert).
+
Each `UPSERT` operation is restricted to a single collection, and the
[collection name](../../concepts/data-structure/collections.md#collection-names) must not be dynamic.
Only a single `UPSERT` statement per collection is allowed per AQL query, and
@@ -15,13 +21,15 @@ traversal operations, or AQL functions that can read documents.
## Syntax
-The syntax for upsert and repsert operations is:
+The syntax for an upsert operation:
UPSERT searchExpression
INSERT insertExpression
UPDATE updateExpression
IN collection
+The syntax for a repsert operation:
+
UPSERT searchExpression
INSERT insertExpression
REPLACE updateExpression
@@ -29,30 +37,31 @@ IN collection
Both variants can optionally end with an `OPTIONS { … }` clause.
-When using the `UPDATE` variant of the upsert operation, the found document
-will be partially updated, meaning only the attributes specified in
-*updateExpression* will be updated or added. When using the `REPLACE` variant
-of upsert (repsert), existing documents will be replaced with the contexts of
+When using the `UPDATE` variant of the `UPSERT` operation, the found document
+is partially updated, meaning only the attributes specified in
+*updateExpression* are updated or added. When using the `REPLACE` variant
+of `UPSERT` (repsert), the found document is replaced with the content of
*updateExpression*.
-Updating a document will modify the document's revision number with a server-generated value.
-The system attributes `_id`, `_key` and `_rev` cannot be updated, `_from` and `_to` can.
+Updating a document modifies the document's revision number with a server-generated value.
+The system attributes `_id`, `_key`, and `_rev` cannot be updated, but `_from` and `_to`
+can be modified.
-The *searchExpression* contains the document to be looked for. It must be an object
-literal without dynamic attribute names. In case no such document can be found in
-*collection*, a new document will be inserted into the collection as specified in the
-*insertExpression*.
+The *searchExpression* contains the document to be looked for. It must be an
+**object literal** (`UPSERT { : , ... } ...`) without dynamic
+attribute names. In case no such document can be found in *collection*, a new
+document is inserted into the collection as specified in the *insertExpression*.
-In case at least one document in *collection* matches the *searchExpression*, it will
-be updated using the *updateExpression*. When more than one document in the collection
-matches the *searchExpression*, it is undefined which of the matching documents will
-be updated. It is therefore often sensible to make sure by other means (such as unique
+In case at least one document in *collection* matches the *searchExpression*, it is
+updated using the *updateExpression*. When more than one document in the collection
+matches the *searchExpression*, it is undefined which of the matching documents is
+updated. It is therefore often sensible to make sure by other means (such as unique
indexes, application logic etc.) that at most one document matches *searchExpression*.
-The following query will look in the *users* collection for a document with a specific
-*name* attribute value. If the document exists, its *logins* attribute will be increased
-by one. If it does not exist, a new document will be inserted, consisting of the
-attributes *name*, *logins*, and *dateCreated*:
+The following query looks for a document in the `users` collection with a specific
+`name` attribute value. If the document exists, its *logins* attribute is increased
+by one. If it does not exist, a new document is inserted, consisting of the
+attributes `name`, `logins`, and `dateCreated`:
```aql
UPSERT { name: 'superuser' }
@@ -96,7 +105,7 @@ inside of arrays (e.g. `{ attr: [ { nested: null } ] }`).
### `mergeObjects`
-The option `mergeObjects` controls whether object contents will be
+The option `mergeObjects` controls whether object contents are
merged if an object attribute is present in both the `UPDATE` query and in the
to-be-updated document.
@@ -125,9 +134,9 @@ FOR i IN 1..1000
```
{{< info >}}
-You need to add the `_rev` value in the *updateExpression*. It will not be used
+You need to add the `_rev` value in the *updateExpression*. It is not used
within the *searchExpression*. Even worse, if you use an outdated `_rev` in the
-*searchExpression*, `UPSERT` will trigger the `INSERT` path instead of the
+*searchExpression*, `UPSERT` triggers the `INSERT` path instead of the
`UPDATE` path, because it has not found a document exactly matching the
*searchExpression*.
{{< /info >}}
@@ -153,7 +162,7 @@ FOR i IN 1..1000
### `indexHint`
-The `indexHint` option will be used as a hint for the document lookup
+The `indexHint` option is used as a hint for the document lookup
performed as part of the `UPSERT` operation, and can help in cases such as
`UPSERT` not picking the best index automatically.
@@ -167,6 +176,8 @@ UPSERT { a: 1234 }
The index hint is passed through to an internal `FOR` loop that is used for the
lookup. Also see [`indexHint` Option of the `FOR` Operation](for.md#indexhint).
+Inverted indexes cannot be used for `UPSERT` lookups.
+
### `forceIndexHint`
Makes the index or indexes specified in `indexHint` mandatory if enabled. The
@@ -185,11 +196,11 @@ UPSERT { a: 1234 }
`UPSERT` statements can optionally return data. To do so, they need to be followed
by a `RETURN` statement (intermediate `LET` statements are allowed, too). These statements
can optionally perform calculations and refer to the pseudo-values `OLD` and `NEW`.
-In case the upsert performed an insert operation, `OLD` will have a value of `null`.
-In case the upsert performed an update or replace operation, `OLD` will contain the
+In case the upsert performed an insert operation, `OLD` has a value of `null`.
+In case the upsert performed an update or replace operation, `OLD` contains the
previous version of the document, before update/replace.
-`NEW` will always be populated. It will contain the inserted document in case the
+`NEW` is always populated. It contains the inserted document in case the
upsert performed an insert, or the updated/replaced document in case it performed an
update/replace.
diff --git a/site/content/3.11/aql/high-level-operations/upsert.md b/site/content/3.11/aql/high-level-operations/upsert.md
index aa0e23633e..caf694d0be 100644
--- a/site/content/3.11/aql/high-level-operations/upsert.md
+++ b/site/content/3.11/aql/high-level-operations/upsert.md
@@ -3,10 +3,16 @@ title: '`UPSERT` operation in AQL'
menuTitle: UPSERT
weight: 70
description: >-
- The `UPSERT` operations either modifies an existing document, or creates a new
+ An `UPSERT` operation either modifies an existing document, or creates a new
document if it does not exist
archetype: default
---
+`UPSERT` looks up a single document that matches the provided example.
+If there is no match, an insert operation is executed to create a
+document. If a document is found, you can either update or replace the document.
+These subtypes are called **upsert** (update or insert) and **repsert**
+(replace or insert).
+
Each `UPSERT` operation is restricted to a single collection, and the
[collection name](../../concepts/data-structure/collections.md#collection-names) must not be dynamic.
Only a single `UPSERT` statement per collection is allowed per AQL query, and
@@ -15,13 +21,15 @@ traversal operations, or AQL functions that can read documents.
## Syntax
-The syntax for upsert and repsert operations is:
+The syntax for an upsert operation:
UPSERT searchExpression
INSERT insertExpression
UPDATE updateExpression
IN collection
+The syntax for a repsert operation:
+
UPSERT searchExpression
INSERT insertExpression
REPLACE updateExpression
@@ -29,30 +37,31 @@ IN collection
Both variants can optionally end with an `OPTIONS { … }` clause.
-When using the `UPDATE` variant of the upsert operation, the found document
-will be partially updated, meaning only the attributes specified in
-*updateExpression* will be updated or added. When using the `REPLACE` variant
-of upsert (repsert), existing documents will be replaced with the contexts of
+When using the `UPDATE` variant of the `UPSERT` operation, the found document
+is partially updated, meaning only the attributes specified in
+*updateExpression* are updated or added. When using the `REPLACE` variant
+of `UPSERT` (repsert), the found document is replaced with the content of
*updateExpression*.
-Updating a document will modify the document's revision number with a server-generated value.
-The system attributes `_id`, `_key` and `_rev` cannot be updated, `_from` and `_to` can.
+Updating a document modifies the document's revision number with a server-generated value.
+The system attributes `_id`, `_key`, and `_rev` cannot be updated, but `_from` and `_to`
+can be modified.
-The *searchExpression* contains the document to be looked for. It must be an object
-literal without dynamic attribute names. In case no such document can be found in
-*collection*, a new document will be inserted into the collection as specified in the
-*insertExpression*.
+The *searchExpression* contains the document to be looked for. It must be an
+**object literal** (`UPSERT { : , ... } ...`) without dynamic
+attribute names. In case no such document can be found in *collection*, a new
+document is inserted into the collection as specified in the *insertExpression*.
-In case at least one document in *collection* matches the *searchExpression*, it will
-be updated using the *updateExpression*. When more than one document in the collection
-matches the *searchExpression*, it is undefined which of the matching documents will
-be updated. It is therefore often sensible to make sure by other means (such as unique
+In case at least one document in *collection* matches the *searchExpression*, it is
+updated using the *updateExpression*. When more than one document in the collection
+matches the *searchExpression*, it is undefined which of the matching documents is
+updated. It is therefore often sensible to make sure by other means (such as unique
indexes, application logic etc.) that at most one document matches *searchExpression*.
-The following query will look in the *users* collection for a document with a specific
-*name* attribute value. If the document exists, its *logins* attribute will be increased
-by one. If it does not exist, a new document will be inserted, consisting of the
-attributes *name*, *logins*, and *dateCreated*:
+The following query looks for a document in the `users` collection with a specific
+`name` attribute value. If the document exists, its *logins* attribute is increased
+by one. If it does not exist, a new document is inserted, consisting of the
+attributes `name`, `logins`, and `dateCreated`:
```aql
UPSERT { name: 'superuser' }
@@ -96,7 +105,7 @@ inside of arrays (e.g. `{ attr: [ { nested: null } ] }`).
### `mergeObjects`
-The option `mergeObjects` controls whether object contents will be
+The option `mergeObjects` controls whether object contents are
merged if an object attribute is present in both the `UPDATE` query and in the
to-be-updated document.
@@ -125,9 +134,9 @@ FOR i IN 1..1000
```
{{< info >}}
-You need to add the `_rev` value in the *updateExpression*. It will not be used
+You need to add the `_rev` value in the *updateExpression*. It is not used
within the *searchExpression*. Even worse, if you use an outdated `_rev` in the
-*searchExpression*, `UPSERT` will trigger the `INSERT` path instead of the
+*searchExpression*, `UPSERT` triggers the `INSERT` path instead of the
`UPDATE` path, because it has not found a document exactly matching the
*searchExpression*.
{{< /info >}}
@@ -153,7 +162,7 @@ FOR i IN 1..1000
### `indexHint`
-The `indexHint` option will be used as a hint for the document lookup
+The `indexHint` option is used as a hint for the document lookup
performed as part of the `UPSERT` operation, and can help in cases such as
`UPSERT` not picking the best index automatically.
@@ -167,6 +176,8 @@ UPSERT { a: 1234 }
The index hint is passed through to an internal `FOR` loop that is used for the
lookup. Also see [`indexHint` Option of the `FOR` Operation](for.md#indexhint).
+Inverted indexes cannot be used for `UPSERT` lookups.
+
### `forceIndexHint`
Makes the index or indexes specified in `indexHint` mandatory if enabled. The
@@ -185,11 +196,11 @@ UPSERT { a: 1234 }
`UPSERT` statements can optionally return data. To do so, they need to be followed
by a `RETURN` statement (intermediate `LET` statements are allowed, too). These statements
can optionally perform calculations and refer to the pseudo-values `OLD` and `NEW`.
-In case the upsert performed an insert operation, `OLD` will have a value of `null`.
-In case the upsert performed an update or replace operation, `OLD` will contain the
+In case the upsert performed an insert operation, `OLD` has a value of `null`.
+In case the upsert performed an update or replace operation, `OLD` contains the
previous version of the document, before update/replace.
-`NEW` will always be populated. It will contain the inserted document in case the
+`NEW` is always populated. It contains the inserted document in case the
upsert performed an insert, or the updated/replaced document in case it performed an
update/replace.
diff --git a/site/content/3.12/aql/high-level-operations/upsert.md b/site/content/3.12/aql/high-level-operations/upsert.md
index aa0e23633e..29bfa9e3b8 100644
--- a/site/content/3.12/aql/high-level-operations/upsert.md
+++ b/site/content/3.12/aql/high-level-operations/upsert.md
@@ -3,25 +3,36 @@ title: '`UPSERT` operation in AQL'
menuTitle: UPSERT
weight: 70
description: >-
- The `UPSERT` operations either modifies an existing document, or creates a new
+ An `UPSERT` operation either modifies an existing document, or creates a new
document if it does not exist
archetype: default
---
+`UPSERT` looks up a single document that matches the provided example or filter
+conditions. If there is no match, an insert operation is executed to create a
+document. If a document is found, you can either update or replace the document.
+These subtypes are called **upsert** (update or insert) and **repsert**
+(replace or insert).
+
Each `UPSERT` operation is restricted to a single collection, and the
[collection name](../../concepts/data-structure/collections.md#collection-names) must not be dynamic.
Only a single `UPSERT` statement per collection is allowed per AQL query, and
it cannot be followed by read or write operations that access the same collection, by
traversal operations, or AQL functions that can read documents.
-## Syntax
+## Exact-value matching syntax
+
+This syntax can only look up documents by exact-value matching using an
+object literal and does not support arbitrary filter conditions.
-The syntax for upsert and repsert operations is:
+The syntax for an upsert operation:
UPSERT searchExpression
INSERT insertExpression
UPDATE updateExpression
IN collection
+The syntax for a repsert operation:
+
UPSERT searchExpression
INSERT insertExpression
REPLACE updateExpression
@@ -29,30 +40,31 @@ IN collection
Both variants can optionally end with an `OPTIONS { … }` clause.
-When using the `UPDATE` variant of the upsert operation, the found document
-will be partially updated, meaning only the attributes specified in
-*updateExpression* will be updated or added. When using the `REPLACE` variant
-of upsert (repsert), existing documents will be replaced with the contexts of
+When using the `UPDATE` variant of the `UPSERT` operation, the found document
+is partially updated, meaning only the attributes specified in
+*updateExpression* are updated or added. When using the `REPLACE` variant
+of `UPSERT` (repsert), the found document is replaced with the content of
*updateExpression*.
-Updating a document will modify the document's revision number with a server-generated value.
-The system attributes `_id`, `_key` and `_rev` cannot be updated, `_from` and `_to` can.
+Updating a document modifies the document's revision number with a server-generated value.
+The system attributes `_id`, `_key`, and `_rev` cannot be updated, but `_from` and `_to`
+can be modified.
-The *searchExpression* contains the document to be looked for. It must be an object
-literal without dynamic attribute names. In case no such document can be found in
-*collection*, a new document will be inserted into the collection as specified in the
-*insertExpression*.
+The *searchExpression* contains the document to be looked for. It must be an
+**object literal** (`UPSERT { : , ... } ...`) without dynamic
+attribute names. In case no such document can be found in *collection*, a new
+document is inserted into the collection as specified in the *insertExpression*.
-In case at least one document in *collection* matches the *searchExpression*, it will
-be updated using the *updateExpression*. When more than one document in the collection
-matches the *searchExpression*, it is undefined which of the matching documents will
-be updated. It is therefore often sensible to make sure by other means (such as unique
+In case at least one document in *collection* matches the *searchExpression*, it is
+updated using the *updateExpression*. When more than one document in the collection
+matches the *searchExpression*, it is undefined which of the matching documents is
+updated. It is therefore often sensible to make sure by other means (such as unique
indexes, application logic etc.) that at most one document matches *searchExpression*.
-The following query will look in the *users* collection for a document with a specific
-*name* attribute value. If the document exists, its *logins* attribute will be increased
-by one. If it does not exist, a new document will be inserted, consisting of the
-attributes *name*, *logins*, and *dateCreated*:
+The following query looks for a document in the `users` collection with a specific
+`name` attribute value. If the document exists, its *logins* attribute is increased
+by one. If it does not exist, a new document is inserted, consisting of the
+attributes `name`, `logins`, and `dateCreated`:
```aql
UPSERT { name: 'superuser' }
@@ -63,6 +75,55 @@ UPDATE { logins: OLD.logins + 1 } IN users
Note that in the `UPDATE` case it is possible to refer to the previous version of the
document using the `OLD` pseudo-value.
+## Filter matching syntax
+
+The alternative syntax for `UPSERT` operations allows you to use more flexible
+filter conditions beyond equality matches to look up documents.
+
+The syntax for an upsert operation:
+
+UPSERT FILTERfilter-condition
+INSERT insertExpression
+UPDATE updateExpression
+IN collection
+
+The syntax for a repsert operation:
+
+UPSERT FILTERfilter-condition
+INSERT insertExpression
+REPLACE updateExpression
+IN collection
+
+Both variants can optionally end with an `OPTIONS { … }` clause.
+
+The filter condition for the lookup can make use of the `CURRENT` pseudo-variable
+to access the lookup document. Example:
+
+```aql
+UPSERT FILTER CURRENT.name == 'superuser'
+INSERT { name: 'superuser', logins: 1, dateCreated: DATE_NOW() }
+UPDATE { logins: OLD.logins + 1 } IN users
+```
+
+The `FILTER` expression can contain operators such as `AND` and `OR` to create
+complex filter conditions. You can apply all kinds of filters on the
+`CURRENT` pseudo-variable, and indexes may get utilized to accelerate the lookup
+if they are eligible.
+
+```aql
+UPSERT FILTER CURRENT.age < 30 AND (STARTS_WITH(CURRENT.name, "Jo") OR CURRENT.gender IN ["f", "x"])
+INSERT { name: 'Jordan', age: 29, logins: 1, dateCreated: DATE_NOW() }
+UPDATE { logins: OLD.logins + 1 } IN users
+```
+
+If no document is found in the collection which fits the `FILTER` condition,
+then the `INSERT` operation is executed.
+If exactly one document is found in the collection which fits the `FILTER`
+condition, then the `UPDATE` operation is executed on that document.
+If many documents are found in the collection which fit the `FILTER`
+condition, then the first found document that satisfies the condition is
+updated/replaced.
+
## Query options
### `ignoreErrors`
@@ -96,7 +157,7 @@ inside of arrays (e.g. `{ attr: [ { nested: null } ] }`).
### `mergeObjects`
-The option `mergeObjects` controls whether object contents will be
+The option `mergeObjects` controls whether object contents are
merged if an object attribute is present in both the `UPDATE` query and in the
to-be-updated document.
@@ -125,9 +186,9 @@ FOR i IN 1..1000
```
{{< info >}}
-You need to add the `_rev` value in the *updateExpression*. It will not be used
+You need to add the `_rev` value in the *updateExpression*. It is not used
within the *searchExpression*. Even worse, if you use an outdated `_rev` in the
-*searchExpression*, `UPSERT` will trigger the `INSERT` path instead of the
+*searchExpression*, `UPSERT` triggers the `INSERT` path instead of the
`UPDATE` path, because it has not found a document exactly matching the
*searchExpression*.
{{< /info >}}
@@ -153,7 +214,7 @@ FOR i IN 1..1000
### `indexHint`
-The `indexHint` option will be used as a hint for the document lookup
+The `indexHint` option is used as a hint for the document lookup
performed as part of the `UPSERT` operation, and can help in cases such as
`UPSERT` not picking the best index automatically.
@@ -167,6 +228,8 @@ UPSERT { a: 1234 }
The index hint is passed through to an internal `FOR` loop that is used for the
lookup. Also see [`indexHint` Option of the `FOR` Operation](for.md#indexhint).
+Inverted indexes cannot be used for `UPSERT` lookups.
+
### `forceIndexHint`
Makes the index or indexes specified in `indexHint` mandatory if enabled. The
@@ -185,11 +248,11 @@ UPSERT { a: 1234 }
`UPSERT` statements can optionally return data. To do so, they need to be followed
by a `RETURN` statement (intermediate `LET` statements are allowed, too). These statements
can optionally perform calculations and refer to the pseudo-values `OLD` and `NEW`.
-In case the upsert performed an insert operation, `OLD` will have a value of `null`.
-In case the upsert performed an update or replace operation, `OLD` will contain the
+In case the upsert performed an insert operation, `OLD` has a value of `null`.
+In case the upsert performed an update or replace operation, `OLD` contains the
previous version of the document, before update/replace.
-`NEW` will always be populated. It will contain the inserted document in case the
+`NEW` is always populated. It contains the inserted document in case the
upsert performed an insert, or the updated/replaced document in case it performed an
update/replace.
diff --git a/site/content/3.12/release-notes/version-3.12/whats-new-in-3-12.md b/site/content/3.12/release-notes/version-3.12/whats-new-in-3-12.md
index 7f61f82b12..5b4cba4880 100644
--- a/site/content/3.12/release-notes/version-3.12/whats-new-in-3-12.md
+++ b/site/content/3.12/release-notes/version-3.12/whats-new-in-3-12.md
@@ -130,7 +130,49 @@ Swagger 2.x compatibility.
## AQL
-
+### Filter matching syntax for `UPSERT` operations
+
+Version 3.12 introduces an alternative syntax for
+[`UPSERT` operations](../../aql/high-level-operations/upsert.md) that allows
+you to use more flexible filter conditions to look up documents. Previously,
+the expression used to look up a document had to be an object literal, and this
+is now called the "exact-value matching" syntax.
+
+The new "filter matching" syntax lets you use a `FILTER` statement for the
+`UPSERT` operation. The filter condition for the lookup can make use of the
+`CURRENT` pseudo-variable to access the lookup document.
+
+{{< tabs groupid="upsert-syntax" >}}
+
+{{< tab name="Filter matching" >}}
+```aql
+UPSERT FILTER CURRENT.name == 'superuser'
+INSERT { name: 'superuser', logins: 1, dateCreated: DATE_NOW() }
+UPDATE { logins: OLD.logins + 1 } IN users
+```
+{{< /tab >}}
+
+{{< tab name="Exact-value matching" >}}
+```aql
+UPSERT { name: 'superuser' }
+INSERT { name: 'superuser', logins: 1, dateCreated: DATE_NOW() }
+UPDATE { logins: OLD.logins + 1 } IN users
+```
+{{< /tab >}}
+
+{{< /tabs >}}
+
+The `FILTER` expression can contain operators such as `AND` and `OR` to create
+complex filter conditions, and you can apply more filters on the `CURRENT`
+pseudo-variable than just equality matches.
+
+```aql
+UPSERT FILTER CURRENT.age < 30 AND (STARTS_WITH(CURRENT.name, "Jo") OR CURRENT.gender IN ["f", "x"])
+INSERT { name: 'Jordan', age: 29, logins: 1, dateCreated: DATE_NOW() }
+UPDATE { logins: OLD.logins + 1 } IN users
+```
+
+Read more about [`UPSERT` operations](../../aql/high-level-operations/upsert.md) in AQL.
## Indexing