@sap/cds-compiler 2.4.4 → 2.10.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 (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. package/lib/utils/string.js +0 -17
@@ -3,11 +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,
7
7
  } = require('../model/csnUtils');
8
8
  const keywords = require('../base/keywords');
9
9
  const {
10
- renderFunc, processExprArray, getRealName, addContextMarkers, addIntermediateContexts, cdsToSqlTypes,
10
+ renderFunc, beautifyExprArray, getRealName, addContextMarkers, addIntermediateContexts, cdsToSqlTypes,
11
+ hasHanaComment, getHanaComment, findElement, funcWithoutParen,
11
12
  } = require('./utils/common');
12
13
  const {
13
14
  renderReferentialConstraint,
@@ -18,9 +19,21 @@ const { checkCSNVersion } = require('../json/csnVersion');
18
19
  const { makeMessageFunction } = require('../base/messages');
19
20
  const timetrace = require('../utils/timetrace');
20
21
 
22
+ const { smartId, delimitedId } = require('../sql-identifier');
23
+
21
24
  const $PROJECTION = '$projection';
22
25
  const $SELF = '$self';
23
26
 
27
+ /**
28
+ * Get the comment and in addition escape \n so HANA CDS can handle it.
29
+ *
30
+ * @param {CSN.Artifact} obj
31
+ * @returns {string}
32
+ */
33
+ function getEscapedHanaComment(obj) {
34
+ return getHanaComment(obj).replace(/\n/g, '\\n');
35
+ }
36
+
24
37
  /**
25
38
  * Render the CSN model 'model' to CDS source text. One source is created per
26
39
  * top-level artifact. Return a dictionary of top-level artifacts
@@ -28,8 +41,7 @@ const $SELF = '$self';
28
41
  * { "foo" : "using XY; context foo {...};",
29
42
  * "bar::wiz" : "namespace bar::; entity wiz {...};"
30
43
  * }
31
- * If 'options.toHana' is set, render HANA-ish source dialect (currently
32
- * only affects translation of '$self.foo' in paths and ::-ish namespace declarations)
44
+ *
33
45
  * FIXME: This comment no longer tells the whole truth
34
46
  *
35
47
  * @param {CSN.Model} csn HANA transformed CSN
@@ -38,9 +50,9 @@ const $SELF = '$self';
38
50
  */
39
51
  function toHdbcdsSource(csn, options) {
40
52
  timetrace.start('HDBCDS rendering');
41
- const plainNames = options.toHana && options.toHana.names === 'plain';
42
- const quotedNames = options.toHana && options.toHana.names === 'quoted';
43
- 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';
44
56
 
45
57
  const {
46
58
  info, warning, error, throwWithError,
@@ -51,7 +63,7 @@ function toHdbcdsSource(csn, options) {
51
63
  const result = Object.create(null);
52
64
 
53
65
 
54
- 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
55
67
 
56
68
  const killList = [];
57
69
  if (quotedNames)
@@ -378,7 +390,10 @@ function toHdbcdsSource(csn, options) {
378
390
  const childEnv = increaseIndent(env);
379
391
  const normalizedArtifactName = renderArtifactName(artifactName, env);
380
392
 
381
- globalDuplicateChecker.addArtifact(art['@cds.persistence.name'], art && art.$location, artifactName);
393
+ globalDuplicateChecker.addArtifact(art['@cds.persistence.name'], env.path, artifactName);
394
+
395
+ if (hasHanaComment(art, options))
396
+ result += `${env.indent}@Comment: '${getEscapedHanaComment(art)}'\n`;
382
397
 
383
398
  result += `${env.indent + (art.abstract ? 'abstract ' : '')}entity ${normalizedArtifactName}`;
384
399
  if (art.includes) {
@@ -387,7 +402,11 @@ function toHdbcdsSource(csn, options) {
387
402
  }
388
403
  result += ' {\n';
389
404
  const duplicateChecker = new DuplicateChecker(); // registry for all artifact names and element names
390
- 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);
391
410
  for (const name in art.elements)
392
411
  result += renderElement(name, art.elements[name], childEnv, duplicateChecker);
393
412
 
@@ -397,6 +416,33 @@ function toHdbcdsSource(csn, options) {
397
416
  return result;
398
417
  }
399
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
+
400
446
  /**
401
447
  * Render the 'technical configuration { ... }' section 'tc' of an entity.
402
448
  *
@@ -502,9 +548,13 @@ function toHdbcdsSource(csn, options) {
502
548
  // Special handling for HANA CDS: Must omit the ':' before anonymous structured types (for historical reasons)
503
549
  const omitColon = (!elm.type && elm.elements);
504
550
  let result = '';
505
- const quotedElementName = quoteOrUppercaseId(elementName, elm.$location);
551
+ const quotedElementName = formatIdentifier(elementName);
506
552
  if (duplicateChecker)
507
- duplicateChecker.addElement(quotedElementName, elm && elm.$location, elementName);
553
+ duplicateChecker.addElement(quotedElementName, env.path, elementName);
554
+
555
+ if (hasHanaComment(elm, options))
556
+ result += `${env.indent}@Comment: '${getEscapedHanaComment(elm)}'\n`;
557
+
508
558
  result += env.indent + (elm.key && !isSubElement ? 'key ' : '') +
509
559
  (elm.masked ? 'masked ' : '') +
510
560
  quotedElementName + (omitColon ? ' ' : ' : ') +
@@ -530,7 +580,7 @@ function toHdbcdsSource(csn, options) {
530
580
  if (source.SELECT || source.SET) {
531
581
  let result = `(${renderQuery(source, false, increaseIndent(env))})`;
532
582
  if (source.as)
533
- result += ` as ${quoteOrUppercaseId(source.as)}`;
583
+ result += ` as ${formatIdentifier(source.as)}`;
534
584
 
535
585
  return result;
536
586
  }
@@ -606,14 +656,14 @@ function toHdbcdsSource(csn, options) {
606
656
  function renderAbsolutePathWithAlias(path, env) {
607
657
  let result = renderAbsolutePath(path, env);
608
658
  // Take care of aliases - for artifact references, use the resulting name (multi-dot joined with _)
609
- 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);
610
660
  if (path.as) {
611
661
  // Source had an alias - render it
612
- result += ` as ${quoteOrUppercaseId(path.as)}`;
662
+ result += ` as ${formatIdentifier(path.as)}`;
613
663
  }
614
- else if (getLastPartOf(result) !== quoteOrUppercaseId(implicitAlias)) {
664
+ else if (getLastPartOf(result) !== formatIdentifier(implicitAlias)) {
615
665
  // Render an artificial alias if the result would produce a different one
616
- result += ` as ${quoteOrUppercaseId(implicitAlias)}`;
666
+ result += ` as ${formatIdentifier(implicitAlias)}`;
617
667
  }
618
668
  return result;
619
669
  }
@@ -625,19 +675,19 @@ function toHdbcdsSource(csn, options) {
625
675
  *
626
676
  * @param {object} col Column to render
627
677
  * @param {CdlRenderEnvironment} env Environment
678
+ * @param {CSN.Element} [element] Element (non-enum from subquery possibly) corresponding to the column ref
628
679
  * @returns {string} Rendered column
629
680
  */
630
- function renderViewColumn(col, env) {
681
+ function renderViewColumn(col, env, element) {
631
682
  // Annotations and column
632
- let result = '';
683
+ let result = element && hasHanaComment(element, options) ? `${env.indent}@Comment: '${getEscapedHanaComment(element)}'\n` : '';
633
684
 
634
685
  const leaf = col.as || col.ref && col.ref[col.ref.length - 1];
635
- const element = env._artifact.elements[leaf];
636
686
  // Render 'null as <alias>' only for database and if element is virtual
637
687
 
638
- if (element && element.virtual) {
688
+ if (element && element.virtual || env._artifact.elements[leaf] && env._artifact.elements[leaf].virtual) {
639
689
  if (isDeprecatedEnabled(options, 'renderVirtualElements'))
640
- return `${result}${env.indent}null as ${quoteOrUppercaseId(leaf)}`;
690
+ return `${result}${env.indent}null as ${formatIdentifier(leaf)}`;
641
691
  }
642
692
  else {
643
693
  return renderNonVirtualColumn();
@@ -652,7 +702,7 @@ function toHdbcdsSource(csn, options) {
652
702
  result += 'virtual ';
653
703
  // If key is explicitly set in a non-leading query, issue an error.
654
704
  if (col.key && env.skipKeys)
655
- error(null, env.path, 'KEY must only be added in the leading query');
705
+ error(null, env.path, { tokensymbol: 'key', $reviewed: true }, 'Unexpected $(TOKENSYMBOL) in subquery');
656
706
 
657
707
  const key = (!env.skipKeys && (col.key || (element && element.key)) ? 'key ' : '');
658
708
  result += key + renderExpr(col, env, true);
@@ -665,7 +715,7 @@ function toHdbcdsSource(csn, options) {
665
715
  alias = leaf;
666
716
 
667
717
  if (alias)
668
- result += ` as ${quoteOrUppercaseId(alias)}`;
718
+ result += ` as ${formatIdentifier(alias)}`;
669
719
 
670
720
  // Explicit type provided for the view element?
671
721
  if (col.cast && col.cast.target) {
@@ -692,7 +742,12 @@ function toHdbcdsSource(csn, options) {
692
742
  */
693
743
  function renderView(artifactName, art, env) {
694
744
  let result = '';
695
- globalDuplicateChecker.addArtifact(art['@cds.persistence.name'], art.$location, artifactName);
745
+ const artifactPath = [ 'definitions', artifactName ];
746
+ globalDuplicateChecker.addArtifact(art['@cds.persistence.name'], artifactPath, artifactName);
747
+
748
+ if (hasHanaComment(art, options))
749
+ result += `${env.indent}@Comment: '${getEscapedHanaComment(art)}'\n`;
750
+
696
751
  result += `${env.indent}${art.abstract ? 'abstract ' : ''}view ${renderArtifactName(artifactName, env)}`;
697
752
  if (art.params) {
698
753
  const childEnv = increaseIndent(env);
@@ -704,7 +759,7 @@ function toHdbcdsSource(csn, options) {
704
759
  result += ' as ';
705
760
  }
706
761
  env._artifact = art;
707
- result += renderQuery(getNormalizedQuery(art).query, true, env, [ 'definitions', artifactName, 'query' ]);
762
+ result += renderQuery(getNormalizedQuery(art).query, true, env, artifactPath.concat(art.projection ? 'projection' : 'query'), art.elements);
708
763
  result += ';\n';
709
764
  return result;
710
765
  }
@@ -719,15 +774,16 @@ function toHdbcdsSource(csn, options) {
719
774
  * @param {boolean} isLeadingQuery Wether the query is the leading query or not
720
775
  * @param {CdlRenderEnvironment} env Environment
721
776
  * @param {CSN.Path} [path=[]] CSN path to the query
777
+ * @param {object} [elements] For leading query, the elements of the artifact
722
778
  * @returns {string} The rendered query
723
779
  */
724
- function renderQuery(query, isLeadingQuery, env, path = []) {
780
+ function renderQuery(query, isLeadingQuery, env, path = [], elements) {
725
781
  let result = '';
726
782
  env.skipKeys = !isLeadingQuery;
727
783
  // Set operator, like UNION, INTERSECT, ...
728
784
  if (query.SET) {
729
785
  // First arg may be leading query
730
- result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env, path.concat([ 'SET', 'args', 0 ]))}`;
786
+ result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env, path.concat([ 'SET', 'args', 0 ]), elements)}`;
731
787
  // FIXME: Clarify if set operators can be n-ary (assuming binary here)
732
788
  if (query.SET.op) {
733
789
  // Loop over all other arguments, i.e. for A UNION B UNION C UNION D ...
@@ -753,7 +809,6 @@ function toHdbcdsSource(csn, options) {
753
809
  const childEnv = increaseIndent(env);
754
810
  childEnv.currentArtifactName = $PROJECTION; // $self to be replaced by $projection
755
811
  result += `select from ${renderViewSource(select.from, env)}`;
756
-
757
812
  if (select.mixin) {
758
813
  let elems = '';
759
814
  for (const name in select.mixin)
@@ -768,13 +823,13 @@ function toHdbcdsSource(csn, options) {
768
823
  result += select.distinct ? ' distinct' : '';
769
824
  if (select.columns) {
770
825
  result += ' {\n';
771
- result += `${select.columns.map(col => renderViewColumn(col, childEnv))
826
+ result += `${select.columns.map(col => renderViewColumn(col, childEnv, findElement(elements, col)))
772
827
  .filter(s => s !== '')
773
828
  .join(',\n')}\n`;
774
829
  result += `${env.indent}}`;
775
830
  }
776
831
  if (select.excluding) {
777
- 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`;
778
833
  result += `${env.indent}}`;
779
834
  }
780
835
 
@@ -872,7 +927,7 @@ function toHdbcdsSource(csn, options) {
872
927
  function renderParameter(parName, par, env) {
873
928
  if (par.notNull === true || par.notNull === false)
874
929
  info(null, env.path.concat([ 'params', parName ]), 'Not Null constraints on HDBCDS view parameters are not allowed and are ignored');
875
- return `${env.indent + quoteOrUppercaseId(parName)} : ${renderTypeReference(par, env)}`;
930
+ return `${env.indent + formatParamIdentifier(parName, env.path.concat([ 'params', parName ]))} : ${renderTypeReference(par, env)}`;
876
931
  }
877
932
 
878
933
  /**
@@ -989,10 +1044,24 @@ function toHdbcdsSource(csn, options) {
989
1044
 
990
1045
  result += `${renderCardinality(elm.cardinality)} to `;
991
1046
 
1047
+
992
1048
  // normal target or named aspect
993
1049
  if (elm.target || elm.targetAspect && typeof elm.targetAspect === 'string') {
994
- result += plainNames ? renderAbsoluteNamePlain(elm.target || elm.targetAspect, env)
995
- : renderAbsoluteNameWithQuotes(elm.target || elm.targetAspect, env);
1050
+ // we might have a "using target as __target"
1051
+ const targetArtifact = csn.definitions[elm.target];
1052
+ const targetAlias = env.topLevelAliases[targetArtifact['@cds.persistence.name']];
1053
+ if (targetAlias) {
1054
+ result += targetAlias.quotedAlias;
1055
+ }
1056
+ else {
1057
+ result += plainNames ? renderAbsoluteNamePlain(elm.target || elm.targetAspect, env)
1058
+ : renderAbsoluteNameWithQuotes(elm.target || elm.targetAspect, env);
1059
+ }
1060
+ }
1061
+
1062
+ // ON-condition (if any)
1063
+ if (elm.on) {
1064
+ result += ` on ${renderExpr(elm.on, env, true, true)}`;
996
1065
  }
997
1066
  else if (elm.targetAspect && elm.targetAspect.elements) { // anonymous aspect
998
1067
  const childEnv = increaseIndent(env);
@@ -1004,11 +1073,6 @@ function toHdbcdsSource(csn, options) {
1004
1073
  }
1005
1074
 
1006
1075
 
1007
- // ON-condition (if any)
1008
- if (elm.on)
1009
- result += ` on ${renderExpr(elm.on, env, true, true)}`;
1010
-
1011
-
1012
1076
  // Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
1013
1077
  if (elm.keys && !elm.on)
1014
1078
  result += ` { ${Object.keys(elm.keys).map(name => renderForeignKey(elm.keys[name], env)).join(', ')} }`;
@@ -1064,7 +1128,7 @@ function toHdbcdsSource(csn, options) {
1064
1128
  function renderExpr(x, env, inline = true, inExpr = false) {
1065
1129
  // Compound expression
1066
1130
  if (Array.isArray(x))
1067
- return processExprArray(x, renderExpr, env, inline, inExpr);
1131
+ return beautifyExprArray(x.map(item => renderExpr(item, env, inline, inExpr)));
1068
1132
 
1069
1133
  if (typeof x === 'object' && x !== null) {
1070
1134
  if (inExpr && x.cast && x.cast.type)
@@ -1106,6 +1170,9 @@ function toHdbcdsSource(csn, options) {
1106
1170
 
1107
1171
  const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
1108
1172
  const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
1173
+ // we can't quote functions with parens, issue warning if it is a reserved keyword
1174
+ if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
1175
+ warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
1109
1176
  return renderFunc( funcName, x, 'hana', a => renderArgs(a, '=>', env) );
1110
1177
  }
1111
1178
  // Nested expression
@@ -1165,11 +1232,11 @@ function toHdbcdsSource(csn, options) {
1165
1232
  if (x.ref[0] === '$user') {
1166
1233
  // FIXME: this is all not enough: we might need an explicit select item alias
1167
1234
  if (x.ref[1] === 'id') {
1168
- if (options.toHana.user && typeof options.toHana.user === 'string' || options.toHana.user instanceof String)
1169
- return `'${options.toHana.user}'`;
1235
+ if (options.magicVars && options.magicVars.user && (typeof options.magicVars.user === 'string' || options.magicVars.user instanceof String))
1236
+ return `'${options.magicVars.user}'`;
1170
1237
 
1171
- else if ((options.toHana.user && options.toHana.user.id) && (typeof options.toHana.user.id === 'string' || options.toHana.user.id instanceof String))
1172
- return `'${options.toHana.user.id}'`;
1238
+ else if ((options.magicVars && options.magicVars.user && options.magicVars.user.id) && (typeof options.magicVars.user.id === 'string' || options.magicVars.user.id instanceof String))
1239
+ return `'${options.magicVars.user.id}'`;
1173
1240
 
1174
1241
  return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1175
1242
  }
@@ -1244,7 +1311,7 @@ function toHdbcdsSource(csn, options) {
1244
1311
  [ $SELF, $PROJECTION, '$session' ].includes(s))
1245
1312
  return s;
1246
1313
 
1247
- return quoteOrUppercaseId(s);
1314
+ return formatIdentifier(s);
1248
1315
  }
1249
1316
  // ID with filters or parameters
1250
1317
  else if (typeof s === 'object') {
@@ -1257,7 +1324,7 @@ function toHdbcdsSource(csn, options) {
1257
1324
  return `${s.func}(${renderArgs(s.args, '=>', env)})`;
1258
1325
 
1259
1326
  // Path step, possibly with view parameters and/or filters
1260
- let result = `${quoteOrUppercaseId(s.id)}`;
1327
+ let result = `${formatIdentifier(s.id)}`;
1261
1328
  if (s.args) {
1262
1329
  // View parameters
1263
1330
  result += `(${renderArgs(s.args, ':', env)})`;
@@ -1289,7 +1356,7 @@ function toHdbcdsSource(csn, options) {
1289
1356
 
1290
1357
  // Named arguments (object/dict)
1291
1358
  else if (typeof args === 'object')
1292
- return Object.keys(args).map(key => `${quoteOrUppercaseId(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1359
+ return Object.keys(args).map(key => `${formatIdentifier(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1293
1360
 
1294
1361
 
1295
1362
  throw new Error(`Unknown args: ${JSON.stringify(args)}`);
@@ -1382,10 +1449,10 @@ function toHdbcdsSource(csn, options) {
1382
1449
  function renderAbsoluteNamePlain(absName, env) {
1383
1450
  // Add using declaration
1384
1451
  env.topLevelAliases[absName] = {
1385
- quotedName: uppercaseAndUnderscore(absName),
1386
- quotedAlias: uppercaseAndUnderscore(absName),
1452
+ quotedName: formatIdentifier(uppercaseAndUnderscore(absName)),
1453
+ quotedAlias: formatIdentifier(uppercaseAndUnderscore(absName)),
1387
1454
  };
1388
- return uppercaseAndUnderscore(absName);
1455
+ return formatIdentifier(uppercaseAndUnderscore(absName));
1389
1456
  }
1390
1457
 
1391
1458
  /**
@@ -1470,7 +1537,7 @@ function toHdbcdsSource(csn, options) {
1470
1537
  function renderUsings(artifactName, env) {
1471
1538
  const distinct = {};
1472
1539
  Object.keys(env.topLevelAliases)
1473
- .filter(name => !(plainNames && env.topLevelAliases[name].quotedName === uppercaseAndUnderscore(artifactName))) // avoid "using FOO as FOO" in FOO.cds
1540
+ .filter(name => env.topLevelAliases[name].quotedAlias !== formatIdentifier(uppercaseAndUnderscore(artifactName))) // avoid "using FOO as FOO" in FOO.cds
1474
1541
  .forEach((name) => {
1475
1542
  distinct[`using ${env.topLevelAliases[name].quotedName} as ${env.topLevelAliases[name].quotedAlias};\n`] = '';
1476
1543
  });
@@ -1541,6 +1608,8 @@ function toHdbcdsSource(csn, options) {
1541
1608
  topLevelAliases: Object.create(null),
1542
1609
  // Current name prefix (including trailing dot if not empty)
1543
1610
  namePrefix: '',
1611
+ // CSN path - should at least point to the correct artifact
1612
+ path: [],
1544
1613
  };
1545
1614
  }
1546
1615
 
@@ -1585,7 +1654,7 @@ function toHdbcdsSource(csn, options) {
1585
1654
  */
1586
1655
  function quoteAbsolutePathString(abspath) {
1587
1656
  const namespace = getNamespace(csn, abspath);
1588
- const resultingName = getResultingName(csn, options.toHana.names, abspath);
1657
+ const resultingName = getResultingName(csn, options.sqlMapping, abspath);
1589
1658
 
1590
1659
  if (hdbcdsNames && namespace)
1591
1660
  return `${quotePathString(namespace)}::${quotePathString(resultingName.slice(namespace.length + 2))}`;
@@ -1610,10 +1679,19 @@ function toHdbcdsSource(csn, options) {
1610
1679
  return id;
1611
1680
 
1612
1681
 
1613
- return `"${id.replace(/"/g, '""')}"`;
1682
+ switch (options.forHana.names) {
1683
+ case 'plain':
1684
+ return smartId(id, 'hdbcds');
1685
+ case 'quoted':
1686
+ return delimitedId(id, 'hdbcds');
1687
+ case 'hdbcds':
1688
+ return delimitedId(id, 'hdbcds');
1689
+ default:
1690
+ return null;
1691
+ }
1614
1692
  }
1615
1693
 
1616
- /**
1694
+ /*
1617
1695
  * Return an absolute name 'absname', with '::' inserted if required by naming strategy 'hdbcds', quoted
1618
1696
  * as if it was a single identifier (required only for native USINGs)
1619
1697
  *
@@ -1631,21 +1709,34 @@ function toHdbcdsSource(csn, options) {
1631
1709
  }
1632
1710
 
1633
1711
  /**
1634
- * Quote or uppercase an identifier 'id', depending on naming strategy
1712
+ * Quote and/or uppercase an identifier 'id', depending on naming strategy
1635
1713
  *
1636
1714
  * @param {string} id Identifier
1637
- * @param {CSN.Location} [location] Optional location for the warning.
1638
1715
  * @returns {string} Quoted/uppercased id
1639
1716
  */
1640
- function quoteOrUppercaseId(id, location = null) {
1641
- if (plainNames) {
1642
- const result = uppercaseAndUnderscore(id);
1643
- // Warn if colliding with HANA keyword
1644
- if (keywords.hana.includes(result))
1645
- warning(null, location, `The identifier "${id}" is a SAP HANA keyword`);
1717
+ function formatIdentifier(id) {
1718
+ id = options.forHana.names === 'plain' ? id.toUpperCase() : id;
1719
+ return quoteId(id);
1720
+ }
1721
+
1722
+ /**
1723
+ * Quote or uppercase a parameter identifier 'id', depending on naming strategy
1724
+ * Smart quoting cannot be applied to the parameter identifiers, issue warning instead.
1725
+ *
1726
+ *
1727
+ * @param {string} id Identifier
1728
+ * @param {CSN.Path} [location] Optional location for the warning.
1729
+ * @returns {string} Quoted/uppercased id
1730
+ */
1731
+ function formatParamIdentifier(id, location) {
1732
+ // Warn if colliding with HANA keyword, but do not quote for plain
1733
+ // --> quoted reserved words as param lead to a weird deployment error
1734
+ if (keywords.hdbcds.includes(uppercaseAndUnderscore(id)))
1735
+ warning(null, location, { id }, 'The identifier $(ID) is a SAP HANA keyword');
1736
+
1737
+ if (plainNames)
1738
+ return uppercaseAndUnderscore(id);
1646
1739
 
1647
- return result;
1648
- }
1649
1740
  return quoteId(id);
1650
1741
  }
1651
1742
 
@@ -1668,7 +1759,7 @@ function toHdbcdsSource(csn, options) {
1668
1759
  */
1669
1760
  function renderArtifactName(artifactName, env, fallthrough = false) {
1670
1761
  if (plainNames && !fallthrough)
1671
- return uppercaseAndUnderscore(artifactName);
1762
+ return formatIdentifier(uppercaseAndUnderscore(artifactName));
1672
1763
  // hdbcds with quoted or hdbcds naming
1673
1764
  return env.namePrefix + quoteId(getRealName(csn, artifactName).replace(/\./g, '_'));
1674
1765
  }