@sap/cds-compiler 3.5.4 → 3.6.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 (84) hide show
  1. package/CHANGELOG.md +65 -2
  2. package/bin/cdsc.js +14 -6
  3. package/doc/CHANGELOG_ARCHIVE.md +10 -10
  4. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  5. package/lib/api/main.js +32 -55
  6. package/lib/api/options.js +3 -2
  7. package/lib/api/validate.js +5 -0
  8. package/lib/base/message-registry.js +104 -32
  9. package/lib/base/messages.js +277 -212
  10. package/lib/base/optionProcessorHelper.js +9 -2
  11. package/lib/base/shuffle.js +50 -0
  12. package/lib/checks/actionsFunctions.js +37 -20
  13. package/lib/checks/foreignKeys.js +13 -6
  14. package/lib/checks/nonexpandableStructured.js +1 -2
  15. package/lib/checks/onConditions.js +21 -19
  16. package/lib/checks/parameters.js +1 -1
  17. package/lib/checks/queryNoDbArtifacts.js +2 -0
  18. package/lib/checks/types.js +16 -22
  19. package/lib/compiler/assert-consistency.js +31 -28
  20. package/lib/compiler/builtins.js +20 -4
  21. package/lib/compiler/checks.js +72 -63
  22. package/lib/compiler/define.js +396 -314
  23. package/lib/compiler/extend.js +55 -49
  24. package/lib/compiler/index.js +5 -0
  25. package/lib/compiler/populate.js +28 -11
  26. package/lib/compiler/propagator.js +2 -1
  27. package/lib/compiler/resolve.js +28 -13
  28. package/lib/compiler/shared.js +15 -10
  29. package/lib/compiler/utils.js +7 -7
  30. package/lib/edm/annotations/genericTranslation.js +51 -46
  31. package/lib/edm/annotations/preprocessAnnotations.js +37 -40
  32. package/lib/edm/csn2edm.js +69 -21
  33. package/lib/edm/edm.js +2 -2
  34. package/lib/edm/edmInboundChecks.js +6 -8
  35. package/lib/edm/edmPreprocessor.js +88 -80
  36. package/lib/edm/edmUtils.js +6 -15
  37. package/lib/gen/Dictionary.json +81 -13
  38. package/lib/gen/language.checksum +1 -1
  39. package/lib/gen/language.interp +2 -1
  40. package/lib/gen/languageParser.js +4680 -4484
  41. package/lib/inspect/inspectModelStatistics.js +2 -1
  42. package/lib/inspect/inspectPropagation.js +2 -1
  43. package/lib/json/from-csn.js +131 -78
  44. package/lib/json/to-csn.js +39 -23
  45. package/lib/language/antlrParser.js +0 -3
  46. package/lib/language/docCommentParser.js +7 -3
  47. package/lib/language/errorStrategy.js +3 -2
  48. package/lib/language/genericAntlrParser.js +96 -41
  49. package/lib/language/language.g4 +112 -128
  50. package/lib/language/multiLineStringParser.js +2 -1
  51. package/lib/main.d.ts +115 -2
  52. package/lib/main.js +16 -3
  53. package/lib/model/csnRefs.js +32 -4
  54. package/lib/model/csnUtils.js +109 -179
  55. package/lib/model/enrichCsn.js +13 -8
  56. package/lib/model/revealInternalProperties.js +4 -3
  57. package/lib/optionProcessor.js +22 -3
  58. package/lib/render/manageConstraints.js +11 -15
  59. package/lib/render/toCdl.js +144 -47
  60. package/lib/render/toHdbcds.js +22 -22
  61. package/lib/render/toRename.js +3 -4
  62. package/lib/render/toSql.js +31 -22
  63. package/lib/render/utils/delta.js +3 -1
  64. package/lib/render/utils/sql.js +2 -14
  65. package/lib/transform/db/associations.js +6 -6
  66. package/lib/transform/db/cdsPersistence.js +3 -3
  67. package/lib/transform/db/constraints.js +4 -6
  68. package/lib/transform/db/expansion.js +4 -4
  69. package/lib/transform/db/flattening.js +12 -15
  70. package/lib/transform/db/temporal.js +4 -3
  71. package/lib/transform/db/transformExists.js +13 -7
  72. package/lib/transform/draft/db.js +7 -7
  73. package/lib/transform/forOdataNew.js +15 -4
  74. package/lib/transform/forRelationalDB.js +59 -41
  75. package/lib/transform/odata/toFinalBaseType.js +106 -82
  76. package/lib/transform/odata/typesExposure.js +26 -17
  77. package/lib/transform/odata/utils.js +1 -1
  78. package/lib/transform/parseExpr.js +1 -1
  79. package/lib/transform/transformUtilsNew.js +33 -10
  80. package/lib/transform/translateAssocsToJoins.js +8 -7
  81. package/lib/transform/universalCsn/coreComputed.js +7 -5
  82. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -4
  83. package/lib/utils/timetrace.js +2 -2
  84. package/package.json +1 -2
@@ -15,6 +15,7 @@ optionProcessor
15
15
  .option('-w, --warning <level>', ['0', '1', '2', '3'])
16
16
  .option(' --quiet')
17
17
  .option(' --show-message-id')
18
+ .option(' --no-message-id')
18
19
  .option(' --no-message-context')
19
20
  .option(' --color <mode>', ['auto', 'always', 'never'])
20
21
  .option('-o, --out <dir>')
@@ -33,6 +34,7 @@ optionProcessor
33
34
  .option(' --deprecated <list>')
34
35
  .option(' --direct-backend')
35
36
  .option(' --fallback-parser <type>', ['cdl', 'csn', 'csn!'])
37
+ .option(' --shuffle <seed>') // 0 | 1..4294967296
36
38
  .option(' --test-mode')
37
39
  .option(' --test-sort-csn')
38
40
  .option(' --doc-comment')
@@ -64,7 +66,8 @@ optionProcessor
64
66
  --options <file> Use the given JSON file as input options.
65
67
  The key 'cdsc' of 'cds' is used. If not present 'cdsc' is used.
66
68
  Otherwise, the JSON as-is is used as options.
67
- --show-message-id Show message ID in error, warning and info messages
69
+ --no-message-id Don't show message IDs in errors, warnings, info and debug messages
70
+ --show-message-id DEPRECATED: Showing the message ID is now the default.
68
71
  --no-message-context Print messages as single lines without code context (useful for
69
72
  redirecting output to other processes). Default is to print human-
70
73
  readable text similar to Rust's compiler with a code excerpt.
@@ -106,7 +109,7 @@ optionProcessor
106
109
  postgres
107
110
  aspectWithoutElements
108
111
  odataOpenType
109
- odtaTerms
112
+ odataTerms
110
113
  optionalActionFunctionParameters
111
114
  --deprecated <list> Comma separated list of deprecated options.
112
115
  Valid values are:
@@ -121,6 +124,11 @@ optionProcessor
121
124
  Can only be used with certain new CSN based backends. Combination with
122
125
  other flags is limited, e.g. --test-mode will not run a consistency check.
123
126
  No recompilation is triggered in case of errors. cdsc will dump.
127
+ --shuffle <seed> If provided, some internal processing sequences are changed, most notably by
128
+ using a shuffled version of ‹model›.definitions. <seed> should be a number
129
+ between 1 and 4294967296, the compiler uses a random number in that range if the
130
+ provided argument is 0 or not a number. The same number always produces the same
131
+ shuffled version of ‹model›.definitions. This option also enables --test-mode.
124
132
  --test-mode Produce extra-stable output for automated tests (normalize filenames
125
133
  in errors, sort properties in CSN, omit version in CSN)
126
134
  --test-sort-csn Sort the generated CSN by definitions. This impacts the order of EDMX,
@@ -150,7 +158,8 @@ optionProcessor
150
158
  Environment variables
151
159
  NO_COLOR If set, compiler messages (/output) will not be colored.
152
160
  Can be overwritten by '--color'
153
- CDSC_TIMETRACING If set, additional timing information is printed to stderr.
161
+ CDSC_TRACE_TIME If set, additional timing information is printed to stderr.
162
+ CDSC_TRACE_API If set, additional API calling information is printed to stderr.
154
163
  `);
155
164
 
156
165
  // ----------- toHana -----------
@@ -284,6 +293,7 @@ optionProcessor.command('Q, toSql')
284
293
  .option(' --constraints-in-create-table')
285
294
  .option(' --pre2134ReferentialConstraintNames')
286
295
  .option(' --disable-hana-comments')
296
+ .option(' --generated-by-comment')
287
297
  .help(`
288
298
  Usage: cdsc toSql [options] <files...>
289
299
 
@@ -334,6 +344,8 @@ optionProcessor.command('Q, toSql')
334
344
  "ALTER TABLE ADD CONSTRAINT" statements
335
345
  --pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
336
346
  --disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
347
+ --generated-by-comment Enable rendering of the initial SQL comment for HDI-based artifacts
348
+
337
349
  `);
338
350
 
339
351
  optionProcessor.command('toRename')
@@ -367,6 +379,7 @@ optionProcessor.command('manageConstraints')
367
379
  .option(' --violations')
368
380
  .option(' --integrity-not-validated')
369
381
  .option(' --integrity-not-enforced')
382
+ .option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres', 'h2'], { aliases: [ '--dialect' ] })
370
383
  .help(`
371
384
  Usage: cdsc manageConstraints [options] <files...>
372
385
 
@@ -395,6 +408,12 @@ optionProcessor.command('manageConstraints')
395
408
  referential integrity violations on the existing data
396
409
  --integrity-not-enforced If this option is supplied, referential constraints are NOT ENFORCED.
397
410
  --integrity-not-validated If this option is supplied, referential constraints are NOT VALIDATED.
411
+ -d, --sql-dialect <dialect> SQL dialect to be generated:
412
+ plain : (default) Common SQL - no assumptions about DB restrictions (constraints not supported in this dialect)
413
+ hana : SQL with HANA specific language features
414
+ sqlite : Common SQL for sqlite
415
+ postgres : Common SQL for postgres - beta-feature
416
+ h2 : Common SQL for h2 (constraints not supported in this dialect)
398
417
  `);
399
418
 
400
419
  optionProcessor.command('toCsn')
@@ -13,6 +13,7 @@ const { transformForRelationalDBWithCsn } = require('../transform/forRelationalD
13
13
  const {
14
14
  renderReferentialConstraint, getIdentifierUtils,
15
15
  } = require('./utils/sql');
16
+ const { sortCsn } = require('../json/to-csn');
16
17
 
17
18
  /**
18
19
  * Used only by `cdsc manageConstraints`.
@@ -25,17 +26,15 @@ function alterConstraintsWithCsn( csn, options ) {
25
26
  const { error } = makeMessageFunction(csn, options, 'manageConstraints');
26
27
 
27
28
  const {
28
- drop, alter, names, src, violations,
29
- } = options.manageConstraints || {};
29
+ drop, alter, src, violations,
30
+ } = options || {};
30
31
 
31
32
  if (drop && alter)
32
33
  error(null, null, 'Option “--drop” can\'t be combined with “--alter”');
33
34
 
34
- options.sqlDialect = 'hana';
35
- options.sqlMapping = names || 'plain';
36
35
 
37
36
  // Of course we want the database constraints
38
- options.assertIntegrityType = 'DB';
37
+ options.assertIntegrityType = options.assertIntegrityType || 'DB';
39
38
 
40
39
  const transformedOptions = _transformSqlOptions(csn, options);
41
40
  const forSqlCsn = transformForRelationalDBWithCsn(csn, transformedOptions, 'to.sql');
@@ -49,7 +48,7 @@ function alterConstraintsWithCsn( csn, options ) {
49
48
  if (violations)
50
49
  intermediateResult = listReferentialIntegrityViolations(forSqlCsn, transformedOptions);
51
50
  else
52
- intermediateResult = manageConstraints(forSqlCsn, transformedOptions);
51
+ intermediateResult = manageConstraints(options.testMode ? sortCsn(forSqlCsn) : forSqlCsn, transformedOptions);
53
52
 
54
53
  return intermediateResult;
55
54
  }
@@ -92,19 +91,16 @@ function _transformSqlOptions( model, options ) {
92
91
  * @returns a map holding the constraint identifier as key and the corresponding, rendered SQL statement / hdbconstraint artifact as value.
93
92
  */
94
93
  function manageConstraints( csn, options ) {
95
- const {
96
- drop, alter, src,
97
- } = options.manageConstraints || {};
98
- const indent = '';
94
+ const indent = options.src === 'hdi' ? ' ' : ''; // indent `.hdbconstraint`
99
95
  // either ALTER TABLE statements or .hdbconstraint artifacts
100
96
  const resultArtifacts = {};
101
97
  const { quoteSqlId } = getIdentifierUtils(csn, options);
102
98
  forEachDefinition(csn, (artifact) => {
103
- if (artifact.$tableConstraints && artifact.$tableConstraints.referential) {
99
+ if (artifact.$tableConstraints?.referential) {
104
100
  forEach(artifact.$tableConstraints.referential, (fileName, constraint) => {
105
- const renderAlterConstraintStatement = alter && src !== 'hdi';
101
+ const renderAlterConstraintStatement = options.alter && options.src !== 'hdi';
106
102
  const renderedConstraint = renderReferentialConstraint(constraint, indent, false, csn, options, renderAlterConstraintStatement);
107
- if (src === 'hdi') {
103
+ if (options.src === 'hdi' && !options.drop) {
108
104
  resultArtifacts[fileName] = renderedConstraint;
109
105
  return;
110
106
  }
@@ -112,7 +108,7 @@ function manageConstraints( csn, options ) {
112
108
  alterTableStatement += `${indent}ALTER TABLE ${quoteSqlId(getResultingName(csn, options.sqlMapping, constraint.dependentTable))}`;
113
109
  if (renderAlterConstraintStatement)
114
110
  alterTableStatement += `\n${indent}ALTER ${renderedConstraint};`;
115
- else if (drop)
111
+ else if (options.drop)
116
112
  alterTableStatement += `${indent} DROP CONSTRAINT ${quoteSqlId(constraint.identifier)};`;
117
113
  else
118
114
  alterTableStatement += `\n${indent}ADD ${renderedConstraint};`;
@@ -121,7 +117,7 @@ function manageConstraints( csn, options ) {
121
117
  });
122
118
  }
123
119
  });
124
- return resultArtifacts;
120
+ return options.src === 'hdi' ? resultArtifacts : Object.values(resultArtifacts);
125
121
  }
126
122
 
127
123
  /**
@@ -5,7 +5,7 @@ const { findElement, createExpressionRenderer, withoutCast } = require('./utils/
5
5
  const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
6
6
  const { checkCSNVersion } = require('../json/csnVersion');
7
7
  const { timetrace } = require('../utils/timetrace');
8
- const { forEachDefinition } = require('../model/csnUtils');
8
+ const { forEachDefinition, normalizeTypeRef } = require('../model/csnUtils');
9
9
  const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
10
10
  const { isBetaEnabled } = require('../base/model');
11
11
  const { ModelError } = require('../base/error');
@@ -19,6 +19,7 @@ const {
19
19
  } = require('../model/csnUtils');
20
20
 
21
21
  const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
22
+ const specialFunctionKeywords = Object.create(null);
22
23
 
23
24
  /**
24
25
  * Render the CSN model 'model' to CDS source text.
@@ -30,6 +31,7 @@ const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
30
31
  * @param {CSN.Options} [options]
31
32
  */
32
33
  function csnToCdl( csn, options ) {
34
+ const special$self = !csn?.definitions?.$self && '$self';
33
35
  timetrace.start('CDL rendering');
34
36
 
35
37
  if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
@@ -48,7 +50,7 @@ function csnToCdl( csn, options ) {
48
50
  addIfRequired(name) {
49
51
  // RegEx is at least twice as fast as .split()[0]
50
52
  const first = name.match(/^[^.]+/)[0];
51
- if (!this.available.includes(first) && !this.list.includes(first))
53
+ if (name !== special$self && !this.available.includes(first) && !this.list.includes(first))
52
54
  this.list.push(first);
53
55
  },
54
56
  renderUsings() {
@@ -197,11 +199,11 @@ function csnToCdl( csn, options ) {
197
199
  // The former one can not only extend (sub-)elements but also actions in the same statement whereas
198
200
  // the latter cannot.
199
201
  // If there are actions, check if there are also elements/columns, and if so, use the prefix notation.
200
- const usePrefixNotation = isElementExtend || ext.actions && (ext.columns || ext.elements);
202
+ const usePrefixNotation = ext.actions && (ext.columns || ext.elements);
201
203
  if (usePrefixNotation)
202
204
  result += `${env.indent}extend ${getExtendPrefixVariant(ext)} ${extName} with {\n`;
203
205
  else
204
- result += `${env.indent}extend ${extName} with ${getExtendPostfixVariant(ext)} {\n`;
206
+ result += `${env.indent}extend ${extName} with ${getExtendPostfixVariant(ext)}{\n`;
205
207
 
206
208
  if (ext.columns)
207
209
  result += renderViewColumns(ext.columns, increaseIndent(env));
@@ -252,13 +254,20 @@ function csnToCdl( csn, options ) {
252
254
  */
253
255
  function getExtendPostfixVariant( ext ) {
254
256
  if (ext.columns)
255
- return 'columns';
257
+ return 'columns ';
256
258
  if (ext.actions)
257
- return 'actions';
258
- if (ext.elements)
259
- return 'elements';
259
+ return 'actions ';
260
260
  if (ext.enum)
261
- return 'enum';
261
+ return 'enum ';
262
+ if (ext.elements) { // enum/elements ambiguity -> look into elements
263
+ // TODO: Check for `type` as well once this is supported by the parser (and identified as elements):
264
+ // `extend E with { a = 'string'; b: String }`
265
+ const isLikelyElement = Object.keys(ext.elements)
266
+ .find(name => ext.elements[name].value !== undefined);
267
+ if (isLikelyElement)
268
+ return 'elements ';
269
+ }
270
+ // ambiguity; no postfix, i.e. `extend … with { … }`.s
262
271
  return '';
263
272
  }
264
273
 
@@ -544,16 +553,25 @@ function csnToCdl( csn, options ) {
544
553
  // TODO(v4): Remove once deprecated flag for `masked` is removed.
545
554
  result += elm.masked ? 'masked ' : '';
546
555
  result += quoteIdIfRequired(elementName);
547
- if (elm.val !== undefined) {
556
+ if (elm.val !== undefined) { // enum value
548
557
  result += ` = ${exprRenderer.renderExpr(elm, env)}`;
549
558
  }
550
- else if (elm['#'] !== undefined) {
559
+ else if (elm['#'] !== undefined) { // enum symbol reference
551
560
  result += ` = #${elm['#']}`;
552
561
  }
553
562
  else {
554
- const props = renderTypeReferenceAndProps(elm, env);
555
- if (props !== '')
556
- result += ` : ${props}`;
563
+ // TODO: Maybe push the .value check into renderTypeReferenceAndProps?
564
+ const type = normalizeTypeRef(elm.items?.type || elm.type);
565
+ const isAssoc = type === 'cds.Association' || type === 'cds.Composition';
566
+ if (!isAssoc || elm.value === undefined) {
567
+ const props = renderTypeReferenceAndProps(elm, env);
568
+ if (props !== '')
569
+ result += ` : ${props}`;
570
+ }
571
+ }
572
+
573
+ if (elm.value !== undefined) { // calculated element // @ts-ignore
574
+ result += ` = ${exprRenderer.renderExpr(elm.value, env)}`;
557
575
  }
558
576
 
559
577
  return `${result};\n`;
@@ -632,7 +650,8 @@ function csnToCdl( csn, options ) {
632
650
  * @return {boolean} True, if there were annotations, false otherwise.
633
651
  */
634
652
  function collectAnnos( annotateObj, art ) {
635
- if (!art.elements && !art.enum)
653
+ if (!Object.hasOwnProperty.call(art, 'elements') &&
654
+ !Object.hasOwnProperty.call(art, 'enum'))
636
655
  return false;
637
656
 
638
657
  const dictKey = art.elements ? 'elements' : 'enum';
@@ -680,7 +699,7 @@ function csnToCdl( csn, options ) {
680
699
  if (source.SELECT || source.SET) {
681
700
  let result = `(${renderQuery(source, false, 'view', increaseIndent(env))})`;
682
701
  if (source.as)
683
- result += ` as ${quoteIdIfRequired(source.as)}`;
702
+ result += renderAlias(source.as);
684
703
 
685
704
  return result;
686
705
  }
@@ -770,7 +789,7 @@ function csnToCdl( csn, options ) {
770
789
  let result = renderAbsolutePath(path, env);
771
790
  if (path.as) {
772
791
  // Source had an alias - render it
773
- result += ` as ${quoteIdIfRequired(path.as)}`;
792
+ result += renderAlias(path.as);
774
793
  }
775
794
  return result;
776
795
  }
@@ -825,7 +844,7 @@ function csnToCdl( csn, options ) {
825
844
  // A new association (cast with `type` and `target`) uses `as` as its primary name, not alias.
826
845
  const isNewAssociation = col.cast?.type && col.cast.target;
827
846
  if (!isNewAssociation && col.as && !col.inline && !col.expand)
828
- result += ` as ${quoteIdIfRequired(col.as)}`;
847
+ result += renderAlias(col.as);
829
848
 
830
849
  // Explicit type provided for the view element?
831
850
  if (col.cast) {
@@ -852,7 +871,7 @@ function csnToCdl( csn, options ) {
852
871
 
853
872
  // s as alias { * }
854
873
  if (obj.as && (obj.ref || obj.xpr || obj.val !== undefined || obj.func !== undefined))
855
- result += ` as ${obj.as}`;
874
+ result += renderAlias(obj.as);
856
875
 
857
876
  // We found a leaf - no further drilling
858
877
  if (!obj.inline && !obj.expand) {
@@ -882,7 +901,7 @@ function csnToCdl( csn, options ) {
882
901
 
883
902
  // { * } as expand
884
903
  if (!obj.ref && obj.as)
885
- result += ` as ${obj.as}`;
904
+ result += renderAlias(obj.as);
886
905
 
887
906
  return result;
888
907
  }
@@ -900,12 +919,16 @@ function csnToCdl( csn, options ) {
900
919
  else if (obj && obj.doc === null) // empty doc comment needs to be rendered
901
920
  return `\n${env.indent}/** */\n`;
902
921
 
903
- // Smaller comment for single-line comments. If the comments starts or ends with whitespace
922
+ let { doc } = obj;
923
+ if (/[^\\][*]\//.test(doc))
924
+ doc = doc.replace(/([^\\])[*]\//g, '$1*\\/');
925
+
926
+ // Smaller comment for single-line comments. If the comment starts or ends with whitespace
904
927
  // we must use a block comment, or it will be lost when compiling the source again.
905
- if (!obj.doc.includes('\n') && !/^\s|\s$/.test(obj.doc))
906
- return `${env.indent}/** ${obj.doc} */\n`;
928
+ if (!obj.doc.includes('\n') && !/(^\s)|(\s$)/.test(obj.doc))
929
+ return `${env.indent}/** ${doc} */\n`;
907
930
 
908
- const comment = obj.doc.split('\n').map(line => `${env.indent} * ${line}`).join('\n');
931
+ const comment = doc.split('\n').map(line => `${env.indent} * ${line}`).join('\n');
909
932
  return `${env.indent}/**\n${comment}\n${env.indent} */\n`;
910
933
  }
911
934
 
@@ -984,8 +1007,7 @@ function csnToCdl( csn, options ) {
984
1007
  result += ` excluding {\n${select.excluding.map(id => `${childEnv.indent}${quoteIdIfRequired(id)}`).join(',\n')}\n`;
985
1008
  result += `${env.indent}}`;
986
1009
  }
987
- // FIXME: Currently, only projections can have actions and functions, but we cannot distinguish
988
- // a projection from a view any more
1010
+
989
1011
  if (isLeadingQuery)
990
1012
  result += renderActionsAndFunctions(query, env);
991
1013
 
@@ -1215,7 +1237,9 @@ function csnToCdl( csn, options ) {
1215
1237
  // "many many" does not work in CDL, so we don't check for it.
1216
1238
  }
1217
1239
 
1218
- if (!artifact.type && artifact.elements) {
1240
+ const type = normalizeTypeRef(artifact.type);
1241
+
1242
+ if (!type && artifact.elements) {
1219
1243
  result += renderElements(artifact, env);
1220
1244
  if (!isTypeDef)
1221
1245
  result += renderNullability(artifact);
@@ -1224,8 +1248,8 @@ function csnToCdl( csn, options ) {
1224
1248
  }
1225
1249
 
1226
1250
  // Association type
1227
- if (artifact.type === 'cds.Association' || artifact.type === 'cds.Composition') {
1228
- const isComp = artifact.type === 'cds.Composition';
1251
+ if (type === 'cds.Association' || type === 'cds.Composition') {
1252
+ const isComp = type === 'cds.Composition';
1229
1253
  // Type, cardinality and target; CAPire uses CamelCase
1230
1254
  result += isComp ? 'Composition' : 'Association';
1231
1255
  result += renderCardinality(artifact);
@@ -1276,11 +1300,11 @@ function csnToCdl( csn, options ) {
1276
1300
  }
1277
1301
 
1278
1302
  // Reference to another artifact
1279
- if (typeof artifact.type === 'string') {
1303
+ if (typeof type === 'string') {
1280
1304
  // If we get here, it must be a named type
1281
1305
  result += renderNamedTypeWithParameters(artifact);
1282
1306
  }
1283
- else if (artifact.type?.ref) {
1307
+ else if (type?.ref) {
1284
1308
  result += renderAbsolutePath(artifact.type, env);
1285
1309
  }
1286
1310
 
@@ -1316,21 +1340,22 @@ function csnToCdl( csn, options ) {
1316
1340
  * @return {string}
1317
1341
  */
1318
1342
  function renderNamedTypeWithParameters( artWithType ) {
1343
+ const type = normalizeTypeRef(artWithType.type);
1319
1344
  let result = '';
1320
1345
 
1321
- if (isBuiltinType(artWithType.type)) {
1346
+ if (isBuiltinType(type)) {
1322
1347
  // If there is a user-defined type that starts with the same short name
1323
1348
  // (cds.Integer -> Integer), we render the full name, including the leading "cds."
1324
- const shortHand = artWithType.type.slice(4);
1349
+ const shortHand = type.slice(4);
1325
1350
  if (shortHand.startsWith('hana.') && hanaRequiresAbsolutePath)
1326
- result += artWithType.type;
1351
+ result += type;
1327
1352
  else if (usings.available.includes(shortHand))
1328
- result += artWithType.type;
1353
+ result += type;
1329
1354
  else
1330
1355
  result += shortHand;
1331
1356
  }
1332
1357
  else {
1333
- result += renderDefinitionReference(artWithType.type);
1358
+ result += renderDefinitionReference(type);
1334
1359
  }
1335
1360
 
1336
1361
  result += renderTypeParameters(artWithType);
@@ -1341,7 +1366,7 @@ function csnToCdl( csn, options ) {
1341
1366
  /**
1342
1367
  * Render the 'enum { ... } part of a type declaration
1343
1368
  *
1344
- * @param {CSN.EnumElements} enumPart
1369
+ * @param {CSN.EnumList} enumPart
1345
1370
  * @param {CdlRenderEnvironment} env
1346
1371
  * @return {string}
1347
1372
  */
@@ -1391,7 +1416,8 @@ function csnToCdl( csn, options ) {
1391
1416
  const values = keys.map(key => `${quoteAnnotationPathIfRequired(key)}: ${renderAnnotationValue(x[key], childEnv)}`);
1392
1417
  if (values.length <= 1)
1393
1418
  return `{ ${values.join(', ')} }`;
1394
- return `{\n${childEnv.indent}${values.join(`,\n${childEnv.indent}`)}\n${env.indent}}`;
1419
+ const valueList = values.join(`,\n${childEnv.indent}`);
1420
+ return `{\n${childEnv.indent}${valueList}\n${env.indent}}`;
1395
1421
  }
1396
1422
  // Null
1397
1423
  else if (x === null) {
@@ -1539,7 +1565,7 @@ function csnToCdl( csn, options ) {
1539
1565
  * @return {string}
1540
1566
  */
1541
1567
  function renderBracketCardinality( art ) {
1542
- const isComp = art.type === 'cds.Composition';
1568
+ const isComp = normalizeTypeRef(art.type) === 'cds.Composition';
1543
1569
  const suffix = (isComp ? ' of ' : ' to ');
1544
1570
  const card = art.cardinality;
1545
1571
 
@@ -1583,7 +1609,7 @@ function csnToCdl( csn, options ) {
1583
1609
  * @return {string}
1584
1610
  */
1585
1611
  function renderSimpleCardinality( elem ) {
1586
- let result = (elem.type === 'cds.Association' ? ' to ' : ' of ');
1612
+ let result = (normalizeTypeRef(elem.type) === 'cds.Association' ? ' to ' : ' of ');
1587
1613
  if (!elem.cardinality)
1588
1614
  return result;
1589
1615
  if (elem.cardinality.max === '*')
@@ -1610,10 +1636,20 @@ function csnToCdl( csn, options ) {
1610
1636
  * @return {string}
1611
1637
  */
1612
1638
  function renderForeignKey( fKey, env ) {
1613
- const alias = fKey.as ? (` as ${fKey.as}`) : '';
1639
+ const alias = fKey.as ? renderAlias(fKey.as) : '';
1614
1640
  return exprRenderer.renderExpr(fKey, env) + alias;
1615
1641
  }
1616
1642
 
1643
+ /**
1644
+ * Render an explicit alias, e.g. for columns.
1645
+ *
1646
+ * @param {string} alias
1647
+ * @return {string}
1648
+ */
1649
+ function renderAlias( alias ) {
1650
+ return ` as ${quoteIdIfRequired(alias)}`;
1651
+ }
1652
+
1617
1653
  /**
1618
1654
  * Render (primitive) type parameters of artifact 'artWithType', i.e.
1619
1655
  * length, precision and scale (even if incomplete), plus any other unknown ones.
@@ -1695,7 +1731,7 @@ function csnToCdl( csn, options ) {
1695
1731
  result += '(';
1696
1732
 
1697
1733
  if (annoRequiresQuoting || variantRequiresQuoting)
1698
- result += quote(name);
1734
+ result += delimitedId(name);
1699
1735
  else
1700
1736
  result += name;
1701
1737
 
@@ -1775,9 +1811,9 @@ function csnToCdl( csn, options ) {
1775
1811
  return `${funcDef} ${this.renderExpr(x.xpr)}`; // xpr[0] is 'over'
1776
1812
  },
1777
1813
  func(x) {
1778
- if (keywords.cdl_functions.includes(x.func.toUpperCase()))
1814
+ if (keywords.cdl_functions.includes(x.func.toUpperCase()) && !x.args)
1779
1815
  return x.func;
1780
- const name = identifierRegex.test(x.func) ? x.func : quote(x.func);
1816
+ const name = smartFunctionId(x.func);
1781
1817
  return `${name}(${renderArguments( x, '=>', this.env )})`;
1782
1818
  },
1783
1819
  xpr(x) {
@@ -1863,7 +1899,7 @@ function quotePathIfRequired( path ) {
1863
1899
  function quoteIdIfRequired( id, additionalKeywords ) {
1864
1900
  // Quote if required for CDL
1865
1901
  if (requiresQuotingForCdl(id, additionalKeywords || []))
1866
- return quote(id);
1902
+ return delimitedId(id);
1867
1903
  return id;
1868
1904
  }
1869
1905
 
@@ -1890,7 +1926,7 @@ function quoteAnnotationPathIfRequired( anno ) {
1890
1926
  * @param id
1891
1927
  * @returns {string}
1892
1928
  */
1893
- function quote( id ) {
1929
+ function delimitedId( id ) {
1894
1930
  return `![${id.replace(/]/g, ']]')}]`;
1895
1931
  }
1896
1932
 
@@ -1980,6 +2016,31 @@ function getKeywordsForSpecialFunctionArgument( funcName, argumentIndex ) {
1980
2016
  return additionalKeywords;
1981
2017
  }
1982
2018
 
2019
+ /**
2020
+ * Get a list of all special keywords for the given function.
2021
+ *
2022
+ * @param {string} funcName
2023
+ * @return {undefined|string[]}
2024
+ */
2025
+ function getAllKeywordsForSpecialFunction( funcName ) {
2026
+ if (specialFunctionKeywords[funcName])
2027
+ return specialFunctionKeywords[funcName];
2028
+ else if (!specialFunctions[funcName])
2029
+ return undefined;
2030
+
2031
+ const additionalKeywords = [];
2032
+ for (const arg of specialFunctions[funcName]) {
2033
+ if (arg.intro)
2034
+ additionalKeywords.push(...arg.intro);
2035
+ if (arg.expr)
2036
+ additionalKeywords.push(...arg.expr);
2037
+ if (arg.separator)
2038
+ additionalKeywords.push(...arg.separator);
2039
+ }
2040
+ specialFunctionKeywords[funcName] = additionalKeywords;
2041
+ return additionalKeywords;
2042
+ }
2043
+
1983
2044
  /**
1984
2045
  * Render the given string. Uses back-tick strings.
1985
2046
  * env is used for indentation of three-back-tick strings.
@@ -2068,6 +2129,37 @@ function availableFirstPathSteps( csn ) {
2068
2129
  return Array.from(unique);
2069
2130
  }
2070
2131
 
2132
+ /**
2133
+ * Returns a delimited identifier if the given identifier needs quoting.
2134
+ * Because "special functions" such as SAP HANA RegEx functions have local keywords that
2135
+ * are not default CDL keywords, specify a function name to take care of that.
2136
+ *
2137
+ * @param {string} id
2138
+ * @param {null|string} insideFunction
2139
+ * @return {string}
2140
+ */
2141
+ function smartId( id, insideFunction = null ) {
2142
+ insideFunction = insideFunction?.toUpperCase();
2143
+ if (!insideFunction || !specialFunctions[insideFunction])
2144
+ return quoteIdIfRequired(id);
2145
+ return quoteIdIfRequired(id, getAllKeywordsForSpecialFunction(insideFunction));
2146
+ }
2147
+
2148
+ /**
2149
+ * Quote the given function name if required.
2150
+ *
2151
+ * @param {string} funcName
2152
+ * @return {string}
2153
+ */
2154
+ function smartFunctionId( funcName ) {
2155
+ const funcId = funcName.toUpperCase();
2156
+ const requiresQuoting = !identifierRegex.test(funcName) ||
2157
+ (keywords.cdl.includes(funcId) && !specialFunctions[funcId]);
2158
+ if (requiresQuoting)
2159
+ return delimitedId(funcName);
2160
+ return funcName;
2161
+ }
2162
+
2071
2163
  /**
2072
2164
  * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
2073
2165
  *
@@ -2078,4 +2170,9 @@ function availableFirstPathSteps( csn ) {
2078
2170
  * @property {string[]} [additionalKeywords] For function rendering: Words that are also keywords.
2079
2171
  */
2080
2172
 
2081
- module.exports = { csnToCdl };
2173
+ module.exports = {
2174
+ csnToCdl,
2175
+ smartId,
2176
+ smartFunctionId,
2177
+ delimitedId,
2178
+ };