-
Notifications
You must be signed in to change notification settings - Fork 125
Java: Views and Projections #1916
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
base: main
Are you sure you want to change the base?
Changes from all commits
9c8d82c
75a0bca
aab143d
9bc4262
f5b71c9
f937dcb
502a2ab
c421e25
6ddfb0b
5bf74f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -117,12 +117,12 @@ var params = Map.of("minStock", 100); | |
Result result = service.run(query, params); | ||
``` | ||
|
||
#### Adding Query Hints for SAP HANA { #hana-hints} | ||
### Query Hints { #hana-hints} | ||
|
||
To add a hint clause to a statement, use the `hints` method and prefix the [SAP HANA hints](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c1d3f60099654ecfb3fe36ac93c121bb/4ba9edce1f2347a0b9fcda99879c17a1.htmlS) with `hdb.`: | ||
|
||
```java | ||
CqnSelect query = Select.from(BOOKS).hints("hdb.USE_HEX_PLAN", "hdb.ESTIMATION_SAMPLES(0)"); | ||
Select.from(BOOKS).hints("hdb.USE_HEX_PLAN", "hdb.ESTIMATION_SAMPLES(0)"); | ||
``` | ||
::: warning | ||
Hints prefixed with `hdb.` are directly rendered into SQL for SAP HANA and therefore **must not** contain external input! | ||
|
@@ -160,7 +160,7 @@ If no rows are touched the execution is successful but the row count is 0. | |
The setters of an [update with expressions](../working-with-cql/query-api#update-expressions) are evaluated on the database. The result of these expressions is not contained in the update result. | ||
::: | ||
|
||
### Working with Structured Documents | ||
### Structured Documents | ||
|
||
It's possible to work with structured data as the insert, update, and delete operations cascade along *compositions*. | ||
|
||
|
@@ -223,45 +223,165 @@ CqnDelete delete = Delete.from("bookshop.Orders").matching(singletonMap("OrderNo | |
long deleteCount = service.run(delete).rowCount(); | ||
``` | ||
|
||
### Resolvable Views and Projections { #updatable-views} | ||
## Views and Projections { #views } | ||
|
||
The CAP Java SDK aims to resolve statements on non-complex views and projections to their underlying entity. When delegating queries between Application Services and Remote Services, statements are resolved to the entity definitions of the targeted service. Using the Persistence Service, only modifying statements are resolved before executing database queries. This allows to execute [Insert](./query-api#insert), [Upsert](./query-api#upsert), [Update](./query-api#update), and [Delete](./query-api#delete) operations on database views. For [Select](./query-api#select) statements database views are always leveraged, if available. | ||
The CDS compiler generates [DDL](../../guides/databases?impl-variant=java#generating-sql-ddl) files from your CDS model, including database views for all CDS [views and projections](../../cds/cdl#views-projections). | ||
|
||
Views and projections can be resolved if the following conditions are met: | ||
Entity tables and views are deployed to the database. Read statements on CDS views target the database view, while write operations are resolved to the underlying entity table by the CAP Java runtime, when possible. | ||
|
||
- The view definition does not use any other clause than `columns` and `excluding`. | ||
- The projection includes all key elements; with the exception of insert operations with generated UUID keys. | ||
- The projection includes all elements with a `not null` constraint, unless they have a default value. | ||
- The projection must not include calculated fields when running queries against a remote OData service. | ||
- The projection must not include [path expressions](../../cds/cql#path-expressions) using to-many associations. | ||
::: tip Avoid mixin compositions | ||
Avoid introducing new compositions in CDS views and prefer associations instead, as [deep write](#updatable-views) and [cascading delete](#delete-via-view) are only supported for compositions in persistence entities. | ||
::: | ||
::: warning Avoid to-many associations | ||
Do not use to-many associations in the select clause of CDS views. This blocks write operations and can cause performance issues due to record duplication on read. | ||
::: | ||
|
||
### Read from Runtime Views { #runtimeviews } | ||
|
||
To add or update CDS views without redeploying the database schema, annotate your views with [@cds.persistence.skip](../../guides/databases#cds-persistence-skip). This tells the CDS compiler to skip generating database views for these entities, and the CAP Java runtime resolves them dynamically at runtime. | ||
|
||
::: info Limitations | ||
Runtime views do not support aggregations, unions, joins, or subqueries in the FROM clause. | ||
MattSchur marked this conversation as resolved.
Show resolved
Hide resolved
|
||
::: | ||
|
||
**Example** - consider the following CDS model and query: | ||
|
||
```cds | ||
entity Books { | ||
key ID : UUID; | ||
title : String; | ||
stock : Integer; | ||
author : Association to one Authors; | ||
} | ||
@cds.persistence.skip | ||
entity BooksWithLowStock as projection on Books { | ||
ID, title, author.name as author | ||
} where stock < 10; // makes the view read only | ||
``` | ||
```sql | ||
SELECT from BooksWithLowStock where author = 'Kafka' | ||
``` | ||
|
||
CAP Java supports two modes for resolving runtime views: | ||
|
||
#### CTE mode | ||
|
||
This is the default mode in CAP Java `4.x`. The runtime translates the view definition into a _Common Table Expression_ (CTE) and sends it with the query to the database. | ||
|
||
```sql | ||
WITH BOOKSWITHLOWSTOCK_CTE AS ( | ||
SELECT B.ID, | ||
B.TITLE, | ||
A.NAME AS "AUTHOR" | ||
FROM BOOKS B | ||
LEFT OUTER JOIN AUTHOR A ON B.AUTHOR_ID = A.ID | ||
WHERE B.STOCK < 10 | ||
) | ||
SELECT ID, TITLE, AUTHOR AS "author" | ||
FROM BOOKSWITHLOWSTOCK_CTE | ||
WHERE A.NAME = ? | ||
``` | ||
|
||
::: tip CAP Java 3.10 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we really document outdated stuff? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should mention this, since |
||
Enable *cte* mode with *cds.sql.runtimeView.mode: cte* | ||
::: | ||
|
||
#### Resolve mode | ||
|
||
The runtime _resolves_ the view definition to the underlying persistence entities and executes the query directly against the corresponding tables. | ||
|
||
```sql | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The more I read this (especially these references later in chapters on modifications and deletions), the more I feel that we are to vague about what this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should not reference this mode later for modifications. This is actually very hard to match the statements:
|
||
SELECT B.ID, B.TITLE, A.NAME AS "author" | ||
FROM BOOKS AS B | ||
LEFT OUTER JOIN AUTHORS AS A ON B.AUTHOR_ID = A.ID | ||
WHERE B.STOCK < 10 AND A.NAME = ? | ||
``` | ||
|
||
::: info Limitations of `resolve` mode | ||
Using associations introduced by the view (mixins), as well as complex draft queries, and [draft-enabling](../fiori-drafts#reading-drafts) runtime views are not supported in *resolve* mode. | ||
::: | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe also add that runtime views are not lockable. See https://cap.cloud.sap/docs/java/working-with-cql/query-api#write-lock There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pessimistic locking is possible if the runtime view can be resolved or the view (generated CTE) fulfills the conditions mentioned in locking, which should be the case since we don't support union, join and aggregations in runtime views. It locks the underlying table rows. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On HANA? Wow. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, HANA seems to not support locks via CTEs, so only the resolve mode works. I'll add a note. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should fix this? E.g. use different mode for queries with locks? They are usually very simple and should resolve to the baseline entity 1:1. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I already created a BLI to introduce query hints to control the mode and for lock we can set the hint to resolve mode. |
||
### Draft with Runtime Views { #draft-views } | ||
|
||
If you define runtime views on [draft-enabled](../fiori-drafts#reading-drafts) entities and want to run draft specific queries on these views, set the *cds.drafts.persistence* configuration to `split` and run the queries through the [Draft Service](../fiori-drafts#draft-service) or [Application Service](../cqn-services/application-services#application-services). The [Persistence Service](../cqn-services/persistence-services) only works for non-draft specific queries. | ||
|
||
::: warning Avoid draft-enabling runtime views | ||
[Draft-enabling](../fiori-drafts#reading-drafts) runtime views is only supported in *cte* mode. The cds-compiler creates a corresponding draft persistence table for the view and a database schema update is required each time the runtime view is changed. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this whole chapter should sound like: if you draft-enable runtime views make sure that
CDS Compiler? |
||
|
||
[Draft activate](../fiori-drafts#editing-drafts) updates the active entity via the runtime view, therefore the view must fulfill all requirements of an [updatable view](#updatable-views). | ||
::: | ||
|
||
### Write through Views { #updatable-views } | ||
|
||
You can run [Insert](./query-api#insert), [Upsert](./query-api#upsert), and [Update](./query-api#update) statements on CDS views. The CAP Java runtime attempts to resolve these to the underlying entity definitions—similar to the runtime view [resolve mode](query-execution#resolve-mode) for queries. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
When delegating queries between Application Services and Remote Services, statements are also resolved to the targeted service's entity definitions. | ||
|
||
Write operations on CDS views are supported if: | ||
|
||
For [Insert](./query-api#insert) or [Update](./query-api#update) operations, if the projection contains functions or expressions, these values are ignored. Path expressions navigating *to-one* associations, can be used in projections as shown by the `Header` view in the following example. The `Header` view includes the element `country` from the associated entity `Address`. | ||
- The view definition uses only *columns* and *excluding* (no *join*, *union*, or *where*). | ||
- The projection includes all *not null* elements (incl. keys), unless they have a default or generated value. | ||
- The projection does not include [path expressions](../../cds/cql#path-expressions) using *to-many* associations. | ||
- Projections targeting *remote OData* services must not include calculated elements. | ||
|
||
You can use path expressions navigating *to-one* compositions, as shown by `headerStatus`: | ||
|
||
```cds | ||
// Supported | ||
entity Order as projection on bookshop.Order; | ||
entity Order as projection on bookshop.Order { ID, status as state }; | ||
// CDS views supporting write operations | ||
entity Order as projection on bookshop.Order excluding { status }; | ||
entity Header as projection on bookshop.OrderHeader { key ID, address.country as country }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This model should have reference to its base model. It is not clear that |
||
|
||
entity Order as projection on bookshop.Order { | ||
key ID, | ||
header.status as headerStatus, | ||
header.customer.name as customerName @readonly, | ||
items as lineItems, // aliased composition | ||
toUpper(shipToCountry) as country : String // ignored on write | ||
}; | ||
``` | ||
|
||
If a view is too complex to be resolved by the CDS runtime, the statement remains unmodified. Views that cannot be resolved by the CDS runtime include the use of `join`, `union` and the `where` clause. | ||
- For the Persistence Service, this means the runtime _attempts_ to execute the write operation on the database view. Whether this execution is possible is [database dependent](../cqn-services/persistence-services#database-support). | ||
- For Application Services and Remote Services, the targeted service will reject the statement. | ||
- Data for elements corresponding to *expressions* and *functions* (*country*) is ignored. | ||
- [Deep write](./query-execution#deep-insert-upsert) via (aliased) compositions (*items*) is supported if there are corresponding compositions in the underlying entity definition. Deep write via compositions that are only defined in the view (mixins) is not supported and the data for such mixin compositions is ignored. | ||
|
||
::: warning Path Expressions in Views | ||
Path expressions navigating *associations* (e.g. *header.customer.name*) are [not writable](#cascading-over-associations) by default. To avoid issues on write, annotate them with [@readonly](../../guides/providing-services#readonly). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that warning is not necessary here. We just list instructions: the one about mixins (maybe also add a reference to some definition of this concept) is as important as the paths inside associations not being writable. Except the keys, right? |
||
|
||
Path expressions over *compositions* (e.g. *header.status*) are writable but require the Insert data to include values for all *not null* elements of the target entity. In the example above, the order header must have a generated key to support inserting new orders with a status. | ||
::: | ||
|
||
If the CAP Java runtime cannot resolve a view, write operations are either rejected (Application/Remote Services) or attempted directly on the database view (Persistence Service). In the second case the execution depends on the [database support](../cqn-services/persistence-services#database-support). | ||
|
||
Example of a view that can't be resolved: | ||
### Delete through Views { #delete-via-view } | ||
|
||
The CAP Java runtime attempts to resolve [Delete](./query-api#delete) operations to the underlying entity definitions—similar to the runtime view [resolve mode](query-execution#resolve-mode) for queries. | ||
|
||
::: warning Cascading Delete is applied on persistence entity level | ||
Compositions that are added, changed or removed in CDS views are not considered by [cascading delete](./query-execution#cascading-delete). | ||
::: | ||
|
||
For example, the following CDS model defines `Order` with header and items, and `OrderView` which excludes header and exposes items as `lineItems`: | ||
|
||
```cds | ||
// Unsupported | ||
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 }; | ||
entity Order : cuid, managed { | ||
header : Composition of one OrderHeader; | ||
items : Composition of many OrderItem on items.parent = $self; | ||
} | ||
entity OrderView as projection on db.Order { | ||
ID, | ||
items as lineItems, | ||
delivery : Composition of one Delivery on delivery.order = $self | ||
} | ||
``` | ||
```sql | ||
DELETE from OrderView where ID = 42 | ||
``` | ||
The delete operation is resolved to the underlying `Order` entity with ID *42* and cascades over the `header` and `items` compositions. The `delivery` composition, which is only defined in the view, is ignored and does not cascade the delete operation to `Delivery`. | ||
|
||
|
||
## Concurrency Control | ||
|
||
Concurrency control allows protecting your data against unexpected concurrent changes. | ||
|
||
### Optimistic Concurrency Control {#optimistic} | ||
### Optimistic Locking {#optimistic} | ||
|
||
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. | ||
|
||
|
@@ -400,64 +520,6 @@ The `lock()` method has an optional parameter `timeout` that indicates the maxim | |
|
||
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 from your CDS model, including SQL views for all CDS [views and projections](../../cds/cdl#views-projections). As a result, adding or modifying CDS views typically requires redeploying the database schema. | ||
|
||
To avoid schema redeployments when you add or update CDS views, annotate them with [@cds.persistence.skip](../../guides/databases#cds-persistence-skip). This annotation tells the CDS compiler to skip generating database views for these entities. Instead, the CAP Java runtime dynamically resolves such views at runtime. | ||
|
||
::: warning Limitations | ||
Runtime views support only simple [CDS projections](../../cds/cdl#as-projection-on). They do not support complex views that use aggregations, unions, joins, or subqueries in the `FROM` clause. To read [draft-enabled](../fiori-drafts#reading-drafts) entities, set `cds.drafts.persistence` to `split`. [Calculated elements](../../cds/cdl#calculated-elements) are not yet supported in runtime views. | ||
::: | ||
|
||
For example, consider the following CDS model and query: | ||
|
||
```cds | ||
entity Books { | ||
key id : UUID; | ||
title : String; | ||
stock : Integer; | ||
author : Association to one Authors; | ||
} | ||
@cds.persistence.skip | ||
entity BooksWithLowStock as projection on Books { | ||
id, title, author.name as author | ||
} where stock < 10; | ||
``` | ||
```sql | ||
Select BooksWithLowStock where author = 'Kafka' | ||
``` | ||
|
||
CAP Java provides two modes for resolving runtime views: | ||
|
||
**`cte` mode**: The runtime translates the view definition into a _Common Table Expression_ (CTE) and sends it with the query to the database. | ||
|
||
```sql | ||
WITH BOOKSWITHLOWSTOCK_CTE AS ( | ||
SELECT B.ID, | ||
B.TITLE, | ||
A.NAME AS "AUTHOR" | ||
FROM BOOKS B | ||
LEFT OUTER JOIN AUTHOR A ON B.AUTHOR_ID = A.ID | ||
WHERE B.STOCK < 10 | ||
) | ||
SELECT ID, TITLE, AUTHOR AS "author" | ||
FROM BOOKSWITHLOWSTOCK_CTE | ||
WHERE A.NAME = ? | ||
``` | ||
|
||
::: tip | ||
CAP Java 4.x uses `cte` mode by default. In 3.10, enable it with **cds.sql.runtimeView.mode: cte**. | ||
::: | ||
|
||
**`resolve` mode**: The runtime _resolves_ the view definition to the underlying persistence entities and executes the query directly against them. | ||
|
||
```sql | ||
SELECT B.ID, B.TITLE, A.NAME AS "author" | ||
FROM BOOKS AS B | ||
LEFT OUTER JOIN AUTHORS AS A ON B.AUTHOR_ID = A.ID | ||
WHERE B.STOCK < 10 AND A.NAME = ? | ||
``` | ||
|
||
## Using I/O Streams in Queries | ||
|
||
|
@@ -602,7 +664,7 @@ Map<String, String> titleToDescription = | |
For the entities defined in the data model, CAP Java SDK can generate interfaces for you through [a Maven plugin](../cqn-services/persistence-services#staticmodel). | ||
|
||
|
||
### Using Entity References from Result Rows in CDS QL Statements {#entity-refs} | ||
### Entity References {#entity-refs} | ||
|
||
For result rows that contain all key values of an entity, you get an [entity reference](./query-api#entity-refs) via the `ref()` method. This reference addresses the entity via the key values from the result row. | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a red box? If this leads to a data loss or garbage being left, then it is nasty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be sufficiently explain later. Why mention it here?