@sap/cds-compiler 4.0.2 → 4.2.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 +200 -5
- package/bin/cdsc.js +18 -15
- package/doc/CHANGELOG_BETA.md +16 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +33 -13
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +25 -25
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +123 -42
- package/lib/base/messages.js +18 -10
- package/lib/base/model.js +43 -10
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/elements.js +11 -10
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +22 -14
- package/lib/checks/queryNoDbArtifacts.js +132 -73
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +4 -3
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +71 -40
- package/lib/compiler/base.js +7 -2
- package/lib/compiler/builtins.js +40 -41
- package/lib/compiler/checks.js +415 -367
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +9 -9
- package/lib/compiler/define.js +124 -90
- package/lib/compiler/extend.js +115 -88
- package/lib/compiler/finalize-parse-cdl.js +26 -25
- package/lib/compiler/generate.js +57 -49
- package/lib/compiler/index.js +56 -56
- package/lib/compiler/kick-start.js +10 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +180 -144
- package/lib/compiler/propagator.js +10 -9
- package/lib/compiler/resolve.js +321 -246
- package/lib/compiler/shared.js +812 -433
- package/lib/compiler/tweak-assocs.js +114 -50
- package/lib/compiler/utils.js +241 -46
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +679 -770
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +689 -648
- package/lib/edm/edmUtils.js +279 -300
- package/lib/gen/Dictionary.json +34 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2857 -2856
- package/lib/json/from-csn.js +77 -51
- package/lib/json/to-csn.js +15 -15
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +61 -64
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +65 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +51 -18
- package/lib/model/revealInternalProperties.js +30 -22
- package/lib/modelCompare/compare.js +149 -41
- package/lib/modelCompare/utils/filter.js +55 -25
- package/lib/optionProcessor.js +21 -9
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +63 -23
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +82 -35
- package/lib/render/utils/common.js +11 -9
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +62 -21
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +9 -9
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +138 -68
- package/lib/transform/db/flattening.js +98 -30
- package/lib/transform/db/rewriteCalculatedElements.js +20 -14
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +8 -7
- package/lib/transform/db/views.js +73 -33
- package/lib/transform/draft/db.js +11 -9
- package/lib/transform/draft/odata.js +1 -1
- package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
- package/lib/transform/forRelationalDB.js +148 -136
- package/lib/transform/localized.js +92 -54
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
- package/share/messages/message-explanations.json +1 -1
|
@@ -5,8 +5,9 @@ const {
|
|
|
5
5
|
applyTransformations,
|
|
6
6
|
setDependencies,
|
|
7
7
|
walkCsnPath,
|
|
8
|
+
getUtils,
|
|
8
9
|
} = require('../../model/csnUtils');
|
|
9
|
-
const {
|
|
10
|
+
const { implicitAs, columnAlias } = require('../../model/csnRefs');
|
|
10
11
|
const { setProp } = require('../../base/model');
|
|
11
12
|
const { forEach } = require('../../utils/objectUtils');
|
|
12
13
|
|
|
@@ -18,34 +19,28 @@ const { forEach } = require('../../utils/objectUtils');
|
|
|
18
19
|
* @param {CSN.Options} options
|
|
19
20
|
* @param {string} pathDelimiter
|
|
20
21
|
* @param {object} messageFunctions
|
|
21
|
-
* @param {Function} messageFunctions.error
|
|
22
|
-
* @param {Function} messageFunctions.info
|
|
23
|
-
* @param {Function} messageFunctions.throwWithAnyError
|
|
24
22
|
* @param {object} csnUtils
|
|
25
23
|
* @param {object} [iterateOptions]
|
|
26
24
|
*/
|
|
27
|
-
function expandStructureReferences( csn, options, pathDelimiter,
|
|
28
|
-
const {
|
|
29
|
-
isStructured, get$combined, getFinalTypeInfo,
|
|
30
|
-
} = csnUtils;
|
|
31
|
-
let { effectiveType, inspectRef } = csnUtils;
|
|
25
|
+
function expandStructureReferences( csn, options, pathDelimiter, messageFunctions, csnUtils, iterateOptions = {} ) {
|
|
26
|
+
const { error, info, throwWithAnyError } = messageFunctions;
|
|
32
27
|
|
|
33
28
|
rewriteExpandInline();
|
|
34
29
|
|
|
35
|
-
|
|
36
30
|
applyTransformations(csn, {
|
|
37
31
|
keys: (parent, name, keys, path) => {
|
|
38
32
|
parent.keys = expand(keys, path.concat('keys'), true);
|
|
39
33
|
},
|
|
40
34
|
columns: (parent, name, columns, path) => {
|
|
41
35
|
const artifact = csn.definitions[path[1]];
|
|
36
|
+
csnUtils.initDefinition(artifact); // potentially no initialized, yet
|
|
42
37
|
if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
|
|
43
|
-
const root = get$combined({ SELECT: parent });
|
|
38
|
+
const root = csnUtils.get$combined({ SELECT: parent });
|
|
44
39
|
// TODO: replace with the correct options.transformation?
|
|
45
40
|
// Do not expand the * in OData for a moment, not to introduce changes
|
|
46
41
|
// while the OData CSN is still official
|
|
47
42
|
if (!options.toOdata)
|
|
48
|
-
parent.columns = replaceStar(root, columns, parent.excluding);
|
|
43
|
+
parent.columns = replaceStar(root, columns, parent.excluding, parent.from.join !== undefined);
|
|
49
44
|
parent.columns = expand(parent.columns, path.concat('columns'), true);
|
|
50
45
|
}
|
|
51
46
|
},
|
|
@@ -74,7 +69,7 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
74
69
|
// get$combined expects a SET/SELECT - so we wrap the parent
|
|
75
70
|
// (which is the thing inside SET/SELECT)
|
|
76
71
|
// We can directly use SELECT here, as only projections and SELECT can have .columns
|
|
77
|
-
const root = get$combined({ SELECT: parent });
|
|
72
|
+
const root = csnUtils.get$combined({ SELECT: parent });
|
|
78
73
|
if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
|
|
79
74
|
// Make root look like normal .elements - we never cared about conflict afaik anyway
|
|
80
75
|
Object.keys(root).forEach((key) => {
|
|
@@ -102,7 +97,7 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
102
97
|
|
|
103
98
|
cleanup.forEach(fn => fn());
|
|
104
99
|
|
|
105
|
-
|
|
100
|
+
csnUtils = getUtils(csn);
|
|
106
101
|
|
|
107
102
|
const publishing = [];
|
|
108
103
|
// OData must allow navigations to @cds.persistence.skip targets
|
|
@@ -144,7 +139,7 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
144
139
|
if (!(obj && obj.ref) || obj.$scope === 'alias')
|
|
145
140
|
continue;
|
|
146
141
|
|
|
147
|
-
const links = obj._links || inspectRef(path.concat([ name, i ])).links;
|
|
142
|
+
const links = obj._links || csnUtils.inspectRef(path.concat([ name, i ])).links;
|
|
148
143
|
|
|
149
144
|
if (!links)
|
|
150
145
|
continue;
|
|
@@ -244,7 +239,7 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
244
239
|
*/
|
|
245
240
|
function nextBase( parent, base ) {
|
|
246
241
|
if (parent.ref) {
|
|
247
|
-
const finalBaseType = getFinalTypeInfo(parent._art.type);
|
|
242
|
+
const finalBaseType = csnUtils.getFinalTypeInfo(parent._art.type);
|
|
248
243
|
const art = parent._art;
|
|
249
244
|
|
|
250
245
|
if (finalBaseType && (finalBaseType.type === 'cds.Association' || finalBaseType.type === 'cds.Composition'))
|
|
@@ -274,15 +269,8 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
274
269
|
return { columns, toMany: [] };
|
|
275
270
|
|
|
276
271
|
for (const col of columns) {
|
|
277
|
-
if (col.expand) {
|
|
278
|
-
|
|
279
|
-
const { expanded, toManys } = expandInline(root, col, col.ref || [], [ dbName(col) ]);
|
|
280
|
-
|
|
281
|
-
allToMany.push(...toManys);
|
|
282
|
-
newThing.push(...expanded);
|
|
283
|
-
}
|
|
284
|
-
else if (col.inline) {
|
|
285
|
-
const { expanded, toManys } = expandInline(root, col, col.ref || [], []);
|
|
272
|
+
if (col.expand || col.inline) {
|
|
273
|
+
const { expanded, toManys } = expandInline(root, col, col.ref || [], col.expand ? [ dbName(col) ] : []);
|
|
286
274
|
|
|
287
275
|
allToMany.push(...toManys);
|
|
288
276
|
newThing.push(...expanded);
|
|
@@ -304,7 +292,7 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
304
292
|
function isToMany( obj ) {
|
|
305
293
|
if (!obj._art)
|
|
306
294
|
return false;
|
|
307
|
-
const eType = effectiveType(obj._art);
|
|
295
|
+
const eType = csnUtils.effectiveType(obj._art);
|
|
308
296
|
return (eType.type === 'cds.Association' || eType.type === 'cds.Composition') && eType.cardinality && eType.cardinality.max !== 1;
|
|
309
297
|
}
|
|
310
298
|
|
|
@@ -335,18 +323,21 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
335
323
|
});
|
|
336
324
|
toManys.push({ art: current, ref: currentRef, as: currentAlias.join(pathDelimiter) });
|
|
337
325
|
}
|
|
338
|
-
else if (current.expand) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
326
|
+
else if (current.expand || current.inline) {
|
|
327
|
+
const withoutStar = replaceStar(nextBase(current, base), current.expand || current.inline, current.excluding);
|
|
328
|
+
current[current.expand ? 'expand' : 'inline'] = withoutStar;
|
|
329
|
+
for (let i = withoutStar.length - 1; i >= 0; i--) {
|
|
330
|
+
const sub = withoutStar[i];
|
|
331
|
+
let subRef;
|
|
332
|
+
if (sub.ref) {
|
|
333
|
+
// Each expand/inline can introduce another layer of $self/$projection. Since $self is
|
|
334
|
+
// a path-breakout, we can simply use the ref without outer expand/inline-references.
|
|
335
|
+
subRef = (sub.$scope === '$self') ? sub.ref : currentRef.concat(sub.ref);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
subRef = currentRef;
|
|
339
|
+
}
|
|
340
|
+
stack.push([ nextBase(current, base), sub, subRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
|
|
350
341
|
}
|
|
351
342
|
}
|
|
352
343
|
else if (current.xpr || current.args) {
|
|
@@ -520,7 +511,7 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
520
511
|
*
|
|
521
512
|
* @param {Array} thing
|
|
522
513
|
* @param {CSN.Path} path
|
|
523
|
-
* @param {boolean} [withAlias=false] Whether to "expand" the (implicit) alias
|
|
514
|
+
* @param {boolean} [withAlias=false] Whether to "expand" the (implicit) alias as well.
|
|
524
515
|
* @returns {Array} New array - with all structured things expanded
|
|
525
516
|
*/
|
|
526
517
|
function expand( thing, path, withAlias = false ) {
|
|
@@ -528,10 +519,9 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
528
519
|
for (let i = 0; i < thing.length; i++) {
|
|
529
520
|
const col = thing[i];
|
|
530
521
|
if (col.ref && col.$scope !== '$magic') {
|
|
531
|
-
const _art = col._art || inspectRef(path.concat(i)).art;
|
|
532
|
-
if (_art && isStructured(_art))
|
|
522
|
+
const _art = col._art || csnUtils.inspectRef(path.concat(i)).art;
|
|
523
|
+
if (_art && csnUtils.isStructured(_art))
|
|
533
524
|
newThing.push(...expandRef(_art, col, withAlias));
|
|
534
|
-
|
|
535
525
|
else
|
|
536
526
|
newThing.push(col);
|
|
537
527
|
}
|
|
@@ -539,6 +529,23 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
539
529
|
col.as = implicitAs(col.ref);
|
|
540
530
|
newThing.push(col);
|
|
541
531
|
}
|
|
532
|
+
else if (col.cast?.type) {
|
|
533
|
+
const _art = col.cast._type || csnUtils.inspectRef(path.concat(i, 'cast', 'type')).art;
|
|
534
|
+
if (_art && csnUtils.isStructured(_art)) {
|
|
535
|
+
// special case for `null as name : Struct`
|
|
536
|
+
if (col.val === null) {
|
|
537
|
+
newThing.push(...expandValAsStructure(_art, col, withAlias));
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
error('type-invalid-cast', path.concat(i, 'cast', 'type'), {
|
|
541
|
+
'#': col.val !== undefined ? 'val-to-structure' : 'expr-to-structure', value: col.val,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
newThing.push(col);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
542
549
|
else {
|
|
543
550
|
newThing.push(col);
|
|
544
551
|
}
|
|
@@ -548,44 +555,102 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
548
555
|
}
|
|
549
556
|
|
|
550
557
|
/**
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
* Iterative, to not run into stack overflow.
|
|
558
|
+
* Expands a column, and calls leafCallback() when a leaf node is reached.
|
|
554
559
|
*
|
|
555
560
|
* @param {CSN.Element} art
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
* @
|
|
561
|
+
* Structured Artifact which is used for expansion (and names, etc.). For a ref, it's the
|
|
562
|
+
* underlying type or a cast-type, for a value, it's always the cast-type.
|
|
563
|
+
* @param {string} colName
|
|
564
|
+
* Name of the column, that is used as the first name segment, e.g. a column `a` may end up in
|
|
565
|
+
* leafs `a_b` and `a_c`, if `art` has elements `b` and `c`.
|
|
566
|
+
* @param {string[]} colTypeRef
|
|
567
|
+
* Expanded type for the column. Basically the path to the to-be-expanded `art`.
|
|
568
|
+
* @param {(currentRef: any[], currentAlias: string[]) => object} leafCallback
|
|
569
|
+
* Callback when leaf nodes are reached. currentRef is the type reference for the expanded
|
|
570
|
+
* column. currentAlias is the columns calculated alias.
|
|
571
|
+
* @returns {object[]}
|
|
559
572
|
*/
|
|
560
|
-
function
|
|
573
|
+
function _expandStructCol( art, colName, colTypeRef, leafCallback ) {
|
|
561
574
|
const expanded = [];
|
|
562
|
-
/** @type {Array<[CSN.Element, any[],
|
|
563
|
-
const stack = [ [ art,
|
|
575
|
+
/** @type {Array<[CSN.Element, any[], string[]]>} */
|
|
576
|
+
const stack = [ [ art, colTypeRef, [ colName ] ] ];
|
|
564
577
|
while (stack.length > 0) {
|
|
565
578
|
const [ current, currentRef, currentAlias ] = stack.pop();
|
|
566
|
-
if (isStructured(current)) {
|
|
567
|
-
|
|
568
|
-
|
|
579
|
+
if (csnUtils.isStructured(current)) {
|
|
580
|
+
const elements = Object.entries(current.elements || csnUtils.effectiveType(current).elements).reverse();
|
|
581
|
+
for (const [ name, elem ] of elements)
|
|
582
|
+
stack.push([ elem, currentRef.concat(name), currentAlias.concat(name) ]);
|
|
569
583
|
}
|
|
570
584
|
else {
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
const newAlias = currentAlias.join(pathDelimiter);
|
|
574
|
-
// if (alias !== undefined) // explicit alias
|
|
575
|
-
obj.as = newAlias;
|
|
576
|
-
// alias was implicit - to later distinguish expanded s -> s.a from explicitly written s.a
|
|
577
|
-
if (root.as === undefined)
|
|
578
|
-
setProp(obj, '$implicitAlias', true);
|
|
579
|
-
}
|
|
580
|
-
if (root.key)
|
|
581
|
-
obj.key = true;
|
|
582
|
-
expanded.push(obj);
|
|
585
|
+
const newCol = leafCallback(currentRef, currentAlias);
|
|
586
|
+
expanded.push(newCol);
|
|
583
587
|
}
|
|
584
588
|
}
|
|
585
589
|
|
|
586
590
|
return expanded;
|
|
587
591
|
}
|
|
588
592
|
|
|
593
|
+
/**
|
|
594
|
+
* Expand the ref and - if requested - expand/set the alias with it.
|
|
595
|
+
*
|
|
596
|
+
* @param {CSN.Element} art
|
|
597
|
+
* @param {object} root Column, ref in order by, etc.
|
|
598
|
+
* @param {boolean} withAlias Whether to add an explicit flattened alias to the expanded columns/references.
|
|
599
|
+
* @returns {Array}
|
|
600
|
+
*/
|
|
601
|
+
function expandRef( art, root, withAlias ) {
|
|
602
|
+
return _expandStructCol(art, columnAlias(root), root.ref, ( currentRef, currentAlias) => {
|
|
603
|
+
const obj = { ...root, ref: currentRef };
|
|
604
|
+
if (withAlias) {
|
|
605
|
+
// TODO: Remove this line in case foreign key annotations should
|
|
606
|
+
// be adressed via full path into target instead of using alias
|
|
607
|
+
// names. See flattening.js::flattenAllStructStepsInRefs()
|
|
608
|
+
// apply transformations on `ref` counterpart comment.
|
|
609
|
+
setProp(obj, '$structRef', currentAlias);
|
|
610
|
+
obj.as = currentAlias.join(pathDelimiter);
|
|
611
|
+
// alias was implicit - to later distinguish expanded s -> s.a from explicitly written s.a
|
|
612
|
+
if (root.as === undefined)
|
|
613
|
+
setProp(obj, '$implicitAlias', true);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (typeof root.$env === 'string')
|
|
617
|
+
obj.ref = [ root.$env, ...obj.ref ];
|
|
618
|
+
|
|
619
|
+
return obj;
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Expand `null` columns which were cast to a structure, that is: `null as name : Struct`.
|
|
625
|
+
* Requires that `col` has an alias.
|
|
626
|
+
*
|
|
627
|
+
* @param {CSN.Element} art
|
|
628
|
+
* @param {object} col
|
|
629
|
+
* @param {boolean} withAlias Whether to add an explicit flattened alias to the expanded columns/references.
|
|
630
|
+
* @returns {Array}
|
|
631
|
+
*/
|
|
632
|
+
function expandValAsStructure( art, col, withAlias ) {
|
|
633
|
+
const colName = col.as || '';
|
|
634
|
+
// Expression-columns may have an internal name such as `$_column_N`. If the name is internal,
|
|
635
|
+
// we should not publish names based upon the internal name.
|
|
636
|
+
const isInternal = !col.as || !Object.prototype.propertyIsEnumerable.call(col, 'as');
|
|
637
|
+
|
|
638
|
+
return _expandStructCol(art, colName, col.cast.type?.ref || [ col.cast.type ], ( currentRef, currentAlias) => {
|
|
639
|
+
const newCol = {
|
|
640
|
+
...col,
|
|
641
|
+
val: col.val,
|
|
642
|
+
cast: { type: { ref: currentRef } },
|
|
643
|
+
};
|
|
644
|
+
if (withAlias) {
|
|
645
|
+
if (!isInternal)
|
|
646
|
+
newCol.as = currentAlias.join(pathDelimiter);
|
|
647
|
+
else
|
|
648
|
+
setProp(newCol, 'as', currentAlias.join(pathDelimiter));
|
|
649
|
+
}
|
|
650
|
+
return newCol;
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
|
|
589
654
|
/**
|
|
590
655
|
* Get the effective name produced by the object
|
|
591
656
|
*
|
|
@@ -608,9 +673,10 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
608
673
|
* @param {object} base The raw set of things a * can expand to
|
|
609
674
|
* @param {Array} subs Things - the .expand/.inline or .columns
|
|
610
675
|
* @param {string[]} [excluding=[]]
|
|
676
|
+
* @param {boolean} [isComplexQuery=false] Wether the query is a single source select or something more complex
|
|
611
677
|
* @returns {Array} If there was a star, expand it and handle shadowing/excluding, else just return subs
|
|
612
678
|
*/
|
|
613
|
-
function replaceStar( base, subs, excluding = [] ) {
|
|
679
|
+
function replaceStar( base, subs, excluding = [], isComplexQuery = false ) {
|
|
614
680
|
const stars = [];
|
|
615
681
|
const names = Object.create(null);
|
|
616
682
|
for (let i = 0; i < subs.length; i++) {
|
|
@@ -642,7 +708,11 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
642
708
|
}
|
|
643
709
|
}
|
|
644
710
|
else { // the thing is not shadowed - use the name from the base
|
|
645
|
-
|
|
711
|
+
const col = { ref: [ part ] };
|
|
712
|
+
if (isComplexQuery) // $env: tableAlias
|
|
713
|
+
setProp(col, '$env', base[part][0].parent);
|
|
714
|
+
|
|
715
|
+
star.push(col);
|
|
646
716
|
}
|
|
647
717
|
}
|
|
648
718
|
}
|
|
@@ -5,9 +5,9 @@ const {
|
|
|
5
5
|
isBuiltinType, cloneCsnNonDict,
|
|
6
6
|
copyAnnotations, implicitAs, isDeepEqual,
|
|
7
7
|
} = require('../../model/csnUtils');
|
|
8
|
-
const transformUtils = require('../
|
|
8
|
+
const transformUtils = require('../transformUtils');
|
|
9
9
|
const { csnRefs } = require('../../model/csnRefs');
|
|
10
|
-
const { setProp } = require('../../base/model');
|
|
10
|
+
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
11
11
|
const { forEach } = require('../../utils/objectUtils');
|
|
12
12
|
const { cardinality2str } = require('../../model/csnUtils');
|
|
13
13
|
|
|
@@ -79,24 +79,23 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
|
|
|
79
79
|
const ignoreOdataKinds = { aspect: 1, event: 1, type: 1 };
|
|
80
80
|
const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
|
|
81
81
|
applyTransformations(csn, {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (cast.type && !isBuiltinType(cast.type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(cast.type, path) && !isODataItems(cast.type)))
|
|
85
|
-
toFinalBaseType(parent.cast, resolved, true);
|
|
86
|
-
},
|
|
87
|
-
// @ts-ignore
|
|
88
|
-
type: (parent, prop, type, path) => {
|
|
89
|
-
if (options.toOdata && parent.kind && parent.kind in ignoreOdataKinds)
|
|
82
|
+
type: (node, prop, type, path, parent, parentProp) => {
|
|
83
|
+
if (options.toOdata && node.kind && node.kind in ignoreOdataKinds)
|
|
90
84
|
return;
|
|
85
|
+
if (parentProp === 'cast') {
|
|
86
|
+
const e = csnUtils.getFinalTypeInfo(type, t => resolved.get(t)?.art || csnUtils.artifactRef(t));
|
|
87
|
+
if (!e || e.items || e.elements)
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
91
90
|
if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
|
|
92
|
-
toFinalBaseType(
|
|
91
|
+
toFinalBaseType(node, resolved, true);
|
|
93
92
|
|
|
94
|
-
if (
|
|
95
|
-
toFinalBaseType(
|
|
93
|
+
if (node.items) // items could have unresolved types
|
|
94
|
+
toFinalBaseType(node.items, resolved, true);
|
|
96
95
|
|
|
97
96
|
// structured types might not have the child-types replaced.
|
|
98
97
|
// Drill down to ensure this.
|
|
99
|
-
let nextElements =
|
|
98
|
+
let nextElements = node.elements || node.items?.elements;
|
|
100
99
|
if (nextElements) {
|
|
101
100
|
const stack = [ nextElements ];
|
|
102
101
|
while (stack.length > 0) {
|
|
@@ -111,9 +110,9 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
|
|
|
111
110
|
}
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
const directLocalized =
|
|
113
|
+
const directLocalized = node.localized || false;
|
|
115
114
|
if (!directLocalized && !options.toOdata)
|
|
116
|
-
removeLocalized(
|
|
115
|
+
removeLocalized(node);
|
|
117
116
|
}
|
|
118
117
|
},
|
|
119
118
|
}, [ (definitions, artifactName, artifact) => {
|
|
@@ -209,7 +208,10 @@ function flattenAllStructStepsInRefs( csn, options, resolved, pathDelimiter, ite
|
|
|
209
208
|
const lastRef = ref[ref.length - 1];
|
|
210
209
|
const fn = () => {
|
|
211
210
|
const scopedPath = [ ...parent.$path ];
|
|
212
|
-
|
|
211
|
+
// TODO: If foreign key annotations should be assigned via
|
|
212
|
+
// full path into target, uncomment this line and
|
|
213
|
+
// comment/remove setProp in expansion.js
|
|
214
|
+
// setProp(parent, '$structRef', parent.ref);
|
|
213
215
|
parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes);
|
|
214
216
|
resolved.set(parent, { links, art, scope });
|
|
215
217
|
// Explicitly set implicit alias for things that are now flattened - but only in columns
|
|
@@ -220,7 +222,9 @@ function flattenAllStructStepsInRefs( csn, options, resolved, pathDelimiter, ite
|
|
|
220
222
|
delete parent.$implicitAlias;
|
|
221
223
|
}
|
|
222
224
|
// To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
|
|
223
|
-
else if (parent.ref[parent.ref.length - 1] !== lastRef &&
|
|
225
|
+
else if (parent.ref[parent.ref.length - 1] !== lastRef &&
|
|
226
|
+
(insideColumns(scopedPath) || insideKeys(scopedPath)) &&
|
|
227
|
+
!parent.as) {
|
|
224
228
|
parent.as = lastRef;
|
|
225
229
|
}
|
|
226
230
|
};
|
|
@@ -284,8 +288,7 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
|
|
|
284
288
|
forEach(dict, (elementName, element) => {
|
|
285
289
|
if (element.elements) {
|
|
286
290
|
// Ignore the structured element, replace it by its flattened form
|
|
287
|
-
|
|
288
|
-
element._ignore = true;
|
|
291
|
+
element.$ignore = true;
|
|
289
292
|
|
|
290
293
|
const branches = getBranches(element, elementName, effectiveType, pathDelimiter);
|
|
291
294
|
const flatElems = flattenStructuredElement(element, elementName, [], path.concat([ 'elements', elementName ]));
|
|
@@ -308,6 +311,8 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
|
|
|
308
311
|
|
|
309
312
|
|
|
310
313
|
if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
|
|
314
|
+
// unmanaged relations can't be primary key
|
|
315
|
+
delete flatElement.key;
|
|
311
316
|
// Make refs resolvable by fixing the first ref step
|
|
312
317
|
for (const onPart of flatElement.on) {
|
|
313
318
|
if (onPart.ref) {
|
|
@@ -396,6 +401,36 @@ function getBranches( element, elementName, effectiveType, pathDelimiter ) {
|
|
|
396
401
|
return branches;
|
|
397
402
|
}
|
|
398
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Link annotate extensions to managed associations as a preparational step
|
|
406
|
+
* for later annotation assignment on the final foreignkeys
|
|
407
|
+
* This function must be applied on an unmodified, structured CSN in order to
|
|
408
|
+
* traverse both the extensions and dictionary trees in corresponding order.
|
|
409
|
+
*
|
|
410
|
+
* @param {CSN.Model} csn
|
|
411
|
+
* @param {object} options
|
|
412
|
+
*/
|
|
413
|
+
function linkForeignKeyAnnotationExtensionsToAssociation( csn, options ) {
|
|
414
|
+
if (isBetaEnabled(options, 'annotateForeignKeys')) {
|
|
415
|
+
csn.extensions?.forEach(( ext ) => {
|
|
416
|
+
const defName = ext.annotate;
|
|
417
|
+
|
|
418
|
+
const traverseExtensions = (env, enode) => {
|
|
419
|
+
if (env?.target && env?.keys) {
|
|
420
|
+
setProp(env, '$fkExtensions', enode);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
const elements = env?.items?.elements || env?.elements;
|
|
424
|
+
if (enode?.elements && elements)
|
|
425
|
+
Object.keys(enode.elements).forEach(en => traverseExtensions(elements[en], enode.elements[en]));
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
if (ext.annotate)
|
|
429
|
+
traverseExtensions(csn.definitions[defName], ext);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
399
434
|
/**
|
|
400
435
|
* @param {CSN.Model} csn
|
|
401
436
|
* @param {CSN.Options} options
|
|
@@ -441,6 +476,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
441
476
|
* @param {*} path
|
|
442
477
|
*/
|
|
443
478
|
function flattenFKs( assoc, assocName, path ) {
|
|
479
|
+
// TODO Depth first search and not iterate mark and sweep approach
|
|
444
480
|
let finished = false;
|
|
445
481
|
while (!finished) {
|
|
446
482
|
const newKeys = [];
|
|
@@ -465,8 +501,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
465
501
|
const { ref } = assoc.keys[i];
|
|
466
502
|
if (isStructured(art)) {
|
|
467
503
|
done = false;
|
|
468
|
-
// Mark this element to filter it later - not needed after expansion
|
|
469
|
-
setProp(assoc.keys[i], '$toDelete', true);
|
|
470
504
|
const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
|
|
471
505
|
Object.keys(flat).forEach((flatElemName) => {
|
|
472
506
|
const key = assoc.keys[i];
|
|
@@ -502,12 +536,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
502
536
|
}
|
|
503
537
|
else if (art.target) {
|
|
504
538
|
done = false;
|
|
505
|
-
// Mark this element to filter it later - not needed after expansion
|
|
506
|
-
setProp(assoc.keys[i], '$toDelete', true);
|
|
507
539
|
// Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
|
|
508
540
|
// Add the newly generated foreign keys to the end - they will be picked up later on
|
|
509
541
|
// Recursive solutions run into call stack issues
|
|
510
|
-
art.keys
|
|
542
|
+
art.keys?.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i], ref)));
|
|
511
543
|
}
|
|
512
544
|
else if (assoc.keys[i].ref && !assoc.keys[i].as) {
|
|
513
545
|
setProp(assoc.keys[i], inferredAlias, true);
|
|
@@ -523,7 +555,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
523
555
|
}
|
|
524
556
|
return done;
|
|
525
557
|
}
|
|
526
|
-
assoc.keys = assoc.keys.filter(o => !o.$toDelete);
|
|
527
558
|
|
|
528
559
|
/**
|
|
529
560
|
* Clone base and extend the .ref and .as of the clone with the .ref and .as of ref.
|
|
@@ -534,7 +565,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
534
565
|
* @returns {object} The clone of base
|
|
535
566
|
*/
|
|
536
567
|
function cloneAndExtendRef( key, base, ref ) {
|
|
537
|
-
const clone = cloneCsnNonDict(base, options);
|
|
568
|
+
const clone = cloneCsnNonDict(base, { ...options, hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ] } );
|
|
538
569
|
if (key.ref) {
|
|
539
570
|
// We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
|
|
540
571
|
// Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
|
|
@@ -550,6 +581,8 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
550
581
|
}
|
|
551
582
|
setProp(clone, '$ref', $ref);
|
|
552
583
|
clone.ref = clone.ref.concat(key.ref);
|
|
584
|
+
if (clone.$structRef && key.$structRef)
|
|
585
|
+
clone.$structRef = clone.$structRef.concat(key.$structRef);
|
|
553
586
|
}
|
|
554
587
|
|
|
555
588
|
if (!clone.as && clone.ref && clone.ref.length > 0) {
|
|
@@ -612,6 +645,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
612
645
|
return acc;
|
|
613
646
|
}, Object.create(null));
|
|
614
647
|
|
|
648
|
+
// set default for single foreign key from association (if available)
|
|
649
|
+
if (element.default?.val !== undefined && fks.length === 1)
|
|
650
|
+
fks[0][1].default = element.default;
|
|
651
|
+
|
|
615
652
|
// check for duplicate foreign keys
|
|
616
653
|
Object.entries(refCount).forEach(([ name, occ ]) => {
|
|
617
654
|
if (occ > 1)
|
|
@@ -646,6 +683,34 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
646
683
|
}
|
|
647
684
|
}
|
|
648
685
|
}
|
|
686
|
+
// assign annotations from fkExtension tree to foreign keys
|
|
687
|
+
if (isBetaEnabled(options, 'annotateForeignKeys')) {
|
|
688
|
+
const extCollector = {};
|
|
689
|
+
fks.forEach(([ _fkn, fk ]) => {
|
|
690
|
+
let ext = element.$fkExtensions;
|
|
691
|
+
let extKey = elementName;
|
|
692
|
+
for (const step of fk.$extensionPath) {
|
|
693
|
+
extKey += `.${step}`;
|
|
694
|
+
ext = ext?.elements?.[step];
|
|
695
|
+
if (!ext)
|
|
696
|
+
break;
|
|
697
|
+
// collect annotations, lowest wins
|
|
698
|
+
// eslint-disable-next-line no-loop-func
|
|
699
|
+
Object.entries(ext).forEach(([ k, v ]) => {
|
|
700
|
+
if (k[0] === '@') {
|
|
701
|
+
fk[k] = v;
|
|
702
|
+
extCollector[extKey] = ext;
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
// remove consumed annotations after applying the annotation hierarchy to each fk!
|
|
709
|
+
Object.values(extCollector).forEach(ext => Object.keys(ext).forEach((k) => {
|
|
710
|
+
if (k[0] === '@')
|
|
711
|
+
delete ext[k];
|
|
712
|
+
}));
|
|
713
|
+
}
|
|
649
714
|
orderedElements.push(...fks);
|
|
650
715
|
});
|
|
651
716
|
|
|
@@ -669,10 +734,11 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
669
734
|
* @param {CSN.Model} csn
|
|
670
735
|
* @param {object} options
|
|
671
736
|
* @param {string} pathDelimiter
|
|
737
|
+
* @param {object} extensionPath
|
|
672
738
|
* @param {number} lvl
|
|
673
739
|
* @returns {Array[]} First element of every sub-array is the foreign key name, second is the foreign key definition
|
|
674
740
|
*/
|
|
675
|
-
function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
|
|
741
|
+
function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter, extensionPath = [], lvl = 0 ) {
|
|
676
742
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
677
743
|
const isInspectRefResult = !Array.isArray(path);
|
|
678
744
|
|
|
@@ -718,7 +784,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
718
784
|
const continuePath = getContinuePath([ 'keys', keyIndex ]);
|
|
719
785
|
const alias = key.as || implicitAs(key.ref);
|
|
720
786
|
const result = csnUtils.inspectRef(continuePath);
|
|
721
|
-
fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
|
|
787
|
+
fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter, extensionPath.concat(key.$structRef), lvl + 1));
|
|
722
788
|
});
|
|
723
789
|
if (!hasKeys)
|
|
724
790
|
delete finalElement.keys;
|
|
@@ -732,13 +798,14 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
732
798
|
// Skip already produced foreign keys
|
|
733
799
|
if (!elem['@odata.foreignKey4']) {
|
|
734
800
|
const continuePath = getContinuePath([ 'elements', elemName ]);
|
|
735
|
-
fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
|
|
801
|
+
fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, extensionPath.concat(elemName), lvl + 1));
|
|
736
802
|
}
|
|
737
803
|
});
|
|
738
804
|
}
|
|
739
805
|
// we have reached a leaf element, create a foreign key
|
|
740
806
|
else if (finalElement.type == null || isBuiltinType(finalElement.type)) {
|
|
741
807
|
const newFk = Object.create(null);
|
|
808
|
+
setProp(newFk, '$extensionPath', extensionPath);
|
|
742
809
|
for (const prop of [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type' ]) {
|
|
743
810
|
// copy props from original element to preserve derived types!
|
|
744
811
|
if (element[prop] !== undefined)
|
|
@@ -794,6 +861,7 @@ module.exports = {
|
|
|
794
861
|
flattenAllStructStepsInRefs,
|
|
795
862
|
flattenElements,
|
|
796
863
|
removeLeadingSelf,
|
|
864
|
+
linkForeignKeyAnnotationExtensionsToAssociation,
|
|
797
865
|
handleManagedAssociationsAndCreateForeignKeys,
|
|
798
866
|
getBranches,
|
|
799
867
|
};
|