Skip to content

Commit

Permalink
fix(cds.infer): Always use srv.model (#451)
Browse files Browse the repository at this point in the history
  • Loading branch information
danjoa authored Feb 20, 2024
1 parent 51be94d commit 41cf4a2
Show file tree
Hide file tree
Showing 29 changed files with 127 additions and 64 deletions.
15 changes: 10 additions & 5 deletions db-service/lib/cqn2sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class CQN2SQLRenderer {
this.context = srv?.context || cds.context // Using srv.context is required due to stakeholders doing unmanaged txs without cds.context being set
this.class = new.target // for IntelliSense
this.class._init() // is a noop for subsequent calls
this.model = srv?.model
}

static _add_mixins(aspect, mixins) {
Expand Down Expand Up @@ -94,6 +95,10 @@ class CQN2SQLRenderer {
return q.target ? q : cds_infer(q)
}

cqn4sql(q) {
return cqn4sql(q, this.model)
}

// CREATE Statements ------------------------------------------------

/**
Expand All @@ -109,7 +114,7 @@ class CQN2SQLRenderer {
this.sql =
!query || target['@cds.persistence.table']
? `CREATE TABLE ${name} ( ${this.CREATE_elements(target.elements)} )`
: `CREATE VIEW ${name} AS ${this.SELECT(cqn4sql(query))}`
: `CREATE VIEW ${name} AS ${this.SELECT(this.cqn4sql(query))}`
this.values = []
return
}
Expand Down Expand Up @@ -613,7 +618,7 @@ class CQN2SQLRenderer {
c => c in elements && !elements[c].virtual && !elements[c].isAssociation,
))
this.sql = `INSERT INTO ${entity}${alias ? ' as ' + this.quote(alias) : ''} (${columns}) ${this.SELECT(
cqn4sql(INSERT.as),
this.cqn4sql(INSERT.as),
)}`
this.entries = [this.values]
return this.sql
Expand Down Expand Up @@ -819,8 +824,8 @@ class CQN2SQLRenderer {
*/
ref({ ref }) {
switch (ref[0]) {
case '$now': return this.func({ func: 'session_context', args: [{ val: '$now', param: false }] }) // REVISIT: why do we need param: false here?
case '$user': return this.func({ func: 'session_context', args: [{ val: '$user.'+ref[1]||'id', param: false }] }) // REVISIT: same here?
case '$now': return this.func({ func: 'session_context', args: [{ val: '$now', param: false }] }) // REVISIT: why do we need param: false here?
case '$user': return this.func({ func: 'session_context', args: [{ val: '$user.' + ref[1] || 'id', param: false }] }) // REVISIT: same here?
default: return ref.map(r => this.quote(r)).join('.')
}
}
Expand Down Expand Up @@ -988,6 +993,6 @@ const _empty = a => !a || a.length === 0
* @param {import('@sap/cds/apis/cqn').Query} q
* @param {import('@sap/cds/apis/csn').CSN} m
*/
module.exports = (q, m) => new CQN2SQLRenderer().render(cqn4sql(q, m), m)
module.exports = (q, m) => new CQN2SQLRenderer({ model: m }).render(cqn4sql(q, m))
module.exports.class = CQN2SQLRenderer
module.exports.classDefinition = CQN2SQLRenderer // class is a reserved typescript word
4 changes: 2 additions & 2 deletions db-service/lib/cqn4sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const { pseudos } = require('./infer/pseudos')
* @param {object} model
* @returns {object} transformedQuery the transformed query
*/
function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
function cqn4sql(originalQuery, model) {
const inferred = infer(originalQuery, model)
if (originalQuery.SELECT?.from.args && !originalQuery.joinTree) return inferred

Expand Down Expand Up @@ -122,7 +122,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
primaryKey.list.push({ ref: [transformedFrom.as, k.name] })
})

const transformedSubquery = cqn4sql(subquery)
const transformedSubquery = cqn4sql(subquery, model)

// replace where condition of original query with the transformed subquery
// correlate UPDATE / DELETE query with subquery by primary key matches
Expand Down
2 changes: 1 addition & 1 deletion db-service/lib/infer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ for (const each in cdsTypes) cdsTypes[`cds.${each}`] = cdsTypes[each]
* @param {import('@sap/cds/apis/csn').CSN} [model]
* @returns {import('./cqn').Query} = q with .target and .elements
*/
function infer(originalQuery, model = cds.context?.model || cds.model) {
function infer(originalQuery, model) {
if (!model) throw new Error('Please specify a model')
const inferred = typeof originalQuery === 'string' ? cds.parse.cql(originalQuery) : cds.ql.clone(originalQuery)

Expand Down
6 changes: 5 additions & 1 deletion db-service/test/cds-infer/api.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
'use strict'

const cds = require('@sap/cds/lib')
const _inferred = require('../../lib/infer')
const inferred = require('../../lib/infer')
function _inferred(q, m = cds.model) {
return inferred(q, m)
}

const { expect } = cds.test.in(__dirname + '/../bookshop')

describe('infer elements', () => {
Expand Down
7 changes: 5 additions & 2 deletions db-service/test/cds-infer/column.element.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ const cds = require('@sap/cds/lib')

const { expect } = cds.test.in(__dirname + '/../bookshop') // IMPORTANT: that has to go before the requires below to avoid loading cds.env before cds.test()
const cqn4sql = require('../../lib/cqn4sql')
const _inferred = require('../../lib/infer')
const inferred = require('../../lib/infer')
function _inferred(q, m = cds.model) {
return inferred(q, m)
}

describe('assign element onto columns', () => {
let model
Expand Down Expand Up @@ -108,7 +111,7 @@ describe('assign element onto columns', () => {
1 + 1 as Two,
(select from bookshop.Authors { name }) as subquery
}`
let inferred = cqn4sql(query) // cqn4sql will trigger recursive infer
let inferred = cqn4sql(query, model) // cqn4sql will trigger recursive infer
let inferredSubquery = inferred.SELECT.columns[1]

let { Authors } = model.entities
Expand Down
5 changes: 4 additions & 1 deletion db-service/test/cds-infer/elements.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

const cds = require('@sap/cds/lib')
const { expect } = cds.test.in(__dirname + '/../bookshop')
const _inferred = require('../../lib/infer')
const inferred = require('../../lib/infer')
function _inferred(q, m = cds.model) {
return inferred(q, m)
}

describe('infer elements', () => {
let model
Expand Down
5 changes: 4 additions & 1 deletion db-service/test/cds-infer/negative.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ const cds = require('@sap/cds/lib')

const { expect } = cds.test.in(__dirname + '/../bookshop') // IMPORTANT: that has to go before the requires below to avoid loading cds.env before cds.test()
const cqn4sql = require('../../lib/cqn4sql')
const _inferred = require('../../lib/infer')
const inferred = require('../../lib/infer')
function _inferred(q, m = cds.model) {
return inferred(q, m)
}

describe('negative', () => {
let model
Expand Down
5 changes: 4 additions & 1 deletion db-service/test/cds-infer/nested-projections.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

const cds = require('@sap/cds/lib')
const { expect } = cds.test
const _inferred = require('../../lib/infer')
const inferred = require('../../lib/infer')
function _inferred(q, m = cds.model) {
return inferred(q, m)
}

describe('nested projections', () => {
describe('expand', () => {
Expand Down
5 changes: 4 additions & 1 deletion db-service/test/cds-infer/source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

const cds = require('@sap/cds/lib')
const { expect } = cds.test.in(__dirname + '/../bookshop')
const _inferred = require('../../lib/infer')
const inferred = require('../../lib/infer')
function _inferred(q, m = cds.model) {
return inferred(q, m)
}

describe('simple', () => {
let model
Expand Down
5 changes: 4 additions & 1 deletion db-service/test/cqn2sql/create.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
'use strict'
const cds = require('@sap/cds/lib')
const cqn2sql = require('../../lib/cqn2sql')
const _cqn2sql = require('../../lib/cqn2sql')
function cqn2sql(q, m = cds.model) {
return _cqn2sql(q, m)
}
const cqn = require('./cqn.js')

beforeAll(async () => {
Expand Down
5 changes: 4 additions & 1 deletion db-service/test/cqn2sql/delete.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
'use strict'
const cds = require('@sap/cds/lib')
const cqn2sql = require('../../lib/cqn2sql')
const _cqn2sql = require('../../lib/cqn2sql')
function cqn2sql(q, m = cds.model) {
return _cqn2sql(q, m)
}

beforeAll(async () => {
cds.model = await cds.load(__dirname + '/testModel').then(cds.linked)
Expand Down
5 changes: 4 additions & 1 deletion db-service/test/cqn2sql/drop.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
'use strict'
const cds = require('@sap/cds/lib')
const cqn2sql = require('../../lib/cqn2sql')
const _cqn2sql = require('../../lib/cqn2sql')
function cqn2sql(q, m = cds.model) {
return _cqn2sql(q, m)
}

beforeAll(async () => {
cds.model = await cds.load(__dirname + '/testModel').then(cds.linked)
Expand Down
5 changes: 4 additions & 1 deletion db-service/test/cqn2sql/expression.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
'use strict'
const cds = require('@sap/cds/lib')
const cqn2sql = require('../../lib/cqn2sql')
const _cqn2sql = require('../../lib/cqn2sql')
function cqn2sql(q, m = cds.model) {
return _cqn2sql(q, m)
}

beforeAll(async () => {
cds.model = await cds.load(__dirname + '/testModel').then(cds.linked)
Expand Down
5 changes: 4 additions & 1 deletion db-service/test/cqn2sql/function.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const cds = require('@sap/cds/lib')
const cqn2sql = require('../../lib/cqn2sql')
const _cqn2sql = require('../../lib/cqn2sql')
function cqn2sql(q, m = cds.model) {
return _cqn2sql(q, m)
}

beforeAll(async () => {
cds.model = await cds.load(__dirname + '/testModel').then(cds.linked)
Expand Down
19 changes: 12 additions & 7 deletions db-service/test/cqn2sql/insert.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
const { text } = require('stream/consumers')

const cds = require('@sap/cds/lib')
const cqn2sql = require('../../lib/cqn2sql')

beforeAll(async () => {
let model = await cds.load(__dirname + '/testModel').then(cds.linked)
cds.model = cds.compile.for.nodejs(JSON.parse(JSON.stringify(model)))
})
const _cqn2sql = require('../../lib/cqn2sql')

describe('insert', () => {
let model
function cqn2sql(q) {
return _cqn2sql(q, model)
}

beforeAll(async () => {
model = await cds.load(__dirname + '/testModel').then(cds.linked)
model = cds.compile.for.nodejs(JSON.parse(JSON.stringify(model)))
})

describe('insert only', () => {
// Values are missing
test('test with insert values into columns', async () => {
Expand Down Expand Up @@ -110,7 +115,7 @@ describe('insert', () => {
},
}

const { sql } = cqn2sql(cqnInsert)
const { sql } = cqn2sql(cqnInsert, cds.model)
expect(sql).toEqual('INSERT INTO Foo (ID) SELECT Foo2.ID FROM Foo2 as Foo2')
})
})
Expand Down
10 changes: 7 additions & 3 deletions db-service/test/cqn2sql/select.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
'use strict'
const cds = require('@sap/cds/lib')
const cqn2sql = require('../../lib/cqn2sql')
const _cqn2sql = require('../../lib/cqn2sql')
function cqn2sql(q, m = cds.model) {

return _cqn2sql(q, m)
}
const cqn = require('./cqn.js')

// const getExpected = (sql, values) => {
Expand Down Expand Up @@ -59,7 +63,7 @@ describe('cqn2sql', () => {
expect(() => {
let q = cqn.selectNonExistent
// Skip cqn4sql as infer requires the entity to exist
const render = q => new cqn2sql.class().render(q)
const render = q => new _cqn2sql.class().render(q)
const { sql } = render(q)
expect(sql).toMatchSnapshot()
q = cds.ql.clone(q)
Expand All @@ -70,7 +74,7 @@ describe('cqn2sql', () => {

test('with select from non existent entity with star wildcard (extended)', () => {
expect(() => {
const customCqn2sql = class extends cqn2sql.class {
const customCqn2sql = class extends _cqn2sql.class {
SELECT_columns({ SELECT }) {
return SELECT.columns.map(x => `${this.quote(this.column_name(x))}`)
}
Expand Down
5 changes: 4 additions & 1 deletion db-service/test/cqn2sql/update.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
'use strict'
const cds = require('@sap/cds/lib')
const cqn2sql = require('../../lib/cqn2sql')
const _cqn2sql = require('../../lib/cqn2sql')
function cqn2sql(q, m = cds.model) {
return _cqn2sql(q, m)
}

beforeAll(async () => {
cds.model = await cds.load(__dirname + '/testModel').then(cds.linked)
Expand Down
5 changes: 4 additions & 1 deletion db-service/test/cqn2sql/upsert.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
const { text } = require('stream/consumers')

const cds = require('@sap/cds/lib')
const cqn2sql = require('../../lib/cqn2sql')
const _cqn2sql = require('../../lib/cqn2sql')
function cqn2sql(q, m = cds.model) {
return _cqn2sql(q, m)
}

beforeAll(async () => {
cds.model = await cds.load(__dirname + '/testModel').then(cds.linked)
Expand Down
15 changes: 8 additions & 7 deletions db-service/test/cqn4sql/DELETE.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ const cds = require('@sap/cds/lib')
const { expect } = cds.test

describe('DELETE', () => {
let model
beforeAll(async () => {
cds.model = await cds.load(__dirname + '/../bookshop/db/schema').then(cds.linked)
model = cds.model = await cds.load(__dirname + '/../bookshop/db/schema').then(cds.linked)
})
it('flatten structured access in where', () => {
const { DELETE } = cds.ql
let d = DELETE.from('bookshop.Books').where({ 'dedication.text': { '=': 'foo' } })
const query = cqn4sql(d)
const query = cqn4sql(d, model)
const expected = JSON.parse(
'{"DELETE":{"from":{"ref": ["bookshop.Books"], "as": "Books"},"where":[{"ref":["Books","dedication_text"]},"=",{"val":"foo"}]}}',
)
Expand All @@ -21,7 +22,7 @@ describe('DELETE', () => {
it('DELETE with where exists expansion', () => {
const { DELETE } = cds.ql
let d = DELETE.from('bookshop.Books:author')
const query = cqn4sql(d)
const query = cqn4sql(d, model)
// how to express this in CQN?
// DELETE.from({ref: ['bookshop.Authors'], as: 'author'}).where('exists ( SELECT 1 from bookshop.Books as Books where author_ID = author.ID)')
const expected = JSON.parse(`{
Expand Down Expand Up @@ -70,10 +71,10 @@ describe('DELETE', () => {
expect(query.DELETE).to.deep.equal(expected.DELETE)
})
it('DELETE with where exists expansion and path expression', () => {
cds.model = cds.compile.for.nodejs(JSON.parse(JSON.stringify(cds.model)))
const forNodeModel = cds.compile.for.nodejs(JSON.parse(JSON.stringify(cds.model)))
const { DELETE } = cds.ql
let d = DELETE.from('bookshop.Books:author').where(`books.title = 'Harry Potter'`)
const query = cqn4sql(d)
const query = cqn4sql(d, forNodeModel)

// this is the final exists subquery
const subquery = CQL`
Expand Down Expand Up @@ -179,14 +180,14 @@ describe('DELETE', () => {
],
},
}
const res = cqn4sql(query)
const res = cqn4sql(query, model)
expect(res).to.deep.equal(expected)
})

it('DELETE with assoc filter and where exists expansion', () => {
const { DELETE } = cds.ql
let d = DELETE.from('bookshop.Reproduce[author = null and ID = 99]:accessGroup')
const query = cqn4sql(d)
const query = cqn4sql(d, model)

const expected = {
DELETE: {
Expand Down
11 changes: 6 additions & 5 deletions db-service/test/cqn4sql/INSERT.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ const cds = require('@sap/cds/lib')
const { expect } = cds.test

describe('INSERT', () => {
let model
beforeAll(async () => {
cds.model = await cds.load(__dirname + '/../bookshop/db/schema').then(cds.linked)
model = cds.model = await cds.load(__dirname + '/../bookshop/db/schema').then(cds.linked)
})
it('simple', () => {
let i = INSERT.into('bookshop.Books')
const query = cqn4sql(i)
const query = cqn4sql(i, model)
expect(query.INSERT.into).to.deep.equal({ ref: ['bookshop.Books'] })
})
it('path expression in into clause', () => {
let i = INSERT.into('bookshop.Books:author')
const query = cqn4sql(i)
const query = cqn4sql(i, model)
expect(query.INSERT.into).to.deep.equal({ ref: ['bookshop.Authors'] })
})
it('path expression in into clause with alias', () => {
Expand All @@ -24,12 +25,12 @@ describe('INSERT', () => {
into: { ref: ['bookshop.Books', 'author'], as: 'Foo' },
},
}
const result = cqn4sql(i)
const result = cqn4sql(i, model)
expect(result.INSERT.into).to.deep.equal({ ref: ['bookshop.Authors'], as: 'Foo' })
})
it('path expression in into clause with UPSERT', () => {
let upsert = UPSERT.into('bookshop.Books:author')
const result = cqn4sql(upsert)
const result = cqn4sql(upsert, model)
expect(result.UPSERT.into).to.deep.equal({ ref: ['bookshop.Authors'] })
})
})
Loading

0 comments on commit 41cf4a2

Please sign in to comment.