@cap-js/db-service 1.19.1 → 2.0.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 +34 -0
- package/lib/InsertResults.js +3 -3
- package/lib/SQLService.js +59 -37
- package/lib/cql-functions.js +231 -4
- package/lib/cqn2sql.js +318 -20
- package/lib/cqn4sql.js +22 -77
- package/lib/infer/index.js +57 -55
- package/lib/infer/join-tree.js +1 -1
- package/lib/utils.js +76 -5
- package/package.json +4 -4
package/lib/infer/index.js
CHANGED
|
@@ -4,7 +4,7 @@ const cds = require('@sap/cds')
|
|
|
4
4
|
|
|
5
5
|
const JoinTree = require('./join-tree')
|
|
6
6
|
const { pseudos } = require('./pseudos')
|
|
7
|
-
const { isCalculatedOnRead, getImplicitAlias } = require('../utils')
|
|
7
|
+
const { isCalculatedOnRead, getImplicitAlias, getModelUtils, defineProperty } = require('../utils')
|
|
8
8
|
const cdsTypes = cds.linked({
|
|
9
9
|
definitions: {
|
|
10
10
|
Timestamp: { type: 'cds.Timestamp' },
|
|
@@ -27,6 +27,8 @@ function infer(originalQuery, model) {
|
|
|
27
27
|
if (!model) throw new Error('Please specify a model')
|
|
28
28
|
const inferred = originalQuery
|
|
29
29
|
|
|
30
|
+
const { getDefinition } = getModelUtils(model, originalQuery)
|
|
31
|
+
|
|
30
32
|
// REVISIT: The more edge use cases we support, thes less optimized are we for the 90+% use cases
|
|
31
33
|
// e.g. there's a lot of overhead for infer( SELECT.from(Books) )
|
|
32
34
|
if (originalQuery.SET) throw new Error('”UNION” based queries are not supported')
|
|
@@ -44,7 +46,7 @@ function infer(originalQuery, model) {
|
|
|
44
46
|
|
|
45
47
|
let $combinedElements
|
|
46
48
|
|
|
47
|
-
const sources = inferTarget(_.
|
|
49
|
+
const sources = inferTarget(_.into || _.from || _.entity, {}) // IMPORTANT: _.into has to go before _.from for INSERT.into().from(SELECT)
|
|
48
50
|
const joinTree = new JoinTree(sources)
|
|
49
51
|
const aliases = Object.keys(sources)
|
|
50
52
|
const target = aliases.length === 1 ? getDefinitionFromSources(sources, aliases[0]) : originalQuery
|
|
@@ -73,7 +75,7 @@ function infer(originalQuery, model) {
|
|
|
73
75
|
joinTree: { value: joinTree, writable: true, configurable: true }, // REVISIT: eliminate
|
|
74
76
|
})
|
|
75
77
|
// also enrich original query -> writable because it may be inferred again
|
|
76
|
-
|
|
78
|
+
defineProperty(originalQuery, 'elements', elements)
|
|
77
79
|
}
|
|
78
80
|
return inferred
|
|
79
81
|
|
|
@@ -111,13 +113,13 @@ function infer(originalQuery, model) {
|
|
|
111
113
|
|
|
112
114
|
inferArg(from, null, null, { inFrom: true })
|
|
113
115
|
const alias =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
from.uniqueSubqueryAlias ||
|
|
117
|
+
from.as ||
|
|
118
|
+
(ref.length === 1
|
|
119
|
+
? getImplicitAlias(first, useTechnicalAlias)
|
|
120
|
+
: getImplicitAlias(ref.at(-1).id || ref.at(-1), useTechnicalAlias))
|
|
119
121
|
if (alias in querySources) throw new Error(`Duplicate alias "${alias}"`)
|
|
120
|
-
querySources[alias] = { definition: target, args }
|
|
122
|
+
querySources[alias] = { definition: getDefinition(target.name), args }
|
|
121
123
|
const last = from.$refLinks.at(-1)
|
|
122
124
|
last.alias = alias
|
|
123
125
|
} else if (from.args) {
|
|
@@ -169,10 +171,7 @@ function infer(originalQuery, model) {
|
|
|
169
171
|
* @param {csn.Element} element
|
|
170
172
|
*/
|
|
171
173
|
function setElementOnColumns(col, element) {
|
|
172
|
-
|
|
173
|
-
value: element,
|
|
174
|
-
writable: true,
|
|
175
|
-
})
|
|
174
|
+
defineProperty(col, 'element', element)
|
|
176
175
|
}
|
|
177
176
|
|
|
178
177
|
/**
|
|
@@ -209,7 +208,7 @@ function infer(originalQuery, model) {
|
|
|
209
208
|
if (col.func) {
|
|
210
209
|
if (col.args) {
|
|
211
210
|
// {func}.args are optional
|
|
212
|
-
applyToFunctionArgs(col.args, inferArg, [false, null, {dollarSelfRefs}])
|
|
211
|
+
applyToFunctionArgs(col.args, inferArg, [false, null, { dollarSelfRefs }])
|
|
213
212
|
}
|
|
214
213
|
queryElements[as] = getElementForCast(col)
|
|
215
214
|
}
|
|
@@ -243,7 +242,7 @@ function infer(originalQuery, model) {
|
|
|
243
242
|
// link $refLinks -> special name resolution rules for orderBy
|
|
244
243
|
orderBy.forEach(token => {
|
|
245
244
|
let $baseLink
|
|
246
|
-
let
|
|
245
|
+
let needsElementsOfQueryAsBase
|
|
247
246
|
// first check if token ref is resolvable in query elements
|
|
248
247
|
if (columns) {
|
|
249
248
|
const firstStep = token.ref?.[0].id || token.ref?.[0]
|
|
@@ -251,14 +250,11 @@ function infer(originalQuery, model) {
|
|
|
251
250
|
const columnName = c.as || c.flatName || c.ref?.at(-1).id || c.ref?.at(-1) || c.func
|
|
252
251
|
return columnName === firstStep
|
|
253
252
|
})
|
|
254
|
-
|
|
253
|
+
needsElementsOfQueryAsBase =
|
|
255
254
|
tokenPointsToQueryElements &&
|
|
256
|
-
queryElements[
|
|
257
|
-
/* expand on structure can be addressed */ !queryElements[
|
|
255
|
+
queryElements[firstStep] &&
|
|
256
|
+
/* expand on structure can be addressed */ !queryElements[firstStep].$assocExpand
|
|
258
257
|
|
|
259
|
-
// if the ref points into the query itself and follows an exposed association
|
|
260
|
-
// to a non-fk column, we must reject the ref, as we can't join with the queries own results
|
|
261
|
-
rejectJoinRelevantPath = needsElementsOfQueryAsBase
|
|
262
258
|
if (needsElementsOfQueryAsBase) $baseLink = { definition: { elements: queryElements }, target: inferred }
|
|
263
259
|
} else {
|
|
264
260
|
// fallback to elements of query source
|
|
@@ -266,7 +262,9 @@ function infer(originalQuery, model) {
|
|
|
266
262
|
}
|
|
267
263
|
|
|
268
264
|
inferArg(token, queryElements, $baseLink, { inQueryModifier: true })
|
|
269
|
-
if
|
|
265
|
+
// if the ref points into the query itself and follows an exposed association
|
|
266
|
+
// to a non-fk column, we must reject the ref, as we can't join with the queries own results
|
|
267
|
+
if (token.isJoinRelevant && needsElementsOfQueryAsBase) {
|
|
270
268
|
// reverse the array, find the last association and calculate the index of the association in non-reversed order
|
|
271
269
|
const assocIndex =
|
|
272
270
|
token.$refLinks.length - 1 - token.$refLinks.reverse().findIndex(link => link.definition.isAssociation)
|
|
@@ -350,7 +348,7 @@ function infer(originalQuery, model) {
|
|
|
350
348
|
}
|
|
351
349
|
|
|
352
350
|
function handleRef(col, inXpr) {
|
|
353
|
-
inferArg(col, queryElements, null,
|
|
351
|
+
inferArg(col, queryElements, null, { inXpr })
|
|
354
352
|
const { definition } = col.$refLinks[col.$refLinks.length - 1]
|
|
355
353
|
if (col.cast)
|
|
356
354
|
// final type overwritten -> element not visible anymore
|
|
@@ -399,22 +397,27 @@ function infer(originalQuery, model) {
|
|
|
399
397
|
*/
|
|
400
398
|
|
|
401
399
|
function inferArg(arg, queryElements = null, $baseLink = null, context = {}) {
|
|
402
|
-
const { inExists, inXpr, inCalcElement, baseColumn, inInfixFilter, inQueryModifier, inFrom, dollarSelfRefs } =
|
|
400
|
+
const { inExists, inXpr, inCalcElement, baseColumn, inInfixFilter, inQueryModifier, inFrom, dollarSelfRefs } =
|
|
401
|
+
context
|
|
403
402
|
if (arg.param || arg.SELECT) return // parameter references are only resolved into values on execution e.g. :val, :1 or ?
|
|
404
403
|
if (arg.args) applyToFunctionArgs(arg.args, inferArg, [null, $baseLink, context])
|
|
405
404
|
if (arg.list) arg.list.forEach(arg => inferArg(arg, null, $baseLink, context))
|
|
406
|
-
if (arg.xpr)
|
|
405
|
+
if (arg.xpr)
|
|
406
|
+
arg.xpr.forEach((token, i) =>
|
|
407
|
+
inferArg(token, queryElements, $baseLink, { ...context, inXpr: true, inExists: arg.xpr[i - 1] === 'exists' }),
|
|
408
|
+
) // e.g. function in expression
|
|
407
409
|
|
|
408
410
|
if (!arg.ref) {
|
|
409
411
|
if (arg.expand && queryElements) queryElements[arg.as] = resolveExpand(arg)
|
|
410
412
|
return
|
|
411
413
|
}
|
|
412
414
|
|
|
413
|
-
//
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
415
|
+
// Before the arg is linked, it's meta information should be cleaned up.
|
|
416
|
+
// This may be important if one manipulates the arg object
|
|
417
|
+
// __after__ a query has been fired and re-uses the manipulated query
|
|
418
|
+
defineProperty(arg, '$refLinks', [])
|
|
419
|
+
defineProperty(arg, 'isJoinRelevant', false)
|
|
420
|
+
|
|
418
421
|
// if any path step points to an artifact with `@cds.persistence.skip`
|
|
419
422
|
// we must ignore the element from the queries elements
|
|
420
423
|
let isPersisted = true
|
|
@@ -424,8 +427,8 @@ function infer(originalQuery, model) {
|
|
|
424
427
|
firstStepIsSelf = !firstStepIsTableAlias && arg.ref.length > 1 && ['$self', '$projection'].includes(arg.ref[0])
|
|
425
428
|
expandOnTableAlias = arg.ref.length === 1 && arg.ref[0] in sources && (arg.expand || arg.inline)
|
|
426
429
|
}
|
|
427
|
-
if(dollarSelfRefs && firstStepIsSelf) {
|
|
428
|
-
|
|
430
|
+
if (dollarSelfRefs && firstStepIsSelf) {
|
|
431
|
+
defineProperty(arg, 'inXpr', true)
|
|
429
432
|
dollarSelfRefs.push(arg)
|
|
430
433
|
return
|
|
431
434
|
}
|
|
@@ -452,7 +455,7 @@ function infer(originalQuery, model) {
|
|
|
452
455
|
const nextStep = arg.ref[1]?.id || arg.ref[1]
|
|
453
456
|
if (isNonForeignKeyNavigation(element, nextStep)) {
|
|
454
457
|
if (inExists) {
|
|
455
|
-
|
|
458
|
+
defineProperty($baseLink, 'pathExpressionInsideFilter', true)
|
|
456
459
|
} else {
|
|
457
460
|
rejectNonFkNavigation(element, element.on ? $baseLink.definition.name : nextStep)
|
|
458
461
|
}
|
|
@@ -516,7 +519,7 @@ function infer(originalQuery, model) {
|
|
|
516
519
|
const nextStep = arg.ref[i + 1]?.id || arg.ref[i + 1]
|
|
517
520
|
if (isNonForeignKeyNavigation(element, nextStep)) {
|
|
518
521
|
if (inExists) {
|
|
519
|
-
|
|
522
|
+
defineProperty($baseLink, 'pathExpressionInsideFilter', true)
|
|
520
523
|
} else {
|
|
521
524
|
rejectNonFkNavigation(element, element.on ? $baseLink.definition.name : nextStep)
|
|
522
525
|
}
|
|
@@ -532,7 +535,7 @@ function infer(originalQuery, model) {
|
|
|
532
535
|
} else if (id === '$dummy') {
|
|
533
536
|
// `some.known.element.$dummy` -> no error; used by cds.ql to simulate joins
|
|
534
537
|
arg.$refLinks.push({ definition: { name: '$dummy', parent: arg.$refLinks[i - 1].target } })
|
|
535
|
-
|
|
538
|
+
defineProperty(arg, 'isJoinRelevant', true)
|
|
536
539
|
} else {
|
|
537
540
|
const notFoundIn = pseudoPath ? arg.ref[i - 1] : getFullPathForLinkedArg(arg)
|
|
538
541
|
stepNotFoundInPredecessor(id, notFoundIn)
|
|
@@ -558,7 +561,7 @@ function infer(originalQuery, model) {
|
|
|
558
561
|
const definition = arg.$refLinks[i].definition
|
|
559
562
|
if ((!definition.target && definition.kind !== 'entity') || (!inFrom && danglingFilter))
|
|
560
563
|
throw new Error('A filter can only be provided when navigating along associations')
|
|
561
|
-
if (!inFrom && !arg.expand)
|
|
564
|
+
if (!inFrom && !arg.expand)defineProperty(arg, 'isJoinRelevant', true)
|
|
562
565
|
let skipJoinsForFilter = false
|
|
563
566
|
step.where.forEach(token => {
|
|
564
567
|
if (token === 'exists') {
|
|
@@ -587,7 +590,7 @@ function infer(originalQuery, model) {
|
|
|
587
590
|
if (getDefinition(arg.$refLinks[i].definition.target)?.['@cds.persistence.skip'] === true) isPersisted = false
|
|
588
591
|
if (!arg.ref[i + 1]) {
|
|
589
592
|
const flatName = nameSegments.join('_')
|
|
590
|
-
|
|
593
|
+
defineProperty(arg, 'flatName', flatName)
|
|
591
594
|
// if column is casted, we overwrite it's origin with the new type
|
|
592
595
|
if (arg.cast) {
|
|
593
596
|
const base = getElementForCast(arg)
|
|
@@ -632,7 +635,7 @@ function infer(originalQuery, model) {
|
|
|
632
635
|
})
|
|
633
636
|
|
|
634
637
|
// we need inner joins for the path expressions inside filter expressions after exists predicate
|
|
635
|
-
if ($baseLink?.pathExpressionInsideFilter)
|
|
638
|
+
if ($baseLink?.pathExpressionInsideFilter) defineProperty(arg, 'join', 'inner')
|
|
636
639
|
|
|
637
640
|
// ignore whole expand if target of assoc along path has ”@cds.persistence.skip”
|
|
638
641
|
if (arg.expand) {
|
|
@@ -652,7 +655,7 @@ function infer(originalQuery, model) {
|
|
|
652
655
|
? { ref: [...baseColumn.ref, ...arg.ref], $refLinks: [...baseColumn.$refLinks, ...arg.$refLinks] }
|
|
653
656
|
: arg
|
|
654
657
|
if (isColumnJoinRelevant(colWithBase)) {
|
|
655
|
-
|
|
658
|
+
defineProperty(arg, 'isJoinRelevant', true)
|
|
656
659
|
joinTree.mergeColumn(colWithBase, originalQuery.outerQueries)
|
|
657
660
|
}
|
|
658
661
|
}
|
|
@@ -758,7 +761,7 @@ function infer(originalQuery, model) {
|
|
|
758
761
|
const res = $leafLink.definition.is2one
|
|
759
762
|
? new cds.struct({ elements: inferredExpandSubquery.elements })
|
|
760
763
|
: new cds.array({ items: new cds.struct({ elements: inferredExpandSubquery.elements }) })
|
|
761
|
-
return
|
|
764
|
+
return defineProperty(res, '$assocExpand', true)
|
|
762
765
|
} else if ($leafLink.definition.elements) {
|
|
763
766
|
let elements = {}
|
|
764
767
|
expand.forEach(e => {
|
|
@@ -810,7 +813,8 @@ function infer(originalQuery, model) {
|
|
|
810
813
|
else alreadySeenCalcElements.add(calcElement)
|
|
811
814
|
const { ref, xpr } = calcElement.value
|
|
812
815
|
if (ref || xpr) {
|
|
813
|
-
|
|
816
|
+
const parentElementDefinition = getDefinition(calcElement.parent.name)
|
|
817
|
+
baseLink = { definition: parentElementDefinition, target: parentElementDefinition }
|
|
814
818
|
inferArg(calcElement.value, null, baseLink, { inCalcElement: true, ...context })
|
|
815
819
|
const basePath =
|
|
816
820
|
column.$refLinks?.length > 1
|
|
@@ -825,7 +829,13 @@ function infer(originalQuery, model) {
|
|
|
825
829
|
|
|
826
830
|
if (calcElement.value.args) {
|
|
827
831
|
const processArgument = (arg, calcElement, column) => {
|
|
828
|
-
|
|
832
|
+
const parentElementDefinition = getDefinition(calcElement.parent.name)
|
|
833
|
+
inferArg(
|
|
834
|
+
arg,
|
|
835
|
+
null,
|
|
836
|
+
{ definition: parentElementDefinition, target: parentElementDefinition },
|
|
837
|
+
{ inCalcElement: true },
|
|
838
|
+
)
|
|
829
839
|
const basePath =
|
|
830
840
|
column.$refLinks?.length > 1
|
|
831
841
|
? { $refLinks: column.$refLinks.slice(0, -1), ref: column.ref.slice(0, -1) }
|
|
@@ -879,8 +889,7 @@ function infer(originalQuery, model) {
|
|
|
879
889
|
step[nestedProp].forEach(a => {
|
|
880
890
|
// reset sub path for each nested argument
|
|
881
891
|
// e.g. case when <path> then <otherPath> else <anotherPath> end
|
|
882
|
-
if(!a.ref)
|
|
883
|
-
subPath = { $refLinks: [...basePath.$refLinks], ref: [...basePath.ref] }
|
|
892
|
+
if (!a.ref) subPath = { $refLinks: [...basePath.$refLinks], ref: [...basePath.ref] }
|
|
884
893
|
mergePathsIntoJoinTree(a, subPath)
|
|
885
894
|
})
|
|
886
895
|
}
|
|
@@ -891,13 +900,13 @@ function infer(originalQuery, model) {
|
|
|
891
900
|
const calcElementIsJoinRelevant = isColumnJoinRelevant(p)
|
|
892
901
|
if (calcElementIsJoinRelevant) {
|
|
893
902
|
if (!calcElement.value.isJoinRelevant)
|
|
894
|
-
|
|
903
|
+
defineProperty(step, 'isJoinRelevant',true)
|
|
895
904
|
joinTree.mergeColumn(p, originalQuery.outerQueries)
|
|
896
905
|
} else {
|
|
897
906
|
// we need to explicitly set the value to false in this case,
|
|
898
907
|
// e.g. `SELECT from booksCalc.Books { ID, author.{name }, author {name } }`
|
|
899
908
|
// --> for the inline column, the name is join relevant, while for the expand, it is not
|
|
900
|
-
|
|
909
|
+
defineProperty(step, 'isJoinRelevant', false)
|
|
901
910
|
}
|
|
902
911
|
}
|
|
903
912
|
}
|
|
@@ -960,7 +969,7 @@ function infer(originalQuery, model) {
|
|
|
960
969
|
if (Object.keys(queryElements).length === 0 && aliases.length === 1) {
|
|
961
970
|
const { elements } = getDefinitionFromSources(sources, aliases[0])
|
|
962
971
|
// only one query source and no overwritten columns
|
|
963
|
-
for (const k
|
|
972
|
+
for (const k in elements) {
|
|
964
973
|
if (!exclude(k)) {
|
|
965
974
|
const element = elements[k]
|
|
966
975
|
if (element.type !== 'cds.LargeBinary') {
|
|
@@ -1055,7 +1064,6 @@ function infer(originalQuery, model) {
|
|
|
1055
1064
|
* @returns {object} a copy of @param base with all annotations of @param from
|
|
1056
1065
|
* @TODO prototype based
|
|
1057
1066
|
*/
|
|
1058
|
-
// REVISIT: TODO: inferred.elements should be linked
|
|
1059
1067
|
function getCopyWithAnnos(from, base) {
|
|
1060
1068
|
const result = { ...base }
|
|
1061
1069
|
// REVISIT: we don't need to and hence should not handle annotations at runtime
|
|
@@ -1063,7 +1071,7 @@ function infer(originalQuery, model) {
|
|
|
1063
1071
|
if (prop.startsWith('@')) result[prop] = from[prop]
|
|
1064
1072
|
}
|
|
1065
1073
|
|
|
1066
|
-
if (from.as && base.name !== from.as)
|
|
1074
|
+
if (from.as && base.name !== from.as) defineProperty(result, 'name', from.as) // TODO double check if this is needed
|
|
1067
1075
|
// in subqueries we need the linked element if an outer query accesses it
|
|
1068
1076
|
return Object.setPrototypeOf(result, base)
|
|
1069
1077
|
}
|
|
@@ -1083,12 +1091,6 @@ function infer(originalQuery, model) {
|
|
|
1083
1091
|
}
|
|
1084
1092
|
}
|
|
1085
1093
|
|
|
1086
|
-
/** returns the CSN definition for the given name from the model */
|
|
1087
|
-
function getDefinition(name) {
|
|
1088
|
-
if (!name) return null
|
|
1089
|
-
return model.definitions[name]
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
1094
|
function getDefinitionFromSources(sources, id) {
|
|
1093
1095
|
return sources[id].definition
|
|
1094
1096
|
}
|
package/lib/infer/join-tree.js
CHANGED
|
@@ -170,7 +170,7 @@ class JoinTree {
|
|
|
170
170
|
// find the correct query source
|
|
171
171
|
if (
|
|
172
172
|
r.queryArtifact === head.target ||
|
|
173
|
-
r.queryArtifact === head.target.
|
|
173
|
+
r.queryArtifact === head.target._target /** might as well be a query for order by */
|
|
174
174
|
)
|
|
175
175
|
node = r
|
|
176
176
|
})
|
package/lib/utils.js
CHANGED
|
@@ -40,32 +40,103 @@ function isCalculatedElement(def) {
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Calculates the implicit table alias for a given string.
|
|
43
|
-
*
|
|
43
|
+
*
|
|
44
44
|
* Based on the last part of the string, the implicit alias is calculated
|
|
45
45
|
* by taking the first character and prepending it with '$'.
|
|
46
46
|
* A leading '$' is removed if the last part already starts with '$'.
|
|
47
|
-
*
|
|
47
|
+
*
|
|
48
48
|
* @example
|
|
49
49
|
* getImplicitAlias('Books') => '$B'
|
|
50
50
|
* getImplicitAlias('bookshop.Books') => '$B'
|
|
51
51
|
* getImplicitAlias('bookshop.$B') => '$B'
|
|
52
|
-
*
|
|
52
|
+
*
|
|
53
53
|
* @param {string} str - The input string.
|
|
54
|
-
* @returns {string}
|
|
54
|
+
* @returns {string}
|
|
55
55
|
*/
|
|
56
56
|
function getImplicitAlias(str, useTechnicalAlias = true) {
|
|
57
57
|
const index = str.lastIndexOf('.')
|
|
58
|
-
if(useTechnicalAlias) {
|
|
58
|
+
if (useTechnicalAlias) {
|
|
59
59
|
const postfix = (index != -1 ? str.substring(index + 1) : str).replace(/^\$/, '')[0] || /* str === '$' */ '$'
|
|
60
60
|
return '$' + postfix
|
|
61
61
|
}
|
|
62
62
|
return index != -1 ? str.substring(index + 1) : str
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
function defineProperty(obj, prop, value) {
|
|
66
|
+
return Object.defineProperty(obj, prop, {
|
|
67
|
+
value,
|
|
68
|
+
writable: true,
|
|
69
|
+
configurable: true,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Shared utility functions which operate dynamically on the model / query.
|
|
75
|
+
*
|
|
76
|
+
* @param {CSN.model} model
|
|
77
|
+
* @param {CQL} query
|
|
78
|
+
*/
|
|
79
|
+
function getModelUtils(model, query) {
|
|
80
|
+
/**
|
|
81
|
+
* Returns the name of the localized entity for the given `definition`.
|
|
82
|
+
*
|
|
83
|
+
* If the query is `localized`, returns the name of the `localized` version of the `definition`.
|
|
84
|
+
* If there is no `localized` version of the `definition`, return the name of the `definition`
|
|
85
|
+
*
|
|
86
|
+
* @param {CSN.definition} definition
|
|
87
|
+
* @returns the name of the localized entity for the given `definition` or `definition.name`
|
|
88
|
+
*/
|
|
89
|
+
function getLocalizedName(definition) {
|
|
90
|
+
if (!isLocalized(definition)) return definition.name
|
|
91
|
+
const view = getDefinition(`localized.${definition.name}`)
|
|
92
|
+
return view?.name || definition.name
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns true if the definition shall be localized, in the context of the given query.
|
|
97
|
+
*
|
|
98
|
+
* If a given query is required to be translated, the query has
|
|
99
|
+
* the `.localized` property set to `true`. If that is the case,
|
|
100
|
+
* and the definition has not set the `@cds.localized` annotation
|
|
101
|
+
* to `false`, the given definition must be translated.
|
|
102
|
+
*
|
|
103
|
+
* @returns true if the given definition shall be localized
|
|
104
|
+
*/
|
|
105
|
+
function isLocalized(definition) {
|
|
106
|
+
return (
|
|
107
|
+
query.SELECT?.localized &&
|
|
108
|
+
definition?.['@cds.localized'] !== false &&
|
|
109
|
+
!query.SELECT.forUpdate &&
|
|
110
|
+
!query.SELECT.forShareLock
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Returns the (potentially localized) CSN definition for the given name from the model.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} name - The name of the definition to retrieve.
|
|
118
|
+
* @returns {Object|null} The CSN definition or null if not found. The definition may be localized.
|
|
119
|
+
*/
|
|
120
|
+
function getDefinition(name) {
|
|
121
|
+
if (!name) return null
|
|
122
|
+
const def = model.definitions[name]
|
|
123
|
+
if (!def || !isLocalized(def)) return def
|
|
124
|
+
return model.definitions[`localized.${def.name}`] || def
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
getLocalizedName,
|
|
129
|
+
isLocalized,
|
|
130
|
+
getDefinition,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
65
134
|
// export the function to be used in other modules
|
|
66
135
|
module.exports = {
|
|
67
136
|
prettyPrintRef,
|
|
68
137
|
isCalculatedOnRead,
|
|
69
138
|
isCalculatedElement,
|
|
70
139
|
getImplicitAlias,
|
|
140
|
+
defineProperty,
|
|
141
|
+
getModelUtils,
|
|
71
142
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/db-service",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/cap-js/cds-dbs"
|
|
8
|
+
"url": "git+https://github.com/cap-js/cds-dbs.git"
|
|
9
9
|
},
|
|
10
10
|
"bugs": {
|
|
11
11
|
"url": "https://github.com/cap-js/cds-dbs/issues"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"generic-pool": "^3.9.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@sap/cds": ">=
|
|
30
|
+
"@sap/cds": ">=9"
|
|
31
31
|
},
|
|
32
|
-
"license": "
|
|
32
|
+
"license": "Apache-2.0"
|
|
33
33
|
}
|