@sap/cds-compiler 2.4.4 → 2.10.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 (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. package/lib/utils/string.js +0 -17
@@ -7,7 +7,7 @@ const {
7
7
  forEachDefinition, getResultingName,
8
8
  } = require('../model/csnUtils');
9
9
  const {
10
- renderFunc, processExprArray, cdsToSqlTypes,
10
+ renderFunc, beautifyExprArray, cdsToSqlTypes, getHanaComment, hasHanaComment,
11
11
  } = require('./utils/common');
12
12
  const {
13
13
  renderReferentialConstraint, getIdentifierUtils,
@@ -152,6 +152,24 @@ function toSqlDdl(csn, options) {
152
152
  .join(', ');
153
153
  return primaryKeys && `PRIMARY KEY(${primaryKeys})`;
154
154
  },
155
+ /*
156
+ Render entity-comment modifications as HANA SQL.
157
+ */
158
+ alterEntityComment(tableName, comment) {
159
+ return [ `COMMENT ON TABLE ${tableName} IS ${render.comment(comment)};` ];
160
+ },
161
+ /*
162
+ Render column-comment modifications as HANA SQL.
163
+ */
164
+ alterColumnComment(tableName, columnName, comment) {
165
+ return [ `COMMENT ON COLUMN ${tableName}.${columnName} IS ${render.comment(comment)};` ];
166
+ },
167
+ /*
168
+ Render comment string.
169
+ */
170
+ comment(comment) {
171
+ return comment && `'${comment.replace(/'/g, '\'\'')}'` || 'NULL';
172
+ },
155
173
  /*
156
174
  Concatenate multiple statements which are to be treated as one by the API caller.
157
175
  */
@@ -211,8 +229,9 @@ function toSqlDdl(csn, options) {
211
229
  for (const extension of options && options.testMode ? sortCsn(csn.extensions) : csn.extensions) {
212
230
  if (extension.extend) {
213
231
  const artifactName = extension.extend;
214
- const env = { indent: '' };
215
- renderArtifactExtensionInto(artifactName, csn.definitions[artifactName], extension, resultObj, env);
232
+ const _artifact = csn.definitions[artifactName];
233
+ const env = { indent: '', _artifact };
234
+ renderArtifactExtensionInto(artifactName, _artifact, extension, resultObj, env);
216
235
  }
217
236
  }
218
237
  }
@@ -223,7 +242,8 @@ function toSqlDdl(csn, options) {
223
242
  for (const migration of options && options.testMode ? sortCsn(csn.migrations) : csn.migrations) {
224
243
  if (migration.migrate) {
225
244
  const artifactName = migration.migrate;
226
- const env = { indent: '' };
245
+ const _artifact = csn.definitions[artifactName];
246
+ const env = { indent: '', _artifact };
227
247
  renderArtifactMigrationInto(artifactName, migration, resultObj, env);
228
248
  }
229
249
  }
@@ -367,10 +387,30 @@ function toSqlDdl(csn, options) {
367
387
  return def.old.type === def.new.type &&
368
388
  [ 'length', 'precision', 'scale' ].some(param => def.new[param] < def.old[param]);
369
389
  }
390
+ function getEltStr(defVariant, eltName) {
391
+ return defVariant.target
392
+ ? renderAssociationElement(eltName, defVariant, env)
393
+ : renderElement(artifactName, eltName, defVariant, null, null, env);
394
+ }
395
+ function getEltStrNoProp(defVariant, prop, eltName) {
396
+ const defNoProp = Object.assign({}, defVariant);
397
+ delete defNoProp[prop];
398
+ return getEltStr(defNoProp, eltName);
399
+ }
370
400
 
371
401
  const tableName = renderArtifactName(artifactName);
372
402
 
373
- // Drop column (unsupported in sqlite)
403
+ // Change entity properties
404
+ if (migration.properties) {
405
+ for (const [ prop, def ] of Object.entries(migration.properties)) {
406
+ if (prop === 'doc') {
407
+ const alterComment = render.alterEntityComment(tableName, def.new);
408
+ addMigration(resultObj, artifactName, false, alterComment);
409
+ }
410
+ }
411
+ }
412
+
413
+ // Drop columns (unsupported in sqlite)
374
414
  if (migration.remove) {
375
415
  const entries = Object.entries(migration.remove);
376
416
  if (entries.length) {
@@ -389,12 +429,28 @@ function toSqlDdl(csn, options) {
389
429
  }
390
430
  }
391
431
 
392
- // Change column type (unsupported in sqlite)
432
+ // Change column types (unsupported in sqlite)
393
433
  if (migration.change) {
394
434
  changeElementsDuplicateChecker.addArtifact(tableName, undefined, artifactName);
395
435
  for (const [ eltName, def ] of Object.entries(migration.change)) {
396
436
  const sqlId = quoteSqlId(eltName);
397
437
  changeElementsDuplicateChecker.addElement(sqlId, undefined, eltName);
438
+
439
+ const eltStrOld = getEltStr(def.old, eltName);
440
+ const eltStrNew = getEltStr(def.new, eltName);
441
+ if (eltStrNew === eltStrOld)
442
+ return; // Prevent spurious migrations, where the column DDL does not change.
443
+
444
+ if (def.old.doc !== def.new.doc) {
445
+ const eltStrOldNoDoc = getEltStrNoProp(def.old, 'doc', eltName);
446
+ const eltStrNewNoDoc = getEltStrNoProp(def.new, 'doc', eltName);
447
+ if (eltStrOldNoDoc === eltStrNewNoDoc) { // only `doc` changed
448
+ const alterComment = render.alterColumnComment(tableName, sqlId, def.new.doc);
449
+ addMigration(resultObj, artifactName, false, alterComment);
450
+ continue;
451
+ }
452
+ }
453
+
398
454
  if (options.sqlChangeMode === 'drop' || def.old.target || def.new.target || reducesTypeSize(def)) {
399
455
  // Lossy change because either an association is removed and/or added, or the type size is reduced.
400
456
  // Drop old element and re-add it in its new shape.
@@ -408,8 +464,7 @@ function toSqlDdl(csn, options) {
408
464
  }
409
465
  else {
410
466
  // Lossless change: no associations directly affected, no size reduction.
411
- const eltStr = renderElement(artifactName, eltName, def.new, null, null, env);
412
- addMigration(resultObj, artifactName, false, render.alterColumns(tableName, eltStr));
467
+ addMigration(resultObj, artifactName, false, render.alterColumns(tableName, eltStrNew));
413
468
  }
414
469
  }
415
470
  }
@@ -425,6 +480,7 @@ function toSqlDdl(csn, options) {
425
480
  * @param {object} env Render environment
426
481
  */
427
482
  function renderEntityInto(artifactName, art, resultObj, env) {
483
+ env._artifact = art;
428
484
  const childEnv = increaseIndent(env);
429
485
  const hanaTc = art.technicalConfig && art.technicalConfig.hana;
430
486
  let result = '';
@@ -512,6 +568,9 @@ function toSqlDdl(csn, options) {
512
568
  if (options.toSql.dialect === 'hana')
513
569
  renderIndexesInto(art.technicalConfig && art.technicalConfig.hana.indexes, artifactName, resultObj, env);
514
570
 
571
+ if (options.toSql.dialect === 'hana' && hasHanaComment(art, options, art))
572
+ result += ` COMMENT '${getHanaComment(art)}'`;
573
+
515
574
  resultObj.hdbtable[artifactName] = result;
516
575
  }
517
576
 
@@ -610,6 +669,9 @@ function toSqlDdl(csn, options) {
610
669
  if (fzindex && options.toSql.dialect === 'hana')
611
670
  result += ` ${renderExpr(fzindex, env)}`;
612
671
 
672
+ if (options.toSql.dialect === 'hana' && hasHanaComment(elm, options, env._artifact))
673
+ result += ` COMMENT '${getHanaComment(elm)}'`;
674
+
613
675
  return result;
614
676
  }
615
677
 
@@ -991,8 +1053,13 @@ function toSqlDdl(csn, options) {
991
1053
  const viewName = renderArtifactName(artifactName);
992
1054
  definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art && art.$location, artifactName);
993
1055
  let result = `VIEW ${viewName}`;
1056
+
1057
+ if (options.toSql.dialect === 'hana' && hasHanaComment(art, options, art))
1058
+ result += ` COMMENT '${getHanaComment(art)}'`;
1059
+
994
1060
  result += renderParameterDefinitions(artifactName, art.params);
995
1061
  result += ` AS ${renderQuery(artifactName, getNormalizedQuery(art).query, env)}`;
1062
+
996
1063
  const childEnv = increaseIndent(env);
997
1064
  const associations = Object.keys(art.elements).filter(name => !!art.elements[name].target)
998
1065
  .map(name => renderAssociationElement(name, art.elements[name], childEnv))
@@ -1002,6 +1069,7 @@ function toSqlDdl(csn, options) {
1002
1069
  result += `${env.indent}\nWITH ASSOCIATIONS (\n${associations}\n`;
1003
1070
  result += `${env.indent})`;
1004
1071
  }
1072
+
1005
1073
  return result;
1006
1074
  }
1007
1075
 
@@ -1020,8 +1088,15 @@ function toSqlDdl(csn, options) {
1020
1088
  const p = params[pn];
1021
1089
  if (p.notNull === true || p.notNull === false)
1022
1090
  info(null, [ 'definitions', artifactName, 'params', pn ], 'Not Null constraints on SQL view parameters are not allowed and are ignored');
1023
-
1024
- let pstr = `IN ${prepareIdentifier(pn)} ${renderTypeReference(artifactName, pn, p)}`;
1091
+ // do not quote parameter identifiers for naming mode "quoted" / "hdbcds"
1092
+ // this would be an incompatible change, as non-uppercased, quoted identifiers
1093
+ // are rejected by the HANA compiler.
1094
+ let pIdentifier;
1095
+ if (options.toSql.names === 'quoted' || options.toSql.names === 'hdbcds')
1096
+ pIdentifier = prepareIdentifier(pn);
1097
+ else
1098
+ pIdentifier = quoteSqlId(pn);
1099
+ let pstr = `IN ${pIdentifier} ${renderTypeReference(artifactName, pn, p)}`;
1025
1100
  if (p.default)
1026
1101
  pstr += ` DEFAULT ${renderExpr(p.default)}`;
1027
1102
 
@@ -1268,7 +1343,8 @@ function toSqlDdl(csn, options) {
1268
1343
  function renderExpr(x, env, inline = true, nestedExpr = false) {
1269
1344
  // Compound expression
1270
1345
  if (Array.isArray(x)) {
1271
- return processExprArray(x, renderExpr, env, inline, nestedExpr);
1346
+ const tokens = x.map(item => renderExpr(item, env, inline, nestedExpr));
1347
+ return beautifyExprArray(tokens);
1272
1348
  }
1273
1349
  else if (typeof x === 'object' && x !== null) {
1274
1350
  if (nestedExpr && x.cast && x.cast.type)
@@ -18,6 +18,9 @@ const {
18
18
  hasValidSkipOrExists, forEachDefinition, getNamespace, getUnderscoredName,
19
19
  } = require('../../model/csnUtils');
20
20
 
21
+ const { implicitAs } = require('../../model/csnRefs');
22
+
23
+
21
24
  /**
22
25
  * Render the given function
23
26
  *
@@ -50,19 +53,14 @@ function funcWithoutParen( node, dialect ) {
50
53
  }
51
54
 
52
55
  /**
53
- * Process an expression array, rendering each subexpression and joining them appropriately
56
+ * Process already rendered expression parts by joining them nicely
54
57
  *
55
- * @param {Array} xpr Expression array
56
- * @param {Function} renderExpr Function to render expressions
57
- * @param {CdlRenderEnvironment} env Environment for rendering
58
- * @param {boolean} inline Wether to render the expression inline
59
- * @param {boolean} inExpr Wether to render as if a subexpression
58
+ * @param {Array} tokens Array of expression tokens
60
59
  *
61
60
  * @returns {string} The rendered xpr
62
61
  */
63
- function processExprArray(xpr, renderExpr, env, inline, inExpr) {
62
+ function beautifyExprArray(tokens) {
64
63
  // Simply concatenate array parts with spaces (with a tiny bit of beautification)
65
- const tokens = xpr.map(item => renderExpr(item, env, inline, inExpr));
66
64
  let result = '';
67
65
  for (let i = 0; i < tokens.length; i++) {
68
66
  result += tokens[i];
@@ -283,6 +281,26 @@ const cdsToSqlTypes = {
283
281
  },
284
282
  };
285
283
 
284
+ /**
285
+ * Get the element matching the column
286
+ *
287
+ * @param {CSN.Elements} elements Elements of a query
288
+ * @param {CSN.Column} column Column from the same query
289
+ * @returns {CSN.Element}
290
+ */
291
+ function findElement(elements, column) {
292
+ if (!elements)
293
+ return undefined;
294
+ if (column.as)
295
+ return elements[column.as];
296
+ else if (column.ref)
297
+ return elements[implicitAs(column.ref)];
298
+ else if (column.func)
299
+ return elements[column.func];
300
+
301
+ return undefined;
302
+ }
303
+
286
304
  /**
287
305
  * If there is a context A and a context A.B.C without a definition A.B, create an
288
306
  * intermediate context A.B to keep the context hierarchy intact.
@@ -313,6 +331,30 @@ function addIntermediateContexts(csn, killList) {
313
331
  }
314
332
  }
315
333
 
334
+ /**
335
+ * Check wether the given artifact or element has a comment that needs to be rendered.
336
+ * Things annotated with @cds.persistence.journal (for HANA SQL), should not get a comment.
337
+ *
338
+ * @param {CSN.Artifact} obj
339
+ * @param {CSN.Options} options To check for `disableHanaComments`
340
+ * @returns {boolean}
341
+ */
342
+ function hasHanaComment(obj, options) {
343
+ return !options.disableHanaComments && typeof obj.doc === 'string';
344
+ }
345
+ /**
346
+ * Return the comment of the given artifact or element.
347
+ * Uses the first block (everything up to the first empty line (double \n)).
348
+ * Remove leading/trailing whitespace.
349
+ *
350
+ * @param {CSN.Artifact|CSN.Element} obj
351
+ * @returns {string}
352
+ * @todo Warning/info to user?
353
+ */
354
+ function getHanaComment(obj) {
355
+ return obj.doc.split('\n\n')[0].trim().replace(/'/g, "''");
356
+ }
357
+
316
358
  /**
317
359
  * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
318
360
  *
@@ -331,10 +373,14 @@ function addIntermediateContexts(csn, killList) {
331
373
 
332
374
  module.exports = {
333
375
  renderFunc,
334
- processExprArray,
376
+ beautifyExprArray,
335
377
  getNamespace,
336
378
  getRealName,
337
379
  addIntermediateContexts,
338
380
  addContextMarkers,
339
381
  cdsToSqlTypes,
382
+ hasHanaComment,
383
+ getHanaComment,
384
+ findElement,
385
+ funcWithoutParen,
340
386
  };
@@ -20,9 +20,9 @@ const { smartId, delimitedId } = require('../../sql-identifier');
20
20
  function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint) {
21
21
  let quoteId;
22
22
  // for to.hana we can't utilize the sql identifier utils
23
- if (options.toHana) {
23
+ if (options.transformation === 'hdbcds') {
24
24
  quoteId = (id) => {
25
- if (options.toHana.names === 'plain')
25
+ if (options.sqlMapping === 'plain')
26
26
  return id.replace(/\./g, '_');
27
27
  return `"${id}"`;
28
28
  };
@@ -38,7 +38,7 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
38
38
  constraint.parentTable = constraint.parentTable.toUpperCase();
39
39
  }
40
40
 
41
- const renderAsHdbconstraint = options.toHana ||
41
+ const renderAsHdbconstraint = options.transformation === 'hdbcds' ||
42
42
  (options.toSql && options.toSql.src === 'hdi') ||
43
43
  (options.manageConstraints && options.manageConstraints.src === 'hdi');
44
44
 
@@ -51,7 +51,12 @@ const sqlDialects = {
51
51
  effectiveName: name => name.toUpperCase(),
52
52
  asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
53
53
  },
54
- // TODO: hdbcds for HANA CDS
54
+ hdbcds: {
55
+ regularRegex: /^[A-Za-z_][A-Za-z_0-9]*$/,
56
+ reservedWords: keywords.hdbcds,
57
+ effectiveName: name => name,
58
+ asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
59
+ },
55
60
  };
56
61
 
57
62
  function smartId( name, dialect ) {
File without changes
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { forEachDefinition, hasBoolAnnotation } = require('../../model/csnUtils');
3
+ const { forEachDefinition, hasAnnotationValue } = require('../../model/csnUtils');
4
4
  const { getTransformers } = require('../transformUtilsNew');
5
5
  const { setProp } = require('../../base/model');
6
6
 
@@ -31,7 +31,7 @@ function processAssertUnique(csn, options, error, info) {
31
31
  */
32
32
  function handleAssertUnique(artifact, artifactName) {
33
33
  // operate only on real entities that are not abstract
34
- if (artifact.abstract || (artifact.kind !== 'entity' || (artifact.query || artifact.projection) && !hasBoolAnnotation(artifact, '@cds.persistence.table')))
34
+ if (artifact.abstract || (artifact.kind !== 'entity' || (artifact.query || artifact.projection) && !hasAnnotationValue(artifact, '@cds.persistence.table')))
35
35
  return;
36
36
  const constraintXrefs = Object.create(null);
37
37
  const constraintDict = Object.create(null);
@@ -260,7 +260,7 @@ function processAssertUnique(csn, options, error, info) {
260
260
  * If the output format is SQL, the toSql renderer is responsible
261
261
  * to render the table constraints from the constraint dictionary.
262
262
  *
263
- * If options.toHana, no path flattening is done and association
263
+ * If options.transformation === 'hdbcds', no path flattening is done and association
264
264
  * paths are replaced with the foreign key paths by simply
265
265
  * concatenating the foreign key paths (available in element.keys).
266
266
  *
@@ -279,11 +279,10 @@ function rewriteUniqueConstraints(csn, options, pathDelimiter) {
279
279
  * @param {CSN.Artifact} artifact
280
280
  */
281
281
  function rewrite(artifact) {
282
- const naming = options.forHana.names || options.toSql.names;
283
282
  if (artifact.$tableConstraints && artifact.$tableConstraints.unique) {
284
283
  const uniqueConstraints = artifact.$tableConstraints.unique;
285
284
  // it's safe to add the tc here
286
- if (options.toHana) {
285
+ if (options.transformation === 'hdbcds') {
287
286
  if (!artifact.technicalConfig)
288
287
  artifact.technicalConfig = Object.create(null);
289
288
 
@@ -302,12 +301,12 @@ function rewriteUniqueConstraints(csn, options, pathDelimiter) {
302
301
  c.forEach((cpath) => {
303
302
  // If 'toSql' or 'toHana' and naming !== 'hdbcds'
304
303
  // concatenate path refs with appropriate delimiter
305
- if (!options.toHana || (options.toHana && naming !== 'hdbcds'))
304
+ if (options.transformation !== 'hdbcds' || (options.transformation === 'hdbcds' && options.sqlMapping !== 'hdbcds'))
306
305
  cpath.ref = [ cpath.ref.map(p => p.id).join( pathDelimiter ) ];
307
306
 
308
307
  // Foreign key substitution
309
308
  if (cpath._art.target) {
310
- if (!options.toHana || (options.toHana && naming !== 'hdbcds')) {
309
+ if (options.transformation !== 'hdbcds' || (options.transformation === 'hdbcds' && options.sqlMapping !== 'hdbcds')) {
311
310
  // read out new association and use $generatedFieldName
312
311
  // cpath._art still refers to the assoc definition
313
312
  // before the A2J transformation. This assoc
@@ -332,7 +331,7 @@ function rewriteUniqueConstraints(csn, options, pathDelimiter) {
332
331
  uniqueConstraints[uniqueConstraint] = rewrittenPaths;
333
332
 
334
333
  // now add the index for HANA CDS
335
- if (options.toHana) {
334
+ if (options.transformation === 'hdbcds') {
336
335
  const index = [ 'unique', 'index', { ref: [ uniqueConstraint ] }, 'on', '(' ];
337
336
  let i = 0;
338
337
  for (const constraint of rewrittenPaths) {
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { forEachDefinition } = require('../../base/model');
4
- const { forAllElements, hasBoolAnnotation, getResultingName } = require('../../model/csnUtils');
4
+ const { forAllElements, hasAnnotationValue, getResultingName } = require('../../model/csnUtils');
5
5
  const { csnRefs } = require('../../model/csnRefs');
6
6
 
7
7
  const COMPOSITION = 'cds.Composition';
@@ -12,7 +12,6 @@ const ASSOCIATION = 'cds.Association';
12
12
  *
13
13
  * @param {CSN.Model} csn
14
14
  * @param {CSN.Options} options are used to modify the validate / enforced flag on the constraints
15
- *
16
15
  */
17
16
  function createReferentialConstraints(csn, options) {
18
17
  let validated = true;
@@ -46,7 +45,7 @@ function createReferentialConstraints(csn, options) {
46
45
  if (element.type === ASSOCIATION ||
47
46
  element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
48
47
  associations.push(() => {
49
- foreignKeyConstraintForAssociation(element, elements, path.concat([ elementName ]));
48
+ foreignKeyConstraintForAssociation(element, elements, path.concat([ elementName ]), elementName);
50
49
  });
51
50
  }
52
51
  }
@@ -81,12 +80,14 @@ function createReferentialConstraints(csn, options) {
81
80
  // mark each dependent key referenced in the on-condition (in target entity)
82
81
  const dependentKeys = Array.from(elementsOfTargetSide(onCondition, csn.definitions[composition.target].elements));
83
82
  const parentKeys = Array.from(elementsOfSourceSide(onCondition, elements));
83
+ const { backlinkName } = composition.$selfOnCondition || {};
84
84
  // sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
85
- if (dependentKeys.length === parentKeys.length)
86
- attachConstraintsToDependentKeys(dependentKeys, parentKeys, path[path.length - 3], path, 'CASCADE');
85
+ // also: no constraints for compositions of many w/o backlink
86
+ if (dependentKeys.length === parentKeys.length && backlinkName)
87
+ attachConstraintsToDependentKeys(dependentKeys, parentKeys, path[path.length - 3], path, backlinkName, 'CASCADE');
87
88
  }
88
89
  }
89
- else if (!onCondition && composition.keys) {
90
+ else if (!onCondition && composition.keys.length > 0) {
90
91
  throw new Error('Please debug me, an on-condition was expected here, but only found keys');
91
92
  }
92
93
  }
@@ -98,8 +99,9 @@ function createReferentialConstraints(csn, options) {
98
99
  * @param {CSN.Association} association for that a constraint should be generated
99
100
  * @param {CSN.Elements} elements of parent entity.
100
101
  * @param {CSN.Path} path
102
+ * @param {string} assocName passed through as proper constraint suffix
101
103
  */
102
- function foreignKeyConstraintForAssociation(association, elements, path) {
104
+ function foreignKeyConstraintForAssociation(association, elements, path, assocName) {
103
105
  const associationTarget = csn.definitions[association.target];
104
106
  if (skipConstraintGeneration(associationTarget, association))
105
107
  return;
@@ -112,9 +114,9 @@ function createReferentialConstraints(csn, options) {
112
114
  const parentKeys = Array.from(elementsOfTargetSide(onCondition, associationTarget.elements));
113
115
  // sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
114
116
  if (dependentKeys.length === parentKeys.length)
115
- attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path);
117
+ attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path, assocName);
116
118
  }
117
- else if (!onCondition && association.keys) {
119
+ else if (!onCondition && association.keys.length > 0) {
118
120
  throw new Error('Please debug me, an on-condition was expected here, but only found keys');
119
121
  }
120
122
  }
@@ -128,9 +130,10 @@ function createReferentialConstraints(csn, options) {
128
130
  * @param {Array} parentKeys array holding parent keys in the format [['key1', 'value1'], [...], ...]
129
131
  * @param {CSN.PathSegment} parentTable the sql-table where the foreign key constraints will be pointing to
130
132
  * @param {CSN.Path} path
133
+ * @param {string | null} constraintIdentifierSuffix name of the association / the backlink association
131
134
  * @param {string} onDelete the on delete rule which should be applied. Default for associations is 'RESTRICT'
132
135
  */
133
- function attachConstraintsToDependentKeys(dependentKeys, parentKeys, parentTable, path, onDelete = 'RESTRICT') {
136
+ function attachConstraintsToDependentKeys(dependentKeys, parentKeys, parentTable, path, constraintIdentifierSuffix, onDelete = 'RESTRICT') {
134
137
  while (dependentKeys.length > 0) {
135
138
  const dependentKeyValuePair = dependentKeys.pop();
136
139
  const dependentKey = dependentKeyValuePair[1];
@@ -144,7 +147,7 @@ function createReferentialConstraints(csn, options) {
144
147
  parentKey: parentKeyName,
145
148
  parentTable,
146
149
  sourceAssociation: path[path.length - 1],
147
- nameSuffix: dependentKey['@odata.foreignKey4'] || 'up_',
150
+ nameSuffix: constraintIdentifierSuffix || 'up_',
148
151
  onDelete,
149
152
  validated,
150
153
  enforced,
@@ -220,8 +223,8 @@ function createReferentialConstraints(csn, options) {
220
223
  * @returns {boolean}
221
224
  */
222
225
  function skipConstraintGeneration(parent, element) {
223
- if (hasBoolAnnotation(element, '@assert.integrity', false) ||
224
- hasBoolAnnotation(element, '@cds.persistency.assert.integrity', false)) {
226
+ if (hasAnnotationValue(element, '@assert.integrity', false) ||
227
+ hasAnnotationValue(element, '@cds.persistency.assert.integrity', false)) {
225
228
  // in case of managed composition, the 'up_' link should not result in a constraint
226
229
  const target = csn.definitions[element.target];
227
230
  const { up_ } = target.elements;
@@ -230,16 +233,18 @@ function createReferentialConstraints(csn, options) {
230
233
  return true;
231
234
  }
232
235
 
233
- if (element.$skipReferentialConstraintForUp_)
236
+ if (element.$skipReferentialConstraintForUp_) {
237
+ delete element.$skipReferentialConstraintForUp_;
234
238
  return true;
239
+ }
235
240
 
236
- if (hasBoolAnnotation(parent, '@cds.persistence.skip', true) ||
237
- hasBoolAnnotation(parent, '@cds.persistence.exists', true) ||
241
+ if (hasAnnotationValue(parent, '@cds.persistence.skip', true) ||
242
+ hasAnnotationValue(parent, '@cds.persistence.exists', true) ||
238
243
  parent.query)
239
244
  return true;
240
245
 
241
246
  // '@cds.persistency.assert.integrity: true' supersedes global switch
242
- if (!hasBoolAnnotation(element, '@cds.persistency.assert.integrity', true) && options.forHana.skipDbConstraints)
247
+ if (!hasAnnotationValue(element, '@cds.persistency.assert.integrity', true) && options.forHana.skipDbConstraints)
243
248
  return true;
244
249
 
245
250
  return false;
@@ -352,11 +357,21 @@ function createReferentialConstraints(csn, options) {
352
357
  * The constraints will thus be generated in the entity containing the composition and not in the target entity.
353
358
  *
354
359
  * @param {CSN.Composition} composition the composition which might be treated like an association
355
- * @returns {any} true if the composition should be treated as an association in regards to foreign key constraints
360
+ * @returns {boolean} true if the composition should be treated as an association in regards to foreign key constraints
356
361
  */
357
362
  function treatCompositionLikeAssociation(composition) {
358
- const { min, max } = composition.cardinality || {};
359
- return (!min && !max || max === 1) && composition.keys;
363
+ return Boolean((isToOne(composition) && !composition.$selfOnCondition) || composition.keys);
364
+ }
365
+
366
+ /**
367
+ * returns true if the association/composition has a max target cardinality of one
368
+ *
369
+ * @param {CSN.Element} assocOrComposition
370
+ * @returns {boolean}
371
+ */
372
+ function isToOne(assocOrComposition) {
373
+ const { min, max } = assocOrComposition.cardinality || {};
374
+ return !min && !max || max === 1;
360
375
  }
361
376
  }
362
377