@sap/cds-compiler 3.9.4 → 4.0.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 (95) hide show
  1. package/CHANGELOG.md +107 -4
  2. package/README.md +0 -1
  3. package/bin/cdsc.js +11 -23
  4. package/bin/cdsse.js +3 -3
  5. package/doc/API.md +5 -0
  6. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  7. package/doc/CHANGELOG_BETA.md +17 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +28 -0
  9. package/lib/api/.eslintrc.json +1 -1
  10. package/lib/api/main.js +55 -9
  11. package/lib/api/options.js +2 -0
  12. package/lib/base/error.js +2 -0
  13. package/lib/base/message-registry.js +143 -64
  14. package/lib/base/messages.js +213 -107
  15. package/lib/base/model.js +11 -11
  16. package/lib/checks/.eslintrc.json +1 -1
  17. package/lib/checks/annotationsOData.js +2 -2
  18. package/lib/checks/elements.js +1 -1
  19. package/lib/checks/enricher.js +26 -3
  20. package/lib/checks/onConditions.js +67 -12
  21. package/lib/checks/queryNoDbArtifacts.js +106 -105
  22. package/lib/checks/sql-snippets.js +2 -0
  23. package/lib/checks/types.js +12 -6
  24. package/lib/checks/validator.js +2 -2
  25. package/lib/compiler/assert-consistency.js +10 -8
  26. package/lib/compiler/builtins.js +8 -2
  27. package/lib/compiler/checks.js +52 -35
  28. package/lib/compiler/define.js +31 -26
  29. package/lib/compiler/extend.js +120 -65
  30. package/lib/compiler/finalize-parse-cdl.js +12 -43
  31. package/lib/compiler/generate.js +16 -5
  32. package/lib/compiler/index.js +8 -5
  33. package/lib/compiler/kick-start.js +4 -3
  34. package/lib/compiler/populate.js +96 -95
  35. package/lib/compiler/propagator.js +7 -8
  36. package/lib/compiler/resolve.js +377 -103
  37. package/lib/compiler/shared.js +794 -517
  38. package/lib/compiler/tweak-assocs.js +8 -6
  39. package/lib/compiler/utils.js +44 -0
  40. package/lib/edm/annotations/genericTranslation.js +41 -5
  41. package/lib/edm/csn2edm.js +34 -32
  42. package/lib/edm/edm.js +34 -31
  43. package/lib/edm/edmAnnoPreprocessor.js +0 -23
  44. package/lib/edm/edmInboundChecks.js +7 -2
  45. package/lib/edm/edmPreprocessor.js +25 -18
  46. package/lib/edm/edmUtils.js +8 -4
  47. package/lib/gen/Dictionary.json +18 -0
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +4 -2
  50. package/lib/gen/languageParser.js +5006 -4582
  51. package/lib/json/from-csn.js +157 -112
  52. package/lib/json/to-csn.js +60 -89
  53. package/lib/language/antlrParser.js +17 -13
  54. package/lib/language/docCommentParser.js +11 -1
  55. package/lib/language/genericAntlrParser.js +13 -10
  56. package/lib/language/language.g4 +168 -97
  57. package/lib/main.d.ts +128 -36
  58. package/lib/main.js +1 -1
  59. package/lib/model/csnRefs.js +24 -5
  60. package/lib/model/csnUtils.js +9 -8
  61. package/lib/model/revealInternalProperties.js +7 -12
  62. package/lib/model/sortViews.js +4 -2
  63. package/lib/modelCompare/compare.js +1 -1
  64. package/lib/modelCompare/utils/filter.js +40 -2
  65. package/lib/optionProcessor.js +0 -3
  66. package/lib/render/toCdl.js +247 -214
  67. package/lib/render/toHdbcds.js +197 -181
  68. package/lib/render/toSql.js +325 -289
  69. package/lib/render/utils/common.js +42 -4
  70. package/lib/render/utils/delta.js +1 -1
  71. package/lib/render/utils/sql.js +3 -3
  72. package/lib/transform/braceExpression.js +2 -2
  73. package/lib/transform/db/.eslintrc.json +1 -1
  74. package/lib/transform/db/applyTransformations.js +3 -3
  75. package/lib/transform/db/associations.js +24 -12
  76. package/lib/transform/db/expansion.js +17 -18
  77. package/lib/transform/db/flattening.js +17 -21
  78. package/lib/transform/db/rewriteCalculatedElements.js +171 -64
  79. package/lib/transform/db/views.js +3 -4
  80. package/lib/transform/draft/db.js +21 -12
  81. package/lib/transform/draft/odata.js +4 -0
  82. package/lib/transform/forOdataNew.js +62 -47
  83. package/lib/transform/forRelationalDB.js +12 -7
  84. package/lib/transform/localized.js +4 -2
  85. package/lib/transform/odata/toFinalBaseType.js +5 -5
  86. package/lib/transform/odata/typesExposure.js +3 -3
  87. package/lib/transform/parseExpr.js +3 -0
  88. package/lib/transform/transformUtilsNew.js +43 -23
  89. package/lib/transform/translateAssocsToJoins.js +7 -6
  90. package/lib/transform/universalCsn/.eslintrc.json +1 -1
  91. package/lib/transform/universalCsn/coreComputed.js +7 -5
  92. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
  93. package/package.json +2 -2
  94. package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
  95. package/share/messages/message-explanations.json +1 -1
@@ -26,6 +26,42 @@ const { ModelError, CompilerAssertion } = require('../base/error');
26
26
  const $PROJECTION = '$projection';
27
27
  const $SELF = '$self';
28
28
 
29
+
30
+ // TODO: Unify with other RenderEnvironments
31
+ class HdbcdsRenderEnvironment {
32
+ indent = '';
33
+ path = null;
34
+ /**
35
+ * Dictionary of aliases for used artifact names, each entry like 'name' : { quotedName, quotedAlias }
36
+ * @type {{[name: string]: {
37
+ * quotedName: string,
38
+ * quotedAlias: string
39
+ * }}}
40
+ */
41
+ topLevelAliases = Object.create(null);
42
+ // Current name prefix (including trailing dot if not empty)
43
+ namePrefix = '';
44
+ // Skip rendering keys in subqueries
45
+ skipKeys = false;
46
+ currentArtifactName = null;
47
+ // The original view artifact, used when rendering queries
48
+ _artifact = null;
49
+
50
+ constructor(values) {
51
+ Object.assign(this, values);
52
+ }
53
+
54
+ withIncreasedIndent() {
55
+ return new HdbcdsRenderEnvironment({ ...this, namePrefix: '', indent: ` ${this.indent}` });
56
+ }
57
+ withSubPath(path) {
58
+ return new HdbcdsRenderEnvironment({ ...this, path: [ ...this.path, ...path ] });
59
+ }
60
+ cloneWith(values) {
61
+ return Object.assign(new HdbcdsRenderEnvironment(this), values);
62
+ }
63
+ }
64
+
29
65
  /**
30
66
  * Get the comment and in addition escape \n and `'` so SAP HANA CDS can handle it.
31
67
  *
@@ -33,7 +69,9 @@ const $SELF = '$self';
33
69
  * @returns {string}
34
70
  */
35
71
  function getEscapedHanaComment( obj ) {
36
- return getHanaComment(obj).replace(/\n/g, '\\n').replace(/'/g, "''");
72
+ return getHanaComment(obj)
73
+ .replace(/\n/g, '\\n')
74
+ .replace(/'/g, "''");
37
75
  }
38
76
 
39
77
  /**
@@ -66,7 +104,7 @@ function toHdbcdsSource( csn, options ) {
66
104
  const exprRenderer = createExpressionRenderer({
67
105
  finalize: x => x,
68
106
  typeCast(x) {
69
- let typeRef = renderTypeReference(x.cast, this.env);
107
+ let typeRef = renderTypeReference(x.cast, this.env.withSubPath([ 'cast' ]));
70
108
  // inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
71
109
  const hanaSqlType = cdsToSqlTypes.hana[x.cast.type] || cdsToSqlTypes.standard[x.cast.type];
72
110
  if (hanaSqlType) {
@@ -82,17 +120,18 @@ function toHdbcdsSource( csn, options ) {
82
120
  windowFunction: renderExpressionFunc,
83
121
  func: renderExpressionFunc,
84
122
  xpr(x) {
123
+ const xprEnv = this.env.withSubPath([ 'xpr' ]);
85
124
  if (this.isNestedXpr && !x.cast)
86
- return `(${this.renderSubExpr(x.xpr)})`;
87
- return this.renderSubExpr(x.xpr);
125
+ return `(${this.renderSubExpr(x.xpr, xprEnv)})`;
126
+ return this.renderSubExpr(x.xpr, xprEnv);
88
127
  },
89
128
  SELECT(x) {
90
- return `(${renderQuery(x, false, increaseIndent(this.env))})`;
129
+ return `(${renderQuery(x, false, this.env.withIncreasedIndent())})`;
91
130
  },
92
131
  SET(x) {
93
- return `${renderQuery(x, false, increaseIndent(this.env))}`;
132
+ return `${renderQuery(x, false, this.env.withIncreasedIndent())}`;
94
133
  },
95
- });
134
+ }, true);
96
135
 
97
136
  function renderExpr( x, env ) {
98
137
  return exprRenderer.renderExpr(x, env);
@@ -118,7 +157,7 @@ function toHdbcdsSource( csn, options ) {
118
157
  // This environment is passed down the call hierarchy, for dealing with
119
158
  // indentation and name resolution issues
120
159
  const env = createEnv();
121
- const sourceStr = renderArtifact(artifactName, art, env); // Must come first because it populates 'env.topLevelAliases'
160
+ const sourceStr = renderDefinition(artifactName, art, env); // Must come first because it populates 'env.topLevelAliases'
122
161
  if (sourceStr !== '') {
123
162
  const name = plainNames ? artifactName.replace(/\./g, '_').toUpperCase() : artifactName;
124
163
  hdbcds[name] = [
@@ -138,7 +177,7 @@ function toHdbcdsSource( csn, options ) {
138
177
  Object.entries(art.$tableConstraints.referential)
139
178
  .forEach(([ fileName, referentialConstraint ]) => {
140
179
  referentialConstraints[fileName] = renderReferentialConstraint(
141
- referentialConstraint, increaseIndent(createEnv()).indent, false, csn, options
180
+ referentialConstraint, createEnv().withIncreasedIndent().indent, false, csn, options
142
181
  );
143
182
  });
144
183
  Object.entries(referentialConstraints)
@@ -175,14 +214,14 @@ function toHdbcdsSource( csn, options ) {
175
214
  }
176
215
 
177
216
  /**
178
- * Render an artifact. Return the resulting source string.
217
+ * Render a definition. Return the resulting source string.
179
218
  *
180
219
  * @param {string} artifactName Name of the artifact to render
181
220
  * @param {CSN.Artifact} art Content of the artifact to render
182
- * @param {CdlRenderEnvironment} env Environment
221
+ * @param {HdbcdsRenderEnvironment} env Environment
183
222
  * @returns {string} The rendered artifact
184
223
  */
185
- function renderArtifact( artifactName, art, env ) {
224
+ function renderDefinition( artifactName, art, env ) {
186
225
  // We're always a top-level artifact.
187
226
  env.path = [ 'definitions', artifactName ];
188
227
  // Ignore whole artifacts if toHana says so
@@ -203,7 +242,7 @@ function toHdbcdsSource( csn, options ) {
203
242
 
204
243
  case 'context':
205
244
  case 'service':
206
- return renderContext(artifactName, art, env);
245
+ return renderContext(artifactName, art, env, false);
207
246
  case 'namespace':
208
247
  return renderNamespace(artifactName, art, env);
209
248
  case 'type':
@@ -322,7 +361,8 @@ function toHdbcdsSource( csn, options ) {
322
361
  *
323
362
  * @param {string} artifactName Name of the context/service
324
363
  * @param {CSN.Artifact} art Content of the context/service
325
- * @param {CdlRenderEnvironment} env Environment
364
+ * @param {HdbcdsRenderEnvironment} env Environment
365
+ * @param {boolean} isShadowed
326
366
  * @returns {string} The rendered context/service
327
367
  */
328
368
  function renderContext( artifactName, art, env, isShadowed ) {
@@ -332,24 +372,25 @@ function toHdbcdsSource( csn, options ) {
332
372
  if (isShadowed) {
333
373
  const subArtifacts = getSubArtifacts(artifactName);
334
374
  for (const name in subArtifacts)
335
- result += renderArtifact(`${artifactName}.${name}`, subArtifacts[name], env);
375
+ result += renderDefinition(`${artifactName}.${name}`, subArtifacts[name], env);
336
376
 
337
377
  return `${result}\n`;
338
378
  }
339
379
 
340
- const childEnv = increaseIndent(env);
380
+ const childEnv = env.withIncreasedIndent();
341
381
  result += `${env.indent}context ${renderArtifactName(artifactName, env, true)}`;
342
382
  result += ' {\n';
343
383
  const subArtifacts = getSubArtifacts(artifactName);
344
384
  let renderedSubArtifacts = '';
345
385
  for (const name in subArtifacts)
346
- renderedSubArtifacts += renderArtifact(`${artifactName}.${name}`, subArtifacts[name], updatePrefixForDottedName(childEnv, name));
386
+ renderedSubArtifacts += renderDefinition(`${artifactName}.${name}`, subArtifacts[name], updatePrefixForDottedName(childEnv, name));
347
387
 
348
388
  if (renderedSubArtifacts === '')
349
389
  return '';
350
390
 
351
391
  return `${result + renderedSubArtifacts + env.indent}};\n`;
352
392
  }
393
+
353
394
  /**
354
395
  * Check whether the given context is shadowed, i.e. part of his name prefix is shared by a
355
396
  * non-context/service/namespace definition
@@ -375,9 +416,9 @@ function toHdbcdsSource( csn, options ) {
375
416
  * In case of an artifact with . in the name (that are not a namespace/context part),
376
417
  * we need to update the env to correctly render the artifact name.
377
418
  *
378
- * @param {CdlRenderEnvironment} env Environment
419
+ * @param {HdbcdsRenderEnvironment} env Environment
379
420
  * @param {string} name Possibly dotted artifact name
380
- * @returns {CdlRenderEnvironment} Updated env or original instance
421
+ * @returns {HdbcdsRenderEnvironment} Updated env or original instance
381
422
  */
382
423
  function updatePrefixForDottedName( env, name ) {
383
424
  if (plainNames) {
@@ -398,7 +439,7 @@ function toHdbcdsSource( csn, options ) {
398
439
  *
399
440
  * @param {string} artifactName Name of the namespace
400
441
  * @param {CSN.Artifact} art Content of the namespace
401
- * @param {CdlRenderEnvironment} env Environment
442
+ * @param {HdbcdsRenderEnvironment} env Environment
402
443
  * @returns {string} The rendered children of the namespace
403
444
  */
404
445
  function renderNamespace( artifactName, art, env ) {
@@ -408,22 +449,22 @@ function toHdbcdsSource( csn, options ) {
408
449
  const childEnv = addNamePrefix(env, getLastPartOf(artifactName));
409
450
  const subArtifacts = getSubArtifacts(artifactName);
410
451
  for (const name in subArtifacts)
411
- result += renderArtifact(`${artifactName}.${name}`, subArtifacts[name], updatePrefixForDottedName(childEnv, name));
452
+ result += renderDefinition(`${artifactName}.${name}`, subArtifacts[name], updatePrefixForDottedName(childEnv, name));
412
453
 
413
454
  return result;
414
455
  }
415
456
 
416
457
  /**
417
- * Render a (non-projection, non-view) entity. Return the resulting source string.
458
+ * Render a non-query entity. Return the resulting source string.
418
459
  *
419
460
  * @param {string} artifactName Name of the entity
420
461
  * @param {CSN.Artifact} art Content of the entity
421
- * @param {CdlRenderEnvironment} env Environment
462
+ * @param {HdbcdsRenderEnvironment} env Environment
422
463
  * @returns {string} The rendered entity
423
464
  */
424
465
  function renderEntity( artifactName, art, env ) {
425
466
  let result = '';
426
- const childEnv = increaseIndent(env);
467
+ const childEnv = env.withIncreasedIndent();
427
468
  const normalizedArtifactName = renderArtifactName(artifactName, env);
428
469
 
429
470
  globalDuplicateChecker.addArtifact(art['@cds.persistence.name'], env.path, artifactName);
@@ -440,17 +481,16 @@ function toHdbcdsSource( csn, options ) {
440
481
  result += `${env.indent + (art.abstract ? 'abstract ' : '')}entity ${normalizedArtifactName}`;
441
482
  if (art.includes) {
442
483
  // Includes are never flattened (don't exist in HANA)
443
- result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name, env)).join(', ')}`;
484
+ result += ` : ${art.includes.map((name, i) => renderAbsoluteNameWithQuotes(name, env.withSubPath([ 'includes', i ]))).join(', ')}`;
444
485
  }
445
486
  result += ' {\n';
446
487
  const duplicateChecker = new DuplicateChecker(); // registry for all artifact names and element names
447
488
  duplicateChecker.addArtifact(artifactName, env.path, artifactName);
448
- childEnv.path = env.path.concat('elements');
449
489
  // calculate __aliases which must be used in case an association
450
490
  // has the same identifier as it's target
451
491
  createTopLevelAliasesForArtifact(artifactName, art, env);
452
492
  for (const name in art.elements)
453
- result += renderElement(name, art.elements[name], childEnv, duplicateChecker);
493
+ result += renderElement(name, art.elements[name], childEnv.withSubPath([ 'elements', name ]), duplicateChecker);
454
494
 
455
495
  duplicateChecker.check(error);
456
496
  result += `${env.indent}}`;
@@ -464,11 +504,11 @@ function toHdbcdsSource( csn, options ) {
464
504
 
465
505
  /**
466
506
  * If an association/composition has the same identifier as it's target
467
- * we must render an "using target as __target" and use the alias to refer to the target
507
+ * we must render a "using target as __target" and use the alias to refer to the target
468
508
  *
469
509
  * @param {string} artName
470
510
  * @param {CSN.Artifact} art
471
- * @param {CdlRenderEnvironment} env
511
+ * @param {HdbcdsRenderEnvironment} env
472
512
  */
473
513
  function createTopLevelAliasesForArtifact( artName, art, env ) {
474
514
  forEachMember(art, (element) => {
@@ -492,22 +532,21 @@ function toHdbcdsSource( csn, options ) {
492
532
  * Render the 'technical configuration { ... }' section 'tc' of an entity.
493
533
  *
494
534
  * @param {object} tc content of the technical configuration
495
- * @param {CdlRenderEnvironment} env Environment
535
+ * @param {HdbcdsRenderEnvironment} env Environment
496
536
  * @returns {string} Return the resulting source string.
497
537
  */
498
538
  function renderTechnicalConfiguration( tc, env ) {
499
- let result = '';
500
- const childEnv = increaseIndent(env);
501
-
502
539
  if (!tc)
503
- return result;
540
+ return '';
504
541
 
542
+ let result = '';
543
+ const childEnv = env.withIncreasedIndent();
505
544
 
506
545
  // FIXME: How to deal with non-HANA technical configurations? We should probably just iterate all entries
507
546
  // in 'tc' that we find and render them all (is it syntactically allowed yet to have more than one?)
508
547
  tc = tc.hana;
509
548
  if (!tc)
510
- throw new ModelError('Expecting a HANA technical configuration');
549
+ throw new ModelError('Expecting a SAP HANA technical configuration');
511
550
 
512
551
  result += `\n${env.indent}technical ${tc.calculated ? '' : 'hana '}configuration {\n`;
513
552
 
@@ -580,7 +619,7 @@ function toHdbcdsSource( csn, options ) {
580
619
  *
581
620
  * @param {string} elementName Name of the element
582
621
  * @param {CSN.Element} elm Content of the element
583
- * @param {CdlRenderEnvironment} env Environment
622
+ * @param {HdbcdsRenderEnvironment} env Environment
584
623
  * @param {DuplicateChecker} [duplicateChecker] Utility for detecting duplicates
585
624
  * @param {boolean} [isSubElement] Whether the given element is a subelement or not - subelements cannot be key!
586
625
  * @returns {string} The rendered element
@@ -603,10 +642,14 @@ function toHdbcdsSource( csn, options ) {
603
642
  result += env.indent + (elm.key && !isSubElement ? 'key ' : '') +
604
643
  (elm.masked ? 'masked ' : '') +
605
644
  quotedElementName + (omitColon ? ' ' : ' : ') +
606
- renderTypeReference(elm, env) +
607
- renderNullability(elm);
608
- if (elm.default)
609
- result += ` default ${renderExpr(elm.default, env)}`;
645
+ renderTypeReference(elm, env);
646
+ // GENERATED AS ALWAYS() can't have a trailing "[not] null" nor "default".
647
+ // Because we already emit an error that calc-on-write is not supported, just ignore nullability/default.
648
+ if (!elm.value?.stored) {
649
+ result += renderNullability(elm);
650
+ if (elm.default)
651
+ result += ` default ${renderExpr(elm.default, env.withSubPath([ 'default' ]))}`;
652
+ }
610
653
 
611
654
  // (table) elements can only have a @sql.append
612
655
  const { back } = getSqlSnippets(options, elm);
@@ -623,13 +666,13 @@ function toHdbcdsSource( csn, options ) {
623
666
  * Returns the source as a string.
624
667
  *
625
668
  * @param {object} source Source to render
626
- * @param {CdlRenderEnvironment} env Environment
669
+ * @param {HdbcdsRenderEnvironment} env Environment
627
670
  * @returns {string} Rendered view source
628
671
  */
629
672
  function renderViewSource( source, env ) {
630
673
  // Sub-SELECT
631
674
  if (source.SELECT || source.SET) {
632
- let result = `(${renderQuery(source, false, increaseIndent(env))})`;
675
+ let result = `(${renderQuery(source, false, env.withIncreasedIndent())})`;
633
676
  if (source.as)
634
677
  result += ` as ${formatIdentifier(source.as)}`;
635
678
  return result;
@@ -637,12 +680,12 @@ function toHdbcdsSource( csn, options ) {
637
680
  // JOIN
638
681
  else if (source.join) {
639
682
  // One join operation, possibly with ON-condition
640
- let result = `${renderViewSource(source.args[0], env)}`;
683
+ let result = `${renderViewSource(source.args[0], env.withSubPath([ 'args', 0 ]))}`;
641
684
  for (let i = 1; i < source.args.length; i++) {
642
685
  result = `(${result} ${source.join} `;
643
- result += `join ${renderViewSource(source.args[i], env)}`;
686
+ result += `join ${renderViewSource(source.args[i], env.withSubPath([ 'args', i ]))}`;
644
687
  if (source.on)
645
- result += ` on ${renderExpr(source.on, env)}`;
688
+ result += ` on ${renderExpr(source.on, env.withSubPath([ 'on' ]))}`;
646
689
 
647
690
  result += ')';
648
691
  }
@@ -659,7 +702,7 @@ function toHdbcdsSource( csn, options ) {
659
702
  * Returns the name as a string.
660
703
  *
661
704
  * @param {object} path Path to render
662
- * @param {CdlRenderEnvironment} env Environment
705
+ * @param {HdbcdsRenderEnvironment} env Environment
663
706
  * @returns {string} Rendered path
664
707
  */
665
708
  function renderAbsolutePath( path, env ) {
@@ -680,11 +723,11 @@ function toHdbcdsSource( csn, options ) {
680
723
 
681
724
  // Even the first step might have parameters and/or a filter
682
725
  if (path.ref[0].args)
683
- result += `(${renderArgs(path.ref[0], ':', env)})`;
726
+ result += `(${renderArgs(path.ref[0], ':', env.withSubPath([ 'ref', 0 ]))})`;
684
727
 
685
728
  if (path.ref[0].where) {
686
729
  const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
687
- result += `[${cardinality}${renderExpr(path.ref[0].where, env)}]`;
730
+ result += `[${cardinality}${renderExpr(path.ref[0].where, env.withSubPath([ 'ref', 0, 'where' ]))}]`;
688
731
  }
689
732
 
690
733
  // Add any path steps (possibly with parameters and filters) that may follow after that
@@ -702,7 +745,7 @@ function toHdbcdsSource( csn, options ) {
702
745
  * Returns the name and alias as a string.
703
746
  *
704
747
  * @param {object} path Path to render
705
- * @param {CdlRenderEnvironment} env Environment
748
+ * @param {HdbcdsRenderEnvironment} env Environment
706
749
  * @returns {string} Rendered path including alias
707
750
  */
708
751
  function renderAbsolutePathWithAlias( path, env ) {
@@ -727,37 +770,33 @@ function toHdbcdsSource( csn, options ) {
727
770
  *
728
771
  * @param {object} col Column to render
729
772
  * @param {CSN.Elements} elements where column exists
730
- * @param {CdlRenderEnvironment} env Environment
773
+ * @param {HdbcdsRenderEnvironment} env Environment
731
774
  * @returns {string} Rendered column
732
775
  */
733
776
  function renderViewColumn( col, elements, env ) {
734
- // Annotations and column
735
- let result = '';
736
-
737
777
  const leaf = col.as || col.ref && col.ref[col.ref.length - 1] || col.func;
738
778
  const element = elements[leaf];
739
779
 
740
780
  // Render 'null as <alias>' only for database and if element is virtual
741
- if (element && element.virtual) {
781
+ if (element?.virtual) {
742
782
  if (isDeprecatedEnabled(options, '_renderVirtualElements'))
743
- return `${result}${env.indent}null as ${formatIdentifier(leaf)}`;
744
- }
745
- else {
746
- return renderNonVirtualColumn();
783
+ return `${env.indent}null as ${formatIdentifier(leaf)}`;
784
+ return '';
747
785
  }
748
786
 
749
- return result;
787
+ return renderNonVirtualColumn();
788
+
750
789
 
751
790
  function renderNonVirtualColumn() {
752
- result += env.indent;
791
+ let result = env.indent;
753
792
  // only if column is virtual, keyword virtual was present in the source text
754
793
  if (col.virtual)
755
794
  result += 'virtual ';
756
795
  // If key is explicitly set in a non-leading query, issue an error.
757
796
  if (col.key && env.skipKeys)
758
- error(null, env.path, { tokensymbol: 'key', $reviewed: true }, 'Unexpected $(TOKENSYMBOL) in subquery');
797
+ error(null, env.path, { keyword: 'key', $reviewed: true }, 'Unexpected $(KEYWORD) in subquery');
759
798
 
760
- const key = (!env.skipKeys && (col.key || (element && element.key)) ? 'key ' : '');
799
+ const key = (!env.skipKeys && (col.key || element?.key) ? 'key ' : '');
761
800
  result += key + renderExpr(withoutCast(col), env);
762
801
  let alias = col.as || col.func;
763
802
  // HANA requires an alias for 'key' columns just for syntactical reasons
@@ -771,12 +810,12 @@ function toHdbcdsSource( csn, options ) {
771
810
  result += ` as ${formatIdentifier(alias)}`;
772
811
 
773
812
  // Explicit type provided for the view element?
774
- if (col.cast && col.cast.target) {
813
+ if (col.cast?.target) {
775
814
  // Special case: Explicit association type is actually a redirect
776
815
  // Redirections are never flattened (don't exist in HANA)
777
- result += ` : redirected to ${renderAbsoluteNameWithQuotes(col.cast.target, env)}`;
816
+ result += ` : redirected to ${renderAbsoluteNameWithQuotes(col.cast.target, env.withSubPath([ 'cast', 'target' ]))}`;
778
817
  if (col.cast.on)
779
- result += ` on ${renderExpr(col.cast.on, env)}`;
818
+ result += ` on ${renderExpr(col.cast.on, env.withSubPath([ 'cast', 'on' ]))}`;
780
819
  }
781
820
 
782
821
  return result;
@@ -790,7 +829,7 @@ function toHdbcdsSource( csn, options ) {
790
829
  *
791
830
  * @param {string} artifactName Name of the artifact
792
831
  * @param {CSN.Artifact} art Content of the artifact
793
- * @param {CdlRenderEnvironment} env Environment
832
+ * @param {HdbcdsRenderEnvironment} env Environment
794
833
  * @returns {string} The rendered view
795
834
  */
796
835
  function renderView( artifactName, art, env ) {
@@ -803,16 +842,18 @@ function toHdbcdsSource( csn, options ) {
803
842
 
804
843
  result += `${env.indent}${art.abstract ? 'abstract ' : ''}view ${renderArtifactName(artifactName, env)}`;
805
844
  if (art.params) {
806
- const childEnv = increaseIndent(env);
807
- const parameters = Object.keys(art.params).map(name => renderParameter(name, art.params[name], childEnv)).join(',\n');
808
- // HANA only understands the 'with parameters' syntax'
845
+ const childEnv = env.withIncreasedIndent();
846
+ const parameters = Object.keys(art.params)
847
+ .map(name => renderParameter(name, art.params[name], childEnv.withSubPath([ 'params', name ])))
848
+ .join(',\n');
849
+ // SAP HANA only understands the 'with parameters' syntax'
809
850
  result += ` with parameters\n${parameters}\n${env.indent}as `;
810
851
  }
811
852
  else {
812
853
  result += ' as ';
813
854
  }
814
855
  env._artifact = art;
815
- result += renderQuery(getNormalizedQuery(art).query, true, env, artifactPath.concat(art.projection ? 'projection' : 'query'), art.elements);
856
+ result += renderQuery(getNormalizedQuery(art).query, true, env.withSubPath([ art.projection ? 'projection' : 'query' ]), art.elements);
816
857
 
817
858
  // views can only have a @sql.append
818
859
  const { back } = getSqlSnippets(options, art);
@@ -832,47 +873,52 @@ function toHdbcdsSource( csn, options ) {
832
873
  *
833
874
  * @param {CSN.Query} query Query object
834
875
  * @param {boolean} isLeadingQuery Whether the query is the leading query or not
835
- * @param {CdlRenderEnvironment} env Environment
836
- * @param {CSN.Path} [path=[]] CSN path to the query
876
+ * @param {HdbcdsRenderEnvironment} env Environment
837
877
  * @param {object} [elements] For leading query, the elements of the artifact
838
878
  * @returns {string} The rendered query
839
879
  */
840
- function renderQuery( query, isLeadingQuery, env, path = [], elements = null ) {
880
+ function renderQuery( query, isLeadingQuery, env, elements = null ) {
881
+ const isProjection = env.path[env.path.length - 1] === 'projection';
841
882
  let result = '';
842
883
  env.skipKeys = !isLeadingQuery;
843
884
  // Set operator, like UNION, INTERSECT, ...
844
885
  if (query.SET) {
845
886
  // First arg may be leading query
846
- result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env, path.concat([ 'SET', 'args', 0 ]), elements || query.SET.elements)}`;
887
+ result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env.withSubPath([ 'SET', 'args', 0 ]), elements || query.SET.elements)}`;
847
888
  // FIXME: Clarify if set operators can be n-ary (assuming binary here)
848
889
  if (query.SET.op) {
849
890
  // Loop over all other arguments, i.e. for A UNION B UNION C UNION D ...
850
891
  for (let i = 1; i < query.SET.args.length; i++)
851
- result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[i], false, env, path.concat([ 'SET', 'args', i ]), elements || query.SET.elements)}`;
892
+ result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[i], false, env.withSubPath([ 'SET', 'args', i ]), elements || query.SET.elements)}`;
852
893
  }
853
894
  result += ')';
854
895
  // Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
855
896
  // each SELECT)
856
897
  if (query.SET.orderBy)
857
- result += `${continueIndent(result, env)}order by ${query.SET.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
898
+ result += `${continueIndent(result, env)}order by ${query.SET.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'SET', 'orderBy', i ]))).join(', ')}`;
858
899
 
859
900
  if (query.SET.limit)
860
- result += `${continueIndent(result, env)}${renderLimit(query.SET.limit, env)}`;
901
+ result += `${continueIndent(result, env)}${renderLimit(query.SET.limit, env.withSubPath([ 'SET', 'limit' ]))}`;
861
902
 
862
903
  return result;
863
904
  }
864
905
  // Otherwise must have a SELECT
865
906
  else if (!query.SELECT) {
866
- throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
907
+ throw new ModelError(`Unexpected query operation ${JSON.stringify(query)} at ${JSON.stringify(env.path)}`);
867
908
  }
909
+
910
+ if (!isProjection)
911
+ env = env.withSubPath([ 'SELECT' ]);
912
+
868
913
  const select = query.SELECT;
869
- const childEnv = increaseIndent(env);
914
+ result += `select from ${renderViewSource(select.from, env.withSubPath([ 'from' ]))}`;
915
+
916
+ const childEnv = env.withIncreasedIndent();
870
917
  childEnv.currentArtifactName = $PROJECTION; // $self to be replaced by $projection
871
- result += `select from ${renderViewSource(select.from, env)}`;
872
918
  if (select.mixin) {
873
919
  let elems = '';
874
920
  for (const name in select.mixin)
875
- elems += renderElement(name, select.mixin[name], childEnv);
921
+ elems += renderElement(name, select.mixin[name], childEnv.withSubPath([ 'mixin', name ]));
876
922
 
877
923
  if (elems) {
878
924
  result += ' mixin {\n';
@@ -883,10 +929,11 @@ function toHdbcdsSource( csn, options ) {
883
929
  result += select.distinct ? ' distinct' : '';
884
930
  if (select.columns) {
885
931
  result += ' {\n';
886
- result += `${select.columns.map(col => renderViewColumn(col, elements || select.elements, childEnv))
932
+ result += select.columns
933
+ .map((col, i) => renderViewColumn(col, elements || select.elements, childEnv.withSubPath([ 'columns', i ])))
887
934
  .filter(s => s !== '')
888
- .join(',\n')}\n`;
889
- result += `${env.indent}}`;
935
+ .join(',\n');
936
+ result += `\n${env.indent}}`;
890
937
  }
891
938
  if (select.excluding) {
892
939
  const excludingList = select.excluding.map(id => `${childEnv.indent}${formatIdentifier(id)}`).join(',\n');
@@ -902,24 +949,24 @@ function toHdbcdsSource( csn, options ) {
902
949
  *
903
950
  * @param {CSN.QuerySelect} select
904
951
  * @param {string} alreadyRendered The query as it has been rendered so far
905
- * @param {CdlRenderEnvironment} env Environment
952
+ * @param {HdbcdsRenderEnvironment} env Environment
906
953
  * @returns {string} The query with WHERE etc. added
907
954
  */
908
955
  function renderSelectProperties( select, alreadyRendered, env ) {
909
956
  if (select.where)
910
- alreadyRendered += `${continueIndent(alreadyRendered, env)}where ${renderExpr(select.where, env)}`;
957
+ alreadyRendered += `${continueIndent(alreadyRendered, env)}where ${renderExpr(select.where, env.withSubPath([ 'where' ]))}`;
911
958
 
912
959
  if (select.groupBy)
913
- alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
960
+ alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map((expr, i) => renderExpr(expr, env.withSubPath([ 'groupBy', i ]))).join(', ')}`;
914
961
 
915
962
  if (select.having)
916
- alreadyRendered += `${continueIndent(alreadyRendered, env)}having ${renderExpr(select.having, env)}`;
963
+ alreadyRendered += `${continueIndent(alreadyRendered, env)}having ${renderExpr(select.having, env.withSubPath([ 'having' ]))}`;
917
964
 
918
965
  if (select.orderBy)
919
- alreadyRendered += `${continueIndent(alreadyRendered, env)}order by ${select.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
966
+ alreadyRendered += `${continueIndent(alreadyRendered, env)}order by ${select.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'orderBy', i ]))).join(', ')}`;
920
967
 
921
968
  if (select.limit)
922
- alreadyRendered += `${continueIndent(alreadyRendered, env)}${renderLimit(select.limit, env)}`;
969
+ alreadyRendered += `${continueIndent(alreadyRendered, env)}${renderLimit(select.limit, env.withSubPath([ 'limit' ]))}`;
923
970
 
924
971
  return alreadyRendered;
925
972
  }
@@ -928,7 +975,7 @@ function toHdbcdsSource( csn, options ) {
928
975
  * Utility function to make sure that we continue with the same indentation in WHERE, GROUP BY, ... after a closing curly brace and beyond
929
976
  *
930
977
  * @param {string} result Result of a previous render step
931
- * @param {CdlRenderEnvironment} env Environment
978
+ * @param {HdbcdsRenderEnvironment} env Environment
932
979
  * @returns {string} String to join with
933
980
  */
934
981
  function continueIndent( result, env ) {
@@ -937,24 +984,24 @@ function toHdbcdsSource( csn, options ) {
937
984
  return ' ';
938
985
  }
939
986
  // Otherwise, start new line and indent normally
940
- return `\n${increaseIndent(env).indent}`;
987
+ return `\n${env.withIncreasedIndent().indent}`;
941
988
  }
942
989
 
943
990
  /**
944
- * Render a query's LIMIT clause, which may have also have OFFSET.
991
+ * Render a query's LIMIT clause, which may also have OFFSET.
945
992
  *
946
993
  * @param {CSN.QueryLimit} limit CSN limit clause
947
- * @param {CdlRenderEnvironment} env Environment
994
+ * @param {HdbcdsRenderEnvironment} env Environment
948
995
  * @returns {string} Rendered limit clause
949
996
  */
950
997
  function renderLimit( limit, env ) {
951
998
  let result = '';
952
999
  if (limit.rows !== undefined)
953
- result += `limit ${renderExpr(limit.rows, env)}`;
1000
+ result += `limit ${renderExpr(limit.rows, env.withSubPath([ 'rows' ]))}`;
954
1001
 
955
1002
  if (limit.offset !== undefined) {
956
- const indent = result !== '' ? `\n${increaseIndent(env).indent}` : '';
957
- result += `${indent}offset ${renderExpr(limit.offset, env)}`;
1003
+ const indent = result !== '' ? `\n${env.withIncreasedIndent().indent}` : '';
1004
+ result += `${indent}offset ${renderExpr(limit.offset, env.withSubPath([ 'offset' ]))}`;
958
1005
  }
959
1006
 
960
1007
  return result;
@@ -965,7 +1012,7 @@ function toHdbcdsSource( csn, options ) {
965
1012
  * have a 'sort' property for ASC/DESC and a 'nulls' for FIRST/LAST
966
1013
  *
967
1014
  * @param {object} entry CSN order by
968
- * @param {CdlRenderEnvironment} env Environment
1015
+ * @param {HdbcdsRenderEnvironment} env Environment
969
1016
  * @returns {string} Rendered order by
970
1017
  */
971
1018
  function renderOrderByEntry( entry, env ) {
@@ -984,13 +1031,13 @@ function toHdbcdsSource( csn, options ) {
984
1031
  *
985
1032
  * @param {string} parName Name of the parameter
986
1033
  * @param {object} par CSN parameter
987
- * @param {CdlRenderEnvironment} env Environment
1034
+ * @param {HdbcdsRenderEnvironment} env Environment
988
1035
  * @returns {string} The resulting parameter as source string (no trailing LF).
989
1036
  */
990
1037
  function renderParameter( parName, par, env ) {
991
1038
  if (par.notNull === true || par.notNull === false)
992
- info('query-ignoring-param-nullability', env.path.concat([ 'params', parName ]), { '#': 'std' });
993
- return `${env.indent + formatParamIdentifier(parName, env.path.concat([ 'params', parName ]))} : ${renderTypeReference(par, env)}`;
1039
+ info('query-ignoring-param-nullability', env.path, { '#': 'std' });
1040
+ return `${env.indent + formatParamIdentifier(parName, env.path)} : ${renderTypeReference(par, env)}`;
994
1041
  }
995
1042
 
996
1043
  /**
@@ -999,7 +1046,7 @@ function toHdbcdsSource( csn, options ) {
999
1046
  *
1000
1047
  * @param {string} artifactName Name of the artifact
1001
1048
  * @param {CSN.Artifact} art Content of the artifact
1002
- * @param {CdlRenderEnvironment} env Environment
1049
+ * @param {HdbcdsRenderEnvironment} env Environment
1003
1050
  * @returns {string} Rendered type/annotation
1004
1051
  */
1005
1052
  function renderType( artifactName, art, env ) {
@@ -1011,12 +1058,12 @@ function toHdbcdsSource( csn, options ) {
1011
1058
  // Includes are never flattened (don't exist in HANA)
1012
1059
  result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name, env)).join(', ')}`;
1013
1060
  }
1014
- const childEnv = increaseIndent(env);
1015
1061
  if (art.elements && !art.type) {
1062
+ const childEnv = env.withIncreasedIndent();
1016
1063
  // Structured type or annotation with anonymous struct type
1017
1064
  result += ' {\n';
1018
1065
  for (const name in art.elements)
1019
- result += renderElement(name, art.elements[name], childEnv);
1066
+ result += renderElement(name, art.elements[name], childEnv.withSubPath([ 'elements', name ]));
1020
1067
 
1021
1068
  result += `${env.indent}};\n`;
1022
1069
  }
@@ -1032,7 +1079,7 @@ function toHdbcdsSource( csn, options ) {
1032
1079
  * Allow suppressing enum-rendering - used in columns for example
1033
1080
  *
1034
1081
  * @param {object} elm Element using the type reference
1035
- * @param {CdlRenderEnvironment} env Environment
1082
+ * @param {HdbcdsRenderEnvironment} env Environment
1036
1083
  * @returns {string} Rendered type reference
1037
1084
  */
1038
1085
  function renderTypeReference( elm, env ) {
@@ -1041,7 +1088,7 @@ function toHdbcdsSource( csn, options ) {
1041
1088
  // Array type: Render items instead
1042
1089
  if (elm.items && !elm.type) {
1043
1090
  // HANA CDS does not support keyword many
1044
- let rc = `array of ${renderTypeReference(elm.items, env)}`;
1091
+ let rc = `array of ${renderTypeReference(elm.items, env.withSubPath([ 'items' ]))}`;
1045
1092
  if (elm.items.notNull != null)
1046
1093
  rc += elm.items.notNull ? ' not null' : ' null';
1047
1094
  return rc;
@@ -1056,11 +1103,11 @@ function toHdbcdsSource( csn, options ) {
1056
1103
  throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
1057
1104
 
1058
1105
  result += '{\n';
1059
- const childEnv = increaseIndent(env);
1106
+ const childEnv = env.withIncreasedIndent();
1060
1107
  // omit "key" keyword for nested elements, as this will result in a deployment error in naming mode 'hdbcds'
1061
1108
  const dontRenderKeyForNestedElement = hdbcdsNames;
1062
1109
  for (const name in elm.elements)
1063
- result += renderElement(name, elm.elements[name], childEnv, null, dontRenderKeyForNestedElement);
1110
+ result += renderElement(name, elm.elements[name], childEnv.withSubPath([ 'elements', name ]), null, dontRenderKeyForNestedElement);
1064
1111
 
1065
1112
  result += `${env.indent}}`;
1066
1113
  return result;
@@ -1070,11 +1117,10 @@ function toHdbcdsSource( csn, options ) {
1070
1117
  if ([ 'cds.Association', 'cds.Composition' ].includes(elm.type))
1071
1118
  return result + renderAssociationType(elm, env);
1072
1119
 
1073
-
1074
1120
  if (elm.type?.ref) {
1075
1121
  // Reference to another element
1076
1122
  // For HANA CDS, we need a 'type of'
1077
- result += `type of ${renderAbsolutePath(elm.type, env)}`;
1123
+ result += `type of ${renderAbsolutePath(elm.type, env.withSubPath([ 'type' ]))}`;
1078
1124
  }
1079
1125
  else if (isBuiltinType(elm.type)) {
1080
1126
  // If we get here, it must be a named type
@@ -1083,14 +1129,14 @@ function toHdbcdsSource( csn, options ) {
1083
1129
  else {
1084
1130
  // Simple absolute name
1085
1131
  // Type names are never flattened (derived types are unraveled in HANA)
1086
- result += renderAbsoluteNameWithQuotes(elm.type, env);
1132
+ result += renderAbsoluteNameWithQuotes(elm.type, env.withSubPath([ 'type' ]));
1087
1133
  }
1088
1134
 
1089
1135
  if (elm.value) {
1090
1136
  if (!elm.value.stored)
1091
1137
  throw new CompilerAssertion('Found calculated element on-read in rendering; should have been replaced!');
1092
1138
  message('def-unsupported-calc-elem', env.path, { '#': 'hdbcds' });
1093
- result += ` GENERATED ALWAYS AS ${renderExpr(elm.value)}`;
1139
+ result += ` GENERATED ALWAYS AS ${renderExpr(elm.value, env.withSubPath([ 'value' ]))}`;
1094
1140
  return result;
1095
1141
  }
1096
1142
 
@@ -1099,7 +1145,7 @@ function toHdbcdsSource( csn, options ) {
1099
1145
 
1100
1146
  /**
1101
1147
  * @param {CSN.Element} elm
1102
- * @param {CdlRenderEnvironment} env
1148
+ * @param {HdbcdsRenderEnvironment} env
1103
1149
  * @returns {string}
1104
1150
  */
1105
1151
  function renderAssociationType( elm, env ) {
@@ -1108,7 +1154,6 @@ function toHdbcdsSource( csn, options ) {
1108
1154
 
1109
1155
  result += `${renderCardinality(elm.cardinality)} to `;
1110
1156
 
1111
-
1112
1157
  // normal target or named aspect
1113
1158
  if (elm.target || elm.targetAspect && typeof elm.targetAspect === 'string') {
1114
1159
  // we might have a "using target as __target"
@@ -1118,28 +1163,28 @@ function toHdbcdsSource( csn, options ) {
1118
1163
  result += targetAlias.quotedAlias;
1119
1164
  }
1120
1165
  else {
1121
- result += plainNames ? renderAbsoluteNamePlain(elm.target || elm.targetAspect, env)
1122
- : renderAbsoluteNameWithQuotes(elm.target || elm.targetAspect, env);
1166
+ const target = elm.target || elm.targetAspect;
1167
+ const childEnv = env.withSubPath([ elm.target ? 'target' : 'targetAspect' ]);
1168
+ result += plainNames ? renderAbsoluteNamePlain(target, childEnv) : renderAbsoluteNameWithQuotes(target, childEnv);
1123
1169
  }
1124
1170
  }
1125
1171
 
1126
1172
  // ON-condition (if any)
1127
1173
  if (elm.on) {
1128
- result += ` on ${renderExpr(elm.on, env)}`;
1174
+ result += ` on ${renderExpr(elm.on, env.withSubPath([ 'on' ]))}`;
1129
1175
  }
1130
- else if (elm.targetAspect && elm.targetAspect.elements) { // anonymous aspect
1131
- const childEnv = increaseIndent(env);
1176
+ else if (elm.targetAspect?.elements) { // anonymous aspect
1177
+ const childEnv = env.withIncreasedIndent();
1132
1178
  result += '{\n';
1133
1179
  for (const name in elm.targetAspect.elements)
1134
- result += renderElement(name, elm.targetAspect.elements[name], childEnv);
1180
+ result += renderElement(name, elm.targetAspect.elements[name], childEnv.withSubPath([ 'targetAspect', 'elements', name ]));
1135
1181
 
1136
1182
  result += `${env.indent}}`;
1137
1183
  }
1138
1184
 
1139
-
1140
1185
  // Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
1141
1186
  if (elm.keys && !elm.on)
1142
- result += ` { ${Object.keys(elm.keys).map(name => renderForeignKey(elm.keys[name], env)).join(', ')} }`;
1187
+ result += ` { ${Object.keys(elm.keys).map(name => renderForeignKey(elm.keys[name], env.withSubPath([ 'keys', name ]))).join(', ')} }`;
1143
1188
 
1144
1189
  return result;
1145
1190
  }
@@ -1164,6 +1209,8 @@ function toHdbcdsSource( csn, options ) {
1164
1209
  *
1165
1210
  * @param {string|object} s Path step
1166
1211
  * @param {number} idx Path position
1212
+ * @param {any[]} ref
1213
+ * @param {HdbcdsRenderEnvironment} env
1167
1214
  * @returns {string} Rendered path step
1168
1215
  */
1169
1216
  function renderPathStep( s, idx, ref, env ) {
@@ -1171,7 +1218,7 @@ function toHdbcdsSource( csn, options ) {
1171
1218
  if (typeof s === 'string') {
1172
1219
  // HANA-specific extra magic (should actually be in forRelationalDB)
1173
1220
  // In HANA, we replace leading $self by the absolute name of the current artifact
1174
- // (see FIXME at renderArtifact)
1221
+ // (see FIXME at renderDefinition)
1175
1222
  if (idx === 0 && s === $SELF) {
1176
1223
  // do not produce USING for $projection
1177
1224
  if (env.currentArtifactName === $PROJECTION)
@@ -1218,12 +1265,12 @@ function toHdbcdsSource( csn, options ) {
1218
1265
  if (s.where) {
1219
1266
  // Filter, possibly with cardinality
1220
1267
  const cardinality = s.cardinality ? `${s.cardinality.max}: ` : '';
1221
- result += `[${cardinality}${renderExpr(s.where, env)}]`;
1268
+ result += `[${cardinality}${renderExpr(s.where, env.withSubPath([ 'where' ]))}]`;
1222
1269
  }
1223
1270
  return result;
1224
1271
  }
1225
1272
 
1226
- throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
1273
+ throw new ModelError(`Unknown path step: ${JSON.stringify(s)} at ${JSON.stringify(env.path)}`);
1227
1274
  }
1228
1275
 
1229
1276
  /**
@@ -1265,7 +1312,7 @@ function toHdbcdsSource( csn, options ) {
1265
1312
  const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
1266
1313
  // we can't quote functions with parens, issue warning if it is a reserved keyword
1267
1314
  if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
1268
- warning(null, x.$location, { id: uppercaseAndUnderscore(funcName) }, 'The identifier $(ID) is a SAP HANA keyword');
1315
+ warning(null, this.env.path, { id: uppercaseAndUnderscore(funcName) }, 'The identifier $(ID) is a SAP HANA keyword');
1269
1316
  return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', this.env));
1270
1317
  }
1271
1318
 
@@ -1301,7 +1348,7 @@ function toHdbcdsSource( csn, options ) {
1301
1348
  return renderStringForHdbcds(magicReplacement);
1302
1349
  }
1303
1350
  }
1304
- return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref, this.env)).join('.')}`;
1351
+ return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref, this.env.withSubPath([ 'ref', index ]))).join('.')}`;
1305
1352
  }
1306
1353
 
1307
1354
  /**
@@ -1310,19 +1357,22 @@ function toHdbcdsSource( csn, options ) {
1310
1357
  *
1311
1358
  * @param {object} node with `args` to render
1312
1359
  * @param {string} sep Separator between arguments
1313
- * @param {CdlRenderEnvironment} env Environment
1360
+ * @param {HdbcdsRenderEnvironment} env Environment
1314
1361
  * @returns {string} Rendered arguments
1315
1362
  */
1316
1363
  function renderArgs( node, sep, env ) {
1317
- const args = node.args ? node.args : {};
1364
+ const args = node.args || {};
1318
1365
  // Positional arguments
1319
- if (Array.isArray(args))
1320
- return args.map(arg => renderExpr(arg, env)).join(', ');
1321
-
1366
+ if (Array.isArray(args)) {
1367
+ return args.map((arg, i) => renderExpr(arg, env.withSubPath([ 'args', i ]))).join(', ');
1368
+ }
1322
1369
  // Named arguments (object/dict)
1323
- else if (typeof args === 'object')
1370
+ else if (typeof args === 'object') {
1324
1371
  // if this is a function param which is not a reference to the model, we must not quote it
1325
- return Object.keys(args).map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1372
+ return Object.keys(args)
1373
+ .map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env.withSubPath([ 'args', key ]))}`)
1374
+ .join(', ');
1375
+ }
1326
1376
 
1327
1377
 
1328
1378
  throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
@@ -1371,7 +1421,7 @@ function toHdbcdsSource( csn, options ) {
1371
1421
  * @todo Can this still happen after Hana transformation?
1372
1422
  *
1373
1423
  * @param {object} fKey Foreign key to render
1374
- * @param {CdlRenderEnvironment} env Environment
1424
+ * @param {HdbcdsRenderEnvironment} env Environment
1375
1425
  * @returns {string} Rendered foreign key
1376
1426
  */
1377
1427
  function renderForeignKey( fKey, env ) {
@@ -1410,7 +1460,7 @@ function toHdbcdsSource( csn, options ) {
1410
1460
  * if necessary.
1411
1461
  *
1412
1462
  * @param {string} absName Absolute name
1413
- * @param {CdlRenderEnvironment} env Environment
1463
+ * @param {HdbcdsRenderEnvironment} env Environment
1414
1464
  * @returns {string} Uppercased and underscored absName
1415
1465
  */
1416
1466
  function renderAbsoluteNamePlain( absName, env ) {
@@ -1428,7 +1478,7 @@ function toHdbcdsSource( csn, options ) {
1428
1478
  * if necessary.
1429
1479
  *
1430
1480
  * @param {string} absName absolute name
1431
- * @param {CdlRenderEnvironment} env Environment
1481
+ * @param {HdbcdsRenderEnvironment} env Environment
1432
1482
  * @returns {string} absName, with correct quotes
1433
1483
  */
1434
1484
  function renderAbsoluteNameWithQuotes( absName, env ) {
@@ -1498,7 +1548,7 @@ function toHdbcdsSource( csn, options ) {
1498
1548
  * Render appropriate USING directives for all artifacts used by artifact 'artifactName' in 'env'.
1499
1549
  *
1500
1550
  * @param {string} artifactName Artifact to render usings for
1501
- * @param {CdlRenderEnvironment} env Environment
1551
+ * @param {HdbcdsRenderEnvironment} env Environment
1502
1552
  * @returns {string} Usings for the given artifact
1503
1553
  */
1504
1554
  function renderUsings( artifactName, env ) {
@@ -1581,7 +1631,7 @@ function toHdbcdsSource( csn, options ) {
1581
1631
  * Return the namespace declaration (with trailing LF) or an empty string.
1582
1632
  *
1583
1633
  * @param {string} topLevelName Name of a top-level artifact
1584
- * @param {CdlRenderEnvironment} env Environment
1634
+ * @param {HdbcdsRenderEnvironment} env Environment
1585
1635
  * @returns {string} Rendered namespace declaration
1586
1636
  */
1587
1637
  function renderNamespaceDeclaration( topLevelName, env ) {
@@ -1626,40 +1676,22 @@ function toHdbcdsSource( csn, options ) {
1626
1676
  * Returns a newly created default environment (which keeps track of indentation, required USING
1627
1677
  * declarations and name prefixes.
1628
1678
  *
1629
- * @returns {CdlRenderEnvironment} Fresh environment
1679
+ * @param {object} [values]
1680
+ * @returns {HdbcdsRenderEnvironment} Fresh environment
1630
1681
  */
1631
- function createEnv() {
1632
- return {
1633
- // Current indentation string
1634
- indent: '',
1635
- // Dictionary of aliases for used artifact names, each entry like 'name' : { quotedName, quotedAlias }
1636
- topLevelAliases: Object.create(null),
1637
- // Current name prefix (including trailing dot if not empty)
1638
- namePrefix: '',
1639
- // CSN path - should at least point to the correct artifact
1640
- path: [],
1641
- };
1682
+ function createEnv( values = {} ) {
1683
+ return new HdbcdsRenderEnvironment(values);
1642
1684
  }
1643
1685
 
1644
1686
  /**
1645
1687
  * Returns a copy of 'env' with (quoted) name prefix 'id' and a dot appended to the current name prefix
1646
1688
  *
1647
- * @param {CdlRenderEnvironment} env Current environment
1689
+ * @param {HdbcdsRenderEnvironment} env Current environment
1648
1690
  * @param {string} id Name prefix to add
1649
- * @returns {CdlRenderEnvironment} New environment with added prefix
1691
+ * @returns {HdbcdsRenderEnvironment} New environment with added prefix
1650
1692
  */
1651
1693
  function addNamePrefix( env, id ) {
1652
- return Object.assign({}, env, { namePrefix: `${env.namePrefix + quoteId(id)}.` });
1653
- }
1654
-
1655
- /**
1656
- * Returns a copy of 'env' with increased indentation - also resets the name prefix
1657
- *
1658
- * @param {CdlRenderEnvironment} env Current environment
1659
- * @returns {CdlRenderEnvironment} New environment with increased indent
1660
- */
1661
- function increaseIndent( env ) {
1662
- return Object.assign({}, env, { namePrefix: '', indent: `${env.indent} ` });
1694
+ return env.cloneWith({ namePrefix: `${env.namePrefix + quoteId(id)}.` });
1663
1695
  }
1664
1696
 
1665
1697
  /**
@@ -1772,7 +1804,7 @@ function toHdbcdsSource( csn, options ) {
1772
1804
  *
1773
1805
  *
1774
1806
  * @param {string} artifactName Artifact name to render
1775
- * @param {CdlRenderEnvironment} env Render environment
1807
+ * @param {HdbcdsRenderEnvironment} env Render environment
1776
1808
  * @param {boolean} [fallthrough=false] For certain artifacts, plain-rendering is supposed to look like quoted/hdbcds
1777
1809
  * @returns {string} Artifact name ready for rendering
1778
1810
  */
@@ -1799,20 +1831,4 @@ function toHdbcdsSource( csn, options ) {
1799
1831
  }
1800
1832
  }
1801
1833
 
1802
- /**
1803
- * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
1804
- *
1805
- * @property {string} indent Current indentation as a string, e.g. ' ' for two spaces.
1806
- * @property {CSN.Path} [path] CSN path to the current artifact
1807
- * @property {string} [currentArtifactName] Name of the current artifact
1808
- * @property {{[name: string]: {
1809
- quotedName: string,
1810
- quotedAlias: string
1811
- }}} [topLevelAliases] Dictionary of aliases for used artifact names
1812
- *
1813
- * @property {string} [namePrefix] Current name prefix (including trailing dot if not empty)
1814
- * @property {boolean} [skipKeys] Skip rendering keys in subqueries
1815
- * @property {CSN.Artifact} [_artifact] The original view artifact, used when rendering queries
1816
- */
1817
-
1818
1834
  module.exports = { toHdbcdsSource };