diff --git a/db-service/lib/cqn4sql.js b/db-service/lib/cqn4sql.js index 7cdc6de7d..8029206e7 100644 --- a/db-service/lib/cqn4sql.js +++ b/db-service/lib/cqn4sql.js @@ -4,7 +4,7 @@ const cds = require('@sap/cds') const infer = require('./infer') const { computeColumnsToBeSearched } = require('./search') -const { prettyPrintRef } = require('./utils') +const { prettyPrintRef, isCalculatedOnRead } = require('./utils') /** * For operators of , this is replaced by comparing all leaf elements with null, combined with and. @@ -317,10 +317,6 @@ function cqn4sql(originalQuery, model) { } } - function isCalculatedOnRead(def) { - return def?.value && !def.value.stored - } - /** * Walks over a list of columns (ref's, xpr, subqueries, val), applies flattening on structured types and expands wildcards. * diff --git a/db-service/lib/infer/index.js b/db-service/lib/infer/index.js index c02ac1496..d2b604169 100644 --- a/db-service/lib/infer/index.js +++ b/db-service/lib/infer/index.js @@ -4,6 +4,7 @@ const cds = require('@sap/cds') const JoinTree = require('./join-tree') const { pseudos } = require('./pseudos') +const { isCalculatedOnRead } = require('../utils') const cdsTypes = cds.linked({ definitions: { Timestamp: { type: 'cds.Timestamp' }, @@ -746,7 +747,7 @@ function infer(originalQuery, model) { joinTree.mergeColumn(colWithBase, originalQuery.outerQueries) } } - if (leafArt.value && !leafArt.value.stored) { + if (isCalculatedOnRead(leafArt)) { linkCalculatedElement(column, $baseLink, baseColumn) } @@ -1054,7 +1055,7 @@ function infer(originalQuery, model) { if (element.type !== 'cds.LargeBinary') { queryElements[k] = element } - if (element.value) { + if (isCalculatedOnRead(element)) { linkCalculatedElement(element) } } @@ -1071,7 +1072,7 @@ function infer(originalQuery, model) { if (exclude(name) || name in queryElements) return true const element = tableAliases[0].tableAlias.elements[name] if (element.type !== 'cds.LargeBinary') queryElements[name] = element - if (element.value) { + if (isCalculatedOnRead(element)) { linkCalculatedElement(element) } }) diff --git a/db-service/lib/utils.js b/db-service/lib/utils.js index 7e83a40a0..272c1c5e9 100644 --- a/db-service/lib/utils.js +++ b/db-service/lib/utils.js @@ -21,7 +21,22 @@ function prettyPrintRef(ref, model = null) { }, '') } +/** + * Determines if a definition is calculated on read. + * - Stored calculated elements are not unfolded + * - Association like calculated elements have been re-written by the compiler + * they essentially behave like unmanaged associations as their calculations + * have been incorporated into an on-condition which is handled elsewhere + * + * @param {Object} def - The definition to check. + * @returns {boolean} - Returns true if the definition is calculated on read, otherwise false. + */ +function isCalculatedOnRead(def) { + return def?.value && !def.value.stored && !def.on +} + // export the function to be used in other modules module.exports = { prettyPrintRef, + isCalculatedOnRead } diff --git a/db-service/test/bookshop/db/booksWithExpr.cds b/db-service/test/bookshop/db/booksWithExpr.cds index e03d41062..12cd9488c 100644 --- a/db-service/test/bookshop/db/booksWithExpr.cds +++ b/db-service/test/bookshop/db/booksWithExpr.cds @@ -79,7 +79,7 @@ entity LBooks { key ID : Integer; title : localized String; - ctitle = substring(title, 3, 3); // requires compiler 4.1 + ctitle = substring(title, 3, 3); length : Decimal; width : Decimal; @@ -92,3 +92,10 @@ entity Simple { my: Association to Simple; myName: String = my.name; } + +entity VariableReplacements { + key ID: Integer; + author: Association to Authors; + // with variable replacements + authorAlive = author[dateOfBirth <= $now and dateOfDeath >= $now]; +} diff --git a/db-service/test/cqn4sql/calculated-elements.test.js b/db-service/test/cqn4sql/calculated-elements.test.js index 145fcac5d..eff383428 100644 --- a/db-service/test/cqn4sql/calculated-elements.test.js +++ b/db-service/test/cqn4sql/calculated-elements.test.js @@ -968,6 +968,26 @@ describe('Unfolding calculated elements in other places', () => { }` expect(query).to.deep.equal(expected) }) + it('variable replacements are left untouched in calc element navigation', () => { + const q = CQL`SELECT from booksCalc.VariableReplacements { ID, authorAlive.firstName }` + const expected = CQL`SELECT from booksCalc.VariableReplacements as VariableReplacements + left join booksCalc.Authors as authorAlive on ( authorAlive.ID = VariableReplacements.author_ID ) + and ( authorAlive.dateOfBirth <= $now and authorAlive.dateOfDeath >= $now ) + { + VariableReplacements.ID, + authorAlive.firstName as authorAlive_firstName + }` + expect(cqn4sql(q, model)).to.deep.equal(expected) + }) + it('variable replacements are left untouched in calc elements via wildcard', () => { + const q = CQL`SELECT from booksCalc.VariableReplacements { * }` + const expected = CQL`SELECT from booksCalc.VariableReplacements as VariableReplacements + { + VariableReplacements.ID, + VariableReplacements.author_ID + }` + expect(cqn4sql(q, model)).to.deep.equal(expected) + }) }) describe('Unfolding calculated elements ... misc', () => { @@ -1002,7 +1022,6 @@ describe('Unfolding calculated elements and localized', () => { expect(query).to.deep.equal(expected) }) - // enable once cds-compiler v4.1 is released it('calculated element refers to localized element', () => { const q = CQL`SELECT from booksCalc.LBooks { ID, title, ctitle }` q.SELECT.localized = true