Skip to content

Commit

Permalink
fix: nested ternary in calculated element (#981)
Browse files Browse the repository at this point in the history
calculated elements may reference other calcuated elements and so
forth.. that is why we have logic in place which constructs subpaths for
such scenarios which enables us to calculate joins deeply built into a
given calculated element hierarchy. However, the logic had a flaw for
e.g. nested ternary expressions.

For `(1 > 0 ? 1 : (book.stock > 10 ? value : 3));`

we accidentally constructed subpaths like `book.stock.value` which led
to an error because we tried to create a join for `value`, where no
association prefix is present.

fix cap/issue#17660
  • Loading branch information
patricebender authored Jan 17, 2025
1 parent 5346bc4 commit 5f4a1fe
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 1 deletion.
6 changes: 5 additions & 1 deletion db-service/lib/infer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ function infer(originalQuery, model) {
} else if (arg.xpr || arg.args) {
const prop = arg.xpr ? 'xpr' : 'args'
arg[prop].forEach(step => {
const subPath = { $refLinks: [...basePath.$refLinks], ref: [...basePath.ref] }
let subPath = { $refLinks: [...basePath.$refLinks], ref: [...basePath.ref] }
if (step.ref) {
step.$refLinks.forEach((link, i) => {
const { definition } = link
Expand All @@ -874,6 +874,10 @@ function infer(originalQuery, model) {
} else if (step.args || step.xpr) {
const nestedProp = step.xpr ? 'xpr' : 'args'
step[nestedProp].forEach(a => {
// reset sub path for each nested argument
// e.g. case when <path> then <otherPath> else <anotherPath> end
if(!a.ref)
subPath = { $refLinks: [...basePath.$refLinks], ref: [...basePath.ref] }
mergePathsIntoJoinTree(a, subPath)
})
}
Expand Down
10 changes: 10 additions & 0 deletions db-service/test/bookshop/db/booksWithExpr.cds
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,13 @@ entity VariableReplacements {
// with variable replacements
authorAlive = author[dateOfBirth <= $now and dateOfDeath >= $now and $user.unknown.foo.bar = 'Bob'];
}

entity Ternary {
key ID : Integer;
value : Integer;
book : Association to Books;
nestedTernary : Integer = (1 > 0 ? 1 : (book.stock > 10 ? value : 3));
nestedTernaryWithTwoJoins : Integer = (1 > 0 ? 1 : (book.stock > book.author.age ? value : 3));
nestedTernaryWithNestedXpr : Integer = (1 > 0 ? 1 : (((10 + book.stock) in (1, 2, 3 , 4)) ? value : 3));
calculatedElementInNestedTernary : Integer = (1 > 0 ? 1 : (book.stock > nestedTernaryWithTwoJoins ? value : 3));
}
33 changes: 33 additions & 0 deletions db-service/test/cqn4sql/calculated-elements.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,39 @@ describe('Unfolding calculated elements in select list', () => {
expect(query).to.deep.equal(expected)
})

it('in ternary', () => {
let query = cqn4sql(CQL`SELECT from booksCalc.Ternary { ID, nestedTernary }`, model)
const expected = CQL`SELECT from booksCalc.Ternary as Ternary
left join booksCalc.Books as book on book.ID = Ternary.book_ID
{
Ternary.ID,
(case when 1 > 0 then 1 else (case when book.stock > 10 then Ternary.value else 3 end) end) as nestedTernary
}`
expect(query).to.deep.equal(expected)
})

it('calcualted element in nested ternary', () => {
let query = cqn4sql(CQL`SELECT from booksCalc.Ternary { ID, calculatedElementInNestedTernary }`, model)
const expected = CQL`SELECT from booksCalc.Ternary as Ternary
left join booksCalc.Books as book on book.ID = Ternary.book_ID
left join booksCalc.Authors as author on author.ID = book.author_ID
{
Ternary.ID,
(case when 1 > 0 then 1 else (case when book.stock > (case when 1 > 0 then 1 else (case when book.stock > years_between(author.dateOfBirth, author.dateOfDeath) then Ternary.value else 3 end) end) then Ternary.value else 3 end) end) as calculatedElementInNestedTernary
}`
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
expect(query).to.deep.equal(expected)
})
it('list in ternary', () => {
let query = cqn4sql(CQL`SELECT from booksCalc.Ternary { ID, nestedTernaryWithNestedXpr }`, model)
const expected = CQL`SELECT from booksCalc.Ternary as Ternary
left join booksCalc.Books as book on book.ID = Ternary.book_ID
{
Ternary.ID,
(case when 1 > 0 then 1 else (case when ( (10 + book.stock) in (1, 2, 3, 4) ) then Ternary.value else 3 end) end) as nestedTernaryWithNestedXpr
}`
expect(query).to.deep.equal(expected)
})
it('in function', () => {
let query = cqn4sql(CQL`SELECT from booksCalc.Books { ID, round(area, 2) as f }`, model)
const expected = CQL`SELECT from booksCalc.Books as Books {
Expand Down

0 comments on commit 5f4a1fe

Please sign in to comment.