@sap/cds-compiler 2.10.4 → 2.12.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 (103) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +58 -35
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +16 -0
  9. package/lib/api/.eslintrc.json +2 -0
  10. package/lib/api/main.js +10 -36
  11. package/lib/api/options.js +17 -8
  12. package/lib/api/validate.js +30 -3
  13. package/lib/backends.js +12 -13
  14. package/lib/base/dictionaries.js +2 -1
  15. package/lib/base/keywords.js +3 -2
  16. package/lib/base/message-registry.js +64 -11
  17. package/lib/base/messages.js +38 -18
  18. package/lib/base/model.js +6 -4
  19. package/lib/base/optionProcessorHelper.js +148 -86
  20. package/lib/checks/.eslintrc.json +2 -0
  21. package/lib/checks/actionsFunctions.js +2 -1
  22. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  23. package/lib/checks/foreignKeys.js +4 -4
  24. package/lib/checks/managedInType.js +4 -4
  25. package/lib/checks/queryNoDbArtifacts.js +1 -3
  26. package/lib/checks/selectItems.js +4 -0
  27. package/lib/checks/sql-snippets.js +93 -0
  28. package/lib/checks/unknownMagic.js +6 -3
  29. package/lib/checks/validator.js +8 -0
  30. package/lib/compiler/assert-consistency.js +14 -5
  31. package/lib/compiler/base.js +64 -0
  32. package/lib/compiler/builtins.js +62 -16
  33. package/lib/compiler/checks.js +34 -10
  34. package/lib/compiler/definer.js +91 -112
  35. package/lib/compiler/index.js +30 -30
  36. package/lib/compiler/propagator.js +8 -4
  37. package/lib/compiler/resolver.js +279 -63
  38. package/lib/compiler/shared.js +65 -230
  39. package/lib/compiler/utils.js +191 -0
  40. package/lib/edm/annotations/genericTranslation.js +35 -18
  41. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  42. package/lib/edm/csn2edm.js +4 -3
  43. package/lib/edm/edm.js +8 -8
  44. package/lib/edm/edmPreprocessor.js +61 -59
  45. package/lib/edm/edmUtils.js +14 -15
  46. package/lib/gen/Dictionary.json +82 -40
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +19 -1
  49. package/lib/gen/language.tokens +80 -73
  50. package/lib/gen/languageLexer.interp +27 -1
  51. package/lib/gen/languageLexer.js +925 -826
  52. package/lib/gen/languageLexer.tokens +72 -65
  53. package/lib/gen/languageParser.js +4817 -4102
  54. package/lib/json/from-csn.js +57 -26
  55. package/lib/json/to-csn.js +244 -51
  56. package/lib/language/antlrParser.js +12 -1
  57. package/lib/language/docCommentParser.js +1 -1
  58. package/lib/language/errorStrategy.js +26 -8
  59. package/lib/language/genericAntlrParser.js +106 -30
  60. package/lib/language/language.g4 +200 -70
  61. package/lib/language/multiLineStringParser.js +536 -0
  62. package/lib/main.d.ts +220 -21
  63. package/lib/main.js +6 -3
  64. package/lib/model/api.js +2 -2
  65. package/lib/model/csnRefs.js +218 -86
  66. package/lib/model/csnUtils.js +99 -178
  67. package/lib/model/enrichCsn.js +84 -43
  68. package/lib/model/revealInternalProperties.js +25 -8
  69. package/lib/model/sortViews.js +8 -1
  70. package/lib/modelCompare/compare.js +2 -1
  71. package/lib/optionProcessor.js +33 -18
  72. package/lib/render/.eslintrc.json +1 -2
  73. package/lib/render/DuplicateChecker.js +2 -2
  74. package/lib/render/manageConstraints.js +1 -1
  75. package/lib/render/toCdl.js +202 -82
  76. package/lib/render/toHdbcds.js +194 -135
  77. package/lib/render/toRename.js +7 -10
  78. package/lib/render/toSql.js +91 -51
  79. package/lib/render/utils/common.js +24 -5
  80. package/lib/render/utils/sql.js +6 -4
  81. package/lib/transform/braceExpression.js +4 -2
  82. package/lib/transform/db/applyTransformations.js +189 -0
  83. package/lib/transform/db/associations.js +389 -0
  84. package/lib/transform/db/cdsPersistence.js +150 -0
  85. package/lib/transform/db/constraints.js +275 -119
  86. package/lib/transform/db/draft.js +6 -4
  87. package/lib/transform/db/expansion.js +10 -9
  88. package/lib/transform/db/flattening.js +23 -8
  89. package/lib/transform/db/temporal.js +236 -0
  90. package/lib/transform/db/transformExists.js +106 -25
  91. package/lib/transform/db/views.js +485 -0
  92. package/lib/transform/forHanaNew.js +90 -1036
  93. package/lib/transform/forOdataNew.js +11 -3
  94. package/lib/transform/localized.js +5 -14
  95. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  96. package/lib/transform/transformUtilsNew.js +34 -20
  97. package/lib/transform/translateAssocsToJoins.js +15 -23
  98. package/lib/transform/universalCsnEnricher.js +217 -47
  99. package/lib/utils/file.js +13 -6
  100. package/lib/utils/term.js +65 -42
  101. package/lib/utils/timetrace.js +55 -27
  102. package/package.json +1 -1
  103. package/lib/transform/db/helpers.js +0 -58
@@ -1,14 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- getParentNameOf, getLastPartOf, getLastPartOfRef,
4
+ getLastPartOf, getLastPartOfRef,
5
5
  hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
6
- getRootArtifactName, getResultingName, getNamespace, forEachMember,
6
+ getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement, hasAnnotationValue,
7
7
  } = require('../model/csnUtils');
8
8
  const keywords = require('../base/keywords');
9
9
  const {
10
10
  renderFunc, beautifyExprArray, getRealName, addContextMarkers, addIntermediateContexts, cdsToSqlTypes,
11
- hasHanaComment, getHanaComment, findElement, funcWithoutParen,
11
+ hasHanaComment, getHanaComment, findElement, funcWithoutParen, getSqlSnippets,
12
12
  } = require('./utils/common');
13
13
  const {
14
14
  renderReferentialConstraint,
@@ -17,7 +17,7 @@ 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
21
 
22
22
  const { smartId, delimitedId } = require('../sql-identifier');
23
23
 
@@ -60,8 +60,7 @@ function toHdbcdsSource(csn, options) {
60
60
 
61
61
  checkCSNVersion(csn, options);
62
62
 
63
- const result = Object.create(null);
64
-
63
+ const hdbcdsResult = Object.create(null);
65
64
 
66
65
  const globalDuplicateChecker = new DuplicateChecker(options.sqlMapping); // registry for all artifact names and element names
67
66
 
@@ -100,17 +99,17 @@ function toHdbcdsSource(csn, options) {
100
99
  Object.entries(art.$tableConstraints.referential)
101
100
  .forEach(([ fileName, referentialConstraint ]) => {
102
101
  referentialConstraints[fileName] = renderReferentialConstraint(
103
- referentialConstraint, '', renderToUppercase, csn, options, false
102
+ referentialConstraint, '', renderToUppercase, csn, options
104
103
  );
105
104
  });
106
105
  Object.entries(referentialConstraints)
107
- .forEach( ([ fileName, constraint ]) => {
106
+ .forEach(([ fileName, constraint ]) => {
108
107
  hdbconstraint[fileName] = constraint;
109
108
  });
110
109
  }
111
110
  });
112
- result.hdbcds = hdbcds;
113
- result.hdbconstraint = hdbconstraint;
111
+ hdbcdsResult.hdbcds = hdbcds;
112
+ hdbcdsResult.hdbconstraint = hdbconstraint;
114
113
 
115
114
  if (globalDuplicateChecker)
116
115
  globalDuplicateChecker.check(error, options); // perform duplicates check
@@ -119,7 +118,7 @@ function toHdbcdsSource(csn, options) {
119
118
 
120
119
  throwWithError();
121
120
  timetrace.stop();
122
- return options.testMode ? sort(result) : result;
121
+ return options.testMode ? sort(hdbcdsResult) : hdbcdsResult;
123
122
 
124
123
  /**
125
124
  * Sort the given object alphabetically
@@ -224,7 +223,7 @@ function toHdbcdsSource(csn, options) {
224
223
  const parts = containee.split('.');
225
224
  const prefixLength = contextName.split('.').length;
226
225
 
227
- for (let i = parts.length - 1; i > prefixLength; i-- ) {
226
+ for (let i = parts.length - 1; i > prefixLength; i--) {
228
227
  const prefix = parts.slice(0, i).join('.');
229
228
  const art = csn.definitions[prefix];
230
229
  if (art && (art.kind === 'context' || art.kind === 'service'))
@@ -291,7 +290,7 @@ function toHdbcdsSource(csn, options) {
291
290
  function renderContext(artifactName, art, env, isShadowed) {
292
291
  let result = '';
293
292
  if (!isShadowed)
294
- isShadowed = contextIsShadowed(artifactName, csn);
293
+ isShadowed = contextIsShadowed(artifactName);
295
294
  if (isShadowed) {
296
295
  const subArtifacts = getSubArtifacts(artifactName);
297
296
  for (const name in subArtifacts)
@@ -318,10 +317,9 @@ function toHdbcdsSource(csn, options) {
318
317
  * non-context/service/namespace definition
319
318
  *
320
319
  * @param {string} artifactName
321
- * @param {CSN.Model} csn
322
320
  * @returns {boolean}
323
321
  */
324
- function contextIsShadowed(artifactName, csn) {
322
+ function contextIsShadowed(artifactName) {
325
323
  if (artifactName.indexOf('.') === -1)
326
324
  return false;
327
325
 
@@ -395,6 +393,12 @@ function toHdbcdsSource(csn, options) {
395
393
  if (hasHanaComment(art, options))
396
394
  result += `${env.indent}@Comment: '${getEscapedHanaComment(art)}'\n`;
397
395
 
396
+ // tables can have @sql.prepend and @sql.append
397
+ const { front, back } = getSqlSnippets(options, art);
398
+
399
+ if (front) // attach @sql.prepend after adding @Comment annotation
400
+ result += front;
401
+
398
402
  result += `${env.indent + (art.abstract ? 'abstract ' : '')}entity ${normalizedArtifactName}`;
399
403
  if (art.includes) {
400
404
  // Includes are never flattened (don't exist in HANA)
@@ -412,8 +416,12 @@ function toHdbcdsSource(csn, options) {
412
416
 
413
417
  duplicateChecker.check(error);
414
418
  result += `${env.indent}}`;
415
- result += `${renderTechnicalConfiguration(art.technicalConfig, env)};\n`;
416
- return result;
419
+ result += `${renderTechnicalConfiguration(art.technicalConfig, env)}`;
420
+
421
+ if (back)
422
+ result += back;
423
+
424
+ return `${result};\n`;
417
425
  }
418
426
 
419
427
  /**
@@ -429,9 +437,8 @@ function toHdbcdsSource(csn, options) {
429
437
  if (!element.target)
430
438
  return;
431
439
 
432
- let alias = element['@cds.persistence.name'];
433
440
  if (uppercaseAndUnderscore(element.target) === element['@cds.persistence.name']) {
434
- alias = createTopLevelAliasName(element['@cds.persistence.name']);
441
+ let alias = createTopLevelAliasName(element['@cds.persistence.name']);
435
442
  // calculate new alias if it would conflict with other csn.Artifact
436
443
  while (csn.definitions[alias])
437
444
  alias = createTopLevelAliasName(alias);
@@ -556,13 +563,19 @@ function toHdbcdsSource(csn, options) {
556
563
  result += `${env.indent}@Comment: '${getEscapedHanaComment(elm)}'\n`;
557
564
 
558
565
  result += env.indent + (elm.key && !isSubElement ? 'key ' : '') +
559
- (elm.masked ? 'masked ' : '') +
560
- quotedElementName + (omitColon ? ' ' : ' : ') +
561
- renderTypeReference(elm, env, undefined) +
562
- renderNullability(elm);
566
+ (elm.masked ? 'masked ' : '') +
567
+ quotedElementName + (omitColon ? ' ' : ' : ') +
568
+ renderTypeReference(elm, env) +
569
+ renderNullability(elm);
563
570
  if (elm.default)
564
571
  result += ` default ${renderExpr(elm.default, env)}`;
565
572
 
573
+ // (table) elements can only have a @sql.append
574
+ const { back } = getSqlSnippets(options, elm);
575
+
576
+ if (back)
577
+ result += back;
578
+
566
579
  return `${result};\n`;
567
580
  }
568
581
 
@@ -680,7 +693,7 @@ function toHdbcdsSource(csn, options) {
680
693
  */
681
694
  function renderViewColumn(col, env, element) {
682
695
  // Annotations and column
683
- let result = element && hasHanaComment(element, options) ? `${env.indent}@Comment: '${getEscapedHanaComment(element)}'\n` : '';
696
+ let result = '';
684
697
 
685
698
  const leaf = col.as || col.ref && col.ref[col.ref.length - 1];
686
699
  // Render 'null as <alias>' only for database and if element is virtual
@@ -760,7 +773,14 @@ function toHdbcdsSource(csn, options) {
760
773
  }
761
774
  env._artifact = art;
762
775
  result += renderQuery(getNormalizedQuery(art).query, true, env, artifactPath.concat(art.projection ? 'projection' : 'query'), art.elements);
776
+
777
+ // views can only have a @sql.append
778
+ const { back } = getSqlSnippets(options, art);
779
+ if (back)
780
+ result += back;
781
+
763
782
  result += ';\n';
783
+
764
784
  return result;
765
785
  }
766
786
 
@@ -829,72 +849,73 @@ function toHdbcdsSource(csn, options) {
829
849
  result += `${env.indent}}`;
830
850
  }
831
851
  if (select.excluding) {
832
- result += ` excluding {\n${select.excluding.map(id => `${childEnv.indent}${formatIdentifier(id)}`).join(',\n')}\n`;
852
+ const excludingList = select.excluding.map(id => `${childEnv.indent}${formatIdentifier(id)}`).join(',\n');
853
+ result += ` excluding {\n${excludingList}\n`;
833
854
  result += `${env.indent}}`;
834
855
  }
835
856
 
836
- return renderSelectProperties(select, result);
837
-
838
- /**
839
- * Render WHERE, GROUP BY, HAVING, ORDER BY and LIMIT clause
840
- *
841
- * @param {CSN.QuerySelect} select
842
- * @param {string} alreadyRendered The query as it has been rendered so far
843
- * @returns {string} The query with WHERE etc. added
844
- */
845
- function renderSelectProperties(select, alreadyRendered) {
846
- if (select.where)
847
- alreadyRendered += `${continueIndent(alreadyRendered, env)}where ${renderExpr(select.where, env, true, true)}`;
857
+ return renderSelectProperties(select, result, env);
858
+ }
848
859
 
849
- if (select.groupBy)
850
- alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
860
+ /**
861
+ * Render WHERE, GROUP BY, HAVING, ORDER BY and LIMIT clause
862
+ *
863
+ * @param {CSN.QuerySelect} select
864
+ * @param {string} alreadyRendered The query as it has been rendered so far
865
+ * @param {CdlRenderEnvironment} env Environment
866
+ * @returns {string} The query with WHERE etc. added
867
+ */
868
+ function renderSelectProperties(select, alreadyRendered, env) {
869
+ if (select.where)
870
+ alreadyRendered += `${continueIndent(alreadyRendered, env)}where ${renderExpr(select.where, env, true, true)}`;
851
871
 
852
- if (select.having)
853
- alreadyRendered += `${continueIndent(alreadyRendered, env)}having ${renderExpr(select.having, env, true, true)}`;
872
+ if (select.groupBy)
873
+ alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
854
874
 
855
- if (select.orderBy)
856
- alreadyRendered += `${continueIndent(alreadyRendered, env)}order by ${select.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
875
+ if (select.having)
876
+ alreadyRendered += `${continueIndent(alreadyRendered, env)}having ${renderExpr(select.having, env, true, true)}`;
857
877
 
858
- if (select.limit)
859
- alreadyRendered += `${continueIndent(alreadyRendered, env)}${renderLimit(select.limit, env)}`;
878
+ if (select.orderBy)
879
+ alreadyRendered += `${continueIndent(alreadyRendered, env)}order by ${select.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
860
880
 
861
- return alreadyRendered;
862
- }
881
+ if (select.limit)
882
+ alreadyRendered += `${continueIndent(alreadyRendered, env)}${renderLimit(select.limit, env)}`;
863
883
 
884
+ return alreadyRendered;
885
+ }
864
886
 
865
- /**
866
- * Utility function to make sure that we continue with the same indentation in WHERE, GROUP BY, ... after a closing curly brace and beyond
867
- *
868
- * @param {string} result Result of a previous render step
869
- * @param {CdlRenderEnvironment} env Environment
870
- * @returns {string} String to join with
871
- */
872
- function continueIndent(result, env) {
873
- if (result.endsWith('}') || result.endsWith('})')) {
874
- // The preceding clause ended with '}', just append after that
875
- return ' ';
876
- }
877
- // Otherwise, start new line and indent normally
878
- return `\n${increaseIndent(env).indent}`;
887
+ /**
888
+ * Utility function to make sure that we continue with the same indentation in WHERE, GROUP BY, ... after a closing curly brace and beyond
889
+ *
890
+ * @param {string} result Result of a previous render step
891
+ * @param {CdlRenderEnvironment} env Environment
892
+ * @returns {string} String to join with
893
+ */
894
+ function continueIndent(result, env) {
895
+ if (result.endsWith('}') || result.endsWith('})')) {
896
+ // The preceding clause ended with '}', just append after that
897
+ return ' ';
879
898
  }
899
+ // Otherwise, start new line and indent normally
900
+ return `\n${increaseIndent(env).indent}`;
901
+ }
880
902
 
881
- /**
882
- * Render a query's LIMIT clause, which may have also have OFFSET.
883
- *
884
- * @param {CSN.QueryLimit} limit CSN limit clause
885
- * @param {CdlRenderEnvironment} env Environment
886
- * @returns {string} Rendered limit clause
887
- */
888
- function renderLimit(limit, env) {
889
- let result = '';
890
- if (limit.rows !== undefined)
891
- result += `limit ${renderExpr(limit.rows, env)}`;
903
+ /**
904
+ * Render a query's LIMIT clause, which may have also have OFFSET.
905
+ *
906
+ * @param {CSN.QueryLimit} limit CSN limit clause
907
+ * @param {CdlRenderEnvironment} env Environment
908
+ * @returns {string} Rendered limit clause
909
+ */
910
+ function renderLimit(limit, env) {
911
+ let result = '';
912
+ if (limit.rows !== undefined)
913
+ result += `limit ${renderExpr(limit.rows, env)}`;
892
914
 
893
- if (limit.offset !== undefined)
894
- result += `${result !== '' ? `\n${increaseIndent(env).indent}` : ''}offset ${renderExpr(limit.offset, env)}`;
915
+ if (limit.offset !== undefined)
916
+ result += `${result !== '' ? `\n${increaseIndent(env).indent}` : ''}offset ${renderExpr(limit.offset, env)}`;
895
917
 
896
- return result;
897
- }
918
+ return result;
898
919
  }
899
920
 
900
921
  /**
@@ -926,7 +947,7 @@ function toHdbcdsSource(csn, options) {
926
947
  */
927
948
  function renderParameter(parName, par, env) {
928
949
  if (par.notNull === true || par.notNull === false)
929
- info(null, env.path.concat([ 'params', parName ]), 'Not Null constraints on HDBCDS view parameters are not allowed and are ignored');
950
+ info('query-ignoring-param-nullability', env.path.concat([ 'params', parName ]), { '#': 'std' });
930
951
  return `${env.indent + formatParamIdentifier(parName, env.path.concat([ 'params', parName ]))} : ${renderTypeReference(par, env)}`;
931
952
  }
932
953
 
@@ -943,7 +964,7 @@ function toHdbcdsSource(csn, options) {
943
964
  if (art.kind === 'aspect' || art.kind === 'type' && !hdbcdsNames || art.kind === 'type' && hdbcdsNames && !art.elements)
944
965
  return '';
945
966
  let result = '';
946
- result += `${env.indent + (art.kind )} ${renderArtifactName(artifactName, env, true)}`;
967
+ result += `${env.indent + (art.kind)} ${renderArtifactName(artifactName, env, true)}`;
947
968
  if (art.includes) {
948
969
  // Includes are never flattened (don't exist in HANA)
949
970
  result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name, env)).join(', ')}`;
@@ -959,7 +980,7 @@ function toHdbcdsSource(csn, options) {
959
980
  }
960
981
  else {
961
982
  // Derived type or annotation with non-anonymous type
962
- result += ` : ${renderTypeReference(art, env, false)};\n`;
983
+ result += ` : ${renderTypeReference(art, env)};\n`;
963
984
  }
964
985
  return result;
965
986
  }
@@ -968,12 +989,11 @@ function toHdbcdsSource(csn, options) {
968
989
  * Render a reference to a type used by 'elm' (named or inline)
969
990
  * Allow suppressing enum-rendering - used in columns for example
970
991
  *
971
- * @param {CSN.Element} elm Element using the type reference
992
+ * @param {object} elm Element using the type reference
972
993
  * @param {CdlRenderEnvironment} env Environment
973
- * @param {boolean} [noEnum=false] If true, do not render enums
974
994
  * @returns {string} Rendered type reference
975
995
  */
976
- function renderTypeReference(elm, env, noEnum = false) {
996
+ function renderTypeReference(elm, env) {
977
997
  let result = '';
978
998
 
979
999
  // Array type: Render items instead
@@ -1011,11 +1031,7 @@ function toHdbcdsSource(csn, options) {
1011
1031
  // Reference to another element
1012
1032
  if (elm.type.ref) {
1013
1033
  // For HANA CDS, we need a 'type of'
1014
- let result = `type of ${renderAbsolutePath(elm.type, env)}`;
1015
- if (elm.enum)
1016
- result += renderEnum(elm.enum, env);
1017
-
1018
- return result;
1034
+ return `type of ${renderAbsolutePath(elm.type, env)}`;
1019
1035
  }
1020
1036
 
1021
1037
  // If we get here, it must be a named type
@@ -1027,8 +1043,6 @@ function toHdbcdsSource(csn, options) {
1027
1043
  // Type names are never flattened (derived types are unraveled in HANA)
1028
1044
  result += renderAbsoluteNameWithQuotes(elm.type, env);
1029
1045
  }
1030
- if (elm.enum && !noEnum)
1031
- result += renderEnum(elm.enum, env);
1032
1046
 
1033
1047
  return result;
1034
1048
  }
@@ -1094,58 +1108,38 @@ function toHdbcdsSource(csn, options) {
1094
1108
  return elm.type.replace(/^cds\./, '') + renderTypeParameters(elm);
1095
1109
  }
1096
1110
 
1097
- /**
1098
- * Render the 'enum { ... } part of a type declaration
1099
- *
1100
- * @param {CSN.EnumElements} enumPart Enum part of a type declaration
1101
- * @param {CdlRenderEnvironment} env Environment
1102
- * @returns {string} Rendered enum
1103
- */
1104
- function renderEnum(enumPart, env) {
1105
- let result = ' enum {\n';
1106
- const childEnv = increaseIndent(env);
1107
- for (const name in enumPart) {
1108
- const enumConst = enumPart[name];
1109
- result += childEnv.indent + quoteId(name);
1110
- if (enumConst.val !== undefined)
1111
- result += ` = ${renderExpr(enumConst, childEnv)}`;
1112
- result += ';\n';
1113
- }
1114
- result += `${env.indent}}`;
1115
- return result;
1116
- }
1117
-
1118
1111
  /**
1119
1112
  * Render an expression (including paths and values) or condition 'x'.
1120
1113
  * (no trailing LF, don't indent if inline)
1121
1114
  *
1122
- * @param {any} x Expression to render
1115
+ * @param {any} expr Expression to render
1123
1116
  * @param {CdlRenderEnvironment} env Environment
1124
- * @param {boolean} [inline=true] Wether to render inline
1117
+ * @param {boolean} [inline=true] Whether to render inline
1125
1118
  * @param {boolean} [inExpr=false] Whether the expression is already inside another expression
1126
1119
  * @returns {string} Rendered expression
1127
1120
  */
1128
- function renderExpr(x, env, inline = true, inExpr = false) {
1121
+ function renderExpr(expr, env, inline = true, inExpr = false) {
1129
1122
  // Compound expression
1130
- if (Array.isArray(x))
1131
- return beautifyExprArray(x.map(item => renderExpr(item, env, inline, inExpr)));
1123
+ if (Array.isArray(expr))
1124
+ return beautifyExprArray(expr.map(item => renderExpr(item, env, inline, inExpr)));
1132
1125
 
1133
- if (typeof x === 'object' && x !== null) {
1134
- if (inExpr && x.cast && x.cast.type)
1135
- return renderExplicitTypeCast(renderExprObject());
1136
- return renderExprObject();
1126
+ if (typeof expr === 'object' && expr !== null) {
1127
+ if (inExpr && expr.cast && expr.cast.type)
1128
+ return renderExplicitTypeCast(renderExprObject(expr));
1129
+ return renderExprObject(expr);
1137
1130
  }
1138
1131
 
1139
1132
  // Not a literal value but part of an operator, function etc - just leave as it is
1140
- return x;
1133
+ return expr;
1141
1134
 
1142
1135
 
1143
1136
  /**
1144
1137
  * Various special cases represented as objects
1145
1138
  *
1139
+ * @param {object} x Expression
1146
1140
  * @returns {string} Rendered expression object
1147
1141
  */
1148
- function renderExprObject() {
1142
+ function renderExprObject(x) {
1149
1143
  if (x.list) {
1150
1144
  // set "inExpr" to false: treat list elements as new expressions
1151
1145
  return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
@@ -1173,7 +1167,7 @@ function toHdbcdsSource(csn, options) {
1173
1167
  // we can't quote functions with parens, issue warning if it is a reserved keyword
1174
1168
  if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
1175
1169
  warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
1176
- return renderFunc( funcName, x, 'hana', a => renderArgs(a, '=>', env) );
1170
+ return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
1177
1171
  }
1178
1172
  // Nested expression
1179
1173
  else if (x.xpr) {
@@ -1229,7 +1223,12 @@ function toHdbcdsSource(csn, options) {
1229
1223
  */
1230
1224
  function renderExpressionRef(x) {
1231
1225
  if (!x.param && !x.global) {
1226
+ const magicReplacement = getVariableReplacement(x.ref, options);
1232
1227
  if (x.ref[0] === '$user') {
1228
+ if (magicReplacement !== null)
1229
+ return `'${magicReplacement}'`;
1230
+
1231
+ // Keep old way of solving this to remain backwards compatible
1233
1232
  // FIXME: this is all not enough: we might need an explicit select item alias
1234
1233
  if (x.ref[1] === 'id') {
1235
1234
  if (options.magicVars && options.magicVars.user && (typeof options.magicVars.user === 'string' || options.magicVars.user instanceof String))
@@ -1251,6 +1250,9 @@ function toHdbcdsSource(csn, options) {
1251
1250
  else if (x.ref[1] === 'to')
1252
1251
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1253
1252
  }
1253
+ else if (x.ref[0] === '$session' && magicReplacement !== null) {
1254
+ return `'${magicReplacement}'`;
1255
+ }
1254
1256
  }
1255
1257
  return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref)).join('.')}`;
1256
1258
  }
@@ -1262,10 +1264,10 @@ function toHdbcdsSource(csn, options) {
1262
1264
  * @returns {string} Rendered cast()
1263
1265
  */
1264
1266
  function renderExplicitTypeCast(value) {
1265
- let typeRef = renderTypeReference(x.cast, env, true);
1267
+ let typeRef = renderTypeReference(expr.cast, env);
1266
1268
 
1267
1269
  // inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
1268
- const hanaSqlType = cdsToSqlTypes.hana[x.cast.type] || cdsToSqlTypes.standard[x.cast.type];
1270
+ const hanaSqlType = cdsToSqlTypes.hana[expr.cast.type] || cdsToSqlTypes.standard[expr.cast.type];
1269
1271
  if (hanaSqlType) {
1270
1272
  const typeRefWithoutParams = typeRef.substring(0, typeRef.indexOf('(')) || typeRef;
1271
1273
  typeRef = typeRef.replace(typeRefWithoutParams, hanaSqlType);
@@ -1288,7 +1290,7 @@ function toHdbcdsSource(csn, options) {
1288
1290
  // (see FIXME at renderArtifact)
1289
1291
  if (idx === 0 && s === $SELF) {
1290
1292
  // do not produce USING for $projection
1291
- if (env.currentArtifactName === $PROJECTION && env._artifact && env._artifact.projection)
1293
+ if (env.currentArtifactName === $PROJECTION)
1292
1294
  return env.currentArtifactName;
1293
1295
 
1294
1296
  return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
@@ -1541,9 +1543,70 @@ function toHdbcdsSource(csn, options) {
1541
1543
  Object.keys(env.topLevelAliases)
1542
1544
  .filter(name => env.topLevelAliases[name].quotedAlias !== formatIdentifier(uppercaseAndUnderscore(artifactName))) // avoid "using FOO as FOO" in FOO.cds
1543
1545
  .forEach((name) => {
1546
+ const nativeObjectExists = csn.definitions[name] && hasAnnotationValue(csn.definitions[name], '@cds.persistence.exists');
1547
+ if (!plainNames && nativeObjectExists)
1548
+ checkForNameClashesWithNativeObject(name);
1544
1549
  distinct[`using ${env.topLevelAliases[name].quotedName} as ${env.topLevelAliases[name].quotedAlias};\n`] = '';
1545
1550
  });
1551
+ /**
1552
+ * If we generate a `using <native object> from <bar>` clause,
1553
+ * we warn if we generate a SAP HANA CDS artifact which would hide the
1554
+ * native DB object from being found by the SAP HANA CDS compiler
1555
+ * see cap/cds-compiler#8269 for details
1556
+ * @param {string} name of the native db object
1557
+ */
1558
+ function checkForNameClashesWithNativeObject(name) {
1559
+ const possibleShadowName = getNamePrefix(env.topLevelAliases[name].quotedName);
1560
+ const mightBeShadowedBy = csn.definitions[possibleShadowName];
1561
+ if (mightBeShadowedBy) {
1562
+ const artifactWillBeRendered = isArtifactRendered(mightBeShadowedBy, possibleShadowName);
1563
+ // only warn if actually rendered to HANA CDS
1564
+ if (artifactWillBeRendered)
1565
+ warning('anno-hidden-exists', [ 'definitions', name ], { name: possibleShadowName }, 'Native database object is hidden by a definition starting with $(NAME)');
1566
+ }
1567
+ }
1546
1568
 
1569
+ function isArtifactRendered(art, artName) {
1570
+ const isHanaCdsContext = art.kind === 'service' || art.kind === 'context';
1571
+ if (isHanaCdsContext)
1572
+ return isContextRendered(artName);
1573
+ if ([ 'action', 'function', 'event' ].includes(art.kind) || options.sqlMapping !== 'hdbcds' && art.kind === 'type')
1574
+ return false;
1575
+ return !(hasAnnotationValue(art, '@cds.persistence.exists') || hasAnnotationValue(art, '@cds.persistence.skip'));
1576
+ }
1577
+
1578
+ /**
1579
+ * Check if there is at least one entity which will be rendered as SAP HANA CDS entity
1580
+ * inside the given context (or in its sub-contexts).
1581
+ * Or in other words: If the context will be rendered as a SAP HANA CDS context in the end.
1582
+ *
1583
+ * @param {string} contextName
1584
+ * @returns {boolean} true if a context/service will be rendered as a SAP HANA CDS context.
1585
+ */
1586
+ function isContextRendered(contextName) {
1587
+ const subArtifacts = getSubArtifacts(contextName);
1588
+ return Object.entries(subArtifacts).some(([ artName, art ]) => {
1589
+ if (art.kind === 'context')
1590
+ return isContextRendered(`${contextName}.${artName}`);
1591
+ return isArtifactRendered(art, artName);
1592
+ });
1593
+ }
1594
+
1595
+ /**
1596
+ * @param {string} usingName the string which appears in the `using <string> from ..` including the quotes
1597
+ * @returns the prefix of the `using` name.
1598
+ * @example
1599
+ * "com.sap.foo.native.object" --> com
1600
+ * "com.sap.foo::native.object" --> com.sap.foo.native
1601
+ */
1602
+ function getNamePrefix(usingName) {
1603
+ usingName = usingName.replace(/"/g, '');
1604
+ if (usingName.indexOf('::') !== -1) {
1605
+ const parts = usingName.split('::');
1606
+ return `${parts[0]}.${parts[1].split('.')[0]}`;
1607
+ }
1608
+ return usingName.split('.')[0];
1609
+ }
1547
1610
  return Object.keys(distinct).join('');
1548
1611
  }
1549
1612
 
@@ -1675,11 +1738,6 @@ function toHdbcdsSource(csn, options) {
1675
1738
  if (id.indexOf('.') !== -1)
1676
1739
  throw new Error(id);
1677
1740
 
1678
- // FIXME: Somewhat arbitrary magic: Do not quote $projection (because HANA CDS doesn't recognize it otherwise). Similar for $self.
1679
- // FIXME: The test should not be on the name, but by checking the _artifact.
1680
- if (id === $PROJECTION || id === $SELF)
1681
- return id;
1682
-
1683
1741
 
1684
1742
  switch (options.forHana.names) {
1685
1743
  case 'plain':
@@ -1701,13 +1759,14 @@ function toHdbcdsSource(csn, options) {
1701
1759
  * @returns {string} Correctly quoted absname
1702
1760
  */
1703
1761
  function quoteAbsoluteNameAsId(absname) {
1762
+ const resultingName = getResultingName(csn, options.sqlMapping, absname);
1763
+
1704
1764
  if (hdbcdsNames) {
1705
- const topLevelName = getRootArtifactName(absname, csn);
1706
- const namespace = getParentNameOf(topLevelName);
1765
+ const namespace = getNamespace(csn, absname);
1707
1766
  if (namespace)
1708
- return `"${(`${namespace}::${absname.substring(namespace.length + 1)}`).replace(/"/g, '""')}"`;
1767
+ return `"${(`${namespace}::${resultingName.substring(namespace.length + 2)}`).replace(/"/g, '""')}"`;
1709
1768
  }
1710
- return `"${absname.replace(/"/g, '""')}"`;
1769
+ return `"${resultingName.replace(/"/g, '""')}"`;
1711
1770
  }
1712
1771
 
1713
1772
  /**
@@ -1762,7 +1821,7 @@ function toHdbcdsSource(csn, options) {
1762
1821
  function renderArtifactName(artifactName, env, fallthrough = false) {
1763
1822
  if (plainNames && !fallthrough)
1764
1823
  return formatIdentifier(uppercaseAndUnderscore(artifactName));
1765
- // hdbcds with quoted or hdbcds naming
1824
+ // hdbcds with quoted or hdbcds naming
1766
1825
  return env.namePrefix + quoteId(getRealName(csn, artifactName).replace(/\./g, '_'));
1767
1826
  }
1768
1827
 
@@ -70,22 +70,19 @@ function toRenameDdl(csn, options) {
70
70
 
71
71
  resultStr += Object.keys(art.elements).map((name) => {
72
72
  const e = art.elements[name];
73
- let result = '';
73
+ let str = '';
74
74
 
75
75
  const beforeColumnName = quoteSqlId(name);
76
76
  const afterColumnName = plainSqlId(name);
77
77
 
78
78
  if (!e._ignore) {
79
- if (e.target) {
80
- resultStr += ' ';
81
- result = ` EXEC 'ALTER TABLE ${afterTableName} DROP ASSOCIATION ${beforeColumnName}';\n`;
82
- }
83
- else if (beforeColumnName !== afterColumnName) {
84
- resultStr += ' ';
85
- result = ` EXEC 'RENAME COLUMN ${afterTableName}.${beforeColumnName} TO ${afterColumnName}';\n`;
86
- }
79
+ if (e.target)
80
+ str = ` EXEC 'ALTER TABLE ${afterTableName} DROP ASSOCIATION ${beforeColumnName}';\n`;
81
+
82
+ else if (beforeColumnName !== afterColumnName)
83
+ str = ` EXEC 'RENAME COLUMN ${afterTableName}.${beforeColumnName} TO ${afterColumnName}';\n`;
87
84
  }
88
- return result;
85
+ return str;
89
86
  }).join('');
90
87
  }
91
88
  return resultStr;