@sap/cds-compiler 2.7.0 → 2.11.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 (87) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +10 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +17 -33
  7. package/lib/api/options.js +25 -13
  8. package/lib/api/validate.js +33 -9
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +26 -2
  13. package/lib/base/messages.js +25 -9
  14. package/lib/base/model.js +5 -3
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/onConditions.js +5 -0
  17. package/lib/checks/selectItems.js +4 -0
  18. package/lib/checks/types.js +26 -2
  19. package/lib/checks/unknownMagic.js +41 -0
  20. package/lib/checks/validator.js +7 -2
  21. package/lib/compiler/assert-consistency.js +18 -5
  22. package/lib/compiler/base.js +65 -0
  23. package/lib/compiler/builtins.js +30 -1
  24. package/lib/compiler/checks.js +5 -2
  25. package/lib/compiler/definer.js +145 -120
  26. package/lib/compiler/index.js +16 -4
  27. package/lib/compiler/propagator.js +5 -2
  28. package/lib/compiler/resolver.js +207 -47
  29. package/lib/compiler/shared.js +47 -200
  30. package/lib/compiler/utils.js +173 -0
  31. package/lib/edm/annotations/genericTranslation.js +183 -187
  32. package/lib/edm/csn2edm.js +94 -98
  33. package/lib/edm/edm.js +16 -20
  34. package/lib/edm/edmPreprocessor.js +302 -115
  35. package/lib/edm/edmUtils.js +31 -12
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +28 -1
  38. package/lib/gen/language.tokens +79 -69
  39. package/lib/gen/languageLexer.interp +28 -1
  40. package/lib/gen/languageLexer.js +879 -805
  41. package/lib/gen/languageLexer.tokens +71 -62
  42. package/lib/gen/languageParser.js +5308 -4308
  43. package/lib/json/from-csn.js +59 -30
  44. package/lib/json/to-csn.js +354 -105
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +1 -0
  47. package/lib/language/genericAntlrParser.js +81 -14
  48. package/lib/language/language.g4 +163 -31
  49. package/lib/main.d.ts +136 -17
  50. package/lib/main.js +7 -1
  51. package/lib/model/api.js +78 -0
  52. package/lib/model/csnRefs.js +115 -32
  53. package/lib/model/csnUtils.js +71 -33
  54. package/lib/model/enrichCsn.js +36 -9
  55. package/lib/model/revealInternalProperties.js +20 -4
  56. package/lib/modelCompare/compare.js +2 -1
  57. package/lib/optionProcessor.js +33 -16
  58. package/lib/render/.eslintrc.json +3 -1
  59. package/lib/render/DuplicateChecker.js +1 -1
  60. package/lib/render/toCdl.js +60 -17
  61. package/lib/render/toHdbcds.js +122 -74
  62. package/lib/render/toSql.js +57 -32
  63. package/lib/render/utils/common.js +6 -10
  64. package/lib/sql-identifier.js +6 -1
  65. package/lib/transform/db/constraints.js +273 -119
  66. package/lib/transform/db/draft.js +9 -6
  67. package/lib/transform/db/expansion.js +19 -7
  68. package/lib/transform/db/flattening.js +31 -7
  69. package/lib/transform/db/transformExists.js +344 -66
  70. package/lib/transform/db/views.js +438 -0
  71. package/lib/transform/forHanaNew.js +65 -436
  72. package/lib/transform/forOdataNew.js +21 -10
  73. package/lib/transform/localized.js +2 -0
  74. package/lib/transform/odata/attachPath.js +19 -4
  75. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  76. package/lib/transform/odata/referenceFlattener.js +44 -38
  77. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  78. package/lib/transform/odata/structuralPath.js +72 -0
  79. package/lib/transform/odata/structureFlattener.js +13 -10
  80. package/lib/transform/odata/typesExposure.js +22 -12
  81. package/lib/transform/transformUtilsNew.js +55 -9
  82. package/lib/transform/translateAssocsToJoins.js +11 -17
  83. package/lib/transform/universalCsnEnricher.js +67 -0
  84. package/lib/utils/file.js +5 -3
  85. package/lib/utils/term.js +65 -42
  86. package/lib/utils/timetrace.js +48 -26
  87. package/package.json +1 -1
@@ -162,6 +162,17 @@ function parse( source, filename = '<undefined>.cds', options = {}, messageFunct
162
162
  if (rulespec.$frontend)
163
163
  ast.$frontend = rulespec.$frontend;
164
164
 
165
+ // Warn about unused doc-comments.
166
+ // Do not warn if docComments are explicitly disabled.
167
+ if (options.docComment !== false) {
168
+ for (const token of tokenStream.tokens) {
169
+ if (token.channel === antlr4.Token.HIDDEN_CHANNEL && token.type === parser.constructor.DocComment && !token.isUsed) {
170
+ messageFunctions.info('syntax-ignoring-doc-comment', parser.multiLineTokenLocation(token), {},
171
+ "Ignoring doc-comment as it does not belong to any artifact");
172
+ }
173
+ }
174
+ }
175
+
165
176
  // TODO: clarify with LSP colleagues: still necessary?
166
177
  if (parser.messages) {
167
178
  Object.defineProperty( ast, 'messages',
@@ -245,6 +245,7 @@ function reportIgnoredWith( recognizer, t ) {
245
245
  }
246
246
 
247
247
  function consumeUntil( recognizer, set ) {
248
+ // TODO: add trace
248
249
  if (SEMI == null)
249
250
  SEMI = recognizer.literalNames.indexOf( "';'" );
250
251
  if (RBRACE == null)
@@ -49,6 +49,8 @@ GenericAntlrParser.prototype = Object.assign(
49
49
  attachLocation,
50
50
  startLocation,
51
51
  tokenLocation,
52
+ multiLineTokenLocation,
53
+ previousTokenAtLocation,
52
54
  combinedLocation,
53
55
  surroundByParens,
54
56
  unaryOpForParens,
@@ -77,6 +79,7 @@ GenericAntlrParser.prototype = Object.assign(
77
79
  noAssignmentInSameLine,
78
80
  noSemicolonHere,
79
81
  setLocalToken,
82
+ setLocalTokenIfBefore,
80
83
  excludeExpected,
81
84
  isStraightBefore,
82
85
  meltKeywordToIdentifier,
@@ -183,6 +186,14 @@ function setLocalToken( string, tokenName, notBefore, inSameLine ) {
183
186
  ll1.type = this.constructor[tokenName];
184
187
  }
185
188
 
189
+ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
190
+ const ll1 = this.getCurrentToken();
191
+ if (ll1.text.toUpperCase() === string &&
192
+ (!inSameLine || this._input.LT(-1).line === ll1.line) &&
193
+ (!before || before && before.test( this._input.LT(2).text )))
194
+ ll1.type = this.constructor[tokenName];
195
+ }
196
+
186
197
  // // Special function for rule `requiredSemi` before return $ctx
187
198
  // function braceForSemi() {
188
199
  // if (RBRACE == null)
@@ -236,9 +247,12 @@ function prepareGenericKeywords( pathItem ) {
236
247
  // TODO: If not just at the beginning, we need a stack for $genericKeywords,
237
248
  // as we can have nested special functions
238
249
  this.$genericKeywords.argFull = Object.keys( spec );
250
+ // @ts-ignore
239
251
  const token = this.getCurrentToken() || { text: '' };
240
- if (spec[token.text.toUpperCase()] === 'argFull')
252
+ if (spec[token.text.toUpperCase()] === 'argFull') {
253
+ // @ts-ignore
241
254
  token.type = this.constructor.GenericArgFull;
255
+ }
242
256
  }
243
257
 
244
258
  // Attach location matched by current rule to node `art`. If a location is
@@ -288,7 +302,7 @@ function tokenLocation( token, endToken, val ) {
288
302
  file: this.filename,
289
303
  line: token.line,
290
304
  col: token.column + 1,
291
- // we only have single-line tokens
305
+ // we only have single-line tokens, except for docComments
292
306
  endLine: endToken.line,
293
307
  endCol: endToken.stop - endToken.start + endToken.column + 2, // after the last char (special for EOF?)
294
308
  };
@@ -297,6 +311,51 @@ function tokenLocation( token, endToken, val ) {
297
311
  return r;
298
312
  }
299
313
 
314
+ /**
315
+ * Return location of `token`. In contrast to `tokenLocation()`, this function
316
+ * can handle multiline tokens.
317
+ */
318
+ function multiLineTokenLocation(token, val) {
319
+ if (!token)
320
+ return undefined;
321
+
322
+ // Count the number of newlines in the token.
323
+ // TODO: I want to avoid a substring, that's why I don't use RegEx here
324
+ const source = token.source[1].data;
325
+ let newLineCount = 0;
326
+ let lastNewlineIndex = token.start;
327
+ for (let i = token.start; i < token.stop; i++) {
328
+ if (source[i] === 10) { // ASCII code for '\n'
329
+ newLineCount++;
330
+ lastNewlineIndex = i;
331
+ }
332
+ }
333
+ if (newLineCount === 0)
334
+ // endCol calculation below requires at least one newLine.
335
+ return this.tokenLocation(token, token, val);
336
+
337
+ /** @type {CSN.Location} */
338
+ const r = {
339
+ file: this.filename,
340
+ line: token.line,
341
+ col: token.column + 1,
342
+ endLine: token.line + newLineCount,
343
+ endCol: token.stop - lastNewlineIndex + 1, // after the last char (special for EOF?)
344
+ };
345
+ if (val !== undefined)
346
+ return { location: r, val };
347
+ return r;
348
+ }
349
+
350
+ function previousTokenAtLocation( location ) {
351
+ let k = -1;
352
+ let token = this._input.LT(k);
353
+ while (token.line > location.line ||
354
+ token.line === location.line && token.column >= location.col)
355
+ token = this._input.LT(--k);
356
+ return (token.line === location.line && token.column + 1 === location.col) && token;
357
+ }
358
+
300
359
  // Create a location with location properties `filename` and `start` from
301
360
  // argument `start`, and location property `end` from argument `end`.
302
361
  function combinedLocation( start, end ) {
@@ -333,16 +392,20 @@ function unaryOpForParens( query, val ) {
333
392
  // - would influence the prediction, probably even induce adaptivePredict() calls,
334
393
  // - is only slightly "more declarative" in the grammar.
335
394
  function docComment( node ) {
336
- if (!this.options.docComment)
337
- return;
338
395
  const token = this._input.getHiddenTokenToLeft( this.constructor.DocComment );
339
396
  if (!token)
340
397
  return;
398
+
399
+ // This token is actually used by / assigned to an artifact.
400
+ token.isUsed = true;
401
+
402
+ if (!this.options.docComment)
403
+ return;
341
404
  if (node.doc) {
342
405
  this.warning( 'syntax-duplicate-doc-comment', token, {},
343
406
  'Repeated doc comment - previous doc is replaced' );
344
407
  }
345
- node.doc = this.tokenLocation( token, token, parseDocComment( token.text ) );
408
+ node.doc = this.multiLineTokenLocation( token, parseDocComment( token.text ) );
346
409
  }
347
410
 
348
411
  // Classify token (identifier category) for implicit names,
@@ -426,14 +489,18 @@ function valuePathAst( ref ) {
426
489
  path.length = 1;
427
490
  }
428
491
  const { args, id, location } = path[0];
429
- if (args) {
430
- if (path[0].$syntax !== ':')
431
- return { op: { location, val: 'call' }, func: ref, location: ref.location, args };
432
- }
433
- else if (!path[0].$delimited && functionsWithoutParens.includes( id.toUpperCase() )) {
434
- return { op: { location, val: 'call' }, func: ref, location: ref.location };
435
- }
436
- return ref;
492
+ if (args
493
+ ? path[0].$syntax === ':'
494
+ : path[0].$delimited || !functionsWithoutParens.includes( id.toUpperCase() ))
495
+ return ref;
496
+
497
+ const implicit = this.previousTokenAtLocation( location );
498
+ if (implicit && implicit.isIdentifier)
499
+ implicit.isIdentifier = 'func';
500
+ const op = { location, val: 'call' };
501
+ return (args)
502
+ ? { op, func: ref, location: ref.location, args }
503
+ : { op, func: ref, location: ref.location };
437
504
  }
438
505
 
439
506
  // If a '-' is directly before an unsigned number, consider it part of the number;
@@ -592,7 +659,7 @@ function addDef( parent, env, kind, name, annos, props, location ) {
592
659
  // which could be tested in name search (then no undefined-ref error)
593
660
  return art;
594
661
  }
595
- else if (env === 'artifacts') {
662
+ else if (env === 'artifacts' || env === 'vocabularies') {
596
663
  dictAddArray( parent[env], art.name.id, art );
597
664
  }
598
665
  else if (kind || this.options.parseOnly) {
@@ -20,7 +20,7 @@
20
20
  // allow integers at certain places), one token type for non-quoted and
21
21
  // quoted identifiers.
22
22
  //
23
- // * Keep the number of keywords as small as possibile. Thus, built-ins is a
23
+ // * Keep the number of keywords as small as possible. Thus, built-ins is a
24
24
  // topic for the semantic analysis, not the grammar. Examples: no keywords
25
25
  // for built-in types or built-in SQL functions. This also avoids noise in
26
26
  // the grammar and a huge/slow generated parser.
@@ -43,7 +43,7 @@
43
43
  // is not enough for a decision), write a comment starting with `#ATN:`
44
44
  // which describes the ambiguity. Additionally, put a comment `/* #ATN n
45
45
  // */` INSIDE an (`@after`) action of a rule if the corresponding function
46
- // in '../gen/langageParser.js' contains `n` occurrences of
46
+ // in '../gen/languageParser.js' contains `n` occurrences of
47
47
  // `adaptivePredict` calls. This is checked in 'test/testCompiler.js',
48
48
  // which also counts the total number of `adaptivePredict` occurrences.
49
49
  // └─────────────────────────────────────────────────────────────────────────┘
@@ -55,6 +55,10 @@
55
55
  // good idea anyway to avoid calls to `adaptivePredict`, see the rules
56
56
  // starting with `annotationAssignment_`.
57
57
  //
58
+ // * Factoring out a sub rule into a named rule influences the error recovery:
59
+ // the parser tries to consume all tokens which are neither in the follow
60
+ // set of loops and named rules. So be careful.
61
+ //
58
62
  // * Do not use actions in the lexer. Examples: de-quote string literals not
59
63
  // in the lexer, but in the parser; do not throw errors, but produce error
60
64
  // tokens if necessary.
@@ -106,6 +110,9 @@
106
110
  //
107
111
  // * The ANTLR error "missing attribute access on rule reference c in $c" can
108
112
  // be solved with using $ctx.c instead of $c
113
+ //
114
+ // * If you want to set a property starting with '$' like $syntax, use
115
+ // obj['$'+'syntax'] as the ANTLR tool would replace $syntax by $ctx.syntax
109
116
 
110
117
  grammar language;
111
118
  options {
@@ -114,6 +121,7 @@ options {
114
121
  }
115
122
  tokens {
116
123
  VIRTUAL, // used with setLocalToken()
124
+ OVER, // used with setLocalTokenIfBefore()
117
125
  HelperToken1, // used with setLocalToken(), does not appear in messages
118
126
  HelperToken2, // used with setLocalToken(), does not appear in messages
119
127
  HideAlternatives, // hide alternative tokens (no token seq!)
@@ -490,6 +498,8 @@ projectionSpec returns[ query ] locals[ src ]
490
498
  fromPath[ $src, 'ref']
491
499
  )?
492
500
  ( AS aliasName=ident['FromAlias'] { $src.name = $aliasName.id } )?
501
+ // ANTLR errors are better if we use ( A )? instead of ( A | ):
502
+ { if (!$src.name) this.classifyImplicitName( $src.scope ? 'FromAlias' : 'Without' ); }
493
503
  bracedSelectItemListDef[ $query ]?
494
504
  excludingClause[ $query ]?
495
505
  ;
@@ -818,7 +828,7 @@ annotateArtifact[ outer, loc, annos ] locals[ art, name = {} ]
818
828
  )*
819
829
  ')'
820
830
  (
821
- RETURNS '{'
831
+ RETURNS '{' { $art['$'+'syntax'] = 'returns'; }
822
832
  annotateElement[ $art ]*
823
833
  '}'
824
834
  optionalSemi
@@ -826,7 +836,7 @@ annotateArtifact[ outer, loc, annos ] locals[ art, name = {} ]
826
836
  requiredSemi
827
837
  )
828
838
  |
829
- RETURNS '{'
839
+ RETURNS '{' { $art['$'+'syntax'] = 'returns'; }
830
840
  annotateElement[ $art ]*
831
841
  '}'
832
842
  optionalSemi
@@ -1124,13 +1134,7 @@ bracedSelectItemListDef[ query ]
1124
1134
  '{'
1125
1135
  { if (!$query.columns) $query.columns = []; } // set it early to avoid "wildcard" errors
1126
1136
  (
1127
- ( star='*' // TODO: allow everywhere
1128
- {
1129
- $query.columns = [ this.tokenLocation( $star, undefined, '*' ) ];
1130
- }
1131
- |
1132
- selectItemDef[ $query.columns ]
1133
- )
1137
+ selectItemDef[ $query.columns ]
1134
1138
  ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
1135
1139
  selectItemDef[ $query.columns ]
1136
1140
  )*
@@ -1139,8 +1143,11 @@ bracedSelectItemListDef[ query ]
1139
1143
  ;
1140
1144
 
1141
1145
  selectItemDef[ outer ] locals[ annos = [] ]
1142
- @after{ this.attachLocation($art.art); }
1146
+ @after{ if ($ctx.art) this.attachLocation($art.art); }
1143
1147
  :
1148
+ star='*'
1149
+ { $outer.push( this.tokenLocation( $star, undefined, '*' ) ); }
1150
+ |
1144
1151
  { this.docComment( $annos ); }
1145
1152
  annotationAssignment_atn[ $annos ]*
1146
1153
  // VIRTUAL is keyword, except if before the following tokens texts:
@@ -1159,6 +1166,8 @@ selectItemDefBody[ outer, annos ] returns[ art = {} ]
1159
1166
  :
1160
1167
  (
1161
1168
  e=expression
1169
+ // we cannot use 'condition' instead, as long as we allow aliases without
1170
+ // AS (using rule 'ident' instead of 'identNoKeyword') -> ambiguities
1162
1171
  {
1163
1172
  $art = this.addItem( $outer, null, null, $annos, { value: $e.expr } );
1164
1173
  }
@@ -1218,11 +1227,7 @@ selectItemInlineList[ art, clause ]
1218
1227
  '{'
1219
1228
  { $art[$clause] = []; }
1220
1229
  (
1221
- ( star='*' // TODO: allow everywhere
1222
- { $art[$clause].push( this.tokenLocation( $star, undefined, '*' ) ); }
1223
- |
1224
- selectItemInlineDef[ $art[$clause] ]
1225
- )
1230
+ selectItemInlineDef[ $art[$clause] ]
1226
1231
  ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
1227
1232
  selectItemInlineDef[ $art[$clause] ]
1228
1233
  )*
@@ -1231,8 +1236,11 @@ selectItemInlineList[ art, clause ]
1231
1236
  ;
1232
1237
 
1233
1238
  selectItemInlineDef[ outer ] locals[ annos = [] ]
1234
- @after{ this.attachLocation($art.art); }
1239
+ @after{ if ($ctx.art) this.attachLocation($art.art); }
1235
1240
  :
1241
+ star='*'
1242
+ { $outer.push( this.tokenLocation( $star, undefined, '*' ) ); }
1243
+ |
1236
1244
  { this.docComment( $annos ); }
1237
1245
  annotationAssignment_atn[ $annos ]*
1238
1246
  art=selectItemDefBody[ $outer, $annos ]
@@ -1471,8 +1479,20 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
1471
1479
  head=Number
1472
1480
  { $art['$'+'typeArgs'] = [ this.numberLiteral( $head ) ]; }
1473
1481
  ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1474
- tail=Number
1475
- { $art['$'+'typeArgs'].push( this.numberLiteral( $tail ) ); }
1482
+ (
1483
+ v=VARIABLE
1484
+ { $art['$'+'typeArgs'].push(
1485
+ { literal: 'string', val: 'variable', location: this.tokenLocation($v) } );
1486
+ }
1487
+ |
1488
+ f=FLOATING
1489
+ { $art['$'+'typeArgs'].push(
1490
+ { literal: 'string', val: 'floating', location: this.tokenLocation($f) } );
1491
+ }
1492
+ |
1493
+ tail=Number
1494
+ { $art['$'+'typeArgs'].push( this.numberLiteral( $tail ) ); }
1495
+ )
1476
1496
  )*
1477
1497
  ')'
1478
1498
  { this.docComment( $annos ); }
@@ -1696,8 +1716,20 @@ typeRefOptArgs[ art ]
1696
1716
  head=Number
1697
1717
  { $art['$'+'typeArgs'] = [ this.numberLiteral( $head ) ]; }
1698
1718
  ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1699
- tail=Number
1700
- { $art['$'+'typeArgs'].push( this.numberLiteral( $tail ) ); }
1719
+ (
1720
+ v=VARIABLE
1721
+ { $art['$'+'typeArgs'].push(
1722
+ { literal: 'string', val: 'variable', location: this.tokenLocation($v) } );
1723
+ }
1724
+ |
1725
+ f=FLOATING
1726
+ { $art['$'+'typeArgs'].push(
1727
+ { literal: 'string', val: 'floating', location: this.tokenLocation($f) } );
1728
+ }
1729
+ |
1730
+ tail=Number
1731
+ { $art['$'+'typeArgs'].push( this.numberLiteral( $tail ) ); }
1732
+ )
1701
1733
  )*
1702
1734
  ')'
1703
1735
  |
@@ -1732,6 +1764,87 @@ orderByClause[ inQuery ] returns [ query ]
1732
1764
  ( ',' obn=orderBySpec { $query.orderBy.push( $obn.ob ); } )*
1733
1765
  ;
1734
1766
 
1767
+ overOrderByClause returns [ expr ]
1768
+ :
1769
+ o=ORDER b=BY { $expr = { op: this.tokenLocation($o, $b, 'orderBy' ) , args: [] }}
1770
+ ob1=orderBySpec { $expr.args.push( $ob1.ob ); }
1771
+ ( ',' obn=orderBySpec { $expr.args.push( $obn.ob ); } )*
1772
+ ;
1773
+
1774
+ partitionByClause returns [ expr ]
1775
+ :
1776
+ p=PARTITION b=BY { $expr = { op: this.tokenLocation($p, $b, 'partitionBy' ) , args: [] }}
1777
+ e1=expression { $expr.args.push( $e1.expr ); }
1778
+ ( ',' en=expression { $expr.args.push( $en.expr ); } )*
1779
+ ;
1780
+
1781
+ windowFrameClause returns [ wf ]
1782
+ :
1783
+ r=ROWS { $wf = { op: this.tokenLocation($r, null, 'rows' ) , args: [] }}
1784
+ wfe=windowFrameExtentSpec { $wf.args.push( $wfe.wfe ); }
1785
+ ;
1786
+
1787
+ windowFrameExtentSpec returns[ wfe ]
1788
+ :
1789
+ { $wfe = {} }
1790
+ windowFrameStartSpec [ $wfe ]
1791
+ |
1792
+ b=BETWEEN
1793
+ { $wfe = { op: this.tokenLocation( $b, null, 'frameBetween' ), args: [] } }
1794
+ wfb1=windowFrameBoundSpec { $wfe.args.push( $wfb1.wfb ); }
1795
+ AND
1796
+ wfb2=windowFrameBoundSpec { $wfe.args.push( $wfb2.wfb ); }
1797
+ ;
1798
+
1799
+ windowFrameBoundSpec returns [ wfb ]
1800
+ @after{ /* #ATN 1 */ }
1801
+ :
1802
+ // #ATN: Not ll1 because `UNBOUNDED` could also be part of the windowFrameStartSpec
1803
+ // `UNBOUNDED` would then be immediately followed by `PRECEDING`
1804
+ u=UNBOUNDED f=FOLLOWING
1805
+ { $wfb = { op: this.tokenLocation($u, $f, 'unboundedFollowing' ), args: []} }
1806
+ |
1807
+ // #ATN: Not ll1 because `Number` could also be part of the windowFrameStartSpec
1808
+ // `Number` would then be immediately followed by `PRECEDING`
1809
+ n=Number f=FOLLOWING
1810
+ { $wfb = { op: this.tokenLocation($n, $f, 'following' ), args: [ this.numberLiteral( $n ) ]} }
1811
+ |
1812
+ { $wfb = {} }
1813
+ windowFrameStartSpec [ $wfb ]
1814
+ ;
1815
+
1816
+ windowFrameStartSpec [ wf ]
1817
+ :
1818
+ u=UNBOUNDED p=PRECEDING
1819
+ {
1820
+ $wf.op = this.tokenLocation($u, $p, 'unboundedPreceding' );
1821
+ $wf.args = [];
1822
+ }
1823
+ |
1824
+ n=Number p=PRECEDING
1825
+ {
1826
+ $wf.op = this.tokenLocation($p, null, 'preceding' );
1827
+ $wf.args = [ this.numberLiteral( $n ) ];
1828
+ }
1829
+ |
1830
+ c=CURRENT r=ROW
1831
+ {
1832
+ $wf.op = this.tokenLocation($c, $r, 'currentRow' );
1833
+ $wf.args = [];
1834
+ }
1835
+ ;
1836
+
1837
+ overClause returns [ over ]
1838
+ @after { this.attachLocation($over); }
1839
+ :
1840
+ o=OVER { $over = { op: this.tokenLocation( $o, null, 'over' ) , args: [] } }
1841
+ '('
1842
+ ( pb=partitionByClause { $over.args.push( $pb.expr ); } )?
1843
+ ( ob=overOrderByClause { $over.args.push( $ob.expr ); } )?
1844
+ ( wf=windowFrameClause { $over.args.push( $wf.wf ); } )?
1845
+ ')'
1846
+ ;
1847
+
1735
1848
  limitClause[ inQuery ] returns [ query ]
1736
1849
  :
1737
1850
  limkw=LIMIT { $query = this.unaryOpForParens( $inQuery, '$'+'query' ); }
@@ -1790,13 +1903,7 @@ queryPrimary returns[ query = {} ]
1790
1903
  { $query.quantifier = this.tokenLocation( $ad, undefined, $ad.text.toLowerCase() ); }
1791
1904
  )?
1792
1905
  { $query.columns = []; } // set it early to avoid "wildcard" errors
1793
- ( star='*'
1794
- {
1795
- $query.columns = [ this.tokenLocation( $star, undefined, '*' ) ];
1796
- }
1797
- |
1798
- selectItemDef[ $query.columns ]
1799
- )
1906
+ selectItemDef[ $query.columns ]
1800
1907
  ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
1801
1908
  selectItemDef[ $query.columns ]
1802
1909
  )*
@@ -1900,6 +2007,8 @@ tableTerm returns [ table ]
1900
2007
  // if we would use rule `ident`, we would either had to make all JOIN
1901
2008
  // kinds reserved or introduce ATN
1902
2009
  )?
2010
+ // ANTLR errors are better if we use ( A | B )? instead of ( A | B | ):
2011
+ { if (!$table.name) this.classifyImplicitName( $table.scope ? 'FromAlias' : 'Without' ); }
1903
2012
  |
1904
2013
  open='('
1905
2014
  // #ATN: The following alternative is not LL1, because both can start with
@@ -1954,7 +2063,7 @@ fromPath[ qp, idkind ]
1954
2063
  condition returns [ cond ] locals [ args = [], orl = [] ]
1955
2064
  @after{
1956
2065
  $cond = ($args.length == 1)
1957
- ? $args[0]
2066
+ ? this.attachLocation( $args[0] )
1958
2067
  : this.attachLocation({ op: $orl[0], args: $args });
1959
2068
  }
1960
2069
  :
@@ -2115,6 +2224,10 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
2115
2224
  this.notSupportedYet( $ne ); }
2116
2225
  |
2117
2226
  vp=valuePath[ 'ref', null ] { $expr = this.valuePathAst( $vp.qp ); }
2227
+ { this.setLocalTokenIfBefore( 'OVER', 'OVER', /^\($/i ); }
2228
+ (
2229
+ over=overClause { $expr.suffix = [ $over.over ] }
2230
+ )?
2118
2231
  |
2119
2232
  ':'
2120
2233
  ( vp=valuePath[ 'paramref', this.startLocation() ]
@@ -2315,7 +2428,7 @@ optionalCardinality[ pathStep ]
2315
2428
  // completion just produces `:`after having inserted a Number - TODO.
2316
2429
  { if (this._input.LT(2).text !== ':') return $ctx; }
2317
2430
  ( trgMax=Number ':'
2318
- { if ($pathStep) $pathStep.cardinality = { targetMax: this.numberLiteral( $trgMax ) } }
2431
+ { if ($pathStep) $pathStep.cardinality = { targetMax: this.numberLiteral( $trgMax ), location: this.startLocation() }; }
2319
2432
  )
2320
2433
  ;
2321
2434
 
@@ -2550,6 +2663,7 @@ ident[ category ] returns[ id ]
2550
2663
  | COMPOSITION
2551
2664
  | CONTEXT
2552
2665
  | CROSS
2666
+ | CURRENT
2553
2667
  | DAY
2554
2668
  | DEFAULT
2555
2669
  | DEFINE
@@ -2566,6 +2680,8 @@ ident[ category ] returns[ id ]
2566
2680
  | EXCLUDING
2567
2681
  | EXTEND
2568
2682
  | FIRST
2683
+ | FLOATING
2684
+ | FOLLOWING
2569
2685
  | FULL
2570
2686
  | FUNCTION
2571
2687
  | GROUP
@@ -2596,10 +2712,14 @@ ident[ category ] returns[ id ]
2596
2712
  | ORDER
2597
2713
  | OUTER
2598
2714
  | PARAMETERS
2715
+ | PARTITION
2716
+ | PRECEDING
2599
2717
  | PROJECTION
2600
2718
  | REDIRECTED
2601
2719
  | RETURNS
2602
2720
  | RIGHT
2721
+ | ROW
2722
+ | ROWS
2603
2723
  | SECOND
2604
2724
  | SERVICE
2605
2725
  | THEN
@@ -2608,6 +2728,8 @@ ident[ category ] returns[ id ]
2608
2728
  | TO
2609
2729
  | TYPE
2610
2730
  | USING
2731
+ | UNBOUNDED
2732
+ | VARIABLE
2611
2733
  | VIEW
2612
2734
  | YEAR
2613
2735
  ;
@@ -2708,6 +2830,7 @@ BOTH : [bB][oO][tT][hH] ;
2708
2830
  COMPOSITION : [cC][oO][mM][pP][oO][sS][iI][tT][iI][oO][nN] ;
2709
2831
  CONTEXT : [cC][oO][nN][tT][eE][xX][tT] ;
2710
2832
  CROSS : [cC][rR][oO][sS][sS] ;
2833
+ CURRENT : [cC][uU][rR][rR][eE][nN][tT] ;
2711
2834
  DAY : [dD][aA][yY] ;
2712
2835
  DEFAULT : [dD][eE][fF][aA][uU][lL][tT] ;
2713
2836
  DEFINE : [dD][eE][fF][iI][nN][eE] ;
@@ -2724,6 +2847,8 @@ EXCEPT : [eE][xX][cC][eE][pP][tT] ;
2724
2847
  EXCLUDING : [eE][xX][cC][lL][uU][dD][iI][nN][gG] ;
2725
2848
  EXTEND : [eE][xX][tT][eE][nN][dD] ;
2726
2849
  FIRST : [fF][iI][rR][sS][tT] ;
2850
+ FLOATING : [fF][lL][oO][aA][tT][iI][nN][gG] ;
2851
+ FOLLOWING : [fF][oO][lL][lL][oO][wW][iI][nN][gG] ;
2727
2852
  FULL : [fF][uU][lL][lL] ;
2728
2853
  FUNCTION : [fF][uU][nN][cC][tT][iI][oO][nN] ;
2729
2854
  GROUP : [gG][rR][oO][uU][pP] ;
@@ -2753,11 +2878,16 @@ ONE : [oO][nN][eE] ;
2753
2878
  OR : [oO][rR] ;
2754
2879
  ORDER : [oO][rR][dD][eE][rR] ;
2755
2880
  OUTER : [oO][uU][tT][eE][rR] ;
2881
+ // OVER : [oO][vV][eE][rR] ;
2756
2882
  PARAMETERS : [pP][aA][rR][aA][mM][eE][tT][eE][rR][sS] ;
2883
+ PARTITION: [pP][aA][rR][tT][iI][tT][iI][oO][nN] ;
2884
+ PRECEDING: [pP][rR][eE][cC][eE][dD][iI][nN][gG] ;
2757
2885
  PROJECTION : [pP][rR][oO][jJ][eE][cC][tT][iI][oO][nN] ;
2758
2886
  REDIRECTED : [rR][eE][dD][iI][rR][eE][cC][tT][eE][dD] ;
2759
2887
  RETURNS : [rR][eE][tT][uU][rR][nN][sS] ;
2760
2888
  RIGHT : [rR][iI][gG][hH][tT] ;
2889
+ ROW : [rR][oO][wW] ;
2890
+ ROWS : [rR][oO][wW][sS] ;
2761
2891
  SECOND : [sS][eE][cC][oO][nN][dD] ;
2762
2892
  SERVICE : [sS][eE][rR][vV][iI][cC][eE] ;
2763
2893
  THEN : [tT][hH][eE][nN] ;
@@ -2765,7 +2895,9 @@ TRAILING : [tT][rR][aA][iI][lL][iI][nN][gG] ;
2765
2895
  TO : [tT][oO] ; // or make reserved? (is in SQL-92)
2766
2896
  TYPE : [tT][yY][pP][eE] ;
2767
2897
  UNION : [uU][nN][iI][oO][nN] ;
2898
+ UNBOUNDED : [uU][nN][bB][oO][uU][nN][dD][eE][dD] ;
2768
2899
  USING : [uU][sS][iI][nN][gG] ;
2900
+ VARIABLE : [vV][aA][rR][iI][aA][bB][lL][eE] ;
2769
2901
  VIEW : [vV][iI][eE][wW] ;
2770
2902
  // VIRTUAL: [vV][iI][rR][tT][uU][aA][lL] ; see tokens {}
2771
2903
  YEAR : [yY][eE][aA][rR] ;