@sap/cds-compiler 6.1.0 → 6.3.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 (90) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/bin/cdsc.js +17 -6
  3. package/bin/cdsse.js +1 -1
  4. package/bin/cdsv2m.js +1 -1
  5. package/lib/api/main.js +29 -7
  6. package/lib/api/options.js +1 -1
  7. package/lib/base/builtins.js +9 -0
  8. package/lib/base/keywords.js +1 -1
  9. package/lib/base/message-registry.js +41 -10
  10. package/lib/base/messages.js +13 -6
  11. package/lib/base/model.js +1 -1
  12. package/lib/base/optionProcessorHelper.js +7 -2
  13. package/lib/checks/assocOutsideService.js +17 -30
  14. package/lib/checks/checkForTypes.js +0 -18
  15. package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
  16. package/lib/checks/featureFlags.js +4 -1
  17. package/lib/checks/onConditions.js +2 -2
  18. package/lib/checks/queryNoDbArtifacts.js +16 -15
  19. package/lib/checks/types.js +1 -1
  20. package/lib/checks/utils.js +30 -6
  21. package/lib/checks/validator.js +4 -5
  22. package/lib/compiler/assert-consistency.js +3 -1
  23. package/lib/compiler/base.js +1 -1
  24. package/lib/compiler/builtins.js +1 -1
  25. package/lib/compiler/checks.js +85 -39
  26. package/lib/compiler/define.js +24 -5
  27. package/lib/compiler/extend.js +1 -1
  28. package/lib/compiler/finalize-parse-cdl.js +9 -1
  29. package/lib/compiler/generate.js +4 -4
  30. package/lib/compiler/index.js +88 -6
  31. package/lib/compiler/lsp-api.js +2 -0
  32. package/lib/compiler/populate.js +8 -8
  33. package/lib/compiler/propagator.js +1 -1
  34. package/lib/compiler/resolve.js +22 -21
  35. package/lib/compiler/shared.js +6 -6
  36. package/lib/compiler/tweak-assocs.js +53 -31
  37. package/lib/compiler/utils.js +9 -16
  38. package/lib/compiler/xpr-rewrite.js +2 -2
  39. package/lib/gen/BaseParser.js +35 -29
  40. package/lib/gen/CdlGrammar.checksum +1 -1
  41. package/lib/gen/CdlParser.js +1424 -1430
  42. package/lib/gen/Dictionary.json +1 -2
  43. package/lib/gen/cdlKeywords.json +26 -0
  44. package/lib/inspect/inspectPropagation.js +1 -1
  45. package/lib/json/from-csn.js +2 -2
  46. package/lib/json/to-csn.js +1 -1
  47. package/lib/language/multiLineStringParser.js +1 -1
  48. package/lib/model/cloneCsn.js +1 -0
  49. package/lib/model/csnRefs.js +9 -4
  50. package/lib/model/csnUtils.js +67 -2
  51. package/lib/optionProcessor.js +9 -9
  52. package/lib/parsers/AstBuildingParser.js +28 -26
  53. package/lib/parsers/identifiers.js +2 -30
  54. package/lib/render/toCdl.js +73 -13
  55. package/lib/render/toSql.js +127 -108
  56. package/lib/render/utils/common.js +4 -2
  57. package/lib/render/utils/sql.js +67 -0
  58. package/lib/transform/addTenantFields.js +4 -4
  59. package/lib/transform/db/assertUnique.js +2 -1
  60. package/lib/transform/db/associations.js +37 -1
  61. package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
  62. package/lib/transform/db/assocsToQueries/utils.js +1 -1
  63. package/lib/transform/db/cdsPersistence.js +1 -1
  64. package/lib/transform/db/expansion.js +37 -36
  65. package/lib/transform/db/killAnnotations.js +1 -0
  66. package/lib/transform/db/processSqlServices.js +20 -2
  67. package/lib/transform/draft/db.js +20 -20
  68. package/lib/transform/draft/odata.js +38 -40
  69. package/lib/transform/effective/associations.js +1 -1
  70. package/lib/transform/effective/flattening.js +40 -47
  71. package/lib/transform/effective/main.js +6 -4
  72. package/lib/transform/forOdata.js +201 -92
  73. package/lib/transform/forRelationalDB.js +151 -142
  74. package/lib/transform/localized.js +116 -109
  75. package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
  76. package/lib/transform/odata/createForeignKeys.js +73 -70
  77. package/lib/transform/odata/flattening.js +216 -200
  78. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
  79. package/lib/transform/odata/toFinalBaseType.js +40 -39
  80. package/lib/transform/odata/typesExposure.js +151 -133
  81. package/lib/transform/odata/utils.js +7 -6
  82. package/lib/transform/parseExpr.js +165 -162
  83. package/lib/transform/transformUtils.js +184 -551
  84. package/lib/transform/translateAssocsToJoins.js +511 -596
  85. package/lib/transform/tupleExpansion.js +495 -0
  86. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  87. package/lib/utils/moduleResolve.js +1 -1
  88. package/package.json +2 -2
  89. package/lib/base/cleanSymbols.js +0 -17
  90. package/lib/checks/nonexpandableStructured.js +0 -39
@@ -808,7 +808,6 @@
808
808
  "Type": "Core.Tag"
809
809
  },
810
810
  "Common.IsTimezone": {
811
- "$experimental": true,
812
811
  "AppliesTo": [
813
812
  "Property",
814
813
  "Parameter"
@@ -1035,7 +1034,6 @@
1035
1034
  "Type": "Edm.DateTimeOffset"
1036
1035
  },
1037
1036
  "Common.Timezone": {
1038
- "$experimental": true,
1039
1037
  "AppliesTo": [
1040
1038
  "Property",
1041
1039
  "Parameter"
@@ -3506,6 +3504,7 @@
3506
3504
  "Label": "Edm.String",
3507
3505
  "Parameters": "Collection(Common.ValueListParameter)",
3508
3506
  "PresentationVariantQualifier": "Core.SimpleIdentifier",
3507
+ "RelativeCollectionPath": "Edm.NavigationPropertyPath",
3509
3508
  "SearchSupported": "Edm.Boolean",
3510
3509
  "SelectionVariantQualifier": "Core.SimpleIdentifier"
3511
3510
  }
@@ -0,0 +1,26 @@
1
+ {
2
+ "reserved": [
3
+ "ALL",
4
+ "ANY",
5
+ "AS",
6
+ "BY",
7
+ "CASE",
8
+ "CAST",
9
+ "DISTINCT",
10
+ "EXISTS",
11
+ "FALSE",
12
+ "FROM",
13
+ "IN",
14
+ "KEY",
15
+ "NOT",
16
+ "NULL",
17
+ "OF",
18
+ "ON",
19
+ "SELECT",
20
+ "SOME",
21
+ "TRUE",
22
+ "WHEN",
23
+ "WHERE",
24
+ "WITH"
25
+ ]
26
+ }
@@ -36,7 +36,7 @@ function inspectPropagation( xsn, options, artifactName ) {
36
36
 
37
37
  if (!artifactXsn) {
38
38
  error(null, null, { name: artifactName },
39
- // eslint-disable-next-line @stylistic/js/max-len
39
+ // eslint-disable-next-line @stylistic/max-len
40
40
  'Artifact $(NAME) not found, only top-level artifacts and their elements are supported for now');
41
41
  return null;
42
42
  }
@@ -137,7 +137,7 @@ const typeProperties = [
137
137
  // do not include CSN v0.1.0 properties here:
138
138
  'target', 'elements', 'enum', 'items',
139
139
  'cardinality', // for association publishing in views
140
- 'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull',
140
+ 'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull', 'default',
141
141
  'keys', 'on', // only with 'target'
142
142
  ];
143
143
  const exprProperties = [
@@ -1142,7 +1142,7 @@ function elementsDict( def, spec, xsn ) {
1142
1142
  return elements;
1143
1143
  warning( 'syntax-expecting-returns', elements[$location],
1144
1144
  { prop: 'elements', parentprop: 'returns' },
1145
- // eslint-disable-next-line @stylistic/js/max-len
1145
+ // eslint-disable-next-line @stylistic/max-len
1146
1146
  'Expecting property $(PROP) to be put into an object for property $(PARENTPROP) when annotating action return structures' );
1147
1147
  xsn.returns = { kind: 'annotate', elements, location: elements[$location] };
1148
1148
  return undefined;
@@ -201,7 +201,7 @@ const propertyOrder = (function orderPositions() {
201
201
  const typeProperties = [
202
202
  'target', 'elements', 'enum', 'items',
203
203
  'cardinality', // for association publishing in views
204
- 'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull',
204
+ 'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull', 'default',
205
205
  'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED
206
206
  '$typeArgs', // for unresolved type arguments, e.g. through parseCql
207
207
  ];
@@ -77,7 +77,7 @@ class MultiLineStringParser {
77
77
  this.str = token.text; // Copy because .text is a getter
78
78
 
79
79
  if (this.str[0] !== '`' || this.str[this.str.length - 1] !== '`')
80
- // eslint-disable-next-line @stylistic/js/max-len
80
+ // eslint-disable-next-line @stylistic/max-len
81
81
  throw new CompilerAssertion('Invalid multi-line string sequence: Require string to be surrounded by back-ticks!');
82
82
 
83
83
  this.output = [];
@@ -39,6 +39,7 @@ const internalCsnProps = {
39
39
  $notNull: shallowCopy, // used for HANA CSN migrations
40
40
  $sqlService: shallowCopy,
41
41
  $dummyService: shallowCopy,
42
+ $dataProductService: shallowCopy,
42
43
  };
43
44
  const internalEnumerableCsnProps = {
44
45
  __proto__: null,
@@ -250,10 +250,9 @@ function csnRefs( csn, universalReady ) {
250
250
  // const { definitions } = csn;
251
251
  const cache = new WeakMap();
252
252
  setCache( BUILTIN_TYPE, '_origin', null );
253
- if (universalReady === 'init-all') {
254
- for (const name of Object.keys( csn.definitions || {}))
255
- initDefinition( name );
256
- }
253
+ if (universalReady === 'init-all')
254
+ initAllDefinitions();
255
+
257
256
  // Functions which set the new `baseEnv`:
258
257
  resolveRef.expandInline = function resolveExpandInline( ref, ...args ) {
259
258
  return cached( ref, '_env', () => navigationEnv( resolveRef( ref, ...args ).art ) );
@@ -278,6 +277,7 @@ function csnRefs( csn, universalReady ) {
278
277
  getColumnName: col => getCache( col, '$as' ),
279
278
  $getQueries: def => getCache( def, '$queries' ), // unstable API
280
279
  initDefinition,
280
+ initAllDefinitions,
281
281
  dropDefinitionCache,
282
282
  targetAspect,
283
283
  msgLocations,
@@ -489,6 +489,11 @@ function csnRefs( csn, universalReady ) {
489
489
  return query && cache.get( query.projection || query );
490
490
  }
491
491
 
492
+ function initAllDefinitions() {
493
+ for (const name of Object.keys( csn.definitions || {}))
494
+ initDefinition( name );
495
+ }
496
+
492
497
  function initDefinition( main ) {
493
498
  const name = typeof main === 'string' && main;
494
499
  if (name) {
@@ -64,6 +64,7 @@ function getUtils( model, universalReady ) {
64
64
  addStringAnnotationTo,
65
65
  getServiceName,
66
66
  getFinalTypeInfo,
67
+ resolvePath,
67
68
  get$combined,
68
69
  getQueryPrimarySource,
69
70
  ..._csnRefs,
@@ -448,6 +449,71 @@ function getUtils( model, universalReady ) {
448
449
  return target;
449
450
  }
450
451
  }
452
+
453
+
454
+ /**
455
+ * Resolve the type of an artifact
456
+ * If art is undefined, stop
457
+ * If art has elements or items.elements, stop
458
+ * If art has a type and the type is scalar, stop
459
+ * If art has a named type or a type ref, resolve it
460
+ */
461
+ function resolveType( art ) {
462
+ while (art &&
463
+ !((art.items && art.items.elements) || art.elements) &&
464
+ (art.type &&
465
+ ((!art.type.ref && !isBuiltinType(art.type)) || art.type.ref))) {
466
+ if (art.type.ref)
467
+ art = resolvePath(art.type);
468
+ else
469
+ art = model.definitions[art.type];
470
+ }
471
+ return art;
472
+ }
473
+
474
+ /**
475
+ * Path resolution, attach artifact to each path step, if found,
476
+ * Dereference types and follow associations.
477
+ *
478
+ * @param {any} path ref object
479
+ * @param {any} art start environment
480
+ * @returns {any} path with resolved artifacts or artifact
481
+ * (if called with simple ref paths)
482
+ */
483
+ function resolvePath( path, art = undefined ) {
484
+ let notFound = false;
485
+ for (let i = 0; i < path.ref.length && !notFound; i++) {
486
+ const ps = path.ref[i];
487
+ const id = ps.id || ps;
488
+ if (art) {
489
+ if (art.target)
490
+ art = model.definitions[art.target].elements[id];
491
+ else if (art.items && art.items.elements || art.elements)
492
+ art = (art.items && art.items.elements || art.elements)[id];
493
+
494
+ else
495
+ art = undefined;
496
+ }
497
+ else {
498
+ art = model.definitions[id];
499
+ }
500
+ art = resolveType(art);
501
+
502
+ // if path step has id, store art
503
+ if (ps.id && art)
504
+ ps._art = art;
505
+ notFound = !art;
506
+ }
507
+ // if resolve was called on constraint path, path has id.
508
+ // Store art and return path, if called recursively for model ref paths,
509
+ // return artifact only
510
+ if (path.ref[0].id) {
511
+ if (art)
512
+ path._art = art;
513
+ return path;
514
+ }
515
+ return art;
516
+ }
451
517
  }
452
518
 
453
519
 
@@ -1248,7 +1314,6 @@ function walkCsnPath( csn, path ) {
1248
1314
  for (const segment of path)
1249
1315
  obj = obj[segment];
1250
1316
 
1251
-
1252
1317
  return obj;
1253
1318
  }
1254
1319
 
@@ -1356,7 +1421,7 @@ function functionList( functions, thisArg ) {
1356
1421
  * @returns {boolean}
1357
1422
  */
1358
1423
  function isDollarSelfOrProjectionOperand( arg ) {
1359
- return arg.ref && arg.ref.length === 1 &&
1424
+ return arg.ref?.length === 1 &&
1360
1425
  (arg.ref[0] === '$self' || arg.ref[0] === '$projection');
1361
1426
  }
1362
1427
 
@@ -1,6 +1,6 @@
1
1
  // Compiler options
2
2
 
3
- /* eslint @stylistic/js/max-len: 0 */
3
+ /* eslint @stylistic/max-len: 0 */
4
4
 
5
5
  // Remarks:
6
6
  // - The specification is client-tool centric (bin/cdsc.js):
@@ -169,7 +169,8 @@ optionProcessor
169
169
  Q, toSql [options] <files...> Generate SQL DDL statements
170
170
  J, forJava [options] <files...> Generate CSN for the Java Runtime
171
171
  toCsn [options] <files...> (default) Generate original model as CSN
172
- parseCdl [options] <file> Generate a CSN that is close to the CDL source.
172
+ parse [options] <file> Parse the input file. For CDL input, generate a CSN that is
173
+ close to the CDL source.
173
174
  explain <message-id> Explain a compiler message.
174
175
  parseOnly [options] <files...> (internal) Stop compilation after parsing, write messages to <stderr>,
175
176
  per default no output.
@@ -264,7 +265,7 @@ optionProcessor.command('O, toOdata')
264
265
  .option(' --odata-vocabularies <list>')
265
266
  .option(' --odata-no-creator')
266
267
  .option(' --draft-messages')
267
- .option(' --add-annotation-AddressViaNavigationPath')
268
+ .option(' --draft-user-description')
268
269
  .option('-c, --csn')
269
270
  .option('-f, --odata-format <format>', { valid: [ 'flat', 'structured' ] })
270
271
  .option('-n, --sql-mapping <style>', { valid: [ 'plain', 'quoted', 'hdbcds' ], aliases: [ '--names' ] })
@@ -300,8 +301,7 @@ optionProcessor.command('O, toOdata')
300
301
  { prefix: { alias, ns, uri }, ... }
301
302
  --odata-no-creator Omit creator identification in API
302
303
  --draft-messages Add draft messages as part of the draft creation
303
- --add-annotation-AddressViaNavigationPath Add annotation "@Common.AddressViaNavigationPath" to the services
304
- containing draft enabled entitties
304
+ --draft-user-description Add user description fields to the DraftAdministrativeData entity
305
305
  -n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
306
306
  the corresponding database name (see "--sql-mapping" for "toSql")
307
307
  plain : (default) Names in uppercase and flattened with underscores
@@ -534,15 +534,15 @@ optionProcessor.command('toCsn')
534
534
  --with-localized Add localized convenience views to the CSN output.
535
535
  `);
536
536
 
537
- optionProcessor.command('parseCdl')
537
+ optionProcessor.command('parse', { aliases: [ 'parseCdl' ] })
538
538
  .option('-h, --help')
539
539
  .positionalArgument('<file>')
540
540
  .option(' --with-locations')
541
541
  .help(`
542
- Usage: cdsc parseCdl [options] <file>
542
+ Usage: cdsc parse [options] <file>
543
543
 
544
- Only parse the CDL and output a CSN that is close to the source. Does not
545
- resolve imports, apply extensions or expand any queries.
544
+ Only parse the input file. For CDL input, output a CSN that is close to the source.
545
+ Does not resolve imports, apply extensions or expand any queries.
546
546
 
547
547
  Options
548
548
  --with-locations Add $location to CSN artifacts.
@@ -47,7 +47,10 @@ const extensionsCode = {
47
47
  const PRECEDENCE_OF_EQUAL = 10;
48
48
 
49
49
  class AstBuildingParser extends BaseParser {
50
- leanConditions = { afterBrace: true, atRightParen: true, fail: true };
50
+ leanConditions = {
51
+ afterBrace: true,
52
+ fail: true,
53
+ };
51
54
 
52
55
  constructor( lexer, keywords, table, options, messageFunctions ) {
53
56
  super( lexer, keywords, table ); // lexer has file
@@ -115,13 +118,16 @@ class AstBuildingParser extends BaseParser {
115
118
 
116
119
  // Guards, Prepare Commands and Lookahead Methods -----------------------------
117
120
 
118
- tableWithoutAs() { // not used in <guard=…>, only called by other guard
119
- // TODO TOOL: if the tool properly creates `default: this.giR()`, this
120
- // condition method is most likely not necessary
121
- const { keyword } = this.la();
122
- // TODO: if necessary, we could allow some keywords, and just make sure that
123
- // all JOIN variants are still possible
124
- return keyword && this.keywords[keyword] != null;
121
+ queryOnLeftSloppyAlias( _arg, mode ) {
122
+ if (mode === 'M' || this.isNoKeywordInRuleFollow( _arg, mode ))
123
+ return true;
124
+ // TODO TOOL: have a base parser method for the test
125
+ if (this.conditionTokenIdx === this.tokenIdx && // tested on same
126
+ this.conditionStackLength == null) // after error recovery
127
+ return false;
128
+ if (this.constructor.tracingParser)
129
+ this._traceSubPush( 'queryOnLeft' );
130
+ return this.queryOnLeft( 'table', mode );
125
131
  }
126
132
 
127
133
  /**
@@ -134,15 +140,9 @@ class AstBuildingParser extends BaseParser {
134
140
  * - <guard=queryOnLeft> tests whether the expression on the left is a query
135
141
  * - <guard=queryOnLeft, arg=‹SomeVal›>: tests whether the expression on the
136
142
  * left is a query, then make the current context to be not a query anymore
137
- * - <guard=queryOnLeft, arg=tableWithoutAs>: …after having checked
138
- * whether the next token is no (reserved or unreserved) keyword
139
143
  */
140
144
  queryOnLeft( arg, test ) {
141
- if (arg === 'tableWithoutAs') {
142
- if (this.tableWithoutAs())
143
- return true;
144
- }
145
- else if (!arg && !test) {
145
+ if (!arg && !test) {
146
146
  // provide new dynamic parentheses context, except with direct
147
147
  // recursive call:
148
148
  if (this.inSameRule_( this.s, this.stack.at( -1 ).followState ))
@@ -202,6 +202,10 @@ class AstBuildingParser extends BaseParser {
202
202
  }
203
203
 
204
204
  addTokenToSet_( set, tokenName, val, collectKeywordsOnly ) {
205
+ if (!this.dynamic_.call) {
206
+ super.addTokenToSet_( set, tokenName, val, collectKeywordsOnly );
207
+ return;
208
+ }
205
209
  const token = parserTokens[tokenName];
206
210
  // TODO: use lower-case in specialFunctions
207
211
  const realTokens = token && this.dynamic_.generic?.[token];
@@ -214,7 +218,7 @@ class AstBuildingParser extends BaseParser {
214
218
  // Works, since `DeleteStarFromSet` comes after `*` (length-sorted):
215
219
  delete set['*'];
216
220
  }
217
- else {
221
+ else if (!token) {
218
222
  super.addTokenToSet_( set, tokenName, val, collectKeywordsOnly );
219
223
  }
220
224
  }
@@ -488,10 +492,6 @@ class AstBuildingParser extends BaseParser {
488
492
  // token is not on a new line?
489
493
  }
490
494
 
491
- atRightParen() {
492
- return this.l() !== ')';
493
- }
494
-
495
495
  /**
496
496
  * For annotations at the beginning of columns outside parentheses
497
497
  */
@@ -570,7 +570,7 @@ class AstBuildingParser extends BaseParser {
570
570
  if (this.l() === ':') {
571
571
  this.warning( 'syntax-missing-parens', name,
572
572
  { code: '@‹anno›', op: ':', newcode: '@(‹anno›…)' },
573
- // eslint-disable-next-line @stylistic/js/max-len
573
+ // eslint-disable-next-line @stylistic/max-len
574
574
  'When $(CODE) is followed by $(OP), use $(NEWCODE) for annotation assignments at this position' );
575
575
  }
576
576
  }
@@ -582,7 +582,7 @@ class AstBuildingParser extends BaseParser {
582
582
  // do not report error if the '@' is not correct:
583
583
  this.s !== null && this.tokenIdx > this.recoverTokenIdx) {
584
584
  this.warning( 'syntax-missing-semicolon', next, { code: ';' },
585
- // eslint-disable-next-line @stylistic/js/max-len
585
+ // eslint-disable-next-line @stylistic/max-len
586
586
  'Add a $(CODE) and/or newline before the annotation assignment to indicate that it belongs to the next statement' );
587
587
  }
588
588
  }
@@ -737,7 +737,7 @@ class AstBuildingParser extends BaseParser {
737
737
  }
738
738
  else if (text.charAt(0) !== '!') {
739
739
  this.message( 'syntax-deprecated-ident', location, { delimited: id },
740
- // eslint-disable-next-line @stylistic/js/max-len
740
+ // eslint-disable-next-line @stylistic/max-len
741
741
  'Deprecated delimited identifier syntax, use $(DELIMITED) - strings are delimited by single quotes' );
742
742
  }
743
743
  }
@@ -948,7 +948,7 @@ class AstBuildingParser extends BaseParser {
948
948
  {
949
949
  std: 'Annotation number $(RAWVALUE) is put as $(VALUE) into the CSN',
950
950
  rounded: 'Annotation number $(RAWVALUE) is rounded to $(VALUE)',
951
- // eslint-disable-next-line @stylistic/js/max-len
951
+ // eslint-disable-next-line @stylistic/max-len
952
952
  infinite: 'Annotation value $(RAWVALUE) is infinite as number and put as string into the CSN',
953
953
  } );
954
954
  }
@@ -1000,7 +1000,7 @@ class AstBuildingParser extends BaseParser {
1000
1000
 
1001
1001
  // TODO: can we remove `;`/EOF from the expected-set for `annotate Foo with ⎀`?
1002
1002
  checkWith( keyword ) {
1003
- if (this.lb() !== keyword)
1003
+ if (this.lb() !== keyword || ![ ';', '}', 'EOF' ].includes( this.l() ))
1004
1004
  return;
1005
1005
  const tok = this.la();
1006
1006
  const docTokenIndex = this.docCommentIndex &&
@@ -1012,7 +1012,7 @@ class AstBuildingParser extends BaseParser {
1012
1012
  const expecting = this.expectingArray().filter( t => t !== '<EOF>' && t !== '\'}\'' );
1013
1013
  const msg = this.warning( 'syntax-unexpected-semicolon', tok,
1014
1014
  { offending: this.antlrName( tok ), expecting, keyword: 'with' },
1015
- // eslint-disable-next-line @stylistic/js/max-len
1015
+ // eslint-disable-next-line @stylistic/max-len
1016
1016
  'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous $(KEYWORD)' );
1017
1017
  msg.expectedTokens = expecting;
1018
1018
  }
@@ -1523,6 +1523,8 @@ class AstBuildingParser extends BaseParser {
1523
1523
  }
1524
1524
  }
1525
1525
 
1526
+ AstBuildingParser.prototype.queryOnLeftSloppyAlias.afterError = true;
1527
+
1526
1528
  function addOneForDefinition( count, ext ) {
1527
1529
  return (ext.kind === 'extend') ? count : count + 1;
1528
1530
  }
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const cdlKeywords = require('../gen/cdlKeywords.json').reserved;
4
+
3
5
  /** RegEx identifying undelimited identifiers in CDL */
4
6
  const undelimitedIdentifierRegex = /^[$_\p{ID_Start}][$\p{ID_Continue}\u200C\u200D]*$/u;
5
7
 
@@ -13,36 +15,6 @@ const functionsWithoutParentheses = [
13
15
  'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
14
16
  ];
15
17
 
16
- // CDL reserved keywords, used for automatic quoting in 'toCdl' renderer
17
- // TODO: Use `parser.keywords` from our generated CdlParser.js (#13856)
18
- const cdlKeywords = [
19
- 'ALL',
20
- 'ANY',
21
- 'AS',
22
- 'BY',
23
- 'CASE',
24
- 'CAST',
25
- 'DISTINCT',
26
- 'EXISTS',
27
- 'EXTRACT',
28
- 'FALSE', // boolean
29
- 'FROM',
30
- 'IN',
31
- 'KEY',
32
- 'NEW',
33
- 'NOT',
34
- 'NULL',
35
- 'OF',
36
- 'ON',
37
- 'SELECT',
38
- 'SOME',
39
- 'TRIM',
40
- 'TRUE', // boolean
41
- 'WHEN',
42
- 'WHERE',
43
- 'WITH',
44
- ];
45
-
46
18
  function isSimpleCdlIdentifier( id ) {
47
19
  if (undelimitedIdentifierRegex.test(id))
48
20
  return true;
@@ -796,10 +796,15 @@ class CsnToCdl {
796
796
  }
797
797
 
798
798
  // Top-level annotations of the artifact
799
- let result = this.renderAnnotationAssignmentsAndDocComment(ext, env);
799
+ const topLevelAnnotations = this.renderAnnotationAssignmentsAndDocComment(ext, env.withIncreasedIndent());
800
+
800
801
  // Note: Not renderDefinitionReference, because we don't care if there
801
802
  // are annotations to unknown things. That's allowed!
802
- result += `${ env.indent }annotate ${ this.renderArtifactName(ext.annotate, env) }`;
803
+ let result = `${ env.indent }annotate ${ this.renderArtifactName(ext.annotate, env) }`;
804
+
805
+ if (topLevelAnnotations)
806
+ result += ` with\n${ topLevelAnnotations }`;
807
+
803
808
 
804
809
  if (ext.params)
805
810
  result += this.renderAnnotateParamsInParentheses(ext, env);
@@ -1368,7 +1373,7 @@ class CsnToCdl {
1368
1373
  }
1369
1374
 
1370
1375
  /**
1371
- * Render the given columns.
1376
+ * Render the given columns / select items.
1372
1377
  *
1373
1378
  * @param {CSN.Extension | CSN.QuerySelect} art
1374
1379
  * @param {object} elements
@@ -1439,7 +1444,7 @@ class CsnToCdl {
1439
1444
  if (col.cast.target && !col.cast.type)
1440
1445
  result += ` : ${ this.renderRedirectedTo(col.cast, env) }`;
1441
1446
  else
1442
- result += ` : ${ this.renderTypeReferenceAndProps(col.cast, env, { typeRefOnly: true, noAnnoCollect: true }) }`;
1447
+ result += ` : ${ this.renderTypeReferenceAndProps(col.cast, env, { noAnnoCollect: true }) }`;
1443
1448
  env.path.length -= 1;
1444
1449
  }
1445
1450
  return result;
@@ -1459,6 +1464,7 @@ class CsnToCdl {
1459
1464
 
1460
1465
  const isAnonymousExpand = (obj.expand && !obj.ref);
1461
1466
 
1467
+
1462
1468
  // s as alias { * }
1463
1469
  if (obj.as && !isAnonymousExpand)
1464
1470
  result += this.renderAlias(obj.as, env);
@@ -1483,7 +1489,7 @@ class CsnToCdl {
1483
1489
  const childEnv = env.withIncreasedIndent();
1484
1490
  const expandInline = obj.expand || obj.inline;
1485
1491
  result += expandInline //
1486
- .map(elm => this.renderAnnotationAssignmentsAndDocComment(elm, childEnv) + childEnv.indent + this.renderInlineExpand(elm, childEnv))
1492
+ .map(elm => this.renderViewColumn(elm, childEnv))
1487
1493
  .join(',\n');
1488
1494
  result += `\n${ env.indent }}`;
1489
1495
 
@@ -1869,9 +1875,12 @@ class CsnToCdl {
1869
1875
  if (artifact.on)
1870
1876
  result += ` on ${ this.exprRenderer.renderExpr(artifact.on, env.withSubPath([ 'on' ])) }`;
1871
1877
 
1872
- // Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
1873
- if (artifact.keys && !artifact.on)
1874
- result += ` ${ this.renderForeignKeys(artifact, env) }`;
1878
+ // Foreign keys (if any, unless we also have an ON-condition (which means we have been transformed from managed to unmanaged)
1879
+ if (artifact.keys && !artifact.on) {
1880
+ const keys = this.renderForeignKeys(artifact, env, false);
1881
+ if (keys)
1882
+ result += ` ${ keys }`;
1883
+ }
1875
1884
 
1876
1885
  if (!artifact.on) {
1877
1886
  // unmanaged associations can't be followed by "not null" or "default"
@@ -1923,10 +1932,14 @@ class CsnToCdl {
1923
1932
  */
1924
1933
  renderRedirectedTo( art, env ) {
1925
1934
  let result = `redirected to ${ this.renderDefinitionReference(art.target, env) }`;
1926
- if (art.on)
1935
+ if (art.on) {
1927
1936
  result += ` on ${ this.exprRenderer.renderExpr(art.on, env.withSubPath([ 'on' ])) }`;
1928
- else if (art.keys)
1929
- result += ` ${ this.renderForeignKeys(art, env) }`;
1937
+ }
1938
+ else if (art.keys) {
1939
+ const keys = this.renderForeignKeys(art, env, true);
1940
+ if (keys)
1941
+ result += ` ${ keys }`;
1942
+ }
1930
1943
  return result;
1931
1944
  }
1932
1945
 
@@ -2274,13 +2287,16 @@ class CsnToCdl {
2274
2287
  }
2275
2288
 
2276
2289
  /**
2277
- * Render foreign keys.
2290
+ * Render foreign keys. We only render foreign keys if necessary or if we can't say for sure
2291
+ * that the foreign keys would match the implicitly generated ones.
2278
2292
  *
2279
2293
  * @param {object} art
2280
2294
  * @param {CdlRenderEnvironment} env
2295
+ * @param {boolean} alwaysRenderForeignKeys
2296
+ * If false, only render foreign keys if necessary (i.e. can't be inferred by compiler again).
2281
2297
  * @return {string}
2282
2298
  */
2283
- renderForeignKeys( art, env ) {
2299
+ renderForeignKeys( art, env, alwaysRenderForeignKeys = false ) {
2284
2300
  const renderedKeys = [];
2285
2301
  let hasAnnotations = false;
2286
2302
  env = env.withSubPath([ 'keys', -1 ]);
@@ -2301,18 +2317,62 @@ class CsnToCdl {
2301
2317
  renderedKeys.push(`${ key }${ alias },`);
2302
2318
  }
2303
2319
 
2320
+ // Annotations on foreign keys always require rendering of explicit keys.
2321
+ // Otherwise, we'd have to use annotate statements here.
2304
2322
  if (hasAnnotations) {
2305
2323
  const sep = `\n${ env.indent }`;
2306
2324
  env.decreaseIndent();
2307
2325
  return `{${ sep }${ renderedKeys.join(sep) }\n${ env.indent }}`;
2308
2326
  }
2309
2327
 
2328
+ if (!alwaysRenderForeignKeys && this.foreignKeysMatchImplicitOnes( art ))
2329
+ return '';
2330
+
2310
2331
  let result = renderedKeys.join(' ');
2311
2332
  if (result[result.length - 1] === ',') // remove trailing comma
2312
2333
  result = result.slice(0, -1);
2313
2334
  return `{ ${ result } }`;
2314
2335
  }
2315
2336
 
2337
+ /**
2338
+ * Returns true, if `to.cdl()` could leave out explicit foreign keys in the rendered output
2339
+ * without changing recompilation. We do so, because explicit foreign keys are not promoted
2340
+ * on CAPire.
2341
+ *
2342
+ * @param {CSN.Artifact} art
2343
+ * @returns {boolean}
2344
+ */
2345
+ foreignKeysMatchImplicitOnes( art ) {
2346
+ if (art.cardinality && art.cardinality.max !== 1)
2347
+ return false; // e.g. to-many assocs
2348
+
2349
+ const target = this.csn.definitions[art.target];
2350
+ if (!art.keys?.length || !art.target || !target?.elements)
2351
+ return false;
2352
+
2353
+ // We could improve to.cdl() to properly check if keys match, but then we'd have to look
2354
+ // at sub-elements, structures, etc.; for now, we only check “simple” foreign keys that
2355
+ // don't have any alias.
2356
+ const hasComplexKeys = art.keys.some(key => key.ref?.length !== 1 || key.as !== undefined);
2357
+ if (hasComplexKeys)
2358
+ return false;
2359
+
2360
+ const targetKeys = Object.keys(target.elements).filter(name => target.elements[name]?.key);
2361
+ if (targetKeys.length === 0)
2362
+ return false; // always require explicit keys if there are no target side keys
2363
+
2364
+ const foreignKeys = art.keys.map(key => key.ref[0]);
2365
+ if (foreignKeys.length !== targetKeys.length)
2366
+ return false;
2367
+
2368
+ // We require the _same_ order! Otherwise, recompilation would change the generated foreign key order!
2369
+ for (let i = 0; i < foreignKeys.length; ++i) {
2370
+ if (foreignKeys[i] !== targetKeys[i])
2371
+ return false;
2372
+ }
2373
+ return true;
2374
+ }
2375
+
2316
2376
  /**
2317
2377
  * Render an explicit alias, e.g. for columns.
2318
2378
  *