@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 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
 
@@ -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': return this.func({ func: 'session_context', args: [{ val: '$now', param: false }] }) // REVISIT: why do we need param: false here?
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), 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 = cds.context?.model || cds.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) return [] // unmanaged doesn't make it into columns
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)) // no need for transformation
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].definition ===
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].definition.target) result[i].ref = [result[i].ref.join('_')]
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') result[i].ref.splice(0, 1, targetSideRefLink.alias)
1817
- else if (lhs.ref.length > 1) {
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 (targetSideRefLink.definition.elements || targetSideRefLink.definition._target.elements)
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('_')]
@@ -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 = cds.context?.model || cds.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/db-service",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "CDS base database service",
5
5
  "homepage": "https://github.com/cap-js/cds-dbs/tree/main/db-service#cds-base-database-service",
6
6
  "repository": {