@cap-js/db-service 1.11.0 → 1.12.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 +15 -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 +2 -1
- package/lib/cqn2sql.js +7 -3
- package/lib/cqn4sql.js +18 -20
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@
|
|
|
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.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.11.0...db-service-v1.12.0) (2024-07-25)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
*** 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))
|
|
13
|
+
*** 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)
|
|
14
|
+
*** quotations in vals ([#754](https://github.com/cap-js/cds-dbs/issues/754)) ([94d8e97](https://github.com/cap-js/cds-dbs/commit/94d8e977ed00776ff494287ce505d6b7e8017d2e))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
*** 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))
|
|
20
|
+
|
|
21
|
+
|
|
7
22
|
## [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
23
|
|
|
9
24
|
|
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,7 +23,8 @@ 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
|
-
|
|
26
|
+
const sub= /("")|("(?:[^"]|\\")*(?:[^\\]|\\\\)")|(\S*)/.exec(arg.val)
|
|
27
|
+
arg.val = arg.__proto__.val = (sub[2] ? JSON.parse(sub[2]) : sub[3]) || ''
|
|
27
28
|
const refs = ref.list || [ref],
|
|
28
29
|
{ toString } = ref
|
|
29
30
|
return '(' + refs.map(ref2 => this.contains(this.tolower(toString(ref2)), this.tolower(arg))).join(' or ') + ')'
|
package/lib/cqn2sql.js
CHANGED
|
@@ -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
|
|
@@ -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
|
}),
|
|
@@ -895,11 +895,12 @@ function cqn4sql(originalQuery, model) {
|
|
|
895
895
|
)
|
|
896
896
|
}
|
|
897
897
|
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
const
|
|
898
|
+
const copy = Object.create(groupByRef)
|
|
899
|
+
// always alias for this special case, so that they nested element names match the expected result structure
|
|
900
|
+
// otherwise we'd get `author { <outer>.author_ID }`, but we need `author { <outer>.author_ID as ID }`
|
|
901
|
+
copy.as = expand.as || expand.ref.at(-1)
|
|
902
|
+
const tableAlias = getTableAlias(copy)
|
|
903
|
+
const res = getFlatColumnsFor(copy, { tableAlias })
|
|
903
904
|
res.forEach(c => {
|
|
904
905
|
elements[c.as || c.ref.at(-1)] = c.element
|
|
905
906
|
})
|
|
@@ -961,7 +962,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
961
962
|
referredCol.nulls = col.nulls
|
|
962
963
|
col = referredCol
|
|
963
964
|
if (definition.kind === 'element') {
|
|
964
|
-
tableAlias =
|
|
965
|
+
tableAlias = getTableAlias(col)
|
|
965
966
|
} else {
|
|
966
967
|
// we must replace the reference with the underlying expression
|
|
967
968
|
const { val, func, args, xpr } = col
|
|
@@ -973,7 +974,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
973
974
|
}
|
|
974
975
|
}
|
|
975
976
|
} else {
|
|
976
|
-
tableAlias =
|
|
977
|
+
tableAlias = getTableAlias(col) // do not prepend TA if orderBy column addresses element of query
|
|
977
978
|
}
|
|
978
979
|
const leaf = col.$refLinks[col.$refLinks.length - 1].definition
|
|
979
980
|
if (leaf.virtual === true) continue // already in getFlatColumnForElement
|
|
@@ -992,12 +993,9 @@ function cqn4sql(originalQuery, model) {
|
|
|
992
993
|
if (inOrderBy && flatColumns.length > 1)
|
|
993
994
|
throw new Error(`"${getFullName(leaf)}" can't be used in order by as it expands to multiple fields`)
|
|
994
995
|
flatColumns.forEach(fc => {
|
|
995
|
-
if (col.nulls)
|
|
996
|
-
|
|
997
|
-
if (
|
|
998
|
-
fc.sort = col.sort
|
|
999
|
-
if (fc.as)
|
|
1000
|
-
delete fc.as
|
|
996
|
+
if (col.nulls) fc.nulls = col.nulls
|
|
997
|
+
if (col.sort) fc.sort = col.sort
|
|
998
|
+
if (fc.as) delete fc.as
|
|
1001
999
|
})
|
|
1002
1000
|
res.push(...flatColumns)
|
|
1003
1001
|
} else {
|
|
@@ -1156,7 +1154,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1156
1154
|
if (column.val || column.func || column.SELECT) return [column]
|
|
1157
1155
|
|
|
1158
1156
|
const structsAreUnfoldedAlready = model.meta.unfolded?.includes('structs')
|
|
1159
|
-
let { baseName, columnAlias, tableAlias } = names
|
|
1157
|
+
let { baseName, columnAlias = column.as, tableAlias } = names
|
|
1160
1158
|
const { exclude, replace } = excludeAndReplace || {}
|
|
1161
1159
|
const { $refLinks, flatName, isJoinRelevant } = column
|
|
1162
1160
|
let leafAssoc
|
|
@@ -1199,7 +1197,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1199
1197
|
baseName = getFullName(replacedBy.$refLinks?.[replacedBy.$refLinks.length - 2].definition)
|
|
1200
1198
|
if (replacedBy.isJoinRelevant)
|
|
1201
1199
|
// we need to provide the correct table alias
|
|
1202
|
-
tableAlias =
|
|
1200
|
+
tableAlias = getTableAlias(replacedBy)
|
|
1203
1201
|
|
|
1204
1202
|
if (replacedBy.expand) return [{ as: baseName }]
|
|
1205
1203
|
|
|
@@ -1488,7 +1486,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1488
1486
|
// hence we need to ignore the alias of the `$baseLink`
|
|
1489
1487
|
const lastAssoc =
|
|
1490
1488
|
token.isJoinRelevant && [...token.$refLinks].reverse().find(l => l.definition.isAssociation)
|
|
1491
|
-
const tableAlias =
|
|
1489
|
+
const tableAlias = getTableAlias(token, (!lastAssoc?.onlyForeignKeyAccess && lastAssoc) || $baseLink)
|
|
1492
1490
|
if ((!$baseLink || lastAssoc) && token.isJoinRelevant) {
|
|
1493
1491
|
let name = calculateElementName(token, getFullName)
|
|
1494
1492
|
result.ref = [tableAlias, name]
|
|
@@ -1585,7 +1583,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
1585
1583
|
if (!def.$refLinks) return def
|
|
1586
1584
|
const leaf = def.$refLinks[def.$refLinks.length - 1]
|
|
1587
1585
|
const first = def.$refLinks[0]
|
|
1588
|
-
const tableAlias =
|
|
1586
|
+
const tableAlias = getTableAlias(
|
|
1589
1587
|
def,
|
|
1590
1588
|
def.ref.length > 1 && first.definition.isAssociation ? first : $baseLink,
|
|
1591
1589
|
)
|
|
@@ -2194,7 +2192,7 @@ function cqn4sql(originalQuery, model) {
|
|
|
2194
2192
|
* the combined elements of the query
|
|
2195
2193
|
* @returns the source name which can be used to address the node
|
|
2196
2194
|
*/
|
|
2197
|
-
function
|
|
2195
|
+
function getTableAlias(node, $baseLink = null) {
|
|
2198
2196
|
if (!node || !node.$refLinks || !node.ref) {
|
|
2199
2197
|
throw new Error('Invalid node')
|
|
2200
2198
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/db-service",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
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
|
},
|