diff --git a/db-service/lib/infer/index.js b/db-service/lib/infer/index.js index 9b616a7ea..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 = { @@ -795,19 +801,20 @@ 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 }) + } } 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..e50b591d2 100644 --- a/db-service/test/cds-infer/negative.test.js +++ b/db-service/test/cds-infer/negative.test.js @@ -365,6 +365,28 @@ 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" + ) + }) + + 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', () => {