@sap/cds-compiler 3.6.2 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +12 -5
  4. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  5. package/doc/CHANGELOG_BETA.md +35 -2
  6. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  7. package/doc/DeprecatedOptions_v2.md +1 -1
  8. package/doc/NameResolution.md +1 -1
  9. package/lib/api/main.js +63 -23
  10. package/lib/api/options.js +1 -0
  11. package/lib/api/validate.js +5 -0
  12. package/lib/base/dictionaries.js +15 -3
  13. package/lib/base/keywords.js +2 -0
  14. package/lib/base/message-registry.js +120 -34
  15. package/lib/base/messages.js +51 -27
  16. package/lib/base/model.js +4 -2
  17. package/lib/base/shuffle.js +2 -1
  18. package/lib/checks/arrayOfs.js +1 -1
  19. package/lib/checks/defaultValues.js +1 -1
  20. package/lib/checks/elements.js +29 -1
  21. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/onConditions.js +15 -9
  25. package/lib/checks/sql-snippets.js +2 -2
  26. package/lib/checks/types.js +5 -1
  27. package/lib/checks/validator.js +7 -3
  28. package/lib/compiler/assert-consistency.js +42 -26
  29. package/lib/compiler/base.js +50 -4
  30. package/lib/compiler/builtins.js +17 -8
  31. package/lib/compiler/checks.js +241 -246
  32. package/lib/compiler/define.js +113 -146
  33. package/lib/compiler/extend.js +889 -383
  34. package/lib/compiler/finalize-parse-cdl.js +5 -58
  35. package/lib/compiler/index.js +1 -1
  36. package/lib/compiler/kick-start.js +7 -8
  37. package/lib/compiler/populate.js +297 -293
  38. package/lib/compiler/propagator.js +27 -18
  39. package/lib/compiler/resolve.js +146 -463
  40. package/lib/compiler/shared.js +36 -79
  41. package/lib/compiler/tweak-assocs.js +30 -28
  42. package/lib/compiler/utils.js +31 -5
  43. package/lib/edm/annotations/genericTranslation.js +131 -59
  44. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  45. package/lib/edm/csn2edm.js +22 -5
  46. package/lib/edm/edm.js +6 -4
  47. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  48. package/lib/edm/edmPreprocessor.js +42 -26
  49. package/lib/gen/Dictionary.json +38 -2
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -1
  52. package/lib/gen/languageLexer.js +1 -1
  53. package/lib/gen/languageParser.js +4828 -4472
  54. package/lib/inspect/inspectPropagation.js +20 -34
  55. package/lib/json/from-csn.js +140 -44
  56. package/lib/json/to-csn.js +114 -122
  57. package/lib/language/errorStrategy.js +2 -0
  58. package/lib/language/genericAntlrParser.js +156 -36
  59. package/lib/language/language.g4 +100 -58
  60. package/lib/language/textUtils.js +13 -0
  61. package/lib/main.d.ts +43 -3
  62. package/lib/main.js +4 -2
  63. package/lib/model/csnRefs.js +15 -3
  64. package/lib/model/csnUtils.js +12 -74
  65. package/lib/model/revealInternalProperties.js +4 -2
  66. package/lib/modelCompare/compare.js +2 -1
  67. package/lib/optionProcessor.js +3 -0
  68. package/lib/render/manageConstraints.js +5 -2
  69. package/lib/render/toCdl.js +216 -104
  70. package/lib/render/toHdbcds.js +2 -9
  71. package/lib/render/toRename.js +14 -51
  72. package/lib/render/toSql.js +4 -3
  73. package/lib/render/utils/common.js +9 -5
  74. package/lib/transform/braceExpression.js +6 -0
  75. package/lib/transform/db/assertUnique.js +2 -1
  76. package/lib/transform/db/expansion.js +2 -0
  77. package/lib/transform/db/flattening.js +37 -36
  78. package/lib/transform/db/rewriteCalculatedElements.js +600 -0
  79. package/lib/transform/db/transformExists.js +4 -0
  80. package/lib/transform/db/views.js +40 -37
  81. package/lib/transform/forOdataNew.js +20 -15
  82. package/lib/transform/forRelationalDB.js +58 -41
  83. package/lib/transform/odata/typesExposure.js +50 -15
  84. package/lib/transform/parseExpr.js +16 -8
  85. package/lib/transform/transformUtilsNew.js +42 -14
  86. package/lib/transform/translateAssocsToJoins.js +60 -37
  87. package/lib/transform/universalCsn/coreComputed.js +15 -7
  88. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  89. package/package.json +2 -1
@@ -581,17 +581,17 @@ elementDefInner[ art, outer, mightBeEnum ]
581
581
  @after{ this.attachLocation( $art ); }
582
582
  :
583
583
  // VIRTUAL is keyword, except if before the following tokens texts:
584
- { this.setLocalToken( 'VIRTUAL', 'VIRTUAL', /^[:{@=}]$/ ); }
584
+ { this.setLocalToken( 'VIRTUAL', 'VIRTUAL', /^[;:{@=}]$/ ); }
585
585
  ( virtual=VIRTUAL { $art.virtual = this.valueWithTokenLocation( true, $virtual ); } )?
586
586
  ( key=KEY { $art.key = this.valueWithTokenLocation( true, $key ); } )?
587
- { this.setLocalToken( 'MASKED', 'MASKED', /^[:{@=}]$/ ); }
587
+ { this.setLocalToken( 'MASKED', 'MASKED', /^[;:{@=}]$/ ); }
588
588
  ( masked=MASKED
589
589
  {
590
590
  $art.masked = this.valueWithTokenLocation( true, $masked ) ;
591
591
  this.message( 'syntax-unsupported-masked', $masked, { keyword: 'masked' } );
592
592
  }
593
593
  )?
594
- { this.setLocalToken( 'ELEMENT', 'ELEMENT', /^[:{@=}]$/ ); }
594
+ { this.setLocalToken( 'ELEMENT', 'ELEMENT', /^[;:{@=}]$/ ); }
595
595
  ( ELEMENT { $mightBeEnum = false; } )? // auto-recognizable at other places
596
596
  name=ident['Element']
597
597
  { this.addDef( $art, $outer, 'elements', 'element', $name.id );
@@ -610,13 +610,15 @@ elementDefInner[ art, outer, mightBeEnum ]
610
610
  '=' e=expression // SQL has syntax variant using AS - we DO NOT
611
611
  { $art.value = $e.expr;
612
612
  // this.setIntroLocation( eq ); -- future
613
- if ($mightBeEnum && ($e.expr.val !== undefined || $e.expr.sym !== undefined) &&
613
+ if ($mightBeEnum && ($e.expr?.val !== undefined || $e.expr?.sym !== undefined) &&
614
614
  !$virtual && !$key && !$masked && !$art.elements && !$art.type)
615
615
  $art['$'+'syntax'] = 'enum';
616
616
  }
617
617
  { this.docComment( $art ); }
618
618
  annotationAssignment_ll1[ $art ]* // for enum symbol def via EXTEND
619
619
  requiredSemi
620
+ |
621
+ requiredSemi
620
622
  )
621
623
  ;
622
624
 
@@ -716,14 +718,19 @@ elementType[ art ] // TODO: split this monster rule
716
718
  ;
717
719
 
718
720
  elementProperties[ elem ]
721
+ :
722
+ defaultAndNullablity[ $elem ]
723
+ |
724
+ '=' e=expression { $elem.value = $e.expr; }
725
+ ;
726
+
727
+ defaultAndNullablity[ elem ]
719
728
  :
720
729
  defaultValue[ $elem ]
721
730
  nullability[ $elem ]? // placement accoring to SQL spec
722
731
  |
723
732
  nullability[ $elem ]
724
733
  defaultValue[ $elem ]?
725
- |
726
- '=' e=expression { $elem.value = $e.expr; }
727
734
  ;
728
735
 
729
736
  defaultValue[ art ] locals[ elem, elements = {} ]
@@ -761,13 +768,13 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
761
768
  |
762
769
  requiredSemi
763
770
  |
764
- ELEMENTS '{' { $art.elements = this.createDict(); }
771
+ ELEMENTS { $art.elements = this.createDict(); } '{'
765
772
  elementDefOrExtend[ $art ]*
766
773
  '}' { this.finalizeDictOrArray( $art.elements ); }
767
774
  { this.checkExtensionDict( $art.elements ); }
768
775
  optionalSemi
769
776
  |
770
- ENUM '{' { $art.enum = this.createDict(); }
777
+ ENUM { $art.enum = this.createDict(); } '{'
771
778
  enumSymbolDef[ $art ]* // TODO: no EXTEND in enum? (ok, would just allow annos)
772
779
  '}' { this.finalizeDictOrArray( $art.enum ); }
773
780
  optionalSemi
@@ -782,24 +789,22 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
782
789
  includeRef[ $art ] ( ',' includeRef[ $art ] )*
783
790
  requiredSemi
784
791
  |
785
- DEFINITIONS
786
- '{' { $art.artifacts = this.createDict(); }
792
+ DEFINITIONS { $art.artifacts = this.createDict(); } '{'
787
793
  artifactDefOrExtend[ $art, 'definitions' ]*
788
794
  '}' { this.finalizeDictOrArray( $art.artifacts ); }
789
795
  optionalSemi
790
796
  |
791
- COLUMNS
792
- '{' { $art.columns = []; }
797
+ COLUMNS { $art.columns = this.createArray(); } '{'
793
798
  (
794
799
  selectItemDef[ $art.columns ]
795
800
  ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
796
801
  selectItemDef[ $art.columns ]
797
802
  )*
798
803
  )?
799
- '}'
804
+ '}' { this.finalizeDictOrArray( $art.columns ); }
800
805
  optionalSemi
801
806
  |
802
- ACTIONS '{' { $art.actions = this.createDict(); }
807
+ ACTIONS { $art.actions = this.createDict(); } '{'
803
808
  actionFunctionDef[ $art ]* // TODO: no EXTEND in actions? (ok, would just allow annos)
804
809
  '}' { this.finalizeDictOrArray( $art.actions ); }
805
810
  optionalSemi
@@ -811,7 +816,7 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
811
816
  extendService[ art, outer ] locals[ name = {} ]
812
817
  @after { this.attachLocation( $art ); }
813
818
  :
814
- SERVICE { $art.expectedKind = 'service'; }
819
+ SERVICE { $art.expectedKind = this.valueWithTokenLocation(); }
815
820
  simplePath[ $name, 'Service' ]
816
821
  { $art.name = $name; this.addItem( $art, $outer, 'extensions', 'extend' ); }
817
822
  ( WITH { this.noSemicolonHere(); } )?
@@ -821,7 +826,7 @@ extendService[ art, outer ] locals[ name = {} ]
821
826
  extendContext[ art, outer ] locals[ name = {} ]
822
827
  @after { this.attachLocation( $art ); }
823
828
  :
824
- CONTEXT { $art.expectedKind = 'context'; }
829
+ CONTEXT { $art.expectedKind = this.valueWithTokenLocation(); }
825
830
  simplePath[ $name, 'Context' ]
826
831
  { $art.name = $name; this.addItem( $art, $outer, 'extensions', 'extend' ); }
827
832
  ( WITH { this.noSemicolonHere(); } )?
@@ -831,8 +836,9 @@ extendContext[ art, outer ] locals[ name = {} ]
831
836
  extendEntityOrAspect[ art, outer ] locals[ name = {} ]
832
837
  @after { /* #ATN 1 */ this.attachLocation( $art ); }
833
838
  :
834
- kind=(ASPECT | ENTITY) simplePath[ $name, 'Extend' ]
835
- { $art.expectedKind = $kind.text.toLowerCase(); $art.name = $name;
839
+ (ASPECT | ENTITY) { $art.expectedKind = this.valueWithTokenLocation(); }
840
+ simplePath[ $name, 'Extend' ]
841
+ { $art.name = $name;
836
842
  this.addItem( $art, $outer, 'extensions', 'extend' );
837
843
  }
838
844
  (
@@ -858,13 +864,13 @@ extendForEntity[ art ]
858
864
  elementDefOrExtend[ $art ]*
859
865
  '}' { this.finalizeDictOrArray( $art.elements ); }
860
866
  (
861
- ACTIONS '{' { $art.actions = this.createDict(); }
867
+ ACTIONS { $art.actions = this.createDict(); } '{'
862
868
  actionFunctionDef[ $art ]*
863
869
  '}' { this.finalizeDictOrArray( $art.actions ); }
864
870
  )?
865
871
  optionalSemi
866
872
  |
867
- ACTIONS '{' { $art.actions = this.createDict(); }
873
+ ACTIONS { $art.actions = this.createDict(); } '{'
868
874
  actionFunctionDef[ $art ]*
869
875
  '}' { this.finalizeDictOrArray( $art.actions ); }
870
876
  optionalSemi
@@ -875,30 +881,31 @@ extendForEntity[ art ]
875
881
  extendProjection[ art, outer ] locals[ name = {} ]
876
882
  @after { this.attachLocation( $art ); }
877
883
  :
878
- expected=PROJECTION simplePath[ $name, 'Extend' ]
879
- { $art.expectedKind = 'entity'; $art.name = $name;
884
+ PROJECTION { $art.expectedKind = this.valueWithTokenLocation( 'entity' ); }
885
+ simplePath[ $name, 'Extend' ]
886
+ { $art.name = $name;
880
887
  this.addItem( $art, $outer, 'extensions', 'extend' );
881
888
  }
882
889
  ( WITH { this.noSemicolonHere(); } )?
883
890
  { this.docComment( $art ); }
884
891
  annotationAssignment_ll1[ $art ]*
885
892
  (
886
- '{' { $art.columns = []; }
893
+ '{' { $art.columns = this.createArray(); }
887
894
  (
888
895
  selectItemDef[ $art.columns ]
889
896
  ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
890
897
  selectItemDef[ $art.columns ]
891
898
  )*
892
899
  )?
893
- '}'
900
+ '}' { this.finalizeDictOrArray( $art.columns ); }
894
901
  (
895
- ACTIONS '{' { $art.actions = this.createDict(); }
902
+ ACTIONS { $art.actions = this.createDict(); } '{'
896
903
  actionFunctionDef[ $art ]*
897
904
  '}' { this.finalizeDictOrArray( $art.actions ); }
898
905
  )?
899
906
  optionalSemi
900
907
  |
901
- ACTIONS '{' { $art.actions = this.createDict(); }
908
+ ACTIONS { $art.actions = this.createDict(); } '{'
902
909
  actionFunctionDef[ $art ]*
903
910
  '}' { this.finalizeDictOrArray( $art.actions ); }
904
911
  optionalSemi
@@ -910,8 +917,9 @@ extendProjection[ art, outer ] locals[ name = {} ]
910
917
  extendType[ art, outer ] locals[ name = {} ]
911
918
  @after { this.attachLocation( $art ); }
912
919
  :
913
- TYPE simplePath[ $name, 'Extend' ]
914
- { $art.expectedKind = 'type'; $art.name = $name;
920
+ TYPE { $art.expectedKind = this.valueWithTokenLocation(); }
921
+ simplePath[ $name, 'Extend' ]
922
+ { $art.name = $name;
915
923
  this.addItem( $art, $outer, 'extensions', 'extend' );
916
924
  }
917
925
  // extendWithOptElementsOrType + includeRef:
@@ -952,13 +960,13 @@ extendWithOptElementsOrType[ art ]
952
960
  { this.checkExtensionDict( $art.elements ); }
953
961
  optionalSemi
954
962
  |
955
- ELEMENTS '{' { $art.elements = this.createDict(); }
963
+ ELEMENTS { $art.elements = this.createDict(); } '{'
956
964
  elementDefOrExtend[ $art ]*
957
965
  '}' { this.finalizeDictOrArray( $art.elements ); }
958
966
  { this.checkExtensionDict( $art.elements ); }
959
967
  optionalSemi
960
968
  |
961
- ENUM '{' { $art.enum = this.createDict(); }
969
+ ENUM { $art.enum = this.createDict(); } '{'
962
970
  enumSymbolDef[ $art ]* // TODO: no EXTEND in enum? (ok, would just allow annos)
963
971
  '}' { this.finalizeDictOrArray( $art.enum ); }
964
972
  optionalSemi
@@ -1006,7 +1014,7 @@ extendElement[ art, outer ]
1006
1014
  @after{ this.attachLocation( $art ); }
1007
1015
  :
1008
1016
  { this.setLocalToken( 'ELEMENT', 'ELEMENT', /^([:{@=}()]|WITH)$/i ); }
1009
- ( expected=ELEMENT { $art.expectedKind = 'element'; } )?
1017
+ ( ELEMENT { $art.expectedKind = this.valueWithTokenLocation(); } )?
1010
1018
  name=ident['Element']
1011
1019
  { this.addDef( $art, $outer, 'elements', 'extend', $name.id ); }
1012
1020
  extendWithOptElementsOrType[ $art, $art ]
@@ -1016,7 +1024,7 @@ annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
1016
1024
  @after { this.attachLocation( $art ); }
1017
1025
  :
1018
1026
  simplePath[ $name, 'Annotate' ]
1019
- ( ':' simplePath[ $elemName, 'Element'] )?
1027
+ ( ':' simplePath[ $elemName, 'Element'] )? // TODO: do like for extendArtifact !
1020
1028
  { this.addExtension( $art, $outer, 'annotate', $name, $elemName.path ); }
1021
1029
  ( WITH { this.noSemicolonHere(); } )?
1022
1030
  { this.docComment( $art ); }
@@ -1027,16 +1035,14 @@ annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
1027
1035
  '}' { this.finalizeDictOrArray( $art.elements ); }
1028
1036
  { this.checkExtensionDict( $art.elements ); }
1029
1037
  (
1030
- ACTIONS
1031
- '{' { $art.actions = this.createDict(); }
1038
+ ACTIONS { $art.actions = this.createDict(); } '{'
1032
1039
  annotateAction[ $art ]*
1033
1040
  '}' { this.finalizeDictOrArray( $art.actions ); }
1034
1041
  { this.checkExtensionDict( $art.actions ); }
1035
1042
  )?
1036
1043
  optionalSemi
1037
1044
  |
1038
- ACTIONS
1039
- '{' { $art.actions = this.createDict(); }
1045
+ ACTIONS { $art.actions = this.createDict(); } '{'
1040
1046
  annotateAction[ $art ]*
1041
1047
  '}' { this.finalizeDictOrArray( $art.actions ); }
1042
1048
  { this.checkExtensionDict( $art.actions ); }
@@ -1050,8 +1056,8 @@ annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
1050
1056
  ')' { this.finalizeDictOrArray( $art.params ); }
1051
1057
  { this.checkExtensionDict( $art.params ); }
1052
1058
  (
1053
- RETURNS { $art['$'+'syntax'] = 'returns'; }
1054
- '{' { $art.elements = this.createDict(); }
1059
+ // TODO: set proper $art.returns
1060
+ RETURNS { $art.elements = this.createDict(); } { $art['$'+'syntax'] = 'returns'; } '{'
1055
1061
  annotateElement[ $art ]*
1056
1062
  '}' { this.finalizeDictOrArray( $art.elements ); }
1057
1063
  { this.checkExtensionDict( $art.elements ); }
@@ -1060,8 +1066,8 @@ annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
1060
1066
  requiredSemi
1061
1067
  )
1062
1068
  |
1063
- RETURNS { $art['$'+'syntax'] = 'returns'; }
1064
- '{' { $art.elements = this.createDict(); }
1069
+ // TODO: set proper $art.returns
1070
+ RETURNS { $art.elements = this.createDict(); } { $art['$'+'syntax'] = 'returns'; } '{'
1065
1071
  annotateElement[ $art ]*
1066
1072
  '}' { this.finalizeDictOrArray( $art.elements ); }
1067
1073
  { this.checkExtensionDict( $art.elements ); }
@@ -1111,7 +1117,8 @@ annotateAction [ outer ] locals [ art = {} ]
1111
1117
  { this.checkExtensionDict( $art.params ); }
1112
1118
  )?
1113
1119
  (
1114
- RETURNS '{' { $art.elements = this.createDict(); }
1120
+ // TODO: set proper $art.returns
1121
+ RETURNS { $art.elements = this.createDict(); } { $art['$'+'syntax'] = 'returns'; } '{'
1115
1122
  annotateElement[ $art ]*
1116
1123
  '}' { this.finalizeDictOrArray( $art.elements ); }
1117
1124
  { this.checkExtensionDict( $art.elements ); }
@@ -1205,7 +1212,10 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1205
1212
  @after{ /* #ATN 3 */ }
1206
1213
  :
1207
1214
  typeStruct[ $art ]
1208
- optionalSemi
1215
+ ( nullability[ $art ]
1216
+ requiredSemi
1217
+ | optionalSemi
1218
+ )
1209
1219
  |
1210
1220
  ':'
1211
1221
  // #ATN: typeRefOptArgs can start with ARRAY or MANY or ASSOCIATION or TYPE or LOCALIZED
@@ -1213,7 +1223,10 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1213
1223
  { this.setLocalToken( 'MANY', 'HelperToken1', /^[^\{]/ ); }
1214
1224
  (
1215
1225
  typeStruct[ $art ]
1216
- optionalSemi
1226
+ ( nullability[ $art ]
1227
+ requiredSemi
1228
+ | optionalSemi
1229
+ )
1217
1230
  |
1218
1231
  typeAssociationBase[ $art, false ]
1219
1232
  // #ATN: path could start with MANY or ONE - make sure a token follows in same rule!
@@ -1225,7 +1238,7 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1225
1238
  { $art.items = { location: this.tokenLocation( $many ) };}
1226
1239
  typeStruct[ $art.items ]
1227
1240
  nullability[ $art.items ]?
1228
- optionalSemi
1241
+ optionalSemi // TODO(v4): Should be requiredSemi with nullability
1229
1242
  |
1230
1243
  (
1231
1244
  array=ARRAY of=OF
@@ -1236,7 +1249,7 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1236
1249
  // #ATN: typeRefOptArgs can start with TYPE
1237
1250
  ( typeStruct[ $art.items ]
1238
1251
  nullability[ $art.items ]?
1239
- optionalSemi
1252
+ optionalSemi // TODO(v4): Should be requiredSemi with nullability
1240
1253
  | ( typeTypeOf[ $art.items ] | typeRefOptArgs[ $art.items ] )
1241
1254
  nullability[ $art.items ]?
1242
1255
  { this.docComment( $art ); }
@@ -1257,13 +1270,14 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1257
1270
  )
1258
1271
  |
1259
1272
  typeTypeOf[ $art ]
1260
- defaultValue[ $art ]?
1273
+ defaultAndNullablity[ $art ]?
1261
1274
  { this.docComment( $art ); }
1262
- annotationAssignment_ll1[ $art ]* requiredSemi
1275
+ annotationAssignment_ll1[ $art ]*
1276
+ requiredSemi
1263
1277
  |
1264
1278
  l=LOCALIZED { $art.localized = this.valueWithTokenLocation( true, $l ); }
1265
1279
  typeRefOptArgs[ $art ]
1266
- defaultValue[ $art ]?
1280
+ defaultAndNullablity[ $art ]?
1267
1281
  { this.docComment( $art ); }
1268
1282
  annotationAssignment_ll1[ $art ]*
1269
1283
  requiredSemi
@@ -1287,11 +1301,11 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1287
1301
  (
1288
1302
  optionalSemi
1289
1303
  |
1290
- defaultValue[ $art ]
1304
+ defaultAndNullablity[ $art ]
1291
1305
  requiredSemi
1292
1306
  )
1293
1307
  |
1294
- defaultValue[ $art ]?
1308
+ defaultAndNullablity[ $art ]?
1295
1309
  requiredSemi
1296
1310
  )
1297
1311
  |
@@ -1301,7 +1315,10 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1301
1315
  includeRef[ $art ]
1302
1316
  )*
1303
1317
  typeStruct[ $art ]
1304
- optionalSemi
1318
+ ( optionalSemi
1319
+ | nullability[ $art ]
1320
+ requiredSemi
1321
+ )
1305
1322
  )
1306
1323
  )
1307
1324
  ;
@@ -1511,6 +1528,7 @@ typeRefOptArgs[ art ]
1511
1528
  ;
1512
1529
 
1513
1530
  typeRefArgs[ art ]
1531
+ @after { this.checkTypeArgs($art); }
1514
1532
  :
1515
1533
  paren='(' { $art['$'+'typeArgs'] = this.createArray(); }
1516
1534
  (
@@ -1746,7 +1764,7 @@ tableTerm returns [ table ]
1746
1764
  ( AS a1=ident['FromAlias'] { $table.name = $a1.id } // for defining table aliass
1747
1765
  | a2=identNoKeyword['FromAlias'] { $table.name = this.fragileAlias( $a2.id, true ); }
1748
1766
  // not using ident` to have a similar behavior to above
1749
- )
1767
+ )?
1750
1768
  |
1751
1769
  te=tableExpression close=')'
1752
1770
  { $table = this.surroundByParens( $te.table, $open, $close ); }
@@ -2040,8 +2058,21 @@ conditionEOF returns [ cond ]
2040
2058
  c=condition { $cond = $c.cond; } EOF
2041
2059
  ;
2042
2060
 
2043
- condition returns [ cond ] locals[ args = [] ]
2044
- @after{ $cond = this.argsExpression( $args, true ); }
2061
+ condition returns [ cond ] locals [ args = [], orl = [] ]
2062
+ @after{ $cond = this.argsExpression( $args, $ctx.e2 ? '?:' : true ); }
2063
+ :
2064
+ c1=conditionOr { $args.push($c1.cond); }
2065
+ (
2066
+ // '?' is not mentioned in code completion, see errorStrategy.js#intervalSetToArray
2067
+ q='?' { this.pushXprToken( $args ); }
2068
+ e2=expression { $args.push($e2.expr); }
2069
+ colon=':' { this.pushXprToken( $args ); }
2070
+ e3=expression { $args.push($e3.expr); }
2071
+ )?
2072
+ ;
2073
+
2074
+ conditionOr returns [ cond ] locals [ args = [], orl = [] ]
2075
+ @after{ $cond = this.argsExpression( $args, 'or' ); }
2045
2076
  :
2046
2077
  c1=conditionAnd { $args.push($c1.cond); }
2047
2078
  (
@@ -2181,10 +2212,9 @@ expressionTerm returns [ expr ] locals [ args = [] ]
2181
2212
  END { this.pushXprToken( $args ); }
2182
2213
  |
2183
2214
  ne=NEW { this.pushXprToken( $args ); } // token rewrite for NEW
2184
- // please note: there will be no compiler-supported code completion after NEW
2185
- { $expr = { op: this.valueWithTokenLocation( 'new', $ne ), args: [] };
2186
- this.error( 'syntax-unsupported-new', $ne, { keyword: $ne.text }, '$(KEYWORD) is not supported' ); }
2187
2215
  nqp=valuePath[ 'ref', null ]
2216
+ { $args.push( this.valuePathAst( $nqp.qp ) ); }
2217
+ { this.fixNewKeywordPlacement( $args ); }
2188
2218
  |
2189
2219
  vp=valuePath[ 'ref', null ] { $args.push( this.valuePathAst( $vp.qp ) ); }
2190
2220
  { this.setLocalTokenIfBefore( 'OVER', 'OVER', /^\($/i ); }
@@ -2345,7 +2375,7 @@ namedExpression[ pathStep, id ]
2345
2375
  :
2346
2376
  elem=expression
2347
2377
  { if ($pathStep && $id) {
2348
- this.addDef( ($ctx.elem) ? $elem.expr : { location: $id.location },
2378
+ this.addDef( ($ctx.elem && $elem.expr) ? $elem.expr : { location: $id.location },
2349
2379
  $pathStep, 'args', 0, $id );
2350
2380
  }
2351
2381
  }
@@ -2752,6 +2782,13 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
2752
2782
  |
2753
2783
  ( plus='+' | min='-' ) num=Number
2754
2784
  { Object.assign( $assignment, this.numberLiteral( $num, $plus||$min ) ); }
2785
+ |
2786
+ '('
2787
+ cond=condition // 'condition' is also used in 'expression' inside '()'.
2788
+ // TODO: (1,2,3) not supported, yet, only ((1,2,3)); we could support it via:
2789
+ // (',' condition)*
2790
+ { this.expressionAsAnnotationValue( $assignment, $ctx.cond ); }
2791
+ ')'
2755
2792
  ;
2756
2793
 
2757
2794
  flattenedValue[ assignment ] locals[ val = { name: {} } ]
@@ -2807,6 +2844,11 @@ annoSubValue returns[ val = {} ]
2807
2844
  |
2808
2845
  at='@'? annotationPath[ $val, 'ref', $at ]
2809
2846
  ( annotationPathVariant[ $val ] )?
2847
+ |
2848
+ '('
2849
+ cond=condition
2850
+ { this.expressionAsAnnotationValue( $val, $ctx.cond ); }
2851
+ ')'
2810
2852
  ;
2811
2853
 
2812
2854
  // Literal values and identifiers -----------------------------------------------
@@ -37,8 +37,21 @@ function isWhitespaceCharacterNoNewline( char ) {
37
37
  return whitespaceRegEx.test(char);
38
38
  }
39
39
 
40
+ /**
41
+ * Normalized CDL newlines to LF (\n). CDL newlines also contain PS and other
42
+ * characters, see cdlNewLineRegEx.
43
+ *
44
+ * @param {string} str
45
+ * @return {string}
46
+ */
47
+ function normalizeNewLine( str ) {
48
+ // Note: cdlNewLineRegEx does not have `g` flag and we can't use replaceAll in Node 14.
49
+ return str.replace(new RegExp(cdlNewLineRegEx, 'ug'), '\n');
50
+ }
51
+
40
52
  module.exports = {
41
53
  isWhitespaceOrNewLineOnly,
42
54
  isWhitespaceCharacterNoNewline,
43
55
  cdlNewLineRegEx,
56
+ normalizeNewLine,
44
57
  };
package/lib/main.d.ts CHANGED
@@ -818,6 +818,23 @@ declare namespace compiler {
818
818
  * @param dialect
819
819
  */
820
820
  function delimitedId(name: string, dialect: string) : string;
821
+
822
+ /**
823
+ * Return all non-lossy changes in artifacts between two given models.
824
+ * Note: Only supports changes in artifacts compiled/rendered as db-CSN/SQL.
825
+ *
826
+ * ATTENTION: This function may change without prior notice!
827
+ * It is still considered work-in-progress!
828
+ *
829
+ * @beta
830
+ *
831
+ * @param csn A client/inferred CSN model representing the desired "after-image"
832
+ * @param options SQL specific options
833
+ * @param beforeImage A db-transformed CSN representing the "before-image", or null in case no such image
834
+ * is known, i.e. for the very first migration step.
835
+ * @returns See {@link SqlMigrationResult} for details.
836
+ */
837
+ function migration(csn: CSN, options: SqlOptions, beforeImage: CSN): SqlMigrationResult;
821
838
  }
822
839
 
823
840
  /**
@@ -884,10 +901,10 @@ declare namespace compiler {
884
901
  * Return all changes in artifacts between two given models.
885
902
  * Note: Only supports changes in entities (not views etc.) compiled/rendered as HANA-CSN/SQL.
886
903
  *
887
- * @param csn A clean input CSN representing the desired "after-image"
904
+ * @param csn A client/inferred CSN model representing the desired "after-image"
888
905
  * @param options Options
889
- * @param beforeImage A HANA-transformed CSN representing the "before-image", or null in case no such image
890
- * is known, i.e. for the very first migration step
906
+ * @param beforeImage A SAP HANA-transformed CSN representing the "before-image", or null in case
907
+ * no such image is known, i.e. for the very first migration step.
891
908
  * @returns See {@link HdiMigrationResult} for details.
892
909
  */
893
910
  function migration(csn: CSN, options: HdiOptions, beforeImage: CSN): HdiMigrationResult;
@@ -942,6 +959,29 @@ declare namespace compiler {
942
959
  }>,
943
960
  }
944
961
 
962
+ /**
963
+ * Result type of {@link to.sql.migration}.
964
+ *
965
+ * ATTENTION: This structure may change without prior notice!
966
+ * It is still considered work-in-progress!
967
+ *
968
+ * @beta
969
+ */
970
+ export type SqlMigrationResult = {
971
+ /**
972
+ * The desired after-image in db-transformed CSN format
973
+ */
974
+ afterImage: CSN
975
+ /**
976
+ * An array of SQL statements to drop views/tables.
977
+ */
978
+ drops: string[],
979
+ /**
980
+ * An array of SQL statements to ALTER/CREATE tables/views.
981
+ */
982
+ createsAndAlters: string[],
983
+ }
984
+
945
985
  /**
946
986
  * Return the resulting database name for (absolute) 'artifactName', depending on the current naming
947
987
  * mode.
package/lib/main.js CHANGED
@@ -26,6 +26,7 @@ const compiler = lazyload('./compiler');
26
26
  const shared = lazyload('./compiler/shared');
27
27
  const define = lazyload('./compiler/define');
28
28
  const builtins = lazyload('./compiler/builtins');
29
+ const base = lazyload('./compiler/base');
29
30
  const finalizeParseCdl = lazyload('./compiler/finalize-parse-cdl');
30
31
 
31
32
  // The compiler version (taken from package.json)
@@ -157,9 +158,10 @@ module.exports = {
157
158
  // INTERNAL functions for the cds-lsp package and friends - before you use
158
159
  // it, you MUST talk with us - there can be potential incompatibilities with
159
160
  // new releases (even having the same major version):
160
- $lsp: { parse: (...args) => compiler.parseX(...args),
161
+ $lsp: {
162
+ parse: (...args) => compiler.parseX(...args),
161
163
  compile: (...args) => compiler.compileX(...args),
162
- getArtifactName: a => a.name
164
+ getArtifactName: (...args) => base.getArtifactName(...args),
163
165
  },
164
166
 
165
167
  // CSN Model related functionality
@@ -637,10 +637,18 @@ function csnRefs( csn, universalReady ) {
637
637
  /**
638
638
  * @param {CSN.Path} path
639
639
  * @param {CSN.Artifact} art
640
+ * @param {CSN.Artifact} parent
640
641
  * @param {string} [scope]
641
642
  * @param [extraInfo]
642
643
  */
643
644
  function resolvePath( path, art, parent, scope, extraInfo ) {
645
+ if (!art && path.length > 1) {
646
+ // TODO: For path.length===1, it may be that `art` is undefined, e.g. for CSN paths such
647
+ // as `[…, 'on', 1]` where the path segment refers to `=`.
648
+ // TODO: Check the call-side.
649
+ const loc = locationString(parent?.$location);
650
+ throw new ModelError(`Path item 0='${ pathId(path[0]) }' refers to nothing; in ${ loc }; path=${ JSON.stringify(path) }`);
651
+ }
644
652
  const staticAssoc = extraInfo === 'static' && scope === 'global';
645
653
  /** @type {{idx, art?, env?}[]} */
646
654
  const links = path.map( (_v, idx) => ({ idx }) );
@@ -657,7 +665,7 @@ function csnRefs( csn, universalReady ) {
657
665
  if (!art) {
658
666
  const { env } = links[i - 1];
659
667
  const loc = env.name && env.name.$location || env.$location;
660
- throw new ModelError( `Path item ${ i }=${ pathId( path[i] ) } on ${ locationString( loc ) } refers to nothing` );
668
+ throw new ModelError( `Path item ${ i }=${ pathId( path[i] ) } refers to nothing; in ${ locationString( loc ) }; path=${ JSON.stringify(path) }` );
661
669
  }
662
670
  links[i].art = art;
663
671
  }
@@ -706,8 +714,12 @@ function csnRefs( csn, universalReady ) {
706
714
  if (query !== main)
707
715
  cache.set( query, qcache );
708
716
 
709
- if (fromSelect)
710
- getCache( fromSelect, '$aliases' )[query.as] = qcache;
717
+ if (fromSelect) {
718
+ const $queryNumber = all.length + 1;
719
+ const alias = query.as || `$_select_${ $queryNumber }__`;
720
+ getCache(fromSelect, '$aliases')[alias] = qcache;
721
+ }
722
+
711
723
  const select = query.SELECT || query.projection;
712
724
  if (select) {
713
725
  cache.set( select, qcache ); // query and query.SELECT have the same cache qcache