@cap-js/db-service 2.7.0 → 2.8.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,25 @@
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.8.1](https://github.com/cap-js/cds-dbs/compare/db-service-v2.8.0...db-service-v2.8.1) (2025-12-19)
8
+
9
+
10
+ ### Fixed
11
+
12
+ * hierarchy node_id as alias ([#1450](https://github.com/cap-js/cds-dbs/issues/1450)) ([d115345](https://github.com/cap-js/cds-dbs/commit/d115345e5acc30046159ede98d99a864df55e2af))
13
+
14
+ ## [2.8.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.7.0...db-service-v2.8.0) (2025-12-15)
15
+
16
+
17
+ ### Added
18
+
19
+ * Added support to use `UPSERT` from `SELECT` ([#1435](https://github.com/cap-js/cds-dbs/issues/1435)) ([68f3db8](https://github.com/cap-js/cds-dbs/commit/68f3db8d79aa120768fe81324cd164782b9eec1b))
20
+
21
+
22
+ ### Fixed
23
+
24
+ * propagte target to subquery ([#1438](https://github.com/cap-js/cds-dbs/issues/1438)) ([5460e43](https://github.com/cap-js/cds-dbs/commit/5460e4398079750ec3afec9a1747007618d23ecd))
25
+
7
26
  ## [2.7.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.6.0...db-service-v2.7.0) (2025-11-26)
8
27
 
9
28
 
@@ -244,7 +244,11 @@ const HANAFunctions = {
244
244
  let src = args.xpr[1]
245
245
 
246
246
  // Ensure that the orderBy column are exposed by the source for hierarchy sorting
247
- const orderBy = args.xpr.find((_, i, arr) => /ORDER/i.test(arr[i - 2]) && /BY/i.test(arr[i - 1]))
247
+ let orderBy = args.xpr.find((_, i, arr) => /ORDER/i.test(arr[i - 2]) && /BY/i.test(arr[i - 1]))
248
+ // use ![] instead of ""
249
+ if (orderBy && typeof orderBy === 'string') {
250
+ orderBy = orderBy.replace(/"([^"]*)"/g, '![$1]');
251
+ }
248
252
 
249
253
  const passThroughColumns = src.SELECT.columns.map(c => ({ ref: ['Source', this.column_name(c)] }))
250
254
  src.as = 'H' + (uniqueCounter++)
package/lib/cqn2sql.js CHANGED
@@ -380,7 +380,8 @@ class CQN2SQLRenderer {
380
380
 
381
381
  if (orderBy) {
382
382
  orderBy = orderBy.map(r => {
383
- const col = r.ref.at(-1)
383
+ let col = r.ref.at(-1)
384
+ if (col.toUpperCase() in reservedColumnNames) col = `$$${col}$$`
384
385
  if (!columnsIn.find(c => this.column_name(c) === col)) {
385
386
  columnsIn.push({ ref: [col] })
386
387
  }
@@ -393,11 +394,11 @@ class CQN2SQLRenderer {
393
394
  const alias = stableFrom.as
394
395
  const source = () => {
395
396
  return ({
396
- func: 'HIERARCHY',
397
- args: [{ xpr: ['SOURCE', { SELECT: { columns: columnsIn, from: stableFrom } }, ...(orderBy ? ['SIBLING', 'ORDER', 'BY', `${this.orderBy(orderBy)}`] : [])] }],
398
- as: alias
399
- })
400
- }
397
+ func: 'HIERARCHY',
398
+ args: [{ xpr: ['SOURCE', { SELECT: { columns: columnsIn, from: stableFrom } }, ...(orderBy ? ['SIBLING', 'ORDER', 'BY', `${this.orderBy(orderBy)}`] : [])] }],
399
+ as: alias
400
+ })
401
+ }
401
402
 
402
403
  const expandedByNr = { list: [] } // DistanceTo(...,null)
403
404
  const expandedByOne = { list: [] } // DistanceTo(...,1)
@@ -1019,12 +1020,17 @@ class CQN2SQLRenderer {
1019
1020
  const entity = this.name(q._target.name, q)
1020
1021
  const alias = INSERT.into.as
1021
1022
  const elements = q.elements || q._target?.elements || {}
1022
- const columns = (this.columns = (INSERT.columns || ObjectKeys(elements)).filter(
1023
+ let columns = (this.columns = (INSERT.columns || ObjectKeys(elements)).filter(
1023
1024
  c => c in elements && !elements[c].virtual && !elements[c].isAssociation,
1024
1025
  ))
1025
- this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${columns.map(c => this.quote(c))}) ${this.SELECT(
1026
- this.cqn4sql(INSERT.from || INSERT.as),
1027
- )}`
1026
+
1027
+ const src = this.cqn4sql(INSERT.from)
1028
+ const extractions = this._managed = this.managed(columns.map(c => ({ name: c, sql: `NEW.${this.quote(c)}` })), elements)
1029
+ const sql = extractions.length > columns.length
1030
+ ? `SELECT ${extractions.map(c => `${c.insert} AS ${this.quote(c.name)}`)} FROM (${this.SELECT(src)}) AS NEW`
1031
+ : this.SELECT(src)
1032
+ if (extractions.length > columns.length) columns = this.columns = extractions.map(c => c.name)
1033
+ this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${columns.map(c => this.quote(c))}) ${sql}`
1028
1034
  this.entries = [this.values]
1029
1035
  return this.sql
1030
1036
  }
@@ -1077,18 +1083,32 @@ class CQN2SQLRenderer {
1077
1083
  .map(k => `NEW.${this.quote(k)}=OLD.${this.quote(k)}`)
1078
1084
  .join(' AND ')
1079
1085
 
1080
- const columns = this.columns // this.columns is computed as part of this.INSERT
1081
- const managed = this._managed.slice(0, columns.length)
1086
+ let columns = this.columns // this.columns is computed as part of this.INSERT
1087
+ const entity = this.name(q._target?.name || UPSERT.into.ref[0], q)
1088
+ if (UPSERT.entries || UPSERT.rows || UPSERT.values) {
1089
+ const managed = this._managed.slice(0, columns.length)
1082
1090
 
1083
- const extractkeys = managed
1084
- .filter(c => keys.includes(c.name))
1085
- .map(c => `${c.onInsert || c.sql} as ${this.quote(c.name)}`)
1091
+ const extractkeys = managed
1092
+ .filter(c => keys.includes(c.name))
1093
+ .map(c => `${c.onInsert || c.sql} as ${this.quote(c.name)}`)
1086
1094
 
1087
- const entity = this.name(q._target?.name || UPSERT.into.ref[0], q)
1088
- sql = `SELECT ${managed.map(c => c.upsert
1089
- .replace(/value->/g, '"$$$$value$$$$"->')
1090
- .replace(/json_type\(value,/g, 'json_type("$$$$value$$$$",'))
1091
- } FROM (SELECT value as "$$value$$", ${extractkeys} from json_each(?)) as NEW LEFT JOIN ${this.quote(entity)} AS OLD ON ${keyCompare}`
1095
+ sql = `SELECT ${managed.map(c => c.upsert
1096
+ .replace(/value->/g, '"$$$$value$$$$"->')
1097
+ .replace(/json_type\(value,/g, 'json_type("$$$$value$$$$",'))
1098
+ } FROM (SELECT value as "$$value$$", ${extractkeys} from json_each(?)) as NEW LEFT JOIN ${this.quote(entity)} AS OLD ON ${keyCompare}`
1099
+ } else {
1100
+ const extractions = this._managed
1101
+ if (this.values) this.values = [] // Clear previously computed values
1102
+ const src = this.cqn4sql(UPSERT.from || UPSERT.as)
1103
+ const aliasedQuery = cds.ql.SELECT
1104
+ .columns(src.SELECT.columns
1105
+ .map((c, i) => ({ ref: [this.column_name(c)], as: this.columns[i] }))
1106
+ )
1107
+ .from(src)
1108
+ sql = `SELECT ${extractions.map(c => `${c.upsert}`)} FROM (${this.SELECT(aliasedQuery)}) AS NEW LEFT JOIN ${this.quote(entity)} AS OLD ON ${keyCompare}`
1109
+ if (extractions.length > columns.length) columns = this.columns = extractions.map(c => c.name)
1110
+ this.entries = [this.values]
1111
+ }
1092
1112
 
1093
1113
  const updateColumns = columns.filter(c => {
1094
1114
  if (keys.includes(c)) return false //> keys go into ON CONFLICT clause
@@ -1326,7 +1346,7 @@ class CQN2SQLRenderer {
1326
1346
  } else {
1327
1347
  cds.error`Invalid arguments provided for function '${func}' (${args})`
1328
1348
  }
1329
- const fn = this.class.Functions[func]?.apply(this, Array.isArray(args) ? args: [args]) || `${func}(${args})`
1349
+ const fn = this.class.Functions[func]?.apply(this, Array.isArray(args) ? args : [args]) || `${func}(${args})`
1330
1350
  if (xpr) return `${fn} ${this.xpr({ xpr })}`
1331
1351
  return fn
1332
1352
  }
@@ -1430,7 +1450,7 @@ class CQN2SQLRenderer {
1430
1450
 
1431
1451
  let onInsert = this.managed_session_context(element[cdsOnInsert]?.['='])
1432
1452
  || this.managed_session_context(element.default?.ref?.[0])
1433
- || (element.default && { __proto__: element.default, param: false })
1453
+ || (element.default && { __proto__: element.default, param: false })
1434
1454
  let onUpdate = this.managed_session_context(element[cdsOnUpdate]?.['='])
1435
1455
 
1436
1456
  if (onInsert) onInsert = this.expr(onInsert)
package/lib/cqn4sql.js CHANGED
@@ -281,7 +281,11 @@ function cqn4sql(originalQuery, model) {
281
281
  const alreadySeen = new Map()
282
282
  inferred.joinTree._roots.forEach(r => {
283
283
  const args = []
284
- if (r.queryArtifact.SELECT) args.push({ SELECT: transformSubquery(r.queryArtifact).SELECT, as: r.alias })
284
+ if (r.queryArtifact.SELECT) {
285
+ const transformedSubquery = transformSubquery(r.queryArtifact)
286
+ transformedSubquery.as = r.alias
287
+ args.push(transformedSubquery)
288
+ }
285
289
  else {
286
290
  const id = getLocalizedName(r.queryArtifact)
287
291
  args.push({ ref: [r.args ? { id, args: r.args } : id], as: r.alias })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/db-service",
3
- "version": "2.7.0",
3
+ "version": "2.8.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": {