From b47b756380e1813a2051ab009bad3c03339e51f4 Mon Sep 17 00:00:00 2001 From: Patrice Bender Date: Tue, 26 Mar 2024 10:37:15 +0100 Subject: [PATCH 1/2] fix(`expand`): Only accept on structures, assocs or table aliases an `expand` on a `many` type has no defined behavior, yet. we should reject it, just as the compiler does for static views. --- db-service/lib/infer/index.js | 29 ++++++++++++---------- db-service/test/bookshop/db/schema.cds | 3 +++ db-service/test/cds-infer/negative.test.js | 11 ++++++++ 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/db-service/lib/infer/index.js b/db-service/lib/infer/index.js index 9b616a7ea..14431a9e8 100644 --- a/db-service/lib/infer/index.js +++ b/db-service/lib/infer/index.js @@ -795,19 +795,22 @@ function infer(originalQuery, model) { ? new cds.struct({ elements: inferredExpandSubquery.elements }) : new cds.array({ items: new cds.struct({ elements: inferredExpandSubquery.elements }) }) return Object.defineProperty(res, '$assocExpand', { value: true }) - } // struct - let elements = {} - expand.forEach(e => { - if (e === '*') { - elements = { ...elements, ...$leafLink.definition.elements } - } else { - inferQueryElement(e, false, $leafLink, { inExpr: true, inNestedProjection: true }) - if (e.expand) elements[e.as || e.flatName] = resolveExpand(e) - if (e.inline) elements = { ...elements, ...resolveInline(e) } - else elements[e.as || e.flatName] = e.$refLinks ? e.$refLinks[e.$refLinks.length - 1].definition : e - } - }) - return new cds.struct({ elements }) + } else if ($leafLink.definition.elements) { + let elements = {} + expand.forEach(e => { + if (e === '*') { + elements = { ...elements, ...$leafLink.definition.elements } + } else { + inferQueryElement(e, false, $leafLink, { inExpr: true, inNestedProjection: true }) + if (e.expand) elements[e.as || e.flatName] = resolveExpand(e) + if (e.inline) elements = { ...elements, ...resolveInline(e) } + else elements[e.as || e.flatName] = e.$refLinks ? e.$refLinks[e.$refLinks.length - 1].definition : e + } + }) + return new cds.struct({ elements }) + } else { + throw new Error(`Unexpected “expand” on “${col.ref.map(idOnly)}”; can only be used after a reference to a structure, association or table alias`) + } } function stepNotFoundInPredecessor(step, def) { diff --git a/db-service/test/bookshop/db/schema.cds b/db-service/test/bookshop/db/schema.cds index 5002d3d97..62bba3000 100644 --- a/db-service/test/bookshop/db/schema.cds +++ b/db-service/test/bookshop/db/schema.cds @@ -300,6 +300,9 @@ entity SoccerPlayers { key jerseyNumber: Integer; name: String; team: Association to SoccerTeams; + emails: many { + address: String; + } } entity TestPublisher { diff --git a/db-service/test/cds-infer/negative.test.js b/db-service/test/cds-infer/negative.test.js index 1b34c6f9a..bbcd2d6c2 100644 --- a/db-service/test/cds-infer/negative.test.js +++ b/db-service/test/cds-infer/negative.test.js @@ -365,6 +365,17 @@ describe('negative', () => { ), ).to.throw(/"title" not found in the elements of "bookshop.Authors"/) }) + + it('expand on `.items` not possible', () => { + expect(() => _inferred(CQL`SELECT from bookshop.SoccerPlayers { name, emails { address } }`, model)).to.throw( + "Unexpected “expand” on “emails”; can only be used after a reference to a structure, association or table alias" + ) + }) + it('expand on scalar not possible', () => { + expect(() => _inferred(CQL`SELECT from bookshop.SoccerPlayers { name { address } }`, model)).to.throw( + "Unexpected “expand” on “name”; can only be used after a reference to a structure, association or table alias" + ) + }) }) describe('infix filters', () => { From e844bc6570bc92f0bc22d7d9717dcb4e2ca7c93c Mon Sep 17 00:00:00 2001 From: Patrice Bender Date: Tue, 9 Apr 2024 14:53:47 +0200 Subject: [PATCH 2/2] same goes for inline --- db-service/lib/infer/index.js | 8 ++++++-- db-service/test/cds-infer/negative.test.js | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/db-service/lib/infer/index.js b/db-service/lib/infer/index.js index 14431a9e8..2e84335fd 100644 --- a/db-service/lib/infer/index.js +++ b/db-service/lib/infer/index.js @@ -727,6 +727,9 @@ function infer(originalQuery, model) { function resolveInline(col, namePrefix = col.as || col.flatName) { const { inline, $refLinks } = col const $leafLink = $refLinks[$refLinks.length - 1] + if(!$leafLink.definition.target && !$leafLink.definition.elements) { + throw new Error(`Unexpected “inline” on “${col.ref.map(idOnly)}”; can only be used after a reference to a structure, association or table alias`) + } let elements = {} inline.forEach(inlineCol => { inferQueryElement(inlineCol, false, $leafLink, { inExpr: true, inNestedProjection: true, baseColumn: col }) @@ -780,6 +783,9 @@ function infer(originalQuery, model) { function resolveExpand(col) { const { expand, $refLinks } = col const $leafLink = $refLinks?.[$refLinks.length - 1] || inferred.SELECT.from.$refLinks.at(-1) // fallback to anonymous expand + if(!$leafLink.definition.target && !$leafLink.definition.elements) { + throw new Error(`Unexpected “expand” on “${col.ref.map(idOnly)}”; can only be used after a reference to a structure, association or table alias`) + } const target = getDefinition($leafLink.definition.target) if (target) { const expandSubquery = { @@ -808,8 +814,6 @@ function infer(originalQuery, model) { } }) return new cds.struct({ elements }) - } else { - throw new Error(`Unexpected “expand” on “${col.ref.map(idOnly)}”; can only be used after a reference to a structure, association or table alias`) } } diff --git a/db-service/test/cds-infer/negative.test.js b/db-service/test/cds-infer/negative.test.js index bbcd2d6c2..e50b591d2 100644 --- a/db-service/test/cds-infer/negative.test.js +++ b/db-service/test/cds-infer/negative.test.js @@ -376,6 +376,17 @@ describe('negative', () => { "Unexpected “expand” on “name”; can only be used after a reference to a structure, association or table alias" ) }) + + it('inline on `.items` not possible', () => { + expect(() => _inferred(CQL`SELECT from bookshop.SoccerPlayers { name, emails.{ address } }`, model)).to.throw( + "Unexpected “inline” on “emails”; can only be used after a reference to a structure, association or table alias" + ) + }) + it('inline on scalar not possible', () => { + expect(() => _inferred(CQL`SELECT from bookshop.SoccerPlayers { name.{ address } }`, model)).to.throw( + "Unexpected “inline” on “name”; can only be used after a reference to a structure, association or table alias" + ) + }) }) describe('infix filters', () => {