@sap/cds-compiler 2.5.2 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. package/lib/utils/string.js +0 -17
@@ -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';
@@ -39,8 +41,7 @@ function getEscapedHanaComment(obj) {
39
41
  * { "foo" : "using XY; context foo {...};",
40
42
  * "bar::wiz" : "namespace bar::; entity wiz {...};"
41
43
  * }
42
- * If 'options.toHana' is set, render HANA-ish source dialect (currently
43
- * only affects translation of '$self.foo' in paths and ::-ish namespace declarations)
44
+ *
44
45
  * FIXME: This comment no longer tells the whole truth
45
46
  *
46
47
  * @param {CSN.Model} csn HANA transformed CSN
@@ -49,9 +50,9 @@ function getEscapedHanaComment(obj) {
49
50
  */
50
51
  function toHdbcdsSource(csn, options) {
51
52
  timetrace.start('HDBCDS rendering');
52
- const plainNames = options.toHana && options.toHana.names === 'plain';
53
- const quotedNames = options.toHana && options.toHana.names === 'quoted';
54
- const hdbcdsNames = options.toHana && options.toHana.names === 'hdbcds';
53
+ const plainNames = options.sqlMapping === 'plain';
54
+ const quotedNames = options.sqlMapping === 'quoted';
55
+ const hdbcdsNames = options.sqlMapping === 'hdbcds';
55
56
 
56
57
  const {
57
58
  info, warning, error, throwWithError,
@@ -62,7 +63,7 @@ function toHdbcdsSource(csn, options) {
62
63
  const result = Object.create(null);
63
64
 
64
65
 
65
- const globalDuplicateChecker = new DuplicateChecker(options.toHana.names); // registry for all artifact names and element names
66
+ const globalDuplicateChecker = new DuplicateChecker(options.sqlMapping); // registry for all artifact names and element names
66
67
 
67
68
  const killList = [];
68
69
  if (quotedNames)
@@ -389,7 +390,7 @@ function toHdbcdsSource(csn, options) {
389
390
  const childEnv = increaseIndent(env);
390
391
  const normalizedArtifactName = renderArtifactName(artifactName, env);
391
392
 
392
- globalDuplicateChecker.addArtifact(art['@cds.persistence.name'], art && art.$location, artifactName);
393
+ globalDuplicateChecker.addArtifact(art['@cds.persistence.name'], env.path, artifactName);
393
394
 
394
395
  if (hasHanaComment(art, options))
395
396
  result += `${env.indent}@Comment: '${getEscapedHanaComment(art)}'\n`;
@@ -401,7 +402,11 @@ function toHdbcdsSource(csn, options) {
401
402
  }
402
403
  result += ' {\n';
403
404
  const duplicateChecker = new DuplicateChecker(); // registry for all artifact names and element names
404
- duplicateChecker.addArtifact(artifactName, art && art.$location, artifactName);
405
+ duplicateChecker.addArtifact(artifactName, env.path, artifactName);
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,9 +548,9 @@ 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, elm.$location);
551
+ const quotedElementName = formatIdentifier(elementName);
520
552
  if (duplicateChecker)
521
- duplicateChecker.addElement(quotedElementName, elm && elm.$location, elementName);
553
+ duplicateChecker.addElement(quotedElementName, env.path, elementName);
522
554
 
523
555
  if (hasHanaComment(elm, options))
524
556
  result += `${env.indent}@Comment: '${getEscapedHanaComment(elm)}'\n`;
@@ -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)}`;
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)}]`;
@@ -624,14 +656,14 @@ function toHdbcdsSource(csn, options) {
624
656
  function renderAbsolutePathWithAlias(path, env) {
625
657
  let result = renderAbsolutePath(path, env);
626
658
  // Take care of aliases - for artifact references, use the resulting name (multi-dot joined with _)
627
- const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.toHana.names, path.ref[0])) : getLastPartOfRef(path.ref);
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)}`;
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)}`;
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)}`;
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)}`;
718
+ result += ` as ${formatIdentifier(alias)}`;
687
719
 
688
720
  // Explicit type provided for the view element?
689
721
  if (col.cast && col.cast.target) {
@@ -710,7 +742,8 @@ function toHdbcdsSource(csn, options) {
710
742
  */
711
743
  function renderView(artifactName, art, env) {
712
744
  let result = '';
713
- globalDuplicateChecker.addArtifact(art['@cds.persistence.name'], art.$location, artifactName);
745
+ const artifactPath = [ 'definitions', artifactName ];
746
+ globalDuplicateChecker.addArtifact(art['@cds.persistence.name'], artifactPath, artifactName);
714
747
 
715
748
  if (hasHanaComment(art, options))
716
749
  result += `${env.indent}@Comment: '${getEscapedHanaComment(art)}'\n`;
@@ -726,7 +759,7 @@ function toHdbcdsSource(csn, options) {
726
759
  result += ' as ';
727
760
  }
728
761
  env._artifact = art;
729
- result += renderQuery(getNormalizedQuery(art).query, true, env, [ 'definitions', artifactName, 'query' ], art.elements);
762
+ result += renderQuery(getNormalizedQuery(art).query, true, env, artifactPath.concat(art.projection ? 'projection' : 'query'), art.elements);
730
763
  result += ';\n';
731
764
  return result;
732
765
  }
@@ -776,7 +809,6 @@ function toHdbcdsSource(csn, options) {
776
809
  const childEnv = increaseIndent(env);
777
810
  childEnv.currentArtifactName = $PROJECTION; // $self to be replaced by $projection
778
811
  result += `select from ${renderViewSource(select.from, env)}`;
779
-
780
812
  if (select.mixin) {
781
813
  let elems = '';
782
814
  for (const name in select.mixin)
@@ -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)}`).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)} : ${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,24 @@ 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
+ }
1053
+ }
1054
+
1055
+ // ON-condition (if any)
1056
+ if (elm.on) {
1057
+ result += ` on ${renderExpr(elm.on, env, true, true)}`;
1019
1058
  }
1020
1059
  else if (elm.targetAspect && elm.targetAspect.elements) { // anonymous aspect
1021
1060
  const childEnv = increaseIndent(env);
@@ -1027,11 +1066,6 @@ function toHdbcdsSource(csn, options) {
1027
1066
  }
1028
1067
 
1029
1068
 
1030
- // ON-condition (if any)
1031
- if (elm.on)
1032
- result += ` on ${renderExpr(elm.on, env, true, true)}`;
1033
-
1034
-
1035
1069
  // Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
1036
1070
  if (elm.keys && !elm.on)
1037
1071
  result += ` { ${Object.keys(elm.keys).map(name => renderForeignKey(elm.keys[name], env)).join(', ')} }`;
@@ -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,14 +1201,19 @@ 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
- if (options.toHana.user && typeof options.toHana.user === 'string' || options.toHana.user instanceof String)
1192
- return `'${options.toHana.user}'`;
1212
+ if (options.magicVars && options.magicVars.user && (typeof options.magicVars.user === 'string' || options.magicVars.user instanceof String))
1213
+ return `'${options.magicVars.user}'`;
1193
1214
 
1194
- else if ((options.toHana.user && options.toHana.user.id) && (typeof options.toHana.user.id === 'string' || options.toHana.user.id instanceof String))
1195
- return `'${options.toHana.user.id}'`;
1215
+ else if ((options.magicVars && options.magicVars.user && options.magicVars.user.id) && (typeof options.magicVars.user.id === 'string' || options.magicVars.user.id instanceof String))
1216
+ return `'${options.magicVars.user.id}'`;
1196
1217
 
1197
1218
  return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1198
1219
  }
@@ -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);
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
  });
@@ -1564,6 +1590,8 @@ function toHdbcdsSource(csn, options) {
1564
1590
  topLevelAliases: Object.create(null),
1565
1591
  // Current name prefix (including trailing dot if not empty)
1566
1592
  namePrefix: '',
1593
+ // CSN path - should at least point to the correct artifact
1594
+ path: [],
1567
1595
  };
1568
1596
  }
1569
1597
 
@@ -1608,7 +1636,7 @@ function toHdbcdsSource(csn, options) {
1608
1636
  */
1609
1637
  function quoteAbsolutePathString(abspath) {
1610
1638
  const namespace = getNamespace(csn, abspath);
1611
- const resultingName = getResultingName(csn, options.toHana.names, abspath);
1639
+ const resultingName = getResultingName(csn, options.sqlMapping, abspath);
1612
1640
 
1613
1641
  if (hdbcdsNames && namespace)
1614
1642
  return `${quotePathString(namespace)}::${quotePathString(resultingName.slice(namespace.length + 2))}`;
@@ -1633,10 +1661,19 @@ function toHdbcdsSource(csn, options) {
1633
1661
  return id;
1634
1662
 
1635
1663
 
1636
- 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
+ }
1637
1674
  }
1638
1675
 
1639
- /**
1676
+ /*
1640
1677
  * Return an absolute name 'absname', with '::' inserted if required by naming strategy 'hdbcds', quoted
1641
1678
  * as if it was a single identifier (required only for native USINGs)
1642
1679
  *
@@ -1654,21 +1691,34 @@ function toHdbcdsSource(csn, options) {
1654
1691
  }
1655
1692
 
1656
1693
  /**
1657
- * Quote or uppercase an identifier 'id', depending on naming strategy
1694
+ * Quote and/or uppercase an identifier 'id', depending on naming strategy
1658
1695
  *
1659
1696
  * @param {string} id Identifier
1660
- * @param {CSN.Location} [location] Optional location for the warning.
1661
1697
  * @returns {string} Quoted/uppercased id
1662
1698
  */
1663
- function quoteOrUppercaseId(id, location = null) {
1664
- if (plainNames) {
1665
- const result = uppercaseAndUnderscore(id);
1666
- // Warn if colliding with HANA keyword
1667
- if (keywords.hana.includes(result))
1668
- 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);
1669
1721
 
1670
- return result;
1671
- }
1672
1722
  return quoteId(id);
1673
1723
  }
1674
1724
 
@@ -1691,7 +1741,7 @@ function toHdbcdsSource(csn, options) {
1691
1741
  */
1692
1742
  function renderArtifactName(artifactName, env, fallthrough = false) {
1693
1743
  if (plainNames && !fallthrough)
1694
- return uppercaseAndUnderscore(artifactName);
1744
+ return formatIdentifier(uppercaseAndUnderscore(artifactName));
1695
1745
  // hdbcds with quoted or hdbcds naming
1696
1746
  return env.namePrefix + quoteId(getRealName(csn, artifactName).replace(/\./g, '_'));
1697
1747
  }