@cap-js/db-service 1.0.0 → 1.0.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 +9 -0
- package/lib/cql-functions.js +1 -0
- package/lib/cqn4sql.js +112 -21
- package/lib/infer/index.js +77 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
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
|
+
## Version 1.0.1 - 2023-07-03
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Paths addressing a column of the query via `$self.<column>` in `group by` / `order by`, `having` or `where`
|
|
12
|
+
are now correctly substituted.
|
|
13
|
+
- Mapping for OData `average` function to ANSI SQL compliant `avg` function.
|
|
14
|
+
|
|
15
|
+
|
|
7
16
|
## Version 1.0.0 - 2023-06-23
|
|
8
17
|
|
|
9
18
|
- Initial Release
|
package/lib/cql-functions.js
CHANGED
|
@@ -3,6 +3,7 @@ const StandardFunctions = {
|
|
|
3
3
|
|
|
4
4
|
// String and Collection Functions
|
|
5
5
|
// length : (x) => `length(${x})`,
|
|
6
|
+
average: x => `avg(${x})`,
|
|
6
7
|
search: function (ref, arg) {
|
|
7
8
|
if (!('val' in arg)) throw `SQLite only supports single value arguments for $search`
|
|
8
9
|
const refs = ref.list || [ref],
|
package/lib/cqn4sql.js
CHANGED
|
@@ -301,15 +301,24 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
301
301
|
*/
|
|
302
302
|
function getTransformedColumns(columns) {
|
|
303
303
|
const transformedColumns = []
|
|
304
|
-
|
|
305
304
|
for (let i = 0; i < columns.length; i++) {
|
|
306
305
|
const col = columns[i]
|
|
307
306
|
|
|
308
307
|
if (col.expand) {
|
|
308
|
+
if (col.ref?.length > 1 && col.ref[0] === '$self' && !col.$refLinks[0].definition.kind) {
|
|
309
|
+
const dollarSelfReplacement = calculateDollarSelfColumn(col)
|
|
310
|
+
transformedColumns.push(...getTransformedColumns([dollarSelfReplacement]))
|
|
311
|
+
continue
|
|
312
|
+
}
|
|
309
313
|
handleExpand(col)
|
|
310
314
|
} else if (col.inline) {
|
|
311
315
|
handleInline(col)
|
|
312
316
|
} else if (col.ref) {
|
|
317
|
+
if (col.ref.length > 1 && col.ref[0] === '$self' && !col.$refLinks[0].definition.kind) {
|
|
318
|
+
const dollarSelfReplacement = calculateDollarSelfColumn(col)
|
|
319
|
+
transformedColumns.push(...getTransformedColumns([dollarSelfReplacement]))
|
|
320
|
+
continue
|
|
321
|
+
}
|
|
313
322
|
handleRef(col)
|
|
314
323
|
} else if (col === '*') {
|
|
315
324
|
handleWildcard(columns)
|
|
@@ -391,7 +400,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
391
400
|
if (replaceWith === -1) transformedColumns.push(transformedColumn)
|
|
392
401
|
else transformedColumns.splice(replaceWith, 1, transformedColumn)
|
|
393
402
|
|
|
394
|
-
|
|
403
|
+
setElementOnColumns(transformedColumn, originalQuery.elements[col.as])
|
|
395
404
|
}
|
|
396
405
|
|
|
397
406
|
function getTransformedColumn(col) {
|
|
@@ -424,6 +433,64 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
424
433
|
}
|
|
425
434
|
}
|
|
426
435
|
|
|
436
|
+
/**
|
|
437
|
+
* This function resolves a `ref` starting with a `$self`.
|
|
438
|
+
* Such a path targets another element of the query by it's implicit, or explicit alias.
|
|
439
|
+
*
|
|
440
|
+
* A `$self` reference may also target another `$self` path. In this case, this function
|
|
441
|
+
* recursively resolves the tail of the `$self` references (`$selfPath.ref.slice(2)`) onto it's
|
|
442
|
+
* new base.
|
|
443
|
+
*
|
|
444
|
+
* @param {object} col with a ref like `[ '$self', <target column>, <optional further path navigation> ]`
|
|
445
|
+
* @param {boolean} omitAlias if we replace a $self reference in an aggregation or a token stream, we must not add an "as" to the result
|
|
446
|
+
*/
|
|
447
|
+
function calculateDollarSelfColumn(col, omitAlias = false) {
|
|
448
|
+
const dummyColumn = buildDummyColumnForDollarSelf({ ...col }, col.$refLinks)
|
|
449
|
+
|
|
450
|
+
return dummyColumn
|
|
451
|
+
|
|
452
|
+
function buildDummyColumnForDollarSelf(dollarSelfColumn, $refLinks) {
|
|
453
|
+
const { ref, as } = dollarSelfColumn
|
|
454
|
+
const stepToFind = ref[1]
|
|
455
|
+
let referencedColumn = inferred.SELECT.columns.find(
|
|
456
|
+
otherColumn =>
|
|
457
|
+
otherColumn !== dollarSelfColumn &&
|
|
458
|
+
(otherColumn.as
|
|
459
|
+
? stepToFind === otherColumn.as
|
|
460
|
+
: stepToFind === otherColumn.ref?.[otherColumn.ref.length - 1]),
|
|
461
|
+
)
|
|
462
|
+
if (referencedColumn.ref?.[0] === '$self') {
|
|
463
|
+
referencedColumn = buildDummyColumnForDollarSelf({ ...referencedColumn }, referencedColumn.$refLinks)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (referencedColumn.ref) {
|
|
467
|
+
dollarSelfColumn.ref = [...referencedColumn.ref, ...dollarSelfColumn.ref.slice(2)]
|
|
468
|
+
Object.defineProperties(dollarSelfColumn, {
|
|
469
|
+
flatName: {
|
|
470
|
+
value: dollarSelfColumn.ref.join('_'),
|
|
471
|
+
},
|
|
472
|
+
isJoinRelevant: {
|
|
473
|
+
value: referencedColumn.isJoinRelevant,
|
|
474
|
+
},
|
|
475
|
+
$refLinks: {
|
|
476
|
+
value: [...referencedColumn.$refLinks, ...$refLinks.slice(2)],
|
|
477
|
+
},
|
|
478
|
+
})
|
|
479
|
+
} else {
|
|
480
|
+
// target column is `val` or `xpr`, destructure and throw away the ref with the $self
|
|
481
|
+
// eslint-disable-next-line no-unused-vars
|
|
482
|
+
const { xpr, val, ref, as: _as, ...rest } = referencedColumn
|
|
483
|
+
if (xpr) rest.xpr = xpr
|
|
484
|
+
else rest.val = val
|
|
485
|
+
dollarSelfColumn = { ...rest } // reassign dummyColumn without 'ref'
|
|
486
|
+
if (!omitAlias) dollarSelfColumn.as = as
|
|
487
|
+
}
|
|
488
|
+
return dollarSelfColumn.ref?.[0] === '$self'
|
|
489
|
+
? buildDummyColumnForDollarSelf({ ...dollarSelfColumn }, $refLinks)
|
|
490
|
+
: dollarSelfColumn
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
427
494
|
/**
|
|
428
495
|
* Calculates the columns for a nested projection on a structure.
|
|
429
496
|
*
|
|
@@ -622,7 +689,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
622
689
|
if (isLocalized(inferred.target)) subquery.SELECT.localized = true
|
|
623
690
|
const expanded = transformSubquery(subquery)
|
|
624
691
|
const correlated = _correlate({ ...expanded, as: columnAlias }, outerAlias)
|
|
625
|
-
Object.defineProperty(correlated, 'elements', { value: subquery.elements })
|
|
692
|
+
Object.defineProperty(correlated, 'elements', { value: subquery.elements, writable: true })
|
|
626
693
|
return correlated
|
|
627
694
|
|
|
628
695
|
function _correlate(subq, outer) {
|
|
@@ -671,6 +738,11 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
671
738
|
res.push({ ...col })
|
|
672
739
|
} else if (col.ref) {
|
|
673
740
|
if (col.$refLinks.some(link => link.definition._target?.['@cds.persistence.skip'] === true)) continue
|
|
741
|
+
if (col.ref.length > 1 && col.ref[0] === '$self' && !col.$refLinks[0].definition.kind) {
|
|
742
|
+
const dollarSelfReplacement = calculateDollarSelfColumn(col)
|
|
743
|
+
res.push(...getTransformedOrderByGroupBy([dollarSelfReplacement], inOrderBy))
|
|
744
|
+
continue
|
|
745
|
+
}
|
|
674
746
|
const { target } = col.$refLinks[0]
|
|
675
747
|
const tableAlias = target.SELECT ? null : getQuerySourceName(col) // do not prepend TA if orderBy column addresses element of query
|
|
676
748
|
const leaf = col.$refLinks[col.$refLinks.length - 1].definition
|
|
@@ -903,8 +975,9 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
903
975
|
if (columnAlias) flatColumn = { ref: [fkBaseName], as: `${columnAlias}_${fk.ref.join('_')}` }
|
|
904
976
|
else flatColumn = { ref: [fkBaseName] }
|
|
905
977
|
if (tableAlias) flatColumn.ref.unshift(tableAlias)
|
|
906
|
-
|
|
907
|
-
|
|
978
|
+
|
|
979
|
+
setElementOnColumns(flatColumn, fkElement)
|
|
980
|
+
Object.defineProperty(flatColumn, '_csnPath', { value: csnPath, writable: true })
|
|
908
981
|
flatColumns.push(flatColumn)
|
|
909
982
|
}
|
|
910
983
|
})
|
|
@@ -934,8 +1007,8 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
934
1007
|
}
|
|
935
1008
|
if (column.sort) flatRef.sort = column.sort
|
|
936
1009
|
if (columnAlias) flatRef.as = columnAlias
|
|
937
|
-
|
|
938
|
-
Object.defineProperty(flatRef, '_csnPath', { value: csnPath })
|
|
1010
|
+
setElementOnColumns(flatRef, element)
|
|
1011
|
+
Object.defineProperty(flatRef, '_csnPath', { value: csnPath, writable: true })
|
|
939
1012
|
return [flatRef]
|
|
940
1013
|
|
|
941
1014
|
function getReplacement(from) {
|
|
@@ -962,11 +1035,11 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
962
1035
|
* @returns {object[]} - The transformed token stream.
|
|
963
1036
|
*/
|
|
964
1037
|
function getTransformedTokenStream(tokenStream, $baseLink = null) {
|
|
965
|
-
const
|
|
1038
|
+
const transformedTokenStream = []
|
|
966
1039
|
for (let i = 0; i < tokenStream.length; i++) {
|
|
967
1040
|
const token = tokenStream[i]
|
|
968
1041
|
if (token === 'exists') {
|
|
969
|
-
|
|
1042
|
+
transformedTokenStream.push(token)
|
|
970
1043
|
const whereExistsSubSelects = []
|
|
971
1044
|
const { ref, $refLinks } = tokenStream[i + 1]
|
|
972
1045
|
if (!ref) continue
|
|
@@ -1008,7 +1081,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1008
1081
|
}
|
|
1009
1082
|
|
|
1010
1083
|
const whereExists = { SELECT: whereExistsSubqueries(whereExistsSubSelects) }
|
|
1011
|
-
|
|
1084
|
+
transformedTokenStream[i + 1] = whereExists
|
|
1012
1085
|
// skip newly created subquery from being iterated
|
|
1013
1086
|
i += 1
|
|
1014
1087
|
} else if (token.list) {
|
|
@@ -1021,14 +1094,14 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1021
1094
|
typeof precedingTwoTokens[1] === 'string' ? precedingTwoTokens[1].toLowerCase() : ''
|
|
1022
1095
|
|
|
1023
1096
|
if (firstPrecedingToken === 'not') {
|
|
1024
|
-
|
|
1097
|
+
transformedTokenStream.splice(i - 2, 2, 'is', 'not', 'null')
|
|
1025
1098
|
} else if (secondPrecedingToken === 'in') {
|
|
1026
|
-
|
|
1099
|
+
transformedTokenStream.splice(i - 1, 1, '=', { val: null })
|
|
1027
1100
|
} else {
|
|
1028
|
-
|
|
1101
|
+
transformedTokenStream.push({ list: [] })
|
|
1029
1102
|
}
|
|
1030
1103
|
} else {
|
|
1031
|
-
|
|
1104
|
+
transformedTokenStream.push({ list: getTransformedTokenStream(token.list) })
|
|
1032
1105
|
}
|
|
1033
1106
|
} else if (tokenStream.length === 1 && token.val && $baseLink) {
|
|
1034
1107
|
// infix filter - OData variant w/o mentioning key --> flatten out and compare each leaf to token.val
|
|
@@ -1046,12 +1119,12 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1046
1119
|
throw new Error('Filters can only be applied to managed associations which result in a single foreign key')
|
|
1047
1120
|
flatKeys.forEach(c => keyValComparisons.push([...[c, '=', token]]))
|
|
1048
1121
|
keyValComparisons.forEach((kv, j) =>
|
|
1049
|
-
|
|
1122
|
+
transformedTokenStream.push(...kv) && keyValComparisons[j + 1] ? transformedTokenStream.push('and') : null,
|
|
1050
1123
|
)
|
|
1051
1124
|
} else if (token.ref && token.param) {
|
|
1052
|
-
|
|
1125
|
+
transformedTokenStream.push({ ...token })
|
|
1053
1126
|
} else if (pseudos.elements[token.ref?.[0]]) {
|
|
1054
|
-
|
|
1127
|
+
transformedTokenStream.push({ ...token })
|
|
1055
1128
|
} else {
|
|
1056
1129
|
// expand `struct = null | struct2`
|
|
1057
1130
|
const { definition } = token.$refLinks?.[token.$refLinks.length - 1] || {}
|
|
@@ -1075,7 +1148,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1075
1148
|
cds.error(`The operator "${next}" is not supported for structure comparison`)
|
|
1076
1149
|
const newTokens = expandComparison(token, ops, rhs)
|
|
1077
1150
|
const needXpr = Boolean(tokenStream[i - 1] || tokenStream[indexRhs + 1])
|
|
1078
|
-
|
|
1151
|
+
transformedTokenStream.push(...(needXpr ? [asXpr(newTokens)] : newTokens))
|
|
1079
1152
|
i = indexRhs // jump to next relevant index
|
|
1080
1153
|
}
|
|
1081
1154
|
} else {
|
|
@@ -1084,6 +1157,11 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1084
1157
|
|
|
1085
1158
|
let result = is_regexp(token?.val) ? token : copy(token) // REVISIT: too expensive! //
|
|
1086
1159
|
if (token.ref) {
|
|
1160
|
+
if (token.ref.length > 1 && token.ref[0] === '$self' && !token.$refLinks[0].definition.kind) {
|
|
1161
|
+
const dollarSelfReplacement = [calculateDollarSelfColumn(token, true)]
|
|
1162
|
+
transformedTokenStream.push(...getTransformedTokenStream(dollarSelfReplacement))
|
|
1163
|
+
continue
|
|
1164
|
+
}
|
|
1087
1165
|
const tableAlias = getQuerySourceName(token, $baseLink)
|
|
1088
1166
|
if (!$baseLink && token.isJoinRelevant) {
|
|
1089
1167
|
result.ref = [tableAlias, getFullName(token.$refLinks[token.$refLinks.length - 1].definition)]
|
|
@@ -1106,11 +1184,11 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1106
1184
|
})
|
|
1107
1185
|
}
|
|
1108
1186
|
|
|
1109
|
-
|
|
1187
|
+
transformedTokenStream.push(result)
|
|
1110
1188
|
}
|
|
1111
1189
|
}
|
|
1112
1190
|
}
|
|
1113
|
-
return
|
|
1191
|
+
return transformedTokenStream
|
|
1114
1192
|
}
|
|
1115
1193
|
|
|
1116
1194
|
/**
|
|
@@ -1797,7 +1875,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1797
1875
|
}
|
|
1798
1876
|
|
|
1799
1877
|
function getCombinedElementAlias(node) {
|
|
1800
|
-
return getLastStringSegment(inferred.$combinedElements[node.ref[0].id || node.ref[0]][0].index)
|
|
1878
|
+
return getLastStringSegment(inferred.$combinedElements[node.ref[0].id || node.ref[0]]?.[0].index)
|
|
1801
1879
|
}
|
|
1802
1880
|
}
|
|
1803
1881
|
}
|
|
@@ -1842,5 +1920,18 @@ function getLastStringSegment(str) {
|
|
|
1842
1920
|
return index != -1 ? str.substring(index + 1) : str
|
|
1843
1921
|
}
|
|
1844
1922
|
|
|
1923
|
+
/**
|
|
1924
|
+
* Assigns the given `element` as non-enumerable property 'element' onto `col`.
|
|
1925
|
+
*
|
|
1926
|
+
* @param {object} col
|
|
1927
|
+
* @param {csn.Element} element
|
|
1928
|
+
*/
|
|
1929
|
+
function setElementOnColumns(col, element) {
|
|
1930
|
+
Object.defineProperty(col, 'element', {
|
|
1931
|
+
value: element,
|
|
1932
|
+
writable: true,
|
|
1933
|
+
})
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1845
1936
|
const idOnly = ref => ref.id || ref
|
|
1846
1937
|
const is_regexp = x => x?.constructor?.name === 'RegExp' // NOTE: x instanceof RegExp doesn't work in repl
|
package/lib/infer/index.js
CHANGED
|
@@ -275,7 +275,7 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
275
275
|
inferElementsFromWildCard(aliases)
|
|
276
276
|
} else {
|
|
277
277
|
let wildcardSelect = false
|
|
278
|
-
const
|
|
278
|
+
const dollarSelfRefs = []
|
|
279
279
|
columns.forEach(col => {
|
|
280
280
|
if (col === '*') {
|
|
281
281
|
wildcardSelect = true
|
|
@@ -294,24 +294,24 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
294
294
|
}
|
|
295
295
|
setElementOnColumns(col, queryElements[as])
|
|
296
296
|
} else if (col.ref) {
|
|
297
|
-
|
|
297
|
+
const firstStepIsTableAlias =
|
|
298
|
+
(col.ref.length > 1 && col.ref[0] in sources) ||
|
|
299
|
+
// nested projection on table alias
|
|
300
|
+
(col.ref.length === 1 && col.ref[0] in sources && col.inline)
|
|
301
|
+
const firstStepIsSelf =
|
|
302
|
+
!firstStepIsTableAlias && col.ref.length > 1 && ['$self', '$projection'].includes(col.ref[0])
|
|
303
|
+
// we must handle $self references after the query elements have been calculated
|
|
304
|
+
if (firstStepIsSelf) dollarSelfRefs.push(col)
|
|
305
|
+
else handleRef(col)
|
|
298
306
|
} else if (col.expand) {
|
|
299
307
|
inferQueryElement(col)
|
|
300
308
|
} else {
|
|
301
309
|
throw cds.error`Not supported: ${JSON.stringify(col)}`
|
|
302
310
|
}
|
|
303
311
|
})
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (col.cast)
|
|
308
|
-
// final type overwritten -> element not visible anymore
|
|
309
|
-
setElementOnColumns(col, getElementForCast(col))
|
|
310
|
-
else if ((col.ref.length === 1) & (col.ref[0] === '$user'))
|
|
311
|
-
// shortcut to $user.id
|
|
312
|
-
setElementOnColumns(col, queryElements[col.as || '$user'])
|
|
313
|
-
else setElementOnColumns(col, definition)
|
|
314
|
-
})
|
|
312
|
+
|
|
313
|
+
if (dollarSelfRefs.length) inferDollarSelfRefs(dollarSelfRefs)
|
|
314
|
+
|
|
315
315
|
if (wildcardSelect) inferElementsFromWildCard(aliases)
|
|
316
316
|
}
|
|
317
317
|
if (orderBy) {
|
|
@@ -359,6 +359,54 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
359
359
|
|
|
360
360
|
return queryElements
|
|
361
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Processes references starting with `$self`, which are intended to target other query elements.
|
|
364
|
+
* These `$self` paths must be handled after processing the "regular" columns since they are dependent on other query elements.
|
|
365
|
+
*
|
|
366
|
+
* This function checks for `$self` references that may target other `$self` columns, and delays their processing.
|
|
367
|
+
* `$self` references not targeting other `$self` references are handled by the generic `handleRef` function immediately.
|
|
368
|
+
*
|
|
369
|
+
* @param {array} dollarSelfColumns - An array of column objects containing `$self` references.
|
|
370
|
+
*/
|
|
371
|
+
function inferDollarSelfRefs(dollarSelfColumns) {
|
|
372
|
+
do {
|
|
373
|
+
const unprocessedColumns = []
|
|
374
|
+
|
|
375
|
+
for (const currentDollarSelfColumn of dollarSelfColumns) {
|
|
376
|
+
const { ref } = currentDollarSelfColumn
|
|
377
|
+
const stepToFind = ref[1]
|
|
378
|
+
|
|
379
|
+
const referencesOtherDollarSelfColumn = dollarSelfColumns.find(
|
|
380
|
+
otherDollarSelfCol =>
|
|
381
|
+
otherDollarSelfCol !== currentDollarSelfColumn &&
|
|
382
|
+
(otherDollarSelfCol.as
|
|
383
|
+
? stepToFind === otherDollarSelfCol.as
|
|
384
|
+
: stepToFind === otherDollarSelfCol.ref?.[otherDollarSelfCol.ref.length - 1]),
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
if (referencesOtherDollarSelfColumn) {
|
|
388
|
+
unprocessedColumns.push(currentDollarSelfColumn)
|
|
389
|
+
} else {
|
|
390
|
+
handleRef(currentDollarSelfColumn)
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
dollarSelfColumns = unprocessedColumns
|
|
395
|
+
} while (dollarSelfColumns.length > 0)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function handleRef(col) {
|
|
399
|
+
inferQueryElement(col)
|
|
400
|
+
const { definition } = col.$refLinks[col.$refLinks.length - 1]
|
|
401
|
+
if (col.cast)
|
|
402
|
+
// final type overwritten -> element not visible anymore
|
|
403
|
+
setElementOnColumns(col, getElementForCast(col))
|
|
404
|
+
else if ((col.ref.length === 1) & (col.ref[0] === '$user'))
|
|
405
|
+
// shortcut to $user.id
|
|
406
|
+
setElementOnColumns(col, queryElements[col.as || '$user'])
|
|
407
|
+
else setElementOnColumns(col, definition)
|
|
408
|
+
}
|
|
409
|
+
|
|
362
410
|
/**
|
|
363
411
|
* This function is responsible for inferring a query element based on a provided column.
|
|
364
412
|
* It initializes and attaches a non-enumerable `$refLinks` property to the column,
|
|
@@ -477,7 +525,17 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
477
525
|
} else {
|
|
478
526
|
const { definition } = column.$refLinks[i - 1]
|
|
479
527
|
const elements = definition.elements || definition._target?.elements
|
|
480
|
-
|
|
528
|
+
const element = elements?.[id]
|
|
529
|
+
|
|
530
|
+
if (firstStepIsSelf && element?.isAssociation) {
|
|
531
|
+
throw cds.error(
|
|
532
|
+
`Paths starting with “$self” must not contain steps of type “cds.Association”: ref: [ ${column.ref.map(
|
|
533
|
+
idOnly,
|
|
534
|
+
)} ]`,
|
|
535
|
+
)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (element) {
|
|
481
539
|
const $refLink = { definition: elements[id], target: column.$refLinks[i - 1].target }
|
|
482
540
|
column.$refLinks.push($refLink)
|
|
483
541
|
} else if (firstStepIsSelf) {
|
|
@@ -504,7 +562,9 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
504
562
|
: null
|
|
505
563
|
if (foreignKeyAlias) nameSegments.push(foreignKeyAlias)
|
|
506
564
|
else if (skipAliasedFkSegmentsOfNameStack[0] === id) skipAliasedFkSegmentsOfNameStack.shift()
|
|
507
|
-
else
|
|
565
|
+
else {
|
|
566
|
+
nameSegments.push(firstStepIsSelf && i === 1 ? element.__proto__.name : id)
|
|
567
|
+
}
|
|
508
568
|
}
|
|
509
569
|
|
|
510
570
|
if (step.where) {
|
|
@@ -586,7 +646,7 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
586
646
|
}
|
|
587
647
|
const virtual = (column.$refLinks[column.$refLinks.length - 1].definition.virtual || !isPersisted) && !inExpr
|
|
588
648
|
// check if we need to merge the column `ref` into the join tree of the query
|
|
589
|
-
if (!inExists && !virtual && isColumnJoinRelevant(column)) {
|
|
649
|
+
if (!inExists && !virtual && isColumnJoinRelevant(column, firstStepIsSelf)) {
|
|
590
650
|
Object.defineProperty(column, 'isJoinRelevant', { value: true })
|
|
591
651
|
joinTree.mergeColumn(column)
|
|
592
652
|
}
|
|
@@ -760,7 +820,7 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
760
820
|
|
|
761
821
|
if (!assoc) return false
|
|
762
822
|
if (fkAccess) return false
|
|
763
|
-
|
|
823
|
+
return true
|
|
764
824
|
}
|
|
765
825
|
|
|
766
826
|
/**
|