@cap-js/db-service 1.17.0 → 1.17.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 +8 -0
- package/lib/deep-queries.js +1 -1
- package/lib/infer/index.js +26 -20
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
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.1](https://github.com/cap-js/cds-dbs/compare/db-service-v1.17.0...db-service-v1.17.1) (2025-02-04)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* 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))
|
|
13
|
+
* 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))
|
|
14
|
+
|
|
7
15
|
## [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
16
|
|
|
9
17
|
|
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
|
@@ -207,12 +207,12 @@ function infer(originalQuery, model) {
|
|
|
207
207
|
if (as === undefined) cds.error`Expecting expression to have an alias name`
|
|
208
208
|
if (queryElements[as]) cds.error`Duplicate definition of element “${as}”`
|
|
209
209
|
if (col.xpr || col.SELECT) {
|
|
210
|
-
queryElements[as] = getElementForXprOrSubquery(col, queryElements)
|
|
210
|
+
queryElements[as] = getElementForXprOrSubquery(col, queryElements, dollarSelfRefs)
|
|
211
211
|
}
|
|
212
212
|
if (col.func) {
|
|
213
213
|
if (col.args) {
|
|
214
214
|
// {func}.args are optional
|
|
215
|
-
applyToFunctionArgs(col.args, inferArg, [false])
|
|
215
|
+
applyToFunctionArgs(col.args, inferArg, [false, null, {dollarSelfRefs}])
|
|
216
216
|
}
|
|
217
217
|
queryElements[as] = getElementForCast(col)
|
|
218
218
|
}
|
|
@@ -287,7 +287,7 @@ function infer(originalQuery, model) {
|
|
|
287
287
|
if (having) walkTokenStream(having)
|
|
288
288
|
if (_.with)
|
|
289
289
|
// consider UPDATE.with
|
|
290
|
-
Object.values(_.with).forEach(val => inferArg(val, queryElements, null, {
|
|
290
|
+
Object.values(_.with).forEach(val => inferArg(val, queryElements, null, { inXpr: true }))
|
|
291
291
|
|
|
292
292
|
return queryElements
|
|
293
293
|
|
|
@@ -299,7 +299,7 @@ function infer(originalQuery, model) {
|
|
|
299
299
|
*
|
|
300
300
|
* @param {array} tokenStream
|
|
301
301
|
*/
|
|
302
|
-
function walkTokenStream(tokenStream,
|
|
302
|
+
function walkTokenStream(tokenStream, inXpr = false) {
|
|
303
303
|
let skipJoins
|
|
304
304
|
const processToken = t => {
|
|
305
305
|
if (t === 'exists') {
|
|
@@ -309,7 +309,7 @@ function infer(originalQuery, model) {
|
|
|
309
309
|
// don't miss an exists within an expression
|
|
310
310
|
t.xpr.forEach(processToken)
|
|
311
311
|
} else {
|
|
312
|
-
inferArg(t, queryElements, null, { inExists: skipJoins,
|
|
312
|
+
inferArg(t, queryElements, null, { inExists: skipJoins, inXpr, inQueryModifier: true })
|
|
313
313
|
skipJoins = false
|
|
314
314
|
}
|
|
315
315
|
}
|
|
@@ -329,11 +329,12 @@ function infer(originalQuery, model) {
|
|
|
329
329
|
const unprocessedColumns = []
|
|
330
330
|
|
|
331
331
|
for (const currentDollarSelfColumn of dollarSelfColumns) {
|
|
332
|
-
const { ref } = currentDollarSelfColumn
|
|
332
|
+
const { ref, inXpr } = currentDollarSelfColumn
|
|
333
333
|
const stepToFind = ref[1]
|
|
334
334
|
|
|
335
335
|
const referencesOtherDollarSelfColumn = dollarSelfColumns.find(
|
|
336
336
|
otherDollarSelfCol =>
|
|
337
|
+
!(stepToFind in queryElements) &&
|
|
337
338
|
otherDollarSelfCol !== currentDollarSelfColumn &&
|
|
338
339
|
(otherDollarSelfCol.as
|
|
339
340
|
? stepToFind === otherDollarSelfCol.as
|
|
@@ -343,7 +344,7 @@ function infer(originalQuery, model) {
|
|
|
343
344
|
if (referencesOtherDollarSelfColumn) {
|
|
344
345
|
unprocessedColumns.push(currentDollarSelfColumn)
|
|
345
346
|
} else {
|
|
346
|
-
handleRef(currentDollarSelfColumn)
|
|
347
|
+
handleRef(currentDollarSelfColumn, inXpr)
|
|
347
348
|
}
|
|
348
349
|
}
|
|
349
350
|
|
|
@@ -351,8 +352,8 @@ function infer(originalQuery, model) {
|
|
|
351
352
|
} while (dollarSelfColumns.length > 0)
|
|
352
353
|
}
|
|
353
354
|
|
|
354
|
-
function handleRef(col) {
|
|
355
|
-
inferArg(col, queryElements)
|
|
355
|
+
function handleRef(col, inXpr) {
|
|
356
|
+
inferArg(col, queryElements, null, { inXpr })
|
|
356
357
|
const { definition } = col.$refLinks[col.$refLinks.length - 1]
|
|
357
358
|
if (col.cast)
|
|
358
359
|
// final type overwritten -> element not visible anymore
|
|
@@ -379,7 +380,7 @@ function infer(originalQuery, model) {
|
|
|
379
380
|
* @param {object} [context={}] - Contextual information for element inference.
|
|
380
381
|
* @param {boolean} [context.inExists=false] - Flag to control the creation of joins for non-association path traversals.
|
|
381
382
|
* 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.
|
|
383
|
+
* @param {boolean} [context.inXpr=false] - Flag to signal whether the element is part of an expression.
|
|
383
384
|
* Used to ignore non-persisted elements.
|
|
384
385
|
* @param {boolean} [context.inNestedProjection=false] - Flag to signal whether the element is part of a nested projection.
|
|
385
386
|
*
|
|
@@ -401,11 +402,11 @@ function infer(originalQuery, model) {
|
|
|
401
402
|
*/
|
|
402
403
|
|
|
403
404
|
function inferArg(arg, queryElements = null, $baseLink = null, context = {}) {
|
|
404
|
-
const { inExists,
|
|
405
|
+
const { inExists, inXpr, inCalcElement, baseColumn, inInfixFilter, inQueryModifier, inFrom, dollarSelfRefs } = context
|
|
405
406
|
if (arg.param || arg.SELECT) return // parameter references are only resolved into values on execution e.g. :val, :1 or ?
|
|
406
407
|
if (arg.args) applyToFunctionArgs(arg.args, inferArg, [null, $baseLink, context])
|
|
407
408
|
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,
|
|
409
|
+
if (arg.xpr) arg.xpr.forEach(token => inferArg(token, queryElements, $baseLink, { ...context, inXpr: true })) // e.g. function in expression
|
|
409
410
|
|
|
410
411
|
if (!arg.ref) {
|
|
411
412
|
if (arg.expand && queryElements) queryElements[arg.as] = resolveExpand(arg)
|
|
@@ -426,6 +427,11 @@ function infer(originalQuery, model) {
|
|
|
426
427
|
firstStepIsSelf = !firstStepIsTableAlias && arg.ref.length > 1 && ['$self', '$projection'].includes(arg.ref[0])
|
|
427
428
|
expandOnTableAlias = arg.ref.length === 1 && arg.ref[0] in sources && (arg.expand || arg.inline)
|
|
428
429
|
}
|
|
430
|
+
if(dollarSelfRefs && firstStepIsSelf) {
|
|
431
|
+
Object.defineProperty(arg, 'inXpr', { value: true, writable: true })
|
|
432
|
+
dollarSelfRefs.push(arg)
|
|
433
|
+
return
|
|
434
|
+
}
|
|
429
435
|
const nameSegments = []
|
|
430
436
|
// if a (segment) of a (structured) foreign key is renamed, we must not include
|
|
431
437
|
// the aliased ref segments into the name of the final foreign key which is e.g. used in
|
|
@@ -564,7 +570,7 @@ function infer(originalQuery, model) {
|
|
|
564
570
|
} else if (token.ref || token.xpr || token.list) {
|
|
565
571
|
inferArg(token, false, arg.$refLinks[i], {
|
|
566
572
|
inExists: skipJoinsForFilter || inExists,
|
|
567
|
-
|
|
573
|
+
inXpr: !!token.xpr,
|
|
568
574
|
inInfixFilter: true,
|
|
569
575
|
inFrom,
|
|
570
576
|
})
|
|
@@ -573,7 +579,7 @@ function infer(originalQuery, model) {
|
|
|
573
579
|
applyToFunctionArgs(token.args, inferArg, [
|
|
574
580
|
false,
|
|
575
581
|
arg.$refLinks[i],
|
|
576
|
-
{ inExists: skipJoinsForFilter || inExists,
|
|
582
|
+
{ inExists: skipJoinsForFilter || inExists, inXpr: true, inInfixFilter: true, inFrom },
|
|
577
583
|
])
|
|
578
584
|
}
|
|
579
585
|
}
|
|
@@ -641,7 +647,7 @@ function infer(originalQuery, model) {
|
|
|
641
647
|
}
|
|
642
648
|
}
|
|
643
649
|
const leafArt = arg.$refLinks[arg.$refLinks.length - 1].definition
|
|
644
|
-
const virtual = (leafArt.virtual || !isPersisted) && !
|
|
650
|
+
const virtual = (leafArt.virtual || !isPersisted) && !inXpr
|
|
645
651
|
// check if we need to merge the column `ref` into the join tree of the query
|
|
646
652
|
if (!inFrom && !inExists && !virtual && !inCalcElement) {
|
|
647
653
|
// for a ref inside an `inline` we need to consider the column `ref` which has the `inline` prop
|
|
@@ -658,7 +664,7 @@ function infer(originalQuery, model) {
|
|
|
658
664
|
}
|
|
659
665
|
|
|
660
666
|
function insertIntoQueryElements() {
|
|
661
|
-
return queryElements && !
|
|
667
|
+
return queryElements && !inXpr && !inInfixFilter && !inQueryModifier
|
|
662
668
|
}
|
|
663
669
|
|
|
664
670
|
/**
|
|
@@ -686,7 +692,7 @@ function infer(originalQuery, model) {
|
|
|
686
692
|
}
|
|
687
693
|
let elements = {}
|
|
688
694
|
inline.forEach(inlineCol => {
|
|
689
|
-
inferArg(inlineCol, null, $leafLink, {
|
|
695
|
+
inferArg(inlineCol, null, $leafLink, { inXpr: true, baseColumn: col })
|
|
690
696
|
if (inlineCol === '*') {
|
|
691
697
|
const wildCardElements = {}
|
|
692
698
|
// either the `.elements´ of the struct or the `.elements` of the assoc target
|
|
@@ -762,7 +768,7 @@ function infer(originalQuery, model) {
|
|
|
762
768
|
if (e === '*') {
|
|
763
769
|
elements = { ...elements, ...$leafLink.definition.elements }
|
|
764
770
|
} else {
|
|
765
|
-
inferArg(e, false, $leafLink, {
|
|
771
|
+
inferArg(e, false, $leafLink, { inXpr: true })
|
|
766
772
|
if (e.expand) elements[e.as || e.flatName] = resolveExpand(e)
|
|
767
773
|
if (e.inline) elements = { ...elements, ...resolveInline(e) }
|
|
768
774
|
else elements[e.as || e.flatName] = e.$refLinks ? e.$refLinks[e.$refLinks.length - 1].definition : e
|
|
@@ -1008,7 +1014,7 @@ function infer(originalQuery, model) {
|
|
|
1008
1014
|
* @param {object} col
|
|
1009
1015
|
* @returns object
|
|
1010
1016
|
*/
|
|
1011
|
-
function getElementForXprOrSubquery(col, queryElements) {
|
|
1017
|
+
function getElementForXprOrSubquery(col, queryElements, dollarSelfRefs) {
|
|
1012
1018
|
const { xpr } = col
|
|
1013
1019
|
let skipJoins = false
|
|
1014
1020
|
xpr?.forEach(token => {
|
|
@@ -1016,7 +1022,7 @@ function infer(originalQuery, model) {
|
|
|
1016
1022
|
// no joins for infix filters along `exists <path>`
|
|
1017
1023
|
skipJoins = true
|
|
1018
1024
|
} else {
|
|
1019
|
-
inferArg(token, queryElements, null, { inExists: skipJoins,
|
|
1025
|
+
inferArg(token, queryElements, null, { inExists: skipJoins, inXpr: true, dollarSelfRefs })
|
|
1020
1026
|
skipJoins = false
|
|
1021
1027
|
}
|
|
1022
1028
|
})
|
package/package.json
CHANGED