@sap/cds-compiler 3.9.4 → 4.0.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 (94) hide show
  1. package/CHANGELOG.md +92 -4
  2. package/README.md +0 -1
  3. package/bin/cdsc.js +11 -23
  4. package/bin/cdsse.js +3 -3
  5. package/doc/API.md +5 -0
  6. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  7. package/doc/CHANGELOG_BETA.md +17 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +28 -0
  9. package/lib/api/.eslintrc.json +1 -1
  10. package/lib/api/main.js +26 -8
  11. package/lib/api/options.js +2 -0
  12. package/lib/base/error.js +2 -0
  13. package/lib/base/message-registry.js +143 -64
  14. package/lib/base/messages.js +213 -107
  15. package/lib/base/model.js +11 -11
  16. package/lib/checks/.eslintrc.json +1 -1
  17. package/lib/checks/annotationsOData.js +2 -2
  18. package/lib/checks/elements.js +1 -1
  19. package/lib/checks/enricher.js +26 -3
  20. package/lib/checks/onConditions.js +67 -12
  21. package/lib/checks/queryNoDbArtifacts.js +106 -105
  22. package/lib/checks/sql-snippets.js +2 -0
  23. package/lib/checks/types.js +12 -6
  24. package/lib/checks/validator.js +2 -2
  25. package/lib/compiler/assert-consistency.js +10 -8
  26. package/lib/compiler/builtins.js +8 -2
  27. package/lib/compiler/checks.js +52 -35
  28. package/lib/compiler/define.js +31 -26
  29. package/lib/compiler/extend.js +120 -65
  30. package/lib/compiler/finalize-parse-cdl.js +12 -43
  31. package/lib/compiler/generate.js +16 -5
  32. package/lib/compiler/index.js +8 -5
  33. package/lib/compiler/kick-start.js +4 -3
  34. package/lib/compiler/populate.js +96 -95
  35. package/lib/compiler/propagator.js +7 -8
  36. package/lib/compiler/resolve.js +377 -103
  37. package/lib/compiler/shared.js +794 -517
  38. package/lib/compiler/tweak-assocs.js +8 -6
  39. package/lib/compiler/utils.js +44 -0
  40. package/lib/edm/annotations/genericTranslation.js +12 -4
  41. package/lib/edm/csn2edm.js +34 -32
  42. package/lib/edm/edm.js +34 -31
  43. package/lib/edm/edmAnnoPreprocessor.js +0 -23
  44. package/lib/edm/edmInboundChecks.js +7 -2
  45. package/lib/edm/edmPreprocessor.js +18 -17
  46. package/lib/edm/edmUtils.js +8 -4
  47. package/lib/gen/Dictionary.json +18 -0
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +4 -2
  50. package/lib/gen/languageParser.js +5006 -4582
  51. package/lib/json/from-csn.js +157 -112
  52. package/lib/json/to-csn.js +60 -89
  53. package/lib/language/antlrParser.js +17 -13
  54. package/lib/language/docCommentParser.js +11 -1
  55. package/lib/language/genericAntlrParser.js +13 -10
  56. package/lib/language/language.g4 +168 -97
  57. package/lib/main.d.ts +128 -36
  58. package/lib/main.js +1 -1
  59. package/lib/model/csnRefs.js +24 -5
  60. package/lib/model/csnUtils.js +9 -8
  61. package/lib/model/revealInternalProperties.js +7 -12
  62. package/lib/modelCompare/compare.js +1 -1
  63. package/lib/modelCompare/utils/filter.js +40 -2
  64. package/lib/optionProcessor.js +0 -3
  65. package/lib/render/toCdl.js +247 -214
  66. package/lib/render/toHdbcds.js +197 -181
  67. package/lib/render/toSql.js +325 -289
  68. package/lib/render/utils/common.js +42 -4
  69. package/lib/render/utils/delta.js +1 -1
  70. package/lib/render/utils/sql.js +3 -3
  71. package/lib/transform/braceExpression.js +2 -2
  72. package/lib/transform/db/.eslintrc.json +1 -1
  73. package/lib/transform/db/applyTransformations.js +3 -3
  74. package/lib/transform/db/associations.js +24 -12
  75. package/lib/transform/db/expansion.js +17 -18
  76. package/lib/transform/db/flattening.js +17 -21
  77. package/lib/transform/db/rewriteCalculatedElements.js +171 -64
  78. package/lib/transform/db/views.js +3 -4
  79. package/lib/transform/draft/db.js +21 -12
  80. package/lib/transform/draft/odata.js +4 -0
  81. package/lib/transform/forOdataNew.js +11 -10
  82. package/lib/transform/forRelationalDB.js +12 -7
  83. package/lib/transform/localized.js +4 -2
  84. package/lib/transform/odata/toFinalBaseType.js +5 -5
  85. package/lib/transform/odata/typesExposure.js +3 -3
  86. package/lib/transform/parseExpr.js +3 -0
  87. package/lib/transform/transformUtilsNew.js +43 -23
  88. package/lib/transform/translateAssocsToJoins.js +7 -6
  89. package/lib/transform/universalCsn/.eslintrc.json +1 -1
  90. package/lib/transform/universalCsn/coreComputed.js +7 -5
  91. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
  92. package/package.json +2 -2
  93. package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
  94. package/share/messages/message-explanations.json +1 -1
@@ -723,7 +723,7 @@ elementType[ art ] // TODO: split this monster rule
723
723
 
724
724
  elementProperties[ elem ]
725
725
  :
726
- defaultAndNullablity[ $elem ]
726
+ defaultAndNullability[ $elem ]
727
727
  |
728
728
  '=' e=expression
729
729
  stored=STORED?
@@ -733,7 +733,7 @@ elementProperties[ elem ]
733
733
  }
734
734
  ;
735
735
 
736
- defaultAndNullablity[ elem ]
736
+ defaultAndNullability[ elem ]
737
737
  :
738
738
  defaultValue[ $elem ]
739
739
  nullability[ $elem ]? // placement accoring to SQL spec
@@ -763,7 +763,8 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
763
763
  extendWithOptElementsNoWith[ art ]
764
764
  |
765
765
  { this.addExtension( $art, $outer, 'extend', $name ); }
766
- WITH { this.noSemicolonHere(); this.docComment( $art ); }
766
+ WITH { this.noSemicolonHere(); }
767
+ { this.docComment( $art ); }
767
768
  annotationAssignment_ll1[ $art ]*
768
769
  // #ATN: ELEMENTS, ENUM, DEFINITIONS, COLUMNS, ACTIONS are not reserved and
769
770
  // could be includeRef
@@ -1033,57 +1034,59 @@ annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
1033
1034
  @after { this.attachLocation( $art ); }
1034
1035
  :
1035
1036
  simplePath[ $name, 'Annotate' ]
1036
- ( ':' simplePath[ $elemName, 'Element'] )? // TODO: do like for extendArtifact !
1037
- { this.addExtension( $art, $outer, 'annotate', $name, $elemName.path ); }
1038
- ( WITH { this.noSemicolonHere(); } )?
1039
- { this.docComment( $art ); }
1040
- annotationAssignment_ll1[ $art ]*
1041
- (
1042
- '{' { $art.elements = this.createDict(); }
1043
- annotateElement[ $art ]*
1044
- '}' { this.finalizeDictOrArray( $art.elements ); }
1045
- { this.checkExtensionDict( $art.elements ); }
1037
+ ( // Element annotation
1038
+ ':' simplePath[ $elemName, 'Element']
1039
+ { this.addExtension( $art, $outer, 'annotate', $name, $elemName.path ); }
1040
+ ( WITH { this.noSemicolonHere(); } )?
1041
+ { this.docComment( $art ); }
1042
+ annotationAssignment_ll1[ $art ]*
1046
1043
  (
1047
- ACTIONS { $art.actions = this.createDict(); } '{'
1048
- annotateAction[ $art ]*
1049
- '}' { this.finalizeDictOrArray( $art.actions ); }
1050
- { this.checkExtensionDict( $art.actions ); }
1051
- )?
1052
- optionalSemi
1053
- |
1054
- ACTIONS { $art.actions = this.createDict(); } '{'
1055
- annotateAction[ $art ]*
1056
- '}' { this.finalizeDictOrArray( $art.actions ); }
1057
- { this.checkExtensionDict( $art.actions ); }
1058
- optionalSemi
1059
- |
1060
- '(' { $art.params = this.createDict(); }
1061
- annotateParam[ $art ]
1062
- ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1063
- annotateParam[ $art ]
1064
- )*
1065
- ')' { this.finalizeDictOrArray( $art.params ); }
1066
- { this.checkExtensionDict( $art.params ); }
1044
+ '{' { $art.elements = this.createDict(); }
1045
+ annotateElement[ $art ]*
1046
+ '}' { this.finalizeDictOrArray( $art.elements ); }
1047
+ { this.checkExtensionDict( $art.elements ); }
1048
+ optionalSemi
1049
+ | requiredSemi
1050
+ )
1051
+ | // Definition annotation
1052
+ { this.addExtension( $art, $outer, 'annotate', $name ); }
1053
+ ( WITH { this.noSemicolonHere(); } )?
1054
+ { this.docComment( $art ); }
1055
+ annotationAssignment_ll1[ $art ]*
1067
1056
  (
1068
- // TODO: set proper $art.returns
1069
- RETURNS { $art.elements = this.createDict(); } { $art['$'+'syntax'] = 'returns'; } '{'
1057
+ '{' { $art.elements = this.createDict(); }
1070
1058
  annotateElement[ $art ]*
1071
1059
  '}' { this.finalizeDictOrArray( $art.elements ); }
1072
- { this.checkExtensionDict( $art.elements ); }
1060
+ { this.checkExtensionDict( $art.elements ); }
1061
+ (
1062
+ ACTIONS { $art.actions = this.createDict(); } '{'
1063
+ annotateAction[ $art ]*
1064
+ '}' { this.finalizeDictOrArray( $art.actions ); }
1065
+ { this.checkExtensionDict( $art.actions ); }
1066
+ )?
1073
1067
  optionalSemi
1068
+ |
1069
+ ACTIONS { $art.actions = this.createDict(); } '{'
1070
+ annotateAction[ $art ]*
1071
+ '}' { this.finalizeDictOrArray( $art.actions ); }
1072
+ { this.checkExtensionDict( $art.actions ); }
1073
+ optionalSemi
1074
+ |
1075
+ '(' { $art.params = this.createDict(); }
1076
+ annotateParam[ $art ]
1077
+ ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1078
+ annotateParam[ $art ]
1079
+ )*
1080
+ ')' { this.finalizeDictOrArray( $art.params ); }
1081
+ { this.checkExtensionDict( $art.params ); }
1082
+ ( annotateReturns[ $art ]
1083
+ | requiredSemi
1084
+ )
1085
+ |
1086
+ annotateReturns[ $art ]
1074
1087
  |
1075
1088
  requiredSemi
1076
1089
  )
1077
- |
1078
- // TODO: set proper $art.returns
1079
- RETURNS { $art.elements = this.createDict(); } { $art['$'+'syntax'] = 'returns'; } '{'
1080
- annotateElement[ $art ]*
1081
- '}' { this.finalizeDictOrArray( $art.elements ); }
1082
- { this.checkExtensionDict( $art.elements ); }
1083
- optionalSemi
1084
-
1085
- |
1086
- requiredSemi
1087
1090
  )
1088
1091
  ;
1089
1092
 
@@ -1126,17 +1129,27 @@ annotateAction [ outer ] locals [ art = {} ]
1126
1129
  { this.checkExtensionDict( $art.params ); }
1127
1130
  )?
1128
1131
  (
1129
- // TODO: set proper $art.returns
1130
- RETURNS { $art.elements = this.createDict(); } { $art['$'+'syntax'] = 'returns'; } '{'
1131
- annotateElement[ $art ]*
1132
- '}' { this.finalizeDictOrArray( $art.elements ); }
1133
- { this.checkExtensionDict( $art.elements ); }
1134
- optionalSemi
1132
+ annotateReturns[ $art ]
1135
1133
  |
1136
1134
  requiredSemi
1137
1135
  )
1138
1136
  ;
1139
1137
 
1138
+ annotateReturns[ art ]
1139
+ @after{ this.attachLocation( $art.returns ); }
1140
+ :
1141
+ ret=RETURNS { $art.returns = { kind: 'annotate' }; }
1142
+ { this.docComment( $art.returns ); }
1143
+ annotationAssignment_ll1[ $art.returns ]*
1144
+ ( '{' { $art.returns.elements = this.createDict(); }
1145
+ annotateElement[ $art.returns ]*
1146
+ '}' { this.finalizeDictOrArray( $art.returns.elements ); }
1147
+ { this.checkExtensionDict( $art.returns.elements ); }
1148
+ optionalSemi
1149
+ | requiredSemi
1150
+ )
1151
+ ;
1152
+
1140
1153
  annotateParam [ outer ] locals [ art = {} ]
1141
1154
  @after{ this.attachLocation( $art ); }
1142
1155
  :
@@ -1190,6 +1203,8 @@ returnTypeSpec[ art ]
1190
1203
  @after{ /* #ATN 1 */ }
1191
1204
  :
1192
1205
  ret=RETURNS { $art.returns = { location: this.tokenLocation( $ret ), kind: 'param' }; }
1206
+ { this.docComment( $art.returns ); }
1207
+ annotationAssignment_ll1[ $art.returns ]*
1193
1208
  // #ATN: typeSimple can start with ARRAY or TYPE
1194
1209
  ( typeStruct[ $art.returns ]
1195
1210
  nullability[ $art.returns ]?
@@ -1246,8 +1261,10 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1246
1261
  many=HelperToken1 // rewritten MANY before '{'
1247
1262
  { $art.items = { location: this.tokenLocation( $many ) };}
1248
1263
  typeStruct[ $art.items ]
1249
- nullability[ $art.items ]?
1250
- optionalSemi // TODO(v4): Should be requiredSemi with nullability
1264
+ ( nullability[ $art.items ]
1265
+ requiredSemi
1266
+ | optionalSemi
1267
+ )
1251
1268
  |
1252
1269
  (
1253
1270
  array=ARRAY of=OF
@@ -1257,8 +1274,10 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1257
1274
  )
1258
1275
  // #ATN: typeRefOptArgs can start with TYPE
1259
1276
  ( typeStruct[ $art.items ]
1260
- nullability[ $art.items ]?
1261
- optionalSemi // TODO(v4): Should be requiredSemi with nullability
1277
+ ( nullability[ $art.items ]
1278
+ requiredSemi
1279
+ | optionalSemi
1280
+ )
1262
1281
  | ( typeTypeOf[ $art.items ] | typeRefOptArgs[ $art.items ] )
1263
1282
  nullability[ $art.items ]?
1264
1283
  { this.docComment( $art ); }
@@ -1267,26 +1286,23 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1267
1286
  ENUM '{' { $art.items.enum = this.createDict(); }
1268
1287
  enumSymbolDef[ $art.items ]*
1269
1288
  '}' { this.finalizeDictOrArray( $art.items.enum ); }
1270
- (
1271
- nullability[ $art.items ]
1289
+ ( nullability[ $art.items ]
1272
1290
  requiredSemi
1273
- |
1274
- optionalSemi
1291
+ | optionalSemi
1275
1292
  )
1276
- |
1277
- requiredSemi
1293
+ | requiredSemi
1278
1294
  )
1279
1295
  )
1280
1296
  |
1281
1297
  typeTypeOf[ $art ]
1282
- defaultAndNullablity[ $art ]?
1298
+ defaultAndNullability[ $art ]?
1283
1299
  { this.docComment( $art ); }
1284
1300
  annotationAssignment_ll1[ $art ]*
1285
1301
  requiredSemi
1286
1302
  |
1287
1303
  l=LOCALIZED { $art.localized = this.valueWithTokenLocation( true, $l ); }
1288
1304
  typeRefOptArgs[ $art ]
1289
- defaultAndNullablity[ $art ]?
1305
+ defaultAndNullability[ $art ]?
1290
1306
  { this.docComment( $art ); }
1291
1307
  annotationAssignment_ll1[ $art ]*
1292
1308
  requiredSemi
@@ -1301,20 +1317,18 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1301
1317
  { $art.type.scope = $art.type.path.length; }
1302
1318
  simplePath[ $art.type, 'ref']
1303
1319
  )?
1320
+ optInvisibleNullability[ $art ]
1304
1321
  { this.docComment( $art ); }
1305
1322
  annotationAssignment_ll1[ $art ]*
1306
1323
  (
1307
1324
  ENUM '{' { $art.enum = this.createDict(); }
1308
1325
  enumSymbolDef[ $art ]*
1309
1326
  '}' { this.finalizeDictOrArray( $art.enum ); }
1310
- (
1311
- optionalSemi
1312
- |
1313
- defaultAndNullablity[ $art ]
1327
+ ( optionalSemi
1328
+ | defaultAndNullability[ $art ]
1314
1329
  requiredSemi
1315
1330
  )
1316
- |
1317
- defaultAndNullablity[ $art ]?
1331
+ | defaultAndNullability[ $art ]?
1318
1332
  requiredSemi
1319
1333
  )
1320
1334
  |
@@ -1512,7 +1526,7 @@ foreignKey[ outer ] locals[ art = {}, elem = {} ]
1512
1526
  typeTypeOf[ art ] locals[ _sync = 'nop' ]
1513
1527
  @after { this.attachLocation($art.type); }
1514
1528
  :
1515
- TYPE OF
1529
+ t=TYPE o=OF
1516
1530
  { $art.type = { scope: 'typeOf' }; }
1517
1531
  simplePath[ $art.type, 'ref' ]
1518
1532
  ( ':'
@@ -1521,6 +1535,12 @@ typeTypeOf[ art ] locals[ _sync = 'nop' ]
1521
1535
  { $art.type.scope = $art.type.path.length; }
1522
1536
  simplePath[ $art.type, 'ref']
1523
1537
  )?
1538
+ // We do not use (…|) here instead (…)? due to different ANTLR code generation:
1539
+ // (…|) would check for follow set, which does not work with local token rewrite
1540
+ { if ($art.type.scope === 'typeOf')
1541
+ // Better error locations and much simpler code if we consider it as a path breakout:
1542
+ $art.type.path.unshift( { id: 'type of', location: this.tokenLocation( $t, $o )} );
1543
+ }
1524
1544
  ;
1525
1545
 
1526
1546
  typeRefOptArgs[ art ]
@@ -1621,7 +1641,7 @@ projectionSpec returns[ query ] locals[ src ]
1621
1641
  proj=PROJECTION ON
1622
1642
  // now a simplified `tableTerm`:
1623
1643
  {
1624
- $src = { path: [], scope: 0 };
1644
+ $src = { path: [] };
1625
1645
  $query = { op: this.valueWithTokenLocation( 'SELECT', $proj ), from: $src, location: this.startLocation() };
1626
1646
  }
1627
1647
  fromPath[ $src, 'artref']
@@ -1750,11 +1770,19 @@ tableExpression returns[ table ] // TableOrJoin
1750
1770
  tableTerm returns [ table ]
1751
1771
  @after{ /* #ATN 1 */ this.attachLocation($table); }
1752
1772
  :
1753
- { $table = { path: [], scope: 0 }; }
1754
- fromPath[ $table, 'artref']
1755
- ( ':'
1756
- { $table.scope = $table.path.length; }
1757
- fromPath[ $table, 'ref']
1773
+ { $table = { path: [] }; }
1774
+ f=fromPath[ $table, 'artref']
1775
+ { if ($f.dotAfterFilter)
1776
+ this.warning( 'syntax-invalid-path-separator', $f.dotAfterFilter,
1777
+ { '#': 'dot', code: '.', newcode: ':' } );
1778
+ }
1779
+ ( { if (!$table.scope)
1780
+ $table.scope = $table.path.length;
1781
+ else
1782
+ this.warning( 'syntax-invalid-path-separator', this.getCurrentToken(),
1783
+ { '#': 'colon', code: ':', newcode: '.' } );
1784
+ }
1785
+ ':' fromPath[ $table, 'ref']
1758
1786
  )?
1759
1787
  ( AS n1=ident['FromAlias'] { $table.name = $n1.id }
1760
1788
  | n2=identNoKeyword['FromAlias'] { $table.name = this.fragileAlias( $n2.id ); }
@@ -1770,7 +1798,7 @@ tableTerm returns [ table ]
1770
1798
  (
1771
1799
  qe=queryExpression close=')'
1772
1800
  { $table = this.surroundByParens( $qe.query, $open, $close, true ); }
1773
- ( AS a1=ident['FromAlias'] { $table.name = $a1.id } // for defining table aliass
1801
+ ( AS a1=ident['FromAlias'] { $table.name = $a1.id } // for defining table alias
1774
1802
  | a2=identNoKeyword['FromAlias'] { $table.name = this.fragileAlias( $a2.id, true ); }
1775
1803
  // not using ident` to have a similar behavior to above
1776
1804
  )?
@@ -1780,17 +1808,25 @@ tableTerm returns [ table ]
1780
1808
  )
1781
1809
  ;
1782
1810
 
1783
- fromPath[ qp, idkind ]
1811
+ fromPath[ qp, idkind ] returns[ dotAfterFilter = null ]
1784
1812
  @after{ this.attachLocation($qp); }
1785
1813
  :
1786
1814
  id=ident[$idkind] { this.pushIdent( $qp.path, $id.id ); }
1787
1815
  ( fromArguments[ $id.id ] cardinalityAndFilter[ $id.id ]?
1816
+ { $dotAfterFilter = false; }
1788
1817
  | cardinalityAndFilter[ $id.id ]
1818
+ { $dotAfterFilter = false; }
1789
1819
  )?
1790
1820
  (
1821
+ { if ($dotAfterFilter === false) {
1822
+ $dotAfterFilter = this.getCurrentToken();
1823
+ if (!$qp.scope) $qp.scope = $qp.path.length;
1824
+ } }
1791
1825
  '.' id=ident[$idkind] { this.pushIdent( $qp.path, $id.id ); }
1792
- ( fromArguments[ $id.id ] cardinalityAndFilter[ $id.id ]?
1826
+ ( fromArguments[ $id.id ] cardinalityAndFilter[ $id.id ]?
1827
+ { if (!$dotAfterFilter) $dotAfterFilter = false; }
1793
1828
  | cardinalityAndFilter[ $id.id ]
1829
+ { if (!$dotAfterFilter) $dotAfterFilter = false; }
1794
1830
  )?
1795
1831
  )*
1796
1832
  ;
@@ -2301,9 +2337,7 @@ simplePath[ art, category ] locals[ _sync = 'nop' ]
2301
2337
  // path as broken in this case.
2302
2338
  :
2303
2339
  head=ident[ $category ]
2304
- { if (!$art.path) $art.path = []; this.pushIdent( $art.path, $head.id );
2305
- if ($category === 'artref') $art.scope = 0;
2306
- }
2340
+ { if (!$art.path) $art.path = []; this.pushIdent( $art.path, $head.id ); }
2307
2341
  (
2308
2342
  '.' tail=ident[ $category ] { this.pushIdent( $art.path, $tail.id ); }
2309
2343
  )*
@@ -2529,14 +2563,34 @@ windowFrameStartSpec[ args = [] ]
2529
2563
 
2530
2564
  cardinalityAndFilter[ pathStep ] locals [ _sync = 'nop' ]
2531
2565
  :
2532
- '['
2566
+ { if (!$pathStep) $pathStep = {}; }
2567
+ openFilter='['
2533
2568
  optionalCardinality[ pathStep ]?
2534
- //{ $LeaveLoop = false; }
2535
- optionalWhereForFilter
2536
- co=condition { if ($pathStep) $pathStep.where = $co.cond; }
2537
- ']'
2569
+
2570
+ filterWhereClause[ $pathStep ] // required, see rule's comment
2571
+ (
2572
+ group=GROUP by=BY
2573
+ e1=expression { $pathStep.groupBy = [ $e1.expr ]; }
2574
+ ( ',' en=expression { $pathStep.groupBy.push( $en.expr ); } )*
2575
+ { this.csnParseOnly('syntax-unexpected-sql-clause', [ $group, $by ], { keyword: 'GROUP BY' }); }
2576
+ )?
2577
+ ( hv=HAVING
2578
+ having=condition { $pathStep.having = $having.cond; }
2579
+ { this.csnParseOnly('syntax-unexpected-sql-clause', [ $hv ], { keyword: 'HAVING' }); }
2580
+ )?
2581
+ ( { const orderKw = this._input.LT(1); const byKw = this._input.LT(2); }
2582
+ ob=orderByClause[ $pathStep ] { $pathStep = $ob.query;; }
2583
+ { this.csnParseOnly('syntax-unexpected-sql-clause', [ orderKw, byKw ], { keyword: 'ORDER BY' }); }
2584
+ )?
2585
+ ( { const limit = this._input.LT(1); }
2586
+ lc=limitClause[ $pathStep ] { $pathStep = $lc.query;; }
2587
+ { this.csnParseOnly('syntax-unexpected-sql-clause', [ limit ], { keyword: 'LIMIT' }); }
2588
+ )?
2589
+
2590
+ closeFilter=']'
2538
2591
  ;
2539
2592
 
2593
+
2540
2594
  optionalCardinality[ pathStep ]
2541
2595
  @after { if ($pathStep && $pathStep.cardinality) this.attachLocation($pathStep.cardinality); }
2542
2596
  :
@@ -2549,17 +2603,36 @@ optionalCardinality[ pathStep ]
2549
2603
  )
2550
2604
  ;
2551
2605
 
2606
+ filterWhereClause[ pathStep ]
2607
+ :
2608
+ // NOTE: Keep in sync with optionalWhereForFilter!
2609
+ //
2610
+ // For ANTLR, WHERE is required, but the generated parser may skip `match(WHERE)` in
2611
+ // `optionalWhereForFilter`. Because `(WHERE cond)?` would invoke adaptive predict,
2612
+ // we use this hack that skips parsing of the condition, if the token is a SQL clause,
2613
+ // but makes Antlr assume that it is required.
2614
+ {
2615
+ const tok = this.getCurrentToken();
2616
+ if (tok.type === languageParser.GROUP
2617
+ || tok.type === languageParser.ORDER
2618
+ || tok.type === languageParser.LIMIT
2619
+ || tok.type === languageParser.HAVING)
2620
+ return $ctx;
2621
+ }
2622
+ optionalWhereForFilter cond=condition { if ($pathStep) $pathStep.where = $cond.cond; }
2623
+ ;
2624
+
2552
2625
  optionalWhereForFilter
2553
2626
  :
2627
+ // NOTE: only call from rule filterWhereClause!
2628
+ //
2554
2629
  // For ANTLR, WHERE is required, but we allow the generated parser skipping
2555
- // the call of match(WHERE) except for the future (optional) clauses GROUP,
2556
- // ORDER, LIMIT. This hack requires that sync() at each state in the
2557
- // calling rule does not throw an error if the current token does not match
2630
+ // the call of match(WHERE). This hack requires that sync() at each state in
2631
+ // the calling rule does not throw an error if the current token does not match
2558
2632
  // one of the expected ones.
2559
2633
  {
2560
- var text = this.getCurrentToken().text.toUpperCase();
2561
- if (!['WHERE','GROUP','ORDER','LIMIT'].includes( text )) return $ctx;
2562
- // TODO: should we somehow add those keywords to $(EXPECTED)?
2634
+ if (this.getCurrentToken().type !== languageParser.WHERE)
2635
+ return $ctx; // TODO: should we somehow add those keywords to $(EXPECTED)?
2563
2636
  }
2564
2637
  WHERE
2565
2638
  ;
@@ -2693,9 +2766,7 @@ annotationPath[ art, category, headat = null ] locals[ _sync = 'nop' ]
2693
2766
  // path as broken in this case.
2694
2767
  :
2695
2768
  head=ident[ $category ]
2696
- { $art.path = []; this.pushIdent( $art.path, $head.id, $headat );
2697
- if ($category === 'artref') $art.scope = 0;
2698
- }
2769
+ { $art.path = []; this.pushIdent( $art.path, $head.id, $headat ); }
2699
2770
  (
2700
2771
  '.' at='@'? tail=ident[ $category ]
2701
2772
  { this.pushIdent( $art.path, $tail.id, $at );