@sap/cds-compiler 2.12.0 → 2.13.6

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 (118) hide show
  1. package/CHANGELOG.md +110 -15
  2. package/bin/cdsc.js +13 -13
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +28 -63
  8. package/lib/api/options.js +3 -3
  9. package/lib/api/validate.js +0 -5
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +25 -4
  16. package/lib/base/messages.js +16 -26
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +158 -123
  19. package/lib/checks/annotationsOData.js +1 -1
  20. package/lib/checks/cdsPersistence.js +2 -1
  21. package/lib/checks/enricher.js +17 -1
  22. package/lib/checks/invalidTarget.js +3 -1
  23. package/lib/checks/managedWithoutKeys.js +3 -1
  24. package/lib/checks/selectItems.js +4 -4
  25. package/lib/checks/sql-snippets.js +27 -26
  26. package/lib/checks/types.js +1 -1
  27. package/lib/checks/validator.js +4 -7
  28. package/lib/compiler/assert-consistency.js +5 -3
  29. package/lib/compiler/builtins.js +8 -6
  30. package/lib/compiler/checks.js +14 -3
  31. package/lib/compiler/cycle-detector.js +1 -1
  32. package/lib/compiler/define.js +1103 -0
  33. package/lib/compiler/extend.js +983 -0
  34. package/lib/compiler/finalize-parse-cdl.js +231 -0
  35. package/lib/compiler/index.js +32 -13
  36. package/lib/compiler/kick-start.js +190 -0
  37. package/lib/compiler/moduleLayers.js +4 -4
  38. package/lib/compiler/populate.js +1226 -0
  39. package/lib/compiler/propagator.js +111 -46
  40. package/lib/compiler/resolve.js +1433 -0
  41. package/lib/compiler/shared.js +64 -37
  42. package/lib/compiler/tweak-assocs.js +529 -0
  43. package/lib/compiler/utils.js +197 -33
  44. package/lib/edm/.eslintrc.json +5 -0
  45. package/lib/edm/annotations/genericTranslation.js +5 -9
  46. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  47. package/lib/edm/csn2edm.js +9 -8
  48. package/lib/edm/edm.js +11 -12
  49. package/lib/edm/edmPreprocessor.js +137 -73
  50. package/lib/edm/edmUtils.js +116 -22
  51. package/lib/gen/Dictionary.json +10 -3
  52. package/lib/gen/language.checksum +1 -1
  53. package/lib/gen/language.interp +9 -1
  54. package/lib/gen/language.tokens +86 -83
  55. package/lib/gen/languageLexer.interp +10 -1
  56. package/lib/gen/languageLexer.js +860 -833
  57. package/lib/gen/languageLexer.tokens +78 -75
  58. package/lib/gen/languageParser.js +5282 -4265
  59. package/lib/json/from-csn.js +12 -1
  60. package/lib/json/to-csn.js +126 -66
  61. package/lib/language/docCommentParser.js +2 -2
  62. package/lib/language/genericAntlrParser.js +76 -3
  63. package/lib/language/language.g4 +297 -130
  64. package/lib/language/multiLineStringParser.js +5 -5
  65. package/lib/main.d.ts +468 -59
  66. package/lib/main.js +35 -9
  67. package/lib/model/api.js +3 -1
  68. package/lib/model/csnRefs.js +225 -156
  69. package/lib/model/csnUtils.js +192 -223
  70. package/lib/model/enrichCsn.js +70 -29
  71. package/lib/model/revealInternalProperties.js +27 -6
  72. package/lib/model/sortViews.js +2 -1
  73. package/lib/modelCompare/compare.js +17 -12
  74. package/lib/optionProcessor.js +5 -4
  75. package/lib/render/manageConstraints.js +35 -32
  76. package/lib/render/toCdl.js +73 -288
  77. package/lib/render/toHdbcds.js +25 -23
  78. package/lib/render/toSql.js +98 -41
  79. package/lib/render/utils/common.js +5 -10
  80. package/lib/render/utils/sql.js +4 -3
  81. package/lib/render/utils/stringEscapes.js +111 -0
  82. package/lib/sql-identifier.js +1 -1
  83. package/lib/transform/.eslintrc.json +5 -0
  84. package/lib/transform/db/.eslintrc.json +2 -0
  85. package/lib/transform/db/applyTransformations.js +35 -12
  86. package/lib/transform/db/assertUnique.js +1 -1
  87. package/lib/transform/db/associations.js +103 -305
  88. package/lib/transform/db/cdsPersistence.js +2 -2
  89. package/lib/transform/db/constraints.js +55 -52
  90. package/lib/transform/db/expansion.js +46 -24
  91. package/lib/transform/db/flattening.js +553 -102
  92. package/lib/transform/db/groupByOrderBy.js +3 -1
  93. package/lib/transform/db/transformExists.js +59 -6
  94. package/lib/transform/db/views.js +5 -4
  95. package/lib/transform/draft/.eslintrc.json +38 -0
  96. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  97. package/lib/transform/draft/odata.js +227 -0
  98. package/lib/transform/forHanaNew.js +67 -183
  99. package/lib/transform/forOdataNew.js +17 -171
  100. package/lib/transform/localized.js +34 -19
  101. package/lib/transform/odata/generateForeignKeyElements.js +1 -1
  102. package/lib/transform/odata/referenceFlattener.js +95 -89
  103. package/lib/transform/odata/structureFlattener.js +1 -1
  104. package/lib/transform/odata/toFinalBaseType.js +86 -12
  105. package/lib/transform/odata/typesExposure.js +5 -5
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +36 -22
  108. package/lib/transform/translateAssocsToJoins.js +2 -19
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +170 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/objectUtils.js +30 -0
  114. package/package.json +1 -1
  115. package/share/messages/README.md +26 -0
  116. package/lib/compiler/definer.js +0 -2361
  117. package/lib/compiler/resolver.js +0 -3079
  118. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -1,333 +1,55 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- cloneCsn, forEachGeneric,
5
- forAllElements,
4
+ applyTransformationsOnNonDictionary,
5
+ applyTransformations,
6
6
  getUtils,
7
7
  } = require('../../model/csnUtils');
8
8
 
9
- const transformUtils = require('../transformUtilsNew');
10
- const { setProp } = require('../../base/model');
11
-
12
-
13
- /**
14
- * Return a callback function for forEachDefinition that flattens the foreign keys of managed associations.
15
- * Foreign keys that are managed associations are replaced by their respective foreign keys.
16
- *
17
- * @param {CSN.Model} csn
18
- * @param {CSN.Options} options
19
- * @param {string} pathDelimiter
20
- * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
21
- */
22
- function getForeignKeyFlattener(csn, options, pathDelimiter) {
23
- const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
24
- const {
25
- isManagedAssociationElement,
26
- isStructured,
27
- inspectRef,
28
- } = getUtils(csn);
29
-
30
- const {
31
- flattenStructuredElement,
32
- flattenStructStepsInRef,
33
- } = transformUtils.getTransformers(csn, options, pathDelimiter);
34
-
35
- return handleManagedAssociationFKs;
36
-
37
- /**
38
- * Flatten and create the foreign key elements of managed associaitons
39
- *
40
- * @param {CSN.Artifact} art
41
- * @param {string} artName
42
- */
43
- function handleManagedAssociationFKs(art, artName) {
44
- if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
45
- forAllElements(art, artName, (parent, elements, pathToElements) => {
46
- forEachGeneric(parent, 'elements', (element, elemName) => {
47
- if (element.keys && isManagedAssociationElement(element)) {
48
- // replace foreign keys that are managed associations by their respective foreign keys
49
- flattenFKs(element, elemName, [ ...pathToElements, elemName ]);
50
- }
51
- });
52
- });
53
- }
54
- }
55
-
56
- /**
57
- * Flattens all foreign keys
58
- *
59
- * Structures will be resolved to individual elements with scalar types
60
- *
61
- * Associations will be replaced by their respective foreign keys
62
- *
63
- * If a structure contains an assoc, this will also be resolved and vice versa
64
- *
65
- * @param {CSN.Element} assoc
66
- * @param {string} assocName
67
- * @param {CSN.Path} path
68
- */
69
- function flattenFKs(assoc, assocName, path) {
70
- let finished = false;
71
- while (!finished) {
72
- const newKeys = [];
73
- finished = processKeys(assoc, assocName, path, newKeys);
74
- assoc.keys = newKeys;
75
- }
76
- assoc.keys = assoc.keys.filter(o => !o.$toDelete);
77
- }
78
-
79
- /**
80
- * Walk over the keys and replace structures by their leafs, managed associations by their foreign keys and keep scalar values as-is.
81
- *
82
- * @param {CSN.Element} assoc
83
- * @param {string} assocName
84
- * @param {CSN.Path} path
85
- * @param {object[]} collector New keys array to collect the flattened stuff in
86
- * @returns {boolean} True if all keys are scalar - false if there are things that still need to be processed.
87
- */
88
- function processKeys(assoc, assocName, path, collector) {
89
- let finished = true;
90
- for (let i = 0; i < assoc.keys.length; i++) {
91
- const pathToKey = path.concat([ 'keys', i ]);
92
- const { art } = inspectRef(pathToKey);
93
- const { ref } = assoc.keys[i];
94
- if (isStructured(art)) {
95
- finished = false;
96
- // Mark this element to filter it later - not needed after expansion
97
- setProp(assoc.keys[i], '$toDelete', true);
98
- const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
99
- Object.keys(flat).forEach((flatElemName) => {
100
- const key = assoc.keys[i];
101
- const clone = cloneCsn(assoc.keys[i], options);
102
- if (clone.as) {
103
- const lastRef = clone.ref[clone.ref.length - 1];
104
- // Cut off the last ref part from the beginning of the flat name
105
- const flatBaseName = flatElemName.slice(lastRef.length);
106
- // Join it to the existing table alias
107
- clone.as += flatBaseName;
108
- // do not loose the $ref for nested keys
109
- if (key.$ref) {
110
- let aliasedLeaf = key.$ref[key.$ref.length - 1];
111
- aliasedLeaf += flatBaseName;
112
- setProp(clone, '$ref', key.$ref.slice(0, key.$ref.length - 1).concat(aliasedLeaf));
113
- }
114
- }
115
- if (clone.ref) {
116
- clone.ref[clone.ref.length - 1] = flatElemName;
117
- // Now we need to properly flatten the whole ref
118
- clone.ref = flattenStructStepsInRef(clone.ref, pathToKey);
119
- }
120
- if (!clone.as)
121
- clone.as = flatElemName;
122
- // Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
123
- // Add the newly generated foreign keys to the end - they will be picked up later on
124
- // Recursive solutions run into call stack issues
125
- collector.push(clone);
126
- });
127
- }
128
- else if (art.target) {
129
- finished = false;
130
- // Mark this element to filter it later - not needed after expansion
131
- setProp(assoc.keys[i], '$toDelete', true);
132
- // Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
133
- // Add the newly generated foreign keys to the end - they will be picked up later on
134
- // Recursive solutions run into call stack issues
135
- art.keys.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i])));
136
- }
137
- else if (assoc.keys[i].ref && !assoc.keys[i].as) {
138
- assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];
139
- collector.push(assoc.keys[i]);
140
- }
141
- else {
142
- collector.push(assoc.keys[i]);
143
- }
144
- }
145
- return finished;
146
- }
147
-
148
- /**
149
- * Clone base and extend the .ref and .as of the clone with the .ref and .as of ref.
150
- *
151
- * @param {object} key A foreign key entry (of a managed assoc as a fk of another assoc)
152
- * @param {object} base The fk-ref that has key as a fk
153
- * @returns {object} The clone of base
154
- */
155
- function cloneAndExtendRef(key, base) {
156
- const { ref } = base;
157
- const clone = cloneCsn(base, options);
158
- if (key.ref) {
159
- // We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
160
- // Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
161
- // Later on, after we know that these foreign key elements are created, we replace ref with this $ref
162
- let $ref;
163
- if (base.$ref) {
164
- // if a base $ref is provided, use it to correctly resolve association chains
165
- const refChain = [ base.$ref[base.$ref.length - 1] ].concat(key.as || key.ref);
166
- $ref = base.$ref.slice(0, base.$ref.length - 1).concat(refChain);
167
- }
168
- else {
169
- $ref = base.ref.concat( key.as || key.ref); // Keep along the aliases
170
- }
171
- setProp(clone, '$ref', $ref);
172
- clone.ref = clone.ref.concat(key.ref);
173
- }
174
-
175
- if (!clone.as && clone.ref && clone.ref.length > 0)
176
- clone.as = ref[ref.length - 1] + pathDelimiter + (key.as || key.ref.join(pathDelimiter));
177
- else
178
- clone.as += pathDelimiter + (key.as || key.ref.join(pathDelimiter));
179
-
180
- return clone;
181
- }
182
- }
183
-
184
9
 
185
10
  /**
186
- * Return a callback function for forEachDefinition that
11
+ * In all .elements of entities and views (and their bound actions/functions), create the on-condition for
12
+ * a managed associations. This needs to happen after the .keys are expanded and the corresponding elements are created.
187
13
  *
188
14
  * @param {CSN.Model} csn
189
- * @param {CSN.Options} options
190
15
  * @param {string} pathDelimiter
191
- * @param {object} messageFunctions
192
- * @param {Function} messageFunctions.error
193
- * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
16
+ * @returns {CSN.Model} Return the input csn, with the transformations applied
194
17
  */
195
- function getForeignKeyElementCreator(csn, options, pathDelimiter, messageFunctions) {
196
- const { error } = messageFunctions;
197
- const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
18
+ function attachOnConditions(csn, pathDelimiter) {
198
19
  const {
199
- isManagedAssociationElement,
20
+ isManagedAssociation,
200
21
  } = getUtils(csn);
201
22
 
202
- const {
203
- flattenStructStepsInRef, getForeignKeyArtifact,
204
- } = transformUtils.getTransformers(csn, options, pathDelimiter);
205
-
206
-
207
- return createForeignKeyElements;
208
-
209
- /**
210
- * Create the foreign key elements for managed associations.
211
- * Create them in-place, right after the corresponding association.
212
- *
213
- *
214
- * @param {CSN.Artifact} art
215
- * @param {string} artName
216
- */
217
- function createForeignKeyElements(art, artName) {
218
- if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
219
- forAllElements(art, artName, (parent, elements, pathToElements) => {
220
- const elementsArray = [];
221
- forEachGeneric(parent, 'elements', (element, elemName) => {
222
- elementsArray.push([ elemName, element ]);
223
- if (element.keys && isManagedAssociationElement(element)) {
224
- for (let i = 0; i < element.keys.length; i++) {
225
- const foreignKey = element.keys[i];
226
- const path = [ ...pathToElements, elemName, 'keys', i ];
227
- foreignKey.ref = flattenStructStepsInRef(foreignKey.ref, path);
228
- const [ fkName, fkElem ] = getForeignKeyArtifact(element, elemName, foreignKey, path);
229
- if (parent.elements[fkName]) {
230
- error(null, [ ...pathToElements, elemName ], { name: fkName, art: elemName },
231
- 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
232
- }
233
- else {
234
- elementsArray.push([ fkName, fkElem ]);
235
- }
236
- applyCachedAlias(foreignKey);
237
- // join ref array as the struct / assoc steps are not necessary anymore
238
- foreignKey.ref = [ foreignKey.ref.join(pathDelimiter) ];
239
- }
240
- }
241
- });
242
-
243
- // Don't fake consistency of the model by adding empty elements {}
244
- if (elementsArray.length === 0)
245
- return;
246
-
247
- parent.elements = elementsArray.reduce((previous, [ name, element ]) => {
248
- previous[name] = element;
249
- return previous;
250
- }, Object.create(null));
251
- });
252
- }
253
-
254
- /**
255
- * We save the aliased representation of the fk in $ref - I suppose to stay resolvable?
256
- *
257
- * We now use this $ref for ref and delete it.
258
- *
259
- * @param {object} foreignKey
260
- * @todo With nested projections, we solve similar problems, maybe adapt?
261
- */
262
- function applyCachedAlias(foreignKey) {
263
- // If we have a $ref use that - it resolves aliased FKs correctly
264
- if (foreignKey.$ref) {
265
- foreignKey.ref = foreignKey.$ref;
266
- delete foreignKey.$ref;
23
+ const alreadyHandled = new WeakMap();
24
+ applyTransformations(csn, {
25
+ elements: (parent, prop, elements) => {
26
+ for (const elemName in elements) {
27
+ const elem = elements[elemName];
28
+ // (140) Generate the ON-condition for managed associations
29
+ if (isManagedAssociation(elem))
30
+ transformManagedAssociation(elem, elemName);
267
31
  }
268
- }
269
- }
270
- }
32
+ }, /* only for views and entities */
33
+ }, [], { skipIgnore: false, allowArtifact: artifact => (artifact.kind === 'entity') });
271
34
 
272
- /**
273
- * Return a callback function for forEachDefinition that
274
- *
275
- * @param {CSN.Model} csn
276
- * @param {CSN.Options} options
277
- * @param {string} pathDelimiter
278
- * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
279
- */
280
- function getManagedAssociationTransformer(csn, options, pathDelimiter) {
281
- const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
282
- const {
283
- isManagedAssociationElement,
284
- } = getUtils(csn);
285
-
286
- return handleAssociations;
287
-
288
- /**
289
- *
290
- * Generate foreign keys for managed associations
291
- * Forbid aliases for foreign keys
292
- *
293
- * @param {CSN.Artifact} artifact
294
- * @param {string} artifactName
295
- */
296
- function handleAssociations(artifact, artifactName) {
297
- // Do things specific for entities and views (pass 1)
298
- if (artifact.kind === 'entity' || artifact.kind === 'view') {
299
- const alreadyHandled = new WeakMap();
300
- forAllElements(artifact, artifactName, (parent, elements) => {
301
- for (const elemName in elements) {
302
- const elem = elements[elemName];
303
- // (140) Generate foreign key elements and ON-condition for managed associations
304
- // (unless explicitly asked to keep assocs unchanged)
305
- if (doA2J && isManagedAssociationElement(elem))
306
- transformManagedAssociation(parent, artifactName, elem, elemName, alreadyHandled);
307
- }
308
- });
309
- }
310
- }
35
+ return csn;
311
36
 
312
37
  /**
313
38
  * Create the foreign key elements for a managed association and build the on-condition
314
39
  *
315
- * @param {CSN.Artifact} artifact
316
- * @param {string} artifactName
317
40
  * @param {Object} elem The association to process
318
41
  * @param {string} elemName
319
- * @param {WeakMap} alreadyHandled To cache which elements were already processed
320
42
  * @returns {void}
321
43
  */
322
- function transformManagedAssociation(artifact, artifactName, elem, elemName, alreadyHandled) {
44
+ function transformManagedAssociation(elem, elemName) {
323
45
  // No need to run over this - we already did, possibly because it was referenced in the ON-Condition
324
46
  // of another association - see a few lines lower
325
47
  if (alreadyHandled.has(elem))
326
48
  return;
327
- // Generate foreign key elements for managed associations, and assemble an ON-condition with them
49
+ // Assemble an ON-condition with the foreign keys created in earlier steps
328
50
  const onCondParts = [];
329
51
  let joinWithAnd = false;
330
- if (elem.keys.length === 0) {
52
+ if (elem.keys.length === 0) { // TODO: really kill instead of _ignore?
331
53
  elem._ignore = true;
332
54
  }
333
55
  else {
@@ -366,24 +88,100 @@ function getManagedAssociationTransformer(csn, options, pathDelimiter) {
366
88
  if (elem.key)
367
89
  delete elem.key;
368
90
 
369
-
370
91
  // If the managed association has a 'not null' property => remove it
371
92
  if (elem.notNull)
372
93
  delete elem.notNull;
373
94
 
374
-
375
95
  // The association is now unmanaged, i.e. actually it should no longer have foreign keys
376
96
  // at all. But the processing of backlink associations below expects to have them, so
377
- // we don't delete them (but mark them as implicit so that toCdl does not render them)
97
+ // we don't delete them
98
+ // TODO: maybe make non-enumerable, so we become recompilable in the future?
378
99
 
379
100
  // Remember that we already processed this
380
101
  alreadyHandled.set(elem, true);
381
102
  }
382
103
  }
383
104
 
105
+ /**
106
+ * @param {CSN.Model} csn
107
+ * @param {string} pathDelimiter
108
+ * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
109
+ */
110
+ function getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
111
+ const {
112
+ inspectRef,
113
+ } = getUtils(csn);
114
+
115
+ return handleManagedAssocStepsInOnCondition;
116
+
117
+ /**
118
+ * Loop over all elements and for all unmanaged associations translate
119
+ * <assoc base>.<managed assoc>.<fk> to <assoc base>.<managed assoc>_<fk>
120
+ *
121
+ * Or in other words: Allow using the foreign keys of managed associations in on-conditions
122
+ *
123
+ * @param {CSN.Artifact} artifact Artifact to check
124
+ * @param {string} artifactName Name of the artifact
125
+ */
126
+ function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
127
+ for (const elemName in artifact.elements) {
128
+ const elem = artifact.elements[elemName];
129
+ // The association is an unmanaged on
130
+ if (!elem.keys && elem.target && elem.on) {
131
+ applyTransformationsOnNonDictionary(elem, 'on', {
132
+ ref: (refOwner, prop, ref, path) => {
133
+ // [<assoc base>.]<managed assoc>.<field>
134
+ if (ref.length > 1) {
135
+ const { links } = inspectRef(path);
136
+ if (links) {
137
+ // eslint-disable-next-line for-direction
138
+ for (let i = links.length - 1; i >= 0; i--) {
139
+ const link = links[i];
140
+ // We found the latest managed assoc path step
141
+ if (link.art && link.art.target && link.art.keys &&
142
+ // Doesn't work when ref-target (filter condition) or similar is used
143
+ !ref.slice(i).some(refElement => typeof refElement !== 'string')) {
144
+ // We join the managed assoc with everything following it
145
+ const sourceElementName = ref.slice(i).join(pathDelimiter);
146
+ const source = findSource(links, i - 1) || artifact;
147
+ // allow specifying managed assoc on the source side
148
+ const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
149
+ if (fks && fks.length >= 1) {
150
+ const fk = fks[0];
151
+ const managedAssocStepName = refOwner.ref[i];
152
+ const fkName = `${managedAssocStepName}${pathDelimiter}${fk.as}`;
153
+ if (source && source.elements[fkName])
154
+ refOwner.ref = [ ...ref.slice(0, i), fkName ];
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+ },
161
+ }, {}, [ 'definitions', artifactName, 'elements', elemName ]);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Find out where the managed association is
167
+ *
168
+ * @param {Array} links
169
+ * @param {number} startIndex
170
+ * @returns {Object| undefined} CSN definition of the source of the managed association
171
+ */
172
+ function findSource(links, startIndex) {
173
+ for (let i = startIndex; i >= 0; i--) {
174
+ const link = links[i];
175
+ // We found the latest assoc step - now check where that points to
176
+ if (link.art && link.art.target)
177
+ return csn.definitions[link.art.target];
178
+ }
384
179
 
180
+ return undefined;
181
+ }
182
+ }
183
+ }
385
184
  module.exports = {
386
- getForeignKeyFlattener,
387
- getForeignKeyElementCreator,
388
- getManagedAssociationTransformer,
185
+ attachOnConditions,
186
+ getManagedAssocStepsInOnConditionFinalizer,
389
187
  };
@@ -20,7 +20,7 @@ function getAnnoProcessor() {
20
20
  * @param {CSN.Artifact} artifact
21
21
  */
22
22
  function handleCdsPersistence(artifact) {
23
- const ignoreArtifact = (artifact.kind === 'entity' || artifact.kind === 'view') &&
23
+ const ignoreArtifact = (artifact.kind === 'entity') &&
24
24
  (artifact.abstract ||
25
25
  hasAnnotationValue(artifact, '@cds.persistence.skip') ||
26
26
  hasAnnotationValue(artifact, exists));
@@ -91,7 +91,7 @@ function getAssocToSkippedIgnorer(csn, options, messageFunctions) {
91
91
  }
92
92
 
93
93
  /**
94
- * Check wether the given artifact is an unreachable association target because it will not "realy" hit the database:
94
+ * Check whether the given artifact is an unreachable association target because it will not "realy" hit the database:
95
95
  * - @cds.persistence.skip/exists
96
96
  * - abstract
97
97
  *
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const { forEachDefinition } = require('../../base/model');
4
- const { forAllElements, hasAnnotationValue, getResultingName } = require('../../model/csnUtils');
4
+ const { applyTransformations, hasAnnotationValue, getResultingName } = require('../../model/csnUtils');
5
5
  const { csnRefs } = require('../../model/csnRefs');
6
+ const { forEach, forEachKey } = require('../../utils/objectUtils');
6
7
 
7
8
  const COMPOSITION = 'cds.Composition';
8
9
  const ASSOCIATION = 'cds.Association';
@@ -28,34 +29,36 @@ function createReferentialConstraints(csn, options) {
28
29
  // compositions must be processed first, as the <up_> links for them must result in `ON DELETE CASCADE`
29
30
  const compositions = [];
30
31
  const associations = [];
31
- forEachDefinition(csn, (artifact, artifactName) => {
32
- if (!artifact.query && artifact.kind === 'entity') {
33
- forAllElements(artifact, artifactName, (parent, elements, path) => {
34
- // Step I: iterate compositions, enrich dependent keys for <up_> association in target entity of composition
35
- for (const elementName in elements) {
36
- const element = elements[elementName];
37
- if (element.type === COMPOSITION && element.$selfOnCondition) {
38
- compositions.push({
39
- fn: () => {
40
- foreignKeyConstraintForUpLinkOfComposition(element, parent, path.concat([ elementName ]));
41
- },
42
- });
43
- }
32
+ applyTransformations(csn, {
33
+ elements: (parent, prop, elements, path) => {
34
+ // Step I: iterate compositions, enrich dependent keys for <up_> association in target entity of composition
35
+ for (const elementName in elements) {
36
+ const element = elements[elementName];
37
+ const ePath = path.concat([ 'elements', elementName ]); // Save a copy in this scope for the late callback
38
+ if (element.type === COMPOSITION && element.$selfOnCondition) {
39
+ compositions.push({
40
+ fn: () => {
41
+ foreignKeyConstraintForUpLinkOfComposition(element, parent, ePath);
42
+ },
43
+ });
44
44
  }
45
- // Step II: iterate associations, enrich dependent keys (in entity containing the association)
46
- for (const elementName in elements) {
47
- const element = elements[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
- },
53
- });
54
- }
45
+ }
46
+
47
+ // Step II: iterate associations, enrich dependent keys (in entity containing the association)
48
+ for (const elementName in elements) {
49
+ const element = elements[elementName];
50
+ const ePath = path.concat([ 'elements', elementName ]); // Save a copy in this scope for the late callback
51
+ if (element.keys && isToOne(element) && element.type === ASSOCIATION || element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
52
+ associations.push({
53
+ fn: () => {
54
+ foreignKeyConstraintForAssociation(element, ePath );
55
+ },
56
+ });
55
57
  }
56
- });
57
- }
58
- });
58
+ }
59
+ },
60
+ }, [], { skipIgnore: false, skipArtifact: a => a.query || a.kind !== 'entity' });
61
+
59
62
  // create constraints on foreign keys
60
63
  // always process unmanaged first, up_ links must be flagged
61
64
  // before they are processed
@@ -215,8 +218,7 @@ function createReferentialConstraints(csn, options) {
215
218
  * Skip referential constraint if the parent table (association target, or artifact where composition is defined)
216
219
  * of the relation is:
217
220
  * - a query
218
- * - TODO: Revisit -- annotated with '@cds.persistence.skip:true'
219
- * - TODO: Revisit -- annotated with '@cds.persistence.exists:true'
221
+ * - annotated with '@cds.persistence.skip:true'
220
222
  *
221
223
  * The following decision table reflects the current implementation:
222
224
  *
@@ -491,18 +493,20 @@ function createReferentialConstraints(csn, options) {
491
493
  const dependentKey = [ elementName ];
492
494
  const onDeleteRules = new Set();
493
495
  onDeleteRules.add($foreignKeyConstraint.onDelete);
494
- // find all other $foreignKeyConstraint with same $sourceAssociation and same parentTable
495
- Object.entries(artifact.elements)
496
- .filter(([ , e ]) => e.$foreignKeyConstraint &&
497
- e.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
498
- e.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable)
499
- .forEach(([ foreignKeyName, foreignKey ]) => {
500
- const $foreignKeyConstraintCopy = Object.assign({}, foreignKey.$foreignKeyConstraint);
501
- delete foreignKey.$foreignKeyConstraint;
502
- parentKey.push($foreignKeyConstraintCopy.parentKey);
503
- dependentKey.push(foreignKeyName);
504
- onDeleteRules.add($foreignKeyConstraintCopy.onDelete);
505
- });
496
+ forEach(artifact.elements, (foreignKeyName, foreignKey) => {
497
+ // find all other `$foreignKeyConstraint`s with same `$sourceAssociation` and same `parentTable`
498
+ const matchingForeignKeyFound = foreignKey.$foreignKeyConstraint &&
499
+ foreignKey.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
500
+ foreignKey.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable;
501
+ if (!matchingForeignKeyFound)
502
+ return;
503
+
504
+ const $foreignKeyConstraintCopy = Object.assign({}, foreignKey.$foreignKeyConstraint);
505
+ delete foreignKey.$foreignKeyConstraint;
506
+ parentKey.push($foreignKeyConstraintCopy.parentKey);
507
+ dependentKey.push(foreignKeyName);
508
+ onDeleteRules.add($foreignKeyConstraintCopy.onDelete);
509
+ });
506
510
  // onDelete Rule is the "weakest" rule applicable. Precedence: RESTRICT > SET NULL > CASCADE
507
511
  const onDelete = onDeleteRules.has('RESTRICT') ? 'RESTRICT' : 'CASCADE';
508
512
  let onDeleteRemark = null;
@@ -510,14 +514,14 @@ function createReferentialConstraints(csn, options) {
510
514
  if (options.testMode && onDelete === 'CASCADE')
511
515
  onDeleteRemark = `Up_ link for Composition "${$foreignKeyConstraint.upLinkFor}" implies existential dependency`;
512
516
  referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.sourceAssociation}`] = {
513
- identifier: `${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
517
+ // constraint identifier start with `c__` to avoid name clashes
518
+ identifier: `c__${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
514
519
  foreignKey: dependentKey,
515
520
  parentKey,
516
521
  dependentTable: artifactName,
517
522
  parentTable,
518
523
  onDelete,
519
524
  onDeleteRemark, // explain why this particular rule is chosen
520
- // TODO: do we want to switch off validation / enforcement via annotation on association?
521
525
  validated: $foreignKeyConstraint.validated,
522
526
  enforced: $foreignKeyConstraint.enforced,
523
527
  };
@@ -545,15 +549,14 @@ function assertConstraintIdentifierUniqueness(artifact, artifactName, path, erro
545
549
  if (!(artifact.$tableConstraints && artifact.$tableConstraints.referential && artifact.$tableConstraints.unique))
546
550
  return;
547
551
 
548
- Object.keys(artifact.$tableConstraints.unique)
549
- .map(id => `${artifactName}_${id}`) // final unique constraint identifier will be generated in renderer likewise
550
- .forEach((uniqueConstraintIdentifier) => {
551
- if (artifact.$tableConstraints.referential[uniqueConstraintIdentifier]) {
552
- error(null, path,
553
- { name: uniqueConstraintIdentifier, art: artifactName },
554
- 'Duplicate constraint name $(NAME) in artifact $(ART)');
555
- }
556
- });
552
+ forEachKey(artifact.$tableConstraints.unique, (uniqueConstraintKey) => {
553
+ const uniqueConstraintIdentifier = `${artifactName}_${uniqueConstraintKey}`; // final unique constraint identifier will be generated in renderer likewise
554
+ if (artifact.$tableConstraints.referential[uniqueConstraintIdentifier]) {
555
+ error(null, path,
556
+ { name: uniqueConstraintIdentifier, art: artifactName },
557
+ 'Duplicate constraint name $(NAME) in artifact $(ART)');
558
+ }
559
+ });
557
560
  }
558
561
 
559
562
  module.exports = { createReferentialConstraints, assertConstraintIdentifierUniqueness };