@sap/cds-compiler 3.1.2 → 3.4.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 (117) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/bin/cdsc.js +4 -2
  3. package/doc/CHANGELOG_BETA.md +35 -0
  4. package/lib/api/main.js +153 -29
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/error.js +2 -2
  8. package/lib/base/keywords.js +106 -24
  9. package/lib/base/message-registry.js +177 -79
  10. package/lib/base/messages.js +78 -57
  11. package/lib/base/model.js +2 -1
  12. package/lib/checks/actionsFunctions.js +1 -1
  13. package/lib/checks/annotationsOData.js +2 -2
  14. package/lib/checks/arrayOfs.js +15 -7
  15. package/lib/checks/cdsPersistence.js +1 -1
  16. package/lib/checks/checkForTypes.js +53 -0
  17. package/lib/checks/defaultValues.js +4 -2
  18. package/lib/checks/elements.js +81 -6
  19. package/lib/checks/foreignKeys.js +12 -13
  20. package/lib/checks/invalidTarget.js +10 -11
  21. package/lib/checks/managedInType.js +21 -15
  22. package/lib/checks/nullableKeys.js +1 -1
  23. package/lib/checks/onConditions.js +9 -9
  24. package/lib/checks/parameters.js +23 -0
  25. package/lib/checks/queryNoDbArtifacts.js +1 -1
  26. package/lib/checks/selectItems.js +1 -1
  27. package/lib/checks/sql-snippets.js +12 -10
  28. package/lib/checks/types.js +2 -2
  29. package/lib/checks/utils.js +17 -7
  30. package/lib/checks/validator.js +36 -14
  31. package/lib/compiler/assert-consistency.js +21 -13
  32. package/lib/compiler/builtins.js +8 -0
  33. package/lib/compiler/checks.js +57 -40
  34. package/lib/compiler/define.js +139 -69
  35. package/lib/compiler/extend.js +319 -50
  36. package/lib/compiler/finalize-parse-cdl.js +14 -9
  37. package/lib/compiler/kick-start.js +2 -35
  38. package/lib/compiler/populate.js +111 -68
  39. package/lib/compiler/propagator.js +5 -3
  40. package/lib/compiler/resolve.js +71 -108
  41. package/lib/compiler/shared.js +82 -54
  42. package/lib/compiler/tweak-assocs.js +26 -14
  43. package/lib/compiler/utils.js +13 -2
  44. package/lib/edm/annotations/genericTranslation.js +10 -7
  45. package/lib/edm/csn2edm.js +11 -11
  46. package/lib/edm/edm.js +17 -9
  47. package/lib/edm/edmPreprocessor.js +53 -30
  48. package/lib/edm/edmUtils.js +7 -2
  49. package/lib/gen/Dictionary.json +14 -0
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -2
  52. package/lib/gen/languageParser.js +4312 -4186
  53. package/lib/inspect/inspectModelStatistics.js +1 -1
  54. package/lib/inspect/inspectPropagation.js +23 -9
  55. package/lib/json/csnVersion.js +13 -13
  56. package/lib/json/from-csn.js +161 -172
  57. package/lib/json/to-csn.js +70 -10
  58. package/lib/language/.eslintrc.json +4 -0
  59. package/lib/language/antlrParser.js +8 -11
  60. package/lib/language/docCommentParser.js +1 -2
  61. package/lib/language/errorStrategy.js +54 -27
  62. package/lib/language/genericAntlrParser.js +140 -93
  63. package/lib/language/language.g4 +57 -33
  64. package/lib/language/multiLineStringParser.js +75 -63
  65. package/lib/main.d.ts +3 -6
  66. package/lib/main.js +1 -0
  67. package/lib/model/.eslintrc.json +13 -0
  68. package/lib/model/api.js +4 -2
  69. package/lib/model/csnRefs.js +78 -50
  70. package/lib/model/csnUtils.js +272 -222
  71. package/lib/model/enrichCsn.js +41 -31
  72. package/lib/model/revealInternalProperties.js +61 -57
  73. package/lib/model/sortViews.js +35 -31
  74. package/lib/modelCompare/compare.js +52 -18
  75. package/lib/modelCompare/filter.js +83 -0
  76. package/lib/optionProcessor.js +10 -1
  77. package/lib/render/manageConstraints.js +11 -7
  78. package/lib/render/toCdl.js +151 -106
  79. package/lib/render/toHdbcds.js +8 -6
  80. package/lib/render/toRename.js +4 -4
  81. package/lib/render/toSql.js +17 -7
  82. package/lib/render/utils/common.js +27 -9
  83. package/lib/render/utils/sql.js +5 -5
  84. package/lib/sql-identifier.js +7 -0
  85. package/lib/transform/db/applyTransformations.js +32 -3
  86. package/lib/transform/db/assertUnique.js +27 -38
  87. package/lib/transform/db/expansion.js +92 -41
  88. package/lib/transform/db/flattening.js +1 -1
  89. package/lib/transform/db/temporal.js +3 -1
  90. package/lib/transform/db/transformExists.js +8 -2
  91. package/lib/transform/db/views.js +42 -13
  92. package/lib/transform/draft/db.js +2 -2
  93. package/lib/transform/forOdataNew.js +10 -7
  94. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
  95. package/lib/transform/localized.js +29 -20
  96. package/lib/transform/odata/toFinalBaseType.js +8 -11
  97. package/lib/transform/odata/typesExposure.js +2 -1
  98. package/lib/transform/parseExpr.js +245 -0
  99. package/lib/transform/transformUtilsNew.js +122 -51
  100. package/lib/transform/translateAssocsToJoins.js +17 -16
  101. package/lib/utils/moduleResolve.js +5 -5
  102. package/lib/utils/objectUtils.js +3 -3
  103. package/lib/utils/term.js +5 -5
  104. package/package.json +2 -2
  105. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  106. package/share/messages/check-proper-type-of.md +4 -4
  107. package/share/messages/check-proper-type.md +2 -2
  108. package/share/messages/duplicate-autoexposed.md +4 -4
  109. package/share/messages/extend-repeated-intralayer.md +4 -5
  110. package/share/messages/extend-unrelated-layer.md +4 -4
  111. package/share/messages/message-explanations.json +3 -1
  112. package/share/messages/redirected-to-ambiguous.md +7 -6
  113. package/share/messages/redirected-to-complex.md +63 -0
  114. package/share/messages/redirected-to-unrelated.md +6 -5
  115. package/share/messages/rewrite-not-supported.md +4 -4
  116. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
  117. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -20,7 +20,6 @@ const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
20
20
  * Returned object has the following properties:
21
21
  * - `model`: CSN model rendered as CDL (string).
22
22
  * - `namespace`: Namespace statement + `using from './model.cds'.
23
- * - `unappliedExtensions`: Annotations / Extensions from the `csn.extensions` array.
24
23
  *
25
24
  * @param {CSN.Model} csn
26
25
  * @param {CSN.Options} [options]
@@ -45,17 +44,14 @@ function csnToCdl(csn, options) {
45
44
 
46
45
  if (csn.vocabularies)
47
46
  cdlResult.model += renderVocabularies(csn.vocabularies);
47
+ if (csn.extensions)
48
+ cdlResult.model += renderExtensions(csn.extensions, createEnv());
48
49
 
49
50
  if (csn.namespace) {
50
51
  cdlResult.namespace = `namespace ${renderArtifactName(csn.namespace)};\n`;
51
52
  cdlResult.namespace += 'using from \'./model.cds\';';
52
53
  }
53
54
 
54
- // If there are extensions, such as 'extend' and 'annotate' statements, render them separately.
55
- // Used for e.g. parseCdl-style CSN or Universal CSN.
56
- if (csn.extensions)
57
- cdlResult.unappliedExtensions = renderExtensions(csn.extensions, createEnv());
58
-
59
55
  timetrace.stop();
60
56
  return cdlResult;
61
57
 
@@ -109,7 +105,9 @@ function csnToCdl(csn, options) {
109
105
  * @return {string}
110
106
  */
111
107
  function renderExtensions(extensions, env) {
112
- return extensions.map(ext => renderExtension(ext, env)).join('\n');
108
+ if (!env.path)
109
+ env = envNewPath(env, [ 'extensions' ]);
110
+ return extensions.map((ext, index) => renderExtension(ext, envAddPath(env, [ index ]))).join('\n');
113
111
  }
114
112
 
115
113
  /**
@@ -353,13 +351,14 @@ function csnToCdl(csn, options) {
353
351
  if (art.query || art.projection)
354
352
  return renderView(artifactName, art, env);
355
353
  return renderEntity(artifactName, art, env);
354
+ case 'aspect':
355
+ return renderAspect(artifactName, art, env);
356
356
 
357
357
  case 'context':
358
358
  case 'service':
359
359
  return renderContextOrService(artifactName, art, env);
360
360
 
361
361
  case 'type':
362
- case 'aspect':
363
362
  case 'annotation': // annotation in 'csn.definitions' for compiler v1 compatibility
364
363
  return renderTypeOrAnnotation(artifactName, art, env);
365
364
 
@@ -382,7 +381,6 @@ function csnToCdl(csn, options) {
382
381
  */
383
382
  function renderEvent(artifactName, art, env) {
384
383
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
385
- const childEnv = increaseIndent(env);
386
384
  const normalizedArtifactName = renderArtifactName(artifactName);
387
385
  result += `${env.indent}event ${normalizedArtifactName}`;
388
386
  if (art.includes)
@@ -398,11 +396,7 @@ function csnToCdl(csn, options) {
398
396
  result += ` : ${renderTypeReferenceAndProps(art, env)};\n`;
399
397
  }
400
398
  else if (art.elements) {
401
- result += ' {\n';
402
- for (const name in art.elements)
403
- result += renderElement(name, art.elements[name], childEnv);
404
-
405
- result += `${env.indent}}`;
399
+ result += ` ${renderElements(art, env)};\n`;
406
400
  }
407
401
  return result;
408
402
  }
@@ -434,21 +428,57 @@ function csnToCdl(csn, options) {
434
428
 
435
429
  if (art.params)
436
430
  result += renderParameters(art, env);
431
+ if (art.includes)
432
+ result += renderIncludes(art.includes);
433
+ result += ` ${renderElements(art, env)}`;
434
+ result += `${renderActionsAndFunctions(art, env)};\n`;
435
+ return result;
436
+ }
437
437
 
438
+ /**
439
+ * Render an aspect. Return the resulting source string.
440
+ * Behaves very similar to renderEntity, _except_ that aspects are
441
+ * allowed to _not_ have elements, e.g. `aspect A;`.
442
+ *
443
+ * @param {string} artifactName
444
+ * @param {CSN.Artifact} art
445
+ * @param {CdlRenderEnvironment} env
446
+ * @return {string}
447
+ */
448
+ function renderAspect(artifactName, art, env) {
449
+ let result = renderAnnotationAssignmentsAndDocComment(art, env);
450
+ result += `${env.indent}aspect ${renderArtifactName(artifactName)}`;
438
451
  if (art.includes)
439
452
  result += renderIncludes(art.includes);
440
- result += ' {\n';
441
- const childEnv = increaseIndent(env);
442
- for (const name in art.elements) {
443
- const element = art.elements[name];
444
- result += renderElement(name, element, childEnv);
445
- }
446
453
 
447
- result += `${env.indent}}`;
454
+ if (art.elements)
455
+ result += ` ${renderElements(art, env)}`;
456
+ else if (art.actions)
457
+ // if there are no elements, but actions, CDL syntax requires braces.
458
+ result += ' { }';
459
+
448
460
  result += `${renderActionsAndFunctions(art, env)};\n`;
449
461
  return result;
450
462
  }
451
463
 
464
+ /**
465
+ * Render a list of elements enclosed in braces. If the list is empty, returns `{ }`.
466
+ *
467
+ * @param {object} artifact Artifact with `elements` property.
468
+ * @param {CdlRenderEnvironment} env
469
+ * @return {string}
470
+ */
471
+ function renderElements(artifact, env) {
472
+ let elements = '';
473
+ const childEnv = increaseIndent(env);
474
+ for (const name in artifact.elements)
475
+ elements += renderElement(name, artifact.elements[name], childEnv, null);
476
+
477
+ if (elements === '')
478
+ return '{ }';
479
+ return `{\n${elements}${env.indent}}`;
480
+ }
481
+
452
482
  /**
453
483
  * Render an element (of an entity, type or annotation, not a projection or view).
454
484
  * Return the resulting source string.
@@ -471,27 +501,6 @@ function csnToCdl(csn, options) {
471
501
  return `${result};\n`;
472
502
  }
473
503
 
474
- /**
475
- * Render a query's actions and functions (if any) separately as extend-statements, so that actions
476
- * work not only for projections but also for views, which have no syntax (yet) to directly specify
477
- * actions and functions inline.
478
- * Return the resulting 'extend' statement or '' if no actions or functions
479
- * FIXME: Simplify once we have such a syntax
480
- *
481
- * @param {string} artifactName
482
- * @param {CSN.Artifact} art
483
- * @param {CdlRenderEnvironment} env
484
- * @return {string}
485
- */
486
- function renderQueryActionsAndFunctions(artifactName, art, env) {
487
- let result = renderActionsAndFunctions(art, env);
488
- // Even if we have seen actions/functions, they might all have been ignored
489
- if (result !== '')
490
- result = `${env.indent}extend entity ${artifactName} with${result};`;
491
-
492
- return result;
493
- }
494
-
495
504
  /**
496
505
  * Render annotations that were extended to a query element of a view or projection (they only
497
506
  * appear in the view's 'elements', not in their 'columns' for client CSN, because the element
@@ -507,8 +516,8 @@ function csnToCdl(csn, options) {
507
516
  * @param {CdlRenderEnvironment} env
508
517
  * @return {string}
509
518
  */
510
- function renderQueryElementAnnotations(artifactName, art, env) {
511
- const annotate = collectAnnotationsOfElements(art, { artifactName, path: env.path });
519
+ function renderQueryElementAndEnumAnnotations(artifactName, art, env) {
520
+ const annotate = collectAnnotationsOfElementsAndEnum(art, { artifactName, path: env.path });
512
521
  if (annotate)
513
522
  return renderExtensions([ annotate ], env);
514
523
  return '';
@@ -518,17 +527,20 @@ function csnToCdl(csn, options) {
518
527
  * Create an "annotate" statement as a CSN extension for all annotations of (sub-)elements.
519
528
  * If no annotation was found, we return `null`.
520
529
  *
521
- * @param {CSN.Artifact} artWithElements
530
+ * @param {CSN.Artifact} artifact
522
531
  * @param {CdlRenderEnvironment} env
523
532
  * @return {CSN.Extension|null}
524
533
  */
525
- function collectAnnotationsOfElements(artWithElements, env) {
526
- // Array of structures, which may be annotated as well.
527
- if (!artWithElements.elements && artWithElements.items) {
534
+ function collectAnnotationsOfElementsAndEnum(artifact, env) {
535
+ // Array, which may be annotated as well.
536
+ if (artifact.items) {
528
537
  env = envAddPath(env, [ 'items' ]);
529
- artWithElements = artWithElements.items;
538
+ artifact = artifact.items;
530
539
  }
531
540
 
541
+ if (!artifact.elements && !artifact.enum)
542
+ return null;
543
+
532
544
  const annotate = { annotate: env.path[1] };
533
545
 
534
546
  // Based on the current path, create a correctly nested structure
@@ -551,22 +563,26 @@ function csnToCdl(csn, options) {
551
563
  // ignore others, e.g. 'items'
552
564
  }
553
565
  }
554
- return collectAnnos(obj, artWithElements) ? annotate : null;
566
+ return collectAnnos(obj, artifact) ? annotate : null;
555
567
 
556
568
  /**
557
569
  * Recursive function to collect annotations. `annotateObj` will get an `elements`
558
- * object with annotations only if there are annotations on `art`'s (sub-)elements.
570
+ * object with annotations only if there are annotations on `art`'s (sub-)elements or
571
+ * enums. Returned object will use "elements" even for enums, since that is
572
+ * expected in extensions.
559
573
  *
560
574
  * @return {boolean} True, if there were annotations, false otherwise.
561
575
  */
562
576
  function collectAnnos(annotateObj, art) {
563
- if (!art.elements)
577
+ if (!art.elements && !art.enum)
564
578
  return false;
565
579
 
580
+ const dictKey = art.elements ? 'elements' : 'enum';
581
+ // Use "elements" for both enums and elements. This is allowed in extensions.
566
582
  const collected = { elements: Object.create(null) };
567
583
  let hasAnnotation = false;
568
584
 
569
- forEach(art.elements, (elemName, element) => {
585
+ forEach(art[dictKey], (elemName, element) => {
570
586
  if (!collected.elements[elemName])
571
587
  collected.elements[elemName] = { };
572
588
 
@@ -667,7 +683,7 @@ function csnToCdl(csn, options) {
667
683
 
668
684
  // Even the first step might have parameters and/or a filter
669
685
  if (path.ref[0].args)
670
- result += `(${renderArgs(path.ref[0], ':', env)})`;
686
+ result += `(${renderArguments(path.ref[0], ':', env)})`;
671
687
 
672
688
  if (path.ref[0].where) {
673
689
  const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
@@ -848,14 +864,17 @@ function csnToCdl(csn, options) {
848
864
  function renderView(artifactName, art, env) {
849
865
  const syntax = (art.projection) ? 'projection' : 'entity';
850
866
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
851
- result += `${env.indent}${art.abstract ? 'abstract ' : ''}${syntax === 'projection' ? 'entity' : syntax} ${renderArtifactName(artifactName)}`;
867
+ result += `${env.indent}entity ${renderArtifactName(artifactName)}`;
852
868
  if (art.params)
853
869
  result += renderParameters(art, env);
854
870
  result += ' as ';
855
871
  result += renderQuery(getNormalizedQuery(art).query, true, syntax, env, [ 'definitions', artifactName, 'query' ], art.elements);
872
+ if (art.actions) // Views/Projections also allow actions. Just the VIEW keyword variant did not.
873
+ result += renderActionsAndFunctions(art, env);
856
874
  result += ';\n';
857
- result += renderQueryElementAnnotations(artifactName, art, env);
858
- result += renderQueryActionsAndFunctions(artifactName, art, env);
875
+ result += renderQueryElementAndEnumAnnotations(artifactName, art, env);
876
+ if (art.includes)
877
+ result += renderExtension({ extend: artifactName, includes: art.includes }, env);
859
878
  return result;
860
879
  }
861
880
 
@@ -1108,8 +1127,7 @@ function csnToCdl(csn, options) {
1108
1127
  result += ` ${renderTypeReferenceAndProps(art, env)}`;
1109
1128
  else
1110
1129
  result += ` : ${renderTypeReferenceAndProps(art, env)}`;
1111
- // for aspects, but since types don't have `actions` this does not hurt
1112
- result += `${renderActionsAndFunctions(art, env)};\n`;
1130
+ result += ';\n';
1113
1131
  return result;
1114
1132
  }
1115
1133
 
@@ -1145,12 +1163,7 @@ function csnToCdl(csn, options) {
1145
1163
  }
1146
1164
 
1147
1165
  if (!artifact.type && artifact.elements) {
1148
- result += '{\n';
1149
- const childEnv = envAddPath(increaseIndent(env), 'items');
1150
- for (const name in artifact.elements)
1151
- result += renderElement(name, artifact.elements[name], childEnv, null);
1152
-
1153
- result += `${env.indent}}`;
1166
+ result += renderElements(artifact, env);
1154
1167
  if (!isTypeDef)
1155
1168
  result += renderNullability(artifact);
1156
1169
  // structured default not possible at the moment
@@ -1174,12 +1187,7 @@ function csnToCdl(csn, options) {
1174
1187
  }
1175
1188
  else if (elements) {
1176
1189
  // anonymous aspect, either parseCdl or client CSN.
1177
- const childEnv = increaseIndent(env);
1178
- result += '{\n';
1179
- for (const name in elements)
1180
- result += renderElement(name, elements[name], childEnv);
1181
-
1182
- result += `${env.indent}}`;
1190
+ result += renderElements({ elements }, env);
1183
1191
  }
1184
1192
  else {
1185
1193
  throw new ModelError('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
@@ -1205,10 +1213,12 @@ function csnToCdl(csn, options) {
1205
1213
 
1206
1214
  // If we have a type and elements, we may have sub-structure annotates that would
1207
1215
  // get lost if we only render the type name.
1216
+ // We only extract annotations of enums, if "typeRefOnly" is true. Otherwise, since
1217
+ // the full enum is rendered below, we would have unnecessary annotations.
1208
1218
  // TODO: Can we annotate elements of targetAspect?
1209
1219
  // If so, move this block before the composition rendering.
1210
- if (!noAnnoCollect && (artifact.elements || artifact.items?.elements)) {
1211
- const annotate = collectAnnotationsOfElements(artifact, env);
1220
+ if (!noAnnoCollect && (!artifact.enum || typeRefOnly)) {
1221
+ const annotate = collectAnnotationsOfElementsAndEnum(artifact, env);
1212
1222
  if (annotate)
1213
1223
  subelementAnnotates.push(annotate);
1214
1224
  }
@@ -1363,6 +1373,8 @@ function csnToCdl(csn, options) {
1363
1373
  *
1364
1374
  * @param {string|object} s
1365
1375
  * @param {number} idx
1376
+ * @param {boolean} inline
1377
+ * @param {object} env
1366
1378
  * @returns {string}
1367
1379
  */
1368
1380
  function renderPathStep(s, idx, inline, env) {
@@ -1370,12 +1382,9 @@ function csnToCdl(csn, options) {
1370
1382
  if (typeof s === 'string') {
1371
1383
  // In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
1372
1384
  // FIXME: We should rather explicitly recognize quoting somehow
1373
-
1374
- if (idx === 0 &&
1375
- s.startsWith('$'))
1385
+ if (idx === 0 && s.startsWith('$'))
1376
1386
  return s;
1377
-
1378
- return quoteIdIfRequired(s);
1387
+ return quoteIdIfRequired(s, env.additionalKeywords);
1379
1388
  }
1380
1389
  // ID with filters or parameters
1381
1390
  else if (typeof s === 'object') {
@@ -1385,13 +1394,13 @@ function csnToCdl(csn, options) {
1385
1394
 
1386
1395
  // Not really a path step but an object-like function call
1387
1396
  if (s.func)
1388
- return `${s.func}(${renderArgs(s, '=>', env)})`;
1397
+ return `${s.func}(${renderArguments(s, '=>', env)})`;
1389
1398
 
1390
1399
  // Path step, possibly with view parameters and/or filters
1391
- let result = `${quoteIdIfRequired(s.id)}`;
1400
+ let result = `${quoteIdIfRequired(s.id, env.additionalKeywords)}`;
1392
1401
  if (s.args) {
1393
1402
  // View parameters
1394
- result += `(${renderArgs(s, ':', env)})`;
1403
+ result += `(${renderArguments(s, ':', env)})`;
1395
1404
  }
1396
1405
  if (s.where) {
1397
1406
  // Filter, possibly with cardinality
@@ -1415,24 +1424,48 @@ function csnToCdl(csn, options) {
1415
1424
  * @param {CdlRenderEnvironment} env
1416
1425
  * @returns {string}
1417
1426
  */
1418
- function renderArgs(node, sep, env) {
1419
- const args = node.args || [];
1420
-
1421
- // Positional arguments
1422
- if (Array.isArray(args)) {
1423
- const func = node.func?.toUpperCase();
1424
- if (func)
1425
- return args.map((arg, i) => renderArgument(arg, env, getKeywordsForSpecialFunctionArgument(func, i))).join(', ');
1427
+ function renderArguments(node, sep, env) {
1428
+ if (!node.args)
1429
+ return '';
1430
+ else if (Array.isArray(node.args))
1431
+ return renderPositionalArguments(node, env);
1432
+ else if (typeof node.args === 'object')
1433
+ return renderNamedArguments(node, sep, env);
1434
+ throw new ModelError(`Unknown args: ${JSON.stringify(node.args)}; expected array/object`);
1435
+ }
1426
1436
 
1427
- return args.map(arg => renderArgument(arg, env)).join(', ');
1428
- }
1437
+ /**
1438
+ * Render named function arguments or view parameters,
1439
+ * using 'sep' as separator.
1440
+ *
1441
+ * @param {object} node with `args` to render
1442
+ * @param {string} separator
1443
+ * @param {CdlRenderEnvironment} env
1444
+ * @returns {string}
1445
+ */
1446
+ function renderNamedArguments(node, separator, env) {
1447
+ return Object.keys(node.args).map(function renderNamedArgument(key) {
1448
+ return `${quoteIdIfRequired(key, env.additionalKeywords)} ${separator} ${renderArgument(node.args[key], env)}`;
1449
+ }).join(', ');
1450
+ }
1429
1451
 
1430
- // Named arguments (object/dict)
1431
- else if (typeof args === 'object') {
1432
- return Object.keys(args).map(key => `${quoteIdIfRequired(key)} ${sep} ${renderArgument(args[key], env)}`).join(', ');
1452
+ /**
1453
+ * Render a comma separated list of positional function arguments.
1454
+ *
1455
+ * @param {object} node with `args` to render
1456
+ * @param {CdlRenderEnvironment} env
1457
+ * @returns {string}
1458
+ */
1459
+ function renderPositionalArguments(node, env) {
1460
+ if (!node.args)
1461
+ return '';
1462
+ const func = node.func?.toUpperCase();
1463
+ if (func) {
1464
+ return node.args.map(function renderFunctionArg(arg, i) {
1465
+ return renderArgument(arg, env, getKeywordsForSpecialFunctionArgument(func, i));
1466
+ }).join(', ');
1433
1467
  }
1434
-
1435
- throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
1468
+ return node.args.map(arg => renderArgument(arg, env)).join(', ');
1436
1469
  }
1437
1470
 
1438
1471
  /**
@@ -1441,13 +1474,14 @@ function csnToCdl(csn, options) {
1441
1474
  *
1442
1475
  * @param {any} arg
1443
1476
  * @param {CdlRenderEnvironment} env
1444
- * @param {string[]} additionalAllowedKeywords
1477
+ * @param {string[]} additionalKeywords
1445
1478
  * @return {string}
1446
1479
  */
1447
- function renderArgument(arg, env, additionalAllowedKeywords = []) {
1480
+ function renderArgument(arg, env, additionalKeywords = []) {
1448
1481
  // If the argument is a xpr with e.g. `=`, it may require parentheses.
1449
1482
  // For nested xpr, `renderExpr()` will already add parentheses.
1450
- return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr, additionalAllowedKeywords), true);
1483
+ env = { ...env, additionalKeywords };
1484
+ return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr, additionalKeywords), true);
1451
1485
  }
1452
1486
 
1453
1487
  /**
@@ -1652,7 +1686,7 @@ function csnToCdl(csn, options) {
1652
1686
  if (keywords.cdl_functions.includes(obj.func.toUpperCase()))
1653
1687
  return obj.func;
1654
1688
  const name = identifierRegex.test(obj.func) ? obj.func : quote(obj.func);
1655
- return `${name}(${renderArgs( obj, '=>', env )})`;
1689
+ return `${name}(${renderArguments( obj, '=>', env )})`;
1656
1690
  }
1657
1691
 
1658
1692
  /**
@@ -1775,27 +1809,33 @@ function increaseIndent(env) {
1775
1809
  * Quote the path steps with `![]` if necessary. For simple ids such as
1776
1810
  * `elem` use `quoteIdIfRequired` instead.
1777
1811
  *
1812
+ * In contrast to quoteIdIfRequired, does not handle additional keywords,
1813
+ * because it was not required, yet.
1814
+ *
1778
1815
  * @param {string} path
1779
1816
  * @returns {string}
1780
1817
  *
1781
1818
  * @todo For paths such as `E.key`, `key` does not have to be in quotes.
1782
1819
  */
1783
1820
  function quotePathIfRequired(path) {
1784
- return path.split('.').map(quoteIdIfRequired).join('.');
1821
+ return path.split('.').map(step => quoteIdIfRequired(step)).join('.');
1785
1822
  }
1786
1823
 
1787
1824
  /**
1788
1825
  * Quote the id with `![]` if necessary. For paths such as `E.key` use
1789
1826
  * `quotePathIfRequired` instead.
1790
1827
  *
1828
+ * Set additionalKeywords to an array of UPPERCASE keywords
1829
+ * that also need quoting, e.g. in special functions.
1830
+ *
1791
1831
  * @param {string} id
1832
+ * @param {string[]} [additionalKeywords]
1792
1833
  * @return {string}
1793
1834
  */
1794
- function quoteIdIfRequired(id) {
1835
+ function quoteIdIfRequired(id, additionalKeywords) {
1795
1836
  // Quote if required for CDL
1796
- if (requiresQuotingForCdl(id))
1837
+ if (requiresQuotingForCdl(id, additionalKeywords || []))
1797
1838
  return quote(id);
1798
-
1799
1839
  return id;
1800
1840
  }
1801
1841
 
@@ -1831,13 +1871,18 @@ function quote(id) {
1831
1871
  * does not match the first part of the `Identifier` rule of `language.g4`
1832
1872
  * or if 'id' is a reserved keyword.
1833
1873
  *
1874
+ * Set additionalKeywords to an array of UPPERCASE keywords
1875
+ * that also need quoting, e.g. in special functions.
1876
+ *
1834
1877
  * @param {string} id
1878
+ * @param {string[]} [additionalKeywords]
1835
1879
  * @return {boolean}
1836
1880
  */
1837
- function requiresQuotingForCdl(id) {
1881
+ function requiresQuotingForCdl(id, additionalKeywords) {
1838
1882
  return !identifierRegex.test(id) ||
1839
1883
  keywords.cdl.includes(id.toUpperCase()) ||
1840
- keywords.cdl_functions.includes(id.toUpperCase());
1884
+ keywords.cdl_functions.includes(id.toUpperCase()) ||
1885
+ additionalKeywords.includes(id.toUpperCase());
1841
1886
  }
1842
1887
 
1843
1888
  const functionExpressionOperatorsRequireParentheses = [
@@ -7,8 +7,9 @@ const {
7
7
  } = require('../model/csnUtils');
8
8
  const keywords = require('../base/keywords');
9
9
  const {
10
- renderFunc, getExpressionRenderer, getRealName, addContextMarkers, addIntermediateContexts, cdsToSqlTypes,
10
+ renderFunc, getExpressionRenderer, getRealName, addContextMarkers, addIntermediateContexts,
11
11
  hasHanaComment, getHanaComment, funcWithoutParen, getSqlSnippets,
12
+ cdsToSqlTypes, cdsToHdbcdsTypes,
12
13
  } = require('./utils/common');
13
14
  const {
14
15
  renderReferentialConstraint,
@@ -195,7 +196,7 @@ function toHdbcdsSource(csn, options) {
195
196
  switch (art.kind) {
196
197
  case 'entity':
197
198
  // FIXME: For HANA CDS, we need to replace $self at the beginning of paths in association ON-condition
198
- // by the full name of the artifact we are rendering (should actually be done by forHana, but that is
199
+ // by the full name of the artifact we are rendering (should actually be done by forRelationalDB, but that is
199
200
  // somewhat difficult because this kind of absolute path is quite unusual). In order not to have to pass
200
201
  // the current artifact name down through the stack to renderExpr, we just put it into the env.
201
202
  env.currentArtifactName = artifactName;
@@ -763,7 +764,7 @@ function toHdbcdsSource(csn, options) {
763
764
  result += key + renderExpr(col, env, true);
764
765
  let alias = col.as || col.func;
765
766
  // HANA requires an alias for 'key' columns just for syntactical reasons
766
- // FIXME: This will not complain for non-refs (but that should be checked in forHana)
767
+ // FIXME: This will not complain for non-refs (but that should be checked in forRelationalDB)
767
768
  // Explicit or implicit alias?
768
769
  // Shouldn't we simply generate an alias all the time?
769
770
  if ((key || col.cast) && !alias)
@@ -1147,7 +1148,8 @@ function toHdbcdsSource(csn, options) {
1147
1148
  if (elm.type === 'cds.Decimal' && elm.scale === undefined && elm.precision === undefined)
1148
1149
  return 'DecimalFloat';
1149
1150
 
1150
- return elm.type.replace(/^cds\./, '') + renderTypeParameters(elm);
1151
+ const type = cdsToHdbcdsTypes[elm.type] || elm.type;
1152
+ return type.replace(/^cds\./, '') + renderTypeParameters(elm);
1151
1153
  }
1152
1154
 
1153
1155
  /**
@@ -1160,7 +1162,7 @@ function toHdbcdsSource(csn, options) {
1160
1162
  function renderPathStep(s, idx, ref, env, inline) {
1161
1163
  // Simple id or absolute name
1162
1164
  if (typeof s === 'string') {
1163
- // HANA-specific extra magic (should actually be in forHana)
1165
+ // HANA-specific extra magic (should actually be in forRelationalDB)
1164
1166
  // In HANA, we replace leading $self by the absolute name of the current artifact
1165
1167
  // (see FIXME at renderArtifact)
1166
1168
  if (idx === 0 && s === $SELF) {
@@ -1256,7 +1258,7 @@ function toHdbcdsSource(csn, options) {
1256
1258
  const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
1257
1259
  // we can't quote functions with parens, issue warning if it is a reserved keyword
1258
1260
  if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
1259
- warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)} is a SAP HANA keyword`);
1261
+ warning(null, x.$location, { id: uppercaseAndUnderscore(funcName) }, 'The identifier $(ID) is a SAP HANA keyword');
1260
1262
  return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
1261
1263
  }
1262
1264
 
@@ -6,7 +6,7 @@ const { checkCSNVersion } = require('../json/csnVersion');
6
6
  const { getUtils, forEachDefinition } = require('../model/csnUtils');
7
7
  const { optionProcessor } = require('../optionProcessor');
8
8
  const { isBetaEnabled } = require('../base/model');
9
- const { transformForHanaWithCsn } = require('../transform/forHanaNew');
9
+ const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
10
10
 
11
11
 
12
12
  /**
@@ -44,9 +44,9 @@ function toRename(inputCsn, options) {
44
44
  if (!isBetaEnabled(options, 'toRename'))
45
45
  error(null, null, 'Generation of SQL rename statements is not supported yet (only in beta mode)');
46
46
 
47
- // FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forHana)
48
- const csn = transformForHanaWithCsn(inputCsn, options, 'to.rename');
49
- // forHanaCsn looses empty contexts and services, add them again so that toRename can calculate the namespaces
47
+ // FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forRelationalDB)
48
+ const csn = transformForRelationalDBWithCsn(inputCsn, options, 'to.rename');
49
+ // forRelationalDB looses empty contexts and services, add them again so that toRename can calculate the namespaces
50
50
  forEachDefinition(csn, (artifact, artifactName) => {
51
51
  if ((artifact.kind === 'context' || artifact.kind === 'service') && csn.definitions[artifactName] === undefined)
52
52
  csn.definitions[artifactName] = artifact;
@@ -157,7 +157,7 @@ function toSqlDdl(csn, options) {
157
157
  Render column removals as HANA SQL.
158
158
  */
159
159
  dropColumns(tableName, sqlIds) {
160
- return [ `ALTER TABLE ${tableName} DROP (${sqlIds.join(', ')});` ];
160
+ return [ `ALTER TABLE ${tableName} DROP ${options.sqlDialect === 'hana' ? '(' : ''}${sqlIds.join(', ')}${options.sqlDialect === 'hana' ? ')' : ''};` ];
161
161
  },
162
162
  /*
163
163
  Render association removals as HANA SQL.
@@ -221,7 +221,7 @@ function toSqlDdl(csn, options) {
221
221
  };
222
222
 
223
223
  // FIXME: Currently requires 'options.forHana', because it can only render HANA-ish SQL dialect
224
- if (!options.forHana)
224
+ if (!options.forHana && !isBetaEnabled(options, 'sqlExtensions'))
225
225
  throw new Error('toSql can currently only be used with HANA preprocessing');
226
226
 
227
227
  checkCSNVersion(csn, options);
@@ -325,7 +325,7 @@ function toSqlDdl(csn, options) {
325
325
  }
326
326
 
327
327
  // add `ALTER TABLE ADD CONSTRAINT` statements per default for `to.sql` w/ dialect `hana`
328
- if (!options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana') {
328
+ if (!options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres')) {
329
329
  const alterStmts = manageConstraints(csn, options);
330
330
 
331
331
  for ( const constraintName of Object.keys(alterStmts))
@@ -352,7 +352,7 @@ function toSqlDdl(csn, options) {
352
352
  * @param {object} env Render environment
353
353
  */
354
354
  function renderArtifactInto(artifactName, art, resultObj, env) {
355
- // Ignore whole artifacts if forHana says so
355
+ // Ignore whole artifacts if forRelationalDB says so
356
356
  if (art.abstract || hasValidSkipOrExists(art))
357
357
  return;
358
358
 
@@ -605,8 +605,8 @@ function toSqlDdl(csn, options) {
605
605
  if (primaryKeys !== '')
606
606
  result += `,\n${childEnv.indent}${primaryKeys}`;
607
607
 
608
- // for `to.sql` w/ dialect `hana` the constraints will be part of the
609
- const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana';
608
+ // for `to.sql` w/ dialect `hana` the constraints will be part of the alter statement
609
+ const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres');
610
610
  if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
611
611
  const renderReferentialConstraintsAsHdbconstraint = options.src === 'hdi';
612
612
  const referentialConstraints = {};
@@ -1506,8 +1506,10 @@ function toSqlDdl(csn, options) {
1506
1506
  case 'sqlite':
1507
1507
  case 'hana':
1508
1508
  return 'CURRENT_TIMESTAMP';
1509
- case 'postgres':
1509
+ case 'h2':
1510
1510
  return 'current_timestamp';
1511
+ case 'postgres':
1512
+ return '(current_timestamp at time zone \'UTC\')';
1511
1513
  default:
1512
1514
  return quoteSqlId(x.ref[0]);
1513
1515
  }
@@ -1534,12 +1536,16 @@ function toSqlDdl(csn, options) {
1534
1536
  if (x.ref[1] === 'id') {
1535
1537
  if (options.sqlDialect === 'hana')
1536
1538
  return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1539
+ else if (options.sqlDialect === 'postgres')
1540
+ return 'current_setting(\'CAP.APPLICATIONUSER\')';
1537
1541
  warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
1538
1542
  return '\'$user.id\'';
1539
1543
  }
1540
1544
  else if (x.ref[1] === 'locale') {
1541
1545
  if (options.sqlDialect === 'hana')
1542
1546
  return 'SESSION_CONTEXT(\'LOCALE\')';
1547
+ else if (options.sqlDialect === 'postgres')
1548
+ return 'current_setting(\'CAP.LOCALE\')';
1543
1549
  return '\'en\''; // default language
1544
1550
  }
1545
1551
  // Basically: Second path step was invalid, do nothing - should not happen.
@@ -1567,6 +1573,8 @@ function toSqlDdl(csn, options) {
1567
1573
  case 'hana':
1568
1574
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1569
1575
  case 'postgres':
1576
+ return '(to_timestamp(current_setting(\'CAP.VALID_FROM\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
1577
+ case 'h2':
1570
1578
  case 'plain':
1571
1579
  return 'current_timestamp';
1572
1580
  default:
@@ -1584,6 +1592,8 @@ function toSqlDdl(csn, options) {
1584
1592
  case 'hana':
1585
1593
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1586
1594
  case 'postgres':
1595
+ return '(to_timestamp(current_setting(\'CAP.VALID_TO\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
1596
+ case 'h2':
1587
1597
  case 'plain':
1588
1598
  return 'current_timestamp';
1589
1599
  default: