@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 +19 -0
- package/lib/cql-functions.js +5 -1
- package/lib/cqn2sql.js +42 -22
- package/lib/cqn4sql.js +5 -1
- package/package.json +1 -1
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
|
|
package/lib/cql-functions.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1026
|
-
|
|
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
|
-
|
|
1081
|
-
const
|
|
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
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
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
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
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__:
|
|
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)
|
|
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