Skip to content

Commit

Permalink
fix: Treat assoc-like calculated elements as unmanaged assocs (#830)
Browse files Browse the repository at this point in the history
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

→ We don't need to treat them any different than a regular unmanaged
association.
  • Loading branch information
patricebender authored Oct 9, 2024
1 parent 5e62096 commit cbe0df7
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 10 deletions.
6 changes: 1 addition & 5 deletions db-service/lib/cqn4sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <eqOps>, this is replaced by comparing all leaf elements with null, combined with and.
Expand Down Expand Up @@ -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.
*
Expand Down
7 changes: 4 additions & 3 deletions db-service/lib/infer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -1054,7 +1055,7 @@ function infer(originalQuery, model) {
if (element.type !== 'cds.LargeBinary') {
queryElements[k] = element
}
if (element.value) {
if (isCalculatedOnRead(element)) {
linkCalculatedElement(element)
}
}
Expand All @@ -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)
}
})
Expand Down
15 changes: 15 additions & 0 deletions db-service/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
9 changes: 8 additions & 1 deletion db-service/test/bookshop/db/booksWithExpr.cds
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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];
}
21 changes: 20 additions & 1 deletion db-service/test/cqn4sql/calculated-elements.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit cbe0df7

Please sign in to comment.