@sap/cds-compiler 2.11.4 → 2.13.8

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 (133) hide show
  1. package/CHANGELOG.md +159 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +22 -23
  4. package/bin/cdsse.js +2 -2
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +25 -6
  7. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  8. package/doc/NameResolution.md +21 -16
  9. package/lib/api/main.js +30 -63
  10. package/lib/api/options.js +5 -5
  11. package/lib/api/validate.js +0 -5
  12. package/lib/backends.js +15 -23
  13. package/lib/base/dictionaries.js +0 -8
  14. package/lib/base/error.js +26 -0
  15. package/lib/base/keywords.js +7 -17
  16. package/lib/base/location.js +9 -4
  17. package/lib/base/message-registry.js +52 -2
  18. package/lib/base/messages.js +16 -26
  19. package/lib/base/model.js +2 -62
  20. package/lib/base/optionProcessorHelper.js +246 -183
  21. package/lib/checks/.eslintrc.json +2 -0
  22. package/lib/checks/actionsFunctions.js +2 -1
  23. package/lib/checks/annotationsOData.js +1 -1
  24. package/lib/checks/cdsPersistence.js +2 -1
  25. package/lib/checks/enricher.js +17 -1
  26. package/lib/checks/foreignKeys.js +4 -4
  27. package/lib/checks/invalidTarget.js +3 -1
  28. package/lib/checks/managedInType.js +4 -4
  29. package/lib/checks/managedWithoutKeys.js +3 -1
  30. package/lib/checks/queryNoDbArtifacts.js +1 -3
  31. package/lib/checks/selectItems.js +4 -4
  32. package/lib/checks/sql-snippets.js +94 -0
  33. package/lib/checks/types.js +1 -1
  34. package/lib/checks/validator.js +12 -7
  35. package/lib/compiler/assert-consistency.js +10 -6
  36. package/lib/compiler/base.js +0 -1
  37. package/lib/compiler/builtins.js +8 -6
  38. package/lib/compiler/checks.js +46 -12
  39. package/lib/compiler/cycle-detector.js +1 -1
  40. package/lib/compiler/define.js +1103 -0
  41. package/lib/compiler/extend.js +983 -0
  42. package/lib/compiler/finalize-parse-cdl.js +231 -0
  43. package/lib/compiler/index.js +33 -14
  44. package/lib/compiler/kick-start.js +190 -0
  45. package/lib/compiler/moduleLayers.js +4 -4
  46. package/lib/compiler/populate.js +1226 -0
  47. package/lib/compiler/propagator.js +113 -47
  48. package/lib/compiler/resolve.js +1433 -0
  49. package/lib/compiler/shared.js +76 -38
  50. package/lib/compiler/tweak-assocs.js +529 -0
  51. package/lib/compiler/utils.js +204 -33
  52. package/lib/edm/.eslintrc.json +5 -0
  53. package/lib/edm/annotations/genericTranslation.js +38 -25
  54. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  55. package/lib/edm/csn2edm.js +10 -9
  56. package/lib/edm/edm.js +19 -20
  57. package/lib/edm/edmPreprocessor.js +166 -95
  58. package/lib/edm/edmUtils.js +127 -34
  59. package/lib/gen/Dictionary.json +92 -43
  60. package/lib/gen/language.checksum +1 -1
  61. package/lib/gen/language.interp +11 -1
  62. package/lib/gen/language.tokens +86 -82
  63. package/lib/gen/languageLexer.interp +18 -1
  64. package/lib/gen/languageLexer.js +925 -847
  65. package/lib/gen/languageLexer.tokens +78 -74
  66. package/lib/gen/languageParser.js +5434 -4298
  67. package/lib/json/from-csn.js +59 -17
  68. package/lib/json/to-csn.js +143 -71
  69. package/lib/language/antlrParser.js +3 -3
  70. package/lib/language/docCommentParser.js +3 -3
  71. package/lib/language/genericAntlrParser.js +144 -54
  72. package/lib/language/language.g4 +424 -203
  73. package/lib/language/multiLineStringParser.js +536 -0
  74. package/lib/main.d.ts +472 -61
  75. package/lib/main.js +38 -11
  76. package/lib/model/api.js +3 -1
  77. package/lib/model/csnRefs.js +321 -204
  78. package/lib/model/csnUtils.js +224 -263
  79. package/lib/model/enrichCsn.js +97 -40
  80. package/lib/model/revealInternalProperties.js +27 -6
  81. package/lib/model/sortViews.js +2 -1
  82. package/lib/modelCompare/compare.js +17 -12
  83. package/lib/optionProcessor.js +7 -6
  84. package/lib/render/DuplicateChecker.js +1 -1
  85. package/lib/render/manageConstraints.js +36 -33
  86. package/lib/render/toCdl.js +174 -275
  87. package/lib/render/toHdbcds.js +201 -115
  88. package/lib/render/toRename.js +7 -10
  89. package/lib/render/toSql.js +149 -75
  90. package/lib/render/utils/common.js +22 -8
  91. package/lib/render/utils/sql.js +10 -7
  92. package/lib/render/utils/stringEscapes.js +111 -0
  93. package/lib/sql-identifier.js +1 -1
  94. package/lib/transform/.eslintrc.json +5 -0
  95. package/lib/transform/braceExpression.js +4 -2
  96. package/lib/transform/db/.eslintrc.json +2 -0
  97. package/lib/transform/db/applyTransformations.js +35 -12
  98. package/lib/transform/db/assertUnique.js +1 -1
  99. package/lib/transform/db/associations.js +187 -0
  100. package/lib/transform/db/cdsPersistence.js +150 -0
  101. package/lib/transform/db/constraints.js +61 -56
  102. package/lib/transform/db/expansion.js +50 -29
  103. package/lib/transform/db/flattening.js +552 -105
  104. package/lib/transform/db/groupByOrderBy.js +3 -1
  105. package/lib/transform/db/temporal.js +236 -0
  106. package/lib/transform/db/transformExists.js +94 -28
  107. package/lib/transform/db/views.js +5 -4
  108. package/lib/transform/draft/.eslintrc.json +38 -0
  109. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  110. package/lib/transform/draft/odata.js +227 -0
  111. package/lib/transform/forHanaNew.js +94 -801
  112. package/lib/transform/forOdataNew.js +22 -175
  113. package/lib/transform/localized.js +36 -32
  114. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  115. package/lib/transform/odata/referenceFlattener.js +95 -89
  116. package/lib/transform/odata/structureFlattener.js +1 -1
  117. package/lib/transform/odata/toFinalBaseType.js +86 -12
  118. package/lib/transform/odata/typesExposure.js +5 -5
  119. package/lib/transform/odata/utils.js +2 -2
  120. package/lib/transform/transformUtilsNew.js +47 -33
  121. package/lib/transform/translateAssocsToJoins.js +10 -27
  122. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  123. package/lib/transform/universalCsn/coreComputed.js +170 -0
  124. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  125. package/lib/transform/universalCsn/utils.js +63 -0
  126. package/lib/utils/file.js +2 -1
  127. package/lib/utils/objectUtils.js +30 -0
  128. package/lib/utils/timetrace.js +8 -2
  129. package/package.json +1 -1
  130. package/share/messages/README.md +26 -0
  131. package/lib/compiler/definer.js +0 -2340
  132. package/lib/compiler/resolver.js +0 -2988
  133. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -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, getVariableReplacement,
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,
@@ -20,6 +20,7 @@ const { makeMessageFunction } = require('../base/messages');
20
20
  const { timetrace } = require('../utils/timetrace');
21
21
 
22
22
  const { smartId, delimitedId } = require('../sql-identifier');
23
+ const { ModelError } = require('../base/error');
23
24
 
24
25
  const $PROJECTION = '$projection';
25
26
  const $SELF = '$self';
@@ -60,8 +61,7 @@ function toHdbcdsSource(csn, options) {
60
61
 
61
62
  checkCSNVersion(csn, options);
62
63
 
63
- const result = Object.create(null);
64
-
64
+ const hdbcdsResult = Object.create(null);
65
65
 
66
66
  const globalDuplicateChecker = new DuplicateChecker(options.sqlMapping); // registry for all artifact names and element names
67
67
 
@@ -100,17 +100,17 @@ function toHdbcdsSource(csn, options) {
100
100
  Object.entries(art.$tableConstraints.referential)
101
101
  .forEach(([ fileName, referentialConstraint ]) => {
102
102
  referentialConstraints[fileName] = renderReferentialConstraint(
103
- referentialConstraint, '', renderToUppercase, csn, options, false
103
+ referentialConstraint, '', renderToUppercase, csn, options
104
104
  );
105
105
  });
106
106
  Object.entries(referentialConstraints)
107
- .forEach( ([ fileName, constraint ]) => {
107
+ .forEach(([ fileName, constraint ]) => {
108
108
  hdbconstraint[fileName] = constraint;
109
109
  });
110
110
  }
111
111
  });
112
- result.hdbcds = hdbcds;
113
- result.hdbconstraint = hdbconstraint;
112
+ hdbcdsResult.hdbcds = hdbcds;
113
+ hdbcdsResult.hdbconstraint = hdbconstraint;
114
114
 
115
115
  if (globalDuplicateChecker)
116
116
  globalDuplicateChecker.check(error, options); // perform duplicates check
@@ -119,7 +119,7 @@ function toHdbcdsSource(csn, options) {
119
119
 
120
120
  throwWithError();
121
121
  timetrace.stop();
122
- return options.testMode ? sort(result) : result;
122
+ return options.testMode ? sort(hdbcdsResult) : hdbcdsResult;
123
123
 
124
124
  /**
125
125
  * Sort the given object alphabetically
@@ -153,7 +153,6 @@ function toHdbcdsSource(csn, options) {
153
153
 
154
154
  switch (art.kind) {
155
155
  case 'entity':
156
- case 'view':
157
156
  // FIXME: For HANA CDS, we need to replace $self at the beginning of paths in association ON-condition
158
157
  // by the full name of the artifact we are rendering (should actually be done by forHana, but that is
159
158
  // somewhat difficult because this kind of absolute path is quite unusual). In order not to have to pass
@@ -178,7 +177,7 @@ function toHdbcdsSource(csn, options) {
178
177
  case 'event':
179
178
  return '';
180
179
  default:
181
- throw new Error(`Unknown artifact kind: ${art.kind}`);
180
+ throw new ModelError(`Unknown artifact kind: ${art.kind}`);
182
181
  }
183
182
  }
184
183
 
@@ -214,7 +213,7 @@ function toHdbcdsSource(csn, options) {
214
213
  }
215
214
 
216
215
  /**
217
- * Check wether the given context is the direct parent of the containee.
216
+ * Check whether the given context is the direct parent of the containee.
218
217
  *
219
218
  * @param {string} containee Name of the contained artifact
220
219
  * @param {string} contextName Name of the (grand?)parent context
@@ -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)
@@ -314,14 +313,13 @@ function toHdbcdsSource(csn, options) {
314
313
  return `${result + renderedSubArtifacts + env.indent}};\n`;
315
314
  }
316
315
  /**
317
- * Check wether the given context is shadowed, i.e. part of his name prefix is shared by a
316
+ * Check whether the given context is shadowed, i.e. part of his name prefix is shared by a
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);
@@ -462,7 +469,7 @@ function toHdbcdsSource(csn, options) {
462
469
  // in 'tc' that we find and render them all (is it syntactically allowed yet to have more than one?)
463
470
  tc = tc.hana;
464
471
  if (!tc)
465
- throw new Error('Expecting a HANA technical configuration');
472
+ throw new ModelError('Expecting a HANA technical configuration');
466
473
 
467
474
  result += `\n${env.indent}technical ${tc.calculated ? '' : 'hana '}configuration {\n`;
468
475
 
@@ -537,7 +544,7 @@ function toHdbcdsSource(csn, options) {
537
544
  * @param {CSN.Element} elm Content of the element
538
545
  * @param {CdlRenderEnvironment} env Environment
539
546
  * @param {DuplicateChecker} [duplicateChecker] Utility for detecting duplicates
540
- * @param {boolean} [isSubElement] Wether the given element is a subelement or not - subelements cannot be key!
547
+ * @param {boolean} [isSubElement] Whether the given element is a subelement or not - subelements cannot be key!
541
548
  * @returns {string} The rendered element
542
549
  */
543
550
  function renderElement(elementName, elm, env, duplicateChecker, isSubElement) {
@@ -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) +
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
 
@@ -615,7 +628,7 @@ function toHdbcdsSource(csn, options) {
615
628
  function renderAbsolutePath(path, env) {
616
629
  // Sanity checks
617
630
  if (!path.ref)
618
- throw new Error(`Expecting ref in path: ${JSON.stringify(path)}`);
631
+ throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
619
632
 
620
633
 
621
634
  // Determine the absolute name of the first artifact on the path (before any associations or element traversals)
@@ -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
 
@@ -771,7 +791,7 @@ function toHdbcdsSource(csn, options) {
771
791
  * or 'entity')
772
792
  *
773
793
  * @param {CSN.Query} query Query object
774
- * @param {boolean} isLeadingQuery Wether the query is the leading query or not
794
+ * @param {boolean} isLeadingQuery Whether the query is the leading query or not
775
795
  * @param {CdlRenderEnvironment} env Environment
776
796
  * @param {CSN.Path} [path=[]] CSN path to the query
777
797
  * @param {object} [elements] For leading query, the elements of the artifact
@@ -803,7 +823,7 @@ function toHdbcdsSource(csn, options) {
803
823
  }
804
824
  // Otherwise must have a SELECT
805
825
  else if (!query.SELECT) {
806
- throw new Error(`Unexpected query operation ${JSON.stringify(query)}`);
826
+ throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
807
827
  }
808
828
  const select = query.SELECT;
809
829
  const childEnv = increaseIndent(env);
@@ -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, true, false, true)).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
  /**
@@ -906,7 +927,7 @@ function toHdbcdsSource(csn, options) {
906
927
  * @returns {string} Rendered order by
907
928
  */
908
929
  function renderOrderByEntry(entry, env) {
909
- let result = renderExpr(entry, env);
930
+ let result = renderExpr(entry, env, true, false, true);
910
931
  if (entry.sort)
911
932
  result += ` ${entry.sort}`;
912
933
 
@@ -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(', ')}`;
@@ -968,7 +989,7 @@ 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
994
  * @returns {string} Rendered type reference
974
995
  */
@@ -990,7 +1011,7 @@ function toHdbcdsSource(csn, options) {
990
1011
  // Anonymous structured type
991
1012
  if (!elm.type) {
992
1013
  if (!elm.elements)
993
- throw new Error(`Missing type of: ${JSON.stringify(elm)}`);
1014
+ throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
994
1015
 
995
1016
  result += '{\n';
996
1017
  const childEnv = increaseIndent(env);
@@ -1091,33 +1112,36 @@ function toHdbcdsSource(csn, options) {
1091
1112
  * Render an expression (including paths and values) or condition 'x'.
1092
1113
  * (no trailing LF, don't indent if inline)
1093
1114
  *
1094
- * @param {any} x Expression to render
1115
+ * @param {any} expr Expression to render
1095
1116
  * @param {CdlRenderEnvironment} env Environment
1096
- * @param {boolean} [inline=true] Wether to render inline
1117
+ * @param {boolean} [inline=true] Whether to render inline
1097
1118
  * @param {boolean} [inExpr=false] Whether the expression is already inside another expression
1119
+ * @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
1120
+ * Note: This is a hack for casts() inside groupBy.
1098
1121
  * @returns {string} Rendered expression
1099
1122
  */
1100
- function renderExpr(x, env, inline = true, inExpr = false) {
1123
+ function renderExpr(expr, env, inline = true, inExpr = false, alwaysRenderCast = false) {
1101
1124
  // Compound expression
1102
- if (Array.isArray(x))
1103
- return beautifyExprArray(x.map(item => renderExpr(item, env, inline, inExpr)));
1125
+ if (Array.isArray(expr))
1126
+ return beautifyExprArray(expr.map(item => renderExpr(item, env, inline, inExpr)));
1104
1127
 
1105
- if (typeof x === 'object' && x !== null) {
1106
- if (inExpr && x.cast && x.cast.type)
1107
- return renderExplicitTypeCast(renderExprObject());
1108
- return renderExprObject();
1128
+ if (typeof expr === 'object' && expr !== null) {
1129
+ if ((inExpr || alwaysRenderCast) && expr.cast && expr.cast.type)
1130
+ return renderExplicitTypeCast(renderExprObject(expr));
1131
+ return renderExprObject(expr);
1109
1132
  }
1110
1133
 
1111
1134
  // Not a literal value but part of an operator, function etc - just leave as it is
1112
- return x;
1135
+ return expr;
1113
1136
 
1114
1137
 
1115
1138
  /**
1116
1139
  * Various special cases represented as objects
1117
1140
  *
1141
+ * @param {object} x Expression
1118
1142
  * @returns {string} Rendered expression object
1119
1143
  */
1120
- function renderExprObject() {
1144
+ function renderExprObject(x) {
1121
1145
  if (x.list) {
1122
1146
  // set "inExpr" to false: treat list elements as new expressions
1123
1147
  return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
@@ -1145,7 +1169,7 @@ function toHdbcdsSource(csn, options) {
1145
1169
  // we can't quote functions with parens, issue warning if it is a reserved keyword
1146
1170
  if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
1147
1171
  warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
1148
- return renderFunc( funcName, x, 'hana', a => renderArgs(a, '=>', env) );
1172
+ return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
1149
1173
  }
1150
1174
  // Nested expression
1151
1175
  else if (x.xpr) {
@@ -1164,7 +1188,7 @@ function toHdbcdsSource(csn, options) {
1164
1188
  return `${renderQuery(x, false, increaseIndent(env))}`;
1165
1189
  }
1166
1190
 
1167
- throw new Error(`Unknown expression: ${JSON.stringify(x)}`);
1191
+ throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
1168
1192
  }
1169
1193
  /**
1170
1194
  * @param {object} x Expression with a val and/or literal property
@@ -1190,7 +1214,7 @@ function toHdbcdsSource(csn, options) {
1190
1214
 
1191
1215
  // otherwise fall through to
1192
1216
  default:
1193
- throw new Error(`Unknown literal or type: ${JSON.stringify(x)}`);
1217
+ throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
1194
1218
  }
1195
1219
  }
1196
1220
 
@@ -1242,10 +1266,10 @@ function toHdbcdsSource(csn, options) {
1242
1266
  * @returns {string} Rendered cast()
1243
1267
  */
1244
1268
  function renderExplicitTypeCast(value) {
1245
- let typeRef = renderTypeReference(x.cast, env);
1269
+ let typeRef = renderTypeReference(expr.cast, env);
1246
1270
 
1247
1271
  // inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
1248
- const hanaSqlType = cdsToSqlTypes.hana[x.cast.type] || cdsToSqlTypes.standard[x.cast.type];
1272
+ const hanaSqlType = cdsToSqlTypes.hana[expr.cast.type] || cdsToSqlTypes.standard[expr.cast.type];
1249
1273
  if (hanaSqlType) {
1250
1274
  const typeRefWithoutParams = typeRef.substring(0, typeRef.indexOf('(')) || typeRef;
1251
1275
  typeRef = typeRef.replace(typeRefWithoutParams, hanaSqlType);
@@ -1297,7 +1321,7 @@ function toHdbcdsSource(csn, options) {
1297
1321
  else if (typeof s === 'object') {
1298
1322
  // Sanity check
1299
1323
  if (!s.func && !s.id)
1300
- throw new Error(`Unknown path step object: ${JSON.stringify(s)}`);
1324
+ throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
1301
1325
 
1302
1326
  // Not really a path step but an object-like function call
1303
1327
  if (s.func)
@@ -1316,7 +1340,7 @@ function toHdbcdsSource(csn, options) {
1316
1340
  return result;
1317
1341
  }
1318
1342
 
1319
- throw new Error(`Unknown path step: ${JSON.stringify(s)}`);
1343
+ throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
1320
1344
  }
1321
1345
  }
1322
1346
 
@@ -1333,15 +1357,15 @@ function toHdbcdsSource(csn, options) {
1333
1357
  const args = node.args ? node.args : {};
1334
1358
  // Positional arguments
1335
1359
  if (Array.isArray(args))
1336
- return args.map(arg => renderExpr(arg, env)).join(', ');
1360
+ return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
1337
1361
 
1338
1362
  // Named arguments (object/dict)
1339
1363
  else if (typeof args === 'object')
1340
1364
  // 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(', ');
1365
+ return Object.keys(args).map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env, true, false, true)}`).join(', ');
1342
1366
 
1343
1367
 
1344
- throw new Error(`Unknown args: ${JSON.stringify(args)}`);
1368
+ throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
1345
1369
  }
1346
1370
 
1347
1371
  /**
@@ -1521,9 +1545,70 @@ function toHdbcdsSource(csn, options) {
1521
1545
  Object.keys(env.topLevelAliases)
1522
1546
  .filter(name => env.topLevelAliases[name].quotedAlias !== formatIdentifier(uppercaseAndUnderscore(artifactName))) // avoid "using FOO as FOO" in FOO.cds
1523
1547
  .forEach((name) => {
1548
+ const nativeObjectExists = csn.definitions[name] && hasAnnotationValue(csn.definitions[name], '@cds.persistence.exists');
1549
+ if (!plainNames && nativeObjectExists)
1550
+ checkForNameClashesWithNativeObject(name);
1524
1551
  distinct[`using ${env.topLevelAliases[name].quotedName} as ${env.topLevelAliases[name].quotedAlias};\n`] = '';
1525
1552
  });
1553
+ /**
1554
+ * If we generate a `using <native object> from <bar>` clause,
1555
+ * we warn if we generate a SAP HANA CDS artifact which would hide the
1556
+ * native DB object from being found by the SAP HANA CDS compiler
1557
+ * see cap/cds-compiler#8269 for details
1558
+ * @param {string} name of the native db object
1559
+ */
1560
+ function checkForNameClashesWithNativeObject(name) {
1561
+ const possibleShadowName = getNamePrefix(env.topLevelAliases[name].quotedName);
1562
+ const mightBeShadowedBy = csn.definitions[possibleShadowName];
1563
+ if (mightBeShadowedBy) {
1564
+ const artifactWillBeRendered = isArtifactRendered(mightBeShadowedBy, possibleShadowName);
1565
+ // only warn if actually rendered to HANA CDS
1566
+ if (artifactWillBeRendered)
1567
+ warning('anno-hidden-exists', [ 'definitions', name ], { name: possibleShadowName }, 'Native database object is hidden by a definition starting with $(NAME)');
1568
+ }
1569
+ }
1570
+
1571
+ function isArtifactRendered(art, artName) {
1572
+ const isHanaCdsContext = art.kind === 'service' || art.kind === 'context';
1573
+ if (isHanaCdsContext)
1574
+ return isContextRendered(artName);
1575
+ if ([ 'action', 'function', 'event' ].includes(art.kind) || options.sqlMapping !== 'hdbcds' && art.kind === 'type')
1576
+ return false;
1577
+ return !(hasAnnotationValue(art, '@cds.persistence.exists') || hasAnnotationValue(art, '@cds.persistence.skip'));
1578
+ }
1579
+
1580
+ /**
1581
+ * Check if there is at least one entity which will be rendered as SAP HANA CDS entity
1582
+ * inside the given context (or in its sub-contexts).
1583
+ * Or in other words: If the context will be rendered as a SAP HANA CDS context in the end.
1584
+ *
1585
+ * @param {string} contextName
1586
+ * @returns {boolean} true if a context/service will be rendered as a SAP HANA CDS context.
1587
+ */
1588
+ function isContextRendered(contextName) {
1589
+ const subArtifacts = getSubArtifacts(contextName);
1590
+ return Object.entries(subArtifacts).some(([ artName, art ]) => {
1591
+ if (art.kind === 'context')
1592
+ return isContextRendered(`${contextName}.${artName}`);
1593
+ return isArtifactRendered(art, artName);
1594
+ });
1595
+ }
1526
1596
 
1597
+ /**
1598
+ * @param {string} usingName the string which appears in the `using <string> from ..` including the quotes
1599
+ * @returns the prefix of the `using` name.
1600
+ * @example
1601
+ * "com.sap.foo.native.object" --> com
1602
+ * "com.sap.foo::native.object" --> com.sap.foo.native
1603
+ */
1604
+ function getNamePrefix(usingName) {
1605
+ usingName = usingName.replace(/"/g, '');
1606
+ if (usingName.indexOf('::') !== -1) {
1607
+ const parts = usingName.split('::');
1608
+ return `${parts[0]}.${parts[1].split('.')[0]}`;
1609
+ }
1610
+ return usingName.split('.')[0];
1611
+ }
1527
1612
  return Object.keys(distinct).join('');
1528
1613
  }
1529
1614
 
@@ -1562,7 +1647,7 @@ function toHdbcdsSource(csn, options) {
1562
1647
  if (plainNames) {
1563
1648
  const art = csn.definitions[name];
1564
1649
  // For 'plain' naming, take all entities and views, nothing else
1565
- if (art.kind === 'entity' || art.kind === 'view')
1650
+ if (art.kind === 'entity')
1566
1651
  result[name] = art;
1567
1652
  }
1568
1653
  else {
@@ -1653,7 +1738,7 @@ function toHdbcdsSource(csn, options) {
1653
1738
  function quoteId(id) {
1654
1739
  // Should only ever be called for real IDs (i.e. no dots inside)
1655
1740
  if (id.indexOf('.') !== -1)
1656
- throw new Error(id);
1741
+ throw new ModelError(id);
1657
1742
 
1658
1743
 
1659
1744
  switch (options.forHana.names) {
@@ -1676,13 +1761,14 @@ function toHdbcdsSource(csn, options) {
1676
1761
  * @returns {string} Correctly quoted absname
1677
1762
  */
1678
1763
  function quoteAbsoluteNameAsId(absname) {
1764
+ const resultingName = getResultingName(csn, options.sqlMapping, absname);
1765
+
1679
1766
  if (hdbcdsNames) {
1680
- const topLevelName = getRootArtifactName(absname, csn);
1681
- const namespace = getParentNameOf(topLevelName);
1767
+ const namespace = getNamespace(csn, absname);
1682
1768
  if (namespace)
1683
- return `"${(`${namespace}::${absname.substring(namespace.length + 1)}`).replace(/"/g, '""')}"`;
1769
+ return `"${(`${namespace}::${resultingName.substring(namespace.length + 2)}`).replace(/"/g, '""')}"`;
1684
1770
  }
1685
- return `"${absname.replace(/"/g, '""')}"`;
1771
+ return `"${resultingName.replace(/"/g, '""')}"`;
1686
1772
  }
1687
1773
 
1688
1774
  /**
@@ -1737,7 +1823,7 @@ function toHdbcdsSource(csn, options) {
1737
1823
  function renderArtifactName(artifactName, env, fallthrough = false) {
1738
1824
  if (plainNames && !fallthrough)
1739
1825
  return formatIdentifier(uppercaseAndUnderscore(artifactName));
1740
- // hdbcds with quoted or hdbcds naming
1826
+ // hdbcds with quoted or hdbcds naming
1741
1827
  return env.namePrefix + quoteId(getRealName(csn, artifactName).replace(/\./g, '_'));
1742
1828
  }
1743
1829
 
@@ -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;