@sap/cds-compiler 4.0.2 → 4.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +100 -5
  2. package/bin/cdsc.js +12 -12
  3. package/doc/CHANGELOG_BETA.md +11 -0
  4. package/lib/api/main.js +31 -11
  5. package/lib/api/validate.js +1 -1
  6. package/lib/base/location.js +6 -7
  7. package/lib/base/message-registry.js +84 -38
  8. package/lib/base/messages.js +11 -10
  9. package/lib/base/model.js +6 -2
  10. package/lib/checks/defaultValues.js +6 -6
  11. package/lib/checks/foreignKeys.js +0 -5
  12. package/lib/checks/onConditions.js +17 -12
  13. package/lib/checks/queryNoDbArtifacts.js +132 -72
  14. package/lib/checks/sql-snippets.js +15 -4
  15. package/lib/checks/types.js +3 -3
  16. package/lib/checks/utils.js +1 -1
  17. package/lib/compiler/assert-consistency.js +44 -16
  18. package/lib/compiler/base.js +1 -0
  19. package/lib/compiler/builtins.js +7 -8
  20. package/lib/compiler/checks.js +274 -197
  21. package/lib/compiler/classes.js +62 -0
  22. package/lib/compiler/cycle-detector.js +3 -3
  23. package/lib/compiler/define.js +63 -50
  24. package/lib/compiler/extend.js +38 -20
  25. package/lib/compiler/finalize-parse-cdl.js +2 -1
  26. package/lib/compiler/generate.js +0 -8
  27. package/lib/compiler/index.js +9 -7
  28. package/lib/compiler/kick-start.js +2 -0
  29. package/lib/compiler/populate.js +139 -110
  30. package/lib/compiler/propagator.js +4 -3
  31. package/lib/compiler/resolve.js +157 -126
  32. package/lib/compiler/shared.js +706 -404
  33. package/lib/compiler/tweak-assocs.js +21 -10
  34. package/lib/compiler/utils.js +228 -36
  35. package/lib/edm/annotations/genericTranslation.js +1 -1
  36. package/lib/edm/edm.js +4 -1
  37. package/lib/edm/edmPreprocessor.js +5 -4
  38. package/lib/edm/edmUtils.js +2 -4
  39. package/lib/gen/Dictionary.json +34 -10
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +3987 -3963
  43. package/lib/json/from-csn.js +43 -47
  44. package/lib/json/to-csn.js +11 -11
  45. package/lib/language/antlrParser.js +2 -1
  46. package/lib/language/genericAntlrParser.js +52 -43
  47. package/lib/language/language.g4 +59 -59
  48. package/lib/language/multiLineStringParser.js +2 -0
  49. package/lib/main.d.ts +5 -0
  50. package/lib/model/csnRefs.js +37 -19
  51. package/lib/model/csnUtils.js +20 -16
  52. package/lib/model/revealInternalProperties.js +29 -21
  53. package/lib/modelCompare/compare.js +112 -39
  54. package/lib/modelCompare/utils/filter.js +54 -24
  55. package/lib/optionProcessor.js +6 -6
  56. package/lib/render/manageConstraints.js +20 -17
  57. package/lib/render/toCdl.js +34 -20
  58. package/lib/render/toHdbcds.js +2 -2
  59. package/lib/render/toRename.js +4 -9
  60. package/lib/render/toSql.js +77 -26
  61. package/lib/render/utils/common.js +3 -3
  62. package/lib/render/utils/unique.js +52 -0
  63. package/lib/transform/db/applyTransformations.js +61 -20
  64. package/lib/transform/db/assertUnique.js +7 -8
  65. package/lib/transform/db/associations.js +2 -2
  66. package/lib/transform/db/cdsPersistence.js +8 -8
  67. package/lib/transform/db/expansion.js +17 -21
  68. package/lib/transform/db/flattening.js +23 -23
  69. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  70. package/lib/transform/db/temporal.js +1 -1
  71. package/lib/transform/db/transformExists.js +8 -7
  72. package/lib/transform/db/views.js +73 -33
  73. package/lib/transform/draft/db.js +11 -9
  74. package/lib/transform/draft/odata.js +1 -1
  75. package/lib/transform/{forOdataNew.js → forOdata.js} +6 -6
  76. package/lib/transform/forRelationalDB.js +69 -75
  77. package/lib/transform/localized.js +6 -5
  78. package/lib/transform/odata/toFinalBaseType.js +3 -3
  79. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
  80. package/lib/transform/translateAssocsToJoins.js +14 -28
  81. package/package.json +1 -1
  82. package/share/messages/check-proper-type-of.md +1 -1
  83. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  84. package/share/messages/message-explanations.json +1 -1
@@ -126,7 +126,7 @@ function csnToCdl( csn, options ) {
126
126
  return result;
127
127
 
128
128
  function renderVocabulariesEntry( name, anno ) {
129
- if (!anno._ignore) {
129
+ if (!anno.$ignore) {
130
130
  // This environment is passed down the call hierarchy, for dealing with
131
131
  // indentation and name resolution issues
132
132
  const env = createEnv({ path: [ 'vocabularies', name ] });
@@ -512,8 +512,15 @@ function csnToCdl( csn, options ) {
512
512
  result += renderParameters(art, env);
513
513
  if (art.includes)
514
514
  result += renderIncludes(art.includes, env);
515
- result += ` ${renderElements(art, env)}`;
515
+
516
+ if (art.elements)
517
+ result += ` ${renderElements(art, env)}`;
518
+ else if (art.actions)
519
+ // if there are no elements, but actions, CDL syntax requires braces.
520
+ result += ' { }';
521
+
516
522
  result += `${renderActionsAndFunctions(art, env)};\n`;
523
+
517
524
  return result;
518
525
  }
519
526
 
@@ -565,7 +572,7 @@ function csnToCdl( csn, options ) {
565
572
  * Returns the resulting source string.
566
573
  *
567
574
  * @param {string} elementName
568
- * @param {CSN.Element|CSN.Enum} element
575
+ * @param {CSN.Element} element
569
576
  * @param {CdlRenderEnvironment} env
570
577
  */
571
578
  function renderElement( elementName, element, env ) {
@@ -1037,7 +1044,7 @@ function csnToCdl( csn, options ) {
1037
1044
  result += `${env.indent}}`;
1038
1045
  }
1039
1046
 
1040
- if (isLeadingQuery)
1047
+ if (isLeadingQuery && query.actions)
1041
1048
  result += renderActionsAndFunctions(query, env);
1042
1049
 
1043
1050
  if (select.where)
@@ -1148,14 +1155,14 @@ function csnToCdl( csn, options ) {
1148
1155
  */
1149
1156
  function renderActionsAndFunctions( art, env ) {
1150
1157
  let result = '';
1151
- const childEnv = env.withIncreasedIndent();
1152
- for (const name in art.actions)
1153
- result += renderActionOrFunction(name, art.actions[name], childEnv.withSubPath([ 'actions', name ]));
1154
-
1155
- // Even if we have seen actions/functions, they might all have been ignored
1156
- if (result !== '')
1157
- result = ` actions {\n${result}${env.indent}}`;
1158
-
1158
+ if (art.actions) {
1159
+ const childEnv = env.withIncreasedIndent();
1160
+ for (const name in art.actions)
1161
+ result += renderActionOrFunction(name, art.actions[name], childEnv.withSubPath([ 'actions', name ]));
1162
+ result = (result === '')
1163
+ ? ' actions { }'
1164
+ : ` actions {\n${result}${env.indent}}`;
1165
+ }
1159
1166
  return result;
1160
1167
  }
1161
1168
 
@@ -1232,10 +1239,15 @@ function csnToCdl( csn, options ) {
1232
1239
  if (art.includes)
1233
1240
  result += renderIncludes(art.includes, env);
1234
1241
 
1235
- if (!art.type && art.elements) // For nicer output, no colon if unnamed structure is used.
1236
- result += ` ${renderTypeReferenceAndProps(art, env)}`;
1237
- else
1238
- result += ` : ${renderTypeReferenceAndProps(art, env)}`;
1242
+ const type = renderTypeReferenceAndProps(art, env);
1243
+ if (type) {
1244
+ // For nicer output, no colon if unnamed structure is used.
1245
+ result += (!art.type && art.elements) ? ` ${type}` : ` : ${type}`;
1246
+ }
1247
+ else {
1248
+ msg.warning('syntax-missing-type', env.path, { name: artifactName },
1249
+ 'Missing type for definition $(NAME); can\'t be represented in CDL');
1250
+ }
1239
1251
  result += ';\n';
1240
1252
  return result;
1241
1253
  }
@@ -1310,8 +1322,9 @@ function csnToCdl( csn, options ) {
1310
1322
 
1311
1323
  if (artifact.notNull !== undefined && !artifact.on) // unmanaged associations can't be followed by "not null"
1312
1324
  result += renderNullability(artifact);
1313
- // DEFAULT not possible here.
1314
1325
 
1326
+ if (artifact.default && !artifact.on)
1327
+ result += ` default ${exprRenderer.renderExpr(artifact.default, env.withSubPath([ 'default' ]))}`;
1315
1328
  return result;
1316
1329
  }
1317
1330
 
@@ -1814,7 +1827,7 @@ function csnToCdl( csn, options ) {
1814
1827
  // Take the annotation assignment apart into <nameBeforeVariant>#<variantAndRest>
1815
1828
  const parts = name.split('#');
1816
1829
  const nameBeforeVariant = parts[0];
1817
- const variant = parts[1];
1830
+ const variant = parts.length > 1 ? parts.slice(1).join('#') : undefined;
1818
1831
  const { parentheses } = config;
1819
1832
 
1820
1833
  let result = `${env.indent}@`;
@@ -1822,10 +1835,11 @@ function csnToCdl( csn, options ) {
1822
1835
  result += '(';
1823
1836
 
1824
1837
  result += quoteAnnotationPathIfRequired(nameBeforeVariant, env);
1825
- if (variant !== undefined)
1838
+ if (variant !== undefined) {
1826
1839
  // Unfortunately, the compiler does not allow `.@` after the first variant identifier,
1827
- // so we're back at simple paths.
1840
+ // nor multiple `#`, so we're back at simple paths that are possibly quoted.
1828
1841
  result += `#${quotePathIfRequired(variant, env)}`;
1842
+ }
1829
1843
  result += ` : ${renderAnnotationValue(anno, env)}`;
1830
1844
 
1831
1845
  if (parentheses)
@@ -647,7 +647,7 @@ function toHdbcdsSource( csn, options ) {
647
647
  // Because we already emit an error that calc-on-write is not supported, just ignore nullability/default.
648
648
  if (!elm.value?.stored) {
649
649
  result += renderNullability(elm);
650
- if (elm.default)
650
+ if (elm.default && !elm.target)
651
651
  result += ` default ${renderExpr(elm.default, env.withSubPath([ 'default' ]))}`;
652
652
  }
653
653
 
@@ -798,7 +798,7 @@ function toHdbcdsSource( csn, options ) {
798
798
 
799
799
  const key = (!env.skipKeys && (col.key || element?.key) ? 'key ' : '');
800
800
  result += key + renderExpr(withoutCast(col), env);
801
- let alias = col.as || col.func;
801
+ let alias = col.as || (!col.args && col.func); // func: e.g. CURRENT_TIMESTAMP
802
802
  // HANA requires an alias for 'key' columns just for syntactical reasons
803
803
  // FIXME: This will not complain for non-refs (but that should be checked in forRelationalDB)
804
804
  // Explicit or implicit alias?
@@ -5,15 +5,11 @@ const { makeMessageFunction } = require('../base/messages');
5
5
  const { checkCSNVersion } = require('../json/csnVersion');
6
6
  const { forEachDefinition } = require('../model/csnUtils');
7
7
  const { optionProcessor } = require('../optionProcessor');
8
- const { isBetaEnabled } = require('../base/model');
9
8
  const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
10
9
  const { getIdentifierUtils } = require('./utils/sql');
11
10
 
12
11
 
13
12
  /**
14
- * FIXME: Not yet supported, only in beta mode
15
- * FIXME: This is not up-to-date in regards to the changes to hdbcds/sql quoting etc.
16
- *
17
13
  * Generate SQL DDL rename statements for a migration, renaming existing tables and their
18
14
  * columns so that they match the result of "toHana" or "toSql" with the 'plain' option for names.
19
15
  * Expects the naming convention of the existing tables to be either 'quoted' or 'hdbcds' (default).
@@ -32,7 +28,7 @@ const { getIdentifierUtils } = require('./utils/sql');
32
28
  * @returns {object} A dictionary of name: rename statement
33
29
  */
34
30
  function toRename( inputCsn, options ) {
35
- const { error, warning, throwWithError } = makeMessageFunction(inputCsn, options, 'to.rename');
31
+ const { warning, throwWithError } = makeMessageFunction(inputCsn, options, 'to.rename');
36
32
 
37
33
  // Merge options with defaults.
38
34
  options = Object.assign({ sqlMapping: 'hdbcds', sqlDialect: 'hana' }, options);
@@ -41,9 +37,8 @@ function toRename( inputCsn, options ) {
41
37
  optionProcessor.verifyOptions(options, 'toRename').forEach(complaint => warning(null, null, `${complaint}`));
42
38
  checkCSNVersion(inputCsn, options);
43
39
 
44
- // Requires beta mode
45
- if (!isBetaEnabled(options, 'toRename'))
46
- error(null, null, 'Generation of SQL rename statements is not supported yet (only in beta mode)');
40
+ // Let users know that this is internal
41
+ warning(null, null, 'Generation of SQL rename statements is a beta feature and might change in the future');
47
42
 
48
43
  // FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forRelationalDB)
49
44
  const csn = transformForRelationalDBWithCsn(inputCsn, options, 'to.rename');
@@ -102,7 +97,7 @@ function toRename( inputCsn, options ) {
102
97
  const beforeColumnName = hdbcdsOrQuotedIdentifiers.quoteSqlId(name);
103
98
  const afterColumnName = plainIdentifiers.quoteSqlId(name);
104
99
 
105
- if (!e._ignore) {
100
+ if (!e.$ignore) {
106
101
  if (e.target)
107
102
  str = ` EXEC 'ALTER TABLE ${afterTableName} DROP ASSOCIATION ${beforeColumnName}';\n`;
108
103
  else if (beforeColumnName.toUpperCase() === `"${afterColumnName}"` ) // Basically a no-op - render commented out
@@ -24,7 +24,8 @@ const { timetrace } = require('../utils/timetrace');
24
24
  const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
25
25
  const { smartFuncId } = require('../sql-identifier');
26
26
  const { sortCsn } = require('../json/to-csn');
27
- const { manageConstraints } = require('./manageConstraints');
27
+ const { manageConstraints, manageConstraint } = require('./manageConstraints');
28
+ const { renderUniqueConstraintString, renderUniqueConstraintDrop, renderUniqueConstraintAdd } = require('./utils/unique');
28
29
  const { ModelError, CompilerAssertion } = require('../base/error');
29
30
 
30
31
  class SqlRenderEnvironment {
@@ -105,6 +106,7 @@ function toSqlDdl( csn, options ) {
105
106
  error, warning, info, throwWithAnyError,
106
107
  } = makeMessageFunction(csn, options, 'to.sql');
107
108
  const { quoteSqlId, prepareIdentifier, renderArtifactName } = getIdentifierUtils(csn, options);
109
+ let reportedMissingUserReplacement = false;
108
110
 
109
111
  const exprRenderer = createExpressionRenderer({
110
112
  // FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
@@ -175,6 +177,7 @@ function toSqlDdl( csn, options ) {
175
177
  hdbview: Object.create(null),
176
178
  hdbconstraint: Object.create(null),
177
179
  deletions: Object.create(null),
180
+ constraintDeletions: [],
178
181
  migrations: Object.create(null),
179
182
  };
180
183
 
@@ -240,8 +243,10 @@ function toSqlDdl( csn, options ) {
240
243
  const sqlVersionLine = `-- ${generatedByCompilerVersion()}\n`;
241
244
 
242
245
  // Handle hdbKinds separately from alterTable case
243
- // eslint-disable-next-line no-unused-vars
244
- const { deletions, migrations: _, ...hdbKinds } = mainResultObj;
246
+ const {
247
+ // eslint-disable-next-line no-unused-vars
248
+ deletions, constraintDeletions, migrations: _, ...hdbKinds
249
+ } = mainResultObj;
245
250
  for (const hdbKind of Object.keys(hdbKinds)) {
246
251
  for (const name in mainResultObj[hdbKind]) {
247
252
  if (options.src === 'sql') {
@@ -260,14 +265,15 @@ function toSqlDdl( csn, options ) {
260
265
  }
261
266
 
262
267
  // add `ALTER TABLE ADD CONSTRAINT` statements per default for `to.sql` w/ dialect `hana` / `postgres`
263
- // TODO `ALTER TABLE ADD CONSTRAINT` statements also for sqlite constraints
264
268
  if (!options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres' /* || options.sqlDialect === 'sqlite' */)) {
269
+ const constraints = Object.create(null);
265
270
  const alterStmts = manageConstraints(csn, options);
266
271
 
267
272
  forEachKey(alterStmts, (constraintName) => {
268
- sql[constraintName] = `${options.testMode ? '' : sqlVersionLine}${alterStmts[constraintName]}`;
273
+ if (!csn.unchangedConstraints?.has(constraintName))
274
+ constraints[constraintName] = `${options.testMode ? '' : sqlVersionLine}${alterStmts[constraintName]}`;
269
275
  });
270
- mainResultObj.sql = sql;
276
+ mainResultObj.constraints = constraints;
271
277
  }
272
278
 
273
279
  if (options.src === 'sql')
@@ -333,8 +339,12 @@ function toSqlDdl( csn, options ) {
333
339
  function renderArtifactExtensionInto( artifactName, artifact, ext, resultObj, env ) {
334
340
  // Property kind is always omitted for elements and can be omitted for
335
341
  // top-level type definitions, it does not exist for extensions.
336
- if (artifactName && !ext.query)
337
- renderExtendInto(artifactName, artifact.elements, ext.elements, resultObj, env, extensionsDuplicateChecker);
342
+ if (artifactName && !ext.query) {
343
+ if (ext.constraint)
344
+ renderConstraintExtendInto(artifactName, ext, resultObj);
345
+ else
346
+ renderExtendInto(artifactName, artifact.elements, ext.elements, resultObj, env, extensionsDuplicateChecker);
347
+ }
338
348
 
339
349
  if (!artifactName)
340
350
  throw new ModelError(`Undefined artifact name: ${artifactName}`);
@@ -421,6 +431,24 @@ function toSqlDdl( csn, options ) {
421
431
  }
422
432
  }
423
433
 
434
+ if (migration.removeConstraints) {
435
+ const constraintTypes = [ 'unique', 'referential' ];
436
+ constraintTypes.forEach((constraintType) => {
437
+ if (migration.removeConstraints[constraintType]) {
438
+ const entries = Object.entries(migration.removeConstraints[constraintType]);
439
+ const optionsWithDrop = { ...options, drop: true };
440
+ let renderer;
441
+ if (constraintType === 'referential')
442
+ renderer = constraint => manageConstraint(constraint, csn, optionsWithDrop, '', quoteSqlId);
443
+ else
444
+ renderer = (constraint, constraintName) => renderUniqueConstraintDrop(constraint, renderArtifactName(`${artifactName}_${constraintName}`), tableName, quoteSqlId);
445
+ entries.forEach(( [ constraintName, constraint ]) => {
446
+ addConstraintDeletion(resultObj, constraint.parentTable, renderer(constraint, constraintName));
447
+ });
448
+ }
449
+ });
450
+ }
451
+
424
452
  // Change column types (unsupported in sqlite)
425
453
  if (migration.change) {
426
454
  changeElementsDuplicateChecker.addArtifact(tableName, undefined, artifactName);
@@ -550,16 +578,11 @@ function toSqlDdl( csn, options ) {
550
578
  // OR create a unique index for HDI
551
579
  const uniqueConstraints = art.$tableConstraints && art.$tableConstraints.unique;
552
580
  for (const cn in uniqueConstraints) {
553
- const c = uniqueConstraints[cn];
554
- const cnName = renderArtifactName(`${artifactName}_${cn}`);
555
- const refs = c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ');
556
- if (options.src === 'hdi') {
557
- resultObj.hdbindex[`${artifactName}.${cn}`]
558
- = `UNIQUE INVERTED INDEX ${cnName} ON ${tableName} (${refs})`;
559
- }
560
- else {
561
- result += `,\n${childEnv.indent}CONSTRAINT ${cnName} UNIQUE (${refs})`;
562
- }
581
+ const constraint = renderUniqueConstraintString(uniqueConstraints[cn], renderArtifactName(`${artifactName}_${cn}`), tableName, quoteSqlId, options);
582
+ if (options.src === 'hdi')
583
+ resultObj.hdbindex[`${artifactName}.${cn}`] = constraint;
584
+ else
585
+ result += `,\n${childEnv.indent}${constraint}`;
563
586
  }
564
587
  result += `${env.indent}\n)`;
565
588
 
@@ -591,6 +614,21 @@ function toSqlDdl( csn, options ) {
591
614
  resultObj.hdbtable[artifactName] = result;
592
615
  }
593
616
 
617
+ /**
618
+ * Render an extended entity constraint into the appropriate dictionaries of 'resultObj'.
619
+ * Only SAP HANA SQL is currently supported.
620
+ *
621
+ * @param {string} artifactName Name of the artifact to render
622
+ * @param {object} ext Constraint comprising the extension
623
+ * @param {object} resultObj Result collector
624
+ */
625
+ function renderConstraintExtendInto( artifactName, { constraint, constraintName, constraintType }, resultObj ) {
626
+ const result = constraintType === 'unique' ? renderUniqueConstraintAdd(constraint, renderArtifactName(`${artifactName}_${constraintName}`), renderArtifactName(constraint.parentTable), quoteSqlId, options)
627
+ : manageConstraint(constraint, csn, options, '', quoteSqlId);
628
+
629
+ addMigration(resultObj, artifactName, false, [ result ]);
630
+ }
631
+
594
632
 
595
633
  /**
596
634
  * Render an extended entity into the appropriate dictionaries of 'resultObj'.
@@ -632,6 +670,9 @@ function toSqlDdl( csn, options ) {
632
670
  resultObj.migrations[artifactName].push(...migrations);
633
671
  }
634
672
 
673
+ function addConstraintDeletion( resultObj, artifactName, deletionSql ) {
674
+ resultObj.constraintDeletions.push(deletionSql);
675
+ }
635
676
  function addDeletion( resultObj, artifactName, deletionSql ) {
636
677
  resultObj.deletions[artifactName] = deletionSql;
637
678
  }
@@ -1064,7 +1105,7 @@ function toSqlDdl( csn, options ) {
1064
1105
  result = env.indent + renderExpr(withoutCast(col), env);
1065
1106
  if (col.as)
1066
1107
  result += ` AS ${quoteSqlId(col.as)}`;
1067
- else if (col.func)
1108
+ else if (col.func && !col.args) // e.g. CURRENT_TIMESTAMP
1068
1109
  result += ` AS ${quoteSqlId(col.func)}`;
1069
1110
  }
1070
1111
  return result;
@@ -1466,9 +1507,8 @@ function toSqlDdl( csn, options ) {
1466
1507
  case 'hana':
1467
1508
  return 'CURRENT_TIMESTAMP';
1468
1509
  case 'h2':
1469
- return 'current_timestamp';
1470
1510
  case 'postgres':
1471
- return '(current_timestamp at time zone \'UTC\')';
1511
+ return 'current_timestamp';
1472
1512
  default:
1473
1513
  return quoteSqlId(x.ref[0]);
1474
1514
  }
@@ -1498,19 +1538,28 @@ function toSqlDdl( csn, options ) {
1498
1538
  if (options.sqlDialect === 'hana')
1499
1539
  return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1500
1540
  else if (options.sqlDialect === 'postgres')
1501
- return 'current_setting(\'CAP.APPLICATIONUSER\')';
1541
+ return 'current_setting(\'cap.applicationuser\')';
1502
1542
  else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
1503
1543
  return 'session_context( \'$user.id\' )';
1504
- warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
1544
+ else if (options.sqlDialect === 'h2')
1545
+ return '@applicationuser';
1546
+
1547
+ if (!reportedMissingUserReplacement) {
1548
+ warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
1549
+ reportedMissingUserReplacement = true;
1550
+ }
1505
1551
  return '\'$user.id\'';
1506
1552
  }
1507
1553
  else if (x.ref[1] === 'locale') {
1508
1554
  if (options.sqlDialect === 'hana')
1509
1555
  return 'SESSION_CONTEXT(\'LOCALE\')';
1510
1556
  else if (options.sqlDialect === 'postgres')
1511
- return 'current_setting(\'CAP.LOCALE\')';
1557
+ return 'current_setting(\'cap.locale\')';
1512
1558
  else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
1513
1559
  return 'session_context( \'$user.locale\' )';
1560
+ else if (options.sqlDialect === 'h2')
1561
+ return '@locale';
1562
+
1514
1563
  return '\'en\''; // default language
1515
1564
  }
1516
1565
  // Basically: Second path step was invalid, do nothing - should not happen.
@@ -1542,8 +1591,9 @@ function toSqlDdl( csn, options ) {
1542
1591
  case 'hana':
1543
1592
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1544
1593
  case 'postgres':
1545
- return '(to_timestamp(current_setting(\'CAP.VALID_FROM\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
1594
+ return 'current_setting(\'cap.valid_from\')::timestamp';
1546
1595
  case 'h2':
1596
+ return '@valid_from';
1547
1597
  case 'plain':
1548
1598
  return 'current_timestamp';
1549
1599
  default:
@@ -1563,8 +1613,9 @@ function toSqlDdl( csn, options ) {
1563
1613
  case 'hana':
1564
1614
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1565
1615
  case 'postgres':
1566
- return '(to_timestamp(current_setting(\'CAP.VALID_TO\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
1616
+ return 'current_setting(\'cap.valid_to\')::timestamp';
1567
1617
  case 'h2':
1618
+ return '@valid_to';
1568
1619
  case 'plain':
1569
1620
  return 'current_timestamp';
1570
1621
  default:
@@ -146,7 +146,7 @@ function addContextMarkers( csn, killList ) {
146
146
  const contextsToCreate = Object.create(null);
147
147
  forEachDefinition(csn, (art, artifactName) => {
148
148
  const namespace = getNamespace(csn, artifactName);
149
- if (namespace && !(art._ignore || hasValidSkipOrExists(art))) {
149
+ if (namespace && !(art.$ignore || hasValidSkipOrExists(art))) {
150
150
  const parts = namespace.split('.');
151
151
  contextsToCreate[parts[0]] = true;
152
152
 
@@ -204,7 +204,7 @@ function addMissingChildContexts( csn, artifactName, killList ) {
204
204
  const possibleNames = Object.keys(csn.definitions).filter(name => name.startsWith(`${artifactName}.`)).sort((a, b) => a.length - b.length);
205
205
  for (const name of possibleNames) {
206
206
  const artifact = csn.definitions[name];
207
- if (!artifact._ignore && !hasValidSkipOrExists(artifact))
207
+ if (!artifact.$ignore && !hasValidSkipOrExists(artifact))
208
208
  addPossibleGaps(name.slice(artifactName.length + 1).split('.'), artifactName);
209
209
  }
210
210
 
@@ -360,7 +360,7 @@ function findElement( elements, column ) {
360
360
  function addIntermediateContexts( csn, killList ) {
361
361
  for (const artifactName in csn.definitions) {
362
362
  const artifact = csn.definitions[artifactName];
363
- if ((artifact.kind === 'context') && !artifact._ignore) {
363
+ if ((artifact.kind === 'context') && !artifact.$ignore) {
364
364
  // If context A.B.C and entity A exist, we still need generate context A_B.
365
365
  // But if no entity A exists, A.B is just a namespace.
366
366
  // For case 1 and 2, getParentContextName returns undefined - we then use the namespace as our "off-limits"
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Render the "CONSTRAINT <XY>;" for HDI or SQL.
5
+ *
6
+ * @param {object} constraint The constraint to add
7
+ * @param {string} constraintName Name of the constraint - needs to be escaped on caller side
8
+ * @param {string} tableName Name of the table with the constraint - needs to be escaped on caller side
9
+ * @param {function} quoteSqlId Usual rendering function
10
+ * @param {object} options Options
11
+ * @returns {string}
12
+ */
13
+ function renderUniqueConstraintString( constraint, constraintName, tableName, quoteSqlId, options ) {
14
+ const c = constraint.paths;
15
+ const refs = c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ');
16
+ if (options.src === 'hdi')
17
+ return `UNIQUE INVERTED INDEX ${constraintName} ON ${tableName} (${refs})`;
18
+
19
+ return `CONSTRAINT ${constraintName} UNIQUE (${refs})`;
20
+ }
21
+ /**
22
+ * Render the "ALTER TABLE XY DROP CONSTRAINT <Z>;"
23
+ *
24
+ * @param {object} constraint The constraint to drop
25
+ * @param {string} constraintName Name of the constraint - needs to be escaped on caller side
26
+ * @param {string} tableName Name of the table with the constraint - needs to be escaped on caller side
27
+ * @param {function} quoteSqlId Usual rendering function
28
+ * @returns {string}
29
+ */
30
+ function renderUniqueConstraintDrop( constraint, constraintName, tableName, quoteSqlId ) {
31
+ return `ALTER TABLE ${tableName} DROP CONSTRAINT ${quoteSqlId(constraintName)};`;
32
+ }
33
+
34
+ /**
35
+ * Render the "ALTER TABLE XY ADD CONSTRAINT <Z>;"
36
+ *
37
+ * @param {object} constraint The constraint to add
38
+ * @param {string} constraintName Name of the constraint - needs to be escaped on caller side
39
+ * @param {string} tableName Name of the table with the constraint - needs to be escaped on caller side
40
+ * @param {function} quoteSqlId Usual rendering function
41
+ * @param {object} options Options
42
+ * @returns {string}
43
+ */
44
+ function renderUniqueConstraintAdd( constraint, constraintName, tableName, quoteSqlId, options ) {
45
+ return `ALTER TABLE ${tableName} ADD ${renderUniqueConstraintString(constraint, constraintName, tableName, quoteSqlId, options)};`;
46
+ }
47
+
48
+ module.exports = {
49
+ renderUniqueConstraintString,
50
+ renderUniqueConstraintDrop,
51
+ renderUniqueConstraintAdd,
52
+ };
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- /**
3
+ /*
4
4
  * Module for general (partial) CSN looper functions, respecting dictionaries and allowing
5
5
  * to pass custom callbacks for certain properties like "ref".
6
6
  *
@@ -10,6 +10,8 @@
10
10
  *
11
11
  * @module lib/transform/db/applyTransformations
12
12
  */
13
+
14
+
13
15
  const { setProp } = require('../../base/model');
14
16
 
15
17
 
@@ -18,11 +20,17 @@ const { setProp } = require('../../base/model');
18
20
  * @param {string} prop The property of parent to start at
19
21
  * @param {object} customTransformers Map of prop to transform and function to apply
20
22
  * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
21
- * @param {applyTransformationsOptions} [options={}]
23
+ * @param {applyTransformationsOptions} [_options={}]
22
24
  * @param {CSN.Path} path Path to parent
23
25
  * @returns {object} parent with transformations applied
24
26
  */
25
- function applyTransformationsInternal( parent, prop, customTransformers, artifactTransformers, options, path = [] ) {
27
+ function applyTransformationsInternal( parent, prop, customTransformers, artifactTransformers, _options, path = [] ) {
28
+ const options = { ..._options };
29
+ if (!options.skipStandard)
30
+ options.skipStandard = { $tableConstraints: true };
31
+ else if (options.skipStandard.$tableConstraints === undefined)
32
+ options.skipStandard = { ...options.skipStandard, ...{ $tableConstraints: true } };
33
+
26
34
  const transformers = {
27
35
  elements: dictionary,
28
36
  definitions: dictionary,
@@ -43,12 +51,13 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
43
51
  standard( parent, name, parent[name] );
44
52
  }
45
53
  else {
46
- standard(parent, prop, parent[prop]);
54
+ standard( parent, prop, parent[prop] );
47
55
  }
48
56
  return parent;
49
57
 
50
58
  /**
51
- * Default transformer for things that are not dictionaries, like "type" or "keys".
59
+ * Default transformer for things that are not dictionaries or dictionary entries,
60
+ * such as "type" or "keys".
52
61
  * The customTransformers are applied here (and only here).
53
62
  *
54
63
  * @param {object | Array} _parent the thing that has _prop
@@ -59,7 +68,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
59
68
  if (!node || typeof node !== 'object' ||
60
69
  !{}.propertyIsEnumerable.call( _parent, _prop ) ||
61
70
  (typeof _prop === 'string' && _prop.startsWith('@')) ||
62
- (options.skipIgnore && node._ignore) ||
71
+ (options.skipIgnore && node.$ignore) ||
63
72
  options.skipStandard?.[_prop]
64
73
  )
65
74
  return;
@@ -75,13 +84,35 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
75
84
  const trans = transformers[name] || standard;
76
85
  if (customTransformers[name])
77
86
  customTransformers[name](node, name, node[name], csnPath, _parent, _prop);
78
-
79
87
  trans( node, name, node[name], csnPath );
80
88
  }
81
89
  }
82
90
  csnPath.pop();
83
91
  }
84
92
 
93
+ /**
94
+ * Transformer for dictionary entries. Similar to standard(), but does not filter
95
+ * based on the given name. Otherwise, `options.skipStandards` could accidentally skip
96
+ * dictionary entries (e.g. entity called `@Name`).
97
+ *
98
+ * @param {object | Array} dict the thing that has _prop
99
+ * @param {string|number} entryName the name of the current property
100
+ * @param {object} node The value of node[_prop]
101
+ */
102
+ function dictEntry( dict, entryName, node ) {
103
+ if (!node || typeof node !== 'object' || (options.skipIgnore && node.$ignore))
104
+ return;
105
+
106
+ csnPath.push( entryName );
107
+ for (const name of Object.getOwnPropertyNames( node )) {
108
+ const trans = transformers[name] || standard;
109
+ if (customTransformers[name])
110
+ customTransformers[name](node, name, node[name], csnPath, dict);
111
+ trans( node, name, node[name], csnPath );
112
+ }
113
+ csnPath.pop();
114
+ }
115
+
85
116
  /**
86
117
  * Transformer for things that are dictionaries - like "elements".
87
118
  *
@@ -90,12 +121,12 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
90
121
  * @param {object} dict The value of node[_prop]
91
122
  */
92
123
  function dictionary( node, _prop, dict ) {
93
- // Allow skipping dicts like actions in forRelationalDB
94
- if (options.skipDict && options.skipDict[_prop] || dict === null || dict === undefined) // with universal CSN, dicts might be null
124
+ // Allow skipping dictionaries like actions in forRelationalDB
125
+ if (options.skipDict?.[_prop] || dict == null) // with universal CSN, dictionaries might be null
95
126
  return;
96
127
  csnPath.push( _prop );
97
128
  for (const name of Object.getOwnPropertyNames( dict ))
98
- standard( dict, name, dict[name] );
129
+ dictEntry( dict, name, dict[name] );
99
130
 
100
131
  if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
101
132
  setProp(node, `$${_prop}`, dict);
@@ -112,13 +143,9 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
112
143
  function definitions( node, _prop, dict ) {
113
144
  csnPath.push( _prop );
114
145
  for (const name of Object.getOwnPropertyNames( dict )) {
115
- const skip = (options && options.allowArtifact && !options.allowArtifact(dict[name], name)) ||
116
- (options && options.skipArtifact && options.skipArtifact(dict[name], name)) ||
117
- (options && options.skip && options.skip.includes(dict[name].kind)) ||
118
- false;
119
- if (!skip) {
146
+ if (!isArtifactSkipped( dict, name )) {
120
147
  artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
121
- standard( dict, name, dict[name] );
148
+ dictEntry( dict, name, dict[name] );
122
149
  }
123
150
  }
124
151
  if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
@@ -126,6 +153,20 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
126
153
  csnPath.pop();
127
154
  }
128
155
 
156
+ /**
157
+ * Whether the given artifact `dict[name]` is skipped via options.
158
+ *
159
+ * @param {object} dict
160
+ * @param {string} name
161
+ * @returns {boolean}
162
+ */
163
+ function isArtifactSkipped( dict, name ) {
164
+ return options && ((options.allowArtifact && !options.allowArtifact(dict[name], name)) ||
165
+ (options.skipArtifact && options.skipArtifact(dict[name], name)) ||
166
+ (options.skip?.includes(dict[name].kind))) ||
167
+ false;
168
+ }
169
+
129
170
  /**
130
171
  * Keep looping through the pathRef - because in a .ref we can have .args and .where
131
172
  *
@@ -173,7 +214,7 @@ function applyTransformations( csn, customTransformers = {}, artifactTransformer
173
214
  if (options.skipIgnore === undefined)
174
215
  options.skipIgnore = true;
175
216
 
176
- if (csn && csn.definitions)
217
+ if (csn?.definitions)
177
218
  return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, options);
178
219
  return csn;
179
220
  }
@@ -230,12 +271,12 @@ module.exports = {
230
271
 
231
272
  /**
232
273
  * @typedef {object} applyTransformationsOptions
233
- * @property {(artifact, name) => boolean} [allowArtifact] to only allow certain artifacts
234
- * @property {(artifact, name) => boolean} [skipArtifact] to skip certain artifacts
274
+ * @property {(artifact: CSN.Artifact, name: string) => boolean} [allowArtifact] to only allow certain artifacts
275
+ * @property {(artifact: CSN.Artifact, name: string) => boolean} [skipArtifact] to skip certain artifacts
235
276
  * @property {boolean} [drillRef] whether to drill into infix/args
236
277
  * @property {string[]} [skip] skip definitions from certain kind
237
278
  * @property {object} [skipStandard] stop drill-down on certain "standard" props
238
279
  * @property {object} [skipDict] stop drill-down on certain "dictionary" props
239
- * @property {boolean} [skipIgnore=true] Whether to skip _ignore elements or not
280
+ * @property {boolean} [skipIgnore=true] Whether to skip $ignore elements or not
240
281
  * @property {boolean} [directDict=false] Implicitly set via applyTransformationsOnDictionary
241
282
  */