@sap/cds-compiler 5.4.4 → 5.5.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.
- package/CHANGELOG.md +17 -1
- package/bin/cds_remove_invalid_whitespace.js +4 -4
- package/bin/cds_update_annotations.js +3 -3
- package/bin/cds_update_identifiers.js +3 -3
- package/lib/api/main.js +18 -30
- package/lib/api/validate.js +6 -1
- package/lib/base/lazyload.js +28 -0
- package/lib/base/location.js +1 -0
- package/lib/base/message-registry.js +47 -11
- package/lib/base/messages.js +17 -3
- package/lib/checks/{dbFeatureFlags.js → featureFlags.js} +1 -1
- package/lib/checks/parameters.js +61 -4
- package/lib/checks/validator.js +14 -6
- package/lib/compiler/index.js +7 -7
- package/lib/gen/BaseParser.js +345 -235
- package/lib/gen/CdlParser.js +4434 -4492
- package/lib/gen/Dictionary.json +2 -2
- package/lib/language/antlrParser.js +2 -111
- package/lib/main.js +16 -37
- package/lib/modelCompare/utils/filter.js +47 -21
- package/lib/parsers/AstBuildingParser.js +59 -49
- package/lib/parsers/CdlGrammar.g4 +91 -130
- package/lib/parsers/index.js +123 -0
- package/lib/render/toSql.js +8 -2
- package/lib/render/utils/delta.js +33 -1
- package/lib/transform/db/{transformExists.js → assocsToQueries/transformExists.js} +12 -407
- package/lib/transform/db/assocsToQueries/utils.js +440 -0
- package/lib/transform/db/expansion.js +2 -2
- package/lib/transform/draft/db.js +14 -3
- package/lib/transform/effective/annotations.js +3 -3
- package/lib/transform/effective/main.js +5 -7
- package/lib/transform/featureFlags.js +5 -0
- package/lib/transform/forRelationalDB.js +125 -192
- package/lib/transform/transformUtils.js +0 -51
- package/package.json +2 -2
- 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)
|
|
@@ -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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
232
|
-
|
|
228
|
+
)
|
|
229
|
+
actionsBlock[ $art ]?
|
|
233
230
|
;
|
|
234
231
|
|
|
235
232
|
entityDef[ art, outer ]
|
|
@@ -419,7 +416,7 @@ 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
421
|
( ELEMENT { $art.$syntax = 'element'; } )?
|
|
425
422
|
Id['Element'] <prepare=elementRestriction, arg=elem>
|
|
@@ -429,7 +426,7 @@ elementDef[ outer, art = undefined ]
|
|
|
429
426
|
elementsBlock[ $art ]
|
|
430
427
|
nullability[ $art ]?
|
|
431
428
|
|
|
|
432
|
-
':' typeExpression[ $art ] //
|
|
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
|
-
<
|
|
462
|
+
<prefer> String
|
|
468
463
|
{ $art.value = this.quotedLiteral(); }
|
|
469
464
|
|
|
|
470
|
-
<
|
|
465
|
+
<prefer> Number
|
|
471
466
|
{ $art.value = this.numberLiteral(); }
|
|
472
467
|
|
|
|
473
468
|
sign='+'/'-' Number
|
|
@@ -803,7 +798,7 @@ typeOrIncludesSpec[ art ]
|
|
|
803
798
|
(
|
|
804
799
|
typeExpression[ $art ]
|
|
805
800
|
|
|
|
806
|
-
<
|
|
801
|
+
<prefer>
|
|
807
802
|
ref=simplePath { $art.type = $ref; }
|
|
808
803
|
(
|
|
809
804
|
// <default> does not work here
|
|
@@ -830,122 +825,103 @@ typeOrIncludesSpec[ art ]
|
|
|
830
825
|
)
|
|
831
826
|
;
|
|
832
827
|
|
|
833
|
-
// Type expression (after the `:`)
|
|
834
|
-
//
|
|
835
|
-
//
|
|
828
|
+
// Type expression (after the `:`), including `null`/`not null` and `default`;
|
|
829
|
+
// the latter is forbidden in the `returns` type.
|
|
830
|
+
//
|
|
831
|
+
// This rule also parses annotation assignments and doc comments after the
|
|
832
|
+
// type/target reference and after each type property; exceptions are:
|
|
833
|
+
// - not after element and `enum` blocks (would interfere with optional `;`)
|
|
834
|
+
// - no further type property after `many …` @assignment`, because the
|
|
835
|
+
// annotations are attached to the element, the type properties to the line type
|
|
836
836
|
//
|
|
837
837
|
// If used in a definition with additional clauses (currently just `= expr` for
|
|
838
838
|
// elements), these clauses must be guarded with <cond=…>.
|
|
839
839
|
//
|
|
840
|
-
//
|
|
841
|
-
//
|
|
842
|
-
//
|
|
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.
|
|
840
|
+
// This rule is for element, type, (input and `returns`) parameter and annotation
|
|
841
|
+
// definitions. It is not used when the type expression is restricted: CDL-style
|
|
842
|
+
// cast in `select` items, `cast` function, `mixin` definition.
|
|
847
843
|
|
|
848
844
|
typeExpression[ art ]
|
|
849
|
-
// TODO: really introduce <exitRule>
|
|
850
845
|
:
|
|
851
|
-
elementsBlock[ $art ] <prepare=elementRestriction, arg=calc>
|
|
852
|
-
nullability[ $art ]?
|
|
853
|
-
|
|
|
854
846
|
( typeRefOptArgs[ $art ] | typeTypeOf[ $art ] )
|
|
855
847
|
(<altRuleStart>)
|
|
856
|
-
|
|
857
|
-
(
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
{
|
|
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 ]?
|
|
848
|
+
{ this.docComment( $art ); } annoAssignStd[ $art ]*
|
|
849
|
+
( <cond=elementRestriction, arg=notNull> nullability[ $art ]
|
|
850
|
+
{ this.docComment( $art ); } annoAssignStd[ $art ]*
|
|
851
|
+
)?
|
|
852
|
+
( enumSymbolsBlock[ $art ] <prepare=elementRestriction, arg=anno>
|
|
853
|
+
( <cond=elementRestriction, arg=notNull> nullability[ $art ] )?
|
|
854
|
+
( <cond=elementRestriction, arg=default>
|
|
855
|
+
DEFAULT expr=expression { $art.default = $expr; }
|
|
875
856
|
)?
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
nullabilityAndDefault[ $art ]?
|
|
880
|
-
)
|
|
857
|
+
( <cond=elementRestriction, arg=notNull> nullability[ $art ] )?
|
|
858
|
+
| typeProperties[ $art ]
|
|
859
|
+
)?
|
|
881
860
|
|
|
|
882
|
-
LOCALIZED
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
-
)
|
|
861
|
+
LOCALIZED { $art.localized = this.valueWithLocation( true ); }
|
|
862
|
+
typeRefOptArgs[ $art ] // no TYPE OF
|
|
863
|
+
{ this.docComment( $art ); }
|
|
864
|
+
typeProperties[ $art ]?
|
|
894
865
|
|
|
|
895
866
|
assoc=ASSOCIATION <prepare=elementRestriction, arg=calc>
|
|
896
867
|
cardinality[ $art ]? TO card=ONE/MANY?
|
|
897
|
-
|
|
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 ]*
|
|
868
|
+
typeAssocProperties[ $art, $assoc, $card ]
|
|
906
869
|
|
|
|
907
870
|
assoc=COMPOSITION <prepare=elementRestriction, arg=calc>
|
|
908
871
|
cardinality[ $art ]? OF card=ONE/MANY?
|
|
909
|
-
(
|
|
910
|
-
|
|
911
|
-
|
|
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')]; }
|
|
872
|
+
( typeAssocProperties[ $art, $assoc, $card ]
|
|
873
|
+
| elementsBlock[ this.setAssocAndComposition( $art, $assoc, $card ) ]
|
|
874
|
+
{ $art.target.location = $art.target.elements[Symbol.for('cds.$location')]; }
|
|
923
875
|
)
|
|
924
876
|
|
|
|
925
877
|
( ARRAY <prepare=elementRestriction, arg=calc>
|
|
926
878
|
OF { $art.items = { location: this.locationOfPrevTokens( 2 ) }; }
|
|
927
879
|
| MANY <prepare=elementRestriction, arg=calc>
|
|
928
880
|
{ $art.items = { location: this.lb().location }; }
|
|
929
|
-
)
|
|
881
|
+
) // no anno assignments, except to end type expression
|
|
930
882
|
(
|
|
931
|
-
elementsBlock[ $art.items ]
|
|
932
|
-
nullability[ $art.items ]?
|
|
933
|
-
|
|
|
934
883
|
( typeRefOptArgs[ $art.items ] | typeTypeOf[ $art.items ] )
|
|
935
|
-
nullability[ $art.items ]?
|
|
936
|
-
(
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
{ this.elementRestriction( false, 'calc' ); }
|
|
941
|
-
// <prepare=elementRestriction, arg=calc>
|
|
942
|
-
{ this.docComment( $art ); } annoAssignStd[ $art ]+
|
|
943
|
-
|
|
|
944
|
-
{ this.docComment( $art, 'elem' ); }
|
|
884
|
+
( <cond=elementRestriction, arg=notNull> nullability[ $art.items ] )?
|
|
885
|
+
( { this.docComment( $art ); } annoAssignStd[ $art ]*
|
|
886
|
+
{ ; } <exitRule> // TODO TOOL: make it work without workaround { ; }
|
|
887
|
+
// TODO TOOL: investigate why simply `{} <exitRule>` is ignored
|
|
888
|
+
| enumSymbolsBlock[ $art.items ]
|
|
945
889
|
)
|
|
890
|
+
|
|
|
891
|
+
elementsBlock[ $art.items ]
|
|
946
892
|
)
|
|
893
|
+
( <cond=elementRestriction, arg=notNull> nullability[ $art.items ] )?
|
|
894
|
+
|
|
|
895
|
+
elementsBlock[ $art ] <prepare=elementRestriction, arg=calc>
|
|
896
|
+
nullability[ $art ]?
|
|
897
|
+
;
|
|
898
|
+
|
|
899
|
+
typeAssocProperties[ art, assoc, card ]
|
|
900
|
+
:
|
|
901
|
+
target=simplePath { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
|
|
902
|
+
{ this.docComment( $art ); } annoAssignStd[ $art ]*
|
|
903
|
+
( ON cond=condition { $art.on = $cond; }
|
|
904
|
+
{ this.docComment( $art ); } annoAssignStd[ $art ]*
|
|
905
|
+
| foreignKeysBlock[ $art ] { this.docComment( $art ); } typeProperties[ $art ]?
|
|
906
|
+
// remark: no auto-`;` after foreign keys → anno assignment after it possible
|
|
907
|
+
| typeProperties[ $art ]
|
|
908
|
+
)?
|
|
909
|
+
;
|
|
910
|
+
|
|
911
|
+
typeProperties[ art ]
|
|
912
|
+
:
|
|
913
|
+
(
|
|
914
|
+
annoAssignStd[ $art ]
|
|
915
|
+
|
|
|
916
|
+
<cond=elementRestriction, arg=notNull>
|
|
917
|
+
nullability[ $art ] { this.docComment( $art ); }
|
|
918
|
+
|
|
|
919
|
+
<cond=elementRestriction, arg=default>
|
|
920
|
+
DEFAULT expr=expression { $art.default = $expr; this.docComment( $art ); }
|
|
921
|
+
)+
|
|
947
922
|
;
|
|
948
923
|
|
|
924
|
+
|
|
949
925
|
typeTypeOf[ art ] locals[ location ]
|
|
950
926
|
:
|
|
951
927
|
TYPE OF { location = this.locationOfPrevTokens( 2 ); }
|
|
@@ -1049,7 +1025,7 @@ targetCardinality[ card, atAlt = false ]
|
|
|
1049
1025
|
'*' { $card.targetMax = this.valueWithLocation(); }
|
|
1050
1026
|
|
|
|
1051
1027
|
Number { $card.targetMax = this.unsignedIntegerLiteral(); }
|
|
1052
|
-
(
|
|
1028
|
+
(<altRuleStart>) // TODO TOOL: robust error when moved to after '('
|
|
1053
1029
|
(
|
|
1054
1030
|
'..' { $card.targetMin = $card.targetMax; }
|
|
1055
1031
|
( '*' { $card.targetMax = this.valueWithLocation(); }
|
|
@@ -1059,20 +1035,6 @@ targetCardinality[ card, atAlt = false ]
|
|
|
1059
1035
|
)
|
|
1060
1036
|
;
|
|
1061
1037
|
|
|
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
1038
|
nullability[ art ]
|
|
1077
1039
|
:
|
|
1078
1040
|
NULL { this.setNullability( $art, false ); }
|
|
@@ -1209,7 +1171,7 @@ tableExpression returns[ default expr = {} ] // TableOrJoin
|
|
|
1209
1171
|
tableOrQueryParens returns[ default expr ]
|
|
1210
1172
|
:
|
|
1211
1173
|
'(' <prepare=queryOnLeft>
|
|
1212
|
-
( <
|
|
1174
|
+
( <prefer> tableOrQueryParens[ ...$ ]
|
|
1213
1175
|
( tableExpression[ ...$ ]<atAltStart, prepare=queryOnLeft, arg=table>
|
|
1214
1176
|
| <cond=queryOnLeft> queryExpression[ ...$ ]<atAltStart>
|
|
1215
1177
|
)?
|
|
@@ -1498,8 +1460,6 @@ condition returns[ default expr ]
|
|
|
1498
1460
|
expression[ ...$ ]
|
|
1499
1461
|
;
|
|
1500
1462
|
|
|
1501
|
-
// TODO TOOL: sort rules if a rule with <altRuleStart> uses rule with <altRuleStart>
|
|
1502
|
-
// at least issue error if no user-sorted
|
|
1503
1463
|
valuePath returns[ default expr = { path: [] } ] locals[ pathItem ]
|
|
1504
1464
|
@finally{ this.attachLocation( $expr ); }
|
|
1505
1465
|
:
|
|
@@ -1619,7 +1579,7 @@ expression returns[ default expr = {} ]
|
|
|
1619
1579
|
expressionOrQueryParens returns[ default expr ]
|
|
1620
1580
|
:
|
|
1621
1581
|
'(' <prepare=queryOnLeft>
|
|
1622
|
-
( <
|
|
1582
|
+
( <prefer> expressionOrQueryParens[ ...$ ]
|
|
1623
1583
|
( expression[ ...$ ]<atAltStart, prepare=queryOnLeft, arg=expr>
|
|
1624
1584
|
continueExpressionslist[ ...$ ]?
|
|
1625
1585
|
| continueExpressionslist[ ...$ ] <prepare=queryOnLeft, arg=expr>
|
|
@@ -2000,7 +1960,8 @@ annoValue returns[ default value = {} ]
|
|
|
2000
1960
|
}
|
|
2001
1961
|
( ',' | <exitLoop> )
|
|
2002
1962
|
)*
|
|
2003
|
-
//
|
|
1963
|
+
// TODO TOOL: allow:
|
|
1964
|
+
// ( <cond=arrayAnno, …> '}' | <error> ) // TODO TOOL - workaround:
|
|
2004
1965
|
{ this.ec( 'arrayAnno', 'orNotEmpty' ); } '}'
|
|
2005
1966
|
// Do NOT use <prepare=afterBrace> here!
|
|
2006
1967
|
|
|
|
@@ -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 };
|
package/lib/render/toSql.js
CHANGED
|
@@ -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
|
}
|