@sap/cds-compiler 4.4.4 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/bin/cdsc.js +18 -11
  3. package/bin/cdsv2m.js +7 -5
  4. package/doc/CHANGELOG_BETA.md +22 -0
  5. package/lib/api/main.js +306 -144
  6. package/lib/api/options.js +18 -6
  7. package/lib/api/validate.js +1 -1
  8. package/lib/base/message-registry.js +45 -10
  9. package/lib/base/messages.js +33 -16
  10. package/lib/base/model.js +4 -0
  11. package/lib/base/optionProcessorHelper.js +45 -176
  12. package/lib/checks/annotationsOData.js +49 -0
  13. package/lib/checks/elements.js +32 -34
  14. package/lib/checks/enricher.js +39 -3
  15. package/lib/checks/validator.js +8 -7
  16. package/lib/compiler/assert-consistency.js +40 -17
  17. package/lib/compiler/builtins.js +30 -53
  18. package/lib/compiler/checks.js +46 -14
  19. package/lib/compiler/cycle-detector.js +1 -4
  20. package/lib/compiler/define.js +35 -10
  21. package/lib/compiler/extend.js +21 -7
  22. package/lib/compiler/generate.js +3 -0
  23. package/lib/compiler/populate.js +5 -1
  24. package/lib/compiler/propagator.js +46 -9
  25. package/lib/compiler/resolve.js +94 -35
  26. package/lib/compiler/shared.js +60 -33
  27. package/lib/compiler/tweak-assocs.js +188 -92
  28. package/lib/compiler/utils.js +11 -1
  29. package/lib/edm/annotations/edmJson.js +41 -66
  30. package/lib/edm/annotations/genericTranslation.js +27 -9
  31. package/lib/edm/annotations/preprocessAnnotations.js +2 -3
  32. package/lib/edm/csn2edm.js +28 -11
  33. package/lib/edm/edmInboundChecks.js +58 -15
  34. package/lib/edm/edmPreprocessor.js +12 -16
  35. package/lib/edm/edmUtils.js +5 -2
  36. package/lib/gen/Dictionary.json +10 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +15 -2
  39. package/lib/gen/language.tokens +1 -0
  40. package/lib/gen/languageParser.js +6557 -5618
  41. package/lib/json/from-csn.js +4 -5
  42. package/lib/json/to-csn.js +29 -4
  43. package/lib/language/antlrParser.js +19 -1
  44. package/lib/language/errorStrategy.js +28 -7
  45. package/lib/language/genericAntlrParser.js +118 -24
  46. package/lib/language/textUtils.js +16 -0
  47. package/lib/main.d.ts +28 -3
  48. package/lib/main.js +3 -0
  49. package/lib/model/csnRefs.js +4 -1
  50. package/lib/model/csnUtils.js +20 -14
  51. package/lib/model/revealInternalProperties.js +5 -2
  52. package/lib/optionProcessor.js +23 -22
  53. package/lib/render/manageConstraints.js +13 -29
  54. package/lib/render/toCdl.js +47 -26
  55. package/lib/render/toHdbcds.js +63 -42
  56. package/lib/render/toRename.js +6 -10
  57. package/lib/render/toSql.js +71 -117
  58. package/lib/render/utils/common.js +41 -6
  59. package/lib/transform/.eslintrc.json +9 -1
  60. package/lib/transform/addTenantFields.js +228 -0
  61. package/lib/transform/db/applyTransformations.js +57 -4
  62. package/lib/transform/db/assertUnique.js +4 -4
  63. package/lib/transform/db/backlinks.js +13 -1
  64. package/lib/transform/db/cdsPersistence.js +1 -1
  65. package/lib/transform/db/expansion.js +24 -3
  66. package/lib/transform/db/flattening.js +70 -71
  67. package/lib/transform/db/killAnnotations.js +37 -0
  68. package/lib/transform/db/rewriteCalculatedElements.js +46 -6
  69. package/lib/transform/db/temporal.js +1 -1
  70. package/lib/transform/draft/db.js +2 -16
  71. package/lib/transform/draft/odata.js +3 -3
  72. package/lib/transform/effective/associations.js +3 -5
  73. package/lib/transform/effective/main.js +6 -9
  74. package/lib/transform/forOdata.js +26 -55
  75. package/lib/transform/forRelationalDB.js +38 -18
  76. package/lib/transform/odata/toFinalBaseType.js +3 -3
  77. package/lib/transform/odata/typesExposure.js +14 -5
  78. package/lib/transform/transformUtils.js +47 -34
  79. package/lib/transform/translateAssocsToJoins.js +45 -11
  80. package/lib/transform/universalCsn/coreComputed.js +1 -1
  81. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  82. package/package.json +7 -6
@@ -13,12 +13,12 @@ optionProcessor
13
13
  .option('-h, --help')
14
14
  .option('-v, --version')
15
15
  .option(' --options <file>')
16
- .option('-w, --warning <level>', ['0', '1', '2', '3'])
16
+ .option('-w, --warning <level>', { valid: ['0', '1', '2', '3'] })
17
17
  .option(' --quiet')
18
18
  .option(' --show-message-id')
19
19
  .option(' --no-message-id')
20
20
  .option(' --no-message-context')
21
- .option(' --color <mode>', ['auto', 'always', 'never'])
21
+ .option(' --color <mode>', { valid: ['auto', 'always', 'never'] })
22
22
  .option('-o, --out <dir>')
23
23
  .option(' --cds-home <dir>')
24
24
  .option(' --module-lookup-directories <list>')
@@ -34,13 +34,14 @@ optionProcessor
34
34
  .option(' --beta <list>')
35
35
  .option(' --deprecated <list>')
36
36
  .option(' --direct-backend')
37
- .option(' --fallback-parser <type>', ['cdl', 'csn', 'csn!'])
37
+ .option(' --fallback-parser <type>', { valid: ['cdl', 'csn', 'csn!'] })
38
38
  .option(' --shuffle <seed>') // 0 | 1..4294967296
39
39
  .option(' --test-mode')
40
40
  .option(' --test-sort-csn')
41
41
  .option(' --doc-comment')
42
42
  .option(' --add-texts-language-assoc')
43
43
  .option(' --localized-without-coalesce')
44
+ .option(' --tenant-as-column')
44
45
  .option(' --default-binary-length <length>')
45
46
  .option(' --default-string-length <length>')
46
47
  .option(' --no-recompile')
@@ -100,6 +101,7 @@ optionProcessor
100
101
  -E, --enrich-csn Show non-enumerable CSN properties and locations of references
101
102
  -R, --raw-output <name> Write XSN for definition "name" and error output to <stdout>,
102
103
  with name = "+", write complete XSN, long!
104
+ --tenant-as-column Add tenant fields to entities
103
105
  --internal-msg Write raw messages with call stack to <stdout>/<stderr>
104
106
  --beta-mode Enable all unsupported, incomplete (beta) features
105
107
  --beta <list> Comma separated list of unsupported, incomplete (beta) features to use.
@@ -164,7 +166,7 @@ optionProcessor
164
166
  // ----------- toHana -----------
165
167
  optionProcessor.command('H, toHana')
166
168
  .option('-h, --help')
167
- .option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: ['--names'] })
169
+ .option('-n, --sql-mapping <style>', { valid: ['plain', 'quoted', 'hdbcds'], aliases: ['--names'] })
168
170
  .option(' --render-virtual')
169
171
  .option(' --joinfk')
170
172
  .option('-u, --user <user>')
@@ -172,8 +174,8 @@ optionProcessor.command('H, toHana')
172
174
  .option('-c, --csn')
173
175
  .option(' --integrity-not-validated')
174
176
  .option(' --integrity-not-enforced')
175
- .option(' --assert-integrity <mode>', ['true', 'false', 'individual'])
176
- .option(' --assert-integrity-type <type>', ['RT', 'DB'], { ignoreCase: true })
177
+ .option(' --assert-integrity <mode>', { valid: ['true', 'false', 'individual'] })
178
+ .option(' --assert-integrity-type <type>', { valid: ['RT', 'DB'], ignoreCase: true })
177
179
  .option(' --pre2134ReferentialConstraintNames')
178
180
  .option(' --disable-hana-comments')
179
181
  .help(`
@@ -216,7 +218,7 @@ optionProcessor.command('H, toHana')
216
218
 
217
219
  optionProcessor.command('O, toOdata')
218
220
  .option('-h, --help')
219
- .option('-v, --odata-version <version>', ['v2', 'v4', 'v4x'], { aliases: ['--version'] })
221
+ .option('-v, --odata-version <version>', { valid: ['v2', 'v4', 'v4x'], aliases: ['--version'] })
220
222
  .option('-x, --xml')
221
223
  .option('-j, --json')
222
224
  .option(' --odata-containment')
@@ -227,10 +229,9 @@ optionProcessor.command('O, toOdata')
227
229
  .option(' --odata-foreign-keys')
228
230
  .option(' --odata-v2-partial-constr')
229
231
  .option(' --odata-vocabularies <list>')
230
- .option(' --odata-open-type')
231
232
  .option('-c, --csn')
232
- .option('-f, --odata-format <format>', ['flat', 'structured'])
233
- .option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
233
+ .option('-f, --odata-format <format>', { valid: ['flat', 'structured'] })
234
+ .option('-n, --sql-mapping <style>', { valid: ['plain', 'quoted', 'hdbcds'], aliases: [ '--names' ] })
234
235
  .option('-s, --service-names <list>')
235
236
  .option(' --fewer-localized-views')
236
237
  .help(`
@@ -260,7 +261,6 @@ optionProcessor.command('O, toOdata')
260
261
  (Not spec compliant and V2 only)
261
262
  --odata-vocabularies <list> JSON array of adhoc vocabulary definitions
262
263
  { prefix: { alias, ns, uri }, ... }
263
- --odata-open-type Renders all structured types as OpenType=true, if not annotated otherwise.
264
264
  -n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
265
265
  the corresponding database name (see "--sql-mapping" for "toHana or "toSql")
266
266
  plain : (default) Names in uppercase and flattened with underscores
@@ -287,24 +287,25 @@ optionProcessor.command('C, toCdl')
287
287
 
288
288
  optionProcessor.command('Q, toSql')
289
289
  .option('-h, --help')
290
- .option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
291
- .option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres', 'h2'], { aliases: [ '--dialect' ] })
290
+ .option('-n, --sql-mapping <style>', { valid: ['plain', 'quoted', 'hdbcds'], aliases: [ '--names' ] })
291
+ .option('-d, --sql-dialect <dialect>', { valid: ['hana', 'sqlite', 'plain', 'postgres', 'h2'], aliases: [ '--dialect' ] })
292
292
  .option(' --render-virtual')
293
293
  .option(' --joinfk')
294
294
  .option('-u, --user <user>')
295
295
  .option('-l, --locale <locale>')
296
- .option('-s, --src <style>', ['sql', 'hdi'])
296
+ .option('-s, --src <style>', { valid: ['sql', 'hdi'] })
297
297
  .option('-c, --csn')
298
298
  .option(' --integrity-not-validated')
299
299
  .option(' --integrity-not-enforced')
300
- .option(' --assert-integrity <mode>', ['true', 'false', 'individual'])
301
- .option(' --assert-integrity-type <type>', ['RT', 'DB'], { ignoreCase: true })
300
+ .option(' --assert-integrity <mode>', { valid: ['true', 'false', 'individual'] })
301
+ .option(' --assert-integrity-type <type>', { valid: ['RT', 'DB'], ignoreCase: true })
302
302
  .option(' --constraints-in-create-table')
303
303
  .option(' --pre2134ReferentialConstraintNames')
304
304
  .option(' --disable-hana-comments')
305
305
  .option(' --generated-by-comment')
306
306
  .option(' --better-sqlite-session-variables')
307
307
  .option(' --fewer-localized-views')
308
+ .option(' --without-hana-associations')
308
309
  .help(`
309
310
  Usage: cdsc toSql [options] <files...>
310
311
 
@@ -360,12 +361,12 @@ optionProcessor.command('Q, toSql')
360
361
  active if sqlDialect is \`sqlite\`
361
362
  --fewer-localized-views If set, the backends will not create localized convenience views for
362
363
  those views, that only have an association to a localized entity/view.
363
-
364
+ --without-hana-associations If set, the backend will not render a "WITH ASSOCIATIONS" for sqlDialect 'hana'
364
365
  `);
365
366
 
366
367
  optionProcessor.command('toRename')
367
368
  .option('-h, --help')
368
- .option('-n, --sql-mapping <style>', ['quoted', 'hdbcds'], { aliases: ['--names'] })
369
+ .option('-n, --sql-mapping <style>', { valid: ['quoted', 'hdbcds'], aliases: ['--names'] })
369
370
  .help(`
370
371
  Usage: cdsc toRename [options] <files...>
371
372
 
@@ -387,14 +388,14 @@ optionProcessor.command('toRename')
387
388
 
388
389
  optionProcessor.command('manageConstraints')
389
390
  .option('-h, --help')
390
- .option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: ['--names'] })
391
- .option('-s, --src <style>', ['sql', 'hdi'])
391
+ .option('-n, --sql-mapping <style>', { valid: ['plain', 'quoted', 'hdbcds'], aliases: ['--names'] })
392
+ .option('-s, --src <style>', { valid: ['sql', 'hdi'] })
392
393
  .option(' --drop')
393
394
  .option(' --alter')
394
395
  .option(' --violations')
395
396
  .option(' --integrity-not-validated')
396
397
  .option(' --integrity-not-enforced')
397
- .option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres', 'h2'], { aliases: [ '--dialect' ] })
398
+ .option('-d, --sql-dialect <dialect>', { valid: ['hana', 'sqlite', 'plain', 'postgres', 'h2'], aliases: [ '--dialect' ] })
398
399
  .help(`
399
400
  Usage: cdsc manageConstraints [options] <files...>
400
401
 
@@ -433,7 +434,7 @@ optionProcessor.command('manageConstraints')
433
434
 
434
435
  optionProcessor.command('toCsn')
435
436
  .option('-h, --help')
436
- .option('-f, --csn-flavor <flavor>', ['client', 'gensrc', 'universal'], { aliases: ['--flavor'] })
437
+ .option('-f, --csn-flavor <flavor>', { valid: ['client', 'gensrc', 'universal'], aliases: ['--flavor'] })
437
438
  .option(' --with-localized')
438
439
  .option(' --with-locations')
439
440
  .option(' --struct-xpr')
@@ -6,8 +6,6 @@ const {
6
6
  getResultingName,
7
7
  } = require('../model/csnUtils');
8
8
  const { forEach } = require('../utils/objectUtils');
9
- const { makeMessageFunction } = require('../base/messages');
10
- const { optionProcessor } = require('../optionProcessor');
11
9
  const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
12
10
 
13
11
  const {
@@ -20,14 +18,15 @@ const { sortCsn } = require('../json/to-csn');
20
18
  * Not part of our API, yet.
21
19
  *
22
20
  * @param {CSN.Model} csn
21
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
23
22
  * @param {CSN.Options} options
24
23
  */
25
- function alterConstraintsWithCsn( csn, options ) {
26
- const { error, warning } = makeMessageFunction(csn, options, 'manageConstraints');
24
+ function alterConstraintsWithCsn( csn, options, messageFunctions ) {
25
+ const { error, warning } = messageFunctions;
27
26
 
28
27
  const {
29
28
  drop, alter, src, violations, sqlDialect,
30
- } = options || {};
29
+ } = options;
31
30
 
32
31
  if (!sqlDialect || sqlDialect === 'h2' || sqlDialect === 'plain')
33
32
  warning(null, null, { prop: sqlDialect || 'plain' }, 'Referential Constraints are not available for sql dialect $(PROP)');
@@ -35,12 +34,11 @@ function alterConstraintsWithCsn( csn, options ) {
35
34
  if (drop && alter)
36
35
  error(null, null, 'Option “--drop” can\'t be combined with “--alter”');
37
36
 
38
-
39
- // Of course we want the database constraints
37
+ // Of course, we want the database constraints
40
38
  options.assertIntegrityType = options.assertIntegrityType || 'DB';
41
39
 
42
40
  const transformedOptions = _transformSqlOptions(csn, options);
43
- const forSqlCsn = transformForRelationalDBWithCsn(csn, transformedOptions, 'to.sql');
41
+ const forSqlCsn = transformForRelationalDBWithCsn(csn, transformedOptions, messageFunctions);
44
42
 
45
43
  if (violations && src && src !== 'sql') {
46
44
  error(null, null, { value: '--violations', othervalue: src },
@@ -56,32 +54,18 @@ function alterConstraintsWithCsn( csn, options ) {
56
54
  return intermediateResult;
57
55
  }
58
56
 
59
- function _transformSqlOptions( model, options ) {
57
+ // TODO: Remove / Move to api/options.js once alterConstraintsWithCsn is available outside bin/cdsc
58
+ function _transformSqlOptions( csn, options ) {
59
+ const { src } = options;
60
+ // eslint-disable-next-line global-require
61
+ const prepareOptions = require('../api/options');
62
+ options = prepareOptions.to.sql(options);
63
+ options.src = src;
60
64
  // Merge options with defaults.
61
65
  options = Object.assign({ sqlMapping: 'plain', sqlDialect: 'plain' }, options);
62
66
  options.toSql = true;
63
-
64
67
  if (!options.src && !options.csn)
65
68
  options.src = 'sql';
66
-
67
- const { warning, error } = makeMessageFunction(model, options, 'to.sql');
68
-
69
- optionProcessor.verifyOptions(options, 'toSql', true)
70
- // eslint-disable-next-line cds-compiler/message-template-string
71
- .forEach(complaint => warning(null, null, `${complaint}`));
72
-
73
- if (options.sqlDialect !== 'hana') {
74
- // CDXCORE-465, 'quoted' and 'hdbcds' are to be used in combination with dialect 'hana' only
75
- if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds') {
76
- error(null, null, { value: options.sqlDialect, othervalue: options.sqlMapping },
77
- 'Option sqlDialect: $(VALUE) can\'t be combined with sqlMapping: $(OTHERVALUE)');
78
- }
79
-
80
- // No non-HANA SQL for HDI
81
- if (options.src === 'hdi')
82
- error(null, null, { value: options.sqlDialect }, 'Option sqlDialect: $(VALUE) can\'t be used for SAP HANA HDI');
83
- }
84
-
85
69
  return options;
86
70
  }
87
71
 
@@ -5,13 +5,11 @@ const { cdlNewLineRegEx } = require('../language/textUtils');
5
5
  const { findElement, createExpressionRenderer, withoutCast } = require('./utils/common');
6
6
  const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
7
7
  const { checkCSNVersion } = require('../json/csnVersion');
8
- const { timetrace } = require('../utils/timetrace');
9
8
  const { forEachDefinition, normalizeTypeRef } = require('../model/csnUtils');
10
9
  const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
11
10
  const { isBetaEnabled } = require('../base/model');
12
11
  const { ModelError } = require('../base/error');
13
- const { makeMessageFunction } = require('../base/messages.js');
14
- const { typeParameters, specialFunctions } = require('../compiler/builtins');
12
+ const { typeParameters, specialFunctions, isAnnotationExpression } = require('../compiler/builtins');
15
13
  const { forEach } = require('../utils/objectUtils');
16
14
  const {
17
15
  isBuiltinType,
@@ -30,14 +28,11 @@ const specialFunctionKeywords = Object.create(null);
30
28
  * - `namespace`: Namespace statement + `using from './model.cds'.
31
29
  *
32
30
  * @param {CSN.Model} csn
33
- * @param {CSN.Options} [options]
31
+ * @param {CSN.Options} options
32
+ * @param {object} msg Message Functions
34
33
  */
35
- function csnToCdl( csn, options ) {
34
+ function csnToCdl( csn, options, msg ) {
36
35
  const special$self = !csn?.definitions?.$self && '$self';
37
- timetrace.start('CDL rendering');
38
-
39
- const msg = makeMessageFunction(csn, options, 'to.cdl');
40
-
41
36
  if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
42
37
  // Since the expander modifies the CSN, we need to clone it first or
43
38
  // toCdl can't guarantee that the input CSN is not modified.
@@ -91,8 +86,6 @@ function csnToCdl( csn, options ) {
91
86
  cdlResult.model = usingsStr + cdlResult.model;
92
87
  }
93
88
 
94
- timetrace.stop('CDL rendering');
95
-
96
89
  msg.throwWithError();
97
90
  return cdlResult;
98
91
 
@@ -589,11 +582,11 @@ function csnToCdl( csn, options ) {
589
582
  * @param {CdlRenderEnvironment} env
590
583
  */
591
584
  function renderElement( elementName, element, env ) {
585
+ const isCalcElement = (element.value !== undefined);
592
586
  let result = renderAnnotationAssignmentsAndDocComment(element, env);
593
587
  result += env.indent;
594
588
  result += element.virtual ? 'virtual ' : '';
595
589
  result += element.key ? 'key ' : '';
596
- // TODO(v5): Remove once deprecated flag for `masked` is removed.
597
590
  result += element.masked ? 'masked ' : '';
598
591
  result += quoteNonIdentifierOrKeyword(elementName, env);
599
592
  if (element.val !== undefined) { // enum value
@@ -602,13 +595,16 @@ function csnToCdl( csn, options ) {
602
595
  else if (element['#'] !== undefined) { // enum symbol reference
603
596
  result += ` = #${element['#']}`;
604
597
  }
605
- else {
598
+ else if (!isCalcElement || !isDirectAssocOrComp(element.type)) {
599
+ // If the element is a calculated element _and_ a direct association or
600
+ // composition, we'd render `Association to F on (cond) = calcValue;` which
601
+ // would alter the ON-condition.
606
602
  const props = renderTypeReferenceAndProps(element, env);
607
603
  if (props !== '')
608
604
  result += ` : ${props}`;
609
605
  }
610
606
 
611
- if (element.value !== undefined) { // calculated element // @ts-ignore
607
+ if (isCalcElement) { // calculated element // @ts-ignore
612
608
  result += ' = ';
613
609
  if (element.value.xpr && xprContainsCondition(element.value.xpr))
614
610
  result += exprRenderer.renderSubExpr(element.value, env.withSubPath([ 'value' ]));
@@ -873,7 +869,7 @@ function csnToCdl( csn, options ) {
873
869
  // of an `annotate` statement. That may change in the future.
874
870
  result += renderDocComment(element, env);
875
871
  }
876
- // Note: parentheses are a workaround for #9015
872
+ // Note: parentheses are a workaround for #9015; TODO: Check & Update
877
873
  result += renderAnnotationAssignmentsAndDocComment(col, env, { parentheses: true });
878
874
  result += env.indent;
879
875
 
@@ -1316,7 +1312,7 @@ function csnToCdl( csn, options ) {
1316
1312
  }
1317
1313
 
1318
1314
  // Association type
1319
- if (type === 'cds.Association' || type === 'cds.Composition') {
1315
+ if (isDirectAssocOrComp(type)) {
1320
1316
  const isComp = type === 'cds.Composition';
1321
1317
  // Type, cardinality and target; CAPire uses CamelCase
1322
1318
  result += isComp ? 'Composition' : 'Association';
@@ -1350,7 +1346,7 @@ function csnToCdl( csn, options ) {
1350
1346
  result += renderNullability(artifact);
1351
1347
 
1352
1348
  if (artifact.default && !artifact.on)
1353
- result += ` default ${exprRenderer.renderExpr(artifact.default, env.withSubPath([ 'default' ]))}`;
1349
+ result += renderDefaultExpr(artifact, env);
1354
1350
  return result;
1355
1351
  }
1356
1352
 
@@ -1383,7 +1379,7 @@ function csnToCdl( csn, options ) {
1383
1379
  // If there is a default value, and it's a calculated element, do not
1384
1380
  // render the default (because it's not supported for calc elements).
1385
1381
  if (artifact.default !== undefined && !artifact.value)
1386
- result += ` default ${exprRenderer.renderExpr(artifact.default, env.withSubPath([ 'default' ]))}`;
1382
+ result += renderDefaultExpr(artifact, env);
1387
1383
 
1388
1384
  return result;
1389
1385
  }
@@ -1460,11 +1456,7 @@ function csnToCdl( csn, options ) {
1460
1456
  * @param {CdlRenderEnvironment} env
1461
1457
  */
1462
1458
  function renderAnnotationValue( annoValue, env ) {
1463
- // TODO: There must be at least one known expression property, otherwise
1464
- // it could be `type: 'unchecked'`.
1465
- const isXpr = annoValue?.['='] !== undefined && (Object.keys(annoValue).length > 1) &&
1466
- isBetaEnabled(options, 'annotationExpressions');
1467
- if (isXpr) {
1459
+ if (isAnnotationExpression(annoValue)) {
1468
1460
  // Once inside an expression, we stay there.
1469
1461
  const xpr = exprRenderer.renderExpr(annoValue, env);
1470
1462
  return `( ${xpr} )`;
@@ -1758,6 +1750,17 @@ function csnToCdl( csn, options ) {
1758
1750
  return result;
1759
1751
  }
1760
1752
 
1753
+ function renderDefaultExpr( art, env ) {
1754
+ if (!art.default)
1755
+ return '';
1756
+ let result = ' default ';
1757
+ if ( art.default.xpr && xprContainsCondition( art.default.xpr))
1758
+ result += exprRenderer.renderSubExpr(withoutCast( art.default), env.withSubPath([ 'default' ]));
1759
+ else
1760
+ result += exprRenderer.renderExpr(withoutCast( art.default), env.withSubPath([ 'default' ]));
1761
+ return result;
1762
+ }
1763
+
1761
1764
  // Render the nullability of an element or parameter (can be unset, true, or false)
1762
1765
  function renderNullability( obj /* , env */) {
1763
1766
  if (obj.notNull === undefined) {
@@ -1852,7 +1855,7 @@ function csnToCdl( csn, options ) {
1852
1855
  name = name.substring(1);
1853
1856
  // Take the annotation assignment apart into <nameBeforeVariant>#<variantAndRest>
1854
1857
  const parts = name.split('#');
1855
- const nameBeforeVariant = parts[0];
1858
+ let nameBeforeVariant = parts[0];
1856
1859
  const variant = parts.length > 1 ? parts.slice(1).join('#') : undefined;
1857
1860
  const { parentheses } = config;
1858
1861
 
@@ -1860,8 +1863,13 @@ function csnToCdl( csn, options ) {
1860
1863
  if (parentheses)
1861
1864
  result += '(';
1862
1865
 
1866
+ // if the variant is empty, render '#' as part of the name, e.g `variant !== undefined`.
1867
+ if (variant === '')
1868
+ nameBeforeVariant += '#';
1869
+
1863
1870
  result += quoteAnnotationPathIfRequired(nameBeforeVariant, env);
1864
- if (variant !== undefined) {
1871
+
1872
+ if (variant !== undefined && variant !== '') {
1865
1873
  // Unfortunately, the compiler does not allow `.@` after the first variant identifier,
1866
1874
  // nor multiple `#`, so we're back at simple paths that are possibly quoted.
1867
1875
  result += `#${quotePathIfRequired(variant, env)}`;
@@ -1939,7 +1947,7 @@ function csnToCdl( csn, options ) {
1939
1947
  aliasOnly: x => x.as,
1940
1948
  enum: x => `#${x['#']}`,
1941
1949
  ref(x) {
1942
- return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, this.env.withSubPath([ 'ref', index ]))).join('.')}`;
1950
+ return `${x.param ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, this.env.withSubPath([ 'ref', index ]))).join('.')}`;
1943
1951
  },
1944
1952
  windowFunction(x) {
1945
1953
  const funcDef = this.func(x);
@@ -2182,6 +2190,19 @@ function requiresQuotingForCdl( id, additionalKeywords ) {
2182
2190
  additionalKeywords.includes(id.toUpperCase());
2183
2191
  }
2184
2192
 
2193
+ /**
2194
+ * Returns true if the given type is an association or composition,
2195
+ * without type indirection.
2196
+ *
2197
+ * @param {string|string[]} type
2198
+ * @return {boolean}
2199
+ */
2200
+
2201
+ function isDirectAssocOrComp( type ) {
2202
+ type = normalizeTypeRef(type);
2203
+ return (type === 'cds.Association' || type === 'cds.Composition');
2204
+ }
2205
+
2185
2206
  const conditionOperators = [
2186
2207
  // Antlr rule 'condition', 'conditionAnd'
2187
2208
  'AND', 'OR',
@@ -4,12 +4,14 @@ const {
4
4
  getLastPartOf, getLastPartOfRef,
5
5
  hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
6
6
  getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement, hasAnnotationValue,
7
+ pathName, isMagicVariable,
7
8
  } = require('../model/csnUtils');
8
9
  const keywords = require('../base/keywords');
9
10
  const {
10
11
  renderFunc, createExpressionRenderer, getRealName, addContextMarkers, addIntermediateContexts,
11
12
  hasHanaComment, getHanaComment, funcWithoutParen, getSqlSnippets,
12
- cdsToSqlTypes, cdsToHdbcdsTypes, withoutCast,
13
+ cdsToSqlTypes, cdsToHdbcdsTypes, withoutCast, variableForDialect,
14
+ isVariableReplacementRequired,
13
15
  } = require('./utils/common');
14
16
  const {
15
17
  renderReferentialConstraint,
@@ -17,11 +19,11 @@ const {
17
19
  const DuplicateChecker = require('./DuplicateChecker');
18
20
  const { isDeprecatedEnabled, forEachDefinition } = require('../base/model');
19
21
  const { checkCSNVersion } = require('../json/csnVersion');
20
- const { makeMessageFunction } = require('../base/messages');
21
22
  const { timetrace } = require('../utils/timetrace');
22
23
 
23
24
  const { smartId, delimitedId } = require('../sql-identifier');
24
25
  const { ModelError, CompilerAssertion } = require('../base/error');
26
+ const { pathId } = require('../model/csnRefs');
25
27
 
26
28
  const $PROJECTION = '$projection';
27
29
  const $SELF = '$self';
@@ -89,9 +91,10 @@ function renderStringForHdbcds( str ) {
89
91
  *
90
92
  * @param {CSN.Model} csn HANA transformed CSN
91
93
  * @param {CSN.Options} [options] Transformation options
94
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
92
95
  * @returns {object} Dictionary of filename: content
93
96
  */
94
- function toHdbcdsSource( csn, options ) {
97
+ function toHdbcdsSource( csn, options, messageFunctions ) {
95
98
  timetrace.start('HDBCDS rendering');
96
99
  const plainNames = options.sqlMapping === 'plain';
97
100
  const quotedNames = options.sqlMapping === 'quoted';
@@ -99,7 +102,13 @@ function toHdbcdsSource( csn, options ) {
99
102
 
100
103
  const {
101
104
  info, warning, error, throwWithAnyError, message,
102
- } = makeMessageFunction(csn, options, 'to.hdbcds');
105
+ } = messageFunctions;
106
+ if (options.tenantAsColumn) {
107
+ error('api-unexpected-option', null, { option: 'tenantAsColumn', module: 'to.hdbcds' });
108
+ throwWithAnyError();
109
+ }
110
+
111
+ const reportedMissingReplacements = Object.create(null);
103
112
 
104
113
  const exprRenderer = createExpressionRenderer({
105
114
  finalize: x => x,
@@ -732,7 +741,7 @@ function toHdbcdsSource( csn, options ) {
732
741
 
733
742
  // Add any path steps (possibly with parameters and filters) that may follow after that
734
743
  if (path.ref.length > 1)
735
- result += `.${renderExpr({ ref: path.ref.slice(1) }, env)}`;
744
+ result += `.${renderTypeRef({ ref: path.ref.slice(1) }, env)}`;
736
745
 
737
746
  return result;
738
747
  }
@@ -1209,11 +1218,10 @@ function toHdbcdsSource( csn, options ) {
1209
1218
  *
1210
1219
  * @param {string|object} s Path step
1211
1220
  * @param {number} idx Path position
1212
- * @param {any[]} ref
1213
1221
  * @param {HdbcdsRenderEnvironment} env
1214
1222
  * @returns {string} Rendered path step
1215
1223
  */
1216
- function renderPathStep( s, idx, ref, env ) {
1224
+ function renderPathStep( s, idx, env ) {
1217
1225
  // Simple id or absolute name
1218
1226
  if (typeof s === 'string') {
1219
1227
  // HANA-specific extra magic (should actually be in forRelationalDB)
@@ -1227,12 +1235,6 @@ function toHdbcdsSource( csn, options ) {
1227
1235
  return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
1228
1236
  : renderAbsoluteNameWithQuotes(env.currentArtifactName, env);
1229
1237
  }
1230
- // HANA-specific translation of '$now' and '$user'
1231
- if (s === '$now' && ref.length === 1)
1232
- return 'CURRENT_TIMESTAMP';
1233
-
1234
- // In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
1235
- // FIXME: We should rather explicitly recognize quoting somehow
1236
1238
 
1237
1239
  // TODO: quote $parameters if it doesn't reference a parameter, this requires knowledge about the kind
1238
1240
  // Example: both views are correct in HANA CDS
@@ -1316,42 +1318,61 @@ function toHdbcdsSource( csn, options ) {
1316
1318
  return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', this.env));
1317
1319
  }
1318
1320
 
1321
+
1319
1322
  /**
1320
- * @param {object} x Expression with a ref property
1321
- * @returns {string} Rendered expression
1322
- * @todo no extra magic with x.param or x.global
1323
+ * Render a magic variable. Values are determined in following order:
1324
+ * 1. User defined replacement in options.variableReplacements
1325
+ * 2. Predefined fallback values
1326
+ * 3. Rendering of the variable as a string (i.e. its name) + warning
1327
+ *
1328
+ * @param {CSN.Path} ref
1329
+ * @param {object} env
1330
+ * @return {string}
1323
1331
  */
1324
- function renderExpressionRef( x ) {
1325
- if (!x.param && !x.global) {
1326
- const magicReplacement = getVariableReplacement(x.ref, options);
1327
- if (x.ref[0] === '$user') {
1328
- if (magicReplacement !== null)
1329
- return renderStringForHdbcds(magicReplacement);
1332
+ function renderMagicVariable( ref, env ) {
1333
+ const magicReplacement = getVariableReplacement(ref, options);
1334
+ if (magicReplacement !== null)
1335
+ return renderStringForHdbcds(magicReplacement);
1336
+
1337
+ const name = pathName(ref);
1338
+ const result = variableForDialect(options, name);
1339
+ if (result)
1340
+ return result;
1330
1341
 
1331
- // Note: The compiler already transforms $user into $user.id.
1342
+ if (isVariableReplacementRequired(name)) {
1343
+ reportedMissingReplacements[name] = true;
1344
+ error('ref-undefined-var', env.path, { '#': 'value', id: name, option: 'variableReplacements' });
1345
+ }
1346
+ else if (!reportedMissingReplacements[name]) {
1347
+ reportedMissingReplacements[name] = true;
1348
+ warning('ref-unsupported-variable', env.path, { name, option: 'variableReplacements' },
1349
+ 'Variable $(NAME) is not supported. Use option $(OPTION) to specify a value for $(NAME)');
1350
+ }
1332
1351
 
1333
- // FIXME: this is all not enough: we might need an explicit select item alias (?)
1334
- if (x.ref[1] === 'id')
1335
- return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1352
+ return renderStringForHdbcds(name);
1353
+ }
1336
1354
 
1337
- else if (x.ref[1] === 'locale')
1338
- return 'SESSION_CONTEXT(\'LOCALE\')';
1355
+ /**
1356
+ * Must not be used for type refs, as something like `$user` will be interpreted as a magic
1357
+ * variable and not definition name.
1358
+ *
1359
+ * @param {object} x Expression with a ref property
1360
+ * @returns {string} Rendered expression
1361
+ * @todo no extra magic with x.param
1362
+ */
1363
+ function renderExpressionRef( x ) {
1364
+ if (!x.param && isMagicVariable(pathId(x.ref[0])))
1365
+ return renderMagicVariable(x.ref, this.env);
1339
1366
 
1340
- else if (x.ref[1] === 'tenant')
1341
- return 'SESSION_CONTEXT(\'TENANT\')';
1342
- }
1343
- else if (x.ref[0] === '$at' || x.ref[0] === '$valid') {
1344
- if (x.ref[1] === 'from')
1345
- return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1367
+ const prefix = x.param ? ':' : '';
1368
+ const ref = x.ref.map((step, index) => renderPathStep(step, index, this.env.withSubPath([ 'ref', index ]))).join('.');
1369
+ return `${prefix}${ref}`;
1370
+ }
1346
1371
 
1347
- else if (x.ref[1] === 'to')
1348
- return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1349
- }
1350
- else if (x.ref[0] === '$session' && magicReplacement !== null) {
1351
- return renderStringForHdbcds(magicReplacement);
1352
- }
1353
- }
1354
- return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref, this.env.withSubPath([ 'ref', index ]))).join('.')}`;
1372
+ function renderTypeRef( x, env ) {
1373
+ const prefix = x.param ? ':' : '';
1374
+ const ref = x.ref.map((step, index) => renderPathStep(step, index, env.withSubPath([ 'ref', index ]))).join('.');
1375
+ return `${prefix}${ref}`;
1355
1376
  }
1356
1377
 
1357
1378
  /**
@@ -1,10 +1,8 @@
1
1
 
2
2
  'use strict';
3
3
 
4
- const { makeMessageFunction } = require('../base/messages');
5
4
  const { checkCSNVersion } = require('../json/csnVersion');
6
5
  const { forEachDefinition } = require('../model/csnUtils');
7
- const { optionProcessor } = require('../optionProcessor');
8
6
  const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
9
7
  const { getIdentifierUtils } = require('./utils/sql');
10
8
 
@@ -25,25 +23,23 @@ const { getIdentifierUtils } = require('./utils/sql');
25
23
  * @todo clarify input parameters
26
24
  * @param {CSN.Model} inputCsn CSN?
27
25
  * @param {CSN.Options} options Transformation options
26
+ * @param {object} messageFunctions
28
27
  * @returns {object} A dictionary of name: rename statement
29
28
  */
30
- function toRename( inputCsn, options ) {
31
- const { warning, throwWithError } = makeMessageFunction(inputCsn, options, 'to.rename');
29
+ function toRename( inputCsn, options, messageFunctions ) {
30
+ const { warning, throwWithError } = messageFunctions;
32
31
 
33
32
  // Merge options with defaults.
33
+ // TODO: Use api/options.js if this ever becomes an official API.
34
34
  options = Object.assign({ sqlMapping: 'hdbcds', sqlDialect: 'hana' }, options);
35
-
36
- // Verify options
37
- optionProcessor.verifyOptions(options, 'toRename')
38
- // eslint-disable-next-line cds-compiler/message-template-string
39
- .forEach(complaint => warning(null, null, `${complaint}`));
40
35
  checkCSNVersion(inputCsn, options);
41
36
 
42
37
  // Let users know that this is internal
43
38
  warning(null, null, 'Generation of SQL rename statements is a beta feature and might change in the future');
44
39
 
45
40
  // FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forRelationalDB)
46
- const csn = transformForRelationalDBWithCsn(inputCsn, options, 'to.rename');
41
+ const csn = transformForRelationalDBWithCsn(inputCsn, options, messageFunctions);
42
+ messageFunctions.setModel(csn);
47
43
  const hdbcdsOrQuotedIdentifiers = getIdentifierUtils(csn, options);
48
44
  const plainIdentifiers = getIdentifierUtils(csn, { sqlDialect: 'hana', sqlMapping: 'plain' });
49
45