Skip to content

Commit 39fbadf

Browse files
feat: query modifiers on expand ref are propagated to subquery (#1049)
enable the following: ```js cds.ql`SELECT name, books[order by price] { * } FROM Authors` ``` it is still possible to have query modifiers defined as sibling property to `expand`: ```js { ref: ['author'], expand: [ { ref: ['name'], }, ], limit: { offset: { val: 1, }, rows: { val: 1, }, }, orderBy: [ { ref: ['name'], sort: 'asc', }, ], } ``` --> If both are set, the modifiers in the `ref` of the expanded association have precedence
1 parent fddaa08 commit 39fbadf

File tree

2 files changed

+72
-8
lines changed

2 files changed

+72
-8
lines changed

db-service/lib/cqn4sql.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -807,11 +807,13 @@ function cqn4sql(originalQuery, model) {
807807
// `SELECT from Authors { books.genre as genreOfBooks { name } } becomes `SELECT from Books:genre as genreOfBooks`
808808
const from = { ref: subqueryFromRef, as: uniqueSubqueryAlias }
809809
const subqueryBase = {}
810-
for (const [key, value] of Object.entries(column)) {
811-
if (!(key in { ref: true, expand: true })) {
812-
subqueryBase[key] = value
813-
}
810+
const queryModifiers = { ...column, ...ref.at(-1) }
811+
for (const [key, value] of Object.entries(queryModifiers)) {
812+
if (key in { limit: 1, orderBy: 1, groupBy: 1, excluding: 1, where: 1, having: 1 }) subqueryBase[key] = value
814813
}
814+
// where at leaf already part of subqueryBase
815+
if (from.ref.at(-1).where) from.ref[from.ref.length - 1] = [from.ref.at(-1).id]
816+
815817
const subquery = {
816818
SELECT: {
817819
...subqueryBase,
@@ -1225,8 +1227,7 @@ function cqn4sql(originalQuery, model) {
12251227
if (flattenThisForeignKey) {
12261228
const fkElement = getElementForRef(k.ref, getDefinition(element.target))
12271229
let fkBaseName
1228-
if (!leafAssoc || leafAssoc.onlyForeignKeyAccess)
1229-
fkBaseName = `${baseName}_${k.as || k.ref.at(-1)}`
1230+
if (!leafAssoc || leafAssoc.onlyForeignKeyAccess) fkBaseName = `${baseName}_${k.as || k.ref.at(-1)}`
12301231
// e.g. if foreign key is accessed via infix filter - use join alias to access key in target
12311232
else fkBaseName = k.ref.at(-1)
12321233
const fkPath = [...csnPath, k.ref.at(-1)]
@@ -1478,8 +1479,7 @@ function cqn4sql(originalQuery, model) {
14781479
// reject associations in expression, except if we are in an infix filter -> $baseLink is set
14791480
assertNoStructInXpr(token, $baseLink)
14801481
// reject virtual elements in expressions as they will lead to a sql error down the line
1481-
if(definition?.virtual)
1482-
throw new Error(`Virtual elements are not allowed in expressions`)
1482+
if (definition?.virtual) throw new Error(`Virtual elements are not allowed in expressions`)
14831483

14841484
let result = is_regexp(token?.val) ? token : copy(token) // REVISIT: too expensive! //
14851485
if (token.ref) {

db-service/test/cqn4sql/expand.test.js

+64
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,70 @@ describe('Unfold expands on associations to special subselects', () => {
280280
expect(JSON.parse(JSON.stringify(res))).to.deep.equal(expected)
281281
})
282282

283+
it('do not loose additional properties on expand column if defined in ref', () => {
284+
const q = cds.ql`SELECT from bookshop.Authors { books[order by price] { title } }`
285+
const res = cqn4sql(q)
286+
const expected = CQL`SELECT from bookshop.Authors as Authors {
287+
(
288+
SELECT from bookshop.Books as books {
289+
books.title
290+
}
291+
where Authors.ID = books.author_ID
292+
order by books.price
293+
) as books
294+
}`
295+
expect(JSON.parse(JSON.stringify(res))).to.deep.equal(expected)
296+
})
297+
298+
it('query modifiers in ref have precedence over expand siblings', () => {
299+
const q = {
300+
SELECT: {
301+
from: {
302+
ref: ['bookshop.Books'],
303+
},
304+
columns: [
305+
{
306+
ref: [{id: 'author', orderBy: [{ref:['dateOfBirth'], sort: 'desc'}]}],
307+
expand: [
308+
{
309+
ref: ['name'],
310+
},
311+
],
312+
limit: {
313+
offset: {
314+
val: 1,
315+
},
316+
rows: {
317+
val: 1,
318+
},
319+
},
320+
// this order by is overwritten by the one in the ref
321+
orderBy: [
322+
{
323+
ref: ['name'],
324+
sort: 'asc',
325+
},
326+
],
327+
},
328+
],
329+
},
330+
}
331+
332+
const res = cqn4sql(q)
333+
const expected = CQL`SELECT from bookshop.Books as Books {
334+
(
335+
SELECT from bookshop.Authors as author {
336+
author.name
337+
}
338+
where Books.author_ID = author.ID
339+
order by author.dateOfBirth desc
340+
limit 1
341+
offset 1
342+
) as author
343+
}`
344+
expect(JSON.parse(JSON.stringify(res))).to.deep.equal(expected)
345+
})
346+
283347
it('add where exists <assoc> shortcut to expand subquery where condition', () => {
284348
const q = {
285349
SELECT: {

0 commit comments

Comments
 (0)