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

feat: query modifiers on expand ref are propagated to subquery #1049

Merged
merged 1 commit into from
Feb 27, 2025
Merged
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
16 changes: 8 additions & 8 deletions db-service/lib/cqn4sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -807,11 +807,13 @@ function cqn4sql(originalQuery, model) {
// `SELECT from Authors { books.genre as genreOfBooks { name } } becomes `SELECT from Books:genre as genreOfBooks`
const from = { ref: subqueryFromRef, as: uniqueSubqueryAlias }
const subqueryBase = {}
for (const [key, value] of Object.entries(column)) {
if (!(key in { ref: true, expand: true })) {
subqueryBase[key] = value
}
const queryModifiers = { ...column, ...ref.at(-1) }
for (const [key, value] of Object.entries(queryModifiers)) {
if (key in { limit: 1, orderBy: 1, groupBy: 1, excluding: 1, where: 1, having: 1 }) subqueryBase[key] = value
Copy link
Member Author

Choose a reason for hiding this comment

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

everything covered?

Copy link
Contributor

Choose a reason for hiding this comment

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

compared with cqn2sql and lgtm.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 but you can take out excluding → that is always next to expand or inline. Also in CQL syntax as supported by the compiler, and hence the CQN produced by it:

cds.ql `SELECT from Authors { ID, name, books[order by title desc limit 1] {*} excluding { descr } }`
 cds.ql {
  SELECT: {
    from: { ref: [ 'Authors' ] },
    columns: [
      { ref: [ 'ID' ] },
      { ref: [ 'name' ] },
      {
        ref: [
          {
            id: 'books',
            orderBy: [ { ref: [ 'title' ], sort: 'desc' } ],
            limit: { rows: { val: 1 } }
          }
        ],
        expand: [ '*' ],
        excluding: [ 'descr' ]
      }
    ]
  }
}

}
// where at leaf already part of subqueryBase
if (from.ref.at(-1).where) from.ref[from.ref.length - 1] = [from.ref.at(-1).id]

const subquery = {
SELECT: {
...subqueryBase,
Expand Down Expand Up @@ -1225,8 +1227,7 @@ function cqn4sql(originalQuery, model) {
if (flattenThisForeignKey) {
const fkElement = getElementForRef(k.ref, getDefinition(element.target))
let fkBaseName
if (!leafAssoc || leafAssoc.onlyForeignKeyAccess)
fkBaseName = `${baseName}_${k.as || k.ref.at(-1)}`
if (!leafAssoc || leafAssoc.onlyForeignKeyAccess) fkBaseName = `${baseName}_${k.as || k.ref.at(-1)}`
// e.g. if foreign key is accessed via infix filter - use join alias to access key in target
else fkBaseName = k.ref.at(-1)
const fkPath = [...csnPath, k.ref.at(-1)]
Expand Down Expand Up @@ -1478,8 +1479,7 @@ function cqn4sql(originalQuery, model) {
// reject associations in expression, except if we are in an infix filter -> $baseLink is set
assertNoStructInXpr(token, $baseLink)
// reject virtual elements in expressions as they will lead to a sql error down the line
if(definition?.virtual)
throw new Error(`Virtual elements are not allowed in expressions`)
if (definition?.virtual) throw new Error(`Virtual elements are not allowed in expressions`)

let result = is_regexp(token?.val) ? token : copy(token) // REVISIT: too expensive! //
if (token.ref) {
Expand Down
64 changes: 64 additions & 0 deletions db-service/test/cqn4sql/expand.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,70 @@ describe('Unfold expands on associations to special subselects', () => {
expect(JSON.parse(JSON.stringify(res))).to.deep.equal(expected)
})

it('do not loose additional properties on expand column if defined in ref', () => {
const q = cds.ql`SELECT from bookshop.Authors { books[order by price] { title } }`
const res = cqn4sql(q)
const expected = CQL`SELECT from bookshop.Authors as Authors {
(
SELECT from bookshop.Books as books {
books.title
}
where Authors.ID = books.author_ID
order by books.price
) as books
}`
expect(JSON.parse(JSON.stringify(res))).to.deep.equal(expected)
})

it('query modifiers in ref have precedence over expand siblings', () => {
const q = {
SELECT: {
from: {
ref: ['bookshop.Books'],
},
columns: [
{
ref: [{id: 'author', orderBy: [{ref:['dateOfBirth'], sort: 'desc'}]}],
expand: [
{
ref: ['name'],
},
],
limit: {
offset: {
val: 1,
},
rows: {
val: 1,
},
},
// this order by is overwritten by the one in the ref
orderBy: [
{
ref: ['name'],
sort: 'asc',
},
],
},
],
},
}

const res = cqn4sql(q)
const expected = CQL`SELECT from bookshop.Books as Books {
(
SELECT from bookshop.Authors as author {
author.name
}
where Books.author_ID = author.ID
order by author.dateOfBirth desc
limit 1
offset 1
) as author
}`
expect(JSON.parse(JSON.stringify(res))).to.deep.equal(expected)
})

it('add where exists <assoc> shortcut to expand subquery where condition', () => {
const q = {
SELECT: {
Expand Down