@sap/cds-compiler 4.0.2 → 4.2.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 (101) hide show
  1. package/CHANGELOG.md +200 -5
  2. package/bin/cdsc.js +18 -15
  3. package/doc/CHANGELOG_BETA.md +16 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +33 -13
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +25 -25
  8. package/lib/base/location.js +6 -7
  9. package/lib/base/message-registry.js +123 -42
  10. package/lib/base/messages.js +18 -10
  11. package/lib/base/model.js +43 -10
  12. package/lib/checks/defaultValues.js +6 -6
  13. package/lib/checks/elements.js +11 -10
  14. package/lib/checks/foreignKeys.js +0 -5
  15. package/lib/checks/manyNavigations.js +33 -0
  16. package/lib/checks/onConditions.js +22 -14
  17. package/lib/checks/queryNoDbArtifacts.js +132 -73
  18. package/lib/checks/selectItems.js +4 -55
  19. package/lib/checks/sql-snippets.js +15 -4
  20. package/lib/checks/types.js +3 -3
  21. package/lib/checks/utils.js +4 -3
  22. package/lib/checks/validator.js +3 -1
  23. package/lib/compiler/.eslintrc.json +2 -1
  24. package/lib/compiler/assert-consistency.js +71 -40
  25. package/lib/compiler/base.js +7 -2
  26. package/lib/compiler/builtins.js +40 -41
  27. package/lib/compiler/checks.js +415 -367
  28. package/lib/compiler/classes.js +62 -0
  29. package/lib/compiler/cycle-detector.js +9 -9
  30. package/lib/compiler/define.js +124 -90
  31. package/lib/compiler/extend.js +115 -88
  32. package/lib/compiler/finalize-parse-cdl.js +26 -25
  33. package/lib/compiler/generate.js +57 -49
  34. package/lib/compiler/index.js +56 -56
  35. package/lib/compiler/kick-start.js +10 -7
  36. package/lib/compiler/moduleLayers.js +1 -1
  37. package/lib/compiler/populate.js +180 -144
  38. package/lib/compiler/propagator.js +10 -9
  39. package/lib/compiler/resolve.js +321 -246
  40. package/lib/compiler/shared.js +812 -433
  41. package/lib/compiler/tweak-assocs.js +114 -50
  42. package/lib/compiler/utils.js +241 -46
  43. package/lib/edm/.eslintrc.json +40 -1
  44. package/lib/edm/annotations/genericTranslation.js +721 -707
  45. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  46. package/lib/edm/csn2edm.js +389 -378
  47. package/lib/edm/edm.js +679 -770
  48. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  49. package/lib/edm/edmInboundChecks.js +29 -27
  50. package/lib/edm/edmPreprocessor.js +689 -648
  51. package/lib/edm/edmUtils.js +279 -300
  52. package/lib/gen/Dictionary.json +34 -10
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +1 -1
  55. package/lib/gen/languageParser.js +2857 -2856
  56. package/lib/json/from-csn.js +77 -51
  57. package/lib/json/to-csn.js +15 -15
  58. package/lib/language/antlrParser.js +2 -1
  59. package/lib/language/genericAntlrParser.js +52 -43
  60. package/lib/language/language.g4 +61 -64
  61. package/lib/language/multiLineStringParser.js +2 -0
  62. package/lib/main.d.ts +65 -0
  63. package/lib/model/csnRefs.js +37 -19
  64. package/lib/model/csnUtils.js +51 -18
  65. package/lib/model/revealInternalProperties.js +30 -22
  66. package/lib/modelCompare/compare.js +149 -41
  67. package/lib/modelCompare/utils/filter.js +55 -25
  68. package/lib/optionProcessor.js +21 -9
  69. package/lib/render/manageConstraints.js +20 -17
  70. package/lib/render/toCdl.js +63 -23
  71. package/lib/render/toHdbcds.js +2 -2
  72. package/lib/render/toRename.js +4 -9
  73. package/lib/render/toSql.js +82 -35
  74. package/lib/render/utils/common.js +11 -9
  75. package/lib/render/utils/unique.js +52 -0
  76. package/lib/transform/db/applyTransformations.js +62 -21
  77. package/lib/transform/db/assertUnique.js +7 -8
  78. package/lib/transform/db/associations.js +2 -2
  79. package/lib/transform/db/cdsPersistence.js +9 -9
  80. package/lib/transform/db/constraints.js +47 -17
  81. package/lib/transform/db/expansion.js +138 -68
  82. package/lib/transform/db/flattening.js +98 -30
  83. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  84. package/lib/transform/db/temporal.js +1 -1
  85. package/lib/transform/db/transformExists.js +8 -7
  86. package/lib/transform/db/views.js +73 -33
  87. package/lib/transform/draft/db.js +11 -9
  88. package/lib/transform/draft/odata.js +1 -1
  89. package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
  90. package/lib/transform/forRelationalDB.js +148 -136
  91. package/lib/transform/localized.js +92 -54
  92. package/lib/transform/odata/toFinalBaseType.js +3 -3
  93. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
  94. package/lib/transform/translateAssocsToJoins.js +14 -28
  95. package/lib/utils/file.js +7 -7
  96. package/lib/utils/moduleResolve.js +210 -121
  97. package/lib/utils/objectUtils.js +1 -1
  98. package/package.json +5 -5
  99. package/share/messages/check-proper-type-of.md +1 -1
  100. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  101. 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 ] });
@@ -303,6 +303,19 @@ function csnToCdl( csn, options ) {
303
303
  * @return {string}
304
304
  */
305
305
  function renderAnnotateStatement( ext, env ) {
306
+ // Special case: Super annotate has both "returns" and "elements".
307
+ // Render as separate `annotate`s, but keep the order.
308
+ if (ext.elements && ext.returns) {
309
+ const [ , second ] = Object.keys(ext).filter(key => key === 'elements' || key === 'returns');
310
+
311
+ // The first of 'elements' or 'returns' gets all other properties as well.
312
+ // The second only gets one property (itself).
313
+ let result = renderAnnotateStatement({ ...ext, [second]: undefined }, env);
314
+ result += renderAnnotateStatement({ annotate: ext.annotate, [second]: ext[second] }, env);
315
+
316
+ return result;
317
+ }
318
+
306
319
  // Top-level annotations of the artifact
307
320
  let result = renderAnnotationAssignmentsAndDocComment(ext, env);
308
321
  // Note: Not renderDefinitionReference, because we don't care if there
@@ -512,8 +525,15 @@ function csnToCdl( csn, options ) {
512
525
  result += renderParameters(art, env);
513
526
  if (art.includes)
514
527
  result += renderIncludes(art.includes, env);
515
- result += ` ${renderElements(art, env)}`;
528
+
529
+ if (art.elements)
530
+ result += ` ${renderElements(art, env)}`;
531
+ else if (art.actions)
532
+ // if there are no elements, but actions, CDL syntax requires braces.
533
+ result += ' { }';
534
+
516
535
  result += `${renderActionsAndFunctions(art, env)};\n`;
536
+
517
537
  return result;
518
538
  }
519
539
 
@@ -565,7 +585,7 @@ function csnToCdl( csn, options ) {
565
585
  * Returns the resulting source string.
566
586
  *
567
587
  * @param {string} elementName
568
- * @param {CSN.Element|CSN.Enum} element
588
+ * @param {CSN.Element} element
569
589
  * @param {CdlRenderEnvironment} env
570
590
  */
571
591
  function renderElement( elementName, element, env ) {
@@ -1037,7 +1057,7 @@ function csnToCdl( csn, options ) {
1037
1057
  result += `${env.indent}}`;
1038
1058
  }
1039
1059
 
1040
- if (isLeadingQuery)
1060
+ if (isLeadingQuery && query.actions)
1041
1061
  result += renderActionsAndFunctions(query, env);
1042
1062
 
1043
1063
  if (select.where)
@@ -1148,14 +1168,14 @@ function csnToCdl( csn, options ) {
1148
1168
  */
1149
1169
  function renderActionsAndFunctions( art, env ) {
1150
1170
  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
-
1171
+ if (art.actions) {
1172
+ const childEnv = env.withIncreasedIndent();
1173
+ for (const name in art.actions)
1174
+ result += renderActionOrFunction(name, art.actions[name], childEnv.withSubPath([ 'actions', name ]));
1175
+ result = (result === '')
1176
+ ? ' actions { }'
1177
+ : ` actions {\n${result}${env.indent}}`;
1178
+ }
1159
1179
  return result;
1160
1180
  }
1161
1181
 
@@ -1229,14 +1249,31 @@ function csnToCdl( csn, options ) {
1229
1249
  function renderTypeOrAnnotation( artifactName, art, env, artType ) {
1230
1250
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
1231
1251
  result += `${env.indent + (artType || art.$syntax || art.kind )} ${renderArtifactName(artifactName, env)}`;
1232
- if (art.includes)
1252
+
1253
+ const type = renderTypeReferenceAndProps(art, env);
1254
+ const isDirectStruct = type?.startsWith('{');
1255
+ if (art.includes?.length && isDirectStruct)
1256
+ // We can only render includes, if the type is directly structured. Otherwise, we would
1257
+ // render e.g. `type T : Include : T2;`, which is invalid. We use `extend` in such cases.
1233
1258
  result += renderIncludes(art.includes, env);
1234
1259
 
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)}`;
1260
+ if (type) {
1261
+ // For nicer output, no colon if unnamed structure is used.
1262
+ result += (!art.type && art.elements) ? ` ${type}` : ` : ${type}`;
1263
+ }
1264
+ else {
1265
+ msg.warning('syntax-missing-type', env.path, { name: artifactName },
1266
+ 'Missing type for definition $(NAME); can\'t be represented in CDL');
1267
+ }
1268
+
1239
1269
  result += ';\n';
1270
+
1271
+ if (art.includes?.length && !isDirectStruct) {
1272
+ // If we're not a directly structured type, render the `includes` as `extend`
1273
+ // statements directly below the type definition.
1274
+ result += renderExtendStatement(artifactName, { includes: art.includes }, env);
1275
+ }
1276
+
1240
1277
  return result;
1241
1278
  }
1242
1279
 
@@ -1247,8 +1284,9 @@ function csnToCdl( csn, options ) {
1247
1284
  *
1248
1285
  * @param {CSN.Artifact} artifact
1249
1286
  * @param {CdlRenderEnvironment} env
1250
- * @param {object} [config={}] - `typeRefOnly` Whether to only render type defs, no arrayed/structured/enum.
1251
- * - `noAnnoCollect` Do not collect annotations of sub-elements.
1287
+ * @param {object} [config={}]
1288
+ * @param {boolean} [config.typeRefOnly] Whether to only render type defs, no arrayed/structured/enum.
1289
+ * @param {boolean} [config.noAnnoCollect] Do not collect annotations of sub-elements.
1252
1290
  * @return {string}
1253
1291
  */
1254
1292
  function renderTypeReferenceAndProps( artifact, env, config = {} ) {
@@ -1310,8 +1348,9 @@ function csnToCdl( csn, options ) {
1310
1348
 
1311
1349
  if (artifact.notNull !== undefined && !artifact.on) // unmanaged associations can't be followed by "not null"
1312
1350
  result += renderNullability(artifact);
1313
- // DEFAULT not possible here.
1314
1351
 
1352
+ if (artifact.default && !artifact.on)
1353
+ result += ` default ${exprRenderer.renderExpr(artifact.default, env.withSubPath([ 'default' ]))}`;
1315
1354
  return result;
1316
1355
  }
1317
1356
 
@@ -1814,7 +1853,7 @@ function csnToCdl( csn, options ) {
1814
1853
  // Take the annotation assignment apart into <nameBeforeVariant>#<variantAndRest>
1815
1854
  const parts = name.split('#');
1816
1855
  const nameBeforeVariant = parts[0];
1817
- const variant = parts[1];
1856
+ const variant = parts.length > 1 ? parts.slice(1).join('#') : undefined;
1818
1857
  const { parentheses } = config;
1819
1858
 
1820
1859
  let result = `${env.indent}@`;
@@ -1822,10 +1861,11 @@ function csnToCdl( csn, options ) {
1822
1861
  result += '(';
1823
1862
 
1824
1863
  result += quoteAnnotationPathIfRequired(nameBeforeVariant, env);
1825
- if (variant !== undefined)
1864
+ if (variant !== undefined) {
1826
1865
  // Unfortunately, the compiler does not allow `.@` after the first variant identifier,
1827
- // so we're back at simple paths.
1866
+ // nor multiple `#`, so we're back at simple paths that are possibly quoted.
1828
1867
  result += `#${quotePathIfRequired(variant, env)}`;
1868
+ }
1829
1869
  result += ` : ${renderAnnotationValue(anno, env)}`;
1830
1870
 
1831
1871
  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);
@@ -431,7 +459,7 @@ function toSqlDdl( csn, options ) {
431
459
  const eltStrOld = getEltStr(def.old, eltName, 'migration');
432
460
  const eltStrNew = getEltStr(def.new, eltName, 'migration');
433
461
  if (eltStrNew === eltStrOld)
434
- return; // Prevent spurious migrations, where the column DDL does not change.
462
+ continue; // Prevent spurious migrations, where the column DDL does not change.
435
463
 
436
464
  const annosIncompat = [];
437
465
  sqlSnippetAnnos
@@ -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;
@@ -1338,15 +1379,11 @@ function toSqlDdl( csn, options ) {
1338
1379
  * @returns {string} Rendered type
1339
1380
  */
1340
1381
  function renderBuiltinType( typeName ) {
1341
- const forHanaRenamesToEarly = {
1342
- 'cds.UTCDateTime': 'cds.DateTime',
1343
- 'cds.UTCTimestamp': 'cds.Timestamp',
1344
- 'cds.LocalDate': 'cds.Date',
1345
- 'cds.LocalTime': 'cds.Time',
1346
- };
1347
- const tName = forHanaRenamesToEarly[typeName] || typeName;
1348
1382
  const types = cdsToSqlTypes[options.sqlDialect];
1349
- return types && types[tName] || cdsToSqlTypes.standard[tName] || 'CHAR';
1383
+ const result = types && types[typeName] || cdsToSqlTypes.standard[typeName];
1384
+ if (!result && options.testMode)
1385
+ throw new CompilerAssertion(`Expected to find a type mapping for ${typeName}`);
1386
+ return result || 'CHAR';
1350
1387
  }
1351
1388
 
1352
1389
  /**
@@ -1466,9 +1503,8 @@ function toSqlDdl( csn, options ) {
1466
1503
  case 'hana':
1467
1504
  return 'CURRENT_TIMESTAMP';
1468
1505
  case 'h2':
1469
- return 'current_timestamp';
1470
1506
  case 'postgres':
1471
- return '(current_timestamp at time zone \'UTC\')';
1507
+ return 'current_timestamp';
1472
1508
  default:
1473
1509
  return quoteSqlId(x.ref[0]);
1474
1510
  }
@@ -1498,19 +1534,28 @@ function toSqlDdl( csn, options ) {
1498
1534
  if (options.sqlDialect === 'hana')
1499
1535
  return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1500
1536
  else if (options.sqlDialect === 'postgres')
1501
- return 'current_setting(\'CAP.APPLICATIONUSER\')';
1537
+ return 'current_setting(\'cap.applicationuser\')';
1502
1538
  else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
1503
1539
  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"');
1540
+ else if (options.sqlDialect === 'h2')
1541
+ return '@applicationuser';
1542
+
1543
+ if (!reportedMissingUserReplacement) {
1544
+ warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
1545
+ reportedMissingUserReplacement = true;
1546
+ }
1505
1547
  return '\'$user.id\'';
1506
1548
  }
1507
1549
  else if (x.ref[1] === 'locale') {
1508
1550
  if (options.sqlDialect === 'hana')
1509
1551
  return 'SESSION_CONTEXT(\'LOCALE\')';
1510
1552
  else if (options.sqlDialect === 'postgres')
1511
- return 'current_setting(\'CAP.LOCALE\')';
1553
+ return 'current_setting(\'cap.locale\')';
1512
1554
  else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
1513
1555
  return 'session_context( \'$user.locale\' )';
1556
+ else if (options.sqlDialect === 'h2')
1557
+ return '@locale';
1558
+
1514
1559
  return '\'en\''; // default language
1515
1560
  }
1516
1561
  // Basically: Second path step was invalid, do nothing - should not happen.
@@ -1542,8 +1587,9 @@ function toSqlDdl( csn, options ) {
1542
1587
  case 'hana':
1543
1588
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1544
1589
  case 'postgres':
1545
- return '(to_timestamp(current_setting(\'CAP.VALID_FROM\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
1590
+ return 'current_setting(\'cap.valid_from\')::timestamp';
1546
1591
  case 'h2':
1592
+ return '@valid_from';
1547
1593
  case 'plain':
1548
1594
  return 'current_timestamp';
1549
1595
  default:
@@ -1563,8 +1609,9 @@ function toSqlDdl( csn, options ) {
1563
1609
  case 'hana':
1564
1610
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1565
1611
  case 'postgres':
1566
- return '(to_timestamp(current_setting(\'CAP.VALID_TO\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
1612
+ return 'current_setting(\'cap.valid_to\')::timestamp';
1567
1613
  case 'h2':
1614
+ return '@valid_to';
1568
1615
  case 'plain':
1569
1616
  return 'current_timestamp';
1570
1617
  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
 
@@ -258,16 +258,14 @@ const cdsToSqlTypes = {
258
258
  'cds.DateTime': 'TIMESTAMP', // cds-compiler#2758
259
259
  'cds.Timestamp': 'TIMESTAMP',
260
260
  'cds.Boolean': 'BOOLEAN',
261
- 'cds.UUID': 'NVARCHAR', // changed to cds.String earlier
262
261
  // (TODO: do it later; TODO: why not CHAR or at least VARCHAR?)
262
+ 'cds.UUID': 'NVARCHAR', // changed to cds.String earlier
263
+ 'cds.hana.ST_POINT': 'CHAR', // CHAR is implicit fallback used in toSql - make it explicit here
264
+ 'cds.hana.ST_GEOMETRY': 'CHAR', // CHAR is implicit fallback used in toSql - make it explicit here
263
265
  },
264
266
  hana: {
265
267
  'cds.hana.SMALLDECIMAL': 'SMALLDECIMAL',
266
- 'cds.LocalDate': 'DATE',
267
- 'cds.LocalTime': 'TIME',
268
268
  'cds.DateTime': 'SECONDDATE',
269
- 'cds.UTCDateTime': 'SECONDDATE',
270
- 'cds.UTCTimestamp': 'TIMESTAMP',
271
269
  'cds.hana.ST_POINT': 'ST_POINT',
272
270
  'cds.hana.ST_GEOMETRY': 'ST_GEOMETRY',
273
271
  },
@@ -275,7 +273,7 @@ const cdsToSqlTypes = {
275
273
  'cds.Date': 'DATE_TEXT',
276
274
  'cds.Time': 'TIME_TEXT',
277
275
  'cds.Timestamp': 'TIMESTAMP_TEXT',
278
- 'cds.DateTime': 'TIMESTAMP_TEXT',
276
+ 'cds.DateTime': 'DATETIME_TEXT',
279
277
  'cds.Binary': 'BINARY_BLOB',
280
278
  'cds.hana.BINARY': 'BINARY_BLOB',
281
279
  'cds.hana.SMALLDECIMAL': 'SMALLDECIMAL',
@@ -310,6 +308,10 @@ const cdsToHdbcdsTypes = {
310
308
  'cds.Int16': 'cds.hana.SMALLINT',
311
309
  'cds.Int32': 'cds.Integer',
312
310
  'cds.Int64': 'cds.Integer64',
311
+ 'cds.Timestamp': 'cds.UTCTimestamp',
312
+ 'cds.DateTime': 'cds.UTCDateTime',
313
+ 'cds.Date': 'cds.LocalDate',
314
+ 'cds.Time': 'cds.LocalTime',
313
315
  };
314
316
 
315
317
  /**
@@ -360,7 +362,7 @@ function findElement( elements, column ) {
360
362
  function addIntermediateContexts( csn, killList ) {
361
363
  for (const artifactName in csn.definitions) {
362
364
  const artifact = csn.definitions[artifactName];
363
- if ((artifact.kind === 'context') && !artifact._ignore) {
365
+ if ((artifact.kind === 'context') && !artifact.$ignore) {
364
366
  // If context A.B.C and entity A exist, we still need generate context A_B.
365
367
  // But if no entity A exists, A.B is just a namespace.
366
368
  // 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
+ };