@sap/cds-compiler 2.5.2 → 2.11.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 (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. package/lib/utils/string.js +0 -17
@@ -10,7 +10,7 @@
10
10
 
11
11
  const antlr4 = require('antlr4');
12
12
 
13
- const { makeMessageFunction, CompileMessage } = require('../base/messages');
13
+ const { CompileMessage } = require('../base/messages');
14
14
  const errorStrategy = require('./errorStrategy');
15
15
 
16
16
  const Parser = require('../gen/languageParser').languageParser;
@@ -112,7 +112,7 @@ const rules = {
112
112
  expr: { func: 'conditionEOF', returns: 'cond' }, // yes, condition
113
113
  };
114
114
 
115
- function parse( source, filename = '<undefined>.cds', options = {}, rule = 'cdl' ) {
115
+ function parse( source, filename = '<undefined>.cds', options = {}, messageFunctions, rule = 'cdl' ) {
116
116
  const lexer = new Lexer( new antlr4.InputStream(source) );
117
117
  const tokenStream = new RewriteTypeTokenStream(lexer);
118
118
  /** @type {object} */
@@ -121,7 +121,7 @@ function parse( source, filename = '<undefined>.cds', options = {}, rule = 'cdl'
121
121
 
122
122
  parser.filename = filename;
123
123
  parser.options = options;
124
- parser.$message = makeMessageFunction( parser, options, 'parse' ); // sets parser.messages
124
+ parser.$messageFunctions = messageFunctions;
125
125
 
126
126
  initTokenRewrite( parser, tokenStream );
127
127
  // comment the following 2 lines if you want to output the parser errors directly:
@@ -162,6 +162,18 @@ function parse( source, filename = '<undefined>.cds', options = {}, rule = 'cdl'
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
+
176
+ // TODO: clarify with LSP colleagues: still necessary?
165
177
  if (parser.messages) {
166
178
  Object.defineProperty( ast, 'messages',
167
179
  { value: parser.messages, configurable: true, writable: true } );
@@ -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)
@@ -13,6 +13,14 @@ const locUtils = require('../base/location');
13
13
  const { parseDocComment } = require('./docCommentParser');
14
14
  const { functionsWithoutParens, specialFunctions } = require('../compiler/builtins');
15
15
 
16
+
17
+ // Push message `msg` with location `loc` to array of errors:
18
+ function _message( parser, severity, id, loc, ...args ) {
19
+ const msg = parser.$messageFunctions[severity]; // set in antlrParser.js
20
+ return msg( id,
21
+ (loc instanceof antlr4.CommonToken) ? parser.tokenLocation(loc) : loc, ...args );
22
+ }
23
+
16
24
  // Class which is to be used as grammar option with
17
25
  // grammar <name> options { superclass = genericAntlrParser; }
18
26
  //
@@ -34,13 +42,15 @@ function GenericAntlrParser( ...args ) {
34
42
 
35
43
  GenericAntlrParser.prototype = Object.assign(
36
44
  Object.create( antlr4.Parser.prototype ), {
37
- message: function(...args) { return message.call(this, 'message', ...args); },
38
- error: function(...args) { return message.call(this, 'error', ...args); },
39
- warning: function(...args) { return message.call(this, 'warning', ...args); },
40
- info: function(...args) { return message.call(this, 'info', ...args); },
45
+ message: function(...args) { return _message( this, 'message', ...args ); },
46
+ error: function(...args) { return _message( this, 'error', ...args ); },
47
+ warning: function(...args) { return _message( this, 'warning', ...args ); },
48
+ info: function(...args) { return _message( this, 'info', ...args ); },
41
49
  attachLocation,
42
50
  startLocation,
43
51
  tokenLocation,
52
+ multiLineTokenLocation,
53
+ previousTokenAtLocation,
44
54
  combinedLocation,
45
55
  surroundByParens,
46
56
  unaryOpForParens,
@@ -69,6 +79,7 @@ GenericAntlrParser.prototype = Object.assign(
69
79
  noAssignmentInSameLine,
70
80
  noSemicolonHere,
71
81
  setLocalToken,
82
+ setLocalTokenIfBefore,
72
83
  excludeExpected,
73
84
  isStraightBefore,
74
85
  meltKeywordToIdentifier,
@@ -110,14 +121,6 @@ const quotedLiteralPatterns = {
110
121
  },
111
122
  };
112
123
 
113
-
114
- // Push message `msg` with location `loc` to array of errors:
115
- function message( severity, id, loc, ...args ) {
116
- const msg = this.$message[severity];
117
- return msg( id, // function $message is set in antlrParser.js
118
- (loc instanceof antlr4.CommonToken) ? this.tokenLocation(loc) : loc, ...args );
119
- }
120
-
121
124
  // Use the following function for language constructs which we (currently)
122
125
  // just being able to parse, in able to run tests from HANA CDS. As soon as we
123
126
  // create ASTs for the language construct and put it into a CSN, a
@@ -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 ]
@@ -1407,6 +1415,8 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
1407
1415
  |
1408
1416
  ':'
1409
1417
  // #ATN: typeRefOptArgs can start with ARRAY or MANY or ASSOCIATION or TYPE or LOCALIZED
1418
+ // Nevertheless, MANY '{' is handled by local token rewrite:
1419
+ { this.setLocalToken( 'MANY', 'HelperToken1', /^[^\{]/ ); }
1410
1420
  (
1411
1421
  typeStruct[ $art ]
1412
1422
  optionalSemi
@@ -1416,6 +1426,12 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
1416
1426
  ( typeToMany[ $art ] | typeToOne[ $art ] | simplePath[ $art.target, 'artref' ] )
1417
1427
  typeAssociationCont[ $art ]?
1418
1428
  requiredSemi // and if its the ';'...
1429
+ |
1430
+ many=HelperToken1 // rewritten MANY before '{'
1431
+ { $art.items = { location: this.tokenLocation( $many ) };}
1432
+ typeStruct[ $art.items ]
1433
+ nullability[ $art.items ]?
1434
+ optionalSemi
1419
1435
  |
1420
1436
  (
1421
1437
  array=ARRAY of=OF
@@ -1455,6 +1471,7 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
1455
1471
  annotationAssignment_ll1[ $annos ]*
1456
1472
  requiredSemi
1457
1473
  |
1474
+ // alt lookahead includes MANY '{'
1458
1475
  { $art.type = {}; }
1459
1476
  simplePath[ $art.type, 'artref' ]
1460
1477
  (
@@ -1462,8 +1479,20 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
1462
1479
  head=Number
1463
1480
  { $art['$'+'typeArgs'] = [ this.numberLiteral( $head ) ]; }
1464
1481
  ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1465
- tail=Number
1466
- { $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
+ )
1467
1496
  )*
1468
1497
  ')'
1469
1498
  { this.docComment( $annos ); }
@@ -1687,8 +1716,20 @@ typeRefOptArgs[ art ]
1687
1716
  head=Number
1688
1717
  { $art['$'+'typeArgs'] = [ this.numberLiteral( $head ) ]; }
1689
1718
  ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1690
- tail=Number
1691
- { $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
+ )
1692
1733
  )*
1693
1734
  ')'
1694
1735
  |
@@ -1723,6 +1764,87 @@ orderByClause[ inQuery ] returns [ query ]
1723
1764
  ( ',' obn=orderBySpec { $query.orderBy.push( $obn.ob ); } )*
1724
1765
  ;
1725
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
+
1726
1848
  limitClause[ inQuery ] returns [ query ]
1727
1849
  :
1728
1850
  limkw=LIMIT { $query = this.unaryOpForParens( $inQuery, '$'+'query' ); }
@@ -1781,13 +1903,7 @@ queryPrimary returns[ query = {} ]
1781
1903
  { $query.quantifier = this.tokenLocation( $ad, undefined, $ad.text.toLowerCase() ); }
1782
1904
  )?
1783
1905
  { $query.columns = []; } // set it early to avoid "wildcard" errors
1784
- ( star='*'
1785
- {
1786
- $query.columns = [ this.tokenLocation( $star, undefined, '*' ) ];
1787
- }
1788
- |
1789
- selectItemDef[ $query.columns ]
1790
- )
1906
+ selectItemDef[ $query.columns ]
1791
1907
  ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
1792
1908
  selectItemDef[ $query.columns ]
1793
1909
  )*
@@ -1891,6 +2007,8 @@ tableTerm returns [ table ]
1891
2007
  // if we would use rule `ident`, we would either had to make all JOIN
1892
2008
  // kinds reserved or introduce ATN
1893
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' ); }
1894
2012
  |
1895
2013
  open='('
1896
2014
  // #ATN: The following alternative is not LL1, because both can start with
@@ -1945,7 +2063,7 @@ fromPath[ qp, idkind ]
1945
2063
  condition returns [ cond ] locals [ args = [], orl = [] ]
1946
2064
  @after{
1947
2065
  $cond = ($args.length == 1)
1948
- ? $args[0]
2066
+ ? this.attachLocation( $args[0] )
1949
2067
  : this.attachLocation({ op: $orl[0], args: $args });
1950
2068
  }
1951
2069
  :
@@ -2106,6 +2224,10 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
2106
2224
  this.notSupportedYet( $ne ); }
2107
2225
  |
2108
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
+ )?
2109
2231
  |
2110
2232
  ':'
2111
2233
  ( vp=valuePath[ 'paramref', this.startLocation() ]
@@ -2306,7 +2428,7 @@ optionalCardinality[ pathStep ]
2306
2428
  // completion just produces `:`after having inserted a Number - TODO.
2307
2429
  { if (this._input.LT(2).text !== ':') return $ctx; }
2308
2430
  ( trgMax=Number ':'
2309
- { if ($pathStep) $pathStep.cardinality = { targetMax: this.numberLiteral( $trgMax ) } }
2431
+ { if ($pathStep) $pathStep.cardinality = { targetMax: this.numberLiteral( $trgMax ), location: this.startLocation() }; }
2310
2432
  )
2311
2433
  ;
2312
2434
 
@@ -2541,6 +2663,7 @@ ident[ category ] returns[ id ]
2541
2663
  | COMPOSITION
2542
2664
  | CONTEXT
2543
2665
  | CROSS
2666
+ | CURRENT
2544
2667
  | DAY
2545
2668
  | DEFAULT
2546
2669
  | DEFINE
@@ -2557,6 +2680,8 @@ ident[ category ] returns[ id ]
2557
2680
  | EXCLUDING
2558
2681
  | EXTEND
2559
2682
  | FIRST
2683
+ | FLOATING
2684
+ | FOLLOWING
2560
2685
  | FULL
2561
2686
  | FUNCTION
2562
2687
  | GROUP
@@ -2587,10 +2712,14 @@ ident[ category ] returns[ id ]
2587
2712
  | ORDER
2588
2713
  | OUTER
2589
2714
  | PARAMETERS
2715
+ | PARTITION
2716
+ | PRECEDING
2590
2717
  | PROJECTION
2591
2718
  | REDIRECTED
2592
2719
  | RETURNS
2593
2720
  | RIGHT
2721
+ | ROW
2722
+ | ROWS
2594
2723
  | SECOND
2595
2724
  | SERVICE
2596
2725
  | THEN
@@ -2599,6 +2728,8 @@ ident[ category ] returns[ id ]
2599
2728
  | TO
2600
2729
  | TYPE
2601
2730
  | USING
2731
+ | UNBOUNDED
2732
+ | VARIABLE
2602
2733
  | VIEW
2603
2734
  | YEAR
2604
2735
  ;
@@ -2699,6 +2830,7 @@ BOTH : [bB][oO][tT][hH] ;
2699
2830
  COMPOSITION : [cC][oO][mM][pP][oO][sS][iI][tT][iI][oO][nN] ;
2700
2831
  CONTEXT : [cC][oO][nN][tT][eE][xX][tT] ;
2701
2832
  CROSS : [cC][rR][oO][sS][sS] ;
2833
+ CURRENT : [cC][uU][rR][rR][eE][nN][tT] ;
2702
2834
  DAY : [dD][aA][yY] ;
2703
2835
  DEFAULT : [dD][eE][fF][aA][uU][lL][tT] ;
2704
2836
  DEFINE : [dD][eE][fF][iI][nN][eE] ;
@@ -2715,6 +2847,8 @@ EXCEPT : [eE][xX][cC][eE][pP][tT] ;
2715
2847
  EXCLUDING : [eE][xX][cC][lL][uU][dD][iI][nN][gG] ;
2716
2848
  EXTEND : [eE][xX][tT][eE][nN][dD] ;
2717
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] ;
2718
2852
  FULL : [fF][uU][lL][lL] ;
2719
2853
  FUNCTION : [fF][uU][nN][cC][tT][iI][oO][nN] ;
2720
2854
  GROUP : [gG][rR][oO][uU][pP] ;
@@ -2744,11 +2878,16 @@ ONE : [oO][nN][eE] ;
2744
2878
  OR : [oO][rR] ;
2745
2879
  ORDER : [oO][rR][dD][eE][rR] ;
2746
2880
  OUTER : [oO][uU][tT][eE][rR] ;
2881
+ // OVER : [oO][vV][eE][rR] ;
2747
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] ;
2748
2885
  PROJECTION : [pP][rR][oO][jJ][eE][cC][tT][iI][oO][nN] ;
2749
2886
  REDIRECTED : [rR][eE][dD][iI][rR][eE][cC][tT][eE][dD] ;
2750
2887
  RETURNS : [rR][eE][tT][uU][rR][nN][sS] ;
2751
2888
  RIGHT : [rR][iI][gG][hH][tT] ;
2889
+ ROW : [rR][oO][wW] ;
2890
+ ROWS : [rR][oO][wW][sS] ;
2752
2891
  SECOND : [sS][eE][cC][oO][nN][dD] ;
2753
2892
  SERVICE : [sS][eE][rR][vV][iI][cC][eE] ;
2754
2893
  THEN : [tT][hH][eE][nN] ;
@@ -2756,7 +2895,9 @@ TRAILING : [tT][rR][aA][iI][lL][iI][nN][gG] ;
2756
2895
  TO : [tT][oO] ; // or make reserved? (is in SQL-92)
2757
2896
  TYPE : [tT][yY][pP][eE] ;
2758
2897
  UNION : [uU][nN][iI][oO][nN] ;
2898
+ UNBOUNDED : [uU][nN][bB][oO][uU][nN][dD][eE][dD] ;
2759
2899
  USING : [uU][sS][iI][nN][gG] ;
2900
+ VARIABLE : [vV][aA][rR][iI][aA][bB][lL][eE] ;
2760
2901
  VIEW : [vV][iI][eE][wW] ;
2761
2902
  // VIRTUAL: [vV][iI][rR][tT][uU][aA][lL] ; see tokens {}
2762
2903
  YEAR : [yY][eE][aA][rR] ;