@sap/cds-compiler 2.13.8 → 3.0.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 +155 -1594
- package/bin/cdsc.js +144 -66
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +3 -4
- package/doc/CHANGELOG_DEPRECATED.md +35 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +237 -122
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +12 -16
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +152 -37
- package/lib/base/messages.js +145 -83
- package/lib/base/model.js +44 -2
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +5 -1
- package/lib/checks/types.js +4 -2
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +4 -5
- package/lib/compiler/assert-consistency.js +16 -10
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +98 -9
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +61 -13
- package/lib/compiler/extend.js +79 -14
- package/lib/compiler/finalize-parse-cdl.js +46 -29
- package/lib/compiler/index.js +100 -37
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +19 -18
- package/lib/compiler/propagator.js +7 -4
- package/lib/compiler/resolve.js +297 -234
- package/lib/compiler/shared.js +107 -102
- package/lib/compiler/tweak-assocs.js +16 -11
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +230 -115
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +509 -438
- package/lib/edm/edmUtils.js +31 -45
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +10 -30
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +889 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20786 -22199
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +59 -51
- package/lib/json/to-csn.js +10 -10
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +62 -39
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +348 -229
- package/lib/language/language.g4 +629 -653
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +46 -43
- package/lib/main.js +108 -79
- package/lib/model/csnRefs.js +34 -7
- package/lib/model/csnUtils.js +337 -332
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +30 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +73 -46
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +1042 -882
- package/lib/render/toHdbcds.js +195 -245
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +225 -241
- package/lib/render/utils/common.js +145 -15
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +4 -2
- package/lib/transform/db/expansion.js +22 -16
- package/lib/transform/db/flattening.js +109 -80
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +9 -6
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +62 -48
- package/lib/transform/forOdataNew.js +49 -50
- package/lib/transform/localized.js +31 -20
- package/lib/transform/odata/toFinalBaseType.js +16 -14
- package/lib/transform/odata/typesExposure.js +146 -198
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +67 -84
- package/lib/transform/translateAssocsToJoins.js +7 -3
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +16 -9
- package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
- package/lib/utils/file.js +3 -3
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/fix_antlr4-8_warning.js +0 -56
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { forEachManagedAssociation } = require('./utils');
|
|
4
|
-
const { attachPath, attachPathOnPartialCSN } = require('./attachPath');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* This module runs through the model and for each managed association in it,
|
|
8
|
-
* in case the foreign keys are structured, it is expanding them. Example:
|
|
9
|
-
* entity A {
|
|
10
|
-
* toB: association to B { stru };
|
|
11
|
-
* } // -> CSN: keys:[ { ref:['stru'] } ]
|
|
12
|
-
*
|
|
13
|
-
* entity B {
|
|
14
|
-
* stru: {
|
|
15
|
-
* subid: Integer;
|
|
16
|
-
* }
|
|
17
|
-
* }
|
|
18
|
-
* after expand -> keys:[ { ref: ['stru_subid'] } ]
|
|
19
|
-
*/
|
|
20
|
-
module.exports = function (csn, referenceFlattener, csnUtils, isExternalServiceMember) {
|
|
21
|
-
|
|
22
|
-
forEachManagedAssociation(csn, (element) => {
|
|
23
|
-
if (element.keys) {
|
|
24
|
-
expandStructuredKeysForAssociation(element, referenceFlattener);
|
|
25
|
-
}
|
|
26
|
-
}, isExternalServiceMember);
|
|
27
|
-
|
|
28
|
-
// update paths and resolve references
|
|
29
|
-
attachPath(csn);
|
|
30
|
-
referenceFlattener.resolveAllReferences(csn, csnUtils.inspectRef, csnUtils.isStructured);
|
|
31
|
-
|
|
32
|
-
function expandStructuredKeysForAssociation(assoc, referenceFlattener) {
|
|
33
|
-
let newKeys = [];
|
|
34
|
-
for (let key of assoc.keys) {
|
|
35
|
-
// when are assigned $paths and when not???
|
|
36
|
-
let paths = key.$paths;
|
|
37
|
-
if (paths) {
|
|
38
|
-
let lastPath = paths[paths.length - 1];
|
|
39
|
-
let generatedElements = referenceFlattener.getGeneratedElementsForPath(lastPath);
|
|
40
|
-
if (generatedElements) {
|
|
41
|
-
generatedElements.forEach(elementName => {
|
|
42
|
-
let newRef = { ref: [elementName] };
|
|
43
|
-
if (key.as) {
|
|
44
|
-
newRef.as = elementName.replace(key.ref[0], key.as);
|
|
45
|
-
}
|
|
46
|
-
newKeys.push(newRef);
|
|
47
|
-
})
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
newKeys.push(key);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (newKeys.length) {
|
|
55
|
-
attachPathOnPartialCSN(newKeys, assoc.$path.concat('keys'));
|
|
56
|
-
assoc.keys = newKeys;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The module handles the processing of foreign key for managed associations.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { copyAnnotations } = require('../../model/csnUtils');
|
|
8
|
-
const sortByAssociationDependency = require('./sortByAssociationDependency');
|
|
9
|
-
const { flattenStructure } = require('./structureFlattener');
|
|
10
|
-
const { setProp } = require('../../base/model');
|
|
11
|
-
const { implicitAs } = require('../../model/csnRefs');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {CSN.Model} csn
|
|
16
|
-
* @param {*} options
|
|
17
|
-
* @param {*} referenceFlattener
|
|
18
|
-
* @param {*} csnUtils
|
|
19
|
-
* @param {object} error;
|
|
20
|
-
*/
|
|
21
|
-
module.exports = function (csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember) {
|
|
22
|
-
|
|
23
|
-
const structuredOData = options.toOdata.odataFormat === 'structured' && options.toOdata.version === 'v4';
|
|
24
|
-
const flatKeys = !structuredOData || (structuredOData && options.toOdata.odataForeignKeys);
|
|
25
|
-
|
|
26
|
-
// sort all associations by their dependencies
|
|
27
|
-
const sortedAssociations = sortByAssociationDependency(csn, referenceFlattener, isExternalServiceMember);
|
|
28
|
-
|
|
29
|
-
// generate foreign keys
|
|
30
|
-
processSortedAssociations(sortedAssociations, flatKeys);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
function processSortedAssociations(sortedAssociations, flatKeys,) {
|
|
34
|
-
// The map will collect all generated foreign key names for the specific path
|
|
35
|
-
let generatedForeignKeyNamesForPath = Object.create(null); // map<path,[key-name]>
|
|
36
|
-
|
|
37
|
-
sortedAssociations.forEach(item => {
|
|
38
|
-
const { definitionName, structuralNodeName, elementName, element, parent, path } = item;
|
|
39
|
-
|
|
40
|
-
if (csnUtils.isManagedAssociation(element) && element.keys) {
|
|
41
|
-
if (flatKeys) // tackling the ref value in assoc.keys
|
|
42
|
-
takeoverForeignKeysOfTargetAssociations(element, path, generatedForeignKeyNamesForPath);
|
|
43
|
-
// TODO: move in separate function
|
|
44
|
-
fixCardinality(element);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let arrayOfGeneratedForeignKeyNames = generateForeignKeys(definitionName, structuralNodeName, elementName, element, parent, path);
|
|
48
|
-
generatedForeignKeyNamesForPath[item.path.join('/')] = arrayOfGeneratedForeignKeyNames;
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* if a key is an association and it poins to another association,
|
|
55
|
-
* the foreign keys of the target association become primary keys
|
|
56
|
-
* in the current association
|
|
57
|
-
*/
|
|
58
|
-
function takeoverForeignKeysOfTargetAssociations(assoc, path, generatedForeignKeyNamesForPath) {
|
|
59
|
-
let newResult = [];
|
|
60
|
-
assoc.keys.forEach( (key, keyIndex) => {
|
|
61
|
-
let keyPath = path.concat('keys', keyIndex);
|
|
62
|
-
let resolved = csnUtils.inspectRef(keyPath)
|
|
63
|
-
let targetElement = resolved.art;
|
|
64
|
-
if (targetElement) {
|
|
65
|
-
if (csnUtils.isAssociation(targetElement.type)) {
|
|
66
|
-
// association key
|
|
67
|
-
expandAssociationKey(key);
|
|
68
|
-
} else {
|
|
69
|
-
newResult.push(key);
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
// target element does not exist, warning is already reported, pass the key anyway
|
|
73
|
-
newResult.push(key);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
function expandAssociationKey(key) {
|
|
78
|
-
let paths = key.$paths;
|
|
79
|
-
if (!paths) return;
|
|
80
|
-
let lastPath = paths[paths.length - 1];
|
|
81
|
-
let transitionPath = referenceFlattener.getElementTransition(lastPath)
|
|
82
|
-
if (transitionPath)
|
|
83
|
-
lastPath = transitionPath;
|
|
84
|
-
let generatedKeys = generatedForeignKeyNamesForPath[lastPath.join('/')];
|
|
85
|
-
if (!generatedKeys) return;
|
|
86
|
-
generatedKeys.forEach(fkName => {
|
|
87
|
-
let newFkRef = { ref: [fkName] };
|
|
88
|
-
if (key.as) {
|
|
89
|
-
let alias = fkName.replace(key.ref[0], key.as);
|
|
90
|
-
setProp(newFkRef, 'as', alias);
|
|
91
|
-
}
|
|
92
|
-
newResult.push(newFkRef);
|
|
93
|
-
})
|
|
94
|
-
} // expandAssociationKey
|
|
95
|
-
|
|
96
|
-
assoc.keys = newResult;
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function fixCardinality(assoc) {
|
|
101
|
-
if (assoc.notNull) {
|
|
102
|
-
if (!assoc.cardinality) {
|
|
103
|
-
assoc.cardinality = {};
|
|
104
|
-
}
|
|
105
|
-
if (assoc.cardinality.min === undefined) {
|
|
106
|
-
assoc.cardinality.min = 1;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Generates foreign keys and returns their names as an array
|
|
113
|
-
*/
|
|
114
|
-
function generateForeignKeys(definitionName, structuralNodeName, assocName, assoc, parent, path) {
|
|
115
|
-
let foreignKeyElements = Object.create(null);
|
|
116
|
-
|
|
117
|
-
// First, loop over the keys array of the association and generate the FKs.
|
|
118
|
-
// The result of all the FKs for the given association is accumulated
|
|
119
|
-
// in the 'foreignKeyElements' dictionary
|
|
120
|
-
assoc.keys.forEach( (key, keyIndex) => {
|
|
121
|
-
let keyPath = path.concat('keys', keyIndex);
|
|
122
|
-
|
|
123
|
-
let foreignKeyElementsForKey = generateForeignKeysForRef(assoc, assocName, key, keyPath);
|
|
124
|
-
Object.assign(foreignKeyElements, foreignKeyElementsForKey);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// After that, add the new elements to the definition.
|
|
128
|
-
// At the same time:
|
|
129
|
-
// -> Check for coliding element's name
|
|
130
|
-
// &
|
|
131
|
-
// -> Propagate annotations from the association
|
|
132
|
-
if (parent.items) // proceed to items of such
|
|
133
|
-
parent = parent.items;
|
|
134
|
-
if (parent.returns)
|
|
135
|
-
parent = parent.returns.items || parent.returns;
|
|
136
|
-
|
|
137
|
-
const dictionary = parent[structuralNodeName];
|
|
138
|
-
let currElementsNames = Object.keys(parent[structuralNodeName]);
|
|
139
|
-
for (const [foreignKeyName, foreignKey] of Object.entries(foreignKeyElements)) {
|
|
140
|
-
copyAnnotations(assoc, foreignKey, true);
|
|
141
|
-
// Insert artificial element into artifact, with all cross-links
|
|
142
|
-
if (dictionary[foreignKeyName]) {
|
|
143
|
-
if (!(dictionary[foreignKeyName]['@odata.foreignKey4'] || isDeepEqual(dictionary[foreignKeyName], foreignKey))) {
|
|
144
|
-
const path = dictionary[foreignKeyName].$path;
|
|
145
|
-
error(null, path, { name: foreignKeyName, art: assocName }, 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// make sure the generated foreign key(s) is added right after the association (that it belongs to) in the elements dictionary
|
|
151
|
-
const assocIndex = currElementsNames.findIndex(elemName => elemName === assocName);
|
|
152
|
-
// if (flatKeys)
|
|
153
|
-
currElementsNames.splice(assocIndex + 1, 0, ...Object.keys(foreignKeyElements));
|
|
154
|
-
|
|
155
|
-
parent[structuralNodeName] = currElementsNames.reduce((previous, name) => {
|
|
156
|
-
previous[name] = dictionary[name] || foreignKeyElements[name];
|
|
157
|
-
return previous;
|
|
158
|
-
}, Object.create(null));
|
|
159
|
-
|
|
160
|
-
return Object.keys(foreignKeyElements);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// FIXME: Very similar code to
|
|
164
|
-
// transformUtilsNew::getForeignKeyArtifact & createForeignKeyElement
|
|
165
|
-
// Can this be streamlined?
|
|
166
|
-
function generateForeignKeysForRef(assoc, assocName, foreignKeyRef, pathInKeysArr, foreignKey4 = assocName) {
|
|
167
|
-
// in structured OData, might be more than one generated FKs
|
|
168
|
-
let generatedFks = Object.create(null);
|
|
169
|
-
const fkArtifact = csnUtils.inspectRef(pathInKeysArr).art;
|
|
170
|
-
if(fkArtifact) {
|
|
171
|
-
if (csnUtils.isStructured(fkArtifact)) {
|
|
172
|
-
processStucturedKey(fkArtifact, assocName, foreignKeyRef);
|
|
173
|
-
} else {
|
|
174
|
-
// built-in
|
|
175
|
-
const foreignKeyElementName = `${assocName.replace(/\./g, '_')}_${foreignKeyRef.as || foreignKeyRef.ref.join('_')}`;
|
|
176
|
-
newForeignKey(fkArtifact, foreignKeyElementName);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return generatedFks;
|
|
181
|
-
|
|
182
|
-
function processStucturedKey(fkArtifact, assocName, foreignKeyRef) {
|
|
183
|
-
const subStruct = fkArtifact.elements ? fkArtifact : csnUtils.getFinalBaseType(fkArtifact.type);
|
|
184
|
-
const flatElements = flattenStructure(subStruct.elements, subStruct.$path, csnUtils, options, error, undefined, fkArtifact.$path.slice(-1) || []).newFlatElements;
|
|
185
|
-
for (const [flatElemName, flatElem] of Object.entries(flatElements)) {
|
|
186
|
-
const foreignKeyElementName =
|
|
187
|
-
`${assocName.replace(/\./g, '_')}_${foreignKeyRef.as ? flatElemName.replace(implicitAs(foreignKeyRef.ref), foreignKeyRef.as) : flatElemName}`;
|
|
188
|
-
newForeignKey(flatElem, foreignKeyElementName);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function newForeignKey(fkArtifact, foreignKeyElementName) {
|
|
193
|
-
if (fkArtifact.type === 'cds.Association' || fkArtifact.type === 'cds.Composition') {
|
|
194
|
-
processAssociationOrComposition(fkArtifact, foreignKeyElementName);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// FIXME: better use transformUtlsNew::createRealFK(...);
|
|
199
|
-
let foreignKeyElement = Object.create(null);
|
|
200
|
-
|
|
201
|
-
// Transfer selected type properties from target key element
|
|
202
|
-
// FIXME: There is currently no other way but to treat the annotation '@odata.Type' as a type property.
|
|
203
|
-
for (let prop of ['type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type']) {
|
|
204
|
-
if (fkArtifact[prop] !== undefined) {
|
|
205
|
-
foreignKeyElement[prop] = fkArtifact[prop];
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
// If the association is non-fkArtifact resp. key, so should be the foreign key field
|
|
209
|
-
for (let prop of ['notNull', 'key']) {
|
|
210
|
-
if (assoc[prop] !== undefined) {
|
|
211
|
-
foreignKeyElement[prop] = assoc[prop];
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
foreignKeyElement['@odata.foreignKey4'] = foreignKey4;
|
|
216
|
-
if (flatKeys) foreignKeyRef.$generatedFieldName = foreignKeyElementName;
|
|
217
|
-
setProp(foreignKeyElement, '$path', pathInKeysArr); // attach $path to the newly created element - used for inspectRef in processAssociationOrComposition
|
|
218
|
-
if (assoc.$location) {
|
|
219
|
-
setProp(foreignKeyElement, '$location', assoc.$location);
|
|
220
|
-
}
|
|
221
|
-
generatedFks[foreignKeyElementName] = foreignKeyElement;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function processAssociationOrComposition(fkArtifact, foreignKeyElementName) {
|
|
225
|
-
fkArtifact.keys.forEach((keyRef,keyId) => {
|
|
226
|
-
const path = fkArtifact.$path.concat('keys').concat(keyId);
|
|
227
|
-
const fksForAssoc = generateForeignKeysForRef(assoc, foreignKeyElementName, keyRef, path, foreignKey4);
|
|
228
|
-
Object.assign(generatedFks, fksForAssoc);
|
|
229
|
-
})
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
*
|
|
236
|
-
* @param {object} obj
|
|
237
|
-
* @param {*} other
|
|
238
|
-
* @returns {boolean} Whether 'obj' and 'other' are deeply equal. We need the
|
|
239
|
-
* deep comparison because of annotations that have structured values and they
|
|
240
|
-
* are propagated to the generated foreign keys.
|
|
241
|
-
*/
|
|
242
|
-
function isDeepEqual(obj, other) {
|
|
243
|
-
const objectKeys = Object.keys(obj);
|
|
244
|
-
const otherKeys = Object.keys(other);
|
|
245
|
-
|
|
246
|
-
if (objectKeys.length !== otherKeys.length)
|
|
247
|
-
return false;
|
|
248
|
-
|
|
249
|
-
for (let key of objectKeys) {
|
|
250
|
-
const areValuesObjects = (obj[key] != null && typeof obj[key] === 'object')
|
|
251
|
-
&& (other[key] !== null && typeof other[key] === 'object');
|
|
252
|
-
|
|
253
|
-
if (areValuesObjects) {
|
|
254
|
-
if (!isDeepEqual(obj[key], other[key]))
|
|
255
|
-
return false;
|
|
256
|
-
} else if (obj[key] !== other[key]) {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
const { applyTransformations } = require('../../model/csnUtils');
|
|
2
|
-
const { setProp } = require('../../base/model');
|
|
3
|
-
const { implicitAs } = require('../../model/csnRefs');
|
|
4
|
-
const { structuralPath } = require('./structuralPath');
|
|
5
|
-
|
|
6
|
-
/** This class is used for generic reference flattening.
|
|
7
|
-
* It provides the following functionality:
|
|
8
|
-
* - attach the CSN location paths of object as $path property
|
|
9
|
-
* - resolve all references found in the CSN
|
|
10
|
-
* The resolved references and their paths are stored in the $paths property which is attached to the reference
|
|
11
|
-
* In addition, the items of the references will be checked if they point to structured elements,
|
|
12
|
-
* and the result will be cached in the "structuredReference" member variable.
|
|
13
|
-
* - register flattened elements - stored in the member variable "flattenedElementPaths"
|
|
14
|
-
* - check if a specific path points to a structured element
|
|
15
|
-
* - flatten of all references - combines already stored information to flatten the references
|
|
16
|
-
*
|
|
17
|
-
* Generic reference flattening works as follows:
|
|
18
|
-
* - resolve all references and there the single items
|
|
19
|
-
* - attach the resolved paths to the elements
|
|
20
|
-
* - during flattening of elements, transformers register all flattened paths
|
|
21
|
-
* - final flattening detects reference items which point to flattened elements
|
|
22
|
-
* and joins both previous and current reference items
|
|
23
|
-
*
|
|
24
|
-
* Element transitions - when a structure is flattened, elements are moved from one location to another.
|
|
25
|
-
* Those transitions are registered in elementTransitions, where the origin path as key is mapped to the new path location.
|
|
26
|
-
* Used structure: elementTransitions[fromPath.toString()]=toPath.asPath()
|
|
27
|
-
* The key is the origin path joined with slash, new path is stored as path - array of strings.
|
|
28
|
-
* The corresponding functions are: registerElementTransition and getElementTransition.
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
class ReferenceFlattener {
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Reference flattening helper.
|
|
35
|
-
* @constructor
|
|
36
|
-
*/
|
|
37
|
-
constructor() {
|
|
38
|
-
this.flattenedElementPaths = {};
|
|
39
|
-
this.structuredReference = {};
|
|
40
|
-
this.generatedElementsForPath = {};
|
|
41
|
-
this.elementTransitions = {};
|
|
42
|
-
this.elementNamesWithDots = {};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Resolves all references in the specified CSN and attaches the paths of resolved reference items
|
|
47
|
-
* as non-enumerable array of paths property called $paths.
|
|
48
|
-
* In addition stores information in structuredReference about each item in a reference if it points to a structered element in the CSN.
|
|
49
|
-
*
|
|
50
|
-
* @param {*} csn Specifies the CSN to process.
|
|
51
|
-
* @param {*} inspectRef Callback function performing the inspection of the references.
|
|
52
|
-
* @param {*} isStructured Callback function checking of an artifact is a structured element.
|
|
53
|
-
*/
|
|
54
|
-
resolveAllReferences(csn, inspectRef, isStructured) {
|
|
55
|
-
applyTransformations(csn, {
|
|
56
|
-
ref: (node, prop, _ref, path) => {
|
|
57
|
-
if (!path) return;
|
|
58
|
-
let resolved;
|
|
59
|
-
try {
|
|
60
|
-
resolved = inspectRef(path);
|
|
61
|
-
} catch (ex) {
|
|
62
|
-
return; // TODO: fix tests: throw Error("Could not inspectRef: " + path.join("/"));
|
|
63
|
-
}
|
|
64
|
-
if (!resolved)
|
|
65
|
-
return; // TODO: fix tests: throw Error("Could not resolve: " + path.join("/"));
|
|
66
|
-
if (!resolved.links)
|
|
67
|
-
return; // TODO: fix tests: throw Error("Could not resolve links: " + path.join("/"));
|
|
68
|
-
let paths = [];
|
|
69
|
-
resolved.links.forEach((element) => {
|
|
70
|
-
if (!element.art)
|
|
71
|
-
paths = undefined; // not resolved -> no paths
|
|
72
|
-
if (paths) {
|
|
73
|
-
paths.push(element.art.$path);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
if (paths)
|
|
77
|
-
setProp(node, '$paths', paths);
|
|
78
|
-
// cache if structured or not
|
|
79
|
-
let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
|
|
80
|
-
this.structuredReference[path.join('/')] = structured;
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Checks if the provided path identifies a structured node in a CSN.
|
|
87
|
-
* It reuses the information collected from the resolveAllReferences function.
|
|
88
|
-
*
|
|
89
|
-
* @param {Array.<string>} path Array of node names identifying a node in a CSN.
|
|
90
|
-
* @returns {boolean}
|
|
91
|
-
*/
|
|
92
|
-
isStructured(path) {
|
|
93
|
-
return this.structuredReference[path.join('/')];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* During flattening of structured elements in a CSN,
|
|
98
|
-
* leaf-nodes are moved in the root the the artifact.
|
|
99
|
-
* This information is stored in form of source and target paths.
|
|
100
|
-
*
|
|
101
|
-
* @param {Array.<string>} fromPath Path of the source location.
|
|
102
|
-
* @param {Array.<string>} toPath Path of the destination location.
|
|
103
|
-
*/
|
|
104
|
-
registerElementTransition(fromPath, toPath) {
|
|
105
|
-
let sFromPath = fromPath.join('/');
|
|
106
|
-
let sToPath = toPath.join('/');
|
|
107
|
-
if (sFromPath === sToPath) return; // same path -> no transition
|
|
108
|
-
this.elementTransitions[sFromPath] = toPath;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* For specified path of a node in CSN,
|
|
113
|
-
* returns the transition path where the element was moved during the structure flattening.
|
|
114
|
-
* It reuses the collected information from function registerElementTransition.
|
|
115
|
-
*
|
|
116
|
-
* @param {Array.<string>} path The originating path of the node of interest.
|
|
117
|
-
* @returns {Array.<string>} The path of the target location in case of element transition.
|
|
118
|
-
*/
|
|
119
|
-
getElementTransition(path) {
|
|
120
|
-
let sPath = path.join('/');
|
|
121
|
-
return this.elementTransitions[sPath];
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* During structure flattening inner structures will be joined.
|
|
126
|
-
* Each element joined with its children will be registered using its path in the CSN tree.
|
|
127
|
-
* This information will be used in the reference flattener to identify which items to join.
|
|
128
|
-
*
|
|
129
|
-
* @param {Array.<string>} path Path of the element in the CSN tree.
|
|
130
|
-
* @param {Array.<string>} originPath Path of the originating element.
|
|
131
|
-
*/
|
|
132
|
-
registerFlattenedElement(path, originPath) {
|
|
133
|
-
let spath = path.join('/');
|
|
134
|
-
this.flattenedElementPaths[spath] = true;
|
|
135
|
-
if (originPath) {
|
|
136
|
-
let sOriginPath = originPath.join('/');
|
|
137
|
-
this.flattenedElementPaths[sOriginPath] = true;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* During the flattening of structured elements new elements will be produced.
|
|
143
|
-
* The list of the new elements is stored under the path of the originating element used as key.
|
|
144
|
-
* The information will be used by the foreign key processing module.
|
|
145
|
-
*
|
|
146
|
-
* @param {Array.<string>} path Path of originating element.
|
|
147
|
-
* @param {Array.<string>} elementNames List of generated element names.
|
|
148
|
-
*/
|
|
149
|
-
registerGeneratedElementsForPath(path, elementNames) {
|
|
150
|
-
this.generatedElementsForPath[path.join('/')] = elementNames;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Provides information about all generated elements for specific path in the CSN tree.
|
|
155
|
-
*
|
|
156
|
-
* @param {Array.<string>} path Path of the element to get the generated elements for.
|
|
157
|
-
* @returns {Array.<string>} List of generated names for the specified element path.
|
|
158
|
-
*/
|
|
159
|
-
getGeneratedElementsForPath(path) {
|
|
160
|
-
return this.generatedElementsForPath[path.join('/')];
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
setElementNameWithDots(path, elementNameWithDots) {
|
|
164
|
-
this.elementNamesWithDots[path.join('/')] = elementNameWithDots;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
getElementNameWithDots(path) {
|
|
168
|
-
return this.elementNamesWithDots[path.join('/')];
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Generic reference flattener as {@link ReferenceFlattener described}.
|
|
173
|
-
*
|
|
174
|
-
* @param {*} csn
|
|
175
|
-
*/
|
|
176
|
-
flattenAllReferences(csn) {
|
|
177
|
-
applyTransformations(csn, {
|
|
178
|
-
ref: (node, prop, ref, path) => {
|
|
179
|
-
if (node.$paths) {
|
|
180
|
-
let newRef = []; // flattened reference
|
|
181
|
-
let flattenWithPrevious = false;
|
|
182
|
-
let lastFlattenedID = null; // The variable will be set to the index of the last flattened path
|
|
183
|
-
node.$paths.forEach((path, i) => {
|
|
184
|
-
if (path === undefined || !ref[i]) return;
|
|
185
|
-
let spath = path.join('/');
|
|
186
|
-
let movedTo = this.elementTransitions[spath]; // detect element transition
|
|
187
|
-
let flattened = this.flattenedElementPaths[spath];
|
|
188
|
-
if (flattenWithPrevious) {
|
|
189
|
-
newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + (ref[i].id || ref[i]);
|
|
190
|
-
// if we have a filter or args in an assoc, it needs to be kept, therefore
|
|
191
|
-
// the id of the ref is updated with the flattened version
|
|
192
|
-
if (ref[i].id) {
|
|
193
|
-
ref[i].id = newRef[newRef.length - 1];
|
|
194
|
-
newRef[newRef.length - 1] = ref[i];
|
|
195
|
-
}
|
|
196
|
-
lastFlattenedID = i;
|
|
197
|
-
} else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
|
|
198
|
-
let movedToElementName = movedTo[movedTo.length-1];
|
|
199
|
-
newRef.push(movedToElementName);
|
|
200
|
-
lastFlattenedID = i;
|
|
201
|
-
} else {
|
|
202
|
-
newRef.push(ref[i]);
|
|
203
|
-
}
|
|
204
|
-
flattenWithPrevious = flattened;
|
|
205
|
-
});
|
|
206
|
-
if (newRef !== undefined && lastFlattenedID !== null) { // make sure the reference changed and only then replace it with the new one
|
|
207
|
-
// check if this is a column and add alias if missing
|
|
208
|
-
let structural = structuralPath(csn, path);
|
|
209
|
-
if (isColumnInSelectOrProjection(structural)) {
|
|
210
|
-
if (!node.as && lastFlattenedID === ref.length-1) // attach alias only if there is none and it is the last item in the reference that was flattened
|
|
211
|
-
node.as = node.ref[node.ref.length - 1];
|
|
212
|
-
}
|
|
213
|
-
setProp(newRef, '$path', path.concat('ref'));
|
|
214
|
-
if (!node.as) {
|
|
215
|
-
if (isPartOfKeysStructure(structural))
|
|
216
|
-
node.as = implicitAs(node.ref);
|
|
217
|
-
else
|
|
218
|
-
setProp(node, 'as', implicitAs(node.ref))
|
|
219
|
-
}
|
|
220
|
-
node.ref = newRef;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
})
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
applyAliasesInOnCond(csn, inspectRef) {
|
|
228
|
-
applyTransformations(csn, {
|
|
229
|
-
ref: (node, prop, ref, path) => {
|
|
230
|
-
// Process only on-conditions of associations
|
|
231
|
-
let structural = structuralPath(csn, path);
|
|
232
|
-
if(!isOnCondition(structural)) return;
|
|
233
|
-
let { links } = inspectRef(path);
|
|
234
|
-
if(!links) return; // $user not resolvable
|
|
235
|
-
let keysOfPreviousStepWhenManagedAssoc = undefined;
|
|
236
|
-
|
|
237
|
-
let aliasedRef = [...ref];
|
|
238
|
-
|
|
239
|
-
for (let idx = 0; idx < ref.length; idx++) {
|
|
240
|
-
const currArt = links[idx].art;
|
|
241
|
-
if (keysOfPreviousStepWhenManagedAssoc) {
|
|
242
|
-
const usedKey = keysOfPreviousStepWhenManagedAssoc.find(key => key.ref[0] === ref[idx]);
|
|
243
|
-
if (usedKey && usedKey.as) {
|
|
244
|
-
aliasedRef.splice(idx, usedKey.ref.length, usedKey.as);
|
|
245
|
-
idx += usedKey.ref.length - 1;
|
|
246
|
-
keysOfPreviousStepWhenManagedAssoc = undefined;
|
|
247
|
-
}
|
|
248
|
-
} else {
|
|
249
|
-
keysOfPreviousStepWhenManagedAssoc =
|
|
250
|
-
(currArt.type === 'cds.Association' || currArt.type === 'cds.Composition') && currArt.keys;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
node.ref = aliasedRef;
|
|
254
|
-
}
|
|
255
|
-
})
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Checks if the provided path is a column inside a key node
|
|
261
|
-
* by exploring the possibility of the structural path to ends with 'elements' and 'keys'.
|
|
262
|
-
*
|
|
263
|
-
* @param structural structural path to explore
|
|
264
|
-
* @returns {boolean} True if the provided path matched the requirements to be part of a key node.
|
|
265
|
-
*/
|
|
266
|
-
function isPartOfKeysStructure(structural) {
|
|
267
|
-
return structural[structural.length-2] === 'elements'
|
|
268
|
-
&& structural[structural.length-1] === 'keys';
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Checks if the provided path is a column inside a select or a projection node
|
|
273
|
-
* by exploring the possibility of the structural path to contain 'SELECT' or 'projection'
|
|
274
|
-
* and ends with 'columns' or 'columns' and 'expand'.
|
|
275
|
-
*
|
|
276
|
-
* @param structural structural path to explore
|
|
277
|
-
* @returns {boolean} True if the provided path matched the requirements to be a select node.
|
|
278
|
-
*/
|
|
279
|
-
function isColumnInSelectOrProjection(structural) {
|
|
280
|
-
return (structural.includes('SELECT') || structural.includes('projection'))
|
|
281
|
-
&& (structural[structural.length-1] === 'columns' || (structural[structural.length-2] === 'columns' && structural[structural.length-1] === 'expand'));
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Checks if the provided path is a column inside an on-condition of an element
|
|
286
|
-
* by exploring the possibility of the structural path to ends with 'elements' and 'on'.
|
|
287
|
-
*
|
|
288
|
-
* @param structural structural path to explore
|
|
289
|
-
* @returns {boolean} True if the provided path matched the requirements to be part of an element's on-condition.
|
|
290
|
-
*/
|
|
291
|
-
function isOnCondition(structural) {
|
|
292
|
-
return structural[structural.length-2] === 'elements'
|
|
293
|
-
&& structural[structural.length-1] === 'on';
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
module.exports = ReferenceFlattener;
|