From d94fd7d696ac32470e2b3088e9ccf3203ffdb523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20G=C3=B6rler?= Date: Wed, 21 Feb 2024 19:26:12 +0100 Subject: [PATCH 01/11] Java Optimistic Concurrency Control --- java/query-execution.md | 180 +++++++++++++++++++++++++++++++++------- 1 file changed, 151 insertions(+), 29 deletions(-) diff --git a/java/query-execution.md b/java/query-execution.md index b48fbcefa..69cbf1cfa 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -129,35 +129,6 @@ Hints prefixed with `hdb.` are directly rendered into SQL for SAP HANA and there ::: -### Pessimistic Locking { #pessimistic-locking} - -Use database locks to ensure that data returned by a query isn't modified in a concurrent transaction. -_Exclusive_ locks block concurrent modification and the creation of any other lock. _Shared_ locks, however, only block concurrent modifications and exclusive locks but allow the concurrent creation of other shared locks. - -To lock data: -1. Start a transaction (either manually or let the framework take care of it). -2. Query the data and set a lock on it. -3. Perform the processing and, if an exclusive lock is used, modify the data inside the same transaction. -4. Commit (or roll back) the transaction, which releases the lock. - -To be able to query and lock the data until the transaction is completed, just call a [`lock()`](./query-api#write-lock) method and set an optional parameter `timeout`. - -In the following example, a book with `ID` 1 is selected and locked until the transaction is finished. Thus, one can avoid situations when other threads or clients are trying to modify the same data in the meantime: - -```java -// Start transaction -// Obtain and set a write lock on the book with id 1 - service.run(Select.from("bookshop.Books").byId(1).lock()); - ... -// Update the book locked earlier - Map data = Collections.singletonMap("title", "new title"); - service.run(Update.entity("bookshop.Books").data(data).byId(1)); -// Finish transaction -``` - -The `lock()` method has an optional parameter `timeout` that indicates the maximum number of seconds to wait for the lock acquisition. If a lock can't be obtained within the `timeout`, a `CdsLockTimeoutException` is thrown. If `timeout` isn't specified, a database-specific default timeout will be used. - -The parameter `mode` allows to specify whether an `EXCLUSIVE` or a `SHARED` lock should be set. ### Data Manipulation @@ -290,6 +261,157 @@ entity DeliveredOrders as select from bookshop.Order where status = 'delivered'; entity Orders as select from bookshop.Order inner join bookshop.OrderHeader on Order.header.ID = OrderHeader.ID { Order.ID, Order.items, OrderHeader.status }; ``` +## Concurreny Control + + +Concurreny control allows to protect your data against unexpected concurrent changes. + +### Optimisitic Concurreny Control {#optimistic} + +Use _optimistic_ concurrency control to detect concurrent modification of data _across requests_. The implementation relies on a version value - the _ETag_, which changes whenever an entity instance is updated. Typically, the ETag value is stored in an element of the versioned entity. + +#### Optimistic Concurrency Control in OData + +In the [OData protocol](../guides/providing-services#etag), the implementation relies on ETags. + +The `@odata.etag` annotation indicates to the OData protocol adapter that the value of an annotated element should be [used as the ETag for conflict detection](../guides/providing-services#etag): + +{#on-update-example} + +```cds +entity Order : cuid { + @odata.etag + @cds.on.update : $now @cds.on.insert : $now + modifiedAt : Timestamp; + product : Association to Product; +} +``` + +#### The ETag Predicate {#etag-predicate} + +An ETag can also be used programatically in custom code. Use the `CqnEtagPredicate` to specifiy the expected ETag values in an update or delete operation. You can create an ETag predicate using the `CQL.etag` or the `StructuredType.etag` methods. + +```java +PersistenceService db = ... +Instant expectedLastModification = ... +CqnUpdate update = Update.entity(ORDER).entry(newData).where(o -> o.id().eq(85).etag(expectedLastModification)); + +Result rs = db.execute(update); + +if (rs.rowCount() == 0) { + // order 85 does not exist or was modified concurrently +} +``` + +In the example above, an `Order` is updated. The update is protected with a specified ETag value (the expected last modification timestamp). The update is executed only if the expectation is met. + +:::warning +No exception is thrown if an ETag validation does not match but the execution of the update (or delete) will succeed. Instead, the application has to check the `rowCount` of the `Result`. The value 0 indicates that no row was updated (or deleted). +::: + +:::warning +No ETag checks are execute when an upsert is executed. +::: + +#### Providing new ETag Values with Update Data + +The new ETag value can be provided in the update data. + +A convenient option to determine a new ETag value upon update is the [@cds.on.update](../guides/domain-modeling#cds-on-update) annotation as in the [example above](#on-update-example). The CAP Java runtime will automatically handle the `@cds.on.update` annoation and will set a new value in the data before the update is executed. Such _managed data_ can be used with ETags of type `Timestamp` or `UUID` only. + +It is also possible, but not recommened, that the new ETag value is provided by custom code in a `@Before`-update handler. + +:::warning +If an ETag element is annotated `@cds.on.update` and custom code explicitly sets a value for this element the runtime will _not_ generated a new value upon update but the value, which comes from the custom code will be used. +::: + +#### Runtime Managed Versions + +CAP Java also to store ETag values in _version elements_. For version elements, the values are exclusively managed by the runtime without the option to set them in custom code. Annotate an element with `@cds.java.version` to advise the runtime to manage it's value. + +```cds +entity Order : cuid { + @odata.etag + @cds.java.version + version : Int32; + product : Association to Product; +} +``` + +Additionally to elements of type `Timestamp` and `UUID`, `@cds.java.version` supports all integral types `Uint8`, ... `Int64`. For timestamp, the value is set to `$now` upon update, for elements of type UUID a new UUID is generated, and for elements of integral type the value is incremented. + +Version elements can be used with an [ETag predicate](#etag-predicate) to programatically check an expected ETag value. Moreover, if additionally annotated with `@odata.etag`, they can be for [conflict detection](../guides/providing-services#etag) in OData. + +##### Expected Version from Data + +If the update data contains a value for a version element this values is used as the _expected_ value for the version. This allows to very conveniently use version elements in programatic flow: + +```java +PersistenceService db = ... +CqnSelect select = Select.from(ORDER).byId(85); +Order order = db.run(select).single(Order.class); + +order.setAmount(5000); + +CqnUpdate update = Update.entity(ORDER).entry(order); +Result rs = db.execute(update); + +if (rs.rowCount() == 0) { + // order 85 does not exist or was modified concurrently +} +``` + +During the execution of the update statement it is asserted that the `version` has the same value as the `version` which was read previously and hence no concurrent modification occurred. + +The same convenience can be used in bulk operations. Here the individual update counts need to be introspected. + +```java +CqnSelect select = Select.from(ORDER).where(o -> amount().gt(1000)); +List orders = db.run(select).listOf(Order.class); + +orders.forEach(o -> o.setStatus("cancelled")); + +Result rs = db.execute(Update.entity(ORDER).entries(orders)); + +for(int i = 0; i orders.size(); i++) if (rs.rowCount(i) == 0) { + // order does not exist or was modified concurrently +} +``` + +### Pessimistic Locking { #pessimistic-locking} + +Use database locks to ensure that data returned by a query isn't modified in a concurrent transaction. +_Exclusive_ locks block concurrent modification and the creation of any other lock. _Shared_ locks, however, only block concurrent modifications and exclusive locks but allow the concurrent creation of other shared locks. + +To lock data: +1. Start a transaction (either manually or let the framework take care of it). +2. Query the data and set a lock on it. +3. Perform the processing and, if an exclusive lock is used, modify the data inside the same transaction. +4. Commit (or roll back) the transaction, which releases the lock. + +To be able to query and lock the data until the transaction is completed, just call a [`lock()`](./query-api#write-lock) method and set an optional parameter `timeout`. + +In the following example, a book with `ID` 1 is selected and locked until the transaction is finished. Thus, one can avoid situations when other threads or clients are trying to modify the same data in the meantime: + +```java +// Start transaction +// Obtain and set a write lock on the book with id 1 + service.run(Select.from("bookshop.Books").byId(1).lock()); + ... +// Update the book locked earlier + Map data = Collections.singletonMap("title", "new title"); + service.run(Update.entity("bookshop.Books").data(data).byId(1)); +// Finish transaction +``` + +The `lock()` method has an optional parameter `timeout` that indicates the maximum number of seconds to wait for the lock acquisition. If a lock can't be obtained within the `timeout`, a `CdsLockTimeoutException` is thrown. If `timeout` isn't specified, a database-specific default timeout will be used. + +The parameter `mode` allows to specify whether an `EXCLUSIVE` or a `SHARED` lock should be set. + ## Runtime Views { #runtimeviews} The CDS compiler generates [SQL DDL](../guides/databases?impl-variant=java#generating-sql-ddl) statements based on your CDS model, which include SQL views for all CDS [views and projections](../cds/cdl#views-and-projections). This means adding or changing CDS views requires a deployment of the database schema changes. From 4288c6427681f379028a9d4ea44ee352b6009439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20G=C3=B6rler?= Date: Wed, 21 Feb 2024 19:29:41 +0100 Subject: [PATCH 02/11] typos --- java/query-execution.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/java/query-execution.md b/java/query-execution.md index 69cbf1cfa..04ed4d651 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -262,11 +262,6 @@ entity Orders as select from bookshop.Order inner join bookshop.OrderHeader on O ``` ## Concurreny Control - Concurreny control allows to protect your data against unexpected concurrent changes. From bb7e2c1e3579b709e57b518c45d5ce45888c5bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20G=C3=B6rler?= Date: Wed, 21 Feb 2024 19:31:17 +0100 Subject: [PATCH 03/11] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- java/query-execution.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/java/query-execution.md b/java/query-execution.md index 04ed4d651..133fa2c67 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -265,7 +265,7 @@ entity Orders as select from bookshop.Order inner join bookshop.OrderHeader on O Concurreny control allows to protect your data against unexpected concurrent changes. -### Optimisitic Concurreny Control {#optimistic} +### Optimistic Concurreny Control {#optimistic} Use _optimistic_ concurrency control to detect concurrent modification of data _across requests_. The implementation relies on a version value - the _ETag_, which changes whenever an entity instance is updated. Typically, the ETag value is stored in an element of the versioned entity. @@ -288,7 +288,7 @@ entity Order : cuid { #### The ETag Predicate {#etag-predicate} -An ETag can also be used programatically in custom code. Use the `CqnEtagPredicate` to specifiy the expected ETag values in an update or delete operation. You can create an ETag predicate using the `CQL.etag` or the `StructuredType.etag` methods. +An ETag can also be used programatically in custom code. Use the `CqnEtagPredicate` to specify the expected ETag values in an update or delete operation. You can create an ETag predicate using the `CQL.etag` or the `StructuredType.etag` methods. ```java PersistenceService db = ... @@ -304,7 +304,7 @@ if (rs.rowCount() == 0) { In the example above, an `Order` is updated. The update is protected with a specified ETag value (the expected last modification timestamp). The update is executed only if the expectation is met. -:::warning +::: warning No exception is thrown if an ETag validation does not match but the execution of the update (or delete) will succeed. Instead, the application has to check the `rowCount` of the `Result`. The value 0 indicates that no row was updated (or deleted). ::: @@ -318,7 +318,7 @@ The new ETag value can be provided in the update data. A convenient option to determine a new ETag value upon update is the [@cds.on.update](../guides/domain-modeling#cds-on-update) annotation as in the [example above](#on-update-example). The CAP Java runtime will automatically handle the `@cds.on.update` annoation and will set a new value in the data before the update is executed. Such _managed data_ can be used with ETags of type `Timestamp` or `UUID` only. -It is also possible, but not recommened, that the new ETag value is provided by custom code in a `@Before`-update handler. +It is also possible, but not recommend, that the new ETag value is provided by custom code in a `@Before`-update handler. :::warning If an ETag element is annotated `@cds.on.update` and custom code explicitly sets a value for this element the runtime will _not_ generated a new value upon update but the value, which comes from the custom code will be used. From 12591a6fe15c0d76f202c7503d4e939ca620e8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20G=C3=B6rler?= Date: Thu, 22 Feb 2024 10:08:26 +0100 Subject: [PATCH 04/11] builder API --- java/query-api.md | 22 ++++++++++++++++++++++ java/query-execution.md | 8 +++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/java/query-api.md b/java/query-api.md index f890fe694..4f21a9605 100644 --- a/java/query-api.md +++ b/java/query-api.md @@ -1638,6 +1638,28 @@ BETWEEN +#### `ETag Predicate` {#etag-predicate} + +The [ETag predicate](../java/query-execution#etag-predicate) specifies expected ETag values for [conflict detection](../java/query-execution#optimistic) in an [update](#update) or [delete](#delete) statement: + +```java +Instant expectedLastModification = ... ; +Update.entity(ORDER) + .entry(newData) + .where(o -> o.id().eq(85).and(o.etag(expectedLastModification))); +``` + +You can also use the `etag` methods of the `CQL` interface to construct an ETag predicate in [tree style](#cql-helper-interface): + +```java +import static com.sap.cds.ql.CQL.*; + +Instant expectedLastModification = ... ; +Update.entity(ORDER) + .entry(newData) + .where(and(get("id").eq(85), etag(expectedLastModification))); +``` + #### `Logical Operators` {#logical-operators} Predicates can be combined using logical operators: diff --git a/java/query-execution.md b/java/query-execution.md index 133fa2c67..560681aa5 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -293,7 +293,9 @@ An ETag can also be used programatically in custom code. Use the `CqnEtagPredica ```java PersistenceService db = ... Instant expectedLastModification = ... -CqnUpdate update = Update.entity(ORDER).entry(newData).where(o -> o.id().eq(85).etag(expectedLastModification)); +CqnUpdate update = Update.entity(ORDER).entry(newData) + .where(o -> o.id().eq(85).and( + o.etag(expectedLastModification))); Result rs = db.execute(update); @@ -377,6 +379,10 @@ for(int i = 0; i orders.size(); i++) if (rs.rowCount(i) == 0) { } ``` +:::tip +If an [ETag predicate](#etag-predicate) is explicitly specified it overrules a version value given in the data. +::: + ### Pessimistic Locking { #pessimistic-locking} Use database locks to ensure that data returned by a query isn't modified in a concurrent transaction. From 54865ecd047c2185a791cfd957b478a4fa725b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20G=C3=B6rler?= Date: Thu, 22 Feb 2024 10:50:59 +0100 Subject: [PATCH 05/11] comments from Evgeny --- java/query-execution.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/java/query-execution.md b/java/query-execution.md index 560681aa5..3927be00d 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -128,14 +128,10 @@ CqnSelect query = Select.from(BOOKS).hints("hdb.USE_HEX_PLAN", "hdb.ESTIMATION_S Hints prefixed with `hdb.` are directly rendered into SQL for SAP HANA and therefore **must not** contain external input! ::: - - - ### Data Manipulation The CQN API allows to manipulate data by executing insert, update, delete, or upsert statements. - #### Update The [update](./query-api) operation can be executed as follows: @@ -261,17 +257,17 @@ entity DeliveredOrders as select from bookshop.Order where status = 'delivered'; entity Orders as select from bookshop.Order inner join bookshop.OrderHeader on Order.header.ID = OrderHeader.ID { Order.ID, Order.items, OrderHeader.status }; ``` -## Concurreny Control +## Concurrency Control -Concurreny control allows to protect your data against unexpected concurrent changes. +ConcuConcurrencyrreny control allows to protect your data against unexpected concurrent changes. -### Optimistic Concurreny Control {#optimistic} +### Optimistic Concurrency Control {#optimistic} -Use _optimistic_ concurrency control to detect concurrent modification of data _across requests_. The implementation relies on a version value - the _ETag_, which changes whenever an entity instance is updated. Typically, the ETag value is stored in an element of the versioned entity. +Use _optimistic_ concurrency control to detect concurrent modification of data _across requests_. The implementation relies on a _version_ value - the _ETag_, which changes whenever an entity instance is updated. Typically, the ETag value is stored in an element of the versioned entity. #### Optimistic Concurrency Control in OData -In the [OData protocol](../guides/providing-services#etag), the implementation relies on ETags. +In the [OData protocol](../guides/providing-services#etag), the implementation relies on `ETags` and `If-Match` headers in the HTTP request. The `@odata.etag` annotation indicates to the OData protocol adapter that the value of an annotated element should be [used as the ETag for conflict detection](../guides/providing-services#etag): @@ -288,7 +284,7 @@ entity Order : cuid { #### The ETag Predicate {#etag-predicate} -An ETag can also be used programatically in custom code. Use the `CqnEtagPredicate` to specify the expected ETag values in an update or delete operation. You can create an ETag predicate using the `CQL.etag` or the `StructuredType.etag` methods. +An ETag can also be used programmatically in custom code. Use the `CqnEtagPredicate` to specify the expected ETag values in an update or delete operation. You can create an ETag predicate using the `CQL.etag` or the `StructuredType.etag` methods. ```java PersistenceService db = ... @@ -320,10 +316,10 @@ The new ETag value can be provided in the update data. A convenient option to determine a new ETag value upon update is the [@cds.on.update](../guides/domain-modeling#cds-on-update) annotation as in the [example above](#on-update-example). The CAP Java runtime will automatically handle the `@cds.on.update` annoation and will set a new value in the data before the update is executed. Such _managed data_ can be used with ETags of type `Timestamp` or `UUID` only. -It is also possible, but not recommend, that the new ETag value is provided by custom code in a `@Before`-update handler. +It is also possible, but not recommended, that the new ETag value is provided by custom code in a `@Before`-update handler. :::warning -If an ETag element is annotated `@cds.on.update` and custom code explicitly sets a value for this element the runtime will _not_ generated a new value upon update but the value, which comes from the custom code will be used. +If an ETag element is annotated `@cds.on.update` and custom code explicitly sets a value for this element the runtime will _not_ generate a new value upon update but the value, which comes from the custom code will be used. ::: #### Runtime Managed Versions From ea9dd12fdfdec17cd7c5b92cb744eac5119a4d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Jeglinsky?= Date: Thu, 22 Feb 2024 10:52:20 +0100 Subject: [PATCH 06/11] Apply suggestions from code review Co-authored-by: Evgeny Andreev --- java/query-execution.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/java/query-execution.md b/java/query-execution.md index 3927be00d..600a9b027 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -307,7 +307,7 @@ No exception is thrown if an ETag validation does not match but the execution of ::: :::warning -No ETag checks are execute when an upsert is executed. +No ETag checks are executed when an upsert is executed. ::: #### Providing new ETag Values with Update Data @@ -324,7 +324,7 @@ If an ETag element is annotated `@cds.on.update` and custom code explicitly sets #### Runtime Managed Versions -CAP Java also to store ETag values in _version elements_. For version elements, the values are exclusively managed by the runtime without the option to set them in custom code. Annotate an element with `@cds.java.version` to advise the runtime to manage it's value. +CAP Java also to store ETag values in _version elements_. For version elements, the values are exclusively managed by the runtime without the option to set them in custom code. Annotate an element with `@cds.java.version` to advise the runtime to manage its value. ```cds entity Order : cuid { @@ -337,11 +337,11 @@ entity Order : cuid { Additionally to elements of type `Timestamp` and `UUID`, `@cds.java.version` supports all integral types `Uint8`, ... `Int64`. For timestamp, the value is set to `$now` upon update, for elements of type UUID a new UUID is generated, and for elements of integral type the value is incremented. -Version elements can be used with an [ETag predicate](#etag-predicate) to programatically check an expected ETag value. Moreover, if additionally annotated with `@odata.etag`, they can be for [conflict detection](../guides/providing-services#etag) in OData. +Version elements can be used with an [ETag predicate](#etag-predicate) to programmatically check an expected ETag value. Moreover, if additionally annotated with `@odata.etag`, they can be for [conflict detection](../guides/providing-services#etag) in OData. ##### Expected Version from Data -If the update data contains a value for a version element this values is used as the _expected_ value for the version. This allows to very conveniently use version elements in programatic flow: +If the update data contains a value for a version element this values is used as the _expected_ value for the version. This allows to use version elements in programmatic flow conveniently: ```java PersistenceService db = ... From 602aedaf4551e03f43a9410f2d7813c59ec5c8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20G=C3=B6rler?= Date: Thu, 22 Feb 2024 10:54:00 +0100 Subject: [PATCH 07/11] typos --- java/query-execution.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/query-execution.md b/java/query-execution.md index 3927be00d..cd55f6583 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -259,7 +259,7 @@ entity Orders as select from bookshop.Order inner join bookshop.OrderHeader on O ## Concurrency Control -ConcuConcurrencyrreny control allows to protect your data against unexpected concurrent changes. +Concurrency control allows to protect your data against unexpected concurrent changes. ### Optimistic Concurrency Control {#optimistic} @@ -324,7 +324,7 @@ If an ETag element is annotated `@cds.on.update` and custom code explicitly sets #### Runtime Managed Versions -CAP Java also to store ETag values in _version elements_. For version elements, the values are exclusively managed by the runtime without the option to set them in custom code. Annotate an element with `@cds.java.version` to advise the runtime to manage it's value. +CAP Java also to store ETag values in _version elements_. For version elements, the values are exclusively managed by the runtime without the option to set them in custom code. Annotate an element with `@cds.java.version` to advise the runtime to manage its value. ```cds entity Order : cuid { @@ -341,7 +341,7 @@ Version elements can be used with an [ETag predicate](#etag-predicate) to progra ##### Expected Version from Data -If the update data contains a value for a version element this values is used as the _expected_ value for the version. This allows to very conveniently use version elements in programatic flow: +If the update data contains a value for a version element this values is used as the _expected_ value for the version. This allows to use version elements in programmatic flow conveniently: ```java PersistenceService db = ... From 609d63e19ded77823c91ae50f110a39dc56a10f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20G=C3=B6rler?= Date: Thu, 22 Feb 2024 22:27:57 +0100 Subject: [PATCH 08/11] review comments --- java/query-api.md | 6 +++--- java/query-execution.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/java/query-api.md b/java/query-api.md index 4f21a9605..f8a712c09 100644 --- a/java/query-api.md +++ b/java/query-api.md @@ -1646,10 +1646,10 @@ The [ETag predicate](../java/query-execution#etag-predicate) specifies expected Instant expectedLastModification = ... ; Update.entity(ORDER) .entry(newData) - .where(o -> o.id().eq(85).and(o.etag(expectedLastModification))); + .where(o -> o.id().eq(85).and(o.eTag(expectedLastModification))); ``` -You can also use the `etag` methods of the `CQL` interface to construct an ETag predicate in [tree style](#cql-helper-interface): +You can also use the `eTag` methods of the `CQL` interface to construct an ETag predicate in [tree style](#cql-helper-interface): ```java import static com.sap.cds.ql.CQL.*; @@ -1657,7 +1657,7 @@ import static com.sap.cds.ql.CQL.*; Instant expectedLastModification = ... ; Update.entity(ORDER) .entry(newData) - .where(and(get("id").eq(85), etag(expectedLastModification))); + .where(and(get("id").eq(85), eTag(expectedLastModification))); ``` #### `Logical Operators` {#logical-operators} diff --git a/java/query-execution.md b/java/query-execution.md index b24ec34d9..45ce866f1 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -267,7 +267,7 @@ Use _optimistic_ concurrency control to detect concurrent modification of data _ #### Optimistic Concurrency Control in OData -In the [OData protocol](../guides/providing-services#etag), the implementation relies on `ETags` and `If-Match` headers in the HTTP request. +In the [OData protocol](../guides/providing-services#etag), the implementation relies on `ETag` and `If-Match` headers in the HTTP request. The `@odata.etag` annotation indicates to the OData protocol adapter that the value of an annotated element should be [used as the ETag for conflict detection](../guides/providing-services#etag): @@ -284,14 +284,14 @@ entity Order : cuid { #### The ETag Predicate {#etag-predicate} -An ETag can also be used programmatically in custom code. Use the `CqnEtagPredicate` to specify the expected ETag values in an update or delete operation. You can create an ETag predicate using the `CQL.etag` or the `StructuredType.etag` methods. +An ETag can also be used programmatically in custom code. Use the `CqnEtagPredicate` to specify the expected ETag values in an update or delete operation. You can create an ETag predicate using the `CQL.eTag` or the `StructuredType.eTag` methods. ```java PersistenceService db = ... Instant expectedLastModification = ... CqnUpdate update = Update.entity(ORDER).entry(newData) .where(o -> o.id().eq(85).and( - o.etag(expectedLastModification))); + o.eTag(expectedLastModification))); Result rs = db.execute(update); From 4fad7a4a3e3663639307aec6f567cc8f099078f8 Mon Sep 17 00:00:00 2001 From: Rene Jeglinsky Date: Mon, 26 Feb 2024 10:45:10 +0100 Subject: [PATCH 09/11] editing --- java/query-execution.md | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/java/query-execution.md b/java/query-execution.md index 45ce866f1..6087012e6 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -259,7 +259,7 @@ entity Orders as select from bookshop.Order inner join bookshop.OrderHeader on O ## Concurrency Control -Concurrency control allows to protect your data against unexpected concurrent changes. +Concurrency control allows protecting your data against unexpected concurrent changes. ### Optimistic Concurrency Control {#optimistic} @@ -284,7 +284,7 @@ entity Order : cuid { #### The ETag Predicate {#etag-predicate} -An ETag can also be used programmatically in custom code. Use the `CqnEtagPredicate` to specify the expected ETag values in an update or delete operation. You can create an ETag predicate using the `CQL.eTag` or the `StructuredType.eTag` methods. +An ETag can also be used programmatically in custom code. Use the `CqnEtagPredicate` to specify the expected ETag values in an update or delete operation. ETag checks are not executed on upsert. You can create an ETag predicate using the `CQL.eTag` or the `StructuredType.eTag` methods. ```java PersistenceService db = ... @@ -300,31 +300,22 @@ if (rs.rowCount() == 0) { } ``` -In the example above, an `Order` is updated. The update is protected with a specified ETag value (the expected last modification timestamp). The update is executed only if the expectation is met. +In the previous example, an `Order` is updated. The update is protected with a specified ETag value (the expected last modification timestamp). The update is executed only if the expectation is met. -::: warning +::: warning Application has to check the result No exception is thrown if an ETag validation does not match but the execution of the update (or delete) will succeed. Instead, the application has to check the `rowCount` of the `Result`. The value 0 indicates that no row was updated (or deleted). ::: -:::warning -No ETag checks are executed when an upsert is executed. -::: #### Providing new ETag Values with Update Data -The new ETag value can be provided in the update data. +A convenient option to determine a new ETag value upon update is the [@cds.on.update](../guides/domain-modeling#cds-on-update) annotation as in the [example above](#on-update-example). The CAP Java runtime automatically handles the `@cds.on.update` annotation and sets a new value in the data before the update is executed. Such _managed data_ can be used with ETags of type `Timestamp` or `UUID` only. -A convenient option to determine a new ETag value upon update is the [@cds.on.update](../guides/domain-modeling#cds-on-update) annotation as in the [example above](#on-update-example). The CAP Java runtime will automatically handle the `@cds.on.update` annoation and will set a new value in the data before the update is executed. Such _managed data_ can be used with ETags of type `Timestamp` or `UUID` only. +We do not recommend providing a new ETag value by custom code in a `@Before`-update handler. If you do set a value explicitly in custom code and an ETag element is annotated with `@cds.on.update`, the runtime does not generate a new value upon update for this element. Instead, the value that comes from your custom code is used. -It is also possible, but not recommended, that the new ETag value is provided by custom code in a `@Before`-update handler. - -:::warning -If an ETag element is annotated `@cds.on.update` and custom code explicitly sets a value for this element the runtime will _not_ generate a new value upon update but the value, which comes from the custom code will be used. -::: +#### Runtime-Managed Versions -#### Runtime Managed Versions - -CAP Java also to store ETag values in _version elements_. For version elements, the values are exclusively managed by the runtime without the option to set them in custom code. Annotate an element with `@cds.java.version` to advise the runtime to manage its value. +CAP Java also stores ETag values in _version elements_. For version elements, the values are exclusively managed by the runtime without the option to set them in custom code. Annotate an element with `@cds.java.version` to advise the runtime to manage its value. ```cds entity Order : cuid { @@ -335,13 +326,13 @@ entity Order : cuid { } ``` -Additionally to elements of type `Timestamp` and `UUID`, `@cds.java.version` supports all integral types `Uint8`, ... `Int64`. For timestamp, the value is set to `$now` upon update, for elements of type UUID a new UUID is generated, and for elements of integral type the value is incremented. +Additionally, to elements of type `Timestamp` and `UUID`, `@cds.java.version` supports all integral types `Uint8`, ... `Int64`. For timestamp, the value is set to `$now` upon update, for elements of type UUID a new UUID is generated, and for elements of integral type the value is incremented. -Version elements can be used with an [ETag predicate](#etag-predicate) to programmatically check an expected ETag value. Moreover, if additionally annotated with `@odata.etag`, they can be for [conflict detection](../guides/providing-services#etag) in OData. +Version elements can be used with an [ETag predicate](#etag-predicate) to programmatically check an expected ETag value. Moreover, if additionally annotated with `@odata.etag`, they can be used for [conflict detection](../guides/providing-services#etag) in OData. ##### Expected Version from Data -If the update data contains a value for a version element this values is used as the _expected_ value for the version. This allows to use version elements in programmatic flow conveniently: +If the update data contains a value for a version element, this value is used as the _expected_ value for the version. This allows using version elements in a programmatic flow conveniently: ```java PersistenceService db = ... @@ -358,7 +349,7 @@ if (rs.rowCount() == 0) { } ``` -During the execution of the update statement it is asserted that the `version` has the same value as the `version` which was read previously and hence no concurrent modification occurred. +During the execution of the update statement it's asserted that the `version` has the same value as the `version`, which was read previously and hence no concurrent modification occurred. The same convenience can be used in bulk operations. Here the individual update counts need to be introspected. @@ -375,9 +366,8 @@ for(int i = 0; i orders.size(); i++) if (rs.rowCount(i) == 0) { } ``` -:::tip -If an [ETag predicate](#etag-predicate) is explicitly specified it overrules a version value given in the data. -::: +> If an [ETag predicate is explicitly specified](#providing-new-etag-values-with-update-data), it overrules a version value given in the data. + ### Pessimistic Locking { #pessimistic-locking} From d4f5388850307721754edcad3870d9631e6d677e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20G=C3=B6rler?= Date: Tue, 27 Feb 2024 12:14:05 +0100 Subject: [PATCH 10/11] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marc Becker Co-authored-by: René Jeglinsky --- java/query-execution.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/query-execution.md b/java/query-execution.md index 6087012e6..1ecab8f51 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -263,7 +263,7 @@ Concurrency control allows protecting your data against unexpected concurrent ch ### Optimistic Concurrency Control {#optimistic} -Use _optimistic_ concurrency control to detect concurrent modification of data _across requests_. The implementation relies on a _version_ value - the _ETag_, which changes whenever an entity instance is updated. Typically, the ETag value is stored in an element of the versioned entity. +Use _optimistic_ concurrency control to detect concurrent modification of data _across requests_. The implementation relies on an _ETag_, which changes whenever an entity instance is updated. Typically, the ETag value is stored in an element of the entity. #### Optimistic Concurrency Control in OData @@ -303,7 +303,7 @@ if (rs.rowCount() == 0) { In the previous example, an `Order` is updated. The update is protected with a specified ETag value (the expected last modification timestamp). The update is executed only if the expectation is met. ::: warning Application has to check the result -No exception is thrown if an ETag validation does not match but the execution of the update (or delete) will succeed. Instead, the application has to check the `rowCount` of the `Result`. The value 0 indicates that no row was updated (or deleted). +No exception is thrown if an ETag validation does not match. Instead, the execution of the update (or delete) succeeds but doesn't apply any changes. Ensure that the application checks the `rowCount` of the `Result` and implement your error handling. If the value of `rowCount` is 0, that indicates that no row was updated (or deleted). ::: @@ -326,7 +326,7 @@ entity Order : cuid { } ``` -Additionally, to elements of type `Timestamp` and `UUID`, `@cds.java.version` supports all integral types `Uint8`, ... `Int64`. For timestamp, the value is set to `$now` upon update, for elements of type UUID a new UUID is generated, and for elements of integral type the value is incremented. +Compared to `@cds.on.update`, which allows for ETag elements with type `Timestamp` or `UUID` only, `@cds.java.version` additionally supports all integral types `Uint8`, ... `Int64`. For timestamp, the value is set to `$now` upon update, for elements of type UUID a new UUID is generated, and for elements of integral type the value is incremented. Version elements can be used with an [ETag predicate](#etag-predicate) to programmatically check an expected ETag value. Moreover, if additionally annotated with `@odata.etag`, they can be used for [conflict detection](../guides/providing-services#etag) in OData. From 4c04f6512cbb82cfa68db80ada91ef83c0b5b33c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Jeglinsky?= Date: Tue, 27 Feb 2024 12:25:46 +0100 Subject: [PATCH 11/11] Update java/query-execution.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Adrian Görler --- java/query-execution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/query-execution.md b/java/query-execution.md index 1ecab8f51..4a061c337 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -315,7 +315,7 @@ We do not recommend providing a new ETag value by custom code in a `@Before`-upd #### Runtime-Managed Versions -CAP Java also stores ETag values in _version elements_. For version elements, the values are exclusively managed by the runtime without the option to set them in custom code. Annotate an element with `@cds.java.version` to advise the runtime to manage its value. +Alternatively, you can store ETag values in _version elements_. For version elements, the values are exclusively managed by the runtime without the option to set them in custom code. Annotate an element with `@cds.java.version` to advise the runtime to manage its value. ```cds entity Order : cuid {