@cap-js/db-service 1.17.0 → 1.17.2
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/lib/deep-queries.js +1 -1
- package/lib/infer/index.js +32 -24
- package/package.json +1 -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.17.2](https://github.com/cap-js/cds-dbs/compare/db-service-v1.17.1...db-service-v1.17.2) (2025-02-09)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* replace polynomial regex with simple string op ([#1015](https://github.com/cap-js/cds-dbs/issues/1015)) ([3fe6e6b](https://github.com/cap-js/cds-dbs/commit/3fe6e6b7f7aaf5aafb811acf2838cd1da30052a8))
|
|
13
|
+
|
|
14
|
+
## [1.17.1](https://github.com/cap-js/cds-dbs/compare/db-service-v1.17.0...db-service-v1.17.1) (2025-02-04)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
* deep update resulting in deep delete of sub-element ([#1006](https://github.com/cap-js/cds-dbs/issues/1006)) ([ef2f817](https://github.com/cap-js/cds-dbs/commit/ef2f8175df6fc7076fa8a9290e1863f44d267d8d))
|
|
20
|
+
* nested $self reference to other column ([#1009](https://github.com/cap-js/cds-dbs/issues/1009)) ([41a76d8](https://github.com/cap-js/cds-dbs/commit/41a76d89a884ac8266ccbd2d087af435e8f26ccb))
|
|
21
|
+
|
|
7
22
|
## [1.17.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.16.2...db-service-v1.17.0) (2025-01-28)
|
|
8
23
|
|
|
9
24
|
|
package/lib/deep-queries.js
CHANGED
|
@@ -51,7 +51,7 @@ async function onDeep(req, next) {
|
|
|
51
51
|
// - deletes never trigger unique constraints, but can prevent them -> execute first
|
|
52
52
|
// - updates can trigger and prevent unique constraints -> execute second
|
|
53
53
|
// - inserts can only trigger unique constraints -> execute last
|
|
54
|
-
await Promise.all(Array.from(queries.deletes.values()).map(query => this.
|
|
54
|
+
await Promise.all(Array.from(queries.deletes.values()).map(query => this.onDELETE({ query, target: query._target })))
|
|
55
55
|
await Promise.all(queries.updates.map(query => this.onUPDATE({ query })))
|
|
56
56
|
|
|
57
57
|
const rootQuery = queries.inserts.get(ROOT)
|
package/lib/infer/index.js
CHANGED
|
@@ -116,9 +116,11 @@ function infer(originalQuery, model) {
|
|
|
116
116
|
|
|
117
117
|
inferArg(from, null, null, { inFrom: true })
|
|
118
118
|
const alias =
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
from.uniqueSubqueryAlias ||
|
|
120
|
+
from.as ||
|
|
121
|
+
(ref.length === 1
|
|
122
|
+
? first.substring(first.lastIndexOf('.') + 1)
|
|
123
|
+
: (ref.at(-1).id || ref.at(-1)));
|
|
122
124
|
if (alias in querySources) throw new Error(`Duplicate alias "${alias}"`)
|
|
123
125
|
querySources[alias] = { definition: target, args }
|
|
124
126
|
const last = from.$refLinks.at(-1)
|
|
@@ -134,7 +136,7 @@ function infer(originalQuery, model) {
|
|
|
134
136
|
} else if (typeof from === 'string') {
|
|
135
137
|
// TODO: Create unique alias, what about duplicates?
|
|
136
138
|
const definition = getDefinition(from) || cds.error`"${from}" not found in the definitions of your model`
|
|
137
|
-
querySources[
|
|
139
|
+
querySources[from.substring(from.lastIndexOf('.') + 1)] = { definition }
|
|
138
140
|
} else if (from.SET) {
|
|
139
141
|
infer(from, model)
|
|
140
142
|
}
|
|
@@ -207,12 +209,12 @@ function infer(originalQuery, model) {
|
|
|
207
209
|
if (as === undefined) cds.error`Expecting expression to have an alias name`
|
|
208
210
|
if (queryElements[as]) cds.error`Duplicate definition of element “${as}”`
|
|
209
211
|
if (col.xpr || col.SELECT) {
|
|
210
|
-
queryElements[as] = getElementForXprOrSubquery(col, queryElements)
|
|
212
|
+
queryElements[as] = getElementForXprOrSubquery(col, queryElements, dollarSelfRefs)
|
|
211
213
|
}
|
|
212
214
|
if (col.func) {
|
|
213
215
|
if (col.args) {
|
|
214
216
|
// {func}.args are optional
|
|
215
|
-
applyToFunctionArgs(col.args, inferArg, [false])
|
|
217
|
+
applyToFunctionArgs(col.args, inferArg, [false, null, {dollarSelfRefs}])
|
|
216
218
|
}
|
|
217
219
|
queryElements[as] = getElementForCast(col)
|
|
218
220
|
}
|
|
@@ -287,7 +289,7 @@ function infer(originalQuery, model) {
|
|
|
287
289
|
if (having) walkTokenStream(having)
|
|
288
290
|
if (_.with)
|
|
289
291
|
// consider UPDATE.with
|
|
290
|
-
Object.values(_.with).forEach(val => inferArg(val, queryElements, null, {
|
|
292
|
+
Object.values(_.with).forEach(val => inferArg(val, queryElements, null, { inXpr: true }))
|
|
291
293
|
|
|
292
294
|
return queryElements
|
|
293
295
|
|
|
@@ -299,7 +301,7 @@ function infer(originalQuery, model) {
|
|
|
299
301
|
*
|
|
300
302
|
* @param {array} tokenStream
|
|
301
303
|
*/
|
|
302
|
-
function walkTokenStream(tokenStream,
|
|
304
|
+
function walkTokenStream(tokenStream, inXpr = false) {
|
|
303
305
|
let skipJoins
|
|
304
306
|
const processToken = t => {
|
|
305
307
|
if (t === 'exists') {
|
|
@@ -309,7 +311,7 @@ function infer(originalQuery, model) {
|
|
|
309
311
|
// don't miss an exists within an expression
|
|
310
312
|
t.xpr.forEach(processToken)
|
|
311
313
|
} else {
|
|
312
|
-
inferArg(t, queryElements, null, { inExists: skipJoins,
|
|
314
|
+
inferArg(t, queryElements, null, { inExists: skipJoins, inXpr, inQueryModifier: true })
|
|
313
315
|
skipJoins = false
|
|
314
316
|
}
|
|
315
317
|
}
|
|
@@ -329,11 +331,12 @@ function infer(originalQuery, model) {
|
|
|
329
331
|
const unprocessedColumns = []
|
|
330
332
|
|
|
331
333
|
for (const currentDollarSelfColumn of dollarSelfColumns) {
|
|
332
|
-
const { ref } = currentDollarSelfColumn
|
|
334
|
+
const { ref, inXpr } = currentDollarSelfColumn
|
|
333
335
|
const stepToFind = ref[1]
|
|
334
336
|
|
|
335
337
|
const referencesOtherDollarSelfColumn = dollarSelfColumns.find(
|
|
336
338
|
otherDollarSelfCol =>
|
|
339
|
+
!(stepToFind in queryElements) &&
|
|
337
340
|
otherDollarSelfCol !== currentDollarSelfColumn &&
|
|
338
341
|
(otherDollarSelfCol.as
|
|
339
342
|
? stepToFind === otherDollarSelfCol.as
|
|
@@ -343,7 +346,7 @@ function infer(originalQuery, model) {
|
|
|
343
346
|
if (referencesOtherDollarSelfColumn) {
|
|
344
347
|
unprocessedColumns.push(currentDollarSelfColumn)
|
|
345
348
|
} else {
|
|
346
|
-
handleRef(currentDollarSelfColumn)
|
|
349
|
+
handleRef(currentDollarSelfColumn, inXpr)
|
|
347
350
|
}
|
|
348
351
|
}
|
|
349
352
|
|
|
@@ -351,8 +354,8 @@ function infer(originalQuery, model) {
|
|
|
351
354
|
} while (dollarSelfColumns.length > 0)
|
|
352
355
|
}
|
|
353
356
|
|
|
354
|
-
function handleRef(col) {
|
|
355
|
-
inferArg(col, queryElements)
|
|
357
|
+
function handleRef(col, inXpr) {
|
|
358
|
+
inferArg(col, queryElements, null, { inXpr })
|
|
356
359
|
const { definition } = col.$refLinks[col.$refLinks.length - 1]
|
|
357
360
|
if (col.cast)
|
|
358
361
|
// final type overwritten -> element not visible anymore
|
|
@@ -379,7 +382,7 @@ function infer(originalQuery, model) {
|
|
|
379
382
|
* @param {object} [context={}] - Contextual information for element inference.
|
|
380
383
|
* @param {boolean} [context.inExists=false] - Flag to control the creation of joins for non-association path traversals.
|
|
381
384
|
* for `exists <assoc>` paths we do not need to create joins for path expressions as they are part of the semi-joined subquery.
|
|
382
|
-
* @param {boolean} [context.
|
|
385
|
+
* @param {boolean} [context.inXpr=false] - Flag to signal whether the element is part of an expression.
|
|
383
386
|
* Used to ignore non-persisted elements.
|
|
384
387
|
* @param {boolean} [context.inNestedProjection=false] - Flag to signal whether the element is part of a nested projection.
|
|
385
388
|
*
|
|
@@ -401,11 +404,11 @@ function infer(originalQuery, model) {
|
|
|
401
404
|
*/
|
|
402
405
|
|
|
403
406
|
function inferArg(arg, queryElements = null, $baseLink = null, context = {}) {
|
|
404
|
-
const { inExists,
|
|
407
|
+
const { inExists, inXpr, inCalcElement, baseColumn, inInfixFilter, inQueryModifier, inFrom, dollarSelfRefs } = context
|
|
405
408
|
if (arg.param || arg.SELECT) return // parameter references are only resolved into values on execution e.g. :val, :1 or ?
|
|
406
409
|
if (arg.args) applyToFunctionArgs(arg.args, inferArg, [null, $baseLink, context])
|
|
407
410
|
if (arg.list) arg.list.forEach(arg => inferArg(arg, null, $baseLink, context))
|
|
408
|
-
if (arg.xpr) arg.xpr.forEach(token => inferArg(token, queryElements, $baseLink, { ...context,
|
|
411
|
+
if (arg.xpr) arg.xpr.forEach(token => inferArg(token, queryElements, $baseLink, { ...context, inXpr: true })) // e.g. function in expression
|
|
409
412
|
|
|
410
413
|
if (!arg.ref) {
|
|
411
414
|
if (arg.expand && queryElements) queryElements[arg.as] = resolveExpand(arg)
|
|
@@ -426,6 +429,11 @@ function infer(originalQuery, model) {
|
|
|
426
429
|
firstStepIsSelf = !firstStepIsTableAlias && arg.ref.length > 1 && ['$self', '$projection'].includes(arg.ref[0])
|
|
427
430
|
expandOnTableAlias = arg.ref.length === 1 && arg.ref[0] in sources && (arg.expand || arg.inline)
|
|
428
431
|
}
|
|
432
|
+
if(dollarSelfRefs && firstStepIsSelf) {
|
|
433
|
+
Object.defineProperty(arg, 'inXpr', { value: true, writable: true })
|
|
434
|
+
dollarSelfRefs.push(arg)
|
|
435
|
+
return
|
|
436
|
+
}
|
|
429
437
|
const nameSegments = []
|
|
430
438
|
// if a (segment) of a (structured) foreign key is renamed, we must not include
|
|
431
439
|
// the aliased ref segments into the name of the final foreign key which is e.g. used in
|
|
@@ -564,7 +572,7 @@ function infer(originalQuery, model) {
|
|
|
564
572
|
} else if (token.ref || token.xpr || token.list) {
|
|
565
573
|
inferArg(token, false, arg.$refLinks[i], {
|
|
566
574
|
inExists: skipJoinsForFilter || inExists,
|
|
567
|
-
|
|
575
|
+
inXpr: !!token.xpr,
|
|
568
576
|
inInfixFilter: true,
|
|
569
577
|
inFrom,
|
|
570
578
|
})
|
|
@@ -573,7 +581,7 @@ function infer(originalQuery, model) {
|
|
|
573
581
|
applyToFunctionArgs(token.args, inferArg, [
|
|
574
582
|
false,
|
|
575
583
|
arg.$refLinks[i],
|
|
576
|
-
{ inExists: skipJoinsForFilter || inExists,
|
|
584
|
+
{ inExists: skipJoinsForFilter || inExists, inXpr: true, inInfixFilter: true, inFrom },
|
|
577
585
|
])
|
|
578
586
|
}
|
|
579
587
|
}
|
|
@@ -641,7 +649,7 @@ function infer(originalQuery, model) {
|
|
|
641
649
|
}
|
|
642
650
|
}
|
|
643
651
|
const leafArt = arg.$refLinks[arg.$refLinks.length - 1].definition
|
|
644
|
-
const virtual = (leafArt.virtual || !isPersisted) && !
|
|
652
|
+
const virtual = (leafArt.virtual || !isPersisted) && !inXpr
|
|
645
653
|
// check if we need to merge the column `ref` into the join tree of the query
|
|
646
654
|
if (!inFrom && !inExists && !virtual && !inCalcElement) {
|
|
647
655
|
// for a ref inside an `inline` we need to consider the column `ref` which has the `inline` prop
|
|
@@ -658,7 +666,7 @@ function infer(originalQuery, model) {
|
|
|
658
666
|
}
|
|
659
667
|
|
|
660
668
|
function insertIntoQueryElements() {
|
|
661
|
-
return queryElements && !
|
|
669
|
+
return queryElements && !inXpr && !inInfixFilter && !inQueryModifier
|
|
662
670
|
}
|
|
663
671
|
|
|
664
672
|
/**
|
|
@@ -686,7 +694,7 @@ function infer(originalQuery, model) {
|
|
|
686
694
|
}
|
|
687
695
|
let elements = {}
|
|
688
696
|
inline.forEach(inlineCol => {
|
|
689
|
-
inferArg(inlineCol, null, $leafLink, {
|
|
697
|
+
inferArg(inlineCol, null, $leafLink, { inXpr: true, baseColumn: col })
|
|
690
698
|
if (inlineCol === '*') {
|
|
691
699
|
const wildCardElements = {}
|
|
692
700
|
// either the `.elements´ of the struct or the `.elements` of the assoc target
|
|
@@ -762,7 +770,7 @@ function infer(originalQuery, model) {
|
|
|
762
770
|
if (e === '*') {
|
|
763
771
|
elements = { ...elements, ...$leafLink.definition.elements }
|
|
764
772
|
} else {
|
|
765
|
-
inferArg(e, false, $leafLink, {
|
|
773
|
+
inferArg(e, false, $leafLink, { inXpr: true })
|
|
766
774
|
if (e.expand) elements[e.as || e.flatName] = resolveExpand(e)
|
|
767
775
|
if (e.inline) elements = { ...elements, ...resolveInline(e) }
|
|
768
776
|
else elements[e.as || e.flatName] = e.$refLinks ? e.$refLinks[e.$refLinks.length - 1].definition : e
|
|
@@ -1008,7 +1016,7 @@ function infer(originalQuery, model) {
|
|
|
1008
1016
|
* @param {object} col
|
|
1009
1017
|
* @returns object
|
|
1010
1018
|
*/
|
|
1011
|
-
function getElementForXprOrSubquery(col, queryElements) {
|
|
1019
|
+
function getElementForXprOrSubquery(col, queryElements, dollarSelfRefs) {
|
|
1012
1020
|
const { xpr } = col
|
|
1013
1021
|
let skipJoins = false
|
|
1014
1022
|
xpr?.forEach(token => {
|
|
@@ -1016,7 +1024,7 @@ function infer(originalQuery, model) {
|
|
|
1016
1024
|
// no joins for infix filters along `exists <path>`
|
|
1017
1025
|
skipJoins = true
|
|
1018
1026
|
} else {
|
|
1019
|
-
inferArg(token, queryElements, null, { inExists: skipJoins,
|
|
1027
|
+
inferArg(token, queryElements, null, { inExists: skipJoins, inXpr: true, dollarSelfRefs })
|
|
1020
1028
|
skipJoins = false
|
|
1021
1029
|
}
|
|
1022
1030
|
})
|
package/package.json
CHANGED