@sap/cds-compiler 2.5.2 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. package/lib/utils/string.js +0 -17
@@ -16,36 +16,40 @@ const ASSOCIATION = 'cds.Association';
16
16
  function createReferentialConstraints(csn, options) {
17
17
  let validated = true;
18
18
  let enforced = true;
19
- if (options.constraintsNotValidated)
19
+ if (options.integrityNotValidated)
20
20
  validated = false;
21
21
 
22
- if (options.constraintsNotEnforced)
22
+ if (options.integrityNotEnforced)
23
23
  enforced = false;
24
24
 
25
25
  const { inspectRef } = csnRefs(csn);
26
26
  // prepare the functions with the compositions and associations across all entities first
27
- // and execute it afterwards because compositions must be processed first
27
+ // and execute it afterwards.
28
+ // compositions must be processed first, as the <up_> links for them must result in `ON DELETE CASCADE`
28
29
  const compositions = [];
29
30
  const associations = [];
30
31
  forEachDefinition(csn, (artifact, artifactName) => {
31
- if (!artifact.query && artifact.kind === 'entity' ) {
32
+ if (!artifact.query && artifact.kind === 'entity') {
32
33
  forAllElements(artifact, artifactName, (parent, elements, path) => {
33
- // Step I: iterate compositions, enrich dependent keys (in target entity)
34
+ // Step I: iterate compositions, enrich dependent keys for <up_> association in target entity of composition
34
35
  for (const elementName in elements) {
35
36
  const element = elements[elementName];
36
- if (element.type === COMPOSITION && !treatCompositionLikeAssociation(element)) {
37
- compositions.push(() => {
38
- foreignKeyConstraintForComposition(element, parent, path.concat([ elementName ]));
37
+ if (element.type === COMPOSITION && element.$selfOnCondition) {
38
+ compositions.push({
39
+ fn: () => {
40
+ foreignKeyConstraintForUpLinkOfComposition(element, parent, path.concat([ elementName ]));
41
+ },
39
42
  });
40
43
  }
41
44
  }
42
45
  // Step II: iterate associations, enrich dependent keys (in entity containing the association)
43
46
  for (const elementName in elements) {
44
47
  const element = elements[elementName];
45
- if (element.type === ASSOCIATION ||
46
- element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
47
- associations.push(() => {
48
- foreignKeyConstraintForAssociation(element, elements, path.concat([ elementName ]));
48
+ if (element.keys && isToOne(element) && element.type === ASSOCIATION || element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
49
+ associations.push({
50
+ fn: () => {
51
+ foreignKeyConstraintForAssociation(element, path.concat([ elementName ]));
52
+ },
49
53
  });
50
54
  }
51
55
  }
@@ -53,39 +57,36 @@ function createReferentialConstraints(csn, options) {
53
57
  }
54
58
  });
55
59
  // create constraints on foreign keys
56
- compositions.forEach(fn => fn());
57
- associations.forEach(fn => fn());
60
+ // always process unmanaged first, up_ links must be flagged
61
+ // before they are processed
62
+ compositions.forEach(composition => composition.fn());
63
+ associations.forEach(association => association.fn());
64
+
58
65
  // Step III: Create the final referential constraints from all dependent key <-> parent key pairs stemming from the same $sourceAssociation
59
66
  forEachDefinition(csn, collectAndAttachReferentialConstraints);
60
67
 
61
-
62
68
  /**
63
- * Calculate referential constraints for dependent keys in target entity of cds.Composition.
64
- * The DELETE rule for a referential constraint stemming from a composition will be CASCADE.
65
- * A managed composition with a target cardinality of one, will be treated like a regular Association.
69
+ * Retrieve the <up_> link of an `cds.Composition` used in an on-condition like `$self = <comp>.<up_>`
70
+ * and calculate a foreign key constraint for this association if it is constraint compliant.
71
+ * The constraint will have an `ON DELETE CASCADE`.
66
72
  *
67
- * @param {CSN.Element} composition for that a constraint should be generated
73
+ * @param {CSN.Element} composition which might has the `$self = <comp>.<up_>` on-condition
68
74
  * @param {CSN.Artifact} parent artifact containing the composition
69
75
  * @param {CSN.Path} path
70
76
  */
71
- function foreignKeyConstraintForComposition(composition, parent, path) {
72
- if (skipConstraintGeneration(parent, composition))
77
+ function foreignKeyConstraintForUpLinkOfComposition(composition, parent, path) {
78
+ const dependent = csn.definitions[path[1]];
79
+ if (skipConstraintGeneration(parent, dependent, composition))
73
80
  return;
74
81
 
75
- const { elements } = parent;
76
82
  const onCondition = composition.on;
77
- if (onCondition) {
78
- if (hasConstraintCompliantOnCondition(composition, elements, path)) {
79
- // 1. cds.Composition has constraint compliant on-condition
80
- // mark each dependent key referenced in the on-condition (in target entity)
81
- const dependentKeys = Array.from(elementsOfTargetSide(onCondition, csn.definitions[composition.target].elements));
82
- const parentKeys = Array.from(elementsOfSourceSide(onCondition, elements));
83
- // sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
84
- if (dependentKeys.length === parentKeys.length)
85
- attachConstraintsToDependentKeys(dependentKeys, parentKeys, path[path.length - 3], path, 'CASCADE');
86
- }
83
+ if (composition.$selfOnCondition && composition.$selfOnCondition.up_.length === 1) {
84
+ const upLinkName = composition.$selfOnCondition.up_[0];
85
+ const up_ = csn.definitions[composition.target].elements[upLinkName];
86
+ if (up_.keys && isToOne(up_)) // no constraint for unmanaged / to-many up_ links
87
+ foreignKeyConstraintForAssociation(up_, [ 'definitions', composition.target, 'elements', upLinkName ], path[path.length - 1]);
87
88
  }
88
- else if (!onCondition && composition.keys) {
89
+ else if (!onCondition && composition.keys.length > 0) {
89
90
  throw new Error('Please debug me, an on-condition was expected here, but only found keys');
90
91
  }
91
92
  }
@@ -93,27 +94,29 @@ function createReferentialConstraints(csn, options) {
93
94
  /**
94
95
  * Calculate referential constraints for dependent keys in the entity where the cds.Associations is defined.
95
96
  * The DELETE rule for a referential constraint stemming from a cds.Association will be 'RESTRICT'
97
+ * If the association is used as an <up_> link in a compositions on-condition, the ON DELETE rule will be `CASCADE`
96
98
  *
97
99
  * @param {CSN.Association} association for that a constraint should be generated
98
- * @param {CSN.Elements} elements of parent entity.
99
100
  * @param {CSN.Path} path
101
+ * @param {CSN.PathSegment} upLinkFor the name of the composition which used this association in a `$self = <comp>.<up_>` comparison
100
102
  */
101
- function foreignKeyConstraintForAssociation(association, elements, path) {
102
- const associationTarget = csn.definitions[association.target];
103
- if (skipConstraintGeneration(associationTarget, association))
103
+ function foreignKeyConstraintForAssociation(association, path, upLinkFor = null) {
104
+ const parent = csn.definitions[association.target];
105
+ const dependent = csn.definitions[path[1]];
106
+ if (skipConstraintGeneration(parent, dependent, association))
104
107
  return;
105
-
108
+ const { elements } = csn.definitions[path[1]];
106
109
  const onCondition = association.on;
107
110
  if (onCondition && hasConstraintCompliantOnCondition(association, elements, path)) {
108
111
  // 1. cds.Association has constraint compliant on-condition
109
112
  // mark each dependent key - in the entity containing the association - referenced in the on-condition
110
113
  const dependentKeys = Array.from(elementsOfSourceSide(onCondition, elements));
111
- const parentKeys = Array.from(elementsOfTargetSide(onCondition, associationTarget.elements));
114
+ const parentKeys = Array.from(elementsOfTargetSide(onCondition, parent.elements));
112
115
  // sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
113
116
  if (dependentKeys.length === parentKeys.length)
114
- attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path);
117
+ attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path[path.length - 1], upLinkFor);
115
118
  }
116
- else if (!onCondition && association.keys) {
119
+ else if (!onCondition && association.keys.length > 0) {
117
120
  throw new Error('Please debug me, an on-condition was expected here, but only found keys');
118
121
  }
119
122
  }
@@ -126,13 +129,16 @@ function createReferentialConstraints(csn, options) {
126
129
  * @param {Array} dependentKeys array holding dependent keys in the format [['key1', 'value1'], [...], ...]
127
130
  * @param {Array} parentKeys array holding parent keys in the format [['key1', 'value1'], [...], ...]
128
131
  * @param {CSN.PathSegment} parentTable the sql-table where the foreign key constraints will be pointing to
129
- * @param {CSN.Path} path
130
- * @param {string} onDelete the on delete rule which should be applied. Default for associations is 'RESTRICT'
132
+ * @param {CSN.PathSegment} sourceAssociation the name of the association from which the constraint originates
133
+ * @param {CSN.PathSegment} upLinkFor the name of the composition which used this association in a `$self = <comp>.<up_>` comparison
134
+ * it is used for a comment in the constraint, which is only printed out in test-mode
131
135
  */
132
- function attachConstraintsToDependentKeys(dependentKeys, parentKeys, parentTable, path, onDelete = 'RESTRICT') {
136
+ function attachConstraintsToDependentKeys(dependentKeys, parentKeys, parentTable, sourceAssociation, upLinkFor = null) {
133
137
  while (dependentKeys.length > 0) {
134
138
  const dependentKeyValuePair = dependentKeys.pop();
135
139
  const dependentKey = dependentKeyValuePair[1];
140
+ // if it already has a dependent key assigned, do not overwrite it.
141
+ // this is the case for <up_> associations in on-conditions of compositions
136
142
  if (Object.prototype.hasOwnProperty.call(dependentKey, '$foreignKeyConstraint'))
137
143
  return;
138
144
 
@@ -142,9 +148,9 @@ function createReferentialConstraints(csn, options) {
142
148
  const constraint = {
143
149
  parentKey: parentKeyName,
144
150
  parentTable,
145
- sourceAssociation: path[path.length - 1],
146
- nameSuffix: dependentKey['@odata.foreignKey4'] || 'up_',
147
- onDelete,
151
+ upLinkFor,
152
+ sourceAssociation,
153
+ onDelete: upLinkFor ? 'CASCADE' : 'RESTRICT',
148
154
  validated,
149
155
  enforced,
150
156
  };
@@ -164,7 +170,7 @@ function createReferentialConstraints(csn, options) {
164
170
  * b) for cds.Association this is the target entity
165
171
  * 3. parent keys must be the full primary key tuple
166
172
  *
167
- * @param {CSN.Association | CSN.Composition} element
173
+ * @param {CSN.Association} element
168
174
  * @param {CSN.Elements} siblingElements
169
175
  * @param {CSN.Path} path the path to the element
170
176
  * @returns {boolean} indicating whether the association / composition is a constraint candidate
@@ -185,63 +191,245 @@ function createReferentialConstraints(csn, options) {
185
191
  if (onCondition.some((step, index) => typeof step === 'object' && inspectRef(path.concat([ 'on', index ])).scope === '$magic'))
186
192
  return false;
187
193
 
188
- // managed composition with target cardinality of one is treated like an association
189
- const isComposition = element.type === COMPOSITION && !treatCompositionLikeAssociation(element);
190
194
  // for cds.Associations the parent keys are in the associations target entity
191
195
  // for cds.Composition the parent keys are in the entity, where the composition is defined
192
- const parentElements = isComposition ? siblingElements : csn.definitions[element.target].elements;
193
- const parentKeys = isComposition ? elementsOfSourceSide(onCondition, parentElements) : elementsOfTargetSide(onCondition, parentElements);
196
+ const parentElements = csn.definitions[element.target].elements;
197
+ const parentKeys = elementsOfTargetSide(onCondition, parentElements);
198
+
199
+ const referencesNonPrimaryKeyField = Array.from(parentKeys.values()).some(parentKey => !parentKey.key);
200
+ if (referencesNonPrimaryKeyField)
201
+ return false;
202
+
194
203
  // returns true if the parentKeys found in the on-condition are covering the full primary key tuple in the parent entity
195
204
  return Array.from(parentKeys.entries())
196
- // check if primary key found in on-condition is present in association target / composition source
197
- .filter(([ keyName, pk ]) => pk.key && parentElements[keyName].key).length === Object.keys(parentElements)
198
- // compare that with the length of the primary key tuple found in association target / composition source
199
- .filter(key => parentElements[key].key &&
200
- parentElements[key].type !== ASSOCIATION &&
201
- parentElements[key].type !== COMPOSITION)
202
- .length;
205
+ // check if primary key found in on-condition is present in association target / composition source
206
+ .filter(([ keyName, pk ]) => pk.key && parentElements[keyName].key).length ===
207
+ Object.keys(parentElements)
208
+ // compare that with the length of the primary key tuple found in association target / composition source
209
+ .filter(key => parentElements[key].key &&
210
+ parentElements[key].type !== ASSOCIATION &&
211
+ parentElements[key].type !== COMPOSITION)
212
+ .length;
203
213
  }
204
-
205
214
  /**
206
215
  * Skip referential constraint if the parent table (association target, or artifact where composition is defined)
207
216
  * of the relation is:
208
217
  * - a query
209
- * - annotated with '@cds.persistence.skip:true'
210
- * - annotated with '@cds.persistence.exists:true'
218
+ * - TODO: Revisit -- annotated with '@cds.persistence.skip:true'
219
+ * - TODO: Revisit -- annotated with '@cds.persistence.exists:true'
220
+ *
221
+ * The following decision table reflects the current implementation:
211
222
  *
212
- * Skip referential constraint as well if:
213
- * - global option 'skipDbConstraints' is set and if the element is not annotated with
214
- * '@cds.persistency.assert.integrity: true'.
215
- * - the element is annotated either with '@cds.persistency.assert.integrity: false' or '@assert.integrity: false'
223
+ * +-----------------+--------------------+-------------------+----------+
224
+ * | Global Switch: | Global Check Type: | @assert.integrity | Generate |
225
+ * |"assertIntegrity"| "assertIntegrityType"| | Constraint|
226
+ * +-----------------+--------------------+-------------------+----------+
227
+ * | on | RT | false | no |
228
+ * +-----------------+--------------------+-------------------+----------+
229
+ * | on | RT | true/not set | no |
230
+ * +-----------------+--------------------+-------------------+----------+
231
+ * | on | RT | RT | no |
232
+ * +-----------------+--------------------+-------------------+----------+
233
+ * | on | RT | DB | yes |
234
+ * +-----------------+--------------------+-------------------+----------+
235
+ * | | | | |
236
+ * +-----------------+--------------------+-------------------+----------+
237
+ * | on | DB | false | no |
238
+ * +-----------------+--------------------+-------------------+----------+
239
+ * | on | DB | true/not set | yes |
240
+ * +-----------------+--------------------+-------------------+----------+
241
+ * | on | DB | RT | no |
242
+ * +-----------------+--------------------+-------------------+----------+
243
+ * | on | DB | DB | yes |
244
+ * +-----------------+--------------------+-------------------+----------+
245
+ * | | | | |
246
+ * +-----------------+--------------------+-------------------+----------+
247
+ * | off | don't care | don't care | no |
248
+ * +-----------------+--------------------+-------------------+----------+
249
+ * | | | | |
250
+ * +-----------------+--------------------+-------------------+----------+
251
+ * | individual | RT | true | no |
252
+ * +-----------------+--------------------+-------------------+----------+
253
+ * | individual | DB | true | yes |
254
+ * +-----------------+--------------------+-------------------+----------+
255
+ * | individual | don't care | RT | no |
256
+ * +-----------------+--------------------+-------------------+----------+
257
+ * | individual | don't care | DB | yes |
258
+ * +-----------------+--------------------+-------------------+----------+
259
+ * | individual | don't care | false/not set | no |
260
+ * +-----------------+--------------------+-------------------+----------+
216
261
  *
217
- * @param {CSN.Element} parent of association / composition
218
- * @param {CSN.Element} element the composition or association
262
+ * @param {CSN.Definition} parent entity where the foreign key reference will point at
263
+ * @param {CSN.Definition} dependent entity where the constraint will be defined on
264
+ * @param {CSN.Association} element the composition or association
219
265
  * @returns {boolean}
220
266
  */
221
- function skipConstraintGeneration(parent, element) {
222
- if (hasAnnotationValue(element, '@assert.integrity', false) ||
223
- hasAnnotationValue(element, '@cds.persistency.assert.integrity', false)) {
224
- // in case of managed composition, the 'up_' link should not result in a constraint
225
- const target = csn.definitions[element.target];
226
- const { up_ } = target.elements;
227
- if (up_)
228
- up_.$skipReferentialConstraintForUp_ = true;
267
+ function skipConstraintGeneration(parent, dependent, element) {
268
+ // if set to 'off' don't even bother, just skip all constraints
269
+ if (options.assertIntegrity === false || options.assertIntegrity === 'false')
229
270
  return true;
230
- }
231
271
 
232
- if (element.$skipReferentialConstraintForUp_)
272
+ if (parent.query)
233
273
  return true;
234
274
 
235
- if (hasAnnotationValue(parent, '@cds.persistence.skip', true) ||
236
- hasAnnotationValue(parent, '@cds.persistence.exists', true) ||
237
- parent.query)
275
+ // no constraint if either dependent or parent is not persisted
276
+ if (
277
+ hasAnnotationValue(parent, '@cds.persistence.skip') ||
278
+ hasAnnotationValue(dependent, '@cds.persistence.skip')
279
+ )
238
280
  return true;
239
281
 
240
- // '@cds.persistency.assert.integrity: true' supersedes global switch
241
- if (!hasAnnotationValue(element, '@cds.persistency.assert.integrity', true) && options.forHana.skipDbConstraints)
282
+ // some commonly used string literals
283
+ const RT = 'RT';
284
+ const DB = 'DB';
285
+ const CREATE_FOR_UP = '$createReferentialConstraintForUp_';
286
+ const SKIP_FOR_UP = '$skipReferentialConstraintForUp_';
287
+
288
+ // if the element itself is explicitly excluded from being checked
289
+ // skip the constraint for it (and its backlink)
290
+ if (isAssertIntegrityAnnotationSetTo(false) ||
291
+ isAssertIntegrityAnnotationSetTo(RT) ||
292
+ element[SKIP_FOR_UP]
293
+ ) {
294
+ // for "auto-generated" associations like for the up_ of a composition of aspects,
295
+ // the annotation on the composition influences the referential constraint for the
296
+ // up_ association
297
+ if (element.$selfOnCondition && element.targetAspect) {
298
+ assignPropOnBacklinkIfPossible(SKIP_FOR_UP, true);
299
+ return true;
300
+ }
301
+
302
+ if (element.type === 'cds.Composition')
303
+ return false;
304
+
305
+ return true;
306
+ }
307
+
308
+ if ((!options.assertIntegrity || options.assertIntegrity === true || options.assertIntegrity === 'true') &&
309
+ (!options.assertIntegrityType || options.assertIntegrityType === RT))
310
+ return assertForIntegrityTypeRT();
311
+
312
+ if ((!options.assertIntegrity || options.assertIntegrity === true || options.assertIntegrity === 'true') &&
313
+ options.assertIntegrityType === DB)
314
+ return assertForIntegrityTypeDB();
315
+
316
+ if ((options.assertIntegrity === 'individual'))
317
+ return assertForIndividual();
318
+
319
+ // The default for the assertIntegrityType is 'RT', no constraints in that case
320
+ if ((!options.assertIntegrity || options.assertIntegrity === true) &&
321
+ (!options.assertIntegrityType || options.assertIntegrityType === RT))
322
+ return true;
323
+
324
+ if (!element.keys || !isToOne(element))
242
325
  return true;
243
326
 
244
327
  return false;
328
+
329
+ /**
330
+ * if global checks are 'individual' we evaluate every association,
331
+ * we create db constraints if it is annotated with @assert.integrity: 'DB' (or true)
332
+ *
333
+ * @returns {boolean}
334
+ */
335
+ function assertForIndividual() {
336
+ if (isAssertIntegrityAnnotationSetTo(DB) || element[CREATE_FOR_UP]) {
337
+ // if this is has a $self comparison, the up_ link should then result in a constraint
338
+ assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
339
+ return false;
340
+ }
341
+ if (options.assertIntegrityType === DB && isAssertIntegrityAnnotationSetTo(true)) {
342
+ // if this is has a $self comparison, the up_ link should then result in a constraint
343
+ assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
344
+ return false;
345
+ }
346
+
347
+ // individual and no ('DB') annotation on constraint --> skip
348
+ return true;
349
+ }
350
+
351
+ /**
352
+ * if global check type is 'RT' (or not provided) only generate DB constraint if element
353
+ * is explicitly annotated "@assert.integrity: 'DB'"
354
+ *
355
+ * @returns {boolean}
356
+ */
357
+ function assertForIntegrityTypeRT() {
358
+ // for "auto-generated" associations like for the up_ of a composition of aspects,
359
+ // the annotation on the composition influences the referential constraint for the
360
+ // up_ association
361
+ if (isAssertIntegrityAnnotationSetTo(DB)) {
362
+ if (element.targetAspect)
363
+ assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
364
+ return false;
365
+ }
366
+ if (element[CREATE_FOR_UP])
367
+ return false;
368
+ return true;
369
+ }
370
+
371
+ /**
372
+ * if global checks are on and global integrity check type is 'DB'
373
+ * we create db constraints in any case except if annotated
374
+ * with @assert.integrity: 'RT' (or false, but that is rejected earlier)
375
+ *
376
+ * @returns {boolean}
377
+ */
378
+ function assertForIntegrityTypeDB() {
379
+ if (isAssertIntegrityAnnotationSetTo(RT))
380
+ return true;
381
+ return false;
382
+ }
383
+
384
+
385
+ /**
386
+ * convenience to check if value of element's @assert.integrity annotation
387
+ * is the same as a given value
388
+ *
389
+ * @param {string|boolean} value
390
+ * @returns {boolean}
391
+ */
392
+ function isAssertIntegrityAnnotationSetTo(value) {
393
+ return hasAnnotationValue(element, '@assert.integrity', value);
394
+ }
395
+
396
+ /**
397
+ * Assigns a helper key-value pair on the up_ association for a $self comparison
398
+ * for the current 'element', if applicable
399
+ *
400
+ * @param {string} prop
401
+ * @param {object} val
402
+ */
403
+ function assignPropOnBacklinkIfPossible(prop, val) {
404
+ if (!element.$selfOnCondition)
405
+ return;
406
+ const target = csn.definitions[element.target];
407
+ const backlink = target.elements[element.$selfOnCondition.up_[0]];
408
+ backlink[prop] = val;
409
+ }
410
+ }
411
+
412
+ /**
413
+ * If we have a managed composition with a target cardinality of one, we will treat it like
414
+ * a regular association when it comes to referential constraints.
415
+ * The constraint will thus be generated for the foreign key we create in the source entity.
416
+ *
417
+ * @param {CSN.Composition} composition the composition which might be treated like an association
418
+ * @returns {boolean} true if the composition should be treated as an association in regards to foreign key constraints
419
+ */
420
+ function treatCompositionLikeAssociation(composition) {
421
+ return Boolean(isToOne(composition) && composition.keys);
422
+ }
423
+
424
+ /**
425
+ * returns true if the association/composition has a max target cardinality of one
426
+ *
427
+ * @param {CSN.Association|CSN.Composition} assocOrComposition
428
+ * @returns {boolean}
429
+ */
430
+ function isToOne(assocOrComposition) {
431
+ const { min, max } = assocOrComposition.cardinality || {};
432
+ return !min && !max || max === 1;
245
433
  }
246
434
 
247
435
  /**
@@ -254,8 +442,8 @@ function createReferentialConstraints(csn, options) {
254
442
  function elementsOfTargetSide(on, targetElements) {
255
443
  const elements = new Map();
256
444
  on.filter(element => typeof element === 'object' &&
257
- element.ref.length > 1 &&
258
- targetElements[element.ref[element.ref.length - 1]])
445
+ element.ref.length > 1 &&
446
+ targetElements[element.ref[element.ref.length - 1]])
259
447
  .forEach((element) => {
260
448
  elements.set(element.ref[element.ref.length - 1], targetElements[element.ref[element.ref.length - 1]]);
261
449
  });
@@ -266,15 +454,15 @@ function createReferentialConstraints(csn, options) {
266
454
  /**
267
455
  * Finds and return elementNames and elements of source side mentioned in on-condition.
268
456
  *
269
- * @param {CSN.Association.on} on the on-condition
457
+ * @param {CSN.OnCondition} on the on-condition
270
458
  * @param {CSN.Elements} sourceElements elements of source entity where the association/composition is defined.
271
459
  * @returns {Map} of source elements with their name as key
272
460
  */
273
461
  function elementsOfSourceSide(on, sourceElements) {
274
462
  const elements = new Map();
275
463
  on.filter(element => typeof element === 'object' &&
276
- element.ref.length === 1 &&
277
- sourceElements[element.ref[0]])
464
+ element.ref.length === 1 &&
465
+ sourceElements[element.ref[0]])
278
466
  .forEach((element) => {
279
467
  elements.set(element.ref[0], sourceElements[element.ref[0]]);
280
468
  });
@@ -309,8 +497,8 @@ function createReferentialConstraints(csn, options) {
309
497
  // find all other $foreignKeyConstraint with same $sourceAssociation and same parentTable
310
498
  Object.entries(artifact.elements)
311
499
  .filter(([ , e ]) => e.$foreignKeyConstraint &&
312
- e.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
313
- e.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable)
500
+ e.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
501
+ e.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable)
314
502
  .forEach(([ foreignKeyName, foreignKey ]) => {
315
503
  const $foreignKeyConstraintCopy = Object.assign({}, foreignKey.$foreignKeyConstraint);
316
504
  delete foreignKey.$foreignKeyConstraint;
@@ -323,9 +511,9 @@ function createReferentialConstraints(csn, options) {
323
511
  let onDeleteRemark = null;
324
512
  // comments in sqlite files are causing the JDBC driver to throw an error on deployment
325
513
  if (options.testMode && onDelete === 'CASCADE')
326
- onDeleteRemark = `Composition "${$foreignKeyConstraint.sourceAssociation}" implies existential dependency`;
327
- referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.nameSuffix}`] = {
328
- identifier: `${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.nameSuffix}`,
514
+ onDeleteRemark = `Up_ link for Composition "${$foreignKeyConstraint.upLinkFor}" implies existential dependency`;
515
+ referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.sourceAssociation}`] = {
516
+ identifier: `${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
329
517
  foreignKey: dependentKey,
330
518
  parentKey,
331
519
  dependentTable: artifactName,
@@ -344,19 +532,6 @@ function createReferentialConstraints(csn, options) {
344
532
  artifact.$tableConstraints.referential = referentialConstraints;
345
533
  }
346
534
  }
347
-
348
- /**
349
- * If we have a managed composition with a target cardinality of one, we will treat it like
350
- * a regular association when it comes to referential constraints.
351
- * The constraints will thus be generated in the entity containing the composition and not in the target entity.
352
- *
353
- * @param {CSN.Composition} composition the composition which might be treated like an association
354
- * @returns {any} true if the composition should be treated as an association in regards to foreign key constraints
355
- */
356
- function treatCompositionLikeAssociation(composition) {
357
- const { min, max } = composition.cardinality || {};
358
- return (!min && !max || max === 1) && composition.keys;
359
- }
360
535
  }
361
536
 
362
537
  /**
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- hasAnnotationValue, forEachGeneric, getUtils, getServiceNames, forEachDefinition,
5
- getResultingName,
4
+ hasAnnotationValue, getUtils, getServiceNames, forEachDefinition,
5
+ getResultingName, forEachMemberRecursively,
6
6
  } = require('../../model/csnUtils');
7
7
  const { setProp, isDeprecatedEnabled } = require('../../base/model');
8
8
  const { getTransformers } = require('../transformUtilsNew');
@@ -21,6 +21,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
21
21
  const draftSuffix = isDeprecatedEnabled(options, 'generatedEntityNameWithUnderscore') ? '_drafts' : '.drafts';
22
22
  // All services of the model - needed for drafts
23
23
  const allServices = getServiceNames(csn);
24
+ const draftRoots = new WeakMap();
24
25
  const {
25
26
  createForeignKeyElement, createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,
26
27
  addElement, copyAndAddElement, createAssociationPathComparison,
@@ -117,10 +118,12 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
117
118
 
118
119
  // extract keys for UUID inspection
119
120
  const keys = [];
120
- forEachGeneric( artifact, 'elements', (elt) => {
121
+ forEachMemberRecursively(artifact, (elt, name, prop, path) => {
122
+ if (!elt.elements && !elt.type && !elt.virtual) // only check leafs
123
+ error(null, path, 'Expecting element to have a type when used in a draft-enabled artifact');
121
124
  if (elt.key && elt.key === true && !elt.virtual)
122
125
  keys.push(elt);
123
- });
126
+ }, [ 'definitions', artifactName ], true, { elementsOnly: true });
124
127
 
125
128
  // In contrast to EDM, the DB entity may have more than one technical keys but should have idealy exactly one key of type cds.UUID
126
129
  if (keys.length !== 1)
@@ -153,7 +156,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
153
156
  const persistenceName = getResultingName(csn, options.forHana.names, draftsArtifactName);
154
157
  // Duplicate the artifact as a draft shadow entity
155
158
  if (csn.definitions[persistenceName]) {
156
- const definingDraftRoot = csn.definitions[persistenceName].$draftRoot;
159
+ const definingDraftRoot = draftRoots.get(csn.definitions[persistenceName]);
157
160
  if (!definingDraftRoot) {
158
161
  error(null, [ 'definitions', artifactName ], { name: persistenceName },
159
162
  'Generated entity name $(NAME) conflicts with existing entity');
@@ -174,7 +177,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
174
177
  // Add draft shadow entity to the csn
175
178
  csn.definitions[draftsArtifactName] = draftsArtifact;
176
179
 
177
- setProp(draftsArtifact, '$draftRoot', draftRootName);
180
+ draftRoots.set(draftsArtifact, draftRootName);
178
181
  if (artifact.$location)
179
182
  setProp(draftsArtifact, '$location', artifact.$location);
180
183
 
@@ -222,7 +225,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
222
225
  // Note that we may need to do the HANA transformation steps for managed associations
223
226
  // (foreign key field generation, generatedFieldName, creating ON-condition) by hand,
224
227
  // because the corresponding transformation steps have already been done on all artifacts
225
- // when we come here). Only for 'keepStructsAssocs' this is not required.
228
+ // when we come here). Only for to.hdbcds with hdbcds names this is not required.
226
229
  /**
227
230
  * The given association has a key named DraftUUID
228
231
  *
@@ -256,7 +259,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
256
259
  }
257
260
 
258
261
  const draftUUIDKey = getDraftUUIDKey(draftAdministrativeData.DraftAdministrativeData);
259
- if (!options.forHana.keepStructsAssocs && draftUUIDKey) {
262
+ if (!(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') && draftUUIDKey) {
260
263
  const path = [ 'definitions', draftsArtifactName, 'elements', 'DraftAdministrativeData', 'keys', 0 ];
261
264
  createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', draftUUIDKey, draftsArtifact, draftsArtifactName, path);
262
265
  draftAdministrativeData.DraftAdministrativeData.on = createAssociationPathComparison('DraftAdministrativeData',