@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.
Files changed (101) hide show
  1. package/CHANGELOG.md +200 -5
  2. package/bin/cdsc.js +18 -15
  3. package/doc/CHANGELOG_BETA.md +16 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +33 -13
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +25 -25
  8. package/lib/base/location.js +6 -7
  9. package/lib/base/message-registry.js +123 -42
  10. package/lib/base/messages.js +18 -10
  11. package/lib/base/model.js +43 -10
  12. package/lib/checks/defaultValues.js +6 -6
  13. package/lib/checks/elements.js +11 -10
  14. package/lib/checks/foreignKeys.js +0 -5
  15. package/lib/checks/manyNavigations.js +33 -0
  16. package/lib/checks/onConditions.js +22 -14
  17. package/lib/checks/queryNoDbArtifacts.js +132 -73
  18. package/lib/checks/selectItems.js +4 -55
  19. package/lib/checks/sql-snippets.js +15 -4
  20. package/lib/checks/types.js +3 -3
  21. package/lib/checks/utils.js +4 -3
  22. package/lib/checks/validator.js +3 -1
  23. package/lib/compiler/.eslintrc.json +2 -1
  24. package/lib/compiler/assert-consistency.js +71 -40
  25. package/lib/compiler/base.js +7 -2
  26. package/lib/compiler/builtins.js +40 -41
  27. package/lib/compiler/checks.js +415 -367
  28. package/lib/compiler/classes.js +62 -0
  29. package/lib/compiler/cycle-detector.js +9 -9
  30. package/lib/compiler/define.js +124 -90
  31. package/lib/compiler/extend.js +115 -88
  32. package/lib/compiler/finalize-parse-cdl.js +26 -25
  33. package/lib/compiler/generate.js +57 -49
  34. package/lib/compiler/index.js +56 -56
  35. package/lib/compiler/kick-start.js +10 -7
  36. package/lib/compiler/moduleLayers.js +1 -1
  37. package/lib/compiler/populate.js +180 -144
  38. package/lib/compiler/propagator.js +10 -9
  39. package/lib/compiler/resolve.js +321 -246
  40. package/lib/compiler/shared.js +812 -433
  41. package/lib/compiler/tweak-assocs.js +114 -50
  42. package/lib/compiler/utils.js +241 -46
  43. package/lib/edm/.eslintrc.json +40 -1
  44. package/lib/edm/annotations/genericTranslation.js +721 -707
  45. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  46. package/lib/edm/csn2edm.js +389 -378
  47. package/lib/edm/edm.js +679 -770
  48. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  49. package/lib/edm/edmInboundChecks.js +29 -27
  50. package/lib/edm/edmPreprocessor.js +689 -648
  51. package/lib/edm/edmUtils.js +279 -300
  52. package/lib/gen/Dictionary.json +34 -10
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +1 -1
  55. package/lib/gen/languageParser.js +2857 -2856
  56. package/lib/json/from-csn.js +77 -51
  57. package/lib/json/to-csn.js +15 -15
  58. package/lib/language/antlrParser.js +2 -1
  59. package/lib/language/genericAntlrParser.js +52 -43
  60. package/lib/language/language.g4 +61 -64
  61. package/lib/language/multiLineStringParser.js +2 -0
  62. package/lib/main.d.ts +65 -0
  63. package/lib/model/csnRefs.js +37 -19
  64. package/lib/model/csnUtils.js +51 -18
  65. package/lib/model/revealInternalProperties.js +30 -22
  66. package/lib/modelCompare/compare.js +149 -41
  67. package/lib/modelCompare/utils/filter.js +55 -25
  68. package/lib/optionProcessor.js +21 -9
  69. package/lib/render/manageConstraints.js +20 -17
  70. package/lib/render/toCdl.js +63 -23
  71. package/lib/render/toHdbcds.js +2 -2
  72. package/lib/render/toRename.js +4 -9
  73. package/lib/render/toSql.js +82 -35
  74. package/lib/render/utils/common.js +11 -9
  75. package/lib/render/utils/unique.js +52 -0
  76. package/lib/transform/db/applyTransformations.js +62 -21
  77. package/lib/transform/db/assertUnique.js +7 -8
  78. package/lib/transform/db/associations.js +2 -2
  79. package/lib/transform/db/cdsPersistence.js +9 -9
  80. package/lib/transform/db/constraints.js +47 -17
  81. package/lib/transform/db/expansion.js +138 -68
  82. package/lib/transform/db/flattening.js +98 -30
  83. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  84. package/lib/transform/db/temporal.js +1 -1
  85. package/lib/transform/db/transformExists.js +8 -7
  86. package/lib/transform/db/views.js +73 -33
  87. package/lib/transform/draft/db.js +11 -9
  88. package/lib/transform/draft/odata.js +1 -1
  89. package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
  90. package/lib/transform/forRelationalDB.js +148 -136
  91. package/lib/transform/localized.js +92 -54
  92. package/lib/transform/odata/toFinalBaseType.js +3 -3
  93. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
  94. package/lib/transform/translateAssocsToJoins.js +14 -28
  95. package/lib/utils/file.js +7 -7
  96. package/lib/utils/moduleResolve.js +210 -121
  97. package/lib/utils/objectUtils.js +1 -1
  98. package/package.json +5 -5
  99. package/share/messages/check-proper-type-of.md +1 -1
  100. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  101. 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 { csnRefs, implicitAs } = require('../../model/csnRefs');
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, { error, info, throwWithAnyError }, csnUtils, iterateOptions = {} ) {
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
- ({ effectiveType, inspectRef } = csnRefs(csn));
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
- // TODO: Can col.ref be empty without an as? Assumption is it cannot - if it has, it's an error, we throw, compiler checks.
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
- current.expand = replaceStar(nextBase(current, base), current.expand, current.excluding);
340
- for (let i = current.expand.length - 1; i >= 0; i--) {
341
- const sub = current.expand[i];
342
- stack.push([ nextBase(current, base), sub, sub.ref ? currentRef.concat(sub.ref) : currentRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
343
- }
344
- }
345
- else if (current.inline) {
346
- current.inline = replaceStar(nextBase(current, base), current.inline, current.excluding);
347
- for (let i = current.inline.length - 1; i >= 0; i--) {
348
- const sub = current.inline[i];
349
- stack.push([ nextBase(current, base), sub, sub.ref ? currentRef.concat(sub.ref) : currentRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
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 aswell.
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
- * Expand the ref and - if requested - expand the alias with it.
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
- * @param {object} root Column, ref in order by, etc.
557
- * @param {boolean} withAlias
558
- * @returns {Array}
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 expandRef( art, root, withAlias ) {
573
+ function _expandStructCol( art, colName, colTypeRef, leafCallback ) {
561
574
  const expanded = [];
562
- /** @type {Array<[CSN.Element, any[], any[]]>} */
563
- const stack = [ [ art, root.ref, [ root.as || implicitAs(root.ref) ] ] ];
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
- for (const [ n, e ] of Object.entries(current.elements || effectiveType(current).elements).reverse())
568
- stack.push([ e, currentRef.concat(n), currentAlias.concat(n) ]);
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 obj = { ...root, ...{ ref: currentRef } };
572
- if (withAlias) {
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
- star.push({ ref: [ part ] });
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('../transformUtilsNew');
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
- cast: (parent, prop, cast, path) => {
83
- // Resolve cast already - we otherwise lose .localized
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(parent, resolved, true);
91
+ toFinalBaseType(node, resolved, true);
93
92
 
94
- if (parent.items) // items could have unresolved types
95
- toFinalBaseType(parent.items, resolved, true);
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 = parent.elements || parent.items?.elements;
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 = parent.localized || false;
113
+ const directLocalized = node.localized || false;
115
114
  if (!directLocalized && !options.toOdata)
116
- removeLocalized(parent);
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 && (insideColumns(scopedPath) || insideKeys(scopedPath)) && !parent.as) {
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
- // TODO: use $ignore - _ is for links
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.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i], ref)));
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
  };