@sap/cds-compiler 2.13.8 → 2.15.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +128 -4
- package/bin/cdsc.js +112 -37
- package/lib/api/main.js +63 -22
- package/lib/api/options.js +2 -3
- package/lib/api/validate.js +6 -6
- package/lib/base/message-registry.js +100 -17
- package/lib/base/messages.js +85 -64
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/validator.js +2 -4
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +11 -0
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +59 -11
- package/lib/compiler/extend.js +20 -3
- package/lib/compiler/finalize-parse-cdl.js +26 -20
- package/lib/compiler/index.js +75 -26
- package/lib/compiler/populate.js +36 -17
- package/lib/compiler/propagator.js +4 -1
- package/lib/compiler/resolve.js +104 -16
- package/lib/compiler/shared.js +61 -27
- package/lib/compiler/tweak-assocs.js +7 -1
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +216 -98
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +499 -423
- package/lib/edm/edmUtils.js +22 -22
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageParser.js +4636 -4368
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +3 -2
- package/lib/json/to-csn.js +0 -2
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +47 -2
- package/lib/language/language.g4 +59 -27
- package/lib/main.d.ts +19 -1
- package/lib/main.js +6 -0
- package/lib/model/csnRefs.js +33 -6
- package/lib/model/csnUtils.js +193 -75
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +2 -2
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +62 -26
- package/lib/render/toCdl.js +844 -679
- package/lib/render/toHdbcds.js +189 -243
- package/lib/render/toSql.js +180 -198
- package/lib/render/utils/common.js +131 -15
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/constraints.js +3 -1
- package/lib/transform/db/expansion.js +15 -10
- package/lib/transform/db/flattening.js +94 -64
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +6 -3
- package/lib/transform/forHanaNew.js +43 -26
- package/lib/transform/forOdataNew.js +43 -42
- package/lib/transform/localized.js +12 -7
- package/lib/transform/odata/toFinalBaseType.js +8 -6
- package/lib/transform/odata/typesExposure.js +145 -197
- package/lib/transform/transformUtilsNew.js +9 -12
- package/lib/transform/translateAssocsToJoins.js +5 -1
- package/lib/transform/universalCsn/coreComputed.js +5 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
- package/lib/utils/moduleResolve.js +13 -6
- package/package.json +1 -1
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- 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,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;
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* In the OData transformer, managed associations produce foreign keys.
|
|
5
|
-
* If an association is also a primary key, the additionally created foreign keys become also primary keys in the parent artifact.
|
|
6
|
-
* Associations with partial foreign keys force other non-key elements and associations to become virtual primary keys.
|
|
7
|
-
* Proper FK generation requires specific order of performing that.
|
|
8
|
-
* This module provides functionality to sort managed associations according to their dependencies.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const {
|
|
12
|
-
forEachDefinition,
|
|
13
|
-
forEachMemberRecursively
|
|
14
|
-
} = require('../../model/csnUtils');
|
|
15
|
-
|
|
16
|
-
const {
|
|
17
|
-
isAssociationOrComposition
|
|
18
|
-
} = require('./utils');
|
|
19
|
-
const { forEach } = require('../../utils/objectUtils.js');
|
|
20
|
-
|
|
21
|
-
function buildDependenciesFor(csn, referenceFlattener, isExternalServiceMember) {
|
|
22
|
-
|
|
23
|
-
let dependencies = {};
|
|
24
|
-
forEachDefinition(csn, (def, definitionName) => {
|
|
25
|
-
/** @type {CSN.Path} */
|
|
26
|
-
let root = ['definitions', definitionName];
|
|
27
|
-
forEachMemberRecursively(def, (element, elementName, structuralNodeName, subpath, parent) => {
|
|
28
|
-
let path = root.concat(subpath);
|
|
29
|
-
// go only through managed associations and compositions
|
|
30
|
-
if (isAssociationOrComposition(element) && element.keys && !element.on) { // check association FKs
|
|
31
|
-
let elementDependencies = []
|
|
32
|
-
element.keys.forEach(iForeignKey => {
|
|
33
|
-
let paths = iForeignKey.$paths;
|
|
34
|
-
if (!paths) return; // invalid references can not be resolved thus no $paths -> test/odataTransformation/negative/ForeignKeys.cds
|
|
35
|
-
let targetElementPath = paths[paths.length - 1];
|
|
36
|
-
if (!targetElementPath) return; // TODO check why
|
|
37
|
-
let targetElement = getElementForPath(csn, targetElementPath);
|
|
38
|
-
if (!targetElement) { // element was moved
|
|
39
|
-
targetElementPath = referenceFlattener.getElementTransition(targetElementPath);
|
|
40
|
-
if (!targetElementPath) return; // TODO check why
|
|
41
|
-
targetElement = getElementForPath(csn, targetElementPath);
|
|
42
|
-
}
|
|
43
|
-
if (targetElement && isAssociationOrComposition(targetElement)) {
|
|
44
|
-
elementDependencies.push(targetElementPath);
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
dependencies[path.join("/")] = { structuralNodeName, definitionName, elementName, element, path, parent, dependencies: elementDependencies };
|
|
48
|
-
}
|
|
49
|
-
}) // forEachMemberRecursively
|
|
50
|
-
}, { skipArtifact: isExternalServiceMember }) // forEachDefinition
|
|
51
|
-
|
|
52
|
-
let result = []; // final list of sorted association items
|
|
53
|
-
let inResult = {}; // paths of associations which were added in the result
|
|
54
|
-
let maxLoops = Object.keys(dependencies).length;
|
|
55
|
-
let loops = 0;
|
|
56
|
-
let done = false;
|
|
57
|
-
// walk over all dependencies and add to the result if all dependents are already in the result
|
|
58
|
-
while (!done) {
|
|
59
|
-
if (loops > maxLoops) {
|
|
60
|
-
throw Error('Failed to process the association dependencies - max loops reached');
|
|
61
|
-
}
|
|
62
|
-
done = true;
|
|
63
|
-
let toDelete = [];
|
|
64
|
-
forEach(dependencies, (name, item) => {
|
|
65
|
-
let countOfUnprocessedDependents = 0;
|
|
66
|
-
item.dependencies.forEach(path => {
|
|
67
|
-
let spath = path.join('/');
|
|
68
|
-
if (!inResult[spath]) countOfUnprocessedDependents++;
|
|
69
|
-
})
|
|
70
|
-
if (countOfUnprocessedDependents === 0) { // all dependents processed?
|
|
71
|
-
let spath = item.path.join('/');
|
|
72
|
-
if (!inResult[spath]) {
|
|
73
|
-
inResult[spath] = true;
|
|
74
|
-
result.push(item);
|
|
75
|
-
}
|
|
76
|
-
toDelete.push(name); // mark association to be removed from the processing list
|
|
77
|
-
} else {
|
|
78
|
-
done = false;
|
|
79
|
-
}
|
|
80
|
-
})
|
|
81
|
-
// delete already processed associations
|
|
82
|
-
toDelete.forEach(name => {
|
|
83
|
-
delete dependencies[name];
|
|
84
|
-
});
|
|
85
|
-
loops++;
|
|
86
|
-
} // while not done
|
|
87
|
-
|
|
88
|
-
// check if all dependencies were processed
|
|
89
|
-
if (Object.keys(dependencies).length !== 0) {
|
|
90
|
-
throw Error('Failed to process the association dependencies - there are more dependencies left');
|
|
91
|
-
}
|
|
92
|
-
return result;
|
|
93
|
-
|
|
94
|
-
function getElementForPath(node, path) {
|
|
95
|
-
path.forEach(name => {
|
|
96
|
-
if (!node) return;
|
|
97
|
-
node = node[name];
|
|
98
|
-
})
|
|
99
|
-
return node;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
module.exports = buildDependenciesFor
|
|
105
|
-
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// The module traverses a given CSN using a specific path, collects structural node names and returns them.
|
|
2
|
-
|
|
3
|
-
const structuralNodeHandlers = {
|
|
4
|
-
definitions: traverseDict,
|
|
5
|
-
elements: traverseDict,
|
|
6
|
-
actions: traverseDict,
|
|
7
|
-
params: traverseDict,
|
|
8
|
-
items: traverseTyped,
|
|
9
|
-
enum: traverseDict,
|
|
10
|
-
returns: traverseTyped,
|
|
11
|
-
on: traverseArray,
|
|
12
|
-
keys: traverseArray,
|
|
13
|
-
ref: traverseArray,
|
|
14
|
-
query: traverseTyped,
|
|
15
|
-
SELECT: traverseTyped,
|
|
16
|
-
SET: traverseTyped,
|
|
17
|
-
args: traverseArray,
|
|
18
|
-
columns: traverseArray,
|
|
19
|
-
projection: traverseTyped,
|
|
20
|
-
from: traverseTyped,
|
|
21
|
-
mixin: traverseDict,
|
|
22
|
-
where: traverseArray,
|
|
23
|
-
orderBy: traverseArray,
|
|
24
|
-
groupBy: traverseArray,
|
|
25
|
-
having: traverseArray,
|
|
26
|
-
xpr: traverseArray,
|
|
27
|
-
expand: traverseArray,
|
|
28
|
-
inline: traverseArray,
|
|
29
|
-
cast: traverseTyped,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function structuralPath(csn, path) {
|
|
33
|
-
return traverseDict(csn.definitions, path, 1, ['definitions']);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function traverseArray(obj, path, index, typeStack) {
|
|
37
|
-
if(!Array.isArray(obj)) return typeStack;
|
|
38
|
-
const name = path[index];
|
|
39
|
-
const element = obj[name];
|
|
40
|
-
return traverseTyped(element, path, index+1, typeStack);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function traverseDict(obj, path, index, typeStack) {
|
|
44
|
-
if(typeof obj !== 'object') return typeStack;
|
|
45
|
-
const name = path[index];
|
|
46
|
-
if(name === undefined) return typeStack;
|
|
47
|
-
return traverseTyped(obj[name], path, index+1, typeStack);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function traverseDictArray(obj, path, index, typeStack) {
|
|
51
|
-
if(typeof obj !== 'object') return typeStack;
|
|
52
|
-
const name = path[index];
|
|
53
|
-
if(name === undefined) return typeStack;
|
|
54
|
-
return traverseArray(obj[name], path, index+1, typeStack);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function traverseTyped(obj, path, index, typeStack) {
|
|
58
|
-
if(!obj) return typeStack;
|
|
59
|
-
const name = path[index];
|
|
60
|
-
if(name === undefined) return typeStack;
|
|
61
|
-
if(name[0] === '@') return typeStack; // skip annotations
|
|
62
|
-
const func = structuralNodeHandlers[name];
|
|
63
|
-
if(func) return func(obj[name], path, index+1, typeStack.concat(name));
|
|
64
|
-
// not typed -> columns?
|
|
65
|
-
if(typeStack[typeStack.length-1] === 'columns')
|
|
66
|
-
return traverseDictArray(obj, path, index, typeStack);
|
|
67
|
-
return typeStack;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
module.exports = {
|
|
71
|
-
structuralPath
|
|
72
|
-
}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { copyAnnotations } = require('../../model/csnUtils');
|
|
4
|
-
const { cloneCsn, forEachDefinition } = require('../../model/csnUtils');
|
|
5
|
-
const { attachPathOnPartialCSN } = require('./attachPath');
|
|
6
|
-
|
|
7
|
-
// These functions are used for propagation of the annotations, virtual, key,
|
|
8
|
-
// notNull attributes collected along the path during flattening.
|
|
9
|
-
const { addPropsForPropagationFromElement, propagatePropsToElement, resetPropsForPropagation } = function () {
|
|
10
|
-
let toBePropagated = Object.create(null);
|
|
11
|
-
return {
|
|
12
|
-
addPropsForPropagationFromElement: function (element) {
|
|
13
|
-
copyAnnotations(element, toBePropagated);
|
|
14
|
-
if (element.virtual) toBePropagated.virtual = element.virtual;
|
|
15
|
-
if (element.key) toBePropagated.key = element.key;
|
|
16
|
-
},
|
|
17
|
-
propagatePropsToElement: function (element) {
|
|
18
|
-
copyAnnotations(toBePropagated, element);
|
|
19
|
-
if (toBePropagated.virtual) element.virtual = toBePropagated.virtual;
|
|
20
|
-
if (toBePropagated.key) element.key = toBePropagated.key;
|
|
21
|
-
},
|
|
22
|
-
resetPropsForPropagation: function () {
|
|
23
|
-
toBePropagated = Object.create(null);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}();
|
|
27
|
-
|
|
28
|
-
// keep here the state of the 'notNull' attribute
|
|
29
|
-
// this is needed because during flattening all the elements
|
|
30
|
-
// along the chain need to be assigned with not null so
|
|
31
|
-
// the resulting element to be not null as well
|
|
32
|
-
const { isNotNull, setNotNull, setUpNotNull } = function () {
|
|
33
|
-
let notNull = undefined;
|
|
34
|
-
return {
|
|
35
|
-
isNotNull: function () {
|
|
36
|
-
return notNull;
|
|
37
|
-
},
|
|
38
|
-
setNotNull: function (value) {
|
|
39
|
-
notNull = value;
|
|
40
|
-
},
|
|
41
|
-
setUpNotNull: function (element, isParentNotNull) {
|
|
42
|
-
if (isParentNotNull && element.notNull) setNotNull(element.notNull);
|
|
43
|
-
else if (isNotNull() && !element.notNull || (isNotNull() === false && element.notNull !== false)) setNotNull(undefined);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}();
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* During the OData transformations in flat-mode, all structured elements will be flattened.
|
|
50
|
-
* This module performs the complete flattening.
|
|
51
|
-
* It also provides information to the reference flattener: elements produced for specific path in the CSN structure.
|
|
52
|
-
* @param {CSN.Model} csn CSN-object to flatten
|
|
53
|
-
* @param {*} csnUtils instances of utility functions
|
|
54
|
-
* @param {*} options
|
|
55
|
-
* @param {*} referenceFlattener
|
|
56
|
-
* @param {Function} error
|
|
57
|
-
* @param {Function} isExternalServiceMember returns true for an artifact that is part of an external service
|
|
58
|
-
*/
|
|
59
|
-
function flattenCSN(csn, csnUtils, options, referenceFlattener, error, isExternalServiceMember) {
|
|
60
|
-
forEachDefinition(csn, (def, defName, propertyName, path) =>
|
|
61
|
-
flattenDefinition(def, path, csnUtils, options, referenceFlattener, error), { skipArtifact: isExternalServiceMember });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Flattens one single definition and all structures in it. Modifies the definition in place.
|
|
66
|
-
* @param {CSN.Definition} definition definition object to flatten
|
|
67
|
-
* @param {CSN.Path} definitionPath path in CSN object
|
|
68
|
-
* @param {*} csnUtils utility functions
|
|
69
|
-
* @param {*} options
|
|
70
|
-
* @param {*} referenceFlattener
|
|
71
|
-
* @param {Function} error
|
|
72
|
-
*/
|
|
73
|
-
function flattenDefinition(definition, definitionPath, csnUtils, options, referenceFlattener, error) {
|
|
74
|
-
if (definition.kind !== 'entity')
|
|
75
|
-
return;
|
|
76
|
-
|
|
77
|
-
let { newFlatElements } = flattenStructure(definition.elements, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
78
|
-
|
|
79
|
-
attachPathOnPartialCSN(newFlatElements, definitionPath.concat('elements'));
|
|
80
|
-
definition.elements = newFlatElements;
|
|
81
|
-
|
|
82
|
-
if (definition.params) {
|
|
83
|
-
let { newFlatElements } = flattenStructure(definition.params, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
84
|
-
attachPathOnPartialCSN(newFlatElements, definitionPath.concat('params'));
|
|
85
|
-
definition.params = newFlatElements;
|
|
86
|
-
}
|
|
87
|
-
} // flattenDefinition
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Flattens structured element by calling element flattener for each structured child.
|
|
91
|
-
* Returns a dictionary containing all the new elements for the given structure.
|
|
92
|
-
* @param {*} dictionary to flatten
|
|
93
|
-
* @param {CSN.Path} path the path of the structure in the CSN tree
|
|
94
|
-
* @param {*} csnUtils
|
|
95
|
-
* @param {Function} error Error message function
|
|
96
|
-
* @param {*} [referenceFlattener]
|
|
97
|
-
* @param {string[]} [elementPathInStructure] list of parent element names
|
|
98
|
-
* @param {*} [newFlatElements]
|
|
99
|
-
* @param {boolean} [isTopLevelElement] states if this is a top level element
|
|
100
|
-
*/
|
|
101
|
-
function flattenStructure(dictionary, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
|
|
102
|
-
newFlatElements = Object.create(null), isTopLevelElement = true, isParentNotNull = false) {
|
|
103
|
-
|
|
104
|
-
if (!isTopLevelElement) addPropsForPropagationFromElement(dictionary);
|
|
105
|
-
|
|
106
|
-
let generatedNewFlatElementsNames = []; // holds the names of all new child elements of the structure
|
|
107
|
-
|
|
108
|
-
dictionary && Object.entries(dictionary).forEach(([elementName, element]) => {
|
|
109
|
-
let currPath = path.concat('elements', elementName);
|
|
110
|
-
|
|
111
|
-
if (isTopLevelElement) {
|
|
112
|
-
resetPropsForPropagation();
|
|
113
|
-
setNotNull(element.notNull)
|
|
114
|
-
} else {
|
|
115
|
-
setUpNotNull(element, isParentNotNull);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// flat elements when structured and NOT empty (allow incomplete structures - cds-compiler#4337)
|
|
119
|
-
if (csnUtils.isStructured(element) && !(element.elements && Object.keys(element.elements).length === 0)) {
|
|
120
|
-
|
|
121
|
-
if (referenceFlattener) referenceFlattener.registerFlattenedElement(currPath, element.$path);
|
|
122
|
-
|
|
123
|
-
addPropsForPropagationFromElement(element);
|
|
124
|
-
|
|
125
|
-
// if the child element is structured itself -> needs to be flattened
|
|
126
|
-
const elements = element.elements || csnUtils.getFinalBaseType(element.type).elements;
|
|
127
|
-
let result = flattenStructure(elements, currPath, csnUtils, options, error, referenceFlattener, elementPathInStructure.concat(elementName), newFlatElements, false, isNotNull());
|
|
128
|
-
generatedNewFlatElementsNames.push(...result.generatedNewFlatElementsNames); // accomulate names of produced elements
|
|
129
|
-
|
|
130
|
-
} else { // when we do not need to flat, this is scalar or empty (cds-compiler#4337) -> needs to be registered in referenceFlattener
|
|
131
|
-
let newElementName = elementPathInStructure.concat(elementName).join('_');
|
|
132
|
-
let elementNameWithDots = elementPathInStructure.concat(elementName).join('.');
|
|
133
|
-
addNewElementToResult(element, newElementName, elementNameWithDots, currPath);
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
if (referenceFlattener) {
|
|
138
|
-
referenceFlattener.registerGeneratedElementsForPath(path, generatedNewFlatElementsNames);
|
|
139
|
-
}
|
|
140
|
-
return { newFlatElements, generatedNewFlatElementsNames };
|
|
141
|
-
|
|
142
|
-
// adds newly created element into the final dictionary of elements
|
|
143
|
-
function addNewElementToResult(element, elementName, elementNameWithDots, path) {
|
|
144
|
-
if (newFlatElements[elementName]) {
|
|
145
|
-
error(null, path, `Generated element ${elementName} conflicts with other generated element`);
|
|
146
|
-
} else {
|
|
147
|
-
let newPath = path.slice(0, 2).concat('elements', elementName);
|
|
148
|
-
let newElement = createNewElement(element, elementNameWithDots, newPath);
|
|
149
|
-
newFlatElements[elementName] = newElement;
|
|
150
|
-
generatedNewFlatElementsNames.push(elementName);
|
|
151
|
-
|
|
152
|
-
if (referenceFlattener) {
|
|
153
|
-
referenceFlattener.registerElementTransition(path, newPath);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
} // addNewElementToResult
|
|
157
|
-
|
|
158
|
-
// creates new element by copying the properties of the originating element
|
|
159
|
-
function createNewElement(element, elementNameWithDots, path) {
|
|
160
|
-
let newElement = cloneCsn(element, options);
|
|
161
|
-
if (!isTopLevelElement) propagatePropsToElement(newElement);
|
|
162
|
-
if (isNotNull() === undefined) delete newElement.notNull;
|
|
163
|
-
if (!isTopLevelElement && referenceFlattener) {
|
|
164
|
-
referenceFlattener.setElementNameWithDots(path, elementNameWithDots);
|
|
165
|
-
}
|
|
166
|
-
return newElement;
|
|
167
|
-
} // createNewElement
|
|
168
|
-
|
|
169
|
-
} // flattenStructure
|
|
170
|
-
|
|
171
|
-
module.exports = { flattenCSN, flattenStructure };
|