Skip to content

Conversation

@patricebender
Copy link
Contributor

TBD

Comment on lines 37 to 39
No matter where `CXL` is used, it always manifests in queries.
For example, [a calculated element](./cdl/#calculated-elements) defined in an entity will be resolved
to the respective calculation in the generated query when the entity is queried.
Copy link
Contributor

Choose a reason for hiding this comment

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

You can use expressions in various other places

  • translated to EDMX-expressions
  • to define projections between types
  • projections can be resolved at runtime (runtime views)
  • expressions can be evaluated in memory

Copy link
Contributor

@Akatuoro Akatuoro Dec 11, 2025

Choose a reason for hiding this comment

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

Query does not mean that it is a database query. There can be multiple vehicles for an expression. Conceptually though, it can be understood as part of a query - whether it is either sent to the database, converted to edmx (and then sent to the backend again), or evaluated in memory.
This is an important point though. Expressions are not only meant for database queries.

Keeping this open -> happy for suggestions on how to formulate this.

@renejeglinsky
Copy link
Contributor

There's one example where it's not the text but the box that is clickable. Is that on purpose?
image

cds/cql-draft.md Outdated
```
:::

TODO explanation
Copy link
Contributor

Choose a reason for hiding this comment

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

The path expression Books:author denotes the set of all author entities that can be reached by following the association author from any entity Books. I.e. the set of authors that have written a book.

cds/cql-draft.md Outdated

### in the where clause {#in-where-clause}

A path expression can also be used as part of the where clause to filter based on elements of related entities:
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
A path expression can also be used as part of the where clause to filter based on elements of related entities:
A single-valued path expression can also be used as part of the where clause to filter based on elements of related entities:

cds/cql-draft.md Outdated
:::

In this example, we select all books that belong to the `Fantasy` genre.
The table alias for the `genre` association is used in the where clause of the SQL query.
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
The table alias for the `genre` association is used in the where clause of the SQL query.

This implementation detail does not apply to CAP Java. In CAP Java, we use table aliases T0, T1, ..., which is also an implementation detail not worth to mention.

cds/cql-draft.md Outdated
:::

In this example, we select all books and order them by the date of birth of their authors.
The table alias for the `author` association is used in the order by clause of the SQL query.
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
The table alias for the `author` association is used in the order by clause of the SQL query.

see above

cds/cql-draft.md Outdated
```js [CQL] {3}
> await cds.ql`
SELECT from Authors { name,
books[ price < 19.99 ] as cheapBooks {
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
books[ price < 19.99 ] as cheapBooks {
books[ price < 20.00 ] as cheapBooks {

cds/cql-draft.md Outdated

TODO: This is database specific

In this example, the runtime makes use of JSON functions to aggregate the related `books` into a JSON array.
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
In this example, the runtime makes use of JSON functions to aggregate the related `books` into a JSON array.
In this example, the CAP Node.js runtime makes use of JSON functions to aggregate the related `books` into a JSON array.

cds/cql-draft.md Outdated
In this example, the runtime makes use of JSON functions to aggregate the related `books` into a JSON array.
This is because SQL databases do not have a native concept of nested result sets.

> TODO: Link to guide about JSON functions, What about java?
Copy link
Contributor

Choose a reason for hiding this comment

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

What about java?

How CAP Java executes this depends the expand optimization mode used. We always do multiple queries:

join (default):

SELECT T0.name FROM Authors
SELECT T1.title, T1.price FROM Authors T0 JOIN Books T1 ON T1.author_id = T0.ID AND T1.price < 20.00

parent-keys:

SELECT T0.ID, T0.name FROM Authors T0 -- yields <set of author IDs>
SELECT T0.tile, T0.price FROM Books T0 WHERE T0.author_id IN (<set of author IDs>) AND T0.price < 20.00

load-single (legacy):

SELECT T0.ID, T0.name FROM Authors T0 -- yields <set of author IDs>
for(id in <set of author IDs>): 
  SELECT T0.tile, T0.price FROM Books T0 WHERE T0.author_id = id AND T0.price < 20.00

subquery

SELECT T0.ID, T0.name FROM Authors T0 -- yields <set of author IDs>
SELECT T0.tile, T0.price FROM BOOKS T0 WHERE T0.ID IN (SELECT T1.ID FROM Authors T0 JOIN Books T1 ON T1.author_id = T0.ID AND T1.price < 20.00)

(Not 100% sure about subquery)

Copy link
Contributor

@Akatuoro Akatuoro Jan 7, 2026

Choose a reason for hiding this comment

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

Thanks for listing these. Imho we should include the explanation around how expands are executed in a database specific section. While a developer should not need to interact with them or at most simply choose an optimization mode, it does help in the understanding of what is actually happening -> demystifying.

### in the from clause {#in-from-clause}

A path expression can also be used in the `from` clause of a query to navigate to a related entity:
When navigating along a to-many association to a leaf element, the result is flattened:
Copy link
Contributor

Choose a reason for hiding this comment

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

is flattened

No. You create a cross product of the associated entities. This is nothing we should promote as it might have slipped in by accident. Also it might work only in the translation to SQL. The example below will result in a left outer join of books and authors. Also authors which have not written a book will be included.

Please either expand books from authors or start the selection from books:

Select from Authors { books { title } as title, name as author }

(gives a nested result)

or

Select from Books { title, author.name as author }

(will not include authors that have written no books)

The important point here is: the expression language CXL does not make any assumption that it is translated to SQL or how it's converted to SQL.

In your example below, the path expression books.title is just the set of titles of the books which are associated to a particular author via the Authors entity's books association. Hence the query

SELECT from Authors { books.title as title, name as author }

should return something like

[
  { title: ['Wuthering Heights'], author: 'Emily Brontë' },
  { title: ['Jane Eyre'], author: 'Charlotte Brontë' },
  { title: ['Eleonora', 'The Raven'], author: 'Edgar Allen Poe' },
  { title: ['Catweazle'], author: 'Richard Carpenter' }
]

which it doesn't. Instead it returns the "flattened" result which you mention. I think it does so by accident!

To make things worse think about:

SELECT FROM Authors { 
   name, 
   books.title book, 
   addresses.{city, street} as address, 
   phonenumbers.{country_code, area_code, extension}
}

This would result in an unwanted, unexpected 4-way left outer join.

I think there is no valid use case for path expression involving to-many associations besides in expands and exists!

Copy link
Contributor

Choose a reason for hiding this comment

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

This was an ask by Daniel -> we should discuss this in our next review meeting

cds/cxl.md Outdated
### in the where clause {#in-where-clause}

A path expression can also be used as part of the where clause to filter based on elements of related entities:
::: info 💡 Associations are **forward-declared joins**
Copy link
Contributor

Choose a reason for hiding this comment

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

I still think we should sunset the phrase "forward-declared joins"

Copy link
Contributor

Choose a reason for hiding this comment

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

@danjoa the forward declaration terminology according to Wikipedia does not quite match the behavior of associations.

Let's discuss this in our next review meeting.
Earlier discussion: #2260 (comment)

> await cds.ql`SELECT from Books { title } where genre.name = 'Fantasy'` // [!code focus]
[ { title: 'Catweazle' } ]
```
The join condition is defined **ahead of time** as part of the association.
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
The join condition is defined **ahead of time** as part of the association.
The foreign-key relationship is defined **ahead of time** as part of the association.

Copy link
Contributor

Choose a reason for hiding this comment

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

I do think join condition is accurate as it does not need to be a foreign key relationship, although it pretty much always includes one. A join is the SQL terminology and well understood, even if cxl can also manifest in other ways.

@Akatuoro
Copy link
Contributor

Akatuoro commented Jan 8, 2026

@danjoa the PR is now ready for an initial version.

CQL specific sections are moved out to a separate branch for a follow-up PR. We also moved the scientific background section as it still needs some work to be useful.

@agoerler I've gone through your feedback for the parts that are in scope for the merge, will go through the rest later.

Copy link
Contributor

@agoerler agoerler left a comment

Choose a reason for hiding this comment

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

I still think this documentation is way to much focused on explaining how CQL is converted to SQL. Instead we should provide a documentation that explains the fundamentals of the expression language:

Values

  • literals
  • refs
    • paths
      • filtered paths -> predicates
  • parameters
  • value functions
  • value expressions

Predicates

  • comparison predicates
  • connectives (AND, OR)
  • negation
  • Boolean functions

I would not know why you need SQL to explain any of this.

cds/cxl.md Outdated

```cds
annotate AdminService.Authors:dateOfDeath with @assert: (case
when dateOfDeath > $now then 'Cannot be in the future'
Copy link
Contributor

Choose a reason for hiding this comment

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

dateOfDeath > $now

$now is a timestamp whereas dateOfDeath is a (local) date hence the types are incompatible.

We should introduce $today, which would be quite handy.

:::code-group
```js [CQL]
> await cds.ql`SELECT from Books:author { name }` // [!code focus]
> await cds.ql `SELECT from Authors { books.title as title, name as author }` // [!code focus]
Copy link
Contributor

Choose a reason for hiding this comment

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

please void the navigation over the to-many association books as it introduces the before mentioned join

Copy link
Contributor

Choose a reason for hiding this comment

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

As mentioned, it was requested by Daniel -> need to discuss this together. Until then, I'm keeping it in the PR



::: info 💡 A binary operator is an operator that operates on two operands.
::: info A binary operator is an operator that operates on two operands.
Copy link
Contributor

Choose a reason for hiding this comment

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

Here would be the place to document which operators there are. That = does a three-valued comparison whereas == does a two-valued comparison. What's the precedence of operators. That we support || for string concatenation. This is all not documented so far. But it's very fundamental. We can't explain infix filters without explaining filter predicates.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, we still need to document the operators (and also the literals) more.

Here's also some explanation for operators, but interestingly only for the node impl variant:
https://cap.cloud.sap/docs/guides/databases?impl-variant=node#standard-operators

Java then has its own explanation in the Java CQL docs:
https://cap.cloud.sap/docs/java/working-with-cql/query-api#comparison-operators

I like the way it is documented in Java, we can probably do something similar here. Do you want to contribute this?

@Akatuoro
Copy link
Contributor

Akatuoro commented Jan 9, 2026

I still think this documentation is way to much focused on explaining how CQL is converted to SQL. Instead we should provide a documentation that explains the fundamentals of the expression language:

Values

  • literals

  • refs

    • paths

      • filtered paths -> predicates
  • parameters

  • value functions

  • value expressions

Predicates

  • comparison predicates
  • connectives (AND, OR)
  • negation
  • Boolean functions

I would not know why you need SQL to explain any of this.

The samples all have the corresponding SQL to make transparent what actually happens in the background. This is quite important for humans and especially for LLMs. The SQL is always hidden behind an extra tab, so it is not the focus.

If CQL (which CXL is a part of) was really its own language, it would make sense to describe and specify everything in detail from the ground up. Since it is based on SQL, we can use that fact to build on pre-existing knowledge.

To the structure of Value vs. Predicate... I don't agree. Predicates are also "values"... or rather, expressions... simply boolean expressions. There are some constructs which require that an expression returns a boolean (case when, ternary, filters, ...), but this boolean can also come from a literal, a ref etc.

@patricebender patricebender requested a review from danjoa January 13, 2026 16:03
Copy link
Member

@danjoa danjoa left a comment

Choose a reason for hiding this comment

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

👍👍👍

Copy link
Contributor

Choose a reason for hiding this comment

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

The new shape looks much better :-)

Copy link
Contributor

Choose a reason for hiding this comment

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

The vertical alignment of the text is off

### in model definitions

Expressions can be used to define calculated elements.
Typically, this is done in a column of a query. CAP
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
Typically, this is done in a column of a query. CAP
Typically, this is done on the select list of a query. CAP


### in queries

Expressions can be used in various parts of a query, e.g., in the select list, in the where clause, in order by clauses, and more:
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
Expressions can be used in various parts of a query, e.g., in the select list, in the where clause, in order by clauses, and more:
Expressions can be used in various parts of a query, e.g., on the select list, in the where clause, in order by clauses, and more:

| `count(x)` | Returns the count of non-null values of `x`. |
| `countdistinct(x)` | Returns the count of distinct non-null values of `x`. |

CAP supports a set of [portable functions](../guides/databases/cql-to-sql.md#portable-functions) that can be used in all expressions. Those functions are passed through to the underlying database, allowing you to leverage the same functions for different databases, which greatly enhances portability.
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather define the portable functions here (as they are DB-independent and also apply for to remote OData calls). Then link from the database guide to here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants