@sap/cds-compiler 2.10.4 → 2.12.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 (103) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +58 -35
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +16 -0
  9. package/lib/api/.eslintrc.json +2 -0
  10. package/lib/api/main.js +10 -36
  11. package/lib/api/options.js +17 -8
  12. package/lib/api/validate.js +30 -3
  13. package/lib/backends.js +12 -13
  14. package/lib/base/dictionaries.js +2 -1
  15. package/lib/base/keywords.js +3 -2
  16. package/lib/base/message-registry.js +64 -11
  17. package/lib/base/messages.js +38 -18
  18. package/lib/base/model.js +6 -4
  19. package/lib/base/optionProcessorHelper.js +148 -86
  20. package/lib/checks/.eslintrc.json +2 -0
  21. package/lib/checks/actionsFunctions.js +2 -1
  22. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  23. package/lib/checks/foreignKeys.js +4 -4
  24. package/lib/checks/managedInType.js +4 -4
  25. package/lib/checks/queryNoDbArtifacts.js +1 -3
  26. package/lib/checks/selectItems.js +4 -0
  27. package/lib/checks/sql-snippets.js +93 -0
  28. package/lib/checks/unknownMagic.js +6 -3
  29. package/lib/checks/validator.js +8 -0
  30. package/lib/compiler/assert-consistency.js +14 -5
  31. package/lib/compiler/base.js +64 -0
  32. package/lib/compiler/builtins.js +62 -16
  33. package/lib/compiler/checks.js +34 -10
  34. package/lib/compiler/definer.js +91 -112
  35. package/lib/compiler/index.js +30 -30
  36. package/lib/compiler/propagator.js +8 -4
  37. package/lib/compiler/resolver.js +279 -63
  38. package/lib/compiler/shared.js +65 -230
  39. package/lib/compiler/utils.js +191 -0
  40. package/lib/edm/annotations/genericTranslation.js +35 -18
  41. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  42. package/lib/edm/csn2edm.js +4 -3
  43. package/lib/edm/edm.js +8 -8
  44. package/lib/edm/edmPreprocessor.js +61 -59
  45. package/lib/edm/edmUtils.js +14 -15
  46. package/lib/gen/Dictionary.json +82 -40
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +19 -1
  49. package/lib/gen/language.tokens +80 -73
  50. package/lib/gen/languageLexer.interp +27 -1
  51. package/lib/gen/languageLexer.js +925 -826
  52. package/lib/gen/languageLexer.tokens +72 -65
  53. package/lib/gen/languageParser.js +4817 -4102
  54. package/lib/json/from-csn.js +57 -26
  55. package/lib/json/to-csn.js +244 -51
  56. package/lib/language/antlrParser.js +12 -1
  57. package/lib/language/docCommentParser.js +1 -1
  58. package/lib/language/errorStrategy.js +26 -8
  59. package/lib/language/genericAntlrParser.js +106 -30
  60. package/lib/language/language.g4 +200 -70
  61. package/lib/language/multiLineStringParser.js +536 -0
  62. package/lib/main.d.ts +220 -21
  63. package/lib/main.js +6 -3
  64. package/lib/model/api.js +2 -2
  65. package/lib/model/csnRefs.js +218 -86
  66. package/lib/model/csnUtils.js +99 -178
  67. package/lib/model/enrichCsn.js +84 -43
  68. package/lib/model/revealInternalProperties.js +25 -8
  69. package/lib/model/sortViews.js +8 -1
  70. package/lib/modelCompare/compare.js +2 -1
  71. package/lib/optionProcessor.js +33 -18
  72. package/lib/render/.eslintrc.json +1 -2
  73. package/lib/render/DuplicateChecker.js +2 -2
  74. package/lib/render/manageConstraints.js +1 -1
  75. package/lib/render/toCdl.js +202 -82
  76. package/lib/render/toHdbcds.js +194 -135
  77. package/lib/render/toRename.js +7 -10
  78. package/lib/render/toSql.js +91 -51
  79. package/lib/render/utils/common.js +24 -5
  80. package/lib/render/utils/sql.js +6 -4
  81. package/lib/transform/braceExpression.js +4 -2
  82. package/lib/transform/db/applyTransformations.js +189 -0
  83. package/lib/transform/db/associations.js +389 -0
  84. package/lib/transform/db/cdsPersistence.js +150 -0
  85. package/lib/transform/db/constraints.js +275 -119
  86. package/lib/transform/db/draft.js +6 -4
  87. package/lib/transform/db/expansion.js +10 -9
  88. package/lib/transform/db/flattening.js +23 -8
  89. package/lib/transform/db/temporal.js +236 -0
  90. package/lib/transform/db/transformExists.js +106 -25
  91. package/lib/transform/db/views.js +485 -0
  92. package/lib/transform/forHanaNew.js +90 -1036
  93. package/lib/transform/forOdataNew.js +11 -3
  94. package/lib/transform/localized.js +5 -14
  95. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  96. package/lib/transform/transformUtilsNew.js +34 -20
  97. package/lib/transform/translateAssocsToJoins.js +15 -23
  98. package/lib/transform/universalCsnEnricher.js +217 -47
  99. package/lib/utils/file.js +13 -6
  100. package/lib/utils/term.js +65 -42
  101. package/lib/utils/timetrace.js +55 -27
  102. package/package.json +1 -1
  103. package/lib/transform/db/helpers.js +0 -58
@@ -0,0 +1,189 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Module for general (partial) CSN looper functions, respecting dictionaries and allowing
5
+ * to pass custom callbacks for certain properties like "ref".
6
+ *
7
+ * Functions are also published in csnUtils.js for convenience.
8
+ *
9
+ * They should stay here due to the stricter linter rules for the time being.
10
+ *
11
+ * @module lib/transform/db/applyTransformations
12
+ */
13
+ const { setProp } = require('../../base/model');
14
+
15
+
16
+ /**
17
+ * @param {object} parent The "parent" of which we transform a property of
18
+ * @param {string} prop The property of parent to start at
19
+ * @param {object} customTransformers Map of prop to transform and function to apply
20
+ * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
21
+ * @param {boolean} [skipIgnore=true] Wether to skip _ignore elements or not
22
+ * @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
23
+ * @param {CSN.Path} path Path to parent
24
+ * @returns {object} parent with transformations applied
25
+ */
26
+ function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers, skipIgnore, options, path = []) {
27
+ const transformers = {
28
+ elements: dictionary,
29
+ definitions: dictionary,
30
+ actions: dictionary,
31
+ params: dictionary,
32
+ enum: dictionary,
33
+ mixin: dictionary,
34
+ ref: pathRef,
35
+ };
36
+
37
+ const csnPath = [ ...path ];
38
+ if (prop === 'definitions')
39
+ definitions( parent, 'definitions', parent.definitions );
40
+ else
41
+ standard(parent, prop, parent[prop]);
42
+ return parent;
43
+
44
+ /**
45
+ * Default transformer for things that are not dictionaries, like "type" or "keys".
46
+ * The customTransformers are applied here (and only here).
47
+ *
48
+ * @param {object | Array} _parent the thing that has _prop
49
+ * @param {string|number} _prop the name of the current property
50
+ * @param {object} node The value of node[_prop]
51
+ */
52
+ function standard( _parent, _prop, node ) {
53
+ if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( _parent, _prop ) || (typeof _prop === 'string' && _prop.startsWith('@')) || (skipIgnore && node._ignore))
54
+ return;
55
+
56
+ csnPath.push( _prop );
57
+
58
+ if (Array.isArray(node)) {
59
+ node.forEach( (n, i) => standard( node, i, n ) );
60
+ }
61
+
62
+ else {
63
+ for (const name of Object.getOwnPropertyNames( node )) {
64
+ const trans = transformers[name] || standard;
65
+ if (customTransformers[name])
66
+ customTransformers[name](node, name, node[name], csnPath, _parent, _prop);
67
+
68
+ trans( node, name, node[name], csnPath );
69
+ }
70
+ }
71
+ csnPath.pop();
72
+ }
73
+
74
+ /**
75
+ * Transformer for things that are dictionaries - like "elements".
76
+ *
77
+ * @param {object | Array} node the thing that has _prop
78
+ * @param {string|number} _prop the name of the current property
79
+ * @param {object} dict The value of node[_prop]
80
+ */
81
+ function dictionary( node, _prop, dict ) {
82
+ // Allow skipping dicts like actions in forHanaNew
83
+ if (options.skipDict && options.skipDict[_prop])
84
+ return;
85
+ csnPath.push( _prop );
86
+ for (const name of Object.getOwnPropertyNames( dict ))
87
+ standard( dict, name, dict[name] );
88
+
89
+ if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
90
+ setProp(node, `$${_prop}`, dict);
91
+ csnPath.pop();
92
+ }
93
+
94
+ /**
95
+ * Special version of "dictionary" to apply artifactTransformers.
96
+ *
97
+ * @param {object | Array} node the thing that has _prop
98
+ * @param {string|number} _prop the name of the current property
99
+ * @param {object} dict The value of node[_prop]
100
+ */
101
+ function definitions( node, _prop, dict ) {
102
+ csnPath.push( _prop );
103
+ for (const name of Object.getOwnPropertyNames( dict )) {
104
+ const skip = options && options.skipArtifact && options.skipArtifact(dict[name], name) || false;
105
+ if (!skip) {
106
+ artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
107
+ standard( dict, name, dict[name] );
108
+ }
109
+ }
110
+ if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
111
+ setProp(node, `$${_prop}`, dict);
112
+ csnPath.pop();
113
+ }
114
+
115
+ /**
116
+ * Keep looping through the pathRef - because in a .ref we can have .args and .where
117
+ *
118
+ * @param {object | Array} node the thing that has _prop
119
+ * @param {string|number} _prop the name of the current property
120
+ * @param {any} _path The value of node[_prop]
121
+ */
122
+ function pathRef( node, _prop, _path ) {
123
+ csnPath.push( _prop );
124
+ _path.forEach( ( s, i ) => {
125
+ if (s && typeof s === 'object') {
126
+ csnPath.push( i );
127
+ if (options.drillRef) {
128
+ standard(_path, i, s);
129
+ }
130
+ else {
131
+ if (s.args)
132
+ standard( s, 'args', s.args );
133
+ if (s.where)
134
+ standard( s, 'where', s.where );
135
+ }
136
+ csnPath.pop();
137
+ }
138
+ } );
139
+ csnPath.pop();
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Loop through the model, applying the custom transformations on the node's matching.
145
+ *
146
+ * Each transformer gets:
147
+ * - the parent having the property
148
+ * - the name of the property
149
+ * - the value of the property
150
+ * - the path to the property
151
+ *
152
+ * @param {object} csn CSN to enrich in-place
153
+ * @param {object} customTransformers Map of _prop to transform and function to apply
154
+ * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
155
+ * @param {boolean} [skipIgnore=true] Wether to skip _ignore elements or not
156
+ * @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
157
+ * @returns {object} CSN with transformations applied
158
+ */
159
+ function applyTransformations( csn, customTransformers = {}, artifactTransformers = [], skipIgnore = true, options = {} ) {
160
+ if (csn && csn.definitions)
161
+ return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, skipIgnore, options);
162
+ return csn;
163
+ }
164
+
165
+
166
+ /**
167
+ * Instead of looping through the whole model, start at a given thing (like an on-condition),
168
+ * as long as it is not a dictionary.
169
+ *
170
+ * Each transformer gets:
171
+ * - the parent having the property
172
+ * - the name of the property
173
+ * - the value of the property
174
+ * - the path to the property
175
+ *
176
+ * @param {object} parent The "parent" of which we transform a property of
177
+ * @param {string} prop The property of parent to start at
178
+ * @param {object} customTransformers Map of prop to transform and function to apply
179
+ * @param {CSN.Path} path Path pointing to parent
180
+ * @returns {object} parent[prop] with transformations applied
181
+ */
182
+ function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, path = []) {
183
+ return applyTransformationsInternal(parent, prop, customTransformers, [], true, {}, path)[prop];
184
+ }
185
+
186
+ module.exports = {
187
+ applyTransformations,
188
+ applyTransformationsOnNonDictionary,
189
+ };
@@ -0,0 +1,389 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ cloneCsn, forEachGeneric,
5
+ forAllElements,
6
+ getUtils,
7
+ } = require('../../model/csnUtils');
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
+
185
+ /**
186
+ * Return a callback function for forEachDefinition that
187
+ *
188
+ * @param {CSN.Model} csn
189
+ * @param {CSN.Options} options
190
+ * @param {string} pathDelimiter
191
+ * @param {object} messageFunctions
192
+ * @param {Function} messageFunctions.error
193
+ * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
194
+ */
195
+ function getForeignKeyElementCreator(csn, options, pathDelimiter, messageFunctions) {
196
+ const { error } = messageFunctions;
197
+ const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
198
+ const {
199
+ isManagedAssociationElement,
200
+ } = getUtils(csn);
201
+
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;
267
+ }
268
+ }
269
+ }
270
+ }
271
+
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
+ }
311
+
312
+ /**
313
+ * Create the foreign key elements for a managed association and build the on-condition
314
+ *
315
+ * @param {CSN.Artifact} artifact
316
+ * @param {string} artifactName
317
+ * @param {Object} elem The association to process
318
+ * @param {string} elemName
319
+ * @param {WeakMap} alreadyHandled To cache which elements were already processed
320
+ * @returns {void}
321
+ */
322
+ function transformManagedAssociation(artifact, artifactName, elem, elemName, alreadyHandled) {
323
+ // No need to run over this - we already did, possibly because it was referenced in the ON-Condition
324
+ // of another association - see a few lines lower
325
+ if (alreadyHandled.has(elem))
326
+ return;
327
+ // Generate foreign key elements for managed associations, and assemble an ON-condition with them
328
+ const onCondParts = [];
329
+ let joinWithAnd = false;
330
+ if (elem.keys.length === 0) {
331
+ elem._ignore = true;
332
+ }
333
+ else {
334
+ for (const foreignKey of elem.keys) {
335
+ // Assemble left hand side of 'assoc.key = fkey'
336
+ const assocKeyArg = {
337
+ ref: [
338
+ elemName,
339
+ ].concat(foreignKey.ref),
340
+ };
341
+ const fkName = `${elemName}${pathDelimiter}${foreignKey.as}`;
342
+ const fKeyArg = {
343
+ ref: [
344
+ fkName,
345
+ ],
346
+ };
347
+
348
+ if (joinWithAnd) { // more than one FK
349
+ onCondParts.push('and');
350
+ }
351
+
352
+ onCondParts.push(
353
+ assocKeyArg
354
+ );
355
+ onCondParts.push('=');
356
+ onCondParts.push(fKeyArg);
357
+
358
+ if (!joinWithAnd)
359
+ joinWithAnd = true;
360
+ }
361
+ elem.on = onCondParts;
362
+ }
363
+
364
+ // If the managed association has a 'key' property => remove it as unmanaged assocs cannot be keys
365
+ // TODO: Are there other modifiers (like 'key') that are valid for managed, but not valid for unmanaged assocs?
366
+ if (elem.key)
367
+ delete elem.key;
368
+
369
+
370
+ // If the managed association has a 'not null' property => remove it
371
+ if (elem.notNull)
372
+ delete elem.notNull;
373
+
374
+
375
+ // The association is now unmanaged, i.e. actually it should no longer have foreign keys
376
+ // 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)
378
+
379
+ // Remember that we already processed this
380
+ alreadyHandled.set(elem, true);
381
+ }
382
+ }
383
+
384
+
385
+ module.exports = {
386
+ getForeignKeyFlattener,
387
+ getForeignKeyElementCreator,
388
+ getManagedAssociationTransformer,
389
+ };