@cap-js/db-service 2.7.0 → 2.8.0
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 +12 -0
- package/lib/cqn2sql.js +40 -21
- package/lib/cqn4sql.js +5 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
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.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.7.0...db-service-v2.8.0) (2025-12-15)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
* 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))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
* propagte target to subquery ([#1438](https://github.com/cap-js/cds-dbs/issues/1438)) ([5460e43](https://github.com/cap-js/cds-dbs/commit/5460e4398079750ec3afec9a1747007618d23ecd))
|
|
18
|
+
|
|
7
19
|
## [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
20
|
|
|
9
21
|
|
package/lib/cqn2sql.js
CHANGED
|
@@ -393,11 +393,11 @@ class CQN2SQLRenderer {
|
|
|
393
393
|
const alias = stableFrom.as
|
|
394
394
|
const source = () => {
|
|
395
395
|
return ({
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
396
|
+
func: 'HIERARCHY',
|
|
397
|
+
args: [{ xpr: ['SOURCE', { SELECT: { columns: columnsIn, from: stableFrom } }, ...(orderBy ? ['SIBLING', 'ORDER', 'BY', `${this.orderBy(orderBy)}`] : [])] }],
|
|
398
|
+
as: alias
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
401
|
|
|
402
402
|
const expandedByNr = { list: [] } // DistanceTo(...,null)
|
|
403
403
|
const expandedByOne = { list: [] } // DistanceTo(...,1)
|
|
@@ -1019,12 +1019,17 @@ class CQN2SQLRenderer {
|
|
|
1019
1019
|
const entity = this.name(q._target.name, q)
|
|
1020
1020
|
const alias = INSERT.into.as
|
|
1021
1021
|
const elements = q.elements || q._target?.elements || {}
|
|
1022
|
-
|
|
1022
|
+
let columns = (this.columns = (INSERT.columns || ObjectKeys(elements)).filter(
|
|
1023
1023
|
c => c in elements && !elements[c].virtual && !elements[c].isAssociation,
|
|
1024
1024
|
))
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
)}`
|
|
1025
|
+
|
|
1026
|
+
const src = this.cqn4sql(INSERT.from)
|
|
1027
|
+
const extractions = this._managed = this.managed(columns.map(c => ({ name: c, sql: `NEW.${this.quote(c)}` })), elements)
|
|
1028
|
+
const sql = extractions.length > columns.length
|
|
1029
|
+
? `SELECT ${extractions.map(c => `${c.insert} AS ${this.quote(c.name)}`)} FROM (${this.SELECT(src)}) AS NEW`
|
|
1030
|
+
: this.SELECT(src)
|
|
1031
|
+
if (extractions.length > columns.length) columns = this.columns = extractions.map(c => c.name)
|
|
1032
|
+
this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${columns.map(c => this.quote(c))}) ${sql}`
|
|
1028
1033
|
this.entries = [this.values]
|
|
1029
1034
|
return this.sql
|
|
1030
1035
|
}
|
|
@@ -1077,18 +1082,32 @@ class CQN2SQLRenderer {
|
|
|
1077
1082
|
.map(k => `NEW.${this.quote(k)}=OLD.${this.quote(k)}`)
|
|
1078
1083
|
.join(' AND ')
|
|
1079
1084
|
|
|
1080
|
-
|
|
1081
|
-
const
|
|
1085
|
+
let columns = this.columns // this.columns is computed as part of this.INSERT
|
|
1086
|
+
const entity = this.name(q._target?.name || UPSERT.into.ref[0], q)
|
|
1087
|
+
if (UPSERT.entries || UPSERT.rows || UPSERT.values) {
|
|
1088
|
+
const managed = this._managed.slice(0, columns.length)
|
|
1082
1089
|
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1090
|
+
const extractkeys = managed
|
|
1091
|
+
.filter(c => keys.includes(c.name))
|
|
1092
|
+
.map(c => `${c.onInsert || c.sql} as ${this.quote(c.name)}`)
|
|
1086
1093
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1094
|
+
sql = `SELECT ${managed.map(c => c.upsert
|
|
1095
|
+
.replace(/value->/g, '"$$$$value$$$$"->')
|
|
1096
|
+
.replace(/json_type\(value,/g, 'json_type("$$$$value$$$$",'))
|
|
1097
|
+
} FROM (SELECT value as "$$value$$", ${extractkeys} from json_each(?)) as NEW LEFT JOIN ${this.quote(entity)} AS OLD ON ${keyCompare}`
|
|
1098
|
+
} else {
|
|
1099
|
+
const extractions = this._managed
|
|
1100
|
+
if (this.values) this.values = [] // Clear previously computed values
|
|
1101
|
+
const src = this.cqn4sql(UPSERT.from || UPSERT.as)
|
|
1102
|
+
const aliasedQuery = cds.ql.SELECT
|
|
1103
|
+
.columns(src.SELECT.columns
|
|
1104
|
+
.map((c, i) => ({ ref: [this.column_name(c)], as: this.columns[i] }))
|
|
1105
|
+
)
|
|
1106
|
+
.from(src)
|
|
1107
|
+
sql = `SELECT ${extractions.map(c => `${c.upsert}`)} FROM (${this.SELECT(aliasedQuery)}) AS NEW LEFT JOIN ${this.quote(entity)} AS OLD ON ${keyCompare}`
|
|
1108
|
+
if (extractions.length > columns.length) columns = this.columns = extractions.map(c => c.name)
|
|
1109
|
+
this.entries = [this.values]
|
|
1110
|
+
}
|
|
1092
1111
|
|
|
1093
1112
|
const updateColumns = columns.filter(c => {
|
|
1094
1113
|
if (keys.includes(c)) return false //> keys go into ON CONFLICT clause
|
|
@@ -1326,7 +1345,7 @@ class CQN2SQLRenderer {
|
|
|
1326
1345
|
} else {
|
|
1327
1346
|
cds.error`Invalid arguments provided for function '${func}' (${args})`
|
|
1328
1347
|
}
|
|
1329
|
-
const fn = this.class.Functions[func]?.apply(this, Array.isArray(args) ? args: [args]) || `${func}(${args})`
|
|
1348
|
+
const fn = this.class.Functions[func]?.apply(this, Array.isArray(args) ? args : [args]) || `${func}(${args})`
|
|
1330
1349
|
if (xpr) return `${fn} ${this.xpr({ xpr })}`
|
|
1331
1350
|
return fn
|
|
1332
1351
|
}
|
|
@@ -1430,7 +1449,7 @@ class CQN2SQLRenderer {
|
|
|
1430
1449
|
|
|
1431
1450
|
let onInsert = this.managed_session_context(element[cdsOnInsert]?.['='])
|
|
1432
1451
|
|| this.managed_session_context(element.default?.ref?.[0])
|
|
1433
|
-
|| (element.default && { __proto__:
|
|
1452
|
+
|| (element.default && { __proto__: element.default, param: false })
|
|
1434
1453
|
let onUpdate = this.managed_session_context(element[cdsOnUpdate]?.['='])
|
|
1435
1454
|
|
|
1436
1455
|
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