@cap-js/db-service 2.6.0 → 2.7.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 +13 -0
- package/lib/cql-functions.js +42 -0
- package/lib/cqn2sql.js +2 -2
- package/lib/cqn4sql.js +26 -19
- package/lib/infer/index.js +55 -14
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@
|
|
|
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.7.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.6.0...db-service-v2.7.0) (2025-11-26)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
* `error` standard function ([#1421](https://github.com/cap-js/cds-dbs/issues/1421)) ([b1b0fca](https://github.com/cap-js/cds-dbs/commit/b1b0fca00387c45ed91280b2df4282be90ea0a6e))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
* LimitedRank with compositions ([#1391](https://github.com/cap-js/cds-dbs/issues/1391)) ([31766cd](https://github.com/cap-js/cds-dbs/commit/31766cd8f9b626d090129b174ac9a04b4d578c21))
|
|
18
|
+
* reject nested projection if duplicated ([#1411](https://github.com/cap-js/cds-dbs/issues/1411)) ([6e924c9](https://github.com/cap-js/cds-dbs/commit/6e924c9942de6e6a4abf7b2c168d4378efcaefa9))
|
|
19
|
+
|
|
7
20
|
## [2.6.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.5.1...db-service-v2.6.0) (2025-10-23)
|
|
8
21
|
|
|
9
22
|
|
package/lib/cql-functions.js
CHANGED
|
@@ -4,6 +4,48 @@ const cds = require('@sap/cds')
|
|
|
4
4
|
|
|
5
5
|
// OData: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_CanonicalFunctions
|
|
6
6
|
const StandardFunctions = {
|
|
7
|
+
/**
|
|
8
|
+
* Generates SQL statement that produces a runtime compatible error object
|
|
9
|
+
* @param {string|object} message - The i18n key or message of the error object
|
|
10
|
+
* @param {Array<xpr>} args - The arguments to apply to the i18n string
|
|
11
|
+
* @param {Array<xpr>} targets - The name of the element that the error is related to
|
|
12
|
+
* @return {string} - SQL statement
|
|
13
|
+
*/
|
|
14
|
+
error: function (message, args, targets) {
|
|
15
|
+
targets = targets && (targets.list || (targets.val || targets.ref) && [targets])
|
|
16
|
+
if (Array.isArray(targets)) targets = targets.map(e => e.ref && { val: e.ref.at(-1) } || e)
|
|
17
|
+
args = args && (args.list || (args.val || args.ref) && [args])
|
|
18
|
+
|
|
19
|
+
return `(${this.SELECT({
|
|
20
|
+
SELECT: {
|
|
21
|
+
expand: 'root',
|
|
22
|
+
columns: [
|
|
23
|
+
{
|
|
24
|
+
__proto__: (message || { val: null }),
|
|
25
|
+
as: 'message',
|
|
26
|
+
},
|
|
27
|
+
args ? {
|
|
28
|
+
func: 'json_array',
|
|
29
|
+
args: args,
|
|
30
|
+
as: 'args',
|
|
31
|
+
element: cds.builtin.types.Map,
|
|
32
|
+
} : { val: null, as: 'args' },
|
|
33
|
+
targets ? {
|
|
34
|
+
func: 'json_array',
|
|
35
|
+
args: targets,
|
|
36
|
+
as: 'targets',
|
|
37
|
+
element: cds.builtin.types.Map,
|
|
38
|
+
} : { val: null, as: 'targets' },
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
elements: {
|
|
42
|
+
message: cds.builtin.types.String,
|
|
43
|
+
args: cds.builtin.types.Map,
|
|
44
|
+
targets: cds.builtin.types.Map,
|
|
45
|
+
}
|
|
46
|
+
})})`
|
|
47
|
+
},
|
|
48
|
+
|
|
7
49
|
/**
|
|
8
50
|
* Generates SQL statement that produces a boolean value indicating whether the search term is contained in the given columns
|
|
9
51
|
* @param {string} ref - The reference object containing column information
|
package/lib/cqn2sql.js
CHANGED
|
@@ -325,7 +325,7 @@ class CQN2SQLRenderer {
|
|
|
325
325
|
DistanceFromRoot: { xpr: [{ ref: ['HIERARCHY_LEVEL'] }, '-', { val: 1, param: false }], as: 'DistanceFromRoot' },
|
|
326
326
|
DrillState: false,
|
|
327
327
|
LimitedDescendantCount: { xpr: [{ ref: ['HIERARCHY_TREE_SIZE'] }, '-', { val: 1, param: false }], as: 'LimitedDescendantCount' },
|
|
328
|
-
LimitedRank: { xpr: [{ func: 'row_number', args: [] }, 'OVER', { xpr: [] }, '-', { val: 1, param: false }], as: 'LimitedRank' }
|
|
328
|
+
LimitedRank: { xpr: [{ func: 'row_number', args: [] }, 'OVER', { xpr: ['ORDER', 'BY', { ref: ['HIERARCHY_RANK'] }, 'ASC'] }, '-', { val: 1, param: false }], as: 'LimitedRank' }
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
const columnsFiltered = columns
|
|
@@ -1326,7 +1326,7 @@ class CQN2SQLRenderer {
|
|
|
1326
1326
|
} else {
|
|
1327
1327
|
cds.error`Invalid arguments provided for function '${func}' (${args})`
|
|
1328
1328
|
}
|
|
1329
|
-
const fn = this.class.Functions[func]?.apply(this, args) || `${func}(${args})`
|
|
1329
|
+
const fn = this.class.Functions[func]?.apply(this, Array.isArray(args) ? args: [args]) || `${func}(${args})`
|
|
1330
1330
|
if (xpr) return `${fn} ${this.xpr({ xpr })}`
|
|
1331
1331
|
return fn
|
|
1332
1332
|
}
|
package/lib/cqn4sql.js
CHANGED
|
@@ -74,6 +74,12 @@ function cqn4sql(originalQuery, model) {
|
|
|
74
74
|
if (inferred.SELECT?.from.ref?.at(-1).id) {
|
|
75
75
|
assignQueryModifiers(inferred.SELECT, inferred.SELECT.from.ref.at(-1))
|
|
76
76
|
}
|
|
77
|
+
if (inferred.DELETE?.from.ref?.at(-1).id) {
|
|
78
|
+
assignQueryModifiers(inferred.DELETE, inferred.DELETE.from.ref.at(-1))
|
|
79
|
+
}
|
|
80
|
+
if (inferred.UPDATE?.entity.ref?.at(-1).id) {
|
|
81
|
+
assignQueryModifiers(inferred.UPDATE, inferred.UPDATE.entity.ref.at(-1))
|
|
82
|
+
}
|
|
77
83
|
inferred = infer(inferred, model)
|
|
78
84
|
const { getLocalizedName, isLocalized, getDefinition } = getModelUtils(model, originalQuery) // TODO: pass model to getModelUtils
|
|
79
85
|
// if the query has custom joins we don't want to transform it
|
|
@@ -1460,24 +1466,12 @@ function cqn4sql(originalQuery, model) {
|
|
|
1460
1466
|
else transformedTokenStream.push({ list: getTransformedTokenStream(list, { $baseLink, prop: 'list' }) })
|
|
1461
1467
|
}
|
|
1462
1468
|
} else if (tokenStream.length === 1 && token.val && $baseLink) {
|
|
1463
|
-
// infix filter - OData variant w/o mentioning key
|
|
1469
|
+
// infix filter - OData variant w/o mentioning key
|
|
1464
1470
|
const def = getDefinition($baseLink.definition.target) || $baseLink.definition
|
|
1465
|
-
const
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
if (v !== backlinkFor($baseLink.definition)?.[0]) {
|
|
1470
|
-
// up__ID already part of inner where exists, no need to add it explicitly here
|
|
1471
|
-
flatKeys.push(...getFlatColumnsFor(v, { tableAlias: $baseLink.alias }))
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
// TODO: improve error message, the current message is generally not true (only for OData shortcut notation)
|
|
1475
|
-
if (flatKeys.length > 1)
|
|
1476
|
-
throw new Error('Filters can only be applied to managed associations which result in a single foreign key')
|
|
1477
|
-
flatKeys.forEach(c => keyValComparisons.push([...[c, '=', token]]))
|
|
1478
|
-
keyValComparisons.forEach((kv, j) =>
|
|
1479
|
-
transformedTokenStream.push(...kv) && keyValComparisons[j + 1] ? transformedTokenStream.push('and') : null,
|
|
1480
|
-
)
|
|
1471
|
+
const flatKeys = getPrimaryKey(def, $baseLink.alias)
|
|
1472
|
+
if (flatKeys.length > 1) // TODO: what about keyless?
|
|
1473
|
+
throw new Error(`Shortcut notation “[${token.val}]” not available for composite primary key of “${def.name}”, write “<key> = ${token.val}” explicitly`)
|
|
1474
|
+
transformedTokenStream.push(...[flatKeys[0], '=', token]);
|
|
1481
1475
|
} else if (token.ref && token.param) {
|
|
1482
1476
|
transformedTokenStream.push({ ...token })
|
|
1483
1477
|
} else if (pseudos.elements[token.ref?.[0]]) {
|
|
@@ -1785,9 +1779,10 @@ function cqn4sql(originalQuery, model) {
|
|
|
1785
1779
|
}
|
|
1786
1780
|
}
|
|
1787
1781
|
|
|
1788
|
-
//
|
|
1789
|
-
if (refReverse[0].where)
|
|
1782
|
+
// OData variant w/o mentioning key
|
|
1783
|
+
if (refReverse[0].where?.length === 1 && refReverse[0].where[0].val) {
|
|
1790
1784
|
filterConditions.push(getTransformedTokenStream(refReverse[0].where,{ $baseLink: $refLinksReverse[0] }))
|
|
1785
|
+
}
|
|
1791
1786
|
|
|
1792
1787
|
if (existingWhere.length > 0) filterConditions.push(existingWhere)
|
|
1793
1788
|
if (whereExistsSubSelects.length > 0) {
|
|
@@ -2283,6 +2278,12 @@ function cqn4sql(originalQuery, model) {
|
|
|
2283
2278
|
if (!node || !node.$refLinks || !node.ref) {
|
|
2284
2279
|
throw new Error('Invalid node')
|
|
2285
2280
|
}
|
|
2281
|
+
if(node.$refLinks[0].$main) {
|
|
2282
|
+
if (node.isJoinRelevant) {
|
|
2283
|
+
return getJoinRelevantAlias(node)
|
|
2284
|
+
}
|
|
2285
|
+
return node.$refLinks[0].alias
|
|
2286
|
+
}
|
|
2286
2287
|
if ($baseLink) {
|
|
2287
2288
|
return getBaseLinkAlias($baseLink)
|
|
2288
2289
|
}
|
|
@@ -2418,6 +2419,12 @@ function assignQueryModifiers(SELECT, modifiers) {
|
|
|
2418
2419
|
} else if (key === 'having') {
|
|
2419
2420
|
if (!SELECT.having) SELECT.having = val
|
|
2420
2421
|
else SELECT.having.push('and', ...val)
|
|
2422
|
+
} else if (key === 'where') {
|
|
2423
|
+
// ignore OData shortcut variant: `… bookshop.Orders:items[2]`
|
|
2424
|
+
if(!val || val.length === 1 && val[0].val) continue
|
|
2425
|
+
if (!SELECT.where) SELECT.where = val
|
|
2426
|
+
// infix filter comes first in resulting where
|
|
2427
|
+
else SELECT.where = [...(hasLogicalOr(val) ? [asXpr(val)] : val), 'and', ...(hasLogicalOr(SELECT.where) ? [asXpr(SELECT.where)] : SELECT.where)]
|
|
2421
2428
|
}
|
|
2422
2429
|
}
|
|
2423
2430
|
}
|
package/lib/infer/index.js
CHANGED
|
@@ -47,7 +47,6 @@ function infer(originalQuery, model) {
|
|
|
47
47
|
let $combinedElements
|
|
48
48
|
|
|
49
49
|
const sources = inferTarget(_.into || _.from || _.entity, {}) // IMPORTANT: _.into has to go before _.from for INSERT.into().from(SELECT)
|
|
50
|
-
const joinTree = new JoinTree(sources)
|
|
51
50
|
const aliases = Object.keys(sources)
|
|
52
51
|
const target = aliases.length === 1 ? getDefinitionFromSources(sources, aliases[0]) : originalQuery
|
|
53
52
|
Object.defineProperties(inferred, {
|
|
@@ -72,7 +71,6 @@ function infer(originalQuery, model) {
|
|
|
72
71
|
Object.defineProperties(inferred, {
|
|
73
72
|
$combinedElements: { value: $combinedElements, writable: true, configurable: true },
|
|
74
73
|
elements: { value: elements, writable: true, configurable: true },
|
|
75
|
-
joinTree: { value: joinTree, writable: true, configurable: true }, // REVISIT: eliminate
|
|
76
74
|
})
|
|
77
75
|
// also enrich original query -> writable because it may be inferred again
|
|
78
76
|
defineProperty(originalQuery, 'elements', elements)
|
|
@@ -96,6 +94,10 @@ function infer(originalQuery, model) {
|
|
|
96
94
|
*/
|
|
97
95
|
function inferTarget(from, querySources, useTechnicalAlias = true) {
|
|
98
96
|
const { ref } = from
|
|
97
|
+
// Given a from clause `Root:parent[$main.name = name].parent as Foo`
|
|
98
|
+
// we need to first resolve until to the last step of the from.ref
|
|
99
|
+
// before we can replace $main with `Foo`
|
|
100
|
+
const $mainLazyResolve = [] // TODO: remove and replace with real alias breakout
|
|
99
101
|
if (ref) {
|
|
100
102
|
const { id, args } = ref[0]
|
|
101
103
|
const first = id || ref[0]
|
|
@@ -111,7 +113,7 @@ function infer(originalQuery, model) {
|
|
|
111
113
|
if (target.kind !== 'entity' && !target.isAssociation)
|
|
112
114
|
throw new Error('Query source must be a an entity or an association')
|
|
113
115
|
|
|
114
|
-
inferArg(from, null, null, { inFrom: true })
|
|
116
|
+
inferArg(from, null, null, { inFrom: true, $mainLazyResolve })
|
|
115
117
|
const alias =
|
|
116
118
|
from.uniqueSubqueryAlias ||
|
|
117
119
|
from.as ||
|
|
@@ -137,6 +139,10 @@ function infer(originalQuery, model) {
|
|
|
137
139
|
} else if (from.SET) {
|
|
138
140
|
infer(from, model)
|
|
139
141
|
}
|
|
142
|
+
|
|
143
|
+
const joinTree = new JoinTree(querySources)
|
|
144
|
+
Object.defineProperty( inferred, 'joinTree', { value: joinTree, writable: true, configurable: true } )
|
|
145
|
+
for(const lazyRef of $mainLazyResolve) inferArg(lazyRef)
|
|
140
146
|
return querySources
|
|
141
147
|
}
|
|
142
148
|
|
|
@@ -201,7 +207,7 @@ function infer(originalQuery, model) {
|
|
|
201
207
|
} else if (col.val !== undefined || col.xpr || col.SELECT || col.func || col.param) {
|
|
202
208
|
const as = col.as || col.func || col.val
|
|
203
209
|
if (as === undefined) cds.error`Expecting expression to have an alias name`
|
|
204
|
-
if (queryElements[as])
|
|
210
|
+
if (queryElements[as]) rejectDuplicatedElement(as)
|
|
205
211
|
if (col.xpr || col.SELECT) {
|
|
206
212
|
queryElements[as] = getElementForXprOrSubquery(col, queryElements, dollarSelfRefs)
|
|
207
213
|
}
|
|
@@ -422,11 +428,13 @@ function infer(originalQuery, model) {
|
|
|
422
428
|
// we must ignore the element from the queries elements
|
|
423
429
|
let isPersisted = true
|
|
424
430
|
let firstStepIsTableAlias, firstStepIsSelf, expandOnTableAlias
|
|
425
|
-
|
|
431
|
+
const firstStepIsDollarMain = arg.ref.length > 1 && arg.ref[0] === '$main'
|
|
432
|
+
if (!inFrom && !firstStepIsDollarMain) {
|
|
426
433
|
firstStepIsTableAlias = arg.ref.length > 1 && arg.ref[0] in sources
|
|
427
434
|
firstStepIsSelf = !firstStepIsTableAlias && arg.ref.length > 1 && ['$self', '$projection'].includes(arg.ref[0])
|
|
428
435
|
expandOnTableAlias = arg.ref.length === 1 && arg.ref[0] in sources && (arg.expand || arg.inline)
|
|
429
436
|
}
|
|
437
|
+
|
|
430
438
|
if (dollarSelfRefs && firstStepIsSelf) {
|
|
431
439
|
defineProperty(arg, 'inXpr', true)
|
|
432
440
|
dollarSelfRefs.push(arg)
|
|
@@ -438,10 +446,20 @@ function infer(originalQuery, model) {
|
|
|
438
446
|
// on conditions of joins
|
|
439
447
|
const skipAliasedFkSegmentsOfNameStack = []
|
|
440
448
|
let pseudoPath = false
|
|
441
|
-
arg.ref.
|
|
449
|
+
for(let i = 0; i < arg.ref.length; i++) {
|
|
450
|
+
const step = arg.ref[i]
|
|
442
451
|
const id = step.id || step
|
|
443
452
|
if (i === 0) {
|
|
444
|
-
if
|
|
453
|
+
if(firstStepIsDollarMain) {
|
|
454
|
+
if(inFrom) { // we need to resolve the full from clause first
|
|
455
|
+
context.$mainLazyResolve.push(arg)
|
|
456
|
+
return; // this will be done once the from clause is fully resolved
|
|
457
|
+
} else {
|
|
458
|
+
// replace $main with the alias of the outermost query
|
|
459
|
+
const mainAlias = getMainAlias(inferred)
|
|
460
|
+
arg.$refLinks.push(Object.assign(mainAlias, {$main: true}))
|
|
461
|
+
}
|
|
462
|
+
} else if (id in pseudos.elements) {
|
|
445
463
|
// pseudo path
|
|
446
464
|
arg.$refLinks.push({ definition: pseudos.elements[id], target: pseudos })
|
|
447
465
|
pseudoPath = true // only first path step must be well defined
|
|
@@ -569,6 +587,7 @@ function infer(originalQuery, model) {
|
|
|
569
587
|
skipJoinsForFilter = true
|
|
570
588
|
} else if (token.ref || token.xpr || token.list) {
|
|
571
589
|
inferArg(token, false, arg.$refLinks[i], {
|
|
590
|
+
...context,
|
|
572
591
|
inExists: skipJoinsForFilter || inExists,
|
|
573
592
|
inXpr: !!token.xpr,
|
|
574
593
|
inInfixFilter: true,
|
|
@@ -586,7 +605,8 @@ function infer(originalQuery, model) {
|
|
|
586
605
|
})
|
|
587
606
|
}
|
|
588
607
|
|
|
589
|
-
arg.$refLinks[i]
|
|
608
|
+
if(!arg.$refLinks[i].$main)
|
|
609
|
+
arg.$refLinks[i].alias = !arg.ref[i + 1] && arg.as ? arg.as : id.split('.').pop()
|
|
590
610
|
if (hasOwnSkip(getDefinition(arg.$refLinks[i].definition.target))) isPersisted = false
|
|
591
611
|
if (!arg.ref[i + 1]) {
|
|
592
612
|
const flatName = nameSegments.join('_')
|
|
@@ -602,9 +622,17 @@ function infer(originalQuery, model) {
|
|
|
602
622
|
if (arg.$refLinks.length === 1 && arg.$refLinks[0].definition.kind === 'entity')
|
|
603
623
|
elementName = arg.$refLinks[0].alias
|
|
604
624
|
else elementName = arg.as || flatName
|
|
605
|
-
|
|
625
|
+
|
|
626
|
+
if (queryElements) {
|
|
627
|
+
if (queryElements[elementName] !== undefined)
|
|
628
|
+
rejectDuplicatedElement(elementName)
|
|
629
|
+
queryElements[elementName] = elements
|
|
630
|
+
}
|
|
606
631
|
} else if (arg.inline && queryElements) {
|
|
607
632
|
const elements = resolveInline(arg)
|
|
633
|
+
for (const elName in elements) {
|
|
634
|
+
if (queryElements[elName] !== undefined) rejectDuplicatedElement(elName)
|
|
635
|
+
}
|
|
608
636
|
Object.assign(queryElements, elements)
|
|
609
637
|
} else {
|
|
610
638
|
// shortcut for `ref: ['$user']` -> `ref: ['$user', 'id']`
|
|
@@ -626,14 +654,13 @@ function infer(originalQuery, model) {
|
|
|
626
654
|
else elementName = flatName
|
|
627
655
|
}
|
|
628
656
|
if (queryElements[elementName] !== undefined)
|
|
629
|
-
|
|
657
|
+
rejectDuplicatedElement(elementName)
|
|
630
658
|
const element = getCopyWithAnnos(arg, leafArt)
|
|
631
659
|
queryElements[elementName] = element
|
|
632
660
|
}
|
|
633
661
|
}
|
|
634
662
|
}
|
|
635
|
-
}
|
|
636
|
-
|
|
663
|
+
}
|
|
637
664
|
// we need inner joins for the path expressions inside filter expressions after exists predicate
|
|
638
665
|
if ($baseLink?.pathExpressionInsideFilter) defineProperty(arg, 'join', 'inner')
|
|
639
666
|
|
|
@@ -656,7 +683,9 @@ function infer(originalQuery, model) {
|
|
|
656
683
|
: arg
|
|
657
684
|
if (isColumnJoinRelevant(colWithBase)) {
|
|
658
685
|
defineProperty(arg, 'isJoinRelevant', true)
|
|
659
|
-
|
|
686
|
+
// join resolved in outer query
|
|
687
|
+
if(!(arg.$refLinks[0].$main && originalQuery.outerQueries))
|
|
688
|
+
inferred.joinTree.mergeColumn(colWithBase, originalQuery.outerQueries)
|
|
660
689
|
}
|
|
661
690
|
}
|
|
662
691
|
if (isCalculatedOnRead(leafArt)) {
|
|
@@ -807,6 +836,10 @@ function infer(originalQuery, model) {
|
|
|
807
836
|
throw new Error(err.join(','))
|
|
808
837
|
}
|
|
809
838
|
}
|
|
839
|
+
function rejectDuplicatedElement(elementName) {
|
|
840
|
+
throw new Error(`Duplicate definition of element “${elementName}”`)
|
|
841
|
+
}
|
|
842
|
+
|
|
810
843
|
function linkCalculatedElement(column, baseLink, baseColumn, context = {}) {
|
|
811
844
|
const calcElement = column.$refLinks?.[column.$refLinks.length - 1].definition || column
|
|
812
845
|
if (alreadySeenCalcElements.has(calcElement)) return
|
|
@@ -901,7 +934,7 @@ function infer(originalQuery, model) {
|
|
|
901
934
|
if (calcElementIsJoinRelevant) {
|
|
902
935
|
if (!calcElement.value.isJoinRelevant)
|
|
903
936
|
defineProperty(step, 'isJoinRelevant',true)
|
|
904
|
-
joinTree.mergeColumn(p, originalQuery.outerQueries)
|
|
937
|
+
inferred.joinTree.mergeColumn(p, originalQuery.outerQueries)
|
|
905
938
|
} else {
|
|
906
939
|
// we need to explicitly set the value to false in this case,
|
|
907
940
|
// e.g. `SELECT from booksCalc.Books { ID, author.{name }, author {name } }`
|
|
@@ -1160,4 +1193,12 @@ function applyToFunctionArgs(funcArgs, cb, cbArgs) {
|
|
|
1160
1193
|
else if (typeof funcArgs === 'object') Object.keys(funcArgs).forEach(prop => cb(funcArgs[prop], ...cbArgs))
|
|
1161
1194
|
}
|
|
1162
1195
|
|
|
1196
|
+
function getMainAlias (query) {
|
|
1197
|
+
let mainAlias
|
|
1198
|
+
if (query.outerQueries) mainAlias = query.outerQueries[0].SELECT?.from.$refLinks.at(-1)
|
|
1199
|
+
else mainAlias = query.SELECT?.from.$refLinks.at(-1)
|
|
1200
|
+
if(!mainAlias) throw new Error('Cannot determine main query source for $main, please report this')
|
|
1201
|
+
return mainAlias
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1163
1204
|
module.exports = infer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/db-service",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.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": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"generic-pool": "^3.9.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@sap/cds": ">=9"
|
|
30
|
+
"@sap/cds": ">=9.4.5"
|
|
31
31
|
},
|
|
32
32
|
"license": "Apache-2.0"
|
|
33
33
|
}
|