@cap-js/db-service 1.11.0 → 1.12.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 +26 -0
- package/README.md +3 -3
- package/lib/SQLService.js +2 -16
- package/lib/common/generic-pool.js +1 -1
- package/lib/cql-functions.js +12 -5
- package/lib/cqn2sql.js +13 -9
- package/lib/cqn4sql.js +25 -21
- package/lib/deep-queries.js +62 -51
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@
|
|
|
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.12.1](https://github.com/cap-js/cds-dbs/compare/db-service-v1.12.0...db-service-v1.12.1) (2024-09-03)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* deep `groupby` expand queries ([#768](https://github.com/cap-js/cds-dbs/issues/768)) ([5423cf3](https://github.com/cap-js/cds-dbs/commit/5423cf38574962c09b94febab95f2e3dc118d2c9))
|
|
13
|
+
* **deep:** prevent false unique constraint errors and combine delete queries ([#781](https://github.com/cap-js/cds-dbs/issues/781)) ([01de95f](https://github.com/cap-js/cds-dbs/commit/01de95f5050a1d3325459ccb78a4e9a1e0dbcfde))
|
|
14
|
+
* **logging:** from changes in @sap/cds ([#791](https://github.com/cap-js/cds-dbs/issues/791)) ([1e8bf06](https://github.com/cap-js/cds-dbs/commit/1e8bf06c9ae92ba55d13fe9e3297d6a54c4fc8fe))
|
|
15
|
+
* prepend aliases to refs within function args in on conditions ([#795](https://github.com/cap-js/cds-dbs/issues/795)) ([9b34314](https://github.com/cap-js/cds-dbs/commit/9b34314d1ef8c6fd7e77451fe9bf0abdc12c27ea)), closes [#779](https://github.com/cap-js/cds-dbs/issues/779)
|
|
16
|
+
* prevent $search queries from throwing ([#772](https://github.com/cap-js/cds-dbs/issues/772)) ([cdf4d37](https://github.com/cap-js/cds-dbs/commit/cdf4d37590c2949cdfd6c6533370bc96cd8fd0fc))
|
|
17
|
+
|
|
18
|
+
## [1.12.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.11.0...db-service-v1.12.0) (2024-07-25)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
* add placeholder for string values ([#733](https://github.com/cap-js/cds-dbs/issues/733)) ([8136a45](https://github.com/cap-js/cds-dbs/commit/8136a4526f596b67932908b8ab1336cb052100f3))
|
|
24
|
+
* for aggregated `expand` always set explicit alias ([#739](https://github.com/cap-js/cds-dbs/issues/739)) ([53a8075](https://github.com/cap-js/cds-dbs/commit/53a8075a609666a896296401a28b6183ff5aa487)), closes [#708](https://github.com/cap-js/cds-dbs/issues/708)
|
|
25
|
+
* quotations in vals ([#754](https://github.com/cap-js/cds-dbs/issues/754)) ([94d8e97](https://github.com/cap-js/cds-dbs/commit/94d8e977ed00776ff494287ce505d6b7e8017d2e))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
* generic-pool as real dep ([#750](https://github.com/cap-js/cds-dbs/issues/750)) ([b50c907](https://github.com/cap-js/cds-dbs/commit/b50c907880455a41a73826a736bc17ca17e5b9ae))
|
|
31
|
+
|
|
32
|
+
|
|
7
33
|
## [1.11.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.10.3...db-service-v1.11.0) (2024-07-08)
|
|
8
34
|
|
|
9
35
|
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# CDS base database service
|
|
2
2
|
|
|
3
|
-
Welcome to the
|
|
3
|
+
Welcome to the base database service for [SAP Cloud Application Programming Model](https://cap.cloud.sap) Node.js. This service forms the core of all supported databases and is the base of our streamlined database architecture.
|
|
4
4
|
|
|
5
|
-
Find documentation at https://cap.cloud.sap/docs/guides/databases
|
|
5
|
+
Find documentation at <https://cap.cloud.sap/docs/guides/databases>
|
|
6
6
|
|
|
7
7
|
## Support
|
|
8
8
|
|
|
@@ -23,4 +23,4 @@ We as members, contributors, and leaders pledge to make participation in our com
|
|
|
23
23
|
|
|
24
24
|
## Licensing
|
|
25
25
|
|
|
26
|
-
Copyright
|
|
26
|
+
Copyright 2024 SAP SE or an SAP affiliate company and cds-dbs contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/cap-js/cds-dbs).
|
package/lib/SQLService.js
CHANGED
|
@@ -371,10 +371,8 @@ class SQLService extends DatabaseService {
|
|
|
371
371
|
!q.SELECT?.from?.join &&
|
|
372
372
|
!q.SELECT?.from?.SELECT &&
|
|
373
373
|
!this.model?.definitions[_target_name4(q)]
|
|
374
|
-
)
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
return cqn4sql(q, this.model)
|
|
374
|
+
) return q
|
|
375
|
+
else return cqn4sql(q, this.model)
|
|
378
376
|
}
|
|
379
377
|
|
|
380
378
|
/**
|
|
@@ -464,18 +462,6 @@ const _target_name4 = q => {
|
|
|
464
462
|
return first.id || first
|
|
465
463
|
}
|
|
466
464
|
|
|
467
|
-
const _unquirked = !cds.env.ql.quirks_mode ? q => q : q => {
|
|
468
|
-
if (!q) return q
|
|
469
|
-
else if (typeof q.SELECT?.from === 'string') q.SELECT.from = { ref: [q.SELECT.from] }
|
|
470
|
-
else if (typeof q.INSERT?.into === 'string') q.INSERT.into = { ref: [q.INSERT.into] }
|
|
471
|
-
else if (typeof q.UPSERT?.into === 'string') q.UPSERT.into = { ref: [q.UPSERT.into] }
|
|
472
|
-
else if (typeof q.UPDATE?.entity === 'string') q.UPDATE.entity = { ref: [q.UPDATE.entity] }
|
|
473
|
-
else if (typeof q.DELETE?.from === 'string') q.DELETE.from = { ref: [q.DELETE.from] }
|
|
474
|
-
else if (typeof q.CREATE?.entity === 'string') q.CREATE.entity = { ref: [q.CREATE.entity] }
|
|
475
|
-
else if (typeof q.DROP?.entity === 'string') q.DROP.entity = { ref: [q.DROP.entity] }
|
|
476
|
-
return q
|
|
477
|
-
}
|
|
478
|
-
|
|
479
465
|
const sqls = new (class extends SQLService {
|
|
480
466
|
get factory() {
|
|
481
467
|
return null
|
package/lib/cql-functions.js
CHANGED
|
@@ -23,9 +23,16 @@ const StandardFunctions = {
|
|
|
23
23
|
search: function (ref, arg) {
|
|
24
24
|
if (!('val' in arg)) throw new Error(`Only single value arguments are allowed for $search`)
|
|
25
25
|
// only apply first search term, rest is ignored
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const sub = /("")|("(?:[^"]|\\")*(?:[^\\]|\\\\)")|(\S*)/.exec(arg.val)
|
|
27
|
+
let val
|
|
28
|
+
try {
|
|
29
|
+
val = (sub[2] ? JSON.parse(sub[2]) : sub[3]) || ''
|
|
30
|
+
} catch {
|
|
31
|
+
val = sub[2] || sub[3] || ''
|
|
32
|
+
}
|
|
33
|
+
arg.val = arg.__proto__.val = val
|
|
34
|
+
const refs = ref.list || [ref]
|
|
35
|
+
const { toString } = ref
|
|
29
36
|
return '(' + refs.map(ref2 => this.contains(this.tolower(toString(ref2)), this.tolower(arg))).join(' or ') + ')'
|
|
30
37
|
},
|
|
31
38
|
/**
|
|
@@ -158,8 +165,8 @@ const StandardFunctions = {
|
|
|
158
165
|
* Generates SQL statement that produces current point in time (date and time with time zone)
|
|
159
166
|
* @returns {string}
|
|
160
167
|
*/
|
|
161
|
-
|
|
162
|
-
return this.session_context({val: '$now'})
|
|
168
|
+
now: function () {
|
|
169
|
+
return this.session_context({ val: '$now' })
|
|
163
170
|
},
|
|
164
171
|
/**
|
|
165
172
|
* Generates SQL statement that produces the year of a given timestamp
|
package/lib/cqn2sql.js
CHANGED
|
@@ -13,14 +13,14 @@ const BINARY_TYPES = {
|
|
|
13
13
|
const { Readable } = require('stream')
|
|
14
14
|
|
|
15
15
|
const DEBUG = (() => {
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
if (DEBUG) {
|
|
20
|
-
|
|
16
|
+
const LOG = cds.log('sql-json')
|
|
17
|
+
if (LOG._debug) return cds.debug('sql-json')
|
|
18
|
+
return cds.debug('sql|sqlite')
|
|
19
|
+
//if (DEBUG) {
|
|
20
|
+
// return DEBUG
|
|
21
21
|
// (sql, ...more) => DEBUG (sql.replace(/(?:SELECT[\n\r\s]+(json_group_array\()?[\n\r\s]*json_insert\((\n|\r|.)*?\)[\n\r\s]*\)?[\n\r\s]+as[\n\r\s]+_json_[\n\r\s]+FROM[\n\r\s]*\(|\)[\n\r\s]*(\)[\n\r\s]+AS )|\)$)/gim,(a,b,c,d) => d || ''), ...more)
|
|
22
22
|
// FIXME: looses closing ) on INSERT queries
|
|
23
|
-
}
|
|
23
|
+
//}
|
|
24
24
|
})()
|
|
25
25
|
|
|
26
26
|
class CQN2SQLRenderer {
|
|
@@ -85,7 +85,7 @@ class CQN2SQLRenderer {
|
|
|
85
85
|
const sanitize_values = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
|
|
86
86
|
DEBUG?.(
|
|
87
87
|
this.sql,
|
|
88
|
-
sanitize_values && (this.entries || this.values?.length > 0) ? ['***'] : this.entries || this.values,
|
|
88
|
+
...(sanitize_values && (this.entries || this.values?.length > 0) ? ['***'] : this.entries || this.values || []),
|
|
89
89
|
)
|
|
90
90
|
return this
|
|
91
91
|
}
|
|
@@ -311,8 +311,8 @@ class CQN2SQLRenderer {
|
|
|
311
311
|
*/
|
|
312
312
|
column_expr(x, q) {
|
|
313
313
|
if (x === '*') return '*'
|
|
314
|
-
|
|
315
|
-
let sql = this.expr({ param: false, __proto__: x })
|
|
314
|
+
|
|
315
|
+
let sql = x.param !== true && typeof x.val === 'number' ? this.expr({ param: false, __proto__: x }): this.expr(x)
|
|
316
316
|
let alias = this.column_alias4(x, q)
|
|
317
317
|
if (alias) sql += ' as ' + this.quote(alias)
|
|
318
318
|
return sql
|
|
@@ -508,6 +508,7 @@ class CQN2SQLRenderer {
|
|
|
508
508
|
} else {
|
|
509
509
|
const stream = Readable.from(this.INSERT_entries_stream(INSERT.entries), { objectMode: false })
|
|
510
510
|
stream.type = 'json'
|
|
511
|
+
stream._raw = INSERT.entries
|
|
511
512
|
this.entries = [[...this.values, stream]]
|
|
512
513
|
}
|
|
513
514
|
|
|
@@ -652,6 +653,7 @@ class CQN2SQLRenderer {
|
|
|
652
653
|
} else {
|
|
653
654
|
const stream = Readable.from(this.INSERT_rows_stream(INSERT.rows), { objectMode: false })
|
|
654
655
|
stream.type = 'json'
|
|
656
|
+
stream._raw = INSERT.rows
|
|
655
657
|
this.entries = [[...this.values, stream]]
|
|
656
658
|
}
|
|
657
659
|
|
|
@@ -1080,6 +1082,8 @@ Buffer.prototype.toJSON = function () {
|
|
|
1080
1082
|
return this.toString('base64')
|
|
1081
1083
|
}
|
|
1082
1084
|
|
|
1085
|
+
Readable.prototype[require('node:util').inspect.custom] = Readable.prototype.toJSON = function () { return this._raw || `[object ${this.constructor.name}]` }
|
|
1086
|
+
|
|
1083
1087
|
const ObjectKeys = o => (o && [...ObjectKeys(o.__proto__), ...Object.keys(o)]) || []
|
|
1084
1088
|
const _managed = {
|
|
1085
1089
|
'$user.id': '$user.id',
|
package/lib/cqn4sql.js
CHANGED
|
@@ -432,7 +432,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
432
432
|
return
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
-
const tableAlias =
|
|
435
|
+
const tableAlias = getTableAlias(col)
|
|
436
436
|
// re-adjust usage of implicit alias in subquery
|
|
437
437
|
if (col.$refLinks[0].definition.kind === 'entity' && col.ref[0] !== tableAlias) {
|
|
438
438
|
col.ref[0] = tableAlias
|
|
@@ -530,7 +530,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
530
530
|
res = getTransformedTokenStream([value], baseLink)[0]
|
|
531
531
|
} else if (xpr) {
|
|
532
532
|
res = { xpr: getTransformedTokenStream(value.xpr, baseLink) }
|
|
533
|
-
} else if (val) {
|
|
533
|
+
} else if (val !== undefined) {
|
|
534
534
|
res = { val }
|
|
535
535
|
} else if (func) {
|
|
536
536
|
res = { args: getTransformedFunctionArgs(value.args, baseLink), func: value.func }
|
|
@@ -725,7 +725,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
725
725
|
res.push(...getColumnsForWildcard(exclude, replace, col.as))
|
|
726
726
|
} else
|
|
727
727
|
res.push(
|
|
728
|
-
...getFlatColumnsFor(col, { columnAlias: col.as, tableAlias:
|
|
728
|
+
...getFlatColumnsFor(col, { columnAlias: col.as, tableAlias: getTableAlias(col) }, [], {
|
|
729
729
|
exclude,
|
|
730
730
|
replace,
|
|
731
731
|
}),
|
|
@@ -884,6 +884,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
884
884
|
|
|
885
885
|
if (expand.expand) {
|
|
886
886
|
const nested = _subqueryForGroupBy(expand, fullRef, expand.as || expand.ref.map(idOnly).join('_'))
|
|
887
|
+
setElementOnColumns(nested, expand.element)
|
|
887
888
|
elements[expand.as || expand.ref.map(idOnly).join('_')] = nested
|
|
888
889
|
return nested
|
|
889
890
|
}
|
|
@@ -895,11 +896,12 @@ function cqn4sql(originalQuery, model) {
|
|
|
895
896
|
)
|
|
896
897
|
}
|
|
897
898
|
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
const
|
|
899
|
+
const copy = Object.create(groupByRef)
|
|
900
|
+
// always alias for this special case, so that they nested element names match the expected result structure
|
|
901
|
+
// otherwise we'd get `author { <outer>.author_ID }`, but we need `author { <outer>.author_ID as ID }`
|
|
902
|
+
copy.as = expand.as || expand.ref.at(-1)
|
|
903
|
+
const tableAlias = getTableAlias(copy)
|
|
904
|
+
const res = getFlatColumnsFor(copy, { tableAlias })
|
|
903
905
|
res.forEach(c => {
|
|
904
906
|
elements[c.as || c.ref.at(-1)] = c.element
|
|
905
907
|
})
|
|
@@ -961,7 +963,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
961
963
|
referredCol.nulls = col.nulls
|
|
962
964
|
col = referredCol
|
|
963
965
|
if (definition.kind === 'element') {
|
|
964
|
-
tableAlias =
|
|
966
|
+
tableAlias = getTableAlias(col)
|
|
965
967
|
} else {
|
|
966
968
|
// we must replace the reference with the underlying expression
|
|
967
969
|
const { val, func, args, xpr } = col
|
|
@@ -973,7 +975,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
973
975
|
}
|
|
974
976
|
}
|
|
975
977
|
} else {
|
|
976
|
-
tableAlias =
|
|
978
|
+
tableAlias = getTableAlias(col) // do not prepend TA if orderBy column addresses element of query
|
|
977
979
|
}
|
|
978
980
|
const leaf = col.$refLinks[col.$refLinks.length - 1].definition
|
|
979
981
|
if (leaf.virtual === true) continue // already in getFlatColumnForElement
|
|
@@ -992,12 +994,9 @@ function cqn4sql(originalQuery, model) {
|
|
|
992
994
|
if (inOrderBy && flatColumns.length > 1)
|
|
993
995
|
throw new Error(`"${getFullName(leaf)}" can't be used in order by as it expands to multiple fields`)
|
|
994
996
|
flatColumns.forEach(fc => {
|
|
995
|
-
if (col.nulls)
|
|
996
|
-
|
|
997
|
-
if (
|
|
998
|
-
fc.sort = col.sort
|
|
999
|
-
if (fc.as)
|
|
1000
|
-
delete fc.as
|
|
997
|
+
if (col.nulls) fc.nulls = col.nulls
|
|
998
|
+
if (col.sort) fc.sort = col.sort
|
|
999
|
+
if (fc.as) delete fc.as
|
|
1001
1000
|
})
|
|
1002
1001
|
res.push(...flatColumns)
|
|
1003
1002
|
} else {
|
|
@@ -1156,7 +1155,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1156
1155
|
if (column.val || column.func || column.SELECT) return [column]
|
|
1157
1156
|
|
|
1158
1157
|
const structsAreUnfoldedAlready = model.meta.unfolded?.includes('structs')
|
|
1159
|
-
let { baseName, columnAlias, tableAlias } = names
|
|
1158
|
+
let { baseName, columnAlias = column.as, tableAlias } = names
|
|
1160
1159
|
const { exclude, replace } = excludeAndReplace || {}
|
|
1161
1160
|
const { $refLinks, flatName, isJoinRelevant } = column
|
|
1162
1161
|
let leafAssoc
|
|
@@ -1199,7 +1198,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1199
1198
|
baseName = getFullName(replacedBy.$refLinks?.[replacedBy.$refLinks.length - 2].definition)
|
|
1200
1199
|
if (replacedBy.isJoinRelevant)
|
|
1201
1200
|
// we need to provide the correct table alias
|
|
1202
|
-
tableAlias =
|
|
1201
|
+
tableAlias = getTableAlias(replacedBy)
|
|
1203
1202
|
|
|
1204
1203
|
if (replacedBy.expand) return [{ as: baseName }]
|
|
1205
1204
|
|
|
@@ -1488,7 +1487,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1488
1487
|
// hence we need to ignore the alias of the `$baseLink`
|
|
1489
1488
|
const lastAssoc =
|
|
1490
1489
|
token.isJoinRelevant && [...token.$refLinks].reverse().find(l => l.definition.isAssociation)
|
|
1491
|
-
const tableAlias =
|
|
1490
|
+
const tableAlias = getTableAlias(token, (!lastAssoc?.onlyForeignKeyAccess && lastAssoc) || $baseLink)
|
|
1492
1491
|
if ((!$baseLink || lastAssoc) && token.isJoinRelevant) {
|
|
1493
1492
|
let name = calculateElementName(token, getFullName)
|
|
1494
1493
|
result.ref = [tableAlias, name]
|
|
@@ -1585,7 +1584,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1585
1584
|
if (!def.$refLinks) return def
|
|
1586
1585
|
const leaf = def.$refLinks[def.$refLinks.length - 1]
|
|
1587
1586
|
const first = def.$refLinks[0]
|
|
1588
|
-
const tableAlias =
|
|
1587
|
+
const tableAlias = getTableAlias(
|
|
1589
1588
|
def,
|
|
1590
1589
|
def.ref.length > 1 && first.definition.isAssociation ? first : $baseLink,
|
|
1591
1590
|
)
|
|
@@ -1854,6 +1853,11 @@ function cqn4sql(originalQuery, model) {
|
|
|
1854
1853
|
result[i] = asXpr(xpr)
|
|
1855
1854
|
continue
|
|
1856
1855
|
}
|
|
1856
|
+
if(lhs.args) {
|
|
1857
|
+
const args = calculateOnCondition(lhs.args)
|
|
1858
|
+
result[i] = { ...lhs, args }
|
|
1859
|
+
continue
|
|
1860
|
+
}
|
|
1857
1861
|
const rhs = result[i + 2]
|
|
1858
1862
|
if (rhs?.ref || lhs.ref) {
|
|
1859
1863
|
// if we have refs on each side of the comparison, we might need to perform tuple expansion
|
|
@@ -2194,7 +2198,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
2194
2198
|
* the combined elements of the query
|
|
2195
2199
|
* @returns the source name which can be used to address the node
|
|
2196
2200
|
*/
|
|
2197
|
-
function
|
|
2201
|
+
function getTableAlias(node, $baseLink = null) {
|
|
2198
2202
|
if (!node || !node.$refLinks || !node.ref) {
|
|
2199
2203
|
throw new Error('Invalid node')
|
|
2200
2204
|
}
|
package/lib/deep-queries.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const cds = require('@sap/cds')
|
|
2
2
|
const { _target_name4 } = require('./SQLService')
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
const ROOT = Symbol('root')
|
|
4
5
|
|
|
5
6
|
// REVISIT: remove old path with cds^8
|
|
6
7
|
let _compareJson
|
|
@@ -45,20 +46,22 @@ async function onDeep(req, next) {
|
|
|
45
46
|
if (query.UPDATE && !beforeData.length) return 0
|
|
46
47
|
|
|
47
48
|
const queries = getDeepQueries(query, beforeData, target)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
49
|
+
|
|
50
|
+
// first delete, then update, then insert because of potential unique constraints:
|
|
51
|
+
// - deletes never trigger unique constraints, but can prevent them -> execute first
|
|
52
|
+
// - updates can trigger and prevent unique constraints -> execute second
|
|
53
|
+
// - inserts can only trigger unique constraints -> execute last
|
|
54
|
+
await Promise.all(Array.from(queries.deletes.values()).map(query => this.onSIMPLE({ query })))
|
|
55
|
+
await Promise.all(queries.updates.map(query => this.onUPDATE({ query })))
|
|
56
|
+
|
|
57
|
+
const rootQuery = queries.inserts.get(ROOT)
|
|
58
|
+
queries.inserts.delete(ROOT)
|
|
59
|
+
const [rootResult] = await Promise.all([
|
|
60
|
+
rootQuery && this.onINSERT({ query: rootQuery }),
|
|
61
|
+
...Array.from(queries.inserts.values()).map(query => this.onINSERT({ query })),
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
return beforeData.length ?? rootResult
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
const hasDeep = (q, target) => {
|
|
@@ -195,7 +198,7 @@ const getDeepQueries = (query, dbData, target) => {
|
|
|
195
198
|
diff = [diff]
|
|
196
199
|
}
|
|
197
200
|
|
|
198
|
-
return _getDeepQueries(diff, target
|
|
201
|
+
return _getDeepQueries(diff, target)
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
const _hasManagedElements = target => {
|
|
@@ -205,16 +208,19 @@ const _hasManagedElements = target => {
|
|
|
205
208
|
/**
|
|
206
209
|
* @param {unknown[]} diff
|
|
207
210
|
* @param {import('@sap/cds/apis/csn').Definition} target
|
|
208
|
-
* @param {
|
|
209
|
-
* @
|
|
211
|
+
* @param {Map<String, Object>} deletes
|
|
212
|
+
* @param {Map<String, Object>} inserts
|
|
213
|
+
* @param {Object[]} updates
|
|
214
|
+
* @param {boolean} [root=true]
|
|
215
|
+
* @returns {Object|Boolean}
|
|
210
216
|
*/
|
|
211
|
-
const _getDeepQueries = (diff, target, root =
|
|
212
|
-
|
|
213
|
-
|
|
217
|
+
const _getDeepQueries = (diff, target, deletes = new Map(), inserts = new Map(), updates = [], root = true) => {
|
|
218
|
+
// flag to determine if queries were created
|
|
219
|
+
let dirty = false
|
|
214
220
|
for (const diffEntry of diff) {
|
|
215
221
|
if (diffEntry === undefined) continue
|
|
216
|
-
const subQueries = []
|
|
217
222
|
|
|
223
|
+
let childrenDirty = false
|
|
218
224
|
for (const prop in diffEntry) {
|
|
219
225
|
// handle deep operations
|
|
220
226
|
|
|
@@ -224,9 +230,12 @@ const _getDeepQueries = (diff, target, root = false) => {
|
|
|
224
230
|
delete diffEntry[prop]
|
|
225
231
|
} else if (target.compositions?.[prop]) {
|
|
226
232
|
const arrayed = Array.isArray(propData) ? propData : [propData]
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
233
|
+
childrenDirty =
|
|
234
|
+
arrayed
|
|
235
|
+
.map(subEntry =>
|
|
236
|
+
_getDeepQueries([subEntry], target.elements[prop]._target, deletes, inserts, updates, false),
|
|
237
|
+
)
|
|
238
|
+
.some(a => a) || childrenDirty
|
|
230
239
|
delete diffEntry[prop]
|
|
231
240
|
} else if (diffEntry[prop] === undefined) {
|
|
232
241
|
// restore current behavior, if property is undefined, not part of payload
|
|
@@ -242,12 +251,32 @@ const _getDeepQueries = (diff, target, root = false) => {
|
|
|
242
251
|
delete diffEntry._old
|
|
243
252
|
}
|
|
244
253
|
|
|
245
|
-
// first calculate subqueries and rm their properties, then build root query
|
|
246
254
|
if (op === 'create') {
|
|
247
|
-
|
|
255
|
+
dirty = true
|
|
256
|
+
const id = root ? ROOT : target.name
|
|
257
|
+
const insert = inserts.get(id)
|
|
258
|
+
if (insert) {
|
|
259
|
+
insert.INSERT.entries.push(diffEntry)
|
|
260
|
+
} else {
|
|
261
|
+
const q = INSERT.into(target).entries(diffEntry)
|
|
262
|
+
inserts.set(id, q)
|
|
263
|
+
}
|
|
248
264
|
} else if (op === 'delete') {
|
|
249
|
-
|
|
250
|
-
|
|
265
|
+
dirty = true
|
|
266
|
+
const keys = cds.utils
|
|
267
|
+
.Object_keys(target.keys)
|
|
268
|
+
.filter(key => !target.keys[key].virtual && !target.keys[key].isAssociation)
|
|
269
|
+
|
|
270
|
+
const keyVals = keys.map(k => ({ val: diffEntry[k] }))
|
|
271
|
+
const currDelete = deletes.get(target.name)
|
|
272
|
+
if (currDelete) currDelete.DELETE.where[2].list.push({ list: keyVals })
|
|
273
|
+
else {
|
|
274
|
+
const left = { list: keys.map(k => ({ ref: [k] })) }
|
|
275
|
+
const right = { list: [{ list: keyVals }] }
|
|
276
|
+
deletes.set(target.name, DELETE.from(target).where([left, 'in', right]))
|
|
277
|
+
}
|
|
278
|
+
} else if (op === 'update' || (op === undefined && (root || childrenDirty) && _hasManagedElements(target))) {
|
|
279
|
+
dirty = true
|
|
251
280
|
// TODO do we need the where here?
|
|
252
281
|
const keys = target.keys
|
|
253
282
|
const cqn = UPDATE(target).with(diffEntry)
|
|
@@ -259,34 +288,16 @@ const _getDeepQueries = (diff, target, root = false) => {
|
|
|
259
288
|
delete diffEntry[key]
|
|
260
289
|
}
|
|
261
290
|
cqn.with(diffEntry)
|
|
262
|
-
|
|
291
|
+
updates.push(cqn)
|
|
263
292
|
}
|
|
264
|
-
|
|
265
|
-
for (const q of subQueries) queries.push(q)
|
|
266
293
|
}
|
|
267
294
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return queries.map(q => {
|
|
271
|
-
// Merge all INSERT statements for each target
|
|
272
|
-
if (q.INSERT) {
|
|
273
|
-
const target = q.target
|
|
274
|
-
if (insertQueries.has(target)) {
|
|
275
|
-
insertQueries.get(target).INSERT.entries.push(...q.INSERT.entries)
|
|
276
|
-
return
|
|
277
|
-
} else {
|
|
278
|
-
insertQueries.set(target, q)
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
Object.defineProperty(q, handledDeep, { value: true })
|
|
282
|
-
return q
|
|
283
|
-
})
|
|
284
|
-
.filter(a => a)
|
|
295
|
+
return root ? { updates, inserts, deletes } : dirty
|
|
285
296
|
}
|
|
286
297
|
|
|
287
298
|
module.exports = {
|
|
288
299
|
onDeep,
|
|
289
|
-
getDeepQueries,
|
|
290
|
-
getExpandForDeep,
|
|
291
300
|
hasDeep,
|
|
301
|
+
getDeepQueries, // only for testing
|
|
302
|
+
getExpandForDeep, // only for testing
|
|
292
303
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/db-service",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.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": {
|
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"test": "jest --silent"
|
|
26
26
|
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"generic-pool": "^3.9.0"
|
|
29
|
+
},
|
|
27
30
|
"peerDependencies": {
|
|
28
31
|
"@sap/cds": ">=7.9"
|
|
29
32
|
},
|