Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java occ #693

Merged
merged 14 commits into from
Feb 27, 2024
22 changes: 22 additions & 0 deletions java/query-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1638,6 +1638,28 @@ BETWEEN
</tr>
</table>

#### `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:
Expand Down
57 changes: 22 additions & 35 deletions java/query-execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -261,22 +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
<style scoped>
h1:before {
content: "Java"; display: block; font-size: 60%; margin: 0 0 .2em;
}
</style>
## Concurrency Control

Concurreny control allows to protect your data against unexpected concurrent changes.
Concurrency control allows protecting your data against unexpected concurrent changes.

### Optimisitic 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.
agoerler marked this conversation as resolved.
Show resolved Hide resolved

#### 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 `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):

Expand All @@ -293,12 +284,14 @@ 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 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 = ...
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);

Expand All @@ -307,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).
agoerler marked this conversation as resolved.
Show resolved Hide resolved
:::

:::warning
agoerler marked this conversation as resolved.
Show resolved Hide resolved
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.
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.

It is also possible, but not recommened, that the new ETag value is provided by custom code in a `@Before`-update handler.
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.

:::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
#### 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 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.
renejeglinsky marked this conversation as resolved.
Show resolved Hide resolved

```cds
entity Order : cuid {
Expand All @@ -342,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.
agoerler marked this conversation as resolved.
Show resolved Hide resolved

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 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 very conveniently use version elements in programatic flow:
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 = ...
Expand All @@ -365,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.

Expand All @@ -382,6 +366,9 @@ for(int i = 0; i orders.size(); i++) if (rs.rowCount(i) == 0) {
}
```

> 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}

Use database locks to ensure that data returned by a query isn't modified in a concurrent transaction.
Expand Down
Loading