@cap-js/db-service 2.5.0 → 2.5.1

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,13 @@
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
+ ## [2.5.1](https://github.com/cap-js/cds-dbs/compare/db-service-v2.5.0...db-service-v2.5.1) (2025-09-30)
8
+
9
+
10
+ ### Fixed
11
+
12
+ * revert own resolve ([#1366](https://github.com/cap-js/cds-dbs/issues/1366)) ([9037570](https://github.com/cap-js/cds-dbs/commit/9037570c5dda08eb8bc168c0a68045ef9fc85a9f))
13
+
7
14
  ## [2.5.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.4.0...db-service-v2.5.0) (2025-09-30)
8
15
 
9
16
 
package/lib/SQLService.js CHANGED
@@ -2,7 +2,7 @@ const cds = require('@sap/cds'),
2
2
  DEBUG = cds.debug('sql|db')
3
3
  const { Readable, Transform } = require('stream')
4
4
  const { pipeline } = require('stream/promises')
5
- const { getDBTable, getTransition } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
5
+ const { resolveView, getDBTable, getTransition } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
6
6
  const DatabaseService = require('./common/DatabaseService')
7
7
  const cqn4sql = require('./cqn4sql')
8
8
 
@@ -230,9 +230,7 @@ class SQLService extends DatabaseService {
230
230
  // REVISIT: It's not yet 100 % clear under which circumstances we can rely on db constraints
231
231
  return (super.onDELETE = /* cds.env.features.assert_integrity === 'db' ? this.onSIMPLE : */ deep_delete)
232
232
  async function deep_delete(/** @type {Request} */ req) {
233
- const resolve = this.resolve
234
- // REVISIT: remove fallback when cds.dbs requires cds >= 9.3
235
- const transitions = resolve?.transitions4db ? resolve.transitions4db(req.query, false) : getTransition(req.target, this, false, req.query.cmd || 'DELETE')
233
+ const transitions = getTransition(req.target, this, false, req.query.cmd || 'DELETE')
236
234
  if (transitions.target !== transitions.queryTarget) {
237
235
  const keys = []
238
236
  const transitionsTarget = transitions.queryTarget.keys || transitions.queryTarget.elements
@@ -255,8 +253,7 @@ class SQLService extends DatabaseService {
255
253
  })
256
254
  return this.onDELETE({ query, target: transitions.target })
257
255
  }
258
- // REVISIT: remove fallback when cds.dbs requires cds >= 9.3
259
- const table = resolve?.table ? resolve.table(req.target) : getDBTable(req.target)
256
+ const table = getDBTable(req.target)
260
257
  const { compositions } = table
261
258
  if (compositions) {
262
259
  // Transform CQL`DELETE from Foo[p1] WHERE p2` into CQL`DELETE from Foo[p1 and p2]`
@@ -407,6 +404,10 @@ class SQLService extends DatabaseService {
407
404
  */
408
405
  cqn2sql(query, values) {
409
406
  let q = this.cqn4sql(query)
407
+ let kind = q.kind || Object.keys(q)[0]
408
+ if (kind in { INSERT: 1, DELETE: 1, UPSERT: 1, UPDATE: 1 }) {
409
+ q = resolveView(q, this.model, this) // REVISIT: before resolveView was called on flat cqn obtained from cqn4sql -> is it correct to call on original q instead?
410
+ }
410
411
  let cqn2sql = new this.class.CQN2SQL(this)
411
412
  return cqn2sql.render(q, values)
412
413
  }
package/lib/cqn2sql.js CHANGED
@@ -1,7 +1,6 @@
1
1
  const cds = require('@sap/cds')
2
2
  const cds_infer = require('./infer')
3
3
  const cqn4sql = require('./cqn4sql')
4
- const { getDBTable, getTransition } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
5
4
  const _simple_queries = cds.env.features.sql_simple_queries
6
5
  const _strict_booleans = _simple_queries < 2
7
6
 
@@ -27,7 +26,7 @@ class CQN2SQLRenderer {
27
26
  if (cds.env.sql.names === 'quoted') {
28
27
  this.class.prototype.name = (name, query) => {
29
28
  const e = name.id || name
30
- return (this.model?.definitions[e])?.['@cds.persistence.name'] || e || query?._target
29
+ return (query?._target || this.model?.definitions[e])?.['@cds.persistence.name'] || e
31
30
  }
32
31
  this.class.prototype.quote = (s) => `"${String(s).replace(/"/g, '""')}"`
33
32
  }
@@ -91,6 +90,7 @@ class CQN2SQLRenderer {
91
90
  if (vars && Object.keys(vars).length && !this.values?.length) this.values = vars
92
91
  const sanitize_values = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
93
92
 
93
+
94
94
  if (DEBUG && (LOG_SQL._debug || LOG_SQLITE._debug)) {
95
95
  let values = sanitize_values && (this.entries || this.values?.length > 0) ? ['***'] : this.entries || this.values || []
96
96
  if (values && !Array.isArray(values)) {
@@ -722,49 +722,6 @@ class CQN2SQLRenderer {
722
722
  return this.xpr({ xpr })
723
723
  }
724
724
 
725
- /**
726
- * Renders a transformed where clause that maps the query target view to the source table
727
- * @param {import('./infer/cqn').source} from
728
- * @param {import('./infer/cqn').predicate} where
729
- * @param {import('./infer/cqn').query} q
730
- * @returns SQL
731
- */
732
- where_resolved(from, where, q) {
733
- // REVISIT: remove fallback when cds.dbs requires cds >= 9.3
734
- const kind = q.kind || (
735
- q.SELECT ? 'SELECT' :
736
- q.INSERT ? 'INSERT' :
737
- q.UPSERT ? 'UPSERT' :
738
- q.UPDATE ? 'UPDATE' :
739
- q.DELETE ? 'DELETE' :
740
- undefined
741
- )
742
- const transitions = this.srv.resolve?.transitions4db ? this.srv.resolve.transitions4db(q) : getTransition(q._target, this.srv, false, kind)
743
- if (transitions.target === transitions.queryTarget) return this.where(where)
744
-
745
- // view and table column refs to be matched
746
- const viewCols = []
747
- const tableCols = []
748
-
749
- // Only match key columns when possible
750
- const elements = q._target.keys || q._target.elements
751
- for (const c in elements) {
752
- if (
753
- c in elements
754
- && transitions.mapping.has(c)
755
- && !elements[c].virtual
756
- && !elements[c].value
757
- && !elements[c].isAssociation
758
- ) {
759
- viewCols.push({ ref: [c] })
760
- tableCols.push(transitions.mapping.get(c))
761
- }
762
- }
763
- return tableCols.length > 0
764
- ? this.where([{ list: tableCols }, 'in', SELECT.from(from).columns(viewCols).where(where)])
765
- : this.where(where)
766
- }
767
-
768
725
  /**
769
726
  * Renders a HAVING clause into generic SQL
770
727
  * @param {import('./infer/cqn').predicate} xpr
@@ -870,22 +827,15 @@ class CQN2SQLRenderer {
870
827
  if (!elements && !INSERT.entries?.length) {
871
828
  return // REVISIT: mtx sends an insert statement without entries and no reference entity
872
829
  }
873
- // REVISIT: remove fallback when cds.dbs requires cds >= 9.3
874
- const transitions = this.srv.resolve?.transitions4db ? this.srv.resolve.transitions4db(q) : getTransition(q._target, this.srv, false, 'INSERT')
875
830
  const columns = elements
876
- ? ObjectKeys(elements).filter(c => (c = transitions.mapping.get(c)?.ref?.[0] || c)
877
- && c in transitions.target.elements
878
- && !transitions.target.elements[c].virtual
879
- && !transitions.target.elements[c].value
880
- && !transitions.target.elements[c].isAssociation
881
- )
831
+ ? ObjectKeys(elements).filter(c => c in elements && !elements[c].virtual && !elements[c].value && !elements[c].isAssociation)
882
832
  : ObjectKeys(INSERT.entries[0])
883
833
 
884
834
  /** @type {string[]} */
885
835
  this.columns = columns
886
836
 
887
837
  const alias = INSERT.into.as
888
- const entity = q._target ? this.table_name(q) : INSERT.into.ref[0]
838
+ const entity = this.name(q._target?.name || INSERT.into.ref[0], q)
889
839
  if (!elements) {
890
840
  this.entries = INSERT.entries.map(e => columns.map(c => e[c]))
891
841
  const param = this.param.bind(this, { ref: ['?'] })
@@ -907,8 +857,8 @@ class CQN2SQLRenderer {
907
857
  }
908
858
 
909
859
  const extractions = this._managed = this.managed(columns.map(c => ({ name: c })), elements)
910
- return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c => this.quote(transitions.mapping.get(c)?.ref?.[0] || c))
911
- }) SELECT ${extractions.slice(0, columns.length).map(c => c.insert)} FROM json_each(?)`)
860
+ return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c => this.quote(c))
861
+ }) SELECT ${extractions.map(c => c.insert)} FROM json_each(?)`)
912
862
  }
913
863
 
914
864
  async *INSERT_entries_stream(entries, binaryEncoding = 'base64') {
@@ -1014,7 +964,7 @@ class CQN2SQLRenderer {
1014
964
  */
1015
965
  INSERT_rows(q) {
1016
966
  const { INSERT } = q
1017
- const entity = q._target ? this.table_name(q) : INSERT.into.ref[0]
967
+ const entity = this.name(q._target?.name || INSERT.into.ref[0], q)
1018
968
  const alias = INSERT.into.as
1019
969
  const elements = q.elements || q._target?.elements
1020
970
  const columns = this.columns = INSERT.columns || cds.error`Cannot insert rows without columns or elements`
@@ -1038,10 +988,8 @@ class CQN2SQLRenderer {
1038
988
  const extraction = (this._managed = this.managed(columns.map(c => ({ name: c })), elements))
1039
989
  .slice(0, columns.length)
1040
990
  .map(c => c.converter(c.extract))
1041
-
1042
- // REVISIT: remove fallback when cds.dbs requires cds >= 9.3
1043
- const transitions = this.srv.resolve?.transitions4db ? this.srv.resolve.transitions4db(q) : getTransition(q._target, this.srv, false, 'INSERT')
1044
- return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c => this.quote(transitions.mapping.get(c)?.ref?.[0] || c))
991
+
992
+ return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c => this.quote(c))
1045
993
  }) SELECT ${extraction} FROM json_each(?)`)
1046
994
  }
1047
995
 
@@ -1062,26 +1010,15 @@ class CQN2SQLRenderer {
1062
1010
  */
1063
1011
  INSERT_select(q) {
1064
1012
  const { INSERT } = q
1065
- const entity = q._target ? this.table_name(q) : INSERT.into.ref[0]
1013
+ const entity = this.name(q._target.name, q)
1066
1014
  const alias = INSERT.into.as
1067
- const src = this.cqn4sql(INSERT.from)
1068
1015
  const elements = q.elements || q._target?.elements || {}
1069
- // REVISIT: remove fallback when cds.dbs requires cds >= 9.3
1070
- const transitions = this.srv.resolve?.transitions4db ? this.srv.resolve.transitions4db(q, this.srv) : getTransition(q._target, this.srv, false, 'INSERT')
1071
- let columns = (this.columns = (INSERT.columns || src.SELECT.columns?.map(c => this.column_name(c)) || ObjectKeys(src.elements) || ObjectKeys(elements))
1072
- .filter(c => (c = transitions.mapping.get(c)?.ref?.[0] || c)
1073
- && c in transitions.target.elements
1074
- && !transitions.target.elements[c].virtual
1075
- && !transitions.target.elements[c].value
1076
- && !transitions.target.elements[c].isAssociation
1077
- ))
1078
-
1079
- const extractions = this._managed = this.managed(columns.map(c => ({ name: c, sql: `NEW.${this.quote(c)}` })), elements)
1080
- const sql = extractions.length > columns.length
1081
- ? `SELECT ${extractions.map(c => `${c.insert} AS ${this.quote(c.name)}`)} FROM (${this.SELECT(src)}) AS NEW`
1082
- : this.SELECT(src)
1083
- if (extractions.length > columns.length) columns = this.columns = extractions.map(c => c.name)
1084
- this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${columns.map(c => this.quote(transitions.mapping.get(c)?.ref?.[0] || c))}) ${sql}`
1016
+ const columns = (this.columns = (INSERT.columns || ObjectKeys(elements)).filter(
1017
+ c => c in elements && !elements[c].virtual && !elements[c].isAssociation,
1018
+ ))
1019
+ this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${columns.map(c => this.quote(c))}) ${this.SELECT(
1020
+ this.cqn4sql(INSERT.from || INSERT.as),
1021
+ )}`
1085
1022
  this.entries = [this.values]
1086
1023
  return this.sql
1087
1024
  }
@@ -1141,7 +1078,7 @@ class CQN2SQLRenderer {
1141
1078
  .filter(c => keys.includes(c.name))
1142
1079
  .map(c => `${c.onInsert || c.sql} as ${this.quote(c.name)}`)
1143
1080
 
1144
- const entity = q._target ? this.table_name(q) : INSERT.into.ref[0]
1081
+ const entity = this.name(q._target?.name || UPSERT.into.ref[0], q)
1145
1082
  sql = `SELECT ${managed.map(c => c.upsert
1146
1083
  .replace(/value->/g, '"$$$$value$$$$"->')
1147
1084
  .replace(/json_type\(value,/g, 'json_type("$$$$value$$$$",'))
@@ -1157,9 +1094,7 @@ class CQN2SQLRenderer {
1157
1094
  else return true
1158
1095
  }).map(c => `${this.quote(c)} = excluded.${this.quote(c)}`)
1159
1096
 
1160
- // REVISIT: remove fallback when cds.dbs requires cds >= 9.3
1161
- const transitions = this.srv.resolve.transitions4db ? this.srv.resolve.transitions4db(q) : getTransition(q._target, this.srv, false, 'UPDATE')
1162
- return (this.sql = `INSERT INTO ${this.quote(entity)} (${columns.map(c => this.quote(transitions.mapping.get(c)?.ref?.[0] || c))}) ${sql
1097
+ return (this.sql = `INSERT INTO ${this.quote(entity)} (${columns.map(c => this.quote(c))}) ${sql
1163
1098
  } WHERE TRUE ON CONFLICT(${keys.map(c => this.quote(c))}) DO ${updateColumns.length ? `UPDATE SET ${updateColumns}` : 'NOTHING'}`)
1164
1099
  }
1165
1100
 
@@ -1172,37 +1107,29 @@ class CQN2SQLRenderer {
1172
1107
  */
1173
1108
  UPDATE(q) {
1174
1109
  const { entity, with: _with, data, where } = q.UPDATE
1175
- // REVISIT: remove fallback when cds.dbs requires cds >= 9.3
1176
- const transitions = this.srv.resolve?.transitions4db ? this.srv.resolve.transitions4db(q) : getTransition(q._target, this.srv, false, 'UPDATE')
1177
- const elements = q._target?.elements
1178
- let sql = `UPDATE ${this.quote(this.table_name(q))}`
1110
+ const elements = q._target?.elements
1111
+ let sql = `UPDATE ${this.quote(this.name(entity.ref?.[0] || entity, q))}`
1179
1112
  if (entity.as) sql += ` AS ${this.quote(entity.as)}`
1180
1113
 
1181
1114
  let columns = []
1182
1115
  if (data) _add(data, val => this.val({ val }))
1183
1116
  if (_with) _add(_with, x => this.expr(x))
1184
1117
  function _add(data, sql4) {
1185
- for (let col in data) {
1186
- const c = transitions.mapping.get(col)?.ref?.[0] || col
1187
- const columnExistsInDatabase = elements
1188
- && c in transitions.target.elements
1189
- && !transitions.target.elements[c].virtual
1190
- && !transitions.target.elements[c].value
1191
- && !transitions.target.elements[c].isAssociation
1118
+ for (let c in data) {
1119
+ const columnExistsInDatabase =
1120
+ elements && c in elements && !elements[c].virtual && !elements[c].isAssociation && !elements[c].value
1192
1121
  if (!elements || columnExistsInDatabase) {
1193
- columns.push({ name: c, sql: sql4(data[col], col) })
1122
+ columns.push({ name: c, sql: sql4(data[c]) })
1194
1123
  }
1195
1124
  }
1196
1125
  }
1197
1126
 
1198
1127
  const extraction = this.managed(columns, elements)
1199
- .filter((c, i) => {
1200
- if(transitions.mapping.get(c.name)?.ref?.length > 1) return false
1201
- return columns[i] || c.onUpdate
1202
- }).map((c, i) => `${this.quote(transitions.mapping.get(c.name)?.ref?.[0] || c.name)}=${!columns[i] ? c.onUpdate : c.sql}`)
1128
+ .filter((c, i) => columns[i] || c.onUpdate)
1129
+ .map((c, i) => `${this.quote(c.name)}=${!columns[i] ? c.onUpdate : c.sql}`)
1203
1130
 
1204
1131
  sql += ` SET ${extraction}`
1205
- if (where) sql += ` WHERE ${this.where_resolved(entity, where, q)}`
1132
+ if (where) sql += ` WHERE ${this.where(where)}`
1206
1133
  return (this.sql = sql)
1207
1134
  }
1208
1135
 
@@ -1214,9 +1141,8 @@ class CQN2SQLRenderer {
1214
1141
  * @returns {string} SQL
1215
1142
  */
1216
1143
  DELETE(q) {
1217
- const { DELETE: { where, from } } = q
1218
- let sql = `DELETE FROM ${this.quote(this.table_name(q))}`
1219
- if (from.as) sql += ` AS ${this.quote(from.as)}`
1144
+ const { DELETE: { from, where } } = q
1145
+ let sql = `DELETE FROM ${this.from(from, q)}`
1220
1146
  if (where) sql += ` WHERE ${this.where(where)}`
1221
1147
  return (this.sql = sql)
1222
1148
  }
@@ -1429,17 +1355,6 @@ class CQN2SQLRenderer {
1429
1355
  return (typeof col.as === 'string' && col.as) || ('val' in col && col.val + '') || col.func || col.ref.at(-1)
1430
1356
  }
1431
1357
 
1432
- /**
1433
- * Calculates the Database table name of the query target
1434
- * @param {import('./infer/cqn').Query} query
1435
- * @returns {string} Database table name
1436
- */
1437
- table_name(q) {
1438
- // REVISIT: remove fallback when cds.dbs requires cds >= 9.3
1439
- const table = cds.db.resolve?.table ? cds.db.resolve.table(q._target) : getDBTable(q._target)
1440
- return this.name(table.name, q)
1441
- }
1442
-
1443
1358
  /**
1444
1359
  * Calculates the Database name of the given name
1445
1360
  * @param {string|import('./infer/cqn').ref} name
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/db-service",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
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": {