@sap/cds-compiler 2.11.2 → 2.13.6

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 (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- getTopLevelArtifactNameOf, getLastPartOf,
4
+ getLastPartOf,
5
5
  getLastPartOfRef,
6
6
  } = require('../model/csnUtils');
7
7
  const {
@@ -9,12 +9,14 @@ const {
9
9
  } = require('../model/csnUtils');
10
10
  const keywords = require('../base/keywords');
11
11
  const { renderFunc, beautifyExprArray, findElement } = require('./utils/common');
12
+ const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
12
13
  const { checkCSNVersion } = require('../json/csnVersion');
13
14
  const { timetrace } = require('../utils/timetrace');
14
15
  const { csnRefs } = require('../model/csnRefs');
15
16
  const { forEachDefinition } = require('../model/csnUtils');
16
- const enrichUniversalCsn = require('../transform/universalCsnEnricher');
17
+ const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
17
18
  const { isBetaEnabled } = require('../base/model');
19
+ const { ModelError } = require('../base/error');
18
20
 
19
21
  /**
20
22
  * Render the CSN model 'model' to CDS source text. One source is created per
@@ -40,19 +42,19 @@ function toCdsSourceCsn(csn, options) {
40
42
 
41
43
  checkCSNVersion(csn, options);
42
44
 
43
- const result = Object.create(null);
45
+ const cdlResult = Object.create(null);
44
46
 
45
47
  const main = 'model';
46
48
 
47
- result[main] = `${options.testMode ? '' : `// ${generatedByCompilerVersion()} \n`}`;
49
+ cdlResult[main] = options.testMode ? '' : `// ${generatedByCompilerVersion()} \n`;
48
50
 
49
51
  const subelementAnnotates = [];
50
52
 
51
53
  forEachDefinition(csn, (artifact, artifactName) => {
52
54
  const env = createEnv();
53
- const sourceStr = renderArtifact(artifactName, artifact, env); // Must come first because it populates 'env.topLevelAliases'
55
+ const sourceStr = renderArtifact(artifactName, artifact, env);
54
56
  if (sourceStr !== '')
55
- result[main] += `${sourceStr}\n`;
57
+ cdlResult[main] += `${sourceStr}\n`;
56
58
  });
57
59
 
58
60
  // Apply possible subelement/action return annotations with an "annotate X with"
@@ -70,7 +72,7 @@ function toCdsSourceCsn(csn, options) {
70
72
  if (elementName) // action returns do not have element name - we need less {} there
71
73
  sourceStr += ' }\n';
72
74
  sourceStr += '}\n';
73
- result[main] += `${sourceStr}\n`;
75
+ cdlResult[main] += `${sourceStr}\n`;
74
76
  }
75
77
  }
76
78
  }
@@ -117,13 +119,13 @@ function toCdsSourceCsn(csn, options) {
117
119
  sourceStr = renderTypeOrAnnotation(annotationName, anno, env, 'annotation');
118
120
 
119
121
  if (sourceStr !== '')
120
- result[main] += `${sourceStr}\n`;
122
+ cdlResult[main] += `${sourceStr}\n`;
121
123
  }
122
124
  }
123
125
 
124
126
  if (csn.namespace) {
125
- result[csn.namespace] = `namespace ${renderArtifactName(csn.namespace)};\n`;
126
- result[csn.namespace] += `using from './${main}.cds';`;
127
+ cdlResult[csn.namespace] = `namespace ${renderArtifactName(csn.namespace)};\n`;
128
+ cdlResult[csn.namespace] += `using from './${main}.cds';`;
127
129
  }
128
130
 
129
131
 
@@ -132,11 +134,11 @@ function toCdsSourceCsn(csn, options) {
132
134
  if (csn.extensions) {
133
135
  const env = createEnv();
134
136
  const sourceStr = renderUnappliedExtensions(csn.extensions, env);
135
- result.unappliedExtensions = renderUsings('', env) + sourceStr;
137
+ cdlResult.unappliedExtensions = sourceStr;
136
138
  }
137
139
 
138
140
  timetrace.stop();
139
- return result;
141
+ return cdlResult;
140
142
 
141
143
  /**
142
144
  * Render unapplied 'extend' and 'annotate' statements from the 'extensions array'
@@ -233,7 +235,6 @@ function toCdsSourceCsn(csn, options) {
233
235
 
234
236
  switch (art.kind) {
235
237
  case 'entity':
236
- case 'view':
237
238
  if (art.query || art.projection)
238
239
  return renderView(artifactName, art, env);
239
240
 
@@ -242,8 +243,6 @@ function toCdsSourceCsn(csn, options) {
242
243
  case 'context':
243
244
  case 'service':
244
245
  return renderContext(artifactName, art, env);
245
- case 'namespace':
246
- return renderNamespace(artifactName, art, env);
247
246
  case 'type':
248
247
  case 'aspect':
249
248
  case 'annotation':
@@ -254,7 +253,7 @@ function toCdsSourceCsn(csn, options) {
254
253
  case 'event':
255
254
  return renderEventIfCDLMode(artifactName, art, env);
256
255
  default:
257
- throw new Error(`Unknown artifact kind: ${art.kind}`);
256
+ throw new ModelError(`Unknown artifact kind: ${art.kind}`);
258
257
  }
259
258
  }
260
259
 
@@ -294,37 +293,6 @@ function toCdsSourceCsn(csn, options) {
294
293
  return result;
295
294
  }
296
295
 
297
- /**
298
- * Return a dictionary with the direct sub-artifacts of the artifact with name 'artifactName' in the csn
299
- *
300
- * @param {string} artifactName
301
- * @return {object}
302
- */
303
- function getSubArtifacts(artifactName) {
304
- const prefix = `${artifactName}.`;
305
- const result = Object.create(null);
306
- for (const name in csn.definitions) {
307
- // We have a direct child if its name starts with prefix and contains no more dots
308
- if (name.startsWith(prefix) && !name.substring(prefix.length).includes('.')) {
309
- result[getLastPartOf(name)] = csn.definitions[name];
310
- }
311
- else if (name.startsWith(prefix)) {
312
- const prefixPlusNextPart = name.substring(0, name.substring(prefix.length).indexOf('.') + prefix.length);
313
- if (csn.definitions[prefixPlusNextPart]) {
314
- const art = csn.definitions[prefixPlusNextPart];
315
- if (![ 'service', 'context', 'namespace' ].includes(art.kind)) {
316
- const nameWithoutPrefix = name.substring(prefix.length);
317
- result[nameWithoutPrefix] = csn.definitions[name];
318
- }
319
- }
320
- else {
321
- result[name.substring(prefix.length)] = csn.definitions[name];
322
- }
323
- }
324
- }
325
- return result;
326
- }
327
-
328
296
  /* FIXME: Not yet required
329
297
  // Returns the artifact or element that constitutes the final type of
330
298
  // construct 'node', i.e. the object in which we would find type properties for
@@ -385,37 +353,6 @@ function toCdsSourceCsn(csn, options) {
385
353
  return `${result} {};\n`;
386
354
  }
387
355
 
388
- function updatePrefixForDottedName(env, name) {
389
- let innerEnv = env;
390
- if (name.indexOf('.') !== -1) {
391
- const parts = name.split('.');
392
- for (let i = 0; i < parts.length - 1; i++)
393
- innerEnv = addNamePrefix(innerEnv, parts[i]);
394
- }
395
-
396
- return innerEnv;
397
- }
398
-
399
- /**
400
- * Render a namespace. Return the resulting source string.
401
- *
402
- * @param {string} artifactName
403
- * @param {CSN.Artifact} art
404
- * @param {CdlRenderEnvironment} env
405
- * @return {string}
406
- */
407
- function renderNamespace(artifactName, art, env) {
408
- // We currently do not render anything for a namespace, we just append its id to
409
- // the environment's current name prefix and descend into its children
410
- let result = renderDocComment(art, env);
411
- const childEnv = addNamePrefix(env, getLastPartOf(artifactName));
412
- const subArtifacts = getSubArtifacts(artifactName);
413
- for (const name in subArtifacts)
414
- result += renderArtifact(`${artifactName}.${name}`, subArtifacts[name], updatePrefixForDottedName(childEnv, name));
415
-
416
- return result;
417
- }
418
-
419
356
  /**
420
357
  * Render a (non-projection, non-view) entity. Return the resulting source string.
421
358
  *
@@ -446,84 +383,10 @@ function toCdsSourceCsn(csn, options) {
446
383
  }
447
384
 
448
385
  result += `${env.indent}}`;
449
- result += `${renderActionsAndFunctions(art, env) + renderTechnicalConfiguration(art.technicalConfig, env)};\n`;
386
+ result += `${renderActionsAndFunctions(art, env)};\n`;
450
387
  return result;
451
388
  }
452
389
 
453
- // Render the 'technical configuration { ... }' section 'tc' of an entity.
454
- // Return the resulting source string.
455
- function renderTechnicalConfiguration(tc, env) {
456
- let result = renderDocComment(tc, env);
457
- const childEnv = increaseIndent(env);
458
-
459
- if (!tc)
460
- return result;
461
-
462
-
463
- // FIXME: How to deal with non-HANA technical configurations? We should probably just iterate all entries
464
- // in 'tc' that we find and render them all (is it syntactically allowed yet to have more than one?)
465
- tc = tc.hana;
466
- if (!tc)
467
- throw new Error('Expecting a HANA technical configuration');
468
-
469
- result += `\n${env.indent}technical ${tc.calculated ? '' : 'hana '}configuration {\n`;
470
-
471
- // Store type (must be separate because SQL wants it between 'CREATE' and 'TABLE')
472
- if (tc.storeType)
473
- result += `${tc.storeType} store;\n`;
474
-
475
- // Fixed parts belonging to the table (includes migration, unload prio, extended storage,
476
- // auto merge, partitioning, ...)
477
- if (tc.tableSuffix) {
478
- // Unlike SQL, CDL and HANA CDS require a semicolon after each table-suffix part
479
- // (e.g. `migration enabled; row store; ...`). In order to keep both
480
- // the simplicity of "the whole bandwurm is just one expression that can be
481
- // rendered to SQL without further knowledge" and at the same time telling
482
- // CDS about the boundaries, the compactor has put each part into its own `xpr`
483
- // object. Semantically equivalent because a "trivial" SQL renderer would just
484
- // concatenate them.
485
- for (const xpr of tc.tableSuffix)
486
- result += `${childEnv.indent + renderExpr(xpr, childEnv)};\n`;
487
- }
488
-
489
- // Indices and full-text indices
490
- for (const idxName in tc.indexes || {}) {
491
- if (Array.isArray(tc.indexes[idxName][0])) {
492
- // FIXME: Should we allow multiple indices with the same name at all?
493
- for (const index of tc.indexes[idxName])
494
- result += `${childEnv.indent + renderExpr(index, childEnv)};\n`;
495
- }
496
- else {
497
- result += `${childEnv.indent + renderExpr(tc.indexes[idxName], childEnv)};\n`;
498
- }
499
- }
500
- // Fuzzy search indices
501
- for (const columnName in tc.fzindexes || {}) {
502
- if (Array.isArray(tc.fzindexes[columnName][0])) {
503
- // FIXME: Should we allow multiple fuzzy search indices on the same column at all?
504
- // And if not, why do we wrap this into an array?
505
- for (const index of tc.fzindexes[columnName])
506
- result += `${childEnv.indent + renderExpr(fixFuzzyIndex(index, columnName), childEnv)};\n`;
507
- }
508
- else {
509
- result += `${childEnv.indent + renderExpr(fixFuzzyIndex(tc.fzindexes[columnName], columnName), childEnv)};\n`;
510
- }
511
- }
512
- result += `${env.indent}}`;
513
- return result;
514
-
515
- // Fuzzy indices are stored in compact CSN as they would appear in SQL after the column name,
516
- // i.e. the whole line in SQL looks somewhat like this:
517
- // s nvarchar(10) FUZZY SEARCH INDEX ON FUZZY SEARCH MODE 'ALPHANUM'
518
- // But in CDL, we don't write fuzzy search indices together with the table column, so we need
519
- // to insert the name of the column after 'ON' in CDS syntax, making it look like this:
520
- // fuzzy search mode on (s) search mode 'ALPHANUM'
521
- // This function expects an array with the original expression and returns an array with the modified expression
522
- function fixFuzzyIndex(fuzzyIndex, columnName) {
523
- return fuzzyIndex.map(token => (token === 'on' ? { xpr: [ 'on', '(', { ref: columnName.split('.') }, ')' ] } : token));
524
- }
525
- }
526
-
527
390
  /**
528
391
  * Render an element (of an entity, type or annotation, not a projection or view).
529
392
  * Return the resulting source string.
@@ -561,7 +424,7 @@ function toCdsSourceCsn(csn, options) {
561
424
 
562
425
  // Sanity checks
563
426
  if (!query.SET || !query.SET.args || !query.SET.args[0])
564
- throw new Error(`Expecting set with args in query: ${JSON.stringify(query)}`);
427
+ throw new ModelError(`Expecting set with args in query: ${JSON.stringify(query)}`);
565
428
 
566
429
  return leadingQuerySelect(query.SET.args[0]);
567
430
  }
@@ -620,14 +483,14 @@ function toCdsSourceCsn(csn, options) {
620
483
  if (!name.startsWith('@'))
621
484
  continue;
622
485
 
623
- const annotationValue = renderAnnotationValue(elem[name], childEnv);
486
+ const annotationValue = renderAnnotationAssignment(elem[name], name, childEnv);
624
487
  // Skip annotation if column has the same
625
488
  if (columnMap[elemName] && columnMap[elemName][name] &&
626
- renderAnnotationValue(columnMap[elemName][name], childEnv) === annotationValue)
489
+ renderAnnotationAssignment(columnMap[elemName][name], name, childEnv) === annotationValue)
627
490
  continue;
628
491
 
629
492
  // Annotation names are never flattened
630
- elemAnnotations += `${childEnv.indent}${`@${renderAbsoluteNameWithQuotes(name.substring(1))}`} : ${annotationValue}\n`;
493
+ elemAnnotations += annotationValue;
631
494
  }
632
495
  if (elemAnnotations !== '')
633
496
  result += `${elemAnnotations}${childEnv.indent}${quoteOrUppercaseId(elemName)};\n`;
@@ -703,7 +566,7 @@ function toCdsSourceCsn(csn, options) {
703
566
  function renderAbsolutePath(path, env) {
704
567
  // Sanity checks
705
568
  if (!path.ref)
706
- throw new Error(`Expecting ref in path: ${JSON.stringify(path)}`);
569
+ throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
707
570
 
708
571
 
709
572
  // Determine the absolute name of the first artifact on the path (before any associations or element traversals)
@@ -909,6 +772,7 @@ function toCdsSourceCsn(csn, options) {
909
772
  * @param {string} syntax The query syntax, either "projection", "entity" or "view"
910
773
  * @param {CdlRenderEnvironment} env
911
774
  * @param {CSN.Path} [path=[]]
775
+ * @param {object} [elements]
912
776
  */
913
777
  function renderQuery(query, isLeadingQuery, syntax, env, path = [], elements = query.elements || Object.create(null)) {
914
778
  let result = renderDocComment(query, env);
@@ -935,7 +799,7 @@ function toCdsSourceCsn(csn, options) {
935
799
  }
936
800
  // Otherwise must have a SELECT
937
801
  else if (!query.SELECT) {
938
- throw new Error(`Unexpected query operation ${JSON.stringify(query)}`);
802
+ throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
939
803
  }
940
804
  const select = query.SELECT;
941
805
  const childEnv = increaseIndent(env);
@@ -945,7 +809,7 @@ function toCdsSourceCsn(csn, options) {
945
809
  else if (syntax === 'view' || syntax === 'entity')
946
810
  result += `select from ${renderViewSource(select.from, env)}`;
947
811
  else
948
- throw new Error(`Unknown query syntax: ${syntax}`);
812
+ throw new ModelError(`Unknown query syntax: ${syntax}`);
949
813
 
950
814
  if (select.mixin) {
951
815
  let elems = '';
@@ -980,7 +844,7 @@ function toCdsSourceCsn(csn, options) {
980
844
  result += `${continueIndent(result, env)}where ${renderExpr(select.where, env, true, true)}`;
981
845
 
982
846
  if (select.groupBy)
983
- result += `${continueIndent(result, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
847
+ result += `${continueIndent(result, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
984
848
 
985
849
  if (select.having)
986
850
  result += `${continueIndent(result, env)}having ${renderExpr(select.having, env, true, true)}`;
@@ -996,35 +860,35 @@ function toCdsSourceCsn(csn, options) {
996
860
  /**
997
861
  * Utility function to make sure that we continue with the same indentation in WHERE, GROUP BY, ... after a closing curly brace and beyond
998
862
  *
999
- * @param {string} result
1000
- * @param {CdlRenderEnvironment} env
863
+ * @param {string} str
864
+ * @param {CdlRenderEnvironment} indentEnv
1001
865
  * @return {string}
1002
866
  */
1003
- function continueIndent(result, env) {
1004
- if (result.endsWith('}') || result.endsWith('})')) {
867
+ function continueIndent(str, indentEnv) {
868
+ if (str.endsWith('}') || str.endsWith('})')) {
1005
869
  // The preceding clause ended with '}', just append after that
1006
870
  return ' ';
1007
871
  }
1008
872
  // Otherwise, start new line and indent normally
1009
- return `\n${increaseIndent(env).indent}`;
873
+ return `\n${increaseIndent(indentEnv).indent}`;
1010
874
  }
1011
875
 
1012
876
  /**
1013
877
  * Render a query's LIMIT clause, which may have also have OFFSET.
1014
878
  *
1015
879
  * @param {CSN.QueryLimit} limit
1016
- * @param {CdlRenderEnvironment} env
880
+ * @param {CdlRenderEnvironment} limitEnv
1017
881
  * @return {string}
1018
882
  */
1019
- function renderLimit(limit, env) {
1020
- let result = '';
883
+ function renderLimit(limit, limitEnv) {
884
+ let limitStr = '';
1021
885
  if (limit.rows !== undefined)
1022
- result += `limit ${renderExpr(limit.rows, env)}`;
886
+ limitStr += `limit ${renderExpr(limit.rows, limitEnv)}`;
1023
887
 
1024
888
  if (limit.offset !== undefined)
1025
- result += `${result !== '' ? `\n${increaseIndent(env).indent}` : ''}offset ${renderExpr(limit.offset, env)}`;
889
+ limitStr += `${limitStr !== '' ? `\n${increaseIndent(limitEnv).indent}` : ''}offset ${renderExpr(limit.offset, limitEnv)}`;
1026
890
 
1027
- return result;
891
+ return limitStr;
1028
892
  }
1029
893
  }
1030
894
 
@@ -1037,7 +901,7 @@ function toCdsSourceCsn(csn, options) {
1037
901
  * @return {string}
1038
902
  */
1039
903
  function renderOrderByEntry(entry, env) {
1040
- let result = renderDocComment(entry, env) + renderExpr(entry, env);
904
+ let result = renderDocComment(entry, env) + renderExpr(entry, env, true, false, true);
1041
905
  if (entry.sort)
1042
906
  result += ` ${entry.sort}`;
1043
907
 
@@ -1140,7 +1004,10 @@ function toCdsSourceCsn(csn, options) {
1140
1004
  }
1141
1005
  else {
1142
1006
  // Derived type or annotation with non-anonymous type
1143
- result += ` : ${renderTypeReference(art, env, false)};\n`;
1007
+ result += ` : ${renderTypeReference(art, env, false)}`;
1008
+ if (art.default)
1009
+ result += ` default ${renderExpr(art.default, env)}`;
1010
+ result += ';\n';
1144
1011
  }
1145
1012
  return result;
1146
1013
  }
@@ -1176,7 +1043,7 @@ function toCdsSourceCsn(csn, options) {
1176
1043
  // Anonymous structured type
1177
1044
  if (!elm.type) {
1178
1045
  if (!elm.elements)
1179
- throw new Error(`Missing type of: ${JSON.stringify(elm)}`);
1046
+ throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
1180
1047
 
1181
1048
  result += '{\n';
1182
1049
  const childEnv = increaseIndent(env);
@@ -1214,7 +1081,7 @@ function toCdsSourceCsn(csn, options) {
1214
1081
  result += `${env.indent}}`;
1215
1082
  }
1216
1083
  else {
1217
- throw new Error('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
1084
+ throw new ModelError('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
1218
1085
  }
1219
1086
 
1220
1087
 
@@ -1335,7 +1202,7 @@ function toCdsSourceCsn(csn, options) {
1335
1202
  // Primitive: string, number, boolean
1336
1203
 
1337
1204
  // Quote strings, leave all others as they are
1338
- return (typeof x === 'string') ? `'${x.replace(/'/g, '\'\'')}'` : x;
1205
+ return (typeof x === 'string') ? renderString(x, env) : x;
1339
1206
  }
1340
1207
 
1341
1208
  /**
@@ -1346,14 +1213,16 @@ function toCdsSourceCsn(csn, options) {
1346
1213
  * @param {CdlRenderEnvironment} env
1347
1214
  * @param {boolean} [inline=true]
1348
1215
  * @param {boolean} [inExpr=false] Whether the expression is already inside another expression
1216
+ * @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `inExpr === false`
1217
+ * Note: This is a hack for casts() inside groupBy.
1349
1218
  */
1350
- function renderExpr(x, env, inline = true, inExpr = false) {
1219
+ function renderExpr(x, env, inline = true, inExpr = false, alwaysRenderCast = false) {
1351
1220
  // Compound expression
1352
1221
  if (Array.isArray(x))
1353
1222
  return beautifyExprArray(x.map(item => renderExpr(item, env, inline, inExpr)));
1354
1223
 
1355
1224
  if (typeof x === 'object' && x !== null) {
1356
- if (inExpr && x.cast && x.cast.type)
1225
+ if ((inExpr || alwaysRenderCast) && x.cast && x.cast.type)
1357
1226
  return renderExplicitTypeCast(renderExprObject());
1358
1227
  return renderExprObject();
1359
1228
  }
@@ -1385,14 +1254,14 @@ function toCdsSourceCsn(csn, options) {
1385
1254
  case 'timestamp':
1386
1255
  return `${x.literal}'${x.val}'`;
1387
1256
  case 'string':
1388
- return `'${x.val.replace(/'/g, '\'\'')}'`;
1257
+ return renderString(x.val, env);
1389
1258
  case 'object':
1390
1259
  if (x.val === null)
1391
1260
  return 'null';
1392
1261
 
1393
1262
  // otherwise fall through to
1394
1263
  default:
1395
- throw new Error(`Unknown literal or type: ${JSON.stringify(x)}`);
1264
+ throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
1396
1265
  }
1397
1266
  }
1398
1267
  // Enum symbol
@@ -1439,7 +1308,7 @@ function toCdsSourceCsn(csn, options) {
1439
1308
  return `${renderQuery(x, false, 'view', increaseIndent(env))}`;
1440
1309
  }
1441
1310
  else {
1442
- throw new Error(`Unknown expression: ${JSON.stringify(x)}`);
1311
+ throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
1443
1312
  }
1444
1313
  }
1445
1314
 
@@ -1476,7 +1345,7 @@ function toCdsSourceCsn(csn, options) {
1476
1345
  else if (typeof s === 'object') {
1477
1346
  // Sanity check
1478
1347
  if (!s.func && !s.id)
1479
- throw new Error(`Unknown path step object: ${JSON.stringify(s)}`);
1348
+ throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
1480
1349
 
1481
1350
  // Not really a path step but an object-like function call
1482
1351
  if (s.func)
@@ -1496,7 +1365,7 @@ function toCdsSourceCsn(csn, options) {
1496
1365
  return result;
1497
1366
  }
1498
1367
 
1499
- throw new Error(`Unknown path step: ${JSON.stringify(s)}`);
1368
+ throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
1500
1369
  }
1501
1370
  }
1502
1371
 
@@ -1513,14 +1382,14 @@ function toCdsSourceCsn(csn, options) {
1513
1382
  const args = node.args ? node.args : {};
1514
1383
  // Positional arguments
1515
1384
  if (Array.isArray(args))
1516
- return args.map(arg => renderExpr(arg, env)).join(', ');
1385
+ return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
1517
1386
 
1518
1387
  // Named arguments (object/dict)
1519
1388
  else if (typeof args === 'object')
1520
- return Object.keys(args).map(key => `${quoteOrUppercaseId(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1389
+ return Object.keys(args).map(key => `${quoteOrUppercaseId(key)} ${sep} ${renderExpr(args[key], env, true, false, true)}`).join(', ');
1521
1390
 
1522
1391
 
1523
- throw new Error(`Unknown args: ${JSON.stringify(args)}`);
1392
+ throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
1524
1393
  }
1525
1394
 
1526
1395
  /**
@@ -1642,66 +1511,38 @@ function toCdsSourceCsn(csn, options) {
1642
1511
  }
1643
1512
 
1644
1513
  /**
1645
- * Render a single annotation assignment 'ann' with fully qualified name 'name' (no trailing LF).
1646
- * We might see variants like 'A.B.C#foo' or even 'A.B#foo.C'. In both cases, the #foo must be ignored
1647
- * when resolving the name, but must stay in the rendered output (quoted as necessary).
1514
+ * Render a single annotation assignment 'anno' with fully qualified name 'name' (no trailing LF).
1515
+ * We might see variants like 'A.B.C#foo' or even 'A.B#foo.C'. The latter needs to be quoted as
1516
+ * dots in the variant are not recognized by the compiler.
1648
1517
  *
1649
- * @param {any} ann Annotation value
1518
+ * @param {any} anno Annotation value
1650
1519
  * @param {string} name Annotation name, e.g. `@A.B.C#foo.C`
1651
1520
  * @param {CdlRenderEnvironment} env
1652
- * @return {string}
1521
+ * @return {string} Rendered annotation, possibly quoted: `@![A.B.C#foo.C]: value`
1653
1522
  */
1654
- function renderAnnotationAssignment(ann, name, env) {
1655
- // Take the annotation assignment apart into <nameBeforeVariant>[#<variant>[.<nameAfterVariant>]]
1656
- // @1111111 3333333 5555
1657
- const parts = name.match(/@([^#]+)(#([^.]+)(\.(.+))?)?/);
1658
- const nameBeforeVariant = parts[1];
1659
- const variant = parts[3];
1660
- const nameAfterVariant = parts[5];
1661
- const topLevelName = getTopLevelArtifactNameOf(nameBeforeVariant, csn);
1662
- let result = `${env.indent}@`;
1663
- if (topLevelName) {
1664
- // Checked annotation, with declaration - must render top-level absolute name with proper using and alias
1665
- // Annotation names are never flattened
1666
- result += renderAbsoluteNameWithQuotes(topLevelName);
1667
- if (topLevelName !== nameBeforeVariant)
1668
- result += `.${quotePathString(nameBeforeVariant.substring(topLevelName.length + 1))}`;
1669
- }
1670
- else {
1671
- // Unchecked annotation, just render the name as it is
1672
- result += nameBeforeVariant;
1673
- }
1674
- // Append '#'-variant if any
1675
- if (variant) {
1676
- // FIXME: Unfortunately, the compiler does not yet understand an inner variant with proper quoting,
1677
- // i.e. like "__A"."B"#"foo"."C". As a workaround, we present the '#'-variant as a quoted part of the
1678
- // previous path step, i.e as "__A"."B#foo"."C" (which yields the same result). This hack is only necessary
1679
- // for inner '#'-variants, i.e. for those followed by a <nameAfterVariant>.
1680
- // FIXME: Won't work for inner variants on the top-level artifact, because the USING no longer matches
1681
- // for something like "__A#foo"."B"."C"
1682
- if (nameAfterVariant) {
1683
- const resultSteps = result.split('.');
1684
- // Take all paths steps from the result (which is now essentially 'nameBeforeVariant' with USING
1685
- // adaptations) except the last step
1686
- result = resultSteps.slice(0, -1).join('.');
1687
- // Append a combination of last path step and '#'-variant (quoted)
1688
- let lastStep = resultSteps[resultSteps.length - 1];
1689
- if (lastStep.includes('"')) {
1690
- // Last step was already quoted - strip off the existing quotes
1691
- lastStep = lastStep.slice(1, -1);
1692
- }
1693
- result += `.${quoteIdIfRequired(`${lastStep}#${variant}`)}`;
1694
- }
1695
- else {
1696
- // No hack required for trailing '#'-variant
1697
- result += `#${quoteIdIfRequired(variant)}`;
1698
- }
1699
- }
1700
- // Append anything that might have come after the variant
1701
- if (nameAfterVariant)
1702
- result += `.${quotePathString(nameAfterVariant)}`;
1523
+ function renderAnnotationAssignment(anno, name, env) {
1524
+ name = name.substring(1);
1525
+ // Take the annotation assignment apart into <nameBeforeVariant>#<variantAndRest>
1526
+ const parts = name.split('#');
1527
+ const nameBeforeVariant = parts[0];
1528
+ const variant = parts[1];
1529
+
1530
+ // Identifier according to our grammar: /[$_a-zA-Z][$_a-zA-Z0-9]*/
1531
+ // We expand this pattern to also include dots after the first character.
1532
+ // If the annotation does not follow this pattern `ident(.@ident)*`, it must be quoted:
1533
+ // `@identifier@identifier` must be quoted but `@identifier.@identifier` should not.
1534
+ const annoRequiresQuoting = !/^[$_a-zA-Z][$_a-zA-Z0-9.]*(?:\.@[$_a-zA-Z][$_a-zA-Z0-9.]*)*$/.test(nameBeforeVariant);
1535
+ // Unfortunately, the compiler does not allow `.` after the first variant identifier,
1536
+ // even though that is the result after flattening.
1537
+ const variantRequiresQuoting = variant && !/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(variant);
1538
+
1539
+ let result;
1540
+ if (annoRequiresQuoting || variantRequiresQuoting)
1541
+ result = `${env.indent}@${quote(name)}`;
1542
+ else
1543
+ result = `${env.indent}@${name}`;
1703
1544
 
1704
- result += ` : ${renderAnnotationValue(ann, env)}`;
1545
+ result += ` : ${renderAnnotationValue(anno, env)}`;
1705
1546
  return `${result}\n`;
1706
1547
  }
1707
1548
 
@@ -1717,23 +1558,6 @@ function toCdsSourceCsn(csn, options) {
1717
1558
  return absName;
1718
1559
  }
1719
1560
 
1720
- /**
1721
- * Render appropriate USING directives for all artifacts used by artifact 'artifactName' in 'env'.
1722
- *
1723
- * @param {string} artifactName
1724
- * @param {CdlRenderEnvironment} env
1725
- * @return {string}
1726
- */
1727
- function renderUsings(artifactName, env) {
1728
- const distinct = {};
1729
- Object.keys(env.topLevelAliases)
1730
- .forEach((name) => {
1731
- distinct[`using ${env.topLevelAliases[name].quotedName} as ${env.topLevelAliases[name].quotedAlias};\n`] = '';
1732
- });
1733
-
1734
- return Object.keys(distinct).join('');
1735
- }
1736
-
1737
1561
  /**
1738
1562
  * Returns a newly created default environment (which keeps track of indentation, required USING
1739
1563
  * declarations and name prefixes.
@@ -1748,20 +1572,11 @@ function toCdsSourceCsn(csn, options) {
1748
1572
  topLevelAliases: Object.create(null),
1749
1573
  // Current name prefix (including trailing dot if not empty)
1750
1574
  namePrefix: '',
1575
+ artifactName: null,
1576
+ elementName: null,
1751
1577
  };
1752
1578
  }
1753
1579
 
1754
- /**
1755
- * Returns a copy of 'env' with (quoted) name prefix 'id' and a dot appended to the current name prefix
1756
- *
1757
- * @param {CdlRenderEnvironment} env
1758
- * @param {string} id
1759
- * @returns {CdlRenderEnvironment}
1760
- */
1761
- function addNamePrefix(env, id) {
1762
- return Object.assign({}, env, { namePrefix: `${env.namePrefix + quoteIdIfRequired(id)}.` });
1763
- }
1764
-
1765
1580
  /**
1766
1581
  * Returns a copy of 'env' with increased indentation (and resetted name prefix)
1767
1582
  *
@@ -1792,11 +1607,21 @@ function toCdsSourceCsn(csn, options) {
1792
1607
  function quoteIdIfRequired(id) {
1793
1608
  // Quote if required for CDL
1794
1609
  if (requiresQuotingForCdl(id))
1795
- return `![${id.replace(/]/g, ']]')}]`;
1610
+ return quote(id);
1796
1611
 
1797
1612
  return id;
1798
1613
  }
1799
1614
 
1615
+ /**
1616
+ * Quotes the identifier using CDL-style ![]-quotes.
1617
+ *
1618
+ * @param id
1619
+ * @returns {string}
1620
+ */
1621
+ function quote(id) {
1622
+ return `![${id.replace(/]/g, ']]')}]`;
1623
+ }
1624
+
1800
1625
  /**
1801
1626
  * Returns true if 'id' requires quotes for CDL, i.e. if 'id'
1802
1627
  * 1. starts with a digit
@@ -1837,7 +1662,7 @@ function toCdsSourceCsn(csn, options) {
1837
1662
  * of another entity -> Service.E and Service.E.Sub
1838
1663
  *
1839
1664
  * To handle such cases for hdbcds in quoted/hdbcds, we:
1840
- * - Find the part of the name that is no longer prefix (context/service/namespace)
1665
+ * - Find the part of the name that is no longer prefix (context/service)
1841
1666
  * - For Service.E -> E, for Service.E.Sub -> E.Sub
1842
1667
  * - Replace all dots in this "real name" with underscores
1843
1668
  * - Join with the env prefix
@@ -1864,14 +1689,14 @@ function toCdsSourceCsn(csn, options) {
1864
1689
  }
1865
1690
 
1866
1691
  /**
1867
- * Get the part that is really the name of this artifact and not just prefix caused by a context/service/namespace
1692
+ * Get the part that is really the name of this artifact and not just prefix caused by a context/service
1868
1693
  *
1869
1694
  * @param {String} artifactName Artifact name to use
1870
1695
  * @returns {String} non-prefix part of the artifact name
1871
1696
  */
1872
1697
  function getRealName(artifactName) {
1873
1698
  const parts = artifactName.split('.');
1874
- // Lenght of 1 -> There can be no prefix
1699
+ // Length of 1 -> There can be no prefix
1875
1700
  if (parts.length === 1)
1876
1701
  return artifactName;
1877
1702
 
@@ -1885,7 +1710,7 @@ function toCdsSourceCsn(csn, options) {
1885
1710
 
1886
1711
 
1887
1712
  const art = csn.definitions[seen];
1888
- if (!art || ![ 'service', 'context', 'namespace' ].includes(art.kind)) {
1713
+ if (!art || (art.kind !== 'service' && art.kind !== 'context')) {
1889
1714
  // We found a case where the prefix ended
1890
1715
  // Return everything following
1891
1716
  return parts.slice(i).join('.');
@@ -1897,6 +1722,80 @@ function toCdsSourceCsn(csn, options) {
1897
1722
  }
1898
1723
  }
1899
1724
 
1725
+ /**
1726
+ * Render the given string. Uses back-tick strings.
1727
+ * env is used for indentation of three-back-tick strings.
1728
+ *
1729
+ * @param str
1730
+ * @param env
1731
+ * @returns {string}
1732
+ */
1733
+ function renderString(str, env) {
1734
+ if (isSimpleString(str))
1735
+ return `'${str.replace(/'/g, '\'\'')}'`;
1736
+
1737
+ // We try to work similar to how JavaScript implements JSON.stringify.
1738
+ // JSON.stringify() also checks for unpaired unicode surrogates (see §25.5.2.2,
1739
+ // <https://tc39.es/ecma262/#sec-quotejsonstring>).
1740
+ str = escapeString(str, {
1741
+ $: '\\$',
1742
+ '`': '\\`',
1743
+ '\\': '\\\\',
1744
+ // Replace commonly known escape sequences for control characters
1745
+ // See lib/language/multiLineStringParser.js
1746
+ '\f': '\\f',
1747
+ '\v': '\\v',
1748
+ '\t': '\\t',
1749
+ '\b': '\\b',
1750
+ // If CR, LS, or PS appear, they have been encoded explicitly. If we don't escape
1751
+ // them, a recompilation may yield different results because of newline normalization.
1752
+ '\r': '\\r',
1753
+ '\u{2028}': '\\u{2028}',
1754
+ '\u{2029}': '\\u{2029}',
1755
+ // Don't encode LF
1756
+ '\n': '\n',
1757
+ // JSON.stringify() is not required to escape all control characters, but if used, files may
1758
+ // be interpreted as binary. Therefore, we replace them.
1759
+ // We exclude LF from this list (\n). Characters with "nice" escapes have been replaced above.
1760
+ control: hexEscape,
1761
+ unpairedSurrogate: hexEscape,
1762
+ });
1763
+
1764
+ // Note: String is normalized, only \n is the line separator.
1765
+ const lines = str.split('\n');
1766
+ // We don't know whether a text block was used or not. But if there
1767
+ // are more than three lines, text blocks with indentation "look nicer".
1768
+ // This value was chosen by personal taste.
1769
+ if (lines.length > 3) {
1770
+ str = lines.join(`\n${env.indent}`);
1771
+ return `\`\`\`\n${env.indent}${str}\n${env.indent}\`\`\``;
1772
+ }
1773
+
1774
+ return `\`${str}\``;
1775
+ }
1776
+
1777
+ /** @param {number} codePoint */
1778
+ function hexEscape(codePoint) {
1779
+ const hex = codePoint.toString(16);
1780
+ return `\\u{${hex}}`;
1781
+ }
1782
+
1783
+ /**
1784
+ * Returns true if the given string can be represented by using single quotes.
1785
+ * @param {string} str
1786
+ */
1787
+ function isSimpleString(str) {
1788
+ // A single-line string allows everything except certain line separators/breaks.
1789
+ // See ANTLR grammar for specifics.
1790
+ // Furthermore, if control characters are used, we escape them,
1791
+ // as these are explicitly mentioned in the JSON spec (§9):
1792
+ // <https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf>
1793
+ // On top, because (invalid) surrogate pairs need to be handled, we check for them as well.
1794
+ // v3: Not a simple string if ' (\u0027) is in string.
1795
+ return str === '' || (/^[^\u{0000}-\u{001F}\u2028\u2029]+$/u.test(str) &&
1796
+ !hasUnpairedUnicodeSurrogate(str));
1797
+ }
1798
+
1900
1799
  /**
1901
1800
  * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
1902
1801
  *