@sap/cds-compiler 2.7.0 → 2.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +10 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +17 -33
  7. package/lib/api/options.js +25 -13
  8. package/lib/api/validate.js +33 -9
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +26 -2
  13. package/lib/base/messages.js +25 -9
  14. package/lib/base/model.js +5 -3
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/onConditions.js +5 -0
  17. package/lib/checks/selectItems.js +4 -0
  18. package/lib/checks/types.js +26 -2
  19. package/lib/checks/unknownMagic.js +41 -0
  20. package/lib/checks/validator.js +7 -2
  21. package/lib/compiler/assert-consistency.js +18 -5
  22. package/lib/compiler/base.js +65 -0
  23. package/lib/compiler/builtins.js +30 -1
  24. package/lib/compiler/checks.js +5 -2
  25. package/lib/compiler/definer.js +145 -120
  26. package/lib/compiler/index.js +16 -4
  27. package/lib/compiler/propagator.js +5 -2
  28. package/lib/compiler/resolver.js +207 -47
  29. package/lib/compiler/shared.js +47 -200
  30. package/lib/compiler/utils.js +173 -0
  31. package/lib/edm/annotations/genericTranslation.js +183 -187
  32. package/lib/edm/csn2edm.js +94 -98
  33. package/lib/edm/edm.js +16 -20
  34. package/lib/edm/edmPreprocessor.js +302 -115
  35. package/lib/edm/edmUtils.js +31 -12
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +28 -1
  38. package/lib/gen/language.tokens +79 -69
  39. package/lib/gen/languageLexer.interp +28 -1
  40. package/lib/gen/languageLexer.js +879 -805
  41. package/lib/gen/languageLexer.tokens +71 -62
  42. package/lib/gen/languageParser.js +5308 -4308
  43. package/lib/json/from-csn.js +59 -30
  44. package/lib/json/to-csn.js +354 -105
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +1 -0
  47. package/lib/language/genericAntlrParser.js +81 -14
  48. package/lib/language/language.g4 +163 -31
  49. package/lib/main.d.ts +136 -17
  50. package/lib/main.js +7 -1
  51. package/lib/model/api.js +78 -0
  52. package/lib/model/csnRefs.js +115 -32
  53. package/lib/model/csnUtils.js +71 -33
  54. package/lib/model/enrichCsn.js +36 -9
  55. package/lib/model/revealInternalProperties.js +20 -4
  56. package/lib/modelCompare/compare.js +2 -1
  57. package/lib/optionProcessor.js +33 -16
  58. package/lib/render/.eslintrc.json +3 -1
  59. package/lib/render/DuplicateChecker.js +1 -1
  60. package/lib/render/toCdl.js +60 -17
  61. package/lib/render/toHdbcds.js +122 -74
  62. package/lib/render/toSql.js +57 -32
  63. package/lib/render/utils/common.js +6 -10
  64. package/lib/sql-identifier.js +6 -1
  65. package/lib/transform/db/constraints.js +273 -119
  66. package/lib/transform/db/draft.js +9 -6
  67. package/lib/transform/db/expansion.js +19 -7
  68. package/lib/transform/db/flattening.js +31 -7
  69. package/lib/transform/db/transformExists.js +344 -66
  70. package/lib/transform/db/views.js +438 -0
  71. package/lib/transform/forHanaNew.js +65 -436
  72. package/lib/transform/forOdataNew.js +21 -10
  73. package/lib/transform/localized.js +2 -0
  74. package/lib/transform/odata/attachPath.js +19 -4
  75. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  76. package/lib/transform/odata/referenceFlattener.js +44 -38
  77. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  78. package/lib/transform/odata/structuralPath.js +72 -0
  79. package/lib/transform/odata/structureFlattener.js +13 -10
  80. package/lib/transform/odata/typesExposure.js +22 -12
  81. package/lib/transform/transformUtilsNew.js +55 -9
  82. package/lib/transform/translateAssocsToJoins.js +11 -17
  83. package/lib/transform/universalCsnEnricher.js +67 -0
  84. package/lib/utils/file.js +5 -3
  85. package/lib/utils/term.js +65 -42
  86. package/lib/utils/timetrace.js +48 -26
  87. package/package.json +1 -1
@@ -8,11 +8,13 @@ const {
8
8
  isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
9
9
  } = require('../model/csnUtils');
10
10
  const keywords = require('../base/keywords');
11
- const { renderFunc, processExprArray, findElement } = require('./utils/common');
11
+ const { renderFunc, beautifyExprArray, findElement } = require('./utils/common');
12
12
  const { checkCSNVersion } = require('../json/csnVersion');
13
- const timetrace = require('../utils/timetrace');
13
+ const { timetrace } = require('../utils/timetrace');
14
14
  const { csnRefs } = require('../model/csnRefs');
15
15
  const { forEachDefinition } = require('../model/csnUtils');
16
+ const enrichUniversalCsn = require('../transform/universalCsnEnricher');
17
+ const { isBetaEnabled } = require('../base/model');
16
18
 
17
19
  /**
18
20
  * Render the CSN model 'model' to CDS source text. One source is created per
@@ -33,6 +35,9 @@ function toCdsSourceCsn(csn, options) {
33
35
  // Skip compactModel if already using CSN
34
36
  // const csn = cloneCsn(model, options);
35
37
 
38
+ if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
39
+ enrichUniversalCsn(csn, options);
40
+
36
41
  checkCSNVersion(csn, options);
37
42
 
38
43
  const result = Object.create(null);
@@ -50,16 +55,20 @@ function toCdsSourceCsn(csn, options) {
50
55
  result[main] += `${sourceStr}\n`;
51
56
  });
52
57
 
53
- // Apply possible subelement annotations with an "annotate X with"
58
+ // Apply possible subelement/action return annotations with an "annotate X with"
59
+ // Some of them appear in csn.extensions, some not...
54
60
  if (subelementAnnotates.length > 0) {
55
- for (const [ artName, element, elementName ] of subelementAnnotates) {
56
- let sourceStr = `annotate ${artName} with {\n`;
57
- sourceStr += ` ${elementName} {\n`;
61
+ for (const [ artName, element, elementName, suffix ] of subelementAnnotates) {
62
+ // Suffix is used with action return annotations
63
+ let sourceStr = `annotate ${artName} with ${suffix ? `${suffix} ` : ''}{\n`;
64
+ if (elementName) // action returns do not have element name - we need less {} there
65
+ sourceStr += ` ${elementName} {\n`;
58
66
  const env = increaseIndent(increaseIndent(createEnv()));
59
67
  const subelements = renderSubelementAnnotates(element, env);
60
68
  if (subelements !== '') {
61
69
  sourceStr += `${subelements}\n`;
62
- sourceStr += ' }\n';
70
+ if (elementName) // action returns do not have element name - we need less {} there
71
+ sourceStr += ' }\n';
63
72
  sourceStr += '}\n';
64
73
  result[main] += `${sourceStr}\n`;
65
74
  }
@@ -145,6 +154,12 @@ function toCdsSourceCsn(csn, options) {
145
154
  if (ext.elements)
146
155
  result += renderElementExtensions(ext.elements, env);
147
156
 
157
+ // Returns annotations
158
+ if (ext.returns) {
159
+ const childEnv = increaseIndent(env);
160
+ result += ` with returns${renderElementExtensions(ext.returns.elements, childEnv)}`;
161
+ }
162
+
148
163
  // Action annotations
149
164
  if (ext.actions) {
150
165
  result += ' actions {\n';
@@ -162,10 +177,19 @@ function toCdsSourceCsn(csn, options) {
162
177
 
163
178
  result += `${paramAnnotations.join(',\n')}\n${childEnv.indent})`;
164
179
  }
180
+ // Annotations on action returns
181
+ if (action.returns && action.returns.elements) {
182
+ const grandChildEnv = increaseIndent(childEnv);
183
+ result += ` returns${renderElementExtensions(action.returns.elements, grandChildEnv)}`;
184
+ }
185
+
186
+
165
187
  result += ';\n';
166
188
  }
167
189
  result += `${env.indent}}`;
168
190
  }
191
+
192
+
169
193
  result += ';';
170
194
  return result;
171
195
  }).join('\n');
@@ -205,6 +229,7 @@ function toCdsSourceCsn(csn, options) {
205
229
  function renderArtifact(artifactName, art, env) {
206
230
  // FIXME: Correctly build the paths during runtime to give better locations
207
231
  env.path = [ 'definitions', artifactName ];
232
+ env.artifactName = artifactName;
208
233
 
209
234
  switch (art.kind) {
210
235
  case 'entity':
@@ -509,6 +534,7 @@ function toCdsSourceCsn(csn, options) {
509
534
  * @param {Boolean} [isSubElement]
510
535
  */
511
536
  function renderElement(elementName, elm, env, isSubElement) {
537
+ env.elementName = elementName;
512
538
  let result = renderDocComment(elm, env) + renderAnnotationAssignments(elm, env);
513
539
  const quotedElementName = quoteOrUppercaseId(elementName);
514
540
  result += `${env.indent + (elm.virtual ? 'virtual ' : '') +
@@ -516,10 +542,11 @@ function toCdsSourceCsn(csn, options) {
516
542
  ((elm.masked && !elm._ignoreMasked) ? 'masked ' : '') +
517
543
  quotedElementName} : ${
518
544
  renderTypeReference(elm, env, undefined)
519
- }${renderNullability(elm)}`;
545
+ }${elm.on ? '' : renderNullability(elm)}`;
520
546
  if (elm.default)
521
547
  result += ` default ${renderExpr(elm.default, env)}`;
522
548
 
549
+ delete env.elementName;
523
550
  return `${result};\n`;
524
551
  }
525
552
 
@@ -552,7 +579,7 @@ function toCdsSourceCsn(csn, options) {
552
579
  * @return {string}
553
580
  */
554
581
  function renderQueryActionsAndFunctions(artifactName, art, env) {
555
- let result = renderDocComment(art, env) + renderActionsAndFunctions(art, env);
582
+ let result = renderActionsAndFunctions(art, env);
556
583
  // Even if we have seen actions/functions, they might all have been ignored
557
584
  if (result !== '')
558
585
  result = `${env.indent}extend entity ${artifactName} with${result};`;
@@ -585,7 +612,7 @@ function toCdsSourceCsn(csn, options) {
585
612
  }
586
613
  // Now iterate elements - render an annotation if it is different from the column's
587
614
  const childEnv = increaseIndent(env);
588
- let result = renderDocComment(art, env);
615
+ let result = '';
589
616
  for (const elemName in art.elements) {
590
617
  let elemAnnotations = '';
591
618
  const elem = art.elements[elemName];
@@ -688,7 +715,7 @@ function toCdsSourceCsn(csn, options) {
688
715
 
689
716
  // Even the first step might have parameters and/or a filter
690
717
  if (path.ref[0].args)
691
- result += `(${renderArgs(path.ref[0].args, ':', env)})`;
718
+ result += `(${renderArgs(path.ref[0], ':', env)})`;
692
719
 
693
720
  if (path.ref[0].where)
694
721
  result += `[${path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : ''}${renderExpr(path.ref[0].where, env, true, true)}]`;
@@ -1056,8 +1083,11 @@ function toCdsSourceCsn(csn, options) {
1056
1083
  const childEnv = increaseIndent(env);
1057
1084
  const parameters = Object.keys(act.params || []).map(name => renderParameter(name, act.params[name], childEnv)).join(',\n');
1058
1085
  result += (parameters === '') ? '()' : `(\n${parameters}\n${env.indent})`;
1059
- if (act.returns)
1086
+ if (act.returns) {
1087
+ if (act.returns.type && act.returns.elements) // action returns annotations
1088
+ subelementAnnotates.push([ actionName, act.returns, '', 'returns' ]);
1060
1089
  result += ` returns ${renderTypeReference(act.returns, env)}${renderNullability(act.returns)}`;
1090
+ }
1061
1091
 
1062
1092
  result += ';\n';
1063
1093
  return result;
@@ -1133,6 +1163,10 @@ function toCdsSourceCsn(csn, options) {
1133
1163
  let rc = `many ${renderTypeReference(elm.items, env)}`;
1134
1164
  if (elm.items.notNull != null)
1135
1165
  rc += elm.items.notNull ? ' not null' : ' null';
1166
+ // many sub element annotates
1167
+ if (elm.items.type && elm.items.elements && env.artifactName)
1168
+ subelementAnnotates.push([ env.artifactName, elm.items, env.elementName ]);
1169
+
1136
1170
  return rc;
1137
1171
  }
1138
1172
 
@@ -1316,7 +1350,7 @@ function toCdsSourceCsn(csn, options) {
1316
1350
  function renderExpr(x, env, inline = true, inExpr = false) {
1317
1351
  // Compound expression
1318
1352
  if (Array.isArray(x))
1319
- return processExprArray(x, renderExpr, env, inline, inExpr);
1353
+ return beautifyExprArray(x.map(item => renderExpr(item, env, inline, inExpr)));
1320
1354
 
1321
1355
  if (typeof x === 'object' && x !== null) {
1322
1356
  if (inExpr && x.cast && x.cast.type)
@@ -1370,6 +1404,12 @@ function toCdsSourceCsn(csn, options) {
1370
1404
  // FIXME: no extra magic with x.param or x.global
1371
1405
  return `${(x.param || x.global) ? ':' : ''}${x.ref.map(renderPathStep).join('.')}`;
1372
1406
  }
1407
+ else if (x.xpr && x.func) {
1408
+ // window function
1409
+ const funcDef = renderFunc( x.func, x, null, a => renderArgs(a, '=>', env) );
1410
+ const windowFunctionOperator = x.xpr.shift(); // OVER ...
1411
+ return `${funcDef} ${windowFunctionOperator} ( ${renderExpr(x.xpr, env, true)} )`;
1412
+ }
1373
1413
  // Function call, possibly with args (use '=>' for named args)
1374
1414
  else if (x.func) {
1375
1415
  // test for non-regular HANA identifier that needs to be quoted
@@ -1440,13 +1480,13 @@ function toCdsSourceCsn(csn, options) {
1440
1480
 
1441
1481
  // Not really a path step but an object-like function call
1442
1482
  if (s.func)
1443
- return `${s.func}(${renderArgs(s.args, '=>', env)})`;
1483
+ return `${s.func}(${renderArgs(s, '=>', env)})`;
1444
1484
 
1445
1485
  // Path step, possibly with view parameters and/or filters
1446
1486
  let result = `${quoteOrUppercaseId(s.id)}`;
1447
1487
  if (s.args) {
1448
1488
  // View parameters
1449
- result += `(${renderArgs(s.args, ':', env)})`;
1489
+ result += `(${renderArgs(s, ':', env)})`;
1450
1490
  }
1451
1491
  if (s.where) {
1452
1492
  // Filter, possibly with cardinality
@@ -1464,12 +1504,13 @@ function toCdsSourceCsn(csn, options) {
1464
1504
  * Render function arguments or view parameters (positional if array, named if object/dict),
1465
1505
  * using 'sep' as separator for positional parameters
1466
1506
  *
1467
- * @param {object[]|object} args (Un)Named arguments
1507
+ * @param {object} node with `args` to render
1468
1508
  * @param {string} sep
1469
1509
  * @param {CdlRenderEnvironment} env
1470
1510
  * @returns {string}
1471
1511
  */
1472
- function renderArgs(args, sep, env) {
1512
+ function renderArgs(node, sep, env) {
1513
+ const args = node.args ? node.args : {};
1473
1514
  // Positional arguments
1474
1515
  if (Array.isArray(args))
1475
1516
  return args.map(arg => renderExpr(arg, env)).join(', ');
@@ -1861,6 +1902,8 @@ function toCdsSourceCsn(csn, options) {
1861
1902
  *
1862
1903
  * @property {string} indent Current indentation as a string, e.g. ' ' for two spaces.
1863
1904
  * @property {CSN.Path} [path] CSN path to the current artifact
1905
+ * @property {string} artifactName Name of the artifact - set in renderArtifact
1906
+ * @property {string} elementName Name of the element being rendered - set in renderElement
1864
1907
  * @property {{[name: string]: {
1865
1908
  quotedName: string,
1866
1909
  quotedAlias: string
@@ -3,12 +3,12 @@
3
3
  const {
4
4
  getParentNameOf, getLastPartOf, getLastPartOfRef,
5
5
  hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
6
- getRootArtifactName, getResultingName, getNamespace,
6
+ getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement,
7
7
  } = require('../model/csnUtils');
8
8
  const keywords = require('../base/keywords');
9
9
  const {
10
- renderFunc, processExprArray, getRealName, addContextMarkers, addIntermediateContexts, cdsToSqlTypes,
11
- hasHanaComment, getHanaComment, findElement,
10
+ renderFunc, beautifyExprArray, getRealName, addContextMarkers, addIntermediateContexts, cdsToSqlTypes,
11
+ hasHanaComment, getHanaComment, findElement, funcWithoutParen,
12
12
  } = require('./utils/common');
13
13
  const {
14
14
  renderReferentialConstraint,
@@ -17,7 +17,9 @@ const DuplicateChecker = require('./DuplicateChecker');
17
17
  const { isDeprecatedEnabled, forEachDefinition } = require('../base/model');
18
18
  const { checkCSNVersion } = require('../json/csnVersion');
19
19
  const { makeMessageFunction } = require('../base/messages');
20
- const timetrace = require('../utils/timetrace');
20
+ const { timetrace } = require('../utils/timetrace');
21
+
22
+ const { smartId, delimitedId } = require('../sql-identifier');
21
23
 
22
24
  const $PROJECTION = '$projection';
23
25
  const $SELF = '$self';
@@ -402,6 +404,9 @@ function toHdbcdsSource(csn, options) {
402
404
  const duplicateChecker = new DuplicateChecker(); // registry for all artifact names and element names
403
405
  duplicateChecker.addArtifact(artifactName, env.path, artifactName);
404
406
  childEnv.path = env.path.concat('elements');
407
+ // calculate __aliases which must be used in case an association
408
+ // has the same identifier as it's target
409
+ createTopLevelAliasesForArtifact(artifactName, art, env);
405
410
  for (const name in art.elements)
406
411
  result += renderElement(name, art.elements[name], childEnv, duplicateChecker);
407
412
 
@@ -411,6 +416,33 @@ function toHdbcdsSource(csn, options) {
411
416
  return result;
412
417
  }
413
418
 
419
+ /**
420
+ * If an association/composition has the same identifier as it's target
421
+ * we must render an "using target as __target" and use the alias to refer to the target
422
+ *
423
+ * @param {string} artName
424
+ * @param {CSN.Artifact} art
425
+ * @param {CdlRenderEnvironment} env
426
+ */
427
+ function createTopLevelAliasesForArtifact(artName, art, env) {
428
+ forEachMember(art, (element) => {
429
+ if (!element.target)
430
+ return;
431
+
432
+ let alias = element['@cds.persistence.name'];
433
+ if (uppercaseAndUnderscore(element.target) === element['@cds.persistence.name']) {
434
+ alias = createTopLevelAliasName(element['@cds.persistence.name']);
435
+ // calculate new alias if it would conflict with other csn.Artifact
436
+ while (csn.definitions[alias])
437
+ alias = createTopLevelAliasName(alias);
438
+ env.topLevelAliases[element['@cds.persistence.name']] = {
439
+ quotedName: formatIdentifier(element['@cds.persistence.name']),
440
+ quotedAlias: formatIdentifier(alias),
441
+ };
442
+ }
443
+ });
444
+ }
445
+
414
446
  /**
415
447
  * Render the 'technical configuration { ... }' section 'tc' of an entity.
416
448
  *
@@ -516,7 +548,7 @@ function toHdbcdsSource(csn, options) {
516
548
  // Special handling for HANA CDS: Must omit the ':' before anonymous structured types (for historical reasons)
517
549
  const omitColon = (!elm.type && elm.elements);
518
550
  let result = '';
519
- const quotedElementName = quoteOrUppercaseId(elementName, env.path);
551
+ const quotedElementName = formatIdentifier(elementName);
520
552
  if (duplicateChecker)
521
553
  duplicateChecker.addElement(quotedElementName, env.path, elementName);
522
554
 
@@ -526,7 +558,7 @@ function toHdbcdsSource(csn, options) {
526
558
  result += env.indent + (elm.key && !isSubElement ? 'key ' : '') +
527
559
  (elm.masked ? 'masked ' : '') +
528
560
  quotedElementName + (omitColon ? ' ' : ' : ') +
529
- renderTypeReference(elm, env, undefined) +
561
+ renderTypeReference(elm, env) +
530
562
  renderNullability(elm);
531
563
  if (elm.default)
532
564
  result += ` default ${renderExpr(elm.default, env)}`;
@@ -548,7 +580,7 @@ function toHdbcdsSource(csn, options) {
548
580
  if (source.SELECT || source.SET) {
549
581
  let result = `(${renderQuery(source, false, increaseIndent(env))})`;
550
582
  if (source.as)
551
- result += ` as ${quoteOrUppercaseId(source.as, env.path)}`;
583
+ result += ` as ${formatIdentifier(source.as)}`;
552
584
 
553
585
  return result;
554
586
  }
@@ -598,7 +630,7 @@ function toHdbcdsSource(csn, options) {
598
630
 
599
631
  // Even the first step might have parameters and/or a filter
600
632
  if (path.ref[0].args)
601
- result += `(${renderArgs(path.ref[0].args, ':', env)})`;
633
+ result += `(${renderArgs(path.ref[0], ':', env)})`;
602
634
 
603
635
  if (path.ref[0].where)
604
636
  result += `[${path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : ''}${renderExpr(path.ref[0].where, env, true, true)}]`;
@@ -627,11 +659,11 @@ function toHdbcdsSource(csn, options) {
627
659
  const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.sqlMapping, path.ref[0])) : getLastPartOfRef(path.ref);
628
660
  if (path.as) {
629
661
  // Source had an alias - render it
630
- result += ` as ${quoteOrUppercaseId(path.as, env.path)}`;
662
+ result += ` as ${formatIdentifier(path.as)}`;
631
663
  }
632
- else if (getLastPartOf(result) !== quoteOrUppercaseId(implicitAlias)) {
664
+ else if (getLastPartOf(result) !== formatIdentifier(implicitAlias)) {
633
665
  // Render an artificial alias if the result would produce a different one
634
- result += ` as ${quoteOrUppercaseId(implicitAlias, env.path)}`;
666
+ result += ` as ${formatIdentifier(implicitAlias)}`;
635
667
  }
636
668
  return result;
637
669
  }
@@ -648,14 +680,14 @@ function toHdbcdsSource(csn, options) {
648
680
  */
649
681
  function renderViewColumn(col, env, element) {
650
682
  // Annotations and column
651
- let result = element && hasHanaComment(element, options) ? `${env.indent}@Comment: '${getEscapedHanaComment(element)}'\n` : '';
683
+ let result = '';
652
684
 
653
685
  const leaf = col.as || col.ref && col.ref[col.ref.length - 1];
654
686
  // Render 'null as <alias>' only for database and if element is virtual
655
687
 
656
688
  if (element && element.virtual || env._artifact.elements[leaf] && env._artifact.elements[leaf].virtual) {
657
689
  if (isDeprecatedEnabled(options, 'renderVirtualElements'))
658
- return `${result}${env.indent}null as ${quoteOrUppercaseId(leaf, env.path)}`;
690
+ return `${result}${env.indent}null as ${formatIdentifier(leaf)}`;
659
691
  }
660
692
  else {
661
693
  return renderNonVirtualColumn();
@@ -683,7 +715,7 @@ function toHdbcdsSource(csn, options) {
683
715
  alias = leaf;
684
716
 
685
717
  if (alias)
686
- result += ` as ${quoteOrUppercaseId(alias, env.path)}`;
718
+ result += ` as ${formatIdentifier(alias)}`;
687
719
 
688
720
  // Explicit type provided for the view element?
689
721
  if (col.cast && col.cast.target) {
@@ -797,7 +829,7 @@ function toHdbcdsSource(csn, options) {
797
829
  result += `${env.indent}}`;
798
830
  }
799
831
  if (select.excluding) {
800
- result += ` excluding {\n${select.excluding.map(id => `${childEnv.indent}${quoteOrUppercaseId(id, env.path)}`).join(',\n')}\n`;
832
+ result += ` excluding {\n${select.excluding.map(id => `${childEnv.indent}${formatIdentifier(id)}`).join(',\n')}\n`;
801
833
  result += `${env.indent}}`;
802
834
  }
803
835
 
@@ -895,7 +927,7 @@ function toHdbcdsSource(csn, options) {
895
927
  function renderParameter(parName, par, env) {
896
928
  if (par.notNull === true || par.notNull === false)
897
929
  info(null, env.path.concat([ 'params', parName ]), 'Not Null constraints on HDBCDS view parameters are not allowed and are ignored');
898
- return `${env.indent + quoteOrUppercaseId(parName, env.path)} : ${renderTypeReference(par, env)}`;
930
+ return `${env.indent + formatParamIdentifier(parName, env.path.concat([ 'params', parName ]))} : ${renderTypeReference(par, env)}`;
899
931
  }
900
932
 
901
933
  /**
@@ -927,7 +959,7 @@ function toHdbcdsSource(csn, options) {
927
959
  }
928
960
  else {
929
961
  // Derived type or annotation with non-anonymous type
930
- result += ` : ${renderTypeReference(art, env, false)};\n`;
962
+ result += ` : ${renderTypeReference(art, env)};\n`;
931
963
  }
932
964
  return result;
933
965
  }
@@ -938,10 +970,9 @@ function toHdbcdsSource(csn, options) {
938
970
  *
939
971
  * @param {CSN.Element} elm Element using the type reference
940
972
  * @param {CdlRenderEnvironment} env Environment
941
- * @param {boolean} [noEnum=false] If true, do not render enums
942
973
  * @returns {string} Rendered type reference
943
974
  */
944
- function renderTypeReference(elm, env, noEnum = false) {
975
+ function renderTypeReference(elm, env) {
945
976
  let result = '';
946
977
 
947
978
  // Array type: Render items instead
@@ -979,11 +1010,7 @@ function toHdbcdsSource(csn, options) {
979
1010
  // Reference to another element
980
1011
  if (elm.type.ref) {
981
1012
  // For HANA CDS, we need a 'type of'
982
- let result = `type of ${renderAbsolutePath(elm.type, env)}`;
983
- if (elm.enum)
984
- result += renderEnum(elm.enum, env);
985
-
986
- return result;
1013
+ return `type of ${renderAbsolutePath(elm.type, env)}`;
987
1014
  }
988
1015
 
989
1016
  // If we get here, it must be a named type
@@ -995,8 +1022,6 @@ function toHdbcdsSource(csn, options) {
995
1022
  // Type names are never flattened (derived types are unraveled in HANA)
996
1023
  result += renderAbsoluteNameWithQuotes(elm.type, env);
997
1024
  }
998
- if (elm.enum && !noEnum)
999
- result += renderEnum(elm.enum, env);
1000
1025
 
1001
1026
  return result;
1002
1027
  }
@@ -1012,10 +1037,19 @@ function toHdbcdsSource(csn, options) {
1012
1037
 
1013
1038
  result += `${renderCardinality(elm.cardinality)} to `;
1014
1039
 
1040
+
1015
1041
  // normal target or named aspect
1016
1042
  if (elm.target || elm.targetAspect && typeof elm.targetAspect === 'string') {
1017
- result += plainNames ? renderAbsoluteNamePlain(elm.target || elm.targetAspect, env)
1018
- : renderAbsoluteNameWithQuotes(elm.target || elm.targetAspect, env);
1043
+ // we might have a "using target as __target"
1044
+ const targetArtifact = csn.definitions[elm.target];
1045
+ const targetAlias = env.topLevelAliases[targetArtifact['@cds.persistence.name']];
1046
+ if (targetAlias) {
1047
+ result += targetAlias.quotedAlias;
1048
+ }
1049
+ else {
1050
+ result += plainNames ? renderAbsoluteNamePlain(elm.target || elm.targetAspect, env)
1051
+ : renderAbsoluteNameWithQuotes(elm.target || elm.targetAspect, env);
1052
+ }
1019
1053
  }
1020
1054
 
1021
1055
  // ON-condition (if any)
@@ -1053,27 +1087,6 @@ function toHdbcdsSource(csn, options) {
1053
1087
  return elm.type.replace(/^cds\./, '') + renderTypeParameters(elm);
1054
1088
  }
1055
1089
 
1056
- /**
1057
- * Render the 'enum { ... } part of a type declaration
1058
- *
1059
- * @param {CSN.EnumElements} enumPart Enum part of a type declaration
1060
- * @param {CdlRenderEnvironment} env Environment
1061
- * @returns {string} Rendered enum
1062
- */
1063
- function renderEnum(enumPart, env) {
1064
- let result = ' enum {\n';
1065
- const childEnv = increaseIndent(env);
1066
- for (const name in enumPart) {
1067
- const enumConst = enumPart[name];
1068
- result += childEnv.indent + quoteId(name);
1069
- if (enumConst.val !== undefined)
1070
- result += ` = ${renderExpr(enumConst, childEnv)}`;
1071
- result += ';\n';
1072
- }
1073
- result += `${env.indent}}`;
1074
- return result;
1075
- }
1076
-
1077
1090
  /**
1078
1091
  * Render an expression (including paths and values) or condition 'x'.
1079
1092
  * (no trailing LF, don't indent if inline)
@@ -1087,7 +1100,7 @@ function toHdbcdsSource(csn, options) {
1087
1100
  function renderExpr(x, env, inline = true, inExpr = false) {
1088
1101
  // Compound expression
1089
1102
  if (Array.isArray(x))
1090
- return processExprArray(x, renderExpr, env, inline, inExpr);
1103
+ return beautifyExprArray(x.map(item => renderExpr(item, env, inline, inExpr)));
1091
1104
 
1092
1105
  if (typeof x === 'object' && x !== null) {
1093
1106
  if (inExpr && x.cast && x.cast.type)
@@ -1129,6 +1142,9 @@ function toHdbcdsSource(csn, options) {
1129
1142
 
1130
1143
  const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
1131
1144
  const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
1145
+ // we can't quote functions with parens, issue warning if it is a reserved keyword
1146
+ if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
1147
+ warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
1132
1148
  return renderFunc( funcName, x, 'hana', a => renderArgs(a, '=>', env) );
1133
1149
  }
1134
1150
  // Nested expression
@@ -1185,7 +1201,12 @@ function toHdbcdsSource(csn, options) {
1185
1201
  */
1186
1202
  function renderExpressionRef(x) {
1187
1203
  if (!x.param && !x.global) {
1204
+ const magicReplacement = getVariableReplacement(x.ref, options);
1188
1205
  if (x.ref[0] === '$user') {
1206
+ if (magicReplacement !== null)
1207
+ return `'${magicReplacement}'`;
1208
+
1209
+ // Keep old way of solving this to remain backwards compatible
1189
1210
  // FIXME: this is all not enough: we might need an explicit select item alias
1190
1211
  if (x.ref[1] === 'id') {
1191
1212
  if (options.magicVars && options.magicVars.user && (typeof options.magicVars.user === 'string' || options.magicVars.user instanceof String))
@@ -1207,6 +1228,9 @@ function toHdbcdsSource(csn, options) {
1207
1228
  else if (x.ref[1] === 'to')
1208
1229
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1209
1230
  }
1231
+ else if (x.ref[0] === '$session' && magicReplacement !== null) {
1232
+ return `'${magicReplacement}'`;
1233
+ }
1210
1234
  }
1211
1235
  return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref)).join('.')}`;
1212
1236
  }
@@ -1218,7 +1242,7 @@ function toHdbcdsSource(csn, options) {
1218
1242
  * @returns {string} Rendered cast()
1219
1243
  */
1220
1244
  function renderExplicitTypeCast(value) {
1221
- let typeRef = renderTypeReference(x.cast, env, true);
1245
+ let typeRef = renderTypeReference(x.cast, env);
1222
1246
 
1223
1247
  // inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
1224
1248
  const hanaSqlType = cdsToSqlTypes.hana[x.cast.type] || cdsToSqlTypes.standard[x.cast.type];
@@ -1267,7 +1291,7 @@ function toHdbcdsSource(csn, options) {
1267
1291
  [ $SELF, $PROJECTION, '$session' ].includes(s))
1268
1292
  return s;
1269
1293
 
1270
- return quoteOrUppercaseId(s, env.path);
1294
+ return formatIdentifier(s);
1271
1295
  }
1272
1296
  // ID with filters or parameters
1273
1297
  else if (typeof s === 'object') {
@@ -1277,13 +1301,13 @@ function toHdbcdsSource(csn, options) {
1277
1301
 
1278
1302
  // Not really a path step but an object-like function call
1279
1303
  if (s.func)
1280
- return `${s.func}(${renderArgs(s.args, '=>', env)})`;
1304
+ return `${s.func}(${renderArgs(s, '=>', env)})`;
1281
1305
 
1282
1306
  // Path step, possibly with view parameters and/or filters
1283
- let result = `${quoteOrUppercaseId(s.id)}`;
1307
+ let result = `${formatIdentifier(s.id)}`;
1284
1308
  if (s.args) {
1285
1309
  // View parameters
1286
- result += `(${renderArgs(s.args, ':', env)})`;
1310
+ result += `(${renderArgs(s, ':', env)})`;
1287
1311
  }
1288
1312
  if (s.where) {
1289
1313
  // Filter, possibly with cardinality
@@ -1300,19 +1324,21 @@ function toHdbcdsSource(csn, options) {
1300
1324
  * Render function arguments or view parameters (positional if array, named if object/dict),
1301
1325
  * using 'sep' as separator for positional parameters
1302
1326
  *
1303
- * @param {object[]|object} args (Un)Named arguments
1327
+ * @param {object} node with `args` to render
1304
1328
  * @param {string} sep Seperator between arguments
1305
1329
  * @param {CdlRenderEnvironment} env Environment
1306
1330
  * @returns {string} Rendered arguments
1307
1331
  */
1308
- function renderArgs(args, sep, env) {
1332
+ function renderArgs(node, sep, env) {
1333
+ const args = node.args ? node.args : {};
1309
1334
  // Positional arguments
1310
1335
  if (Array.isArray(args))
1311
1336
  return args.map(arg => renderExpr(arg, env)).join(', ');
1312
1337
 
1313
1338
  // Named arguments (object/dict)
1314
1339
  else if (typeof args === 'object')
1315
- return Object.keys(args).map(key => `${quoteOrUppercaseId(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1340
+ // if this is a function param which is not a reference to the model, we must not quote it
1341
+ return Object.keys(args).map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1316
1342
 
1317
1343
 
1318
1344
  throw new Error(`Unknown args: ${JSON.stringify(args)}`);
@@ -1405,10 +1431,10 @@ function toHdbcdsSource(csn, options) {
1405
1431
  function renderAbsoluteNamePlain(absName, env) {
1406
1432
  // Add using declaration
1407
1433
  env.topLevelAliases[absName] = {
1408
- quotedName: uppercaseAndUnderscore(absName),
1409
- quotedAlias: uppercaseAndUnderscore(absName),
1434
+ quotedName: formatIdentifier(uppercaseAndUnderscore(absName)),
1435
+ quotedAlias: formatIdentifier(uppercaseAndUnderscore(absName)),
1410
1436
  };
1411
- return uppercaseAndUnderscore(absName);
1437
+ return formatIdentifier(uppercaseAndUnderscore(absName));
1412
1438
  }
1413
1439
 
1414
1440
  /**
@@ -1493,7 +1519,7 @@ function toHdbcdsSource(csn, options) {
1493
1519
  function renderUsings(artifactName, env) {
1494
1520
  const distinct = {};
1495
1521
  Object.keys(env.topLevelAliases)
1496
- .filter(name => !(plainNames && env.topLevelAliases[name].quotedName === uppercaseAndUnderscore(artifactName))) // avoid "using FOO as FOO" in FOO.cds
1522
+ .filter(name => env.topLevelAliases[name].quotedAlias !== formatIdentifier(uppercaseAndUnderscore(artifactName))) // avoid "using FOO as FOO" in FOO.cds
1497
1523
  .forEach((name) => {
1498
1524
  distinct[`using ${env.topLevelAliases[name].quotedName} as ${env.topLevelAliases[name].quotedAlias};\n`] = '';
1499
1525
  });
@@ -1635,7 +1661,16 @@ function toHdbcdsSource(csn, options) {
1635
1661
  return id;
1636
1662
 
1637
1663
 
1638
- return `"${id.replace(/"/g, '""')}"`;
1664
+ switch (options.forHana.names) {
1665
+ case 'plain':
1666
+ return smartId(id, 'hdbcds');
1667
+ case 'quoted':
1668
+ return delimitedId(id, 'hdbcds');
1669
+ case 'hdbcds':
1670
+ return delimitedId(id, 'hdbcds');
1671
+ default:
1672
+ return null;
1673
+ }
1639
1674
  }
1640
1675
 
1641
1676
  /*
@@ -1656,21 +1691,34 @@ function toHdbcdsSource(csn, options) {
1656
1691
  }
1657
1692
 
1658
1693
  /**
1659
- * Quote or uppercase an identifier 'id', depending on naming strategy
1694
+ * Quote and/or uppercase an identifier 'id', depending on naming strategy
1660
1695
  *
1661
1696
  * @param {string} id Identifier
1662
- * @param {CSN.Location} [location] Optional location for the warning.
1663
1697
  * @returns {string} Quoted/uppercased id
1664
1698
  */
1665
- function quoteOrUppercaseId(id, location = null) {
1666
- if (plainNames) {
1667
- const result = uppercaseAndUnderscore(id);
1668
- // Warn if colliding with HANA keyword
1669
- if (keywords.hana.includes(result))
1670
- warning(null, location, `The identifier "${id}" is a SAP HANA keyword`);
1699
+ function formatIdentifier(id) {
1700
+ id = options.forHana.names === 'plain' ? id.toUpperCase() : id;
1701
+ return quoteId(id);
1702
+ }
1703
+
1704
+ /**
1705
+ * Quote or uppercase a parameter identifier 'id', depending on naming strategy
1706
+ * Smart quoting cannot be applied to the parameter identifiers, issue warning instead.
1707
+ *
1708
+ *
1709
+ * @param {string} id Identifier
1710
+ * @param {CSN.Path} [location] Optional location for the warning.
1711
+ * @returns {string} Quoted/uppercased id
1712
+ */
1713
+ function formatParamIdentifier(id, location) {
1714
+ // Warn if colliding with HANA keyword, but do not quote for plain
1715
+ // --> quoted reserved words as param lead to a weird deployment error
1716
+ if (keywords.hdbcds.includes(uppercaseAndUnderscore(id)))
1717
+ warning(null, location, { id }, 'The identifier $(ID) is a SAP HANA keyword');
1718
+
1719
+ if (plainNames)
1720
+ return uppercaseAndUnderscore(id);
1671
1721
 
1672
- return result;
1673
- }
1674
1722
  return quoteId(id);
1675
1723
  }
1676
1724
 
@@ -1693,7 +1741,7 @@ function toHdbcdsSource(csn, options) {
1693
1741
  */
1694
1742
  function renderArtifactName(artifactName, env, fallthrough = false) {
1695
1743
  if (plainNames && !fallthrough)
1696
- return uppercaseAndUnderscore(artifactName);
1744
+ return formatIdentifier(uppercaseAndUnderscore(artifactName));
1697
1745
  // hdbcds with quoted or hdbcds naming
1698
1746
  return env.namePrefix + quoteId(getRealName(csn, artifactName).replace(/\./g, '_'));
1699
1747
  }