@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.
- package/CHANGELOG.md +136 -0
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +58 -35
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +10 -36
- package/lib/api/options.js +17 -8
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +64 -11
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +6 -4
- package/lib/base/optionProcessorHelper.js +148 -86
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +14 -5
- package/lib/compiler/base.js +64 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +34 -10
- package/lib/compiler/definer.js +91 -112
- package/lib/compiler/index.js +30 -30
- package/lib/compiler/propagator.js +8 -4
- package/lib/compiler/resolver.js +279 -63
- package/lib/compiler/shared.js +65 -230
- package/lib/compiler/utils.js +191 -0
- package/lib/edm/annotations/genericTranslation.js +35 -18
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +4 -3
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +61 -59
- package/lib/edm/edmUtils.js +14 -15
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +19 -1
- package/lib/gen/language.tokens +80 -73
- package/lib/gen/languageLexer.interp +27 -1
- package/lib/gen/languageLexer.js +925 -826
- package/lib/gen/languageLexer.tokens +72 -65
- package/lib/gen/languageParser.js +4817 -4102
- package/lib/json/from-csn.js +57 -26
- package/lib/json/to-csn.js +244 -51
- package/lib/language/antlrParser.js +12 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +106 -30
- package/lib/language/language.g4 +200 -70
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +220 -21
- package/lib/main.js +6 -3
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +218 -86
- package/lib/model/csnUtils.js +99 -178
- package/lib/model/enrichCsn.js +84 -43
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +202 -82
- package/lib/render/toHdbcds.js +194 -135
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +91 -51
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +275 -119
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +10 -9
- package/lib/transform/db/flattening.js +23 -8
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +106 -25
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +90 -1036
- package/lib/transform/forOdataNew.js +11 -3
- package/lib/transform/localized.js +5 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +34 -20
- package/lib/transform/translateAssocsToJoins.js +15 -23
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +13 -6
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +55 -27
- package/package.json +1 -1
- 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
|
+
};
|