@cap-js/db-service 1.6.2 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/lib/cql-functions.js +1 -1
- package/lib/cqn2sql.js +10 -5
- package/lib/cqn4sql.js +18 -12
- package/lib/infer/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
- The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
5
5
|
- This project adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [1.6.3](https://github.com/cap-js/cds-dbs/compare/db-service-v1.6.2...db-service-v1.6.3) (2024-02-20)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* **`cqn4sql`:** be robust against `$self.<element>;` references ([#471](https://github.com/cap-js/cds-dbs/issues/471)) ([2921b0e](https://github.com/cap-js/cds-dbs/commit/2921b0e8ada33b172a001d89904893268e751efd))
|
|
13
|
+
* **`infer`:** Always use srv.model ([#451](https://github.com/cap-js/cds-dbs/issues/451)) ([41cf4a2](https://github.com/cap-js/cds-dbs/commit/41cf4a24cf2f5e2411be0dc647af6eb628a6d312))
|
|
14
|
+
* Throw 'new Error' instead of string on $search with multiple words ([#472](https://github.com/cap-js/cds-dbs/issues/472)) ([51be94d](https://github.com/cap-js/cds-dbs/commit/51be94d2333b4a4007f354c805d1b974b19d6d2d))
|
|
15
|
+
|
|
7
16
|
## [1.6.2](https://github.com/cap-js/cds-dbs/compare/db-service-v1.6.1...db-service-v1.6.2) (2024-02-16)
|
|
8
17
|
|
|
9
18
|
|
package/lib/cql-functions.js
CHANGED
|
@@ -21,7 +21,7 @@ const StandardFunctions = {
|
|
|
21
21
|
* @returns {string}
|
|
22
22
|
*/
|
|
23
23
|
search: function (ref, arg) {
|
|
24
|
-
if (!('val' in arg)) throw `Only single value arguments are allowed for $search`
|
|
24
|
+
if (!('val' in arg)) throw new Error(`Only single value arguments are allowed for $search`)
|
|
25
25
|
const refs = ref.list || [ref],
|
|
26
26
|
{ toString } = ref
|
|
27
27
|
return '(' + refs.map(ref2 => this.contains(this.tolower(toString(ref2)), this.tolower(arg))).join(' or ') + ')'
|
package/lib/cqn2sql.js
CHANGED
|
@@ -31,6 +31,7 @@ class CQN2SQLRenderer {
|
|
|
31
31
|
this.context = srv?.context || cds.context // Using srv.context is required due to stakeholders doing unmanaged txs without cds.context being set
|
|
32
32
|
this.class = new.target // for IntelliSense
|
|
33
33
|
this.class._init() // is a noop for subsequent calls
|
|
34
|
+
this.model = srv?.model
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
static _add_mixins(aspect, mixins) {
|
|
@@ -94,6 +95,10 @@ class CQN2SQLRenderer {
|
|
|
94
95
|
return q.target ? q : cds_infer(q)
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
cqn4sql(q) {
|
|
99
|
+
return cqn4sql(q, this.model)
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
// CREATE Statements ------------------------------------------------
|
|
98
103
|
|
|
99
104
|
/**
|
|
@@ -109,7 +114,7 @@ class CQN2SQLRenderer {
|
|
|
109
114
|
this.sql =
|
|
110
115
|
!query || target['@cds.persistence.table']
|
|
111
116
|
? `CREATE TABLE ${name} ( ${this.CREATE_elements(target.elements)} )`
|
|
112
|
-
: `CREATE VIEW ${name} AS ${this.SELECT(cqn4sql(query))}`
|
|
117
|
+
: `CREATE VIEW ${name} AS ${this.SELECT(this.cqn4sql(query))}`
|
|
113
118
|
this.values = []
|
|
114
119
|
return
|
|
115
120
|
}
|
|
@@ -613,7 +618,7 @@ class CQN2SQLRenderer {
|
|
|
613
618
|
c => c in elements && !elements[c].virtual && !elements[c].isAssociation,
|
|
614
619
|
))
|
|
615
620
|
this.sql = `INSERT INTO ${entity}${alias ? ' as ' + this.quote(alias) : ''} (${columns}) ${this.SELECT(
|
|
616
|
-
cqn4sql(INSERT.as),
|
|
621
|
+
this.cqn4sql(INSERT.as),
|
|
617
622
|
)}`
|
|
618
623
|
this.entries = [this.values]
|
|
619
624
|
return this.sql
|
|
@@ -819,8 +824,8 @@ class CQN2SQLRenderer {
|
|
|
819
824
|
*/
|
|
820
825
|
ref({ ref }) {
|
|
821
826
|
switch (ref[0]) {
|
|
822
|
-
case '$now':
|
|
823
|
-
case '$user': return this.func({ func: 'session_context', args: [{ val: '$user.'+ref[1]||'id', param: false }] }) // REVISIT: same here?
|
|
827
|
+
case '$now': return this.func({ func: 'session_context', args: [{ val: '$now', param: false }] }) // REVISIT: why do we need param: false here?
|
|
828
|
+
case '$user': return this.func({ func: 'session_context', args: [{ val: '$user.' + ref[1] || 'id', param: false }] }) // REVISIT: same here?
|
|
824
829
|
default: return ref.map(r => this.quote(r)).join('.')
|
|
825
830
|
}
|
|
826
831
|
}
|
|
@@ -988,6 +993,6 @@ const _empty = a => !a || a.length === 0
|
|
|
988
993
|
* @param {import('@sap/cds/apis/cqn').Query} q
|
|
989
994
|
* @param {import('@sap/cds/apis/csn').CSN} m
|
|
990
995
|
*/
|
|
991
|
-
module.exports = (q, m) => new CQN2SQLRenderer().render(cqn4sql(q, m)
|
|
996
|
+
module.exports = (q, m) => new CQN2SQLRenderer({ model: m }).render(cqn4sql(q, m))
|
|
992
997
|
module.exports.class = CQN2SQLRenderer
|
|
993
998
|
module.exports.classDefinition = CQN2SQLRenderer // class is a reserved typescript word
|
package/lib/cqn4sql.js
CHANGED
|
@@ -43,7 +43,7 @@ const { pseudos } = require('./infer/pseudos')
|
|
|
43
43
|
* @param {object} model
|
|
44
44
|
* @returns {object} transformedQuery the transformed query
|
|
45
45
|
*/
|
|
46
|
-
function cqn4sql(originalQuery, model
|
|
46
|
+
function cqn4sql(originalQuery, model) {
|
|
47
47
|
const inferred = infer(originalQuery, model)
|
|
48
48
|
if (originalQuery.SELECT?.from.args && !originalQuery.joinTree) return inferred
|
|
49
49
|
|
|
@@ -122,7 +122,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
122
122
|
primaryKey.list.push({ ref: [transformedFrom.as, k.name] })
|
|
123
123
|
})
|
|
124
124
|
|
|
125
|
-
const transformedSubquery = cqn4sql(subquery)
|
|
125
|
+
const transformedSubquery = cqn4sql(subquery, model)
|
|
126
126
|
|
|
127
127
|
// replace where condition of original query with the transformed subquery
|
|
128
128
|
// correlate UPDATE / DELETE query with subquery by primary key matches
|
|
@@ -918,7 +918,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
918
918
|
if (q.SELECT.from.uniqueSubqueryAlias) return
|
|
919
919
|
const last = q.SELECT.from.ref.at(-1)
|
|
920
920
|
const uniqueSubqueryAlias = inferred.joinTree.addNextAvailableTableAlias(
|
|
921
|
-
getLastStringSegment(last.id||last),
|
|
921
|
+
getLastStringSegment(last.id || last),
|
|
922
922
|
originalQuery.outerQueries,
|
|
923
923
|
)
|
|
924
924
|
Object.defineProperty(q.SELECT.from, 'uniqueSubqueryAlias', { value: uniqueSubqueryAlias })
|
|
@@ -976,8 +976,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
976
976
|
*/
|
|
977
977
|
function isManagedAssocInFlatMode(e) {
|
|
978
978
|
return (
|
|
979
|
-
e.isAssociation && e.keys
|
|
980
|
-
&& (model.meta.transformation === 'odata' || model.meta.unfolded?.includes('structs'))
|
|
979
|
+
e.isAssociation && e.keys && (model.meta.transformation === 'odata' || model.meta.unfolded?.includes('structs'))
|
|
981
980
|
)
|
|
982
981
|
}
|
|
983
982
|
}
|
|
@@ -1038,7 +1037,8 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1038
1037
|
let leafAssoc
|
|
1039
1038
|
let element = $refLinks ? $refLinks[$refLinks.length - 1].definition : column
|
|
1040
1039
|
if (isWildcard && element.type === 'cds.LargeBinary') return []
|
|
1041
|
-
if (element.on && !element.keys)
|
|
1040
|
+
if (element.on && !element.keys)
|
|
1041
|
+
return [] // unmanaged doesn't make it into columns
|
|
1042
1042
|
else if (element.virtual === true) return []
|
|
1043
1043
|
else if (!isJoinRelevant && flatName) baseName = flatName
|
|
1044
1044
|
else if (isJoinRelevant) {
|
|
@@ -1276,7 +1276,8 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1276
1276
|
}
|
|
1277
1277
|
} else {
|
|
1278
1278
|
const { list } = token
|
|
1279
|
-
if (list.every(e => e.val))
|
|
1279
|
+
if (list.every(e => e.val))
|
|
1280
|
+
// no need for transformation
|
|
1280
1281
|
transformedTokenStream.push({ list })
|
|
1281
1282
|
else transformedTokenStream.push({ list: getTransformedTokenStream(list, $baseLink) })
|
|
1282
1283
|
}
|
|
@@ -1774,13 +1775,13 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1774
1775
|
// naive assumption: if first step is the association itself, all following ref steps must be resolvable
|
|
1775
1776
|
// within target `assoc.assoc.fk` -> `assoc.assoc_fk`
|
|
1776
1777
|
else if (
|
|
1777
|
-
lhs.$refLinks[0]
|
|
1778
|
+
lhs.$refLinks[0]?.definition ===
|
|
1778
1779
|
getParentEntity(assocRefLink.definition).elements[assocRefLink.definition.name]
|
|
1779
1780
|
)
|
|
1780
1781
|
result[i].ref = [assocRefLink.alias, lhs.ref.slice(1).join('_')]
|
|
1781
1782
|
// naive assumption: if the path starts with an association which is not the association from
|
|
1782
1783
|
// which the on-condition originates, it must be a foreign key and hence resolvable in the source
|
|
1783
|
-
else if (lhs.$refLinks[0]
|
|
1784
|
+
else if (lhs.$refLinks[0]?.definition.target) result[i].ref = [result[i].ref.join('_')]
|
|
1784
1785
|
}
|
|
1785
1786
|
}
|
|
1786
1787
|
if (backlink) {
|
|
@@ -1813,8 +1814,12 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1813
1814
|
result.splice(i, 3, ...(wrapInXpr ? [asXpr(backlinkOnCondition)] : backlinkOnCondition))
|
|
1814
1815
|
i += wrapInXpr ? 1 : backlinkOnCondition.length // skip inserted tokens
|
|
1815
1816
|
} else if (lhs.ref) {
|
|
1816
|
-
if (lhs.ref[0] === '$self')
|
|
1817
|
-
|
|
1817
|
+
if (lhs.ref[0] === '$self') { // $self in ref of length > 1
|
|
1818
|
+
// if $self is followed by association, the alias of the association must be used
|
|
1819
|
+
if (lhs.$refLinks[1].definition.isAssociation) result[i].ref.splice(0, 1)
|
|
1820
|
+
// otherwise $self is replaced by the alias of the entity
|
|
1821
|
+
else result[i].ref.splice(0, 1, targetSideRefLink.alias)
|
|
1822
|
+
} else if (lhs.ref.length > 1) {
|
|
1818
1823
|
if (
|
|
1819
1824
|
!(lhs.ref[0] in pseudos.elements) &&
|
|
1820
1825
|
lhs.ref[0] !== assocRefLink.alias &&
|
|
@@ -1826,7 +1831,8 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1826
1831
|
// first step is the association itself -> use it's name as it becomes the table alias
|
|
1827
1832
|
result[i].ref.splice(0, 1, assocRefLink.alias)
|
|
1828
1833
|
} else if (
|
|
1829
|
-
definition.name in
|
|
1834
|
+
definition.name in
|
|
1835
|
+
(targetSideRefLink.definition.elements || targetSideRefLink.definition._target.elements)
|
|
1830
1836
|
) {
|
|
1831
1837
|
// first step is association which refers to its foreign key by dot notation
|
|
1832
1838
|
result[i].ref = [targetSideRefLink.alias, lhs.ref.join('_')]
|
package/lib/infer/index.js
CHANGED
|
@@ -22,7 +22,7 @@ for (const each in cdsTypes) cdsTypes[`cds.${each}`] = cdsTypes[each]
|
|
|
22
22
|
* @param {import('@sap/cds/apis/csn').CSN} [model]
|
|
23
23
|
* @returns {import('./cqn').Query} = q with .target and .elements
|
|
24
24
|
*/
|
|
25
|
-
function infer(originalQuery, model
|
|
25
|
+
function infer(originalQuery, model) {
|
|
26
26
|
if (!model) throw new Error('Please specify a model')
|
|
27
27
|
const inferred = typeof originalQuery === 'string' ? cds.parse.cql(originalQuery) : cds.ql.clone(originalQuery)
|
|
28
28
|
|
package/package.json
CHANGED