@sap/cds-compiler 4.6.2 → 4.7.4

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 (69) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/cds_update_identifiers.js +6 -2
  3. package/bin/cdsc.js +1 -1
  4. package/doc/CHANGELOG_ARCHIVE.md +9 -9
  5. package/doc/CHANGELOG_BETA.md +6 -0
  6. package/lib/api/main.js +56 -9
  7. package/lib/api/options.js +6 -3
  8. package/lib/api/validate.js +20 -29
  9. package/lib/base/message-registry.js +27 -3
  10. package/lib/base/messages.js +8 -3
  11. package/lib/base/model.js +2 -0
  12. package/lib/checks/dbFeatureFlags.js +28 -0
  13. package/lib/checks/elements.js +81 -13
  14. package/lib/checks/enricher.js +3 -2
  15. package/lib/checks/validator.js +38 -4
  16. package/lib/compiler/assert-consistency.js +4 -4
  17. package/lib/compiler/checks.js +5 -4
  18. package/lib/compiler/define.js +2 -2
  19. package/lib/compiler/generate.js +2 -1
  20. package/lib/compiler/propagator.js +3 -11
  21. package/lib/compiler/shared.js +2 -1
  22. package/lib/compiler/tweak-assocs.js +43 -24
  23. package/lib/edm/annotations/edmJson.js +3 -0
  24. package/lib/edm/annotations/genericTranslation.js +156 -106
  25. package/lib/edm/annotations/preprocessAnnotations.js +11 -14
  26. package/lib/edm/csn2edm.js +27 -24
  27. package/lib/edm/edm.js +8 -8
  28. package/lib/edm/edmPreprocessor.js +135 -37
  29. package/lib/edm/edmUtils.js +20 -7
  30. package/lib/gen/Dictionary.json +1 -0
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +9 -11
  33. package/lib/gen/languageParser.js +5942 -5446
  34. package/lib/json/to-csn.js +7 -114
  35. package/lib/language/genericAntlrParser.js +106 -48
  36. package/lib/model/cloneCsn.js +203 -0
  37. package/lib/model/csnRefs.js +11 -3
  38. package/lib/model/csnUtils.js +42 -85
  39. package/lib/optionProcessor.js +2 -2
  40. package/lib/render/manageConstraints.js +1 -1
  41. package/lib/render/toCdl.js +133 -88
  42. package/lib/render/toHdbcds.js +1 -5
  43. package/lib/render/toSql.js +7 -9
  44. package/lib/render/utils/common.js +9 -16
  45. package/lib/transform/addTenantFields.js +277 -102
  46. package/lib/transform/db/applyTransformations.js +14 -9
  47. package/lib/transform/db/backlinks.js +2 -1
  48. package/lib/transform/db/constraints.js +60 -82
  49. package/lib/transform/db/expansion.js +6 -6
  50. package/lib/transform/db/featureFlags.js +5 -0
  51. package/lib/transform/db/flattening.js +4 -4
  52. package/lib/transform/db/killAnnotations.js +1 -0
  53. package/lib/transform/db/rewriteCalculatedElements.js +2 -2
  54. package/lib/transform/db/transformExists.js +12 -0
  55. package/lib/transform/db/views.js +5 -2
  56. package/lib/transform/draft/odata.js +7 -6
  57. package/lib/transform/effective/associations.js +2 -1
  58. package/lib/transform/effective/main.js +3 -2
  59. package/lib/transform/effective/types.js +6 -3
  60. package/lib/transform/forOdata.js +39 -24
  61. package/lib/transform/forRelationalDB.js +34 -27
  62. package/lib/transform/localized.js +29 -9
  63. package/lib/transform/odata/flattening.js +419 -0
  64. package/lib/transform/odata/toFinalBaseType.js +95 -15
  65. package/lib/transform/odata/typesExposure.js +9 -7
  66. package/lib/transform/transformUtils.js +7 -6
  67. package/lib/transform/translateAssocsToJoins.js +3 -3
  68. package/lib/utils/objectUtils.js +14 -0
  69. package/package.json +1 -1
@@ -1,17 +1,18 @@
1
1
  'use strict';
2
2
 
3
3
  const { csnRefs, implicitAs, pathId } = require('./csnRefs');
4
- const { applyTransformations, applyTransformationsOnNonDictionary, applyTransformationsOnDictionary } = require('../transform/db/applyTransformations');
5
- const { isBuiltinType, isMagicVariable, isAnnotationExpression } = require('../compiler/builtins.js');
6
4
  const {
7
- sortCsn,
8
- cloneCsnDictionary: _cloneCsnDictionary,
9
- cloneAnnotationValue: _cloneAnnotationValue,
10
- } = require('../json/to-csn');
5
+ transformExpression,
6
+ applyTransformations,
7
+ applyTransformationsOnNonDictionary,
8
+ applyTransformationsOnDictionary,
9
+ } = require('../transform/db/applyTransformations');
10
+ const { isBuiltinType, isMagicVariable, isAnnotationExpression } = require('../compiler/builtins.js');
11
11
  const { ModelError, CompilerAssertion } = require('../base/error');
12
12
  const { typeParameters } = require('../compiler/builtins');
13
13
  const { forEach } = require('../utils/objectUtils');
14
14
  const { version } = require('../../package.json');
15
+ const { cloneAnnotationValue } = require('./cloneCsn');
15
16
 
16
17
  // Low-level utility functions to work with compact CSN.
17
18
 
@@ -442,52 +443,6 @@ function getUtils( model, universalReady ) {
442
443
  }
443
444
  }
444
445
 
445
- /**
446
- * Deeply clone the given CSN model and return it.
447
- * In testMode (or with testSortCsn), definitions are sorted.
448
- *
449
- * This function is CSN aware! Don't put annotation values into it, or
450
- * keys such as "elements" will be interpreted according to CSN rules!
451
- *
452
- * @see cloneAnnotationValue
453
- * @see cloneCsnDictionary
454
- *
455
- * @param {object} csn Top-level CSN. You can pass non-dictionary values.
456
- * @param {CSN.Options} options CSN Options, only used for `dictionaryPrototype`, `testMode`, and `testSortCsn`
457
- */
458
- function cloneCsnNonDict( csn, options ) {
459
- return sortCsn(csn, options);
460
- }
461
-
462
- /**
463
- * Deeply clone the given CSN dictionary and return it.
464
- * This function does _not_ sort the given dictionary.
465
- * See cloneCsnNonDict() if you want sorted definitions.
466
- *
467
- * This function is CSN aware! Don't put annotation values into it, or
468
- * keys such as "elements" will be interpreted according to CSN rules!
469
- *
470
- * @see cloneAnnotationValue
471
- * @see cloneCsnNonDict
472
- *
473
- * @param {object} csn
474
- * @param {CSN.Options} options Only cloneOptions.dictionaryPrototype is
475
- * used and cloneOptions are passed to sortCsn().
476
- */
477
- function cloneCsnDictionary( csn, options ) {
478
- return _cloneCsnDictionary(csn, options);
479
- }
480
-
481
- /**
482
- * Clones the given annotation _value_. `value` must not be an object with annotations,
483
- * but the annotation value itself, e.g. `[ { a: 1 } ]`, not `@anno: [...]`.
484
- *
485
- * @param {any} value
486
- * @returns {any}
487
- */
488
- function cloneAnnotationValue( value ) {
489
- return _cloneAnnotationValue( value );
490
- }
491
446
 
492
447
  /**
493
448
  * Apply function `callback` to all artifacts in dictionary
@@ -1092,17 +1047,19 @@ function getLastPartOfRef( ref ) {
1092
1047
  * @returns {array} copiedAnnoNames
1093
1048
  */
1094
1049
  function copyAnnotations( fromNode, toNode, overwrite = false, excludes = {}, annoNames = undefined ) {
1095
- // Ignore if no toNode (in case of errors)
1096
- if (!toNode)
1097
- return;
1098
-
1099
- if (annoNames == null)
1100
- annoNames = Object.keys(fromNode).filter(key => key.startsWith('@'));
1101
-
1102
- annoNames.forEach((anno) => {
1103
- if ((toNode[anno] === undefined || overwrite) && !excludes[anno])
1104
- toNode[anno] = cloneAnnotationValue(fromNode[anno]);
1105
- });
1050
+ const copiedAnnoNames = [];
1051
+ if (toNode) {
1052
+ if (annoNames == null)
1053
+ annoNames = Object.keys(fromNode).filter(key => key.startsWith('@'));
1054
+
1055
+ annoNames.forEach((anno) => {
1056
+ if ((toNode[anno] === undefined || overwrite) && !excludes[anno]) {
1057
+ toNode[anno] = cloneAnnotationValue(fromNode[anno]);
1058
+ copiedAnnoNames.push(anno);
1059
+ }
1060
+ });
1061
+ }
1062
+ return copiedAnnoNames;
1106
1063
  }
1107
1064
 
1108
1065
 
@@ -1275,23 +1232,6 @@ function getNamespace( csn, name, ns = undefined ) {
1275
1232
  return ns;
1276
1233
  }
1277
1234
 
1278
-
1279
- /**
1280
- * Sorts the definition dictionary in tests mode.
1281
- *
1282
- * @param {CSN.Model} csn
1283
- * @param {CSN.Options} options
1284
- */
1285
- function sortCsnDefinitionsForTests( csn, options ) {
1286
- if (!options.testMode && !options.testSortCsn)
1287
- return;
1288
- const sorted = Object.create(null);
1289
- Object.keys(csn.definitions || {}).sort().forEach((name) => {
1290
- sorted[name] = csn.definitions[name];
1291
- });
1292
- csn.definitions = sorted;
1293
- }
1294
-
1295
1235
  /**
1296
1236
  * Return an array of non-abstract service names contained in CSN
1297
1237
  *
@@ -1452,12 +1392,28 @@ function pathName( ref ) {
1452
1392
  return ref ? ref.map( pathId ).join( '.' ) : '';
1453
1393
  }
1454
1394
 
1395
+ /**
1396
+ * Return true if prop is an annotation and the annotation value has an expression
1397
+ *
1398
+ * @param {object} node
1399
+ * @param {string} prop
1400
+ * @returns {boolean}
1401
+ */
1402
+ function findAnnotationExpression( node, prop ) {
1403
+ let isExpr = false;
1404
+ if (prop[0] === '@') {
1405
+ transformExpression(node, prop, {
1406
+ '=': (p) => {
1407
+ isExpr ||= isAnnotationExpression(p);
1408
+ },
1409
+ });
1410
+ }
1411
+
1412
+ return isExpr;
1413
+ }
1414
+
1455
1415
  module.exports = {
1456
1416
  getUtils,
1457
- cloneCsn: cloneCsnNonDict, // Umbrella relies on this name
1458
- cloneCsnNonDict,
1459
- cloneCsnDictionary,
1460
- cloneAnnotationValue,
1461
1417
  isBuiltinType,
1462
1418
  isMagicVariable,
1463
1419
  isAnnotationExpression,
@@ -1473,6 +1429,7 @@ module.exports = {
1473
1429
  getResultingName,
1474
1430
  getUnderscoredName,
1475
1431
  getElementDatabaseNameOf,
1432
+ transformExpression,
1476
1433
  applyTransformations,
1477
1434
  applyTransformationsOnNonDictionary,
1478
1435
  applyTransformationsOnDictionary,
@@ -1491,7 +1448,6 @@ module.exports = {
1491
1448
  isAspect,
1492
1449
  hasValidSkipOrExists,
1493
1450
  getNamespace,
1494
- sortCsnDefinitionsForTests,
1495
1451
  getServiceNames,
1496
1452
  walkCsnPath,
1497
1453
  getVariableReplacement,
@@ -1502,4 +1458,5 @@ module.exports = {
1502
1458
  isAssociationOperand,
1503
1459
  isDollarSelfOrProjectionOperand,
1504
1460
  pathName,
1461
+ findAnnotationExpression,
1505
1462
  };
@@ -41,7 +41,7 @@ optionProcessor
41
41
  .option(' --doc-comment')
42
42
  .option(' --add-texts-language-assoc')
43
43
  .option(' --localized-without-coalesce')
44
- .option(' --tenant-as-column')
44
+ .option(' --tenant-discriminator')
45
45
  .option(' --default-binary-length <length>')
46
46
  .option(' --default-string-length <length>')
47
47
  .option(' --no-recompile')
@@ -101,7 +101,7 @@ optionProcessor
101
101
  -E, --enrich-csn Show non-enumerable CSN properties and locations of references
102
102
  -R, --raw-output <name> Write XSN for definition "name" and error output to <stdout>,
103
103
  with name = "+", write complete XSN, long!
104
- --tenant-as-column Add tenant fields to entities
104
+ --tenant-discriminator Add tenant fields to entities
105
105
  --internal-msg Write raw messages with call stack to <stdout>/<stderr>
106
106
  --beta-mode Enable all unsupported, incomplete (beta) features
107
107
  --beta <list> Comma separated list of unsupported, incomplete (beta) features to use.
@@ -11,7 +11,7 @@ const { transformForRelationalDBWithCsn } = require('../transform/forRelationalD
11
11
  const {
12
12
  renderReferentialConstraint, getIdentifierUtils,
13
13
  } = require('./utils/sql');
14
- const { sortCsn } = require('../json/to-csn');
14
+ const { sortCsn } = require('../model/cloneCsn');
15
15
 
16
16
  /**
17
17
  * Used only by `cdsc manageConstraints`.
@@ -15,8 +15,8 @@ const {
15
15
  isBuiltinType,
16
16
  generatedByCompilerVersion,
17
17
  getNormalizedQuery,
18
- cloneCsnNonDict,
19
18
  } = require('../model/csnUtils');
19
+ const { cloneFullCsn } = require('../model/cloneCsn');
20
20
 
21
21
  const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
22
22
  const specialFunctionKeywords = Object.create(null);
@@ -36,7 +36,7 @@ function csnToCdl( csn, options, msg ) {
36
36
  if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
37
37
  // Since the expander modifies the CSN, we need to clone it first or
38
38
  // toCdl can't guarantee that the input CSN is not modified.
39
- csn = cloneCsnNonDict(csn, options);
39
+ csn = cloneFullCsn(csn, options);
40
40
  enrichUniversalCsn(csn, options);
41
41
  }
42
42
 
@@ -319,29 +319,38 @@ function csnToCdl( csn, options, msg ) {
319
319
  result += renderAnnotateParamsInParentheses(ext, env);
320
320
 
321
321
  // Element extensions and annotations (possibly nested)
322
- // TODO: Deduplicate coding, see renderAnnotateStatementElements()
323
- if (ext.elements)
324
- result += ` ${renderAnnotateStatementElements(ext.elements, env.withSubPath([ 'elements' ]))}`;
325
- else if (ext.enum)
326
- result += ` ${renderAnnotateStatementElements(ext.enum, env.withSubPath([ 'enum' ]))}`;
327
- else if (ext.returns)
322
+ if (ext.elements) {
323
+ env.path.push('elements');
324
+ result += ` ${renderAnnotateStatementElements(ext.elements, env)}`;
325
+ env.path.length -= 1;
326
+ }
327
+ else if (ext.enum) {
328
+ env.path.push('enum');
329
+ result += ` ${renderAnnotateStatementElements(ext.enum, env)}`;
330
+ env.path.length -= 1;
331
+ }
332
+ else if (ext.returns) {
328
333
  result += renderAnnotateReturns(ext, env);
334
+ }
329
335
 
330
336
  if (ext.actions) { // Bound action annotations
331
337
  result += ' actions {\n';
338
+ env.increaseIndent();
339
+ env.path.push('actions', '');
332
340
  for (const name in ext.actions) {
333
- const childEnv = env.withIncreasedIndent().withSubPath([ 'actions', name ]);
341
+ env.path[env.path.length - 1] = name;
334
342
  const action = ext.actions[name];
335
- result += renderAnnotationAssignmentsAndDocComment(action, childEnv) + childEnv.indent + quoteNonIdentifierOrKeyword(name, childEnv);
343
+ result += renderAnnotationAssignmentsAndDocComment(action, env) + env.indent + quoteNonIdentifierOrKeyword(name, env);
336
344
  // Action parameter annotations
337
345
  if (action.params)
338
- result += renderAnnotateParamsInParentheses(action, childEnv);
346
+ result += renderAnnotateParamsInParentheses(action, env);
339
347
  if (action.returns)
340
- result += renderAnnotateReturns(action, childEnv);
348
+ result += renderAnnotateReturns(action, env);
341
349
 
342
350
  result = removeTrailingNewline(result);
343
351
  result += ';\n';
344
352
  }
353
+ env.decreaseIndent();
345
354
  result += `${env.indent}}`;
346
355
  }
347
356
 
@@ -361,18 +370,28 @@ function csnToCdl( csn, options, msg ) {
361
370
  */
362
371
  function renderAnnotateStatementElements( elements, env ) {
363
372
  let result = '{\n';
373
+ env.increaseIndent();
374
+ env.path.push('');
364
375
  for (const name in elements) {
365
- const childEnv = env.withIncreasedIndent().withSubPath([ name ]);
376
+ env.path[env.path.length - 1] = name;
366
377
  const elem = elements[name];
367
- result += renderAnnotationAssignmentsAndDocComment(elem, childEnv);
368
- result += childEnv.indent + quoteNonIdentifierOrKeyword(name, env);
369
- if (elem.elements)
370
- result += ` ${renderAnnotateStatementElements(elem.elements, childEnv.withSubPath([ 'elements' ]))}`;
371
- else if (elem.enum)
372
- result += ` ${renderAnnotateStatementElements(elem.enum, childEnv.withSubPath([ 'enum' ]))}`;
378
+ result += renderAnnotationAssignmentsAndDocComment(elem, env);
379
+ result += env.indent + quoteNonIdentifierOrKeyword(name, env);
380
+ if (elem.elements) {
381
+ env.path.push('elements');
382
+ result += ` ${renderAnnotateStatementElements(elem.elements, env)}`;
383
+ env.path.pop();
384
+ }
385
+ else if (elem.enum) {
386
+ env.path.push('enum');
387
+ result += ` ${renderAnnotateStatementElements(elem.enum, env)}`;
388
+ env.path.pop();
389
+ }
373
390
 
374
391
  result += ';\n';
375
392
  }
393
+ env.path.length -= 1;
394
+ env.decreaseIndent();
376
395
  result += `${env.indent}}`;
377
396
  return result;
378
397
  }
@@ -456,8 +475,7 @@ function csnToCdl( csn, options, msg ) {
456
475
  return renderEvent(artifactName, art, env);
457
476
 
458
477
  default:
459
- // TODO: Make it a error message.
460
- throw new ModelError(`to.cdl: Unknown artifact kind: ${art.kind}`);
478
+ throw new ModelError(`to.cdl: Unknown artifact kind: '${art.kind}' at ${JSON.stringify(env.path)}`);
461
479
  }
462
480
  }
463
481
 
@@ -606,12 +624,14 @@ function csnToCdl( csn, options, msg ) {
606
624
 
607
625
  if (isCalcElement) { // calculated element // @ts-ignore
608
626
  result += ' = ';
609
- if (element.value.xpr && xprContainsCondition(element.value.xpr))
610
- result += exprRenderer.renderSubExpr(element.value, env.withSubPath([ 'value' ]));
611
- else
612
- result += exprRenderer.renderExpr(element.value, env.withSubPath([ 'value' ]));
627
+ env.path.push('value');
628
+ const isSubExpr = (element.value.xpr && xprContainsCondition(element.value.xpr));
629
+ result += isSubExpr
630
+ ? exprRenderer.renderSubExpr(element.value, env)
631
+ : exprRenderer.renderExpr(element.value, env);
613
632
  if (element.value.stored === true)
614
633
  result += ' stored';
634
+ env.path.length -= 1;
615
635
  }
616
636
 
617
637
  return `${result};\n`;
@@ -660,7 +680,6 @@ function csnToCdl( csn, options, msg ) {
660
680
 
661
681
  // Based on the current path, create a correctly nested structure
662
682
  // of elements for which we collect annotations.
663
- // TODO: More properties?
664
683
  let obj = annotate;
665
684
  for (let i = 2; i < env.path.length; ++i) {
666
685
  const key = env.path[i];
@@ -746,13 +765,19 @@ function csnToCdl( csn, options, msg ) {
746
765
  // JOIN
747
766
  else if (source.join) {
748
767
  // One join operation, possibly with ON-condition
749
- let result = `(${renderViewSource(source.args[0], env.withSubPath([ 'args', 0 ]))}`;
768
+ env.path.push('args', 0);
769
+ let result = `(${renderViewSource(source.args[0], env)}`;
750
770
  for (let i = 1; i < source.args.length; i++) {
771
+ env.path[env.path.length - 1] = i;
751
772
  result += ` ${source.join} `;
752
773
  result += renderJoinCardinality(source.cardinality);
753
- result += `join ${renderViewSource(source.args[i], env.withSubPath([ 'args', i ]))}`;
754
- if (source.on)
755
- result += ` on ${exprRenderer.renderExpr(source.on, env.withSubPath([ 'on' ]))}`;
774
+ result += `join ${renderViewSource(source.args[i], env)}`;
775
+ }
776
+ env.path.length -= 2;
777
+ if (source.on) {
778
+ env.path.push('on');
779
+ result += ` on ${exprRenderer.renderExpr(source.on, env.withSubPath([ 'on' ]))}`;
780
+ env.path.length -= 1;
756
781
  }
757
782
  result += ')';
758
783
  return result;
@@ -798,18 +823,12 @@ function csnToCdl( csn, options, msg ) {
798
823
  let result = renderDefinitionReference(firstArtifactName, env);
799
824
 
800
825
  // Even the first step might have parameters and/or a filter
826
+ env.path.push('ref', 0);
801
827
  if (path.ref[0].args)
802
- result += `(${renderArguments(path.ref[0], ':', env.withSubPath([ 'ref', 0 ]))})`;
803
-
804
- if (path.ref[0].where) {
805
- // TODO: Unify with other filter rendering
806
- const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
807
- const expr = exprRenderer.renderExpr(path.ref[0].where, env.withSubPath([ 'ref', 0, 'where' ]));
808
- if (expr.endsWith(']')) // for cases such as [… ![id] ]
809
- result += `[ ${cardinality}${expr} ]`;
810
- else
811
- result += `[${cardinality}${expr}]`;
812
- }
828
+ result += `(${renderArguments(path.ref[0], ':', env)})`;
829
+ if (path.ref[0].where)
830
+ result += renderFilterAndCardinality(path.ref[0], env);
831
+ env.path.length -= 2;
813
832
 
814
833
  // Add any path steps (possibly with parameters and filters) that may follow after that
815
834
  if (path.ref.length > 1)
@@ -847,8 +866,12 @@ function csnToCdl( csn, options, msg ) {
847
866
  * @return {string}
848
867
  */
849
868
  function renderViewColumns( art, env, elements = Object.create(null) ) {
850
- const result = art.columns.map((col, i) => renderViewColumn(col, env.withSubPath([ 'columns', i ]), findElement(elements, col)))
851
- .join(',\n');
869
+ env.path.push( 'columns', -1 );
870
+ const result = art.columns.map((col, i) => {
871
+ env.path[env.path.length - 1] = i;
872
+ return renderViewColumn(col, env, findElement(elements, col));
873
+ }).join(',\n');
874
+ env.path.length -= 2;
852
875
  return `${result}\n`;
853
876
  }
854
877
 
@@ -869,8 +892,7 @@ function csnToCdl( csn, options, msg ) {
869
892
  // of an `annotate` statement. That may change in the future.
870
893
  result += renderDocComment(element, env);
871
894
  }
872
- // Note: parentheses are a workaround for #9015; TODO: Check & Update
873
- result += renderAnnotationAssignmentsAndDocComment(col, env, { parentheses: true });
895
+ result += renderAnnotationAssignmentsAndDocComment(col, env);
874
896
  result += env.indent;
875
897
 
876
898
  // only if column is virtual, keyword virtual was present in the source text
@@ -893,11 +915,13 @@ function csnToCdl( csn, options, msg ) {
893
915
 
894
916
  // Explicit type provided for the view element?
895
917
  if (col.cast) {
918
+ env.path.push('cast');
896
919
  // Special case: Explicit association type is actually a redirect
897
920
  if (col.cast.target && !col.cast.type)
898
- result += ` : ${renderRedirectedTo(col.cast, env.withSubPath([ 'cast' ]))}`;
921
+ result += ` : ${renderRedirectedTo(col.cast, env)}`;
899
922
  else
900
- result += ` : ${renderTypeReferenceAndProps(col.cast, env.withSubPath([ 'cast' ]), { typeRefOnly: true, noAnnoCollect: true })}`;
923
+ result += ` : ${renderTypeReferenceAndProps(col.cast, env, { typeRefOnly: true, noAnnoCollect: true })}`;
924
+ env.path.length -= 1;
901
925
  }
902
926
  return result;
903
927
  }
@@ -920,10 +944,12 @@ function csnToCdl( csn, options, msg ) {
920
944
 
921
945
  // We found a leaf - no further drilling
922
946
  if (!obj.inline && !obj.expand) {
947
+ env.path.push('cast');
923
948
  if (obj.cast && obj.cast.type)
924
- result += ` : ${renderTypeReferenceAndProps(obj.cast, env.withSubPath([ 'cast' ]), { noAnnoCollect: true })}`;
949
+ result += ` : ${renderTypeReferenceAndProps(obj.cast, env, { noAnnoCollect: true })}`;
925
950
  else if (obj.cast && obj.cast.target) // test tbd
926
- result += ` : ${renderRedirectedTo(obj.cast, env.withSubPath([ 'cast' ]))}`;
951
+ result += ` : ${renderRedirectedTo(obj.cast, env)}`;
952
+ env.path.length -= 1;
927
953
  return result;
928
954
  }
929
955
 
@@ -1023,17 +1049,24 @@ function csnToCdl( csn, options, msg ) {
1023
1049
 
1024
1050
  let result = '';
1025
1051
  const select = query.SELECT;
1026
- const childEnv = env.withIncreasedIndent();
1027
1052
 
1028
1053
  // If not a projection, must be view/entity.
1029
1054
  result += (syntax === 'projection') ? 'projection on ' : 'select from ';
1030
- result += renderViewSource(select.from, env.withSubPath([ 'from' ]));
1055
+
1056
+ env.path.push('from');
1057
+ result += renderViewSource(select.from, env);
1058
+ env.path.length -= 1;
1031
1059
 
1032
1060
  if (select.mixin) {
1033
1061
  let elems = '';
1062
+ env.path.push('mixin', '');
1063
+ env.increaseIndent();
1034
1064
  forEach(select.mixin, (name, mixin) => {
1035
- elems += renderElement(name, mixin, childEnv.withSubPath([ 'mixin', name ]));
1065
+ env.path[env.path.length - 1] = name;
1066
+ elems += renderElement(name, mixin, env);
1036
1067
  });
1068
+ env.decreaseIndent();
1069
+ env.path.length -= 2;
1037
1070
  if (elems) {
1038
1071
  result += ' mixin {\n';
1039
1072
  result += elems;
@@ -1043,10 +1076,13 @@ function csnToCdl( csn, options, msg ) {
1043
1076
  result += select.distinct ? ' distinct' : '';
1044
1077
  if (select.columns) {
1045
1078
  result += ' {\n';
1046
- result += renderViewColumns(select, env.withIncreasedIndent(), elements);
1079
+ env.increaseIndent();
1080
+ result += renderViewColumns(select, env, elements);
1081
+ env.decreaseIndent();
1047
1082
  result += `${env.indent}}`;
1048
1083
  }
1049
1084
 
1085
+ const childEnv = env.withIncreasedIndent();
1050
1086
  if (select.excluding) {
1051
1087
  const excludes = select.excluding.map(id => `${childEnv.indent}${quoteNonIdentifierOrKeyword(id, env)}`).join(',\n');
1052
1088
  result += ` excluding {\n${excludes}\n`;
@@ -1568,35 +1604,7 @@ function csnToCdl( csn, options, msg ) {
1568
1604
  result += `(${renderArguments(s, ':', env)})`;
1569
1605
  }
1570
1606
 
1571
- const cardinality = s.cardinality ? (`${s.cardinality.max}: `) : '';
1572
- let filter = '';
1573
-
1574
- // TODO: Unify with other filter rendering for SELECT
1575
- if (s.groupBy)
1576
- filter += ` group by ${s.groupBy.map((expr, i) => exprRenderer.renderExpr(expr, env.withSubPath([ 'groupBy', i ]))).join(', ')}`;
1577
- if (s.having)
1578
- filter += ` having ${exprRenderer.renderExpr(s.having, env.withSubPath([ 'having' ]))}`;
1579
- if (s.orderBy)
1580
- filter += ` order by ${s.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'orderBy', i ]))).join(', ')}`;
1581
- if (s.limit)
1582
- filter += ` ${renderLimit(s.limit, env.withSubPath([ 'limit' ]))}`;
1583
-
1584
- if (s.where) {
1585
- let where = exprRenderer.renderExpr(s.where, env.withSubPath([ 'where' ]));
1586
- // Special rules in CDS parser: If filter starts with one of these SQL keywords, WHERE is mandatory.
1587
- if (filter || /^(?:group|having|order|limit)\s/i.test(where))
1588
- where = ` where ${where}`;
1589
- filter = `${where} ${filter}`;
1590
- }
1591
-
1592
- filter = filter.trim();
1593
-
1594
- if (cardinality || filter) {
1595
- if (filter.endsWith(']')) // for cases such as [… ![id] ]
1596
- result += `[ ${cardinality}${filter} ]`;
1597
- else
1598
- result += `[${cardinality}${filter}]`;
1599
- }
1607
+ result += renderFilterAndCardinality(s, env);
1600
1608
 
1601
1609
  return result;
1602
1610
  }
@@ -1690,8 +1698,6 @@ function csnToCdl( csn, options, msg ) {
1690
1698
  /**
1691
1699
  * Render a cardinality (only those parts that were actually provided)
1692
1700
  *
1693
- * TODO: Check srcmin as well?
1694
- *
1695
1701
  * @param {CSN.Artifact} art
1696
1702
  * @return {string}
1697
1703
  */
@@ -1706,13 +1712,11 @@ function csnToCdl( csn, options, msg ) {
1706
1712
  let result = '[';
1707
1713
  if (card.src !== undefined)
1708
1714
  result += `${card.src}, `;
1709
-
1710
1715
  if (card.min !== undefined)
1711
1716
  result += `${card.min}..`;
1712
-
1713
1717
  if (card.max !== undefined)
1714
1718
  result += card.max;
1715
-
1719
+ // srcmin can't be represented in CDL
1716
1720
  return `${result}]${suffix}`;
1717
1721
  }
1718
1722
 
@@ -1750,6 +1754,41 @@ function csnToCdl( csn, options, msg ) {
1750
1754
  return result;
1751
1755
  }
1752
1756
 
1757
+
1758
+ function renderFilterAndCardinality( s, env ) {
1759
+ let result = '';
1760
+ const cardinality = s.cardinality ? (`${s.cardinality.max}: `) : '';
1761
+ let filter = '';
1762
+
1763
+ // TODO: Unify with other filter rendering for SELECT
1764
+ if (s.groupBy)
1765
+ filter += ` group by ${s.groupBy.map((expr, i) => exprRenderer.renderExpr(expr, env.withSubPath([ 'groupBy', i ]))).join(', ')}`;
1766
+ if (s.having)
1767
+ filter += ` having ${exprRenderer.renderExpr(s.having, env.withSubPath([ 'having' ]))}`;
1768
+ if (s.orderBy)
1769
+ filter += ` order by ${s.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'orderBy', i ]))).join(', ')}`;
1770
+ if (s.limit)
1771
+ filter += ` ${renderLimit(s.limit, env.withSubPath([ 'limit' ]))}`;
1772
+
1773
+ if (s.where) {
1774
+ let where = exprRenderer.renderExpr(s.where, env.withSubPath([ 'where' ]));
1775
+ // Special rules in CDS parser: If filter starts with one of these SQL keywords, WHERE is mandatory.
1776
+ if (filter || /^(?:group|having|order|limit)\s/i.test(where))
1777
+ where = ` where ${where}`;
1778
+ filter = `${where} ${filter}`;
1779
+ }
1780
+
1781
+ filter = filter.trim();
1782
+
1783
+ if (cardinality || filter) {
1784
+ if (filter.endsWith(']')) // for cases such as [… ![id] ]
1785
+ result += `[ ${cardinality}${filter} ]`;
1786
+ else
1787
+ result += `[${cardinality}${filter}]`;
1788
+ }
1789
+ return result;
1790
+ }
1791
+
1753
1792
  function renderDefaultExpr( art, env ) {
1754
1793
  if (!art.default)
1755
1794
  return '';
@@ -1977,7 +2016,7 @@ function csnToCdl( csn, options, msg ) {
1977
2016
  SELECT(x) {
1978
2017
  return `(${renderQuery(x, false, 'view', this.env.withIncreasedIndent())})`;
1979
2018
  },
1980
- }, true);
2019
+ });
1981
2020
  }
1982
2021
 
1983
2022
  // checks -------------------------------------------------------------------
@@ -2135,6 +2174,12 @@ class CdlRenderEnvironment {
2135
2174
  Object.assign(this, values);
2136
2175
  }
2137
2176
 
2177
+ increaseIndent() {
2178
+ this.indent = ` ${this.indent}`;
2179
+ }
2180
+ decreaseIndent() {
2181
+ this.indent = this.indent.substring(0, this.indent.length - 2);
2182
+ }
2138
2183
  withIncreasedIndent() {
2139
2184
  return new CdlRenderEnvironment({ ...this, indent: ` ${this.indent}` });
2140
2185
  }
@@ -103,10 +103,6 @@ function toHdbcdsSource( csn, options, messageFunctions ) {
103
103
  const {
104
104
  info, warning, error, throwWithAnyError, message,
105
105
  } = messageFunctions;
106
- if (options.tenantAsColumn) {
107
- error('api-unexpected-option', null, { option: 'tenantAsColumn', module: 'to.hdbcds' });
108
- throwWithAnyError();
109
- }
110
106
 
111
107
  const reportedMissingReplacements = Object.create(null);
112
108
 
@@ -140,7 +136,7 @@ function toHdbcdsSource( csn, options, messageFunctions ) {
140
136
  SET(x) {
141
137
  return `${renderQuery(x, false, this.env.withIncreasedIndent())}`;
142
138
  },
143
- }, true);
139
+ });
144
140
 
145
141
  function renderExpr( x, env ) {
146
142
  return exprRenderer.renderExpr(x, env);