@sap/cds-compiler 5.4.4 → 5.5.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 (40) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/bin/cds_remove_invalid_whitespace.js +4 -4
  3. package/bin/cds_update_annotations.js +3 -3
  4. package/bin/cds_update_identifiers.js +3 -3
  5. package/lib/api/main.js +18 -30
  6. package/lib/api/validate.js +6 -1
  7. package/lib/base/lazyload.js +28 -0
  8. package/lib/base/location.js +1 -0
  9. package/lib/base/message-registry.js +47 -11
  10. package/lib/base/messages.js +17 -3
  11. package/lib/checks/cdsMap.js +27 -0
  12. package/lib/checks/{dbFeatureFlags.js → featureFlags.js} +1 -1
  13. package/lib/checks/parameters.js +61 -4
  14. package/lib/checks/validator.js +17 -7
  15. package/lib/compiler/define.js +1 -0
  16. package/lib/compiler/index.js +7 -7
  17. package/lib/gen/BaseParser.js +345 -235
  18. package/lib/gen/CdlParser.js +4438 -4492
  19. package/lib/gen/Dictionary.json +2 -2
  20. package/lib/language/antlrParser.js +2 -111
  21. package/lib/main.js +16 -37
  22. package/lib/model/cloneCsn.js +1 -5
  23. package/lib/modelCompare/utils/filter.js +47 -21
  24. package/lib/parsers/AstBuildingParser.js +92 -73
  25. package/lib/parsers/CdlGrammar.g4 +110 -137
  26. package/lib/parsers/index.js +123 -0
  27. package/lib/render/toSql.js +8 -2
  28. package/lib/render/utils/delta.js +33 -1
  29. package/lib/transform/db/{transformExists.js → assocsToQueries/transformExists.js} +12 -407
  30. package/lib/transform/db/assocsToQueries/utils.js +440 -0
  31. package/lib/transform/db/expansion.js +2 -2
  32. package/lib/transform/draft/db.js +14 -3
  33. package/lib/transform/effective/annotations.js +3 -3
  34. package/lib/transform/effective/main.js +5 -7
  35. package/lib/transform/featureFlags.js +5 -0
  36. package/lib/transform/forRelationalDB.js +125 -192
  37. package/lib/transform/transformUtils.js +0 -51
  38. package/lib/utils/objectUtils.js +13 -0
  39. package/package.json +2 -2
  40. package/lib/transform/db/featureFlags.js +0 -5
@@ -9,6 +9,9 @@ options {
9
9
  const { XsnSource, XsnArtifact, XsnName } = require( '../compiler/xsn-model' );
10
10
  const AstBuildingParser = require('../parsers/AstBuildingParser');
11
11
  }
12
+ @footer{
13
+ module.exports = { CdlParser }; // make it work with lazyload()
14
+ }
12
15
 
13
16
  // Content:
14
17
  // - top-level: USING, NAMESPACE, artifactDefOrExtend (start rule: start)
@@ -205,7 +208,7 @@ aspectDef[ art, outer ]
205
208
  @finally{ this.attachLocation( $art ); }
206
209
  :
207
210
  ( ASPECT
208
- | <hide> ABSTRACT { this.warning( 'syntax-deprecated-abstract', this.lb().location ); }
211
+ | <hide> ABSTRACT { this.warning( 'syntax-deprecated-abstract', this.combineLocation( this.lb(), this.la() ) ); }
209
212
  ENTITY
210
213
  )
211
214
  name=namePath[ 'Type' ] // TODO: Type?
@@ -213,23 +216,17 @@ aspectDef[ art, outer ]
213
216
  { this.docComment( $art ); } annoAssignMid[ $art ]*
214
217
  (
215
218
  elementsBlock[ $art ]
216
- actionsBlock[ $art ]?
217
219
  |
218
- ':' aspectColonSpec[ $art ]
219
- )?
220
- ;
221
-
222
- aspectColonSpec[ art ]
223
- options{ minTokensMatched = 1 }
224
- :
225
- (
226
- incl=simplePath { $art.includes ??= []; $art.includes.push( $incl ); }
227
- ( ',' | <exitLoop> )
228
- )*
229
- (
220
+ <exitRule>
221
+ |
222
+ ':'
223
+ (
224
+ incl=simplePath { $art.includes ??= []; $art.includes.push( $incl ); }
225
+ ( ',' | <exitLoop> | <exitRule> )
226
+ )*
230
227
  elementsBlock[ $art ]
231
- actionsBlock[ $art ]?
232
- )? // TODO: no rule end after ','
228
+ )
229
+ actionsBlock[ $art ]?
233
230
  ;
234
231
 
235
232
  entityDef[ art, outer ]
@@ -419,9 +416,9 @@ elementDef[ outer, art = undefined ]
419
416
  { this.docComment( $art ); } annoAssignStd[ $art ]*
420
417
  ( VIRTUAL { $art.virtual = this.valueWithLocation( true ); } )?
421
418
  ( KEY { $art.key = this.valueWithLocation( true ); } )?
422
- ( MASKED { $art.masked = this.valueWithLocation( true ); }
419
+ ( <hide> MASKED { $art.masked = this.valueWithLocation( true ); }
423
420
  { this.message( 'syntax-unsupported-masked', this.lb(), { keyword: 'masked' } ); } )?
424
- ( ELEMENT { $art.$syntax = 'element'; } )?
421
+ ( <hide> ELEMENT { $art.$syntax = 'element'; } )?
425
422
  Id['Element'] <prepare=elementRestriction, arg=elem>
426
423
  { this.addDef( $art, $outer, 'elements', 'element', this.identAst() ); }
427
424
  { this.docComment( $art ); } annoAssignMid[ $art ]*
@@ -429,7 +426,7 @@ elementDef[ outer, art = undefined ]
429
426
  elementsBlock[ $art ]
430
427
  nullability[ $art ]?
431
428
  |
432
- ':' typeExpression[ $art ] // was elementType, with NOT? NULL / DEFAULT
429
+ ':' typeExpression[ $art ] // includes DEFAULT
433
430
  )?
434
431
  (
435
432
  <cond=elementRestriction, arg=calc> '='
@@ -440,9 +437,7 @@ elementDef[ outer, art = undefined ]
440
437
  // TODO: why have `stored` as property of the value?
441
438
  { if (this.elementRestriction( true, 'anno' )) this.docComment( $art ); }
442
439
  ( <cond=elementRestriction, arg=anno> annoAssignStd[ $art ] )*
443
- |
444
- { this.docComment( $art, 'type' ); } // delayed from type expression
445
- )
440
+ )?
446
441
  ;
447
442
 
448
443
  enumSymbolsBlock[ art ]
@@ -464,10 +459,10 @@ enumSymbolDef[ outer ] locals[ art = new XsnArtifact() ]
464
459
  { this.docComment( $art ); } annoAssignStd[ $art ]*
465
460
  ( '='
466
461
  (
467
- <priority> String
462
+ <prefer> String
468
463
  { $art.value = this.quotedLiteral(); }
469
464
  |
470
- <priority> Number
465
+ <prefer> Number
471
466
  { $art.value = this.numberLiteral(); }
472
467
  |
473
468
  sign='+'/'-' Number
@@ -520,7 +515,7 @@ mixinElementDef[ outer ] locals[ art = new XsnArtifact() ]
520
515
  // Annotate and Extend: main definitions ----------------------------------------
521
516
 
522
517
  annotateArtifact[ art, outer ]
523
- @finally{ this.checkWith( $keyword ); this.attachLocation( $art ); }
518
+ @finally{ this.attachLocation( $art ); }
524
519
  :
525
520
  name=namePath[ 'Ext' ]
526
521
  ( // direct element annotation:
@@ -543,10 +538,11 @@ annotateArtifact[ art, outer ]
543
538
  annotateActionsBlock[ $art ]?
544
539
  )
545
540
  )
541
+ { this.checkWith( $keyword ); }
546
542
  ;
547
543
 
548
544
  extendArtifact[ art, outer ]
549
- @finally{ this.checkWith( $keyword ); this.attachLocation( $art ); }
545
+ @finally{ this.attachLocation( $art ); }
550
546
  :
551
547
  name=namePath[ 'Ext' ]
552
548
  ( // direct element annotation:
@@ -591,6 +587,7 @@ extendArtifact[ art, outer ]
591
587
  DEFINITIONS artifactsBlock[ $art, this.lb() ]
592
588
  )?
593
589
  )
590
+ { this.checkWith( $keyword ); }
594
591
  ;
595
592
 
596
593
  extendService[ art, outer ]
@@ -803,7 +800,7 @@ typeOrIncludesSpec[ art ]
803
800
  (
804
801
  typeExpression[ $art ]
805
802
  |
806
- <priority>
803
+ <prefer>
807
804
  ref=simplePath { $art.type = $ref; }
808
805
  (
809
806
  // <default> does not work here
@@ -830,122 +827,103 @@ typeOrIncludesSpec[ art ]
830
827
  )
831
828
  ;
832
829
 
833
- // Type expression (after the `:`) and “final” annotation assignment. Type
834
- // expressions include `null`/`not null` and `default`, the latter is forbidden in
835
- // the `returns` type.
830
+ // Type expression (after the `:`), including `null`/`not null` and `default`;
831
+ // the latter is forbidden in the `returns` type.
832
+ //
833
+ // This rule also parses annotation assignments and doc comments after the
834
+ // type/target reference and after each type property; exceptions are:
835
+ // - not after element and `enum` blocks (would interfere with optional `;`)
836
+ // - no further type property after `many …` @assignment`, because the
837
+ // annotations are attached to the element, the type properties to the line type
836
838
  //
837
839
  // If used in a definition with additional clauses (currently just `= expr` for
838
840
  // elements), these clauses must be guarded with <cond=…>.
839
841
  //
840
- // No annotation assignments are allowed after element and enum blocks, because
841
- // these would conflict with the optional `;` after those blocks. To lower the
842
- // impact with enums, the “final” annotation assignments are actually moved before
843
- // the keyword `enum` (only allowed if not after `many`/`array of`).
844
- //
845
- // This rule is not used when the type expression is restricted: CDL-style cast in
846
- // `select` items, `cast` function, `mixin` definition.
842
+ // This rule is for element, type, (input and `returns`) parameter and annotation
843
+ // definitions. It is not used when the type expression is restricted: CDL-style
844
+ // cast in `select` items, `cast` function, `mixin` definition.
847
845
 
848
846
  typeExpression[ art ]
849
- // TODO: really introduce <exitRule>
850
847
  :
851
- elementsBlock[ $art ] <prepare=elementRestriction, arg=calc>
852
- nullability[ $art ]?
853
- |
854
848
  ( typeRefOptArgs[ $art ] | typeTypeOf[ $art ] )
855
849
  (<altRuleStart>)
856
- nullability[ $art ]?
857
- (
858
- nullabilityAndDefault[ $art ]
859
- // TODO TOOL: with <exitRule>, we could move the following to the end
860
- (
861
- { this.elementRestriction( false, 'calc' ); }
862
- // <prepare=elementRestriction, arg=calc>
863
- { this.docComment( $art ); } annoAssignStd[ $art ]+
864
- |
865
- // We could still have the calc expression after this → delay
866
- { this.docComment( $art, 'elem' ); }
867
- )
868
- |
869
- { this.docComment( $art, 'elem' ); }
870
- |
871
- { this.docComment( $art ); } annoAssignStd[ $art ]+ // +!
872
- ( enumSymbolsBlock[ $art ] <prepare=elementRestriction, arg=anno>
873
- // TODO TOOL: written?
874
- nullabilityAndDefault[ $art ]?
850
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
851
+ ( <cond=elementRestriction, arg=notNull> nullability[ $art ]
852
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
853
+ )?
854
+ ( enumSymbolsBlock[ $art ] <prepare=elementRestriction, arg=anno>
855
+ ( <cond=elementRestriction, arg=notNull> nullability[ $art ] )?
856
+ ( <cond=elementRestriction, arg=default>
857
+ DEFAULT expr=expression { $art.default = $expr; }
875
858
  )?
876
- |
877
- { this.docComment( $art ); }
878
- enumSymbolsBlock[ $art ] <prepare=elementRestriction, arg=anno>
879
- nullabilityAndDefault[ $art ]?
880
- )
859
+ ( <cond=elementRestriction, arg=notNull> nullability[ $art ] )?
860
+ | typeProperties[ $art ]
861
+ )?
881
862
  |
882
- LOCALIZED
883
- { $art.localized = this.valueWithLocation( true ); }
884
- typeRefOptArgs[ $art ] // TODO: why no TYPE OF ?
885
- nullabilityAndDefault[ $art ]?
886
- // TODO TOOL: with <exitRule>, we could move the following to the end
887
- (
888
- { this.elementRestriction( false, 'calc' ); }
889
- // <prepare=elementRestriction, arg=calc>
890
- { this.docComment( $art ); } annoAssignStd[ $art ]+
891
- |
892
- { this.docComment( $art, 'elem' ); }
893
- )
863
+ LOCALIZED { $art.localized = this.valueWithLocation( true ); }
864
+ typeRefOptArgs[ $art ] // no TYPE OF
865
+ { this.docComment( $art ); }
866
+ typeProperties[ $art ]?
894
867
  |
895
868
  assoc=ASSOCIATION <prepare=elementRestriction, arg=calc>
896
869
  cardinality[ $art ]? TO card=ONE/MANY?
897
- target=simplePath { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
898
- ( ON cond=condition { $art.on = $cond; }
899
- | foreignKeysBlock[ $art ]?
900
- nullabilityAndDefault[ $art ]?
901
- // scalar default ? - hm..., what about calc expression?
902
- )
903
- // final anno assignments allowed also with fkeys (no auto-`;` after them):
904
- // (the "TODO TOOL" code for doc/annos would also be fine)
905
- { this.docComment( $art ); } annoAssignStd[ $art ]*
870
+ typeAssocProperties[ $art, $assoc, $card ]
906
871
  |
907
872
  assoc=COMPOSITION <prepare=elementRestriction, arg=calc>
908
873
  cardinality[ $art ]? OF card=ONE/MANY?
909
- (
910
- target=simplePath { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
911
- ( ON cond=condition { $art.on = $cond; }
912
- | foreignKeysBlock[ $art ]?
913
- nullabilityAndDefault[ $art ]?
914
- )
915
- // final anno assignments allowed also with fkeys (no auto-`;` after them):
916
- // (the "TODO TOOL" code for doc/annos would also be fine)
917
- { this.docComment( $art ); } annoAssignStd[ $art ]*
918
- |
919
- { $target = { location: this.startLocation( this.la() ) };
920
- this.setAssocAndComposition( $art, $assoc, $card, $target ); }
921
- elementsBlock[ $target ]
922
- { $target.location = $target.elements[Symbol.for('cds.$location')]; }
874
+ ( typeAssocProperties[ $art, $assoc, $card ]
875
+ | elementsBlock[ this.setAssocAndComposition( $art, $assoc, $card ) ]
876
+ { $art.target.location = $art.target.elements[Symbol.for('cds.$location')]; }
923
877
  )
924
878
  |
925
879
  ( ARRAY <prepare=elementRestriction, arg=calc>
926
880
  OF { $art.items = { location: this.locationOfPrevTokens( 2 ) }; }
927
881
  | MANY <prepare=elementRestriction, arg=calc>
928
882
  { $art.items = { location: this.lb().location }; }
929
- )
883
+ ) // no anno assignments, except to end type expression
930
884
  (
931
- elementsBlock[ $art.items ]
932
- nullability[ $art.items ]?
933
- |
934
885
  ( typeRefOptArgs[ $art.items ] | typeTypeOf[ $art.items ] )
935
- nullability[ $art.items ]?
936
- ( enumSymbolsBlock[ $art.items ]
937
- nullability[ $art.items ]?
938
- |
939
- // TODO TOOL: with <exitRule>, we could move the following to the end
940
- { this.elementRestriction( false, 'calc' ); }
941
- // <prepare=elementRestriction, arg=calc>
942
- { this.docComment( $art ); } annoAssignStd[ $art ]+
943
- |
944
- { this.docComment( $art, 'elem' ); }
886
+ ( <cond=elementRestriction, arg=notNull> nullability[ $art.items ] )?
887
+ ( { this.docComment( $art ); } annoAssignStd[ $art ]*
888
+ { ; } <exitRule> // TODO TOOL: make it work without workaround { ; }
889
+ // TODO TOOL: investigate why simply `{} <exitRule>` is ignored
890
+ | enumSymbolsBlock[ $art.items ]
945
891
  )
892
+ |
893
+ elementsBlock[ $art.items ]
946
894
  )
895
+ ( <cond=elementRestriction, arg=notNull> nullability[ $art.items ] )?
896
+ |
897
+ elementsBlock[ $art ] <prepare=elementRestriction, arg=calc>
898
+ nullability[ $art ]?
947
899
  ;
948
900
 
901
+ typeAssocProperties[ art, assoc, card ]
902
+ :
903
+ target=simplePath { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
904
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
905
+ ( ON cond=condition { $art.on = $cond; }
906
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
907
+ | foreignKeysBlock[ $art ] { this.docComment( $art ); } typeProperties[ $art ]?
908
+ // remark: no auto-`;` after foreign keys → anno assignment after it possible
909
+ | typeProperties[ $art ]
910
+ )?
911
+ ;
912
+
913
+ typeProperties[ art ]
914
+ :
915
+ (
916
+ annoAssignStd[ $art ]
917
+ |
918
+ <cond=elementRestriction, arg=notNull>
919
+ nullability[ $art ] { this.docComment( $art ); }
920
+ |
921
+ <cond=elementRestriction, arg=default>
922
+ DEFAULT expr=expression { $art.default = $expr; this.docComment( $art ); }
923
+ )+
924
+ ;
925
+
926
+
949
927
  typeTypeOf[ art ] locals[ location ]
950
928
  :
951
929
  TYPE OF { location = this.locationOfPrevTokens( 2 ); }
@@ -1049,7 +1027,7 @@ targetCardinality[ card, atAlt = false ]
1049
1027
  '*' { $card.targetMax = this.valueWithLocation(); }
1050
1028
  |
1051
1029
  Number { $card.targetMax = this.unsignedIntegerLiteral(); }
1052
- ( <altRuleStart> ) // TODO TOOL: robust error when moved to after '('
1030
+ (<altRuleStart>) // TODO TOOL: robust error when moved to after '('
1053
1031
  (
1054
1032
  '..' { $card.targetMin = $card.targetMax; }
1055
1033
  ( '*' { $card.targetMax = this.valueWithLocation(); }
@@ -1059,20 +1037,6 @@ targetCardinality[ card, atAlt = false ]
1059
1037
  )
1060
1038
  ;
1061
1039
 
1062
- nullabilityAndDefault[ art ]
1063
- :
1064
- nullability[ $art ]
1065
- ( <cond=elementRestriction, arg=default> DEFAULT expr=expression
1066
- { $art.default = $expr; }
1067
- )?
1068
- |
1069
- <cond=elementRestriction, arg=default> DEFAULT expr=expression
1070
- { $art.default = $expr; }
1071
- nullability[ $art ]?
1072
- // TODO TOOL: when `followUnion` does not contain `Id`, `RuleEnd_` does not
1073
- // need to induce prediction (here for `default`).
1074
- ;
1075
-
1076
1040
  nullability[ art ]
1077
1041
  :
1078
1042
  NULL { this.setNullability( $art, false ); }
@@ -1153,6 +1117,8 @@ selectQuery returns[ default query = {} ]
1153
1117
  excludingClause[ $query ]?
1154
1118
  |
1155
1119
  ( ALL/DISTINCT { $query.quantifier = this.valueWithLocation(); } )?
1120
+ // TODO TOOL: move <prepare> to all branches if "simple", or with special <…,attach>
1121
+ {;} <prepare=inSelectItem, arg=sqlStyle>
1156
1122
  ( '*' { $query.columns = [ this.valueWithLocation() ]; }
1157
1123
  | selectItemDef[ ($query.columns = []) ]
1158
1124
  )
@@ -1209,7 +1175,7 @@ tableExpression returns[ default expr = {} ] // TableOrJoin
1209
1175
  tableOrQueryParens returns[ default expr ]
1210
1176
  :
1211
1177
  '(' <prepare=queryOnLeft>
1212
- ( <priority> tableOrQueryParens[ ...$ ]
1178
+ ( <prefer> tableOrQueryParens[ ...$ ]
1213
1179
  ( tableExpression[ ...$ ]<atAltStart, prepare=queryOnLeft, arg=table>
1214
1180
  | <cond=queryOnLeft> queryExpression[ ...$ ]<atAltStart>
1215
1181
  )?
@@ -1498,8 +1464,6 @@ condition returns[ default expr ]
1498
1464
  expression[ ...$ ]
1499
1465
  ;
1500
1466
 
1501
- // TODO TOOL: sort rules if a rule with <altRuleStart> uses rule with <altRuleStart>
1502
- // at least issue error if no user-sorted
1503
1467
  valuePath returns[ default expr = { path: [] } ] locals[ pathItem ]
1504
1468
  @finally{ this.attachLocation( $expr ); }
1505
1469
  :
@@ -1619,7 +1583,7 @@ expression returns[ default expr = {} ]
1619
1583
  expressionOrQueryParens returns[ default expr ]
1620
1584
  :
1621
1585
  '(' <prepare=queryOnLeft>
1622
- ( <priority> expressionOrQueryParens[ ...$ ]
1586
+ ( <prefer> expressionOrQueryParens[ ...$ ]
1623
1587
  ( expression[ ...$ ]<atAltStart, prepare=queryOnLeft, arg=expr>
1624
1588
  continueExpressionslist[ ...$ ]?
1625
1589
  | continueExpressionslist[ ...$ ] <prepare=queryOnLeft, arg=expr>
@@ -1732,7 +1696,7 @@ options{ minTokensMatched = 1 }
1732
1696
  )*
1733
1697
  )
1734
1698
  )
1735
- ')'
1699
+ ')' { this.finalizeDictOrArray( $pathStep.args ); }
1736
1700
  )?
1737
1701
  // TODO: not with function!
1738
1702
  cardinalityAndFilter[ ...$ ]?
@@ -2000,7 +1964,8 @@ annoValue returns[ default value = {} ]
2000
1964
  }
2001
1965
  ( ',' | <exitLoop> )
2002
1966
  )*
2003
- // ( <cond=TODO> '}' ) // TODO TOOL - workaround:
1967
+ // TODO TOOL: allow:
1968
+ // ( <cond=arrayAnno, …> '}' | <error> ) // TODO TOOL - workaround:
2004
1969
  { this.ec( 'arrayAnno', 'orNotEmpty' ); } '}'
2005
1970
  // Do NOT use <prepare=afterBrace> here!
2006
1971
  |
@@ -2011,11 +1976,19 @@ annoValue returns[ default value = {} ]
2011
1976
  ( sub=annoValue { $value.val.push( $sub ) }
2012
1977
  |
2013
1978
  <cond=arrayAnno, arg=ellipsis> ellipsis='...'
2014
- ( UP TO upTo=annoValue | { $upTo = undefined; } )
2015
- { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
1979
+ ( UP TO upTo=annoValue
1980
+ { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
1981
+ | { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location } ); }
1982
+ )
1983
+ // TODO TOOL: if at last good state the command is ['g'],resume after the
1984
+ // gotos, do not execute its actions - ?
1985
+ // ( UP TO upTo=annoValue | { $upTo = undefined; } )
1986
+ // { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
2016
1987
  )
2017
1988
  ( ',' | <exitLoop> )
2018
1989
  )*
1990
+ // TODO TOOL: allow ( <cond=arrayAnno, arg=bracket> ']' )
1991
+ { this.ec( 'arrayAnno', 'bracket' ); }<always>
2019
1992
  ']'
2020
1993
  |
2021
1994
  '(' $value=condition ')' { $value.$tokenTexts = this.ruleTokensText(); }
@@ -0,0 +1,123 @@
1
+ // Entry point to parsers
2
+
3
+ 'use strict';
4
+
5
+ const lazyload = require('../base/lazyload')( module );
6
+ const { CompilerAssertion } = require( '../base/error' );
7
+ const { createMessageFunctions } = require( '../base/messages' );
8
+ const { XsnSource } = require('../compiler/xsn-model');
9
+
10
+ const parseWithAntlr = lazyload('../language/antlrParser');
11
+ const CdlLexer = require( './Lexer' );
12
+ const gen = lazyload( '../gen/CdlParser' );
13
+
14
+ const rules = {
15
+ cdl: { func: 'start', returns: 'source', $frontend: 'cdl' },
16
+ query: { func: 'queryEOF', returns: 'query' },
17
+ expr: { func: 'conditionEOF', returns: 'cond' }, // yes, condition
18
+ };
19
+
20
+ function parseCdl( source, filename = '<undefined>.cds',
21
+ options = {}, messageFunctions = null,
22
+ rule = 'cdl' ) {
23
+ const rulespec = rules[rule];
24
+ if (!options.newParser)
25
+ return parseWithAntlr( source, filename, options, messageFunctions, rulespec );
26
+ const { CdlParser } = gen;
27
+ if (CdlParser.tracingParser) // tracing → direct console output of message
28
+ messageFunctions = createMessageFunctions( {}, 'parse', {} );
29
+
30
+ const lexer = new CdlLexer( filename, source );
31
+ const parser = new CdlParser( lexer, options, messageFunctions ).init();
32
+ parser.filename = filename; // LSP compatibility
33
+
34
+ // For LSP:
35
+ const { parseListener, attachTokens } = options;
36
+ if (parseListener || attachTokens)
37
+ setTokenStream( parser, lexer );
38
+ if (parseListener)
39
+ setParseListener( parser, parseListener );
40
+
41
+ const result = {};
42
+ try {
43
+ parser[rulespec.func]( result );
44
+ }
45
+ catch (e) {
46
+ if (!(e instanceof RangeError && /Maximum.*exceeded$/i.test( e.message )))
47
+ throw e;
48
+ messageFunctions.error( 'syntax-invalid-source', { file: filename },
49
+ { '#': 'cdl-stackoverflow' } );
50
+ result[rulespec.returns] = undefined;
51
+ }
52
+ const ast = result[rulespec?.returns] || (rule === 'cdl' ? new XsnSource( 'cdl' ) : {} );
53
+ ast.options = options;
54
+ if (attachTokens === true || attachTokens === filename)
55
+ ast.tokenStream = parser._input;
56
+ return ast;
57
+ }
58
+
59
+ function setTokenStream( parser, lexer ) {
60
+ const combined = [];
61
+ const { tokens, comments, docComments } = parser;
62
+ const length = tokens.length + comments.length + docComments.length;
63
+ let tokenIdx = 0;
64
+ let commentIdx = 0;
65
+ let docCommentIdx = 0;
66
+ for (let index = 0; index < length; ++index) {
67
+ if (tokens[tokenIdx].location.tokenIndex === index) // EOF has largest tokenIndex
68
+ combined.push( tokens[tokenIdx++] );
69
+ else if (comments[commentIdx]?.location.tokenIndex === index)
70
+ combined.push( comments[commentIdx++] );
71
+ else
72
+ combined.push( docComments[docCommentIdx++] );
73
+ }
74
+ if (!combined.at( -1 ))
75
+ throw new CompilerAssertion( 'Invalid values for `tokenIndex`' );
76
+ for (const tok of combined)
77
+ tok.start = lexer.characterPos( tok.location.line, tok.location.col );
78
+
79
+ parser._input = { tokens: combined, lexer }; // lexer for characterPos() in cdshi.js
80
+ parser.getTokenStream = function getTokenStream() {
81
+ return this._input;
82
+ };
83
+ }
84
+
85
+ function setParseListener( parser, parseListener ) {
86
+ const { CdlParser } = gen;
87
+ parser.rule_ = function rule_( ...args ) {
88
+ // TODO: can we use `super` here?
89
+ CdlParser.prototype.rule_.apply( this, args );
90
+ let state = this.s;
91
+ while (typeof this.table[--state] !== 'string')
92
+ ;
93
+ const $ctx = { // ANTLR-like context, TODO LSP: more to add?
94
+ parser: this, // set in generated ANTLR parser for each rule context
95
+ ruleName: this.table[state], // instead of ruleIndex
96
+ start: this.la(), // set in Parser#enterRule
97
+ stop: null,
98
+ };
99
+ parser.stack.at( -1 ).$ctx = $ctx;
100
+ parseListener.enterEveryRule( $ctx );
101
+ };
102
+ parser.exit_ = function exit_( ...args ) {
103
+ const { $ctx } = parser.stack.at( -1 );
104
+ // TODO: what should we do in case of errors?
105
+ $ctx.stop = this.lb();
106
+ parseListener.exitEveryRule( $ctx );
107
+ return CdlParser.prototype.exit_.apply( this, args );
108
+ };
109
+ parser.c = function c( ...args ) { // consume
110
+ const symbol = this.la();
111
+ const result = CdlParser.prototype.c.apply( this, args );
112
+ if (result)
113
+ parseListener.visitTerminal( { symbol } );
114
+ return result;
115
+ };
116
+ parser.skipToken_ = function skipToken_( ...args ) { // skip token in error recovery
117
+ const symbol = this.la();
118
+ CdlParser.prototype.skipToken_.apply( this, args ); // = `++this.tokenIdx`
119
+ parseListener.visitErrorNode( { symbol } );
120
+ };
121
+ }
122
+
123
+ module.exports = { parseCdl };
@@ -559,13 +559,19 @@ function toSqlDdl( csn, options, messageFunctions ) {
559
559
  const add = def.new.target
560
560
  ? render.addAssociations(artifactName, { [eltName]: def.new }, env)
561
561
  : render.addColumnsFromElementsObj(artifactName, { [eltName]: def.new }, env);
562
- addMigration(resultObj, artifactName, true, render.concat(...drop, ...add).map(s => (def.lossy && options.src !== 'hdi' ? `-- [WARNING] this statement could be lossy\n${s}` : s)));
562
+ addMigration(resultObj, artifactName, true, render.concat(...drop, ...add).map(s => (def.lossy !== undefined && options.src !== 'hdi' ? `-- [WARNING] this statement could ${def.lossy ? 'be lossy' : 'fail'}: ${def.details}\n${s}` : s)));
563
563
  }
564
564
  else { // Lossless change: no associations directly affected, no size reduction.
565
- addMigration(resultObj, artifactName, false, render.alterColumns(artifactName, sqlId, def, eltStrNew, eltName, activateAlterMode(env, 'migration')).map(s => (def.lossy ? `-- [WARNING] this statement could be lossy\n${s}` : s)));
565
+ addMigration(resultObj, artifactName, false, render.alterColumns(artifactName, sqlId, def, eltStrNew, eltName, activateAlterMode(env, 'migration')).map(s => (def.lossy !== undefined ? `-- [WARNING] this statement could ${def.lossy ? 'be lossy' : 'fail'}: ${def.details}\n${s}` : s)));
566
566
  }
567
567
  }
568
568
  }
569
+
570
+ if (render.getConsolidatedAlterColumn) {
571
+ const consolidated = render.getConsolidatedAlterColumn(artifactName);
572
+ if (consolidated)
573
+ addMigration(resultObj, artifactName, false, consolidated);
574
+ }
569
575
  }
570
576
 
571
577
  /**
@@ -72,6 +72,15 @@ class DeltaRenderer {
72
72
  * Render column modifications as SQL.
73
73
  */
74
74
  alterColumns(artifactName, columnName, delta, definitionsStr, _eltName, _env) {
75
+ if (Array.isArray(definitionsStr)) {
76
+ const prefix = `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER (`;
77
+ let padding = '';
78
+ for (let i = 0; i < prefix.length; i++)
79
+ padding += ' ';
80
+ const body = definitionsStr.map(s => padding + s).join(',\n').slice(padding.length); // no padding for first part
81
+ const postfix = ');';
82
+ return [ prefix + body + postfix ];
83
+ }
75
84
  return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER (${definitionsStr});` ];
76
85
  }
77
86
 
@@ -123,6 +132,29 @@ class DeltaRenderer {
123
132
  }
124
133
 
125
134
  class DeltaRendererHana extends DeltaRenderer {
135
+ #alters = [];
136
+ #details = [];
137
+
138
+ getConsolidatedAlterColumn(artifactName) {
139
+ if (this.#alters.length === 0)
140
+ return null;
141
+ const result = [ ...this.#details, ...super.alterColumns(artifactName, null, null, this.#alters) ];
142
+ this.#alters = [];
143
+ this.#details = [];
144
+ return result;
145
+ }
146
+
147
+ /**
148
+ * Render column modifications as SQL.
149
+ */
150
+ alterColumns(artifactName, columnName, delta, definitionsStr, _eltName, _env) {
151
+ if (delta.details)
152
+ this.#details.push(`-- [WARNING] this statement could ${delta.lossy ? 'be lossy' : 'fail'}: ${delta.details}`);
153
+
154
+ this.#alters.push(definitionsStr);
155
+ return [];
156
+ }
157
+
126
158
  /**
127
159
  * Render column additions as HANA SQL. Checks for duplicate elements.
128
160
  */
@@ -230,7 +262,7 @@ class DeltaRendererH2 extends DeltaRenderer {
230
262
  /**
231
263
  * Render column modifications as H2 SQL - no ().
232
264
  */
233
- alterColumns(artifactName, columnName, delta, definitionsStr) {
265
+ alterColumns(artifactName, columnName, delta, definitionsStr, _eltName, _env) {
234
266
  return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${definitionsStr};` ];
235
267
  }
236
268
  }