Skip to content

Docs for typed result APIs #2006

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions java/cds-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ In CAP Java data is represented in maps. To simplify data access in custom code,

![This graphic is explained in the accompanying text.](./assets/accessor.drawio.svg)

The `Row`s of a [query result](./working-with-cql/query-execution#result) as well as the [generated accessor interfaces](#generated-accessor-interfaces) already extend `CdsData`. Using the helper class [Struct](#struct) you can extend any `Map<String, Object>` with the CdsData `interface`:
The rows of a [query result](./working-with-cql/query-execution#result) as well as the [generated accessor interfaces](#generated-accessor-interfaces) already extend `CdsData`. Using the helper class [Struct](#struct) you can access any `Map<String, Object>` with the CdsData `interface`:

```java
Map<String, Object> map = new HashMap<>();
Expand Down Expand Up @@ -307,7 +307,7 @@ You can use the functions, `CQL.cosineSimilarity` or `CQL.l2Distance` (Euclidean
```Java
CqnVector v = CQL.vector(embedding);

Result similarBooks = service.run(Select.from(BOOKS).where(b ->
CdsResult<Books> similarBooks = service.run(Select.from(BOOKS).where(b ->
CQL.cosineSimilarity(b.embedding(), v).gt(0.9))
);
```
Expand All @@ -322,7 +322,7 @@ CqnSelect query = Select.from(BOOKS)
.where(b -> b.ID().ne(bookId).and(similarity.gt(0.9)))
.orderBy(b -> b.get("similarity").desc());

Result similarBooks = db.run(select, CdsVector.of(embedding));
Result similarBooks = db.run(query, CdsVector.of(embedding));
```

In CDS QL queries, elements of type `cds.Vector` are not included in select _all_ queries. They must be explicitly added to the select list:
Expand Down Expand Up @@ -909,8 +909,8 @@ diff.process(newImage, oldImage, type);
```

```java
Result newImage = service.run(Select.from(...));
Result oldImage = service.run(Select.from(...));
CdsResult<?> newImage = service.run(Select.from(...));
CdsResult<?> oldImage = service.run(Select.from(...));

diff.process(newImage, oldImage, newImage.rowType());
```
Expand Down Expand Up @@ -1373,7 +1373,7 @@ Using a custom `On` handler makes sense if you want to prevent that the default

```java
@On(event = CqnService.EVENT_UPDATE)
public Result processCoverImage(CdsUpdateEventContext context, List<Books> books) {
public CdsResult<?> processCoverImage(CdsUpdateEventContext context, List<Books> books) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check the return type and the execution on 1382

books.forEach(book -> {
book.setCoverImage(new CoverImagePreProcessor(book.getCoverImage()));
});
Expand Down
2 changes: 1 addition & 1 deletion java/cqn-services/application-services.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ If an event handler for an `UPDATE` or `DELETE` event does not specify a result

Event handlers for `INSERT` and `UPSERT` events can return a result representing the data that was inserted/upserted.

A failed insert is indicated by throwing an exception, for example, a `UniqueConstraintException` or a `CdsServiceException` with error status `CONFLICT`.
A failed insert is indicated by throwing an exception, for example, a `UniqueConstraintException` or a `ServiceException` with error status `CONFLICT`.

### Result Builder { #result-builder}

Expand Down
2 changes: 1 addition & 1 deletion java/developing-applications/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public class CatalogServiceTest {

@Test
public void discountApplied() {
Result result = catalogService.run(Select.from(Books_.class).byId("51061ce3-ddde-4d70-a2dc-6314afbcc73e"));
CdsResult<Books> result = catalogService.run(Select.from(Books_.class).byId("51061ce3-ddde-4d70-a2dc-6314afbcc73e"));

// book with title "The Raven" and a stock quantity of > 111
Books book = result.single(Books.class);
Expand Down
4 changes: 2 additions & 2 deletions java/event-handlers/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -447,15 +447,15 @@ The return value of an event can be set by returning a value in an event handler

```java
@On(entity = Books_.CDS_NAME)
public Result readBooks(CdsReadEventContext context) {
public CdsResult<?> readBooks(CdsReadEventContext context) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public CdsResult<?> readBooks(CdsReadEventContext context) {
public Result readBooks(CdsReadEventContext context) {

return db.run(context.getCqn());
}
```

In case an event handler method of the `Before` or `On` phase has a return value it automatically [completes the event processing](#eventcompletion), once it's executed.
Event handler methods of the `After` phase that have a return value, replace the return value of the event.

For [CRUD events](../cqn-services/application-services#crudevents) and [draft-specific CRUD events](../fiori-drafts#draftevents), return values that extend `Iterable<? extends Map<String, Object>>` are supported. The `Result` object or a list of entity data (for example `List<Books>`) fulfill this requirement.
For [CRUD events](../cqn-services/application-services#crudevents) and [draft-specific CRUD events](../fiori-drafts#draftevents), return values that extend `Iterable<? extends Map<String, Object>>` are supported. The `Result` and `CdsResult<?>` interfaces or a list of entity data (for example `List<Books>`) fulfill this requirement.

```java
@On(entity = Books_.CDS_NAME)
Expand Down
2 changes: 1 addition & 1 deletion java/event-handlers/request-contexts.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ To propagate the parent context, create an instance of `RequestContextRunner` in

```java
RequestContextRunner runner = runtime.requestContext();
Future<Result> result = Executors.newSingleThreadExecutor().submit(() -> {
Future<CdsResult<Books>> result = Executors.newSingleThreadExecutor().submit(() -> {
return runner.run(threadContext -> {
return persistenceService.run(Select.from(Books_.class));
});
Expand Down
2 changes: 1 addition & 1 deletion java/fiori-drafts.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ You can then delegate reading of active entities, for example to a remote S/4 sy

```java
@On(entity = MyRemoteDraftEnabledEntity_.CDS_NAME)
public Result delegateToS4(ActiveReadEventContext context) {
public CdsResult<?> delegateToS4(ActiveReadEventContext context) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public CdsResult<?> delegateToS4(ActiveReadEventContext context) {
public Result delegateToS4(ActiveReadEventContext context) {

return remoteS4.run(context.getCqn());
}
```
Expand Down
2 changes: 1 addition & 1 deletion java/reflection-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ FeatureTogglesInfo isbn = FeatureTogglesInfo.create(Collections.singletonMap("is

...

Future<Result> result = Executors.newSingleThreadExecutor().submit(() -> {
Future<CdsResult<Books>> result = Executors.newSingleThreadExecutor().submit(() -> {
return runtime.requestContext().featureToggles(isbn).run(rc -> {
return db.run(Select.from(Books_.CDS_NAME));
});
Expand Down
2 changes: 1 addition & 1 deletion java/working-with-cql/query-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1623,7 +1623,7 @@ BETWEEN

#### `IN` Predicate

The `IN` predicate tests if a value is equal to any value in a given list.
The `IN` predicate tests if a value is equal to any value in a given list.

The following example, filters for books written by Poe or Hemingway:

Expand Down
68 changes: 43 additions & 25 deletions java/working-with-cql/query-execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,12 @@ CqnDelete delete = Delete.from("bookshop.Books").byParams("ID");
Map<String, Object> paramSet1 = singletonMap("ID", 101);
Map<String, Object> paramSet1 = singletonMap("ID", 102);

Result result = service.run(query, asList(paramSet1, paramSet2));
CdsResult<?> result = service.run(query, asList(paramSet1, paramSet2));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
CdsResult<?> result = service.run(query, asList(paramSet1, paramSet2));
Result result = service.run(query, asList(paramSet1, paramSet2));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
CdsResult<?> result = service.run(query, asList(paramSet1, paramSet2));
Result result = service.run(delete, asList(paramSet1, paramSet2));

long deletedRows = result.rowCount();
```

From the result of a batch update/delete the total number of updated/deleted rows can be determined by [rowCount()](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/Result.html#rowCount--), and [rowCount(batchIndex)](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/Result.html#rowCount-int-) returns the number of updated/deleted rows for a specific parameter set of the batch.
The number of batches can be retrieved via the [batchCount()](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/Result.html#batchCount--) method. Batch updates also return the update data.
From the result of a batch update/delete the total number of updated/deleted rows can be determined by [rowCount()](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/CdsResult.html#rowCount--), and [rowCount(batchIndex)](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/CdsResult.html#rowCount-int-) returns the number of updated/deleted rows for a specific parameter set of the batch.
The number of batches can be retrieved via the [batchCount()](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/CdsResult.html#batchCount--) method. Batch updates also return the update data.

The maximum batch size for update and delete can be configured via `cds.sql.max-batch-size` and has a default of 1000.

Expand Down Expand Up @@ -145,7 +145,7 @@ Result updateResult = service.run(update);

The update `Result` contains the data that is written by the statement execution. Additionally to the given data, it may contain values generated for [managed data](../../guides/domain-modeling#managed-data) and foreign key values.

The [row count](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/Result.html#rowCount()) of the update `Result` indicates how many rows where updated during the statement execution:
The [row count](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/CdsResult.html#rowCount()) of the update `CdsResult` indicates how many rows where updated during the statement execution:


```java
Expand Down Expand Up @@ -321,7 +321,7 @@ To add or update CDS views without redeploying the database schema, annotate the

Runtime views must be simple [projections](../../cds/cdl#as-projection-on), not using *aggregations*, *join*, *union* or *subqueries* in the *from* clause, but may have a *where* condition if they are only used to read. On write, the restrictions for [write through views](#updatable-views) apply in the same way as for standard CDS views. However, if a runtime view cannot be resolved, a fallback to database views is not possible, and the statement fails with an error.

CAP Java provides two modes for resolving runtime views during read operations: [cte](#rtview-cte) and [resolve](#rtview-resolve).
CAP Java provides two modes for resolving runtime views during read operations: [cte](#rtview-cte) and [resolve](#rtview-resolve).

::: details Changing the runtime view mode
To globally set the runtime view mode, use the property `cds.sql.runtimeView.mode` with value `cte` (the default) or `resolve` in the *application.yml*. To set the mode for a specific runtime view, annotate it with `@cds.java.runtimeView.mode: cte|resolve`.
Expand Down Expand Up @@ -394,7 +394,7 @@ On PostgreSQL, some [pessimistic locking](#pessimistic-locking) queries on runti

### Draft Queries on Views { #draft-views }

When draft-enabling a CDS view, the CDS Compiler creates a corresponding draft persistence table for this view. [Draft activate](../fiori-drafts#editing-drafts) updates the active entity via the view.
When draft-enabling a CDS view, the CDS Compiler creates a corresponding draft persistence table for this view. [Draft activate](../fiori-drafts#editing-drafts) updates the active entity via the view.

That means:
<br>
Expand All @@ -414,7 +414,7 @@ Draft-enabling runtime views is only supported in [*CTE*](#rtview-cte) mode and

### Views on Remote Services

When delegating queries between Application Services and Remote Services, statements are resolved to the targeted service's entity definition by the CAP Java runtime.
When delegating queries between Application Services and Remote Services, statements are resolved to the targeted service's entity definition by the CAP Java runtime.

For read, the CDS views are resolved similar to the runtime view [resolve](#rtview-resolve) mode. For write operations, views targeting *remote OData* services must fulfill the following:

Expand Down Expand Up @@ -471,7 +471,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. 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).
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 `CdsResult` and implement your error handling. If the value of `rowCount` is 0, that indicates that no row was updated (or deleted).
:::


Expand Down Expand Up @@ -527,7 +527,7 @@ List<Order> orders = db.run(select).listOf(Order.class);

orders.forEach(o -> o.setStatus("cancelled"));

Result rs = db.execute(Update.entity(ORDER).entries(orders));
CdsResult<Order> 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
Expand Down Expand Up @@ -609,22 +609,23 @@ CAP Java doesn't have a dedicated API to execute native SQL Statements. However,

## Query Result Processing { #result}

The result of a query is abstracted by the `Result` interface, which is an iterable of `Row`. A `Row` is a `Map<String, Object>` with additional convenience methods and extends [CdsData](../cds-data#cds-data).
The result of a query is abstracted by the `CdsResult` interface, which is an iterable of [CdsData](../cds-data#cds-data) or its more specific [generated accessor interfaces](../cds-data#typed-access).
`CdsData` itself is a `Map<String, Object>` with additional convenience methods.

You can iterate over a `Result`:
You can iterate over a `CdsResult`:

```java
Result result = ...
CdsResult<?> result = ...

for (Row row : result) {
for (CdsData row : result) {
System.out.println(row.get("title"));
}
```

Or process it with the [Stream API](https://docs.oracle.com/javase/8/docs/api/?java/util/stream/Stream.html):

```java
Result result = ...
CdsResult<?> result = ...

result.forEach(r -> System.out.println(r.get("title")));

Expand All @@ -634,26 +635,30 @@ result.stream().map(r -> r.get("title")).forEach(System.out::println);
If your query is expected to return exactly one row, you can access it with the `single` method:

```java
Result result = ...
CdsResult<?> result = ...

Row row = result.single();
CdsData row = result.single();
```

If it returns a result, like a `find by id` would, you can obtain it using `first`:
::: tip
Set application property `cds.errors.preferServiceException` to ensure `single()` throws an appropriate 404 exception in case no row is found.
:::

You can also obtain the first row using `first`:

```java
Result result = ...
CdsResult<?> result = ...

Optional<Row> row = result.first();
Optional<CdsData> row = result.first();
row.ifPresent(r -> System.out.println(r.get("title")));
```

The `Row`'s `getPath` method supports paths to simplify extracting values from nested maps. This also simplifies extracting values from results with to-one expands using the generic accessor. Paths with collection-valued segments and infix filters are not supported.
The `CdsData`'s `getPath` method supports paths to simplify extracting values from nested maps. This also simplifies extracting values from results with to-one expands using the generic accessor. Paths with collection-valued segments and infix filters are not supported.

```java
CqnSelect select = Select.from(BOOKS).columns(
b -> b.title(), b -> b.author().expand()).byId(101);
Row book = dataStore.execute(select).single();
CdsData book = dataStore.execute(select).single();

String author = book.getPath("author.name");
```
Expand Down Expand Up @@ -690,8 +695,8 @@ interface Book {
Integer getStock();
}

Row row = ...
Book book = row.as(Book.class);
CdsResult<?> result = service.run(query);
Book book = result.single(Book.class);

String title = book.getTitle();
Integer stock = book.getStock();
Expand All @@ -700,7 +705,7 @@ Integer stock = book.getStock();
Interfaces can also be used to get a typed list or stream over the result:

```java
Result result = ...
CdsResult<?> result = ...

List<Book> books = result.listOf(Book.class);

Expand All @@ -710,6 +715,19 @@ 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).

When setting `linkedInterfaces` to `true` in the CDS Maven Plugin's `generate` goal, [query builder interfaces](../working-with-cql/query-api#concepts) and [data accessor interfaces](../cds-data#typed-access) are linked. This enables automatically typed results when executing a `Select` or `Update` statement, avoiding the need to explicitly pass the data accessor interface class to methods like `single(Entity.class)`, `listOf(Entity.class)` or `streamOf(Entity.class)`.

```java
import static cds.gen.catalogservice.CatalogService_.BOOKS;

var select = Select.from(BOOKS).byId(4711); // use var or Select<Books_>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var select = Select.from(BOOKS).byId(4711); // use var or Select<Books_>
Select<Books_> select = Select.from(BOOKS).byId(4711);

CdsResult<Books> result = service.run(select);
Books book = result.single();
```

::: tip
Avoid using `CqnSelect` or `CqnUpdate` for typed query declarations, but prefer `var` to allow the Java compiler to retain the entity query type, linking to the data accessor interface: `var result = service.run(select);`
:::

### Entity References {#entity-refs}

Expand Down Expand Up @@ -757,7 +775,7 @@ CqnDelete d = Delete.from(joyce.address())

### Introspecting the Row Type

The `rowType` method allows to introspect the element names and types of a query's `Result`. It returns a `CdsStructuredType` describing the result in terms of the [Reflection API](../reflection-api):
The `rowType` method allows to introspect the element names and types of a query's `CdsResult`. It returns a `CdsStructuredType` describing the result in terms of the [Reflection API](../reflection-api):

```java
CqnSelect query = Select.from(AUTHOR)
Expand Down