@sap/cds-compiler 3.1.2 → 3.3.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 (100) hide show
  1. package/CHANGELOG.md +80 -3
  2. package/bin/cdsc.js +1 -1
  3. package/doc/CHANGELOG_BETA.md +18 -0
  4. package/lib/api/main.js +8 -13
  5. package/lib/base/error.js +2 -2
  6. package/lib/base/keywords.js +2 -24
  7. package/lib/base/message-registry.js +43 -14
  8. package/lib/base/messages.js +20 -10
  9. package/lib/base/model.js +1 -1
  10. package/lib/checks/actionsFunctions.js +1 -1
  11. package/lib/checks/annotationsOData.js +2 -2
  12. package/lib/checks/arrayOfs.js +15 -7
  13. package/lib/checks/cdsPersistence.js +1 -1
  14. package/lib/checks/checkForTypes.js +48 -0
  15. package/lib/checks/defaultValues.js +2 -2
  16. package/lib/checks/elements.js +81 -6
  17. package/lib/checks/foreignKeys.js +12 -13
  18. package/lib/checks/invalidTarget.js +10 -11
  19. package/lib/checks/managedInType.js +21 -15
  20. package/lib/checks/nullableKeys.js +1 -1
  21. package/lib/checks/onConditions.js +9 -9
  22. package/lib/checks/parameters.js +21 -0
  23. package/lib/checks/selectItems.js +1 -1
  24. package/lib/checks/types.js +2 -2
  25. package/lib/checks/utils.js +17 -7
  26. package/lib/checks/validator.js +26 -14
  27. package/lib/compiler/assert-consistency.js +13 -6
  28. package/lib/compiler/builtins.js +8 -0
  29. package/lib/compiler/checks.js +40 -33
  30. package/lib/compiler/define.js +50 -44
  31. package/lib/compiler/extend.js +303 -37
  32. package/lib/compiler/kick-start.js +2 -35
  33. package/lib/compiler/populate.js +83 -62
  34. package/lib/compiler/propagator.js +1 -1
  35. package/lib/compiler/resolve.js +61 -104
  36. package/lib/compiler/shared.js +16 -6
  37. package/lib/compiler/tweak-assocs.js +25 -12
  38. package/lib/compiler/utils.js +2 -2
  39. package/lib/edm/annotations/genericTranslation.js +3 -3
  40. package/lib/edm/csn2edm.js +10 -10
  41. package/lib/edm/edm.js +17 -9
  42. package/lib/edm/edmPreprocessor.js +53 -30
  43. package/lib/edm/edmUtils.js +7 -2
  44. package/lib/gen/Dictionary.json +14 -0
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +3 -2
  47. package/lib/gen/languageParser.js +4205 -4100
  48. package/lib/inspect/inspectModelStatistics.js +1 -1
  49. package/lib/inspect/inspectPropagation.js +23 -9
  50. package/lib/json/csnVersion.js +1 -1
  51. package/lib/json/from-csn.js +26 -19
  52. package/lib/json/to-csn.js +47 -5
  53. package/lib/language/antlrParser.js +1 -1
  54. package/lib/language/genericAntlrParser.js +29 -13
  55. package/lib/language/language.g4 +28 -8
  56. package/lib/main.d.ts +3 -6
  57. package/lib/model/.eslintrc.json +13 -0
  58. package/lib/model/api.js +4 -2
  59. package/lib/model/csnRefs.js +74 -47
  60. package/lib/model/csnUtils.js +236 -218
  61. package/lib/model/enrichCsn.js +41 -31
  62. package/lib/model/revealInternalProperties.js +61 -57
  63. package/lib/model/sortViews.js +31 -31
  64. package/lib/modelCompare/compare.js +6 -6
  65. package/lib/optionProcessor.js +5 -0
  66. package/lib/render/manageConstraints.js +2 -2
  67. package/lib/render/toCdl.js +31 -44
  68. package/lib/render/toHdbcds.js +7 -5
  69. package/lib/render/toRename.js +4 -4
  70. package/lib/render/toSql.js +11 -5
  71. package/lib/render/utils/common.js +20 -9
  72. package/lib/render/utils/sql.js +5 -5
  73. package/lib/transform/db/applyTransformations.js +32 -3
  74. package/lib/transform/db/expansion.js +81 -37
  75. package/lib/transform/db/flattening.js +1 -1
  76. package/lib/transform/db/temporal.js +1 -1
  77. package/lib/transform/db/transformExists.js +1 -1
  78. package/lib/transform/forOdataNew.js +10 -7
  79. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +7 -7
  80. package/lib/transform/localized.js +28 -19
  81. package/lib/transform/odata/toFinalBaseType.js +8 -11
  82. package/lib/transform/odata/typesExposure.js +1 -1
  83. package/lib/transform/transformUtilsNew.js +101 -39
  84. package/lib/transform/translateAssocsToJoins.js +5 -4
  85. package/lib/utils/moduleResolve.js +5 -5
  86. package/lib/utils/objectUtils.js +3 -3
  87. package/package.json +2 -2
  88. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  89. package/share/messages/check-proper-type-of.md +4 -4
  90. package/share/messages/check-proper-type.md +2 -2
  91. package/share/messages/duplicate-autoexposed.md +4 -4
  92. package/share/messages/extend-repeated-intralayer.md +4 -5
  93. package/share/messages/extend-unrelated-layer.md +4 -4
  94. package/share/messages/message-explanations.json +3 -1
  95. package/share/messages/redirected-to-ambiguous.md +7 -6
  96. package/share/messages/redirected-to-complex.md +63 -0
  97. package/share/messages/redirected-to-unrelated.md +6 -5
  98. package/share/messages/rewrite-not-supported.md +4 -4
  99. package/share/messages/syntax-expected-integer.md +3 -3
  100. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -8,7 +8,7 @@ const {
8
8
  const { forEach } = require('../utils/objectUtils');
9
9
  const { makeMessageFunction } = require('../base/messages');
10
10
  const { optionProcessor } = require('../optionProcessor');
11
- const { transformForHanaWithCsn } = require('../transform/forHanaNew');
11
+ const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
12
12
 
13
13
  const {
14
14
  renderReferentialConstraint, getIdentifierUtils,
@@ -38,7 +38,7 @@ function alterConstraintsWithCsn(csn, options) {
38
38
  options.assertIntegrityType = 'DB';
39
39
 
40
40
  const transformedOptions = _transformSqlOptions(csn, options);
41
- const forSqlCsn = transformForHanaWithCsn(csn, transformedOptions, 'to.sql');
41
+ const forSqlCsn = transformForRelationalDBWithCsn(csn, transformedOptions, 'to.sql');
42
42
 
43
43
  if (violations && src && src !== 'sql')
44
44
  error(null, null, `Option “--violations“ can't be combined with source style “${src}“`);
@@ -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
  /**
@@ -471,27 +469,6 @@ function csnToCdl(csn, options) {
471
469
  return `${result};\n`;
472
470
  }
473
471
 
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
472
  /**
496
473
  * Render annotations that were extended to a query element of a view or projection (they only
497
474
  * appear in the view's 'elements', not in their 'columns' for client CSN, because the element
@@ -507,8 +484,8 @@ function csnToCdl(csn, options) {
507
484
  * @param {CdlRenderEnvironment} env
508
485
  * @return {string}
509
486
  */
510
- function renderQueryElementAnnotations(artifactName, art, env) {
511
- const annotate = collectAnnotationsOfElements(art, { artifactName, path: env.path });
487
+ function renderQueryElementAndEnumAnnotations(artifactName, art, env) {
488
+ const annotate = collectAnnotationsOfElementsAndEnum(art, { artifactName, path: env.path });
512
489
  if (annotate)
513
490
  return renderExtensions([ annotate ], env);
514
491
  return '';
@@ -518,17 +495,20 @@ function csnToCdl(csn, options) {
518
495
  * Create an "annotate" statement as a CSN extension for all annotations of (sub-)elements.
519
496
  * If no annotation was found, we return `null`.
520
497
  *
521
- * @param {CSN.Artifact} artWithElements
498
+ * @param {CSN.Artifact} artifact
522
499
  * @param {CdlRenderEnvironment} env
523
500
  * @return {CSN.Extension|null}
524
501
  */
525
- function collectAnnotationsOfElements(artWithElements, env) {
526
- // Array of structures, which may be annotated as well.
527
- if (!artWithElements.elements && artWithElements.items) {
502
+ function collectAnnotationsOfElementsAndEnum(artifact, env) {
503
+ // Array, which may be annotated as well.
504
+ if (artifact.items) {
528
505
  env = envAddPath(env, [ 'items' ]);
529
- artWithElements = artWithElements.items;
506
+ artifact = artifact.items;
530
507
  }
531
508
 
509
+ if (!artifact.elements && !artifact.enum)
510
+ return null;
511
+
532
512
  const annotate = { annotate: env.path[1] };
533
513
 
534
514
  // Based on the current path, create a correctly nested structure
@@ -551,22 +531,26 @@ function csnToCdl(csn, options) {
551
531
  // ignore others, e.g. 'items'
552
532
  }
553
533
  }
554
- return collectAnnos(obj, artWithElements) ? annotate : null;
534
+ return collectAnnos(obj, artifact) ? annotate : null;
555
535
 
556
536
  /**
557
537
  * Recursive function to collect annotations. `annotateObj` will get an `elements`
558
- * object with annotations only if there are annotations on `art`'s (sub-)elements.
538
+ * object with annotations only if there are annotations on `art`'s (sub-)elements or
539
+ * enums. Returned object will use "elements" even for enums, since that is
540
+ * expected in extensions.
559
541
  *
560
542
  * @return {boolean} True, if there were annotations, false otherwise.
561
543
  */
562
544
  function collectAnnos(annotateObj, art) {
563
- if (!art.elements)
545
+ if (!art.elements && !art.enum)
564
546
  return false;
565
547
 
548
+ const dictKey = art.elements ? 'elements' : 'enum';
549
+ // Use "elements" for both enums and elements. This is allowed in extensions.
566
550
  const collected = { elements: Object.create(null) };
567
551
  let hasAnnotation = false;
568
552
 
569
- forEach(art.elements, (elemName, element) => {
553
+ forEach(art[dictKey], (elemName, element) => {
570
554
  if (!collected.elements[elemName])
571
555
  collected.elements[elemName] = { };
572
556
 
@@ -848,14 +832,15 @@ function csnToCdl(csn, options) {
848
832
  function renderView(artifactName, art, env) {
849
833
  const syntax = (art.projection) ? 'projection' : 'entity';
850
834
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
851
- result += `${env.indent}${art.abstract ? 'abstract ' : ''}${syntax === 'projection' ? 'entity' : syntax} ${renderArtifactName(artifactName)}`;
835
+ result += `${env.indent}entity ${renderArtifactName(artifactName)}`;
852
836
  if (art.params)
853
837
  result += renderParameters(art, env);
854
838
  result += ' as ';
855
839
  result += renderQuery(getNormalizedQuery(art).query, true, syntax, env, [ 'definitions', artifactName, 'query' ], art.elements);
840
+ if (art.actions) // Views/Projections also allow actions. Just the VIEW keyword variant did not.
841
+ result += renderActionsAndFunctions(art, env);
856
842
  result += ';\n';
857
- result += renderQueryElementAnnotations(artifactName, art, env);
858
- result += renderQueryActionsAndFunctions(artifactName, art, env);
843
+ result += renderQueryElementAndEnumAnnotations(artifactName, art, env);
859
844
  return result;
860
845
  }
861
846
 
@@ -1205,10 +1190,12 @@ function csnToCdl(csn, options) {
1205
1190
 
1206
1191
  // If we have a type and elements, we may have sub-structure annotates that would
1207
1192
  // get lost if we only render the type name.
1193
+ // We only extract annotations of enums, if "typeRefOnly" is true. Otherwise, since
1194
+ // the full enum is rendered below, we would have unnecessary annotations.
1208
1195
  // TODO: Can we annotate elements of targetAspect?
1209
1196
  // If so, move this block before the composition rendering.
1210
- if (!noAnnoCollect && (artifact.elements || artifact.items?.elements)) {
1211
- const annotate = collectAnnotationsOfElements(artifact, env);
1197
+ if (!noAnnoCollect && (!artifact.enum || typeRefOnly)) {
1198
+ const annotate = collectAnnotationsOfElementsAndEnum(artifact, env);
1212
1199
  if (annotate)
1213
1200
  subelementAnnotates.push(annotate);
1214
1201
  }
@@ -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) {
@@ -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;
@@ -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 = {};
@@ -1507,7 +1507,7 @@ function toSqlDdl(csn, options) {
1507
1507
  case 'hana':
1508
1508
  return 'CURRENT_TIMESTAMP';
1509
1509
  case 'postgres':
1510
- return 'current_timestamp';
1510
+ return '(current_timestamp at time zone \'UTC\')';
1511
1511
  default:
1512
1512
  return quoteSqlId(x.ref[0]);
1513
1513
  }
@@ -1534,12 +1534,16 @@ function toSqlDdl(csn, options) {
1534
1534
  if (x.ref[1] === 'id') {
1535
1535
  if (options.sqlDialect === 'hana')
1536
1536
  return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1537
+ else if (options.sqlDialect === 'postgres')
1538
+ return 'current_setting(\'CAP.APPLICATIONUSER\')';
1537
1539
  warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
1538
1540
  return '\'$user.id\'';
1539
1541
  }
1540
1542
  else if (x.ref[1] === 'locale') {
1541
1543
  if (options.sqlDialect === 'hana')
1542
1544
  return 'SESSION_CONTEXT(\'LOCALE\')';
1545
+ else if (options.sqlDialect === 'postgres')
1546
+ return 'current_setting(\'CAP.LOCALE\')';
1543
1547
  return '\'en\''; // default language
1544
1548
  }
1545
1549
  // Basically: Second path step was invalid, do nothing - should not happen.
@@ -1567,6 +1571,7 @@ function toSqlDdl(csn, options) {
1567
1571
  case 'hana':
1568
1572
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1569
1573
  case 'postgres':
1574
+ return '(to_timestamp(current_setting(\'CAP.VALID_FROM\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
1570
1575
  case 'plain':
1571
1576
  return 'current_timestamp';
1572
1577
  default:
@@ -1584,6 +1589,7 @@ function toSqlDdl(csn, options) {
1584
1589
  case 'hana':
1585
1590
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1586
1591
  case 'postgres':
1592
+ return '(to_timestamp(current_setting(\'CAP.VALID_TO\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
1587
1593
  case 'plain':
1588
1594
  return 'current_timestamp';
1589
1595
  default:
@@ -242,6 +242,10 @@ const cdsToSqlTypes = {
242
242
  'cds.DecimalFloat': 'DECIMAL',
243
243
  'cds.Integer64': 'BIGINT',
244
244
  'cds.Integer': 'INTEGER',
245
+ 'cds.Int64': 'BIGINT',
246
+ 'cds.Int32': 'INTEGER',
247
+ 'cds.Int16': 'SMALLINT',
248
+ 'cds.UInt8': 'TINYINT',
245
249
  'cds.hana.SMALLINT': 'SMALLINT',
246
250
  'cds.hana.TINYINT': 'TINYINT', // not a Standard SQL type
247
251
  'cds.Double': 'DOUBLE',
@@ -280,19 +284,25 @@ const cdsToSqlTypes = {
280
284
  'cds.hana.SMALLDECIMAL': 'DECIMAL',
281
285
  },
282
286
  postgres: {
283
- // TODO: Type mapping for binary types is not correct, yet.
284
- // We can't use text types for binary on PostgreSQL due to NUL!
287
+ // See <https://www.postgresql.org/docs/current/datatype.html>
285
288
  'cds.String': 'VARCHAR',
286
- 'cds.LargeString': 'text',
287
- 'cds.hana.CLOB': 'text',
288
- 'cds.LargeBinary': 'bytea',
289
- 'cds.Binary': 'bytea',
290
- 'cds.hana.BINARY': 'bytea',
291
- 'cds.Double': 'double precision',
292
- 'cds.hana.TINYINT': 'INTEGER',
289
+ 'cds.LargeString': 'TEXT',
290
+ 'cds.LargeBinary': 'BYTEA',
291
+ 'cds.Binary': 'BYTEA',
292
+ 'cds.Double': 'FLOAT8',
293
+ 'cds.UInt8': 'INTEGER', // Not equivalent
293
294
  },
294
295
  };
295
296
 
297
+ // Type mapping from cds type names to HDBCDS type names:
298
+ // Only those types, that need mapping, are listed.
299
+ const cdsToHdbcdsTypes = {
300
+ 'cds.UInt8': 'cds.hana.TINYINT',
301
+ 'cds.Int16': 'cds.hana.SMALLINT',
302
+ 'cds.Int32': 'cds.Integer',
303
+ 'cds.Int64': 'cds.Integer64',
304
+ };
305
+
296
306
  /**
297
307
  * Get the element matching the column
298
308
  *
@@ -521,6 +531,7 @@ module.exports = {
521
531
  addIntermediateContexts,
522
532
  addContextMarkers,
523
533
  cdsToSqlTypes,
534
+ cdsToHdbcdsTypes,
524
535
  hasHanaComment,
525
536
  getHanaComment,
526
537
  findElement,
@@ -43,8 +43,7 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
43
43
  options.src === 'hdi' ||
44
44
  (options.manageConstraints && options.manageConstraints.src === 'hdi');
45
45
 
46
- const { sqlMapping } = options;
47
- const forSqlite = options.sqlDialect === 'sqlite';
46
+ const { sqlMapping, sqlDialect } = options;
48
47
  let result = '';
49
48
  result += `${indent}CONSTRAINT ${quoteId(constraint.identifier)}\n`;
50
49
  if (renderAsHdbconstraint)
@@ -55,7 +54,7 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
55
54
  const onDeleteRemark = constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : '';
56
55
 
57
56
  // omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
58
- if (forSqlite) {
57
+ if (sqlDialect === 'sqlite') {
59
58
  if (constraint.onDelete === 'CASCADE' )
60
59
  result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;
61
60
  }
@@ -65,13 +64,14 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
65
64
  }
66
65
  }
67
66
  // constraint enforcement / validation must be switched off using sqlite pragma statement
67
+ // constraint enforcement / validation not supported by postgres
68
68
  // Does not include HDBCDS.
69
- if (options.toSql && !forSqlite) {
69
+ if (options.toSql && sqlDialect !== 'sqlite' && sqlDialect !== 'postgres') {
70
70
  result += `${indent}${!constraint.validated ? 'NOT ' : ''}VALIDATED\n`;
71
71
  result += `${indent}${!constraint.enforced ? 'NOT ' : ''}ENFORCED\n`;
72
72
  }
73
73
  // for sqlite, the DEFERRABLE keyword is required
74
- result += `${indent}${forSqlite ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
74
+ result += `${indent}${sqlDialect === 'sqlite' ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
75
75
  return result;
76
76
  }
77
77
 
@@ -35,10 +35,16 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
35
35
  };
36
36
 
37
37
  const csnPath = [ ...path ];
38
- if (prop === 'definitions')
38
+ if (prop === 'definitions') {
39
39
  definitions( parent, 'definitions', parent.definitions );
40
- else
40
+ }
41
+ else if (options.directDict) {
42
+ for (const name of Object.getOwnPropertyNames( parent ))
43
+ standard( parent, name, parent[name] );
44
+ }
45
+ else {
41
46
  standard(parent, prop, parent[prop]);
47
+ }
42
48
  return parent;
43
49
 
44
50
  /**
@@ -84,7 +90,7 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
84
90
  * @param {object} dict The value of node[_prop]
85
91
  */
86
92
  function dictionary( node, _prop, dict ) {
87
- // Allow skipping dicts like actions in forHanaNew
93
+ // Allow skipping dicts like actions in forRelationalDB
88
94
  if (options.skipDict && options.skipDict[_prop] || dict === null || dict === undefined) // with universal CSN, dicts might be null
89
95
  return;
90
96
  csnPath.push( _prop );
@@ -194,9 +200,31 @@ function applyTransformationsOnNonDictionary(parent, prop, customTransformers =
194
200
  return applyTransformationsInternal(parent, prop, customTransformers, [], options, path)[prop];
195
201
  }
196
202
 
203
+ /**
204
+ * Instead of looping through the whole model, start at a given thing (like .elements),
205
+ * as long as it is a dictionary.
206
+ *
207
+ * Each transformer gets:
208
+ * - the parent having the property
209
+ * - the name of the property
210
+ * - the value of the property
211
+ * - the path to the property
212
+ *
213
+ *
214
+ * @param {object} dictionary Dictionary to enrich in-place
215
+ * @param {object} customTransformers Map of prop to transform and function to apply
216
+ * @param {applyTransformationsOptions} [options={}]
217
+ * @param {CSN.Path} path Path pointing to parent
218
+ * @returns {object} dictionary with transformations applied
219
+ */
220
+ function applyTransformationsOnDictionary(dictionary, customTransformers = {}, options = {}, path = []) {
221
+ return applyTransformationsInternal(dictionary, null, customTransformers, [], { directDict: true, ...options }, path);
222
+ }
223
+
197
224
  module.exports = {
198
225
  applyTransformations,
199
226
  applyTransformationsOnNonDictionary,
227
+ applyTransformationsOnDictionary,
200
228
  };
201
229
 
202
230
 
@@ -209,4 +237,5 @@ module.exports = {
209
237
  * @property {object} [skipStandard] stop drill-down on certain "standard" props
210
238
  * @property {object} [skipDict] stop drill-down on certain "dictionary" props
211
239
  * @property {boolean} [skipIgnore=true] Whether to skip _ignore elements or not
240
+ * @property {boolean} [directDict=false] Implicitly set via applyTransformationsOnDictionary
212
241
  */
@@ -7,7 +7,7 @@ const {
7
7
  walkCsnPath,
8
8
  } = require('../../model/csnUtils');
9
9
  const { csnRefs, implicitAs } = require('../../model/csnRefs');
10
- const { setProp, isBetaEnabled } = require('../../base/model');
10
+ const { setProp } = require('../../base/model');
11
11
  const { forEach } = require('../../utils/objectUtils');
12
12
 
13
13
  /**
@@ -26,12 +26,11 @@ const { forEach } = require('../../utils/objectUtils');
26
26
  function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithAnyError }, iterateOptions = {}) {
27
27
  const csnUtils = getUtils(csn);
28
28
  const {
29
- isStructured, get$combined, getFinalBaseTypeWithProps, getServiceName,
29
+ isStructured, get$combined, getFinalBaseTypeWithProps,
30
30
  } = csnUtils;
31
31
  let { effectiveType, inspectRef } = csnUtils;
32
32
 
33
- if (isBetaEnabled(options, 'nestedProjections'))
34
- rewriteExpandInline();
33
+ rewriteExpandInline();
35
34
 
36
35
 
37
36
  applyTransformations(csn, {
@@ -83,8 +82,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
83
82
  */
84
83
  if (rewritten.toMany.length > 0 && !options.toOdata) {
85
84
  markAsToDummyfy(artifact, path[1]);
86
- if (getServiceName(path[1]) === null)
87
- error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME), which is outside any service');
85
+ error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME)');
88
86
  }
89
87
  else {
90
88
  parent.columns = rewritten.columns;
@@ -347,14 +345,18 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
347
345
  }
348
346
  else if (current.xpr || current.args) {
349
347
  // We need to re-write refs in the .xpr/.args so they stay resolvable - we need to prepend the currentRef
350
- rewriteXprArgs(current, currentRef);
348
+ rewriteExpressionArrays(current, currentRef);
351
349
  expanded.push(Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } ));
352
350
  }
351
+ else if (current.on || current.cast?.on) {
352
+ rewriteOn(current, [ currentAlias.slice(0, -1).join(pathDelimiter) ]);
353
+ expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));
354
+ }
353
355
  else if (current.val !== undefined || current.func !== undefined) {
354
356
  expanded.push(Object.assign(current, { as: currentAlias.join(pathDelimiter) }));
355
357
  }
356
- else {
357
- expanded.push({ ref: currentRef, as: currentAlias.join(pathDelimiter) });
358
+ else { // preserve stuff like .cast for redirection
359
+ expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));
358
360
  }
359
361
  }
360
362
 
@@ -367,37 +369,79 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
367
369
  * @param {object} parent Thing that has an .xpr/.args
368
370
  * @param {string[]} ref Ref so far
369
371
  */
370
- function rewriteXprArgs(parent, ref) {
372
+ function rewriteExpressionArrays(parent, ref) {
371
373
  const stack = [ [ parent, ref ] ];
372
374
  while (stack.length > 0) {
373
375
  const [ current, currentRef ] = stack.pop();
374
- if (current.xpr) {
375
- for (let i = 0; i < current.xpr.length; i++) {
376
- const part = current.xpr[i];
377
- if (part.ref) {
378
- part.ref = currentRef.concat(part.ref);
379
- // part.as = currentAlias.concat(part.as || part.ref[ref.length - 1]).join(pathDelimiter);
380
- current.xpr[i] = part;
381
- stack.push([ part, part.ref ]);
382
- }
383
- else {
384
- stack.push([ part, currentRef ]);
385
- }
386
- }
376
+ if (current.xpr)
377
+ rewriteSingleExpressionArray(current.xpr, currentRef, stack);
378
+ if (current.args)
379
+ rewriteSingleExpressionArray(current.args, currentRef, stack);
380
+ }
381
+ }
382
+
383
+ /**
384
+ * With a .cast.on or .on in a .expand/.inline, we need to change the references,
385
+ * since we change the overall scope of things (by "heaving" them up into "normal refs").
386
+ *
387
+ * So anything that does not have a $self/$projection infron get's the so-far-traveled alias,
388
+ * since after the transformation it will basically be in "top-level".
389
+ *
390
+ * @param {object} parent
391
+ * @param {Array} ref The so-far effective name (basically the will-be alias), as an array to treat like a ref
392
+ */
393
+ function rewriteOn(parent, ref) {
394
+ const stack = [ [ parent, ref ] ];
395
+ while (stack.length > 0) {
396
+ const [ current, currentRef ] = stack.pop();
397
+ if (current.on)
398
+ rewriteOnCondition(current.on, currentRef, stack);
399
+ if (current.cast?.on)
400
+ rewriteOnCondition(current.cast.on, currentRef, stack);
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Actually rewrite the given oncondition. Once we find something to rewrite,
406
+ * we preprend the currentRef.
407
+ *
408
+ * All stuff is pushed to the stack.
409
+ *
410
+ * @param {Array} on
411
+ * @param {Array} currentRef
412
+ * @param {Array} stack
413
+ */
414
+ function rewriteOnCondition(on, currentRef, stack) {
415
+ for (let i = 0; i < on.length; i++) {
416
+ const part = on[i];
417
+ if (part.ref && part.ref[0] !== '$self' && part.ref[0] !== '$projection') {
418
+ part.ref = currentRef[0] ? [ currentRef[0], ...part.ref ] : part.ref;
419
+ on[i] = part;
420
+ stack.push([ part, part.ref ]);
387
421
  }
388
- if (current.args) {
389
- for (let i = 0; i < current.args.length; i++) {
390
- const part = current.args[i];
391
- if (part.ref) {
392
- part.ref = currentRef.concat(part.ref);
393
- // part.as = currentAlias.concat(part.as || part.ref[ref.length - 1]).join(pathDelimiter);
394
- current.args[i] = part;
395
- stack.push([ part, part.ref ]);
396
- }
397
- else {
398
- stack.push([ part, currentRef ]);
399
- }
400
- }
422
+ else {
423
+ stack.push([ part, currentRef ]);
424
+ }
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Rewrite the given expressionArray, prefixing currentRef to all refs
430
+ *
431
+ * @param {Array} expressionArray
432
+ * @param {Array} currentRef
433
+ * @param {Array} stack
434
+ */
435
+ function rewriteSingleExpressionArray(expressionArray, currentRef, stack) {
436
+ for (let i = 0; i < expressionArray.length; i++) {
437
+ const part = expressionArray[i];
438
+ if (part.ref) {
439
+ part.ref = currentRef.concat(part.ref);
440
+ expressionArray[i] = part;
441
+ stack.push([ part, part.ref ]);
442
+ }
443
+ else {
444
+ stack.push([ part, currentRef ]);
401
445
  }
402
446
  }
403
447
  }
@@ -409,7 +453,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
409
453
  */
410
454
  function findAnEntity() {
411
455
  for (const name in csn.definitions) {
412
- if (Object.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
456
+ if (Object.prototype.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
413
457
  return name;
414
458
  }
415
459
  return null;