@sap/cds-compiler 3.8.0 → 3.9.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 (79) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +26 -5
  4. package/lib/api/.eslintrc.json +3 -2
  5. package/lib/api/options.js +3 -1
  6. package/lib/api/validate.js +1 -1
  7. package/lib/base/message-registry.js +27 -18
  8. package/lib/base/messages.js +6 -1
  9. package/lib/base/model.js +2 -2
  10. package/lib/checks/.eslintrc.json +1 -0
  11. package/lib/checks/actionsFunctions.js +6 -6
  12. package/lib/checks/annotationsOData.js +1 -1
  13. package/lib/checks/elements.js +28 -17
  14. package/lib/checks/foreignKeys.js +1 -1
  15. package/lib/checks/invalidTarget.js +1 -1
  16. package/lib/checks/onConditions.js +11 -6
  17. package/lib/checks/queryNoDbArtifacts.js +1 -1
  18. package/lib/checks/types.js +1 -1
  19. package/lib/checks/utils.js +1 -1
  20. package/lib/checks/validator.js +3 -2
  21. package/lib/compiler/assert-consistency.js +8 -3
  22. package/lib/compiler/base.js +19 -13
  23. package/lib/compiler/builtins.js +7 -0
  24. package/lib/compiler/checks.js +73 -6
  25. package/lib/compiler/define.js +10 -5
  26. package/lib/compiler/extend.js +924 -1709
  27. package/lib/compiler/finalize-parse-cdl.js +1 -1
  28. package/lib/compiler/generate.js +838 -0
  29. package/lib/compiler/index.js +2 -0
  30. package/lib/compiler/populate.js +2 -2
  31. package/lib/compiler/propagator.js +20 -8
  32. package/lib/compiler/resolve.js +3 -3
  33. package/lib/compiler/shared.js +11 -6
  34. package/lib/edm/annotations/genericTranslation.js +6 -6
  35. package/lib/edm/csn2edm.js +1 -1
  36. package/lib/edm/edm.js +25 -11
  37. package/lib/edm/edmPreprocessor.js +47 -23
  38. package/lib/edm/edmUtils.js +37 -9
  39. package/lib/gen/Dictionary.json +5 -7
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +3 -1
  42. package/lib/gen/language.tokens +24 -23
  43. package/lib/gen/languageLexer.interp +4 -1
  44. package/lib/gen/languageLexer.js +792 -784
  45. package/lib/gen/languageLexer.tokens +12 -11
  46. package/lib/gen/languageParser.js +3944 -3865
  47. package/lib/json/from-csn.js +27 -6
  48. package/lib/json/to-csn.js +10 -6
  49. package/lib/language/antlrParser.js +11 -3
  50. package/lib/language/genericAntlrParser.js +4 -2
  51. package/lib/language/language.g4 +32 -24
  52. package/lib/model/csnRefs.js +15 -7
  53. package/lib/model/csnUtils.js +41 -76
  54. package/lib/modelCompare/utils/.eslintrc.json +1 -1
  55. package/lib/optionProcessor.js +7 -4
  56. package/lib/render/.eslintrc.json +1 -1
  57. package/lib/render/toCdl.js +244 -168
  58. package/lib/render/toHdbcds.js +18 -10
  59. package/lib/render/toSql.js +24 -2
  60. package/lib/transform/db/.eslintrc.json +4 -3
  61. package/lib/transform/db/cdsPersistence.js +1 -1
  62. package/lib/transform/db/expansion.js +11 -6
  63. package/lib/transform/db/flattening.js +22 -15
  64. package/lib/transform/db/rewriteCalculatedElements.js +50 -29
  65. package/lib/transform/db/temporal.js +1 -1
  66. package/lib/transform/db/views.js +1 -1
  67. package/lib/transform/draft/db.js +1 -1
  68. package/lib/transform/draft/odata.js +3 -4
  69. package/lib/transform/forOdataNew.js +5 -6
  70. package/lib/transform/forRelationalDB.js +7 -7
  71. package/lib/transform/odata/toFinalBaseType.js +6 -6
  72. package/lib/transform/odata/typesExposure.js +12 -3
  73. package/lib/transform/odata/utils.js +3 -0
  74. package/lib/transform/transformUtilsNew.js +11 -26
  75. package/lib/transform/translateAssocsToJoins.js +9 -9
  76. package/lib/transform/universalCsn/.eslintrc.json +3 -2
  77. package/lib/transform/universalCsn/coreComputed.js +1 -1
  78. package/lib/transform/universalCsn/universalCsnEnricher.js +6 -4
  79. package/package.json +1 -1
@@ -135,7 +135,18 @@ const xorGroups = {
135
135
  // quantifiers 'some' and 'any are 'xpr' token strings in CSN v1.0
136
136
  };
137
137
 
138
- // Functions reading properties which do no count for the message
138
+ /**
139
+ * Properties that are required next to `=` to make an annotation value an actual expression
140
+ * and not some foreign structure.
141
+ *
142
+ * @type {string[]}
143
+ */
144
+ const xprInAnnoProperties = [
145
+ 'ref', 'xpr', 'list', 'literal', 'val',
146
+ '#', 'func', 'args', 'SELECT', 'SET',
147
+ ];
148
+
149
+ // Functions reading properties which do not count for the message
139
150
  // 'Object in $(PROP) must have at least one property'
140
151
  const functionsOfIrrelevantProps = [ ignore, extra, explicitName ];
141
152
 
@@ -688,6 +699,10 @@ const schema = compileSchema( {
688
699
  vZeroFor: 'val', // CSN v0.1.0 property for `val` in enum def
689
700
  // type: annoValue,
690
701
  inKind: [ 'element' ],
702
+ optional: exprProperties.concat([ 'stored' ]),
703
+ },
704
+ stored: {
705
+ type: boolOrNull,
691
706
  },
692
707
  // ignored: ----------------------------------------------------------------
693
708
  $location: { // special
@@ -1354,8 +1369,15 @@ function annoValue( val, spec ) {
1354
1369
  return retval;
1355
1370
  }
1356
1371
  else if (typeof val['='] === 'string') {
1372
+ // An object with `=` is an expression if and only if:
1373
+ // - there is exactly one property ('=')
1374
+ // - there is at least one other expression property (e.g. "xpr")
1375
+ // TODO: Have xprInAnnoProperties centrally for other backends to use as well (toCdl)
1357
1376
  const valKeys = Object.keys(val);
1358
- if (valKeys.length > 1 && isBetaEnabled(userOptions, 'annotationExpressions')) {
1377
+ if (valKeys.length > 1 &&
1378
+ (isBetaEnabled(userOptions, 'annotationExpressions') ||
1379
+ isBetaEnabled(userOptions, 'v4preview')) &&
1380
+ xprInAnnoProperties.some(prop => val[prop] !== undefined)) {
1359
1381
  const s = schema['@'].schema['-expr'];
1360
1382
  const r = { location: location() };
1361
1383
  Object.assign(r, object(val, s));
@@ -1367,6 +1389,7 @@ function annoValue( val, spec ) {
1367
1389
  ++virtualLine;
1368
1390
  return r;
1369
1391
  }
1392
+ // fallthrough -> unchecked structure
1370
1393
  }
1371
1394
  if (typeof val['#'] === 'string') {
1372
1395
  if (Object.keys( val ).length === 1) {
@@ -1409,8 +1432,7 @@ function annotation( val, spec, xsn, csn, name ) {
1409
1432
  return undefined;
1410
1433
  n.absolute = absolute;
1411
1434
  if (variantIndex < absolute.length)
1412
- n.variant = { id: name.substring( variantIndex ), location: location() };
1413
-
1435
+ n.variant = refSplit( name.substring( variantIndex ), null );
1414
1436
  const r = annoValue( val, spec );
1415
1437
  r.name = n;
1416
1438
  return r;
@@ -1726,8 +1748,7 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1726
1748
  {
1727
1749
  '#': variant,
1728
1750
  prop,
1729
- parentprop:
1730
- parentSpec.msgProp,
1751
+ parentprop: parentSpec.msgProp,
1731
1752
  kind,
1732
1753
  } );
1733
1754
  }
@@ -431,14 +431,15 @@ function i18n( i18nNode ) {
431
431
  return csn;
432
432
  }
433
433
 
434
- function sources( srcDict, csn ) {
435
- const names = Object.keys( srcDict );
434
+ function sources( srcDict, csn, model ) {
435
+ let names = model._sources || Object.keys( srcDict );
436
436
  const $sources = names.length && srcDict[names[0]].$sources;
437
437
  if ($sources) {
438
438
  setHidden( csn, '$sources', $sources );
439
439
  return undefined;
440
440
  }
441
- // TODO: sort according to some layering order, see #6368
441
+ if (model._sortedSources)
442
+ names = model._sortedSources.map( s => s.realname );
442
443
  setHidden( csn, '$sources', (!strictMode) ? names : names.map( relativeName ) );
443
444
  return undefined;
444
445
 
@@ -1242,10 +1243,13 @@ function enumValueOrCalc( v, csn, node ) {
1242
1243
  if (v.$inferred && (universalCsn || gensrcFlavor))
1243
1244
  return undefined;
1244
1245
  // Enums can have values but if enums are extended, their kind is 'element'
1245
- if (node.kind === 'enum' || node.$syntax === 'enum')
1246
+ if (node.kind === 'enum' || node.$syntax === 'enum') {
1246
1247
  Object.assign( csn, expression( v ) );
1247
- else if (node.$syntax === 'calc') // TODO: || node._parent?.kind === 'extend'
1248
- return expression( v );
1248
+ }
1249
+ else if (node.$syntax === 'calc') { // TODO: || node._parent?.kind === 'extend'
1250
+ const stored = v.stored ? { stored: value(v.stored) } : {};
1251
+ return Object.assign( stored, expression( v ) );
1252
+ }
1249
1253
  return undefined;
1250
1254
  }
1251
1255
 
@@ -11,6 +11,7 @@
11
11
  const antlr4 = require('antlr4');
12
12
 
13
13
  const { CompileMessage } = require('../base/messages');
14
+ const { isBetaEnabled } = require('../base/model');
14
15
  const errorStrategy = require('./errorStrategy');
15
16
 
16
17
  const Parser = require('../gen/languageParser').default;
@@ -29,6 +30,11 @@ class ErrorListener extends antlr4.error.ErrorListener {
29
30
  }
30
31
 
31
32
  class RewriteTypeTokenStream extends antlr4.CommonTokenStream {
33
+ constructor(lexer, v4newKeyword) {
34
+ super(lexer);
35
+ this.v4newKeyword = v4newKeyword;
36
+ }
37
+
32
38
  LT( k ) {
33
39
  const t = super.LT(k);
34
40
  if (!t || !t.type)
@@ -49,9 +55,10 @@ class RewriteTypeTokenStream extends antlr4.CommonTokenStream {
49
55
  }
50
56
  else if (t.type === this.NEW) {
51
57
  const n = super.LT(k + 1);
52
- if (n && n.type === this.Identifier && /^st_/i.test( n.text )) {
58
+ // TODO v4: rewrite token in grammar via `this.setLocalToken`
59
+ if (n?.type === this.Identifier && (this.v4newKeyword || /^st_/i.test( n.text ))) {
53
60
  const o = super.LT(k + 2);
54
- if (o && o.type === this.PAREN)
61
+ if (o?.type === this.PAREN)
55
62
  return t;
56
63
  }
57
64
  t.type = this.Identifier;
@@ -111,7 +118,8 @@ function parse( source, filename = '<undefined>.cds',
111
118
  options = {}, messageFunctions = null,
112
119
  rule = 'cdl' ) {
113
120
  const lexer = new Lexer( new antlr4.InputStream(source) );
114
- const tokenStream = new RewriteTypeTokenStream(lexer);
121
+ const v4newKeyword = isBetaEnabled(options, 'v4preview');
122
+ const tokenStream = new RewriteTypeTokenStream(lexer, v4newKeyword);
115
123
  /** @type {object} */
116
124
  const parser = new Parser( tokenStream );
117
125
  const errorListener = new ErrorListener();
@@ -974,7 +974,8 @@ function pushIdent( path, ident, prefix ) {
974
974
  }
975
975
 
976
976
  // For :param, #variant, #symbol, @(…) and @Begin and `@` inside annotation paths
977
- function reportUnexpectedSpace( prefix, location = this.tokenLocation( this._input.LT(1) ) ) {
977
+ function reportUnexpectedSpace( prefix = this._input.LT(-1),
978
+ location = this.tokenLocation( this._input.LT(1) ) ) {
978
979
  const prefixLoc = this.tokenLocation( prefix );
979
980
  if (prefixLoc.endLine !== location.line ||
980
981
  prefixLoc.endCol !== location.col) {
@@ -1094,7 +1095,8 @@ function addExtension( ext, parent, kind, artName, elemPath ) {
1094
1095
  }
1095
1096
 
1096
1097
  function aspectWithoutElements( art ) {
1097
- // Empty dictionary to allow element extensions.
1098
+ // Empty dictionary to allow element extensions. NO, please NO empty dict.
1099
+ // TODO: Checking it here does not prevent aspect in CSN input having no elements!
1098
1100
  art.elements = this.createDict();
1099
1101
  if (!isBetaEnabled( this.options, 'aspectWithoutElements' )) {
1100
1102
  this.error( null, [ art.name.location ], {},
@@ -607,10 +607,14 @@ elementDefInner[ art, outer, mightBeEnum ]
607
607
  ':'
608
608
  elementType[ $art, $mightBeEnum ]
609
609
  |
610
- '=' e=expression // SQL has syntax variant using AS - we DO NOT
610
+ eq='=' e=expression // SQL has syntax variant using AS - we DO NOT
611
+ stored=STORED?
611
612
  { $art.value = $e.expr;
612
613
  // this.setIntroLocation( eq ); -- future
613
- if ($mightBeEnum && ($e.expr?.val !== undefined || $e.expr?.sym !== undefined) &&
614
+ if ($stored)
615
+ $art.value.stored = this.valueWithTokenLocation( true, $stored );
616
+ if ($mightBeEnum && !$stored &&
617
+ ($e.expr?.val !== undefined || $e.expr?.sym !== undefined) &&
614
618
  !$virtual && !$key && !$masked && !$art.elements && !$art.type)
615
619
  $art['$'+'syntax'] = 'enum';
616
620
  }
@@ -721,7 +725,12 @@ elementProperties[ elem ]
721
725
  :
722
726
  defaultAndNullablity[ $elem ]
723
727
  |
724
- '=' e=expression { $elem.value = $e.expr; }
728
+ '=' e=expression
729
+ stored=STORED?
730
+ { $elem.value = $e.expr;
731
+ if ($stored)
732
+ $elem.value.stored = this.valueWithTokenLocation( true, $stored );
733
+ }
725
734
  ;
726
735
 
727
736
  defaultAndNullablity[ elem ]
@@ -2583,7 +2592,7 @@ annotationAssignment_fix[ art ] locals[ assignment ]
2583
2592
  |
2584
2593
  { $assignment = { name: {} }; }
2585
2594
  annotationPath[ $assignment.name, 'anno' ]
2586
- annotationPathVariant[ $assignment.name ]?
2595
+ ( '#' annotationPathVariant[ $assignment.name ] )?
2587
2596
  { this.warnIfColonFollows( $assignment ); }
2588
2597
  )
2589
2598
  ;
@@ -2601,7 +2610,7 @@ annotationAssignment_ll1[ art ] locals[ assignment ]
2601
2610
  |
2602
2611
  { $assignment = { name: {} }; }
2603
2612
  annotationPath[ $assignment.name, 'anno' ]
2604
- annotationPathVariant[ $assignment.name ]?
2613
+ ( '#' annotationPathVariant[ $assignment.name ] )?
2605
2614
  (
2606
2615
  ':' { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
2607
2616
  val=annoValue[ $assignment ]
@@ -2627,11 +2636,7 @@ annotationAssignment_atn[ art ] locals[ assignment ]
2627
2636
  // before an "expression" which can start with a '#' for an enum value
2628
2637
  // -> used to introduce variant name if and only if in same line as previous token
2629
2638
  { this.setLocalToken( '#', 'HelperToken1', null, true ); }
2630
- (
2631
- hash=HelperToken1
2632
- { this.meltKeywordToIdentifier();; this.reportUnexpectedSpace( $hash ); }
2633
- variant=ident['variant'] { $assignment.name.variant = $variant.id; }
2634
- )?
2639
+ ( hash=HelperToken1 annotationPathVariant[ $assignment.name ] )?
2635
2640
  // ':' is in the follow set of this rule, as it is used in rule "selectItemDef"
2636
2641
  // before an "expression" which can start with a ':' for a parameter reference
2637
2642
  // -> used to introduce assignment value if and only if in same line as previous token
@@ -2643,11 +2648,7 @@ annotationAssignment_atn[ art ] locals[ assignment ]
2643
2648
  |
2644
2649
  atv='@'? annotationPath[ $assignment, 'ref', $atv ]
2645
2650
  { this.setLocalToken( '#', 'HelperToken1', null, true ); } // see above
2646
- (
2647
- hash=HelperToken1
2648
- { this.meltKeywordToIdentifier();; this.reportUnexpectedSpace( $hash ); }
2649
- variant=ident['variant'] { $assignment.variant = $variant.id; }
2650
- )?
2651
+ ( hash=HelperToken1 annotationPathVariant[ $assignment ] )?
2651
2652
  )
2652
2653
  )?
2653
2654
  )
@@ -2658,11 +2659,11 @@ annotationAssignment_paren[ art ]
2658
2659
  '('
2659
2660
  // allow completely useless `@()`; no warning anymore - who cares?
2660
2661
  {
2661
- this.meltKeywordToIdentifier();
2662
2662
  if (this.isStraightBefore(')')) {
2663
2663
  this.matchWildcard(); // we know it is the ')' - we do not reach the final match
2664
2664
  return $ctx;
2665
2665
  }
2666
+ this.meltKeywordToIdentifier();
2666
2667
  }
2667
2668
  annotationAssignment_1[ $art ]
2668
2669
  ( ','
@@ -2679,7 +2680,7 @@ annotationAssignment_1[ art ] locals[ assignment = { name: {} } ]
2679
2680
  @after { this.assignAnnotation( $art, $assignment ); }
2680
2681
  :
2681
2682
  annotationPath[ $assignment.name, 'anno' ]
2682
- annotationPathVariant[ $assignment.name ]?
2683
+ ( '#' annotationPathVariant[ $assignment.name ] )?
2683
2684
  (
2684
2685
  ':' { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
2685
2686
  val=annoValue[ $assignment ]
@@ -2704,11 +2705,11 @@ annotationPath[ art, category, headat = null ] locals[ _sync = 'nop' ]
2704
2705
  )*
2705
2706
  ;
2706
2707
 
2708
+ // Before calling this rule, match '#'
2707
2709
  annotationPathVariant[ art ] locals[ variant = {} ]
2708
2710
  @after { this.attachLocation($art); }
2709
2711
  :
2710
- // TODO: warning for space after '#'
2711
- hash='#' { this.meltKeywordToIdentifier();; this.reportUnexpectedSpace( $hash ); }
2712
+ { this.reportUnexpectedSpace();; this.meltKeywordToIdentifier(); }
2712
2713
  simplePath[ $variant, 'variant' ] { $art.variant = $variant; }
2713
2714
  ;
2714
2715
 
@@ -2719,7 +2720,7 @@ annoValue[ assignment ]
2719
2720
  // no docComment() here
2720
2721
  // this alternative is done with token rewrite in rule "annotationAssignment_atn"
2721
2722
  at='@'? annotationPath[ $assignment, 'ref', $at ]
2722
- annotationPathVariant[ $assignment ]?
2723
+ ( '#' annotationPathVariant[ $assignment ] )?
2723
2724
  ;
2724
2725
 
2725
2726
  annoValueBase[ assignment ] locals [ seenEllipsis = false ]
@@ -2739,6 +2740,7 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
2739
2740
  |
2740
2741
  '[' // no need for createArray() here, $assignment.location is set
2741
2742
  { $assignment.val = []; $assignment.literal = 'array'; }
2743
+ { this.meltKeywordToIdentifier(true); }
2742
2744
  (
2743
2745
  (
2744
2746
  head=annoSubValue { $assignment.val.push( $head.val ); }
@@ -2753,6 +2755,7 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
2753
2755
  )
2754
2756
  (
2755
2757
  ',' { if (this.isStraightBefore(']')) break; } // allow ',' before ']'
2758
+ { this.meltKeywordToIdentifier(true); }
2756
2759
  (
2757
2760
  tail=annoSubValue { $assignment.val.push( $tail.val ); }
2758
2761
  |
@@ -2794,7 +2797,7 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
2794
2797
  flattenedValue[ assignment ] locals[ val = { name: {} } ]
2795
2798
  :
2796
2799
  at='@'? annotationPath[ $val.name, 'name', $at ]
2797
- ( annotationPathVariant[ $val.name ] )?
2800
+ ( '#' annotationPathVariant[ $val.name ] )?
2798
2801
  (
2799
2802
  ':' { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
2800
2803
  annoValue[ $val ]
@@ -2805,7 +2808,8 @@ flattenedValue[ assignment ] locals[ val = { name: {} } ]
2805
2808
  namedValue[ struct ] locals[ val = { name: {} } ]
2806
2809
  :
2807
2810
  at='@'? annotationPath[ $val.name, 'name', $at ]
2808
- ( ':' sub=annoSubValue { Object.assign( $val, $sub.val ); } )?
2811
+ ( ':' { this.meltKeywordToIdentifier(true); }
2812
+ sub=annoSubValue { Object.assign( $val, $sub.val ); } )?
2809
2813
  {
2810
2814
  if (!$val.location) $val.location = $val.name.location;
2811
2815
  this.addDef( $val, $struct, 'struct', null, $val.name ); // TODO: re-check name
@@ -2830,8 +2834,10 @@ annoSubValue returns[ val = {} ]
2830
2834
  |
2831
2835
  '[' // no need for createArray() here, $val.location is set
2832
2836
  { $val.val = []; $val.literal = 'array'; }
2837
+ { this.meltKeywordToIdentifier(true); }
2833
2838
  ( head=annoSubValue { $val.val.push( $head.val ); }
2834
2839
  ( ',' { if (this.isStraightBefore(']')) break; } // allow ',' before ']'
2840
+ { this.meltKeywordToIdentifier(true); }
2835
2841
  tail=annoSubValue { $val.val.push( $tail.val ); }
2836
2842
  )*
2837
2843
  )?
@@ -2843,7 +2849,7 @@ annoSubValue returns[ val = {} ]
2843
2849
  { Object.assign( $val, this.numberLiteral( $num, $plus||$min ) ); }
2844
2850
  |
2845
2851
  at='@'? annotationPath[ $val, 'ref', $at ]
2846
- ( annotationPathVariant[ $val ] )?
2852
+ ( '#' annotationPathVariant[ $val ] )?
2847
2853
  |
2848
2854
  '('
2849
2855
  cond=condition
@@ -2857,7 +2863,7 @@ literalValue returns[ val ] locals[ tok ]
2857
2863
  @init{ $tok = this.getCurrentToken(); }
2858
2864
  @after { this.attachLocation($val); }
2859
2865
  :
2860
- hash='#' { this.meltKeywordToIdentifier();; this.reportUnexpectedSpace( $hash ); }
2866
+ hash='#' { this.reportUnexpectedSpace( $hash );; this.meltKeywordToIdentifier(); }
2861
2867
  name=ident['enumref'] // TODO v4: remove from this rule (not in enum!)
2862
2868
  { $val = { literal: 'enum', sym: $name.id } }
2863
2869
  |
@@ -2959,6 +2965,7 @@ ident[ category ] returns[ id ]
2959
2965
  | RIGHT
2960
2966
  | ROW
2961
2967
  | ROWS
2968
+ | STORED
2962
2969
  | SERVICE
2963
2970
  | THEN
2964
2971
  | UNION
@@ -3158,6 +3165,7 @@ RIGHT : [rR][iI][gG][hH][tT] ;
3158
3165
  ROW : [rR][oO][wW] ;
3159
3166
  ROWS : [rR][oO][wW][sS] ;
3160
3167
  SERVICE : [sS][eE][rR][vV][iI][cC][eE] ;
3168
+ STORED : [sS][tT][oO][rR][eE][dD] ;
3161
3169
  THEN : [tT][hH][eE][nN] ;
3162
3170
  TO : [tT][oO] ; // or make reserved? (is in SQL-92)
3163
3171
  TYPE : [tT][yY][pP][eE] ;
@@ -66,8 +66,11 @@
66
66
  // - function `initColumnElement`: issue with column which is neither `*` nor
67
67
  // a `ref` with sibling `inline`, but still has no corresponding element
68
68
 
69
- // The functions in this module also use an internal cache. The second call of
70
- // inspectRef() in the following example might lead to a wrong result or an
69
+ // The functions in this module also use an internal cache, which can be dropped
70
+ // for a single definition (main artifact) with function dropDefinitionCache().
71
+
72
+ // When modifying the CSN, caches might need to be invalidated. In the following
73
+ // example, the second call of inspectRef() might lead to a wrong result or an
71
74
  // exception if the assignment to `inspectRef` is not uncommented:
72
75
  //
73
76
  // let { inspectRef } = csnRefs( csn );
@@ -517,7 +520,7 @@ function csnRefs( csn, universalReady ) {
517
520
  /**
518
521
  * @param {CSN.Path} csnPath
519
522
  *
520
- * - return value `art`: the “resulting” CSN of the reference
523
+ * - return value `art`: the “resulting” CSN node of the reference
521
524
  *
522
525
  * - return value `links`: array of { art, env } in length of ref.path where
523
526
  * art = the definition or element reached by the ref path so far
@@ -620,6 +623,9 @@ function csnRefs( csn, universalReady ) {
620
623
  return resolvePath( path, art, parent, 'parent' );
621
624
  }
622
625
 
626
+ if (!qcache)
627
+ throw new CompilerAssertion( `Query not in cache at: ${ locationString(query.$location) }` );
628
+
623
629
  if (semantics.dynamic === 'query')
624
630
  // TODO: for ON condition in expand, would need to use cached _element
625
631
  return resolvePath( path, qcache.elements[head], null, 'query' );
@@ -882,8 +888,8 @@ function traverseFrom( from, fromSelect, parentQuery, callback ) {
882
888
  }
883
889
  else if (from.args) { // join
884
890
  from.args.forEach( arg => traverseFrom( arg, fromSelect, parentQuery, callback ) );
885
- if (from.on) // join-on, potentially having a sub query
886
- from.on.forEach( arg => traverseQuery( arg, null, fromSelect, callback ) );
891
+ if (from.on) // join-on, potentially having a sub query (in xpr)
892
+ from.on.forEach(arg => traverseExpr(arg, fromSelect, callback));
887
893
  }
888
894
  else { // sub query in FROM
889
895
  traverseQuery( from, fromSelect, parentQuery, callback );
@@ -989,6 +995,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
989
995
  /** @type {boolean|string|number} */
990
996
  let isName = false;
991
997
  let baseRef = null;
998
+ let baseCtx = null;
992
999
  let baseEnv = null;
993
1000
  let {
994
1001
  index, main, parent, art,
@@ -1046,13 +1053,13 @@ function analyseCsnPath( csnPath, csn, resolve ) {
1046
1053
  }
1047
1054
  else if (prop === 'where' && refCtx === 'ref') {
1048
1055
  if (resolve)
1049
- baseEnv = resolve.ref_where( obj, baseRef, refCtx, main, query, parent, baseEnv );
1056
+ baseEnv = resolve.ref_where( obj, baseRef, baseCtx, main, query, parent, baseEnv );
1050
1057
  refCtx = 'ref_where';
1051
1058
  }
1052
1059
  else if (prop === 'expand' || prop === 'inline') {
1053
1060
  if (obj.ref) {
1054
1061
  if (resolve)
1055
- baseEnv = resolve.expandInline( obj, refCtx, main, query, parent, baseEnv );
1062
+ baseEnv = resolve.expandInline( obj, baseCtx, main, query, parent, baseEnv );
1056
1063
  refCtx = prop;
1057
1064
  }
1058
1065
  isName = prop;
@@ -1067,6 +1074,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
1067
1074
  }
1068
1075
  else if (prop === 'ref') {
1069
1076
  baseRef = obj; // needs to be inspected for filter conditions
1077
+ baseCtx = refCtx;
1070
1078
  refCtx = prop;
1071
1079
  }
1072
1080
  else if (prop === 'orderBy') {
@@ -47,13 +47,12 @@ function getUtils( model, universalReady ) {
47
47
  const special$self = !model?.definitions?.$self && '$self';
48
48
  const _csnRefs = csnRefs(model, universalReady);
49
49
  const { artifactRef } = _csnRefs;
50
- /** Cache for getFinalBaseTypeWithProps(). Specific to the current model. */
50
+ /** Cache for getFinalTypeInfo(). Specific to the current model. */
51
51
  const finalBaseTypeCache = Object.create(null);
52
52
 
53
53
  return {
54
54
  getCsnDef,
55
55
  isStructured,
56
- getFinalType,
57
56
  isManagedAssociation,
58
57
  isAssocOrComposition,
59
58
  isAssociation,
@@ -63,7 +62,7 @@ function getUtils( model, universalReady ) {
63
62
  addStringAnnotationTo,
64
63
  getServiceName,
65
64
  hasAnnotationValue,
66
- getFinalBaseTypeWithProps,
65
+ getFinalTypeInfo,
67
66
  get$combined,
68
67
  getQueryPrimarySource,
69
68
  ..._csnRefs,
@@ -136,7 +135,7 @@ function getUtils( model, universalReady ) {
136
135
  elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || implicitAs(arg.ref), implicitAs(arg.ref) || arg.as);
137
136
  }
138
137
  else if (arg.SELECT || arg.SET) {
139
- elements = mergeElementMaps(elements, getSources(arg));
138
+ elements = mergeElementMaps(elements, getSources(arg, true));
140
139
  }
141
140
  }
142
141
 
@@ -224,18 +223,7 @@ function getUtils( model, universalReady ) {
224
223
  * @returns {boolean}
225
224
  */
226
225
  function isStructured( obj ) {
227
- return !!(obj.elements || (obj.type && getFinalBaseTypeWithProps(obj.type)?.elements));
228
- }
229
-
230
- /**
231
- * Resolves typedefs to its final type (name) which is returned.
232
- * @param {string|object} typeName Absolute type name or type ref ({ref: [...]}).
233
- * @returns {string|object}
234
- */
235
- function getFinalType( typeName ) {
236
- if (!typeName)
237
- return typeName;
238
- return getFinalBaseTypeWithProps(typeName)?.type || null;
226
+ return !!(obj.elements || (obj.type && getFinalTypeInfo(obj.type)?.elements));
239
227
  }
240
228
 
241
229
  // Return true if 'node' is a managed association element
@@ -247,30 +235,30 @@ function getUtils( model, universalReady ) {
247
235
  /**
248
236
  * Returns if a type is an association or a composition (possibly via type chain).
249
237
  *
250
- * @param {string} typeName Absolute type name
238
+ * @param {object} artifact Element or other artifact.
251
239
  */
252
- function isAssocOrComposition( typeName ) {
253
- const finalType = getFinalType( typeName );
240
+ function isAssocOrComposition( artifact ) {
241
+ const finalType = artifact && getFinalTypeInfo( artifact.type )?.type;
254
242
  return (finalType === 'cds.Association' || finalType === 'cds.Composition');
255
243
  }
256
244
 
257
245
  /**
258
246
  * Returns true if a type is an association (possibly via type chain).
259
247
  *
260
- * @param {string} typeName Absolute type name
248
+ * @param {object} artifact Element or other artifact.
261
249
  */
262
- function isAssociation( typeName ) {
263
- const finalType = getFinalType( typeName );
250
+ function isAssociation( artifact ) {
251
+ const finalType = artifact && getFinalTypeInfo( artifact.type )?.type;
264
252
  return (finalType === 'cds.Association');
265
253
  }
266
254
 
267
255
  /**
268
256
  * Returns if a type is a composition (possibly via type chain).
269
257
  *
270
- * @param {string} typeName Absolute type name
258
+ * @param {object} artifact Element or other artifact.
271
259
  */
272
- function isComposition( typeName ) {
273
- const finalType = getFinalType( typeName );
260
+ function isComposition( artifact ) {
261
+ const finalType = artifact && getFinalTypeInfo( artifact.type )?.type;
274
262
  return (finalType === 'cds.Composition');
275
263
  }
276
264
 
@@ -340,7 +328,7 @@ function getUtils( model, universalReady ) {
340
328
  *
341
329
  * Notes:
342
330
  * - Caches type lookups. If the CSN changes drastically, you will need to re-call
343
- * `getUtils()` and use the newly returned `getFinalBaseTypeWithProps()`.
331
+ * `getUtils()` and use the newly returned `getFinalTypeInfo()`.
344
332
  * - Does _not_ return the underlying type definition! It is an object with all relevant
345
333
  * type properties collected while traversing the type chain!
346
334
  *
@@ -349,7 +337,7 @@ function getUtils( model, universalReady ) {
349
337
  * @param {string|object} type Type as string or type ref, i.e. `{ ref: [...] }`
350
338
  * @returns {object|null}
351
339
  */
352
- function getFinalBaseTypeWithProps( type ) {
340
+ function getFinalTypeInfo( type ) {
353
341
  type = normalizeTypeRef(type);
354
342
  if (!type)
355
343
  return null;
@@ -392,7 +380,7 @@ function getUtils( model, universalReady ) {
392
380
  finalBaseTypeCache[resolvedKey] = true;
393
381
 
394
382
  // Continue the search
395
- const finalBase = getFinalBaseTypeWithProps(type);
383
+ const finalBase = getFinalTypeInfo(type);
396
384
  if (!finalBase) // Reference has no proper type, e.g. due to `type of View:calculated`.
397
385
  return _cacheResolved(null);
398
386
 
@@ -561,28 +549,6 @@ function forEachMember( construct, callback, path = [], ignoreIgnore = true, ite
561
549
  }
562
550
  }
563
551
 
564
- /**
565
- * Call `forEachMember` and then apply `forEachMember` on queries.
566
- *
567
- * @param {CSN.Artifact} construct
568
- * @param {genericCallback|genericCallback[]} callback
569
- * @param {CSN.Path} [path]
570
- * @param {boolean} [ignoreIgnore]
571
- * @param {object} iterateOptions can be used to skip certain kinds from being iterated
572
- * @param {constructCallback|constructCallback[]} callback
573
- */
574
- function forEachMemberWithQuery( construct, callback, path = [], ignoreIgnore = true, iterateOptions = {},
575
- constructCallback = (_construct, _prop, _path) => {} ) {
576
- forEachMember(construct, callback, path, ignoreIgnore, iterateOptions, constructCallback);
577
- if (construct.query) {
578
- forAllQueries(construct.query, (q, p) => {
579
- const s = q.SELECT;
580
- if (s)
581
- forEachMember(s, callback, p, ignoreIgnore, iterateOptions);
582
- }, [ ...path, 'query' ]);
583
- }
584
- }
585
-
586
552
  /**
587
553
  * Apply function `callback(member, memberName)` to each member in `construct`,
588
554
  * recursively (i.e. also for sub-elements of elements).
@@ -606,30 +572,6 @@ function forEachMemberRecursively( construct, callback, path = [], ignoreIgnore
606
572
  }, path, ignoreIgnore, iterateOptions, constructCallback);
607
573
  }
608
574
 
609
- /**
610
- * Apply function `callback(member, memberName)` to each member in `construct`,
611
- * recursively (i.e. also for sub-elements of elements).
612
- * Recursively iterate over elements of `construct` query.
613
- *
614
- * @param {CSN.Artifact} construct
615
- * @param {genericCallback|genericCallback[]} callback
616
- * @param {CSN.Path} [path]
617
- * @param {boolean} [ignoreIgnore]
618
- * @param {object} iterateOptions can be used to skip certain kinds from being iterated
619
- * @param {constructCallback|constructCallback[]} callback
620
- */
621
- function forEachMemberRecursivelyWithQuery( construct, callback, path = [], ignoreIgnore = true, iterateOptions = {},
622
- constructCallback = (_construct, _prop, _path) => {} ) {
623
- forEachMemberRecursively(construct, callback, path, ignoreIgnore, iterateOptions, constructCallback);
624
- if (construct.query) {
625
- forAllQueries(construct.query, (q, p) => {
626
- const s = q.SELECT;
627
- if (s)
628
- forEachMemberRecursively(s, callback, p, ignoreIgnore, iterateOptions);
629
- }, [ ...path, 'query' ]);
630
- }
631
- }
632
-
633
575
  /**
634
576
  * Apply function `callback` to all objects in dictionary `dict`, including all
635
577
  * duplicates (found under the same name). Function `callback` is called with
@@ -767,6 +709,8 @@ function isEdmPropertyRendered( elementCsn, options ) {
767
709
  // FKs are rendered in
768
710
  // V2/V4 flat: always on
769
711
  // V4 struct: on/off
712
+ if (elementCsn == null)
713
+ return false;
770
714
  const renderForeignKey = (options.odataVersion === 'v4' && options.odataFormat === 'structured') ? !!options.odataForeignKeys : true;
771
715
  const isNotIgnored = !elementCsn.target ? !elementCsn['@cds.api.ignore'] : true;
772
716
  const isNavigable = elementCsn.target
@@ -1398,6 +1342,28 @@ function isDeepEqual( obj, other, noExtendedProps ) {
1398
1342
  return true;
1399
1343
  }
1400
1344
 
1345
+ /**
1346
+ * convert a cardinality object to string representation
1347
+ * @param {object} node
1348
+ * @param {boolean} withSrc
1349
+ * @returns {string} cardinality as string
1350
+ */
1351
+ function cardinality2str( node, withSrc = true ) {
1352
+ const ofto = node.type === 'cds.Composition' ? 'of' : 'to';
1353
+ if (node.cardinality == null || (node.cardinality.src == null || !withSrc) && node.cardinality.min == null && node.cardinality.max === 1)
1354
+ return `${ ofto } one`;
1355
+ if ((node.cardinality.src == null || !withSrc) && node.cardinality.min == null && node.cardinality.max === '*')
1356
+ return `${ ofto } many`;
1357
+ let s = '[';
1358
+ if (node.cardinality.src != null && withSrc)
1359
+ s += `${ node.cardinality.src },`;
1360
+ if (node.cardinality.min != null)
1361
+ s += `${ node.cardinality.min }..`;
1362
+ if (node.cardinality.max != null)
1363
+ s += `${ node.cardinality.max }]`;
1364
+ return s;
1365
+ }
1366
+
1401
1367
  /**
1402
1368
  * Returns a function that, if called, calls all functions inside
1403
1369
  * the given `functions` array with the same arguments.
@@ -1422,9 +1388,7 @@ module.exports = {
1422
1388
  forEachGeneric,
1423
1389
  forEachDefinition,
1424
1390
  forEachMember,
1425
- forEachMemberWithQuery,
1426
1391
  forEachMemberRecursively,
1427
- forEachMemberRecursivelyWithQuery,
1428
1392
  forAllQueries,
1429
1393
  hasAnnotationValue,
1430
1394
  isEdmPropertyRendered,
@@ -1457,4 +1421,5 @@ module.exports = {
1457
1421
  implicitAs,
1458
1422
  isDeepEqual,
1459
1423
  functionList,
1424
+ cardinality2str,
1460
1425
  };