@sap/cds-compiler 2.4.4 → 2.10.2
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 +241 -1
- package/bin/.eslintrc.json +17 -0
- package/bin/cds_update_identifiers.js +8 -7
- package/bin/cdsc.js +180 -132
- package/bin/cdshi.js +18 -11
- package/bin/cdsse.js +38 -32
- package/bin/cdsv2m.js +8 -7
- package/doc/CHANGELOG_BETA.md +36 -1
- package/lib/api/main.js +81 -100
- package/lib/api/options.js +17 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/location.js +2 -2
- package/lib/base/message-registry.js +66 -4
- package/lib/base/messages.js +84 -27
- package/lib/base/model.js +2 -61
- package/lib/checks/arrayOfs.js +0 -1
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/enricher.js +8 -2
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +27 -9
- package/lib/checks/selectItems.js +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +66 -13
- package/lib/compiler/assert-consistency.js +24 -12
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +101 -39
- package/lib/compiler/index.js +88 -59
- package/lib/compiler/resolver.js +455 -209
- package/lib/compiler/shared.js +57 -33
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +128 -99
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -127
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +74 -28
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +18 -4
- package/lib/gen/language.tokens +124 -118
- package/lib/gen/languageLexer.interp +13 -1
- package/lib/gen/languageLexer.js +870 -839
- package/lib/gen/languageLexer.tokens +116 -111
- package/lib/gen/languageParser.js +5894 -5614
- package/lib/json/from-csn.js +152 -67
- package/lib/json/to-csn.js +334 -135
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +24 -14
- package/lib/language/language.g4 +188 -128
- package/lib/main.d.ts +435 -0
- package/lib/main.js +31 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +463 -187
- package/lib/model/csnUtils.js +280 -136
- package/lib/model/enrichCsn.js +75 -4
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +70 -25
- package/lib/optionProcessor.js +13 -10
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +123 -40
- package/lib/render/toHdbcds.js +156 -65
- package/lib/render/toSql.js +87 -11
- package/lib/render/utils/common.js +55 -9
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/{sql → db}/.eslintrc.json +0 -0
- package/lib/transform/{sql → db}/assertUnique.js +7 -8
- package/lib/transform/{sql → db}/constraints.js +35 -20
- package/lib/transform/db/draft.js +353 -0
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
- package/lib/transform/{sql → db}/helpers.js +0 -0
- package/lib/transform/{sql → db}/transformExists.js +256 -60
- package/lib/transform/forHanaNew.js +216 -765
- package/lib/transform/forOdataNew.js +60 -56
- package/lib/transform/localized.js +48 -26
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
- package/lib/transform/odata/generateForeignKeyElements.js +13 -12
- package/lib/transform/odata/referenceFlattener.js +60 -36
- package/lib/transform/odata/sortByAssociationDependency.js +4 -4
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +21 -22
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +27 -17
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +141 -77
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/timetrace.js +6 -1
- package/package.json +2 -1
- package/lib/base/deepCopy.js +0 -66
- package/lib/json/walker.js +0 -26
- package/lib/utils/string.js +0 -17
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { forEachRef } = require('../../model/csnUtils');
|
|
2
2
|
const { setProp } = require('../../base/model');
|
|
3
3
|
const { implicitAs } = require('../../model/csnRefs');
|
|
4
|
+
const { structuralPath } = require('./structuralPath');
|
|
4
5
|
|
|
5
6
|
/** This class is used for generic reference flattening.
|
|
6
7
|
* It provides the following functionality:
|
|
@@ -38,6 +39,7 @@ class ReferenceFlattener {
|
|
|
38
39
|
this.structuredReference = {};
|
|
39
40
|
this.generatedElementsForPath = {};
|
|
40
41
|
this.elementTransitions = {};
|
|
42
|
+
this.elementNamesWithDots = {};
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/**
|
|
@@ -72,8 +74,7 @@ class ReferenceFlattener {
|
|
|
72
74
|
});
|
|
73
75
|
if (paths)
|
|
74
76
|
setProp(node, '$paths', paths);
|
|
75
|
-
|
|
76
|
-
// cache also if structured or not
|
|
77
|
+
// cache if structured or not
|
|
77
78
|
let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
|
|
78
79
|
this.structuredReference[path.join('/')] = structured;
|
|
79
80
|
});
|
|
@@ -157,6 +158,14 @@ class ReferenceFlattener {
|
|
|
157
158
|
return this.generatedElementsForPath[path.join('/')];
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
setElementNameWithDots(path, elementNameWithDots) {
|
|
162
|
+
this.elementNamesWithDots[path.join('/')] = elementNameWithDots;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getElementNameWithDots(path) {
|
|
166
|
+
return this.elementNamesWithDots[path.join('/')];
|
|
167
|
+
}
|
|
168
|
+
|
|
160
169
|
/**
|
|
161
170
|
* Generic reference flattener as {@link ReferenceFlattener described}.
|
|
162
171
|
*
|
|
@@ -167,35 +176,40 @@ class ReferenceFlattener {
|
|
|
167
176
|
if (node.$paths) {
|
|
168
177
|
let newRef = []; // flattened reference
|
|
169
178
|
let flattenWithPrevious = false;
|
|
170
|
-
let
|
|
179
|
+
let lastFlattenedID = null; // The variable will be set to the index of the last flattened path
|
|
171
180
|
node.$paths.forEach((path, i) => {
|
|
172
181
|
if (path === undefined || !ref[i]) return;
|
|
173
|
-
let spath = path.join('/')
|
|
182
|
+
let spath = path.join('/');
|
|
174
183
|
let movedTo = this.elementTransitions[spath]; // detect element transition
|
|
175
184
|
let flattened = this.flattenedElementPaths[spath];
|
|
176
185
|
if (flattenWithPrevious) {
|
|
177
|
-
newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + ref[i];
|
|
178
|
-
|
|
186
|
+
newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + (ref[i].id || ref[i]);
|
|
187
|
+
// if we have a filter or args in an assoc, it needs to be kept, therefore
|
|
188
|
+
// the id of the ref is updated with the flattened version
|
|
189
|
+
if (ref[i].id) {
|
|
190
|
+
ref[i].id = newRef[newRef.length - 1];
|
|
191
|
+
newRef[newRef.length - 1] = ref[i];
|
|
192
|
+
}
|
|
193
|
+
lastFlattenedID = i;
|
|
179
194
|
} else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
|
|
180
195
|
let movedToElementName = movedTo[movedTo.length-1];
|
|
181
196
|
newRef.push(movedToElementName);
|
|
182
|
-
|
|
197
|
+
lastFlattenedID = i;
|
|
183
198
|
} else {
|
|
184
199
|
newRef.push(ref[i]);
|
|
185
200
|
}
|
|
186
201
|
flattenWithPrevious = flattened;
|
|
187
202
|
});
|
|
188
|
-
if (newRef !== undefined &&
|
|
203
|
+
if (newRef !== undefined && lastFlattenedID !== null) { // make sure the reference changed and only then replace it with the new one
|
|
189
204
|
// check if this is a column and add alias if missing
|
|
190
|
-
|
|
191
|
-
|
|
205
|
+
let structural = structuralPath(csn, path);
|
|
206
|
+
if (isColumnInSelectOrProjection(structural)) {
|
|
207
|
+
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
|
|
192
208
|
node.as = node.ref[node.ref.length - 1];
|
|
193
|
-
}
|
|
194
209
|
}
|
|
195
210
|
setProp(newRef, '$path', path.concat('ref'));
|
|
196
211
|
if (!node.as) {
|
|
197
|
-
|
|
198
|
-
if (ifKeysScope(path))
|
|
212
|
+
if (isPartOfKeysStructure(structural))
|
|
199
213
|
node.as = implicitAs(node.ref);
|
|
200
214
|
else
|
|
201
215
|
setProp(node, 'as', implicitAs(node.ref))
|
|
@@ -208,10 +222,11 @@ class ReferenceFlattener {
|
|
|
208
222
|
|
|
209
223
|
applyAliasesInOnCond(csn, inspectRef) {
|
|
210
224
|
forEachRef(csn, (ref, node, path) => {
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
225
|
+
// Process only on-conditions of associations
|
|
226
|
+
let structural = structuralPath(csn, path);
|
|
227
|
+
if(!isOnCondition(structural)) return;
|
|
214
228
|
let { links } = inspectRef(path);
|
|
229
|
+
if(!links) return; // $user not resolvable
|
|
215
230
|
let keysOfPreviousStepWhenManagedAssoc = undefined;
|
|
216
231
|
|
|
217
232
|
let aliasedRef = [...ref];
|
|
@@ -235,32 +250,41 @@ class ReferenceFlattener {
|
|
|
235
250
|
}
|
|
236
251
|
}
|
|
237
252
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Checks if the provided path is a column inside a key node
|
|
255
|
+
* by exploring the possibility of the structural path to ends with 'elements' and 'keys'.
|
|
256
|
+
*
|
|
257
|
+
* @param structural structural path to explore
|
|
258
|
+
* @returns {boolean} True if the provided path matched the requirements to be part of a key node.
|
|
259
|
+
*/
|
|
260
|
+
function isPartOfKeysStructure(structural) {
|
|
261
|
+
return structural[structural.length-2] === 'elements'
|
|
262
|
+
&& structural[structural.length-1] === 'keys';
|
|
244
263
|
}
|
|
245
264
|
|
|
246
265
|
/**
|
|
247
|
-
* Checks if the provided path is a column inside a select node
|
|
248
|
-
* by exploring the possibility of the path to
|
|
266
|
+
* Checks if the provided path is a column inside a select or a projection node
|
|
267
|
+
* by exploring the possibility of the structural path to contain 'SELECT' or 'projection'
|
|
268
|
+
* and ends with 'columns' or 'columns' and 'expand'.
|
|
249
269
|
*
|
|
250
|
-
* @param
|
|
270
|
+
* @param structural structural path to explore
|
|
251
271
|
* @returns {boolean} True if the provided path matched the requirements to be a select node.
|
|
252
272
|
*/
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
273
|
+
function isColumnInSelectOrProjection(structural) {
|
|
274
|
+
return (structural.includes('SELECT') || structural.includes('projection'))
|
|
275
|
+
&& (structural[structural.length-1] === 'columns' || (structural[structural.length-2] === 'columns' && structural[structural.length-1] === 'expand'));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Checks if the provided path is a column inside an on-condition of an element
|
|
280
|
+
* by exploring the possibility of the structural path to ends with 'elements' and 'on'.
|
|
281
|
+
*
|
|
282
|
+
* @param structural structural path to explore
|
|
283
|
+
* @returns {boolean} True if the provided path matched the requirements to be part of an element's on-condition.
|
|
284
|
+
*/
|
|
285
|
+
function isOnCondition(structural) {
|
|
286
|
+
return structural[structural.length-2] === 'elements'
|
|
287
|
+
&& structural[structural.length-1] === 'on';
|
|
264
288
|
}
|
|
265
289
|
|
|
266
290
|
module.exports = ReferenceFlattener;
|
|
@@ -18,13 +18,13 @@ const {
|
|
|
18
18
|
} = require('./utils');
|
|
19
19
|
const { forEach } = require('../../utils/objectUtils.js');
|
|
20
20
|
|
|
21
|
-
function buildDependenciesFor(csn, referenceFlattener) {
|
|
21
|
+
function buildDependenciesFor(csn, referenceFlattener, isExternalServiceMember) {
|
|
22
22
|
|
|
23
23
|
let dependencies = {};
|
|
24
24
|
forEachDefinition(csn, (def, definitionName) => {
|
|
25
25
|
/** @type {CSN.Path} */
|
|
26
26
|
let root = ['definitions', definitionName];
|
|
27
|
-
forEachMemberRecursively(def, (element, elementName,
|
|
27
|
+
forEachMemberRecursively(def, (element, elementName, structuralNodeName, subpath, parent) => {
|
|
28
28
|
let path = root.concat(subpath);
|
|
29
29
|
// go only through managed associations and compositions
|
|
30
30
|
if (isAssociationOrComposition(element) && element.keys && !element.on) { // check association FKs
|
|
@@ -44,10 +44,10 @@ function buildDependenciesFor(csn, referenceFlattener) {
|
|
|
44
44
|
elementDependencies.push(targetElementPath);
|
|
45
45
|
}
|
|
46
46
|
})
|
|
47
|
-
dependencies[path.join("/")] = { definitionName, elementName, element, path, parent, dependencies: elementDependencies };
|
|
47
|
+
dependencies[path.join("/")] = { structuralNodeName, definitionName, elementName, element, path, parent, dependencies: elementDependencies };
|
|
48
48
|
}
|
|
49
49
|
}) // forEachMemberRecursively
|
|
50
|
-
}) // forEachDefinition
|
|
50
|
+
}, { skipArtifact: isExternalServiceMember }) // forEachDefinition
|
|
51
51
|
|
|
52
52
|
let result = []; // final list of sorted association items
|
|
53
53
|
let inResult = {}; // paths of associations which were added in the result
|
|
@@ -0,0 +1,76 @@
|
|
|
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: traverseRef,
|
|
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 traverseRef(obj, path, index, typeStack) {
|
|
37
|
+
return traverseArray(obj, path, index, typeStack);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function traverseArray(obj, path, index, typeStack) {
|
|
41
|
+
if(!Array.isArray(obj)) return typeStack;
|
|
42
|
+
const name = path[index];
|
|
43
|
+
const element = obj[name];
|
|
44
|
+
return traverseTyped(element, path, index+1, typeStack);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function traverseDict(obj, path, index, typeStack) {
|
|
48
|
+
if(typeof obj !== 'object') return typeStack;
|
|
49
|
+
const name = path[index];
|
|
50
|
+
if(name === undefined) return typeStack;
|
|
51
|
+
return traverseTyped(obj[name], path, index+1, typeStack);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function traverseDictArray(obj, path, index, typeStack) {
|
|
55
|
+
if(typeof obj !== 'object') return typeStack;
|
|
56
|
+
const name = path[index];
|
|
57
|
+
if(name === undefined) return typeStack;
|
|
58
|
+
return traverseArray(obj[name], path, index+1, typeStack);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function traverseTyped(obj, path, index, typeStack) {
|
|
62
|
+
if(!obj) return typeStack;
|
|
63
|
+
const name = path[index];
|
|
64
|
+
if(name === undefined) return typeStack;
|
|
65
|
+
if(name[0] === '@') return typeStack; // skip annotations
|
|
66
|
+
const func = structuralNodeHandlers[name];
|
|
67
|
+
if(func) return func(obj[name], path, index+1, typeStack.concat(name));
|
|
68
|
+
// not typed -> columns?
|
|
69
|
+
if(typeStack[typeStack.length-1] === 'columns')
|
|
70
|
+
return traverseDictArray(obj, path, index, typeStack);
|
|
71
|
+
return typeStack;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
structuralPath
|
|
76
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { copyAnnotations } = require('../../model/csnUtils');
|
|
4
|
-
const { setProp } = require('../../base/model');
|
|
5
4
|
const { cloneCsn, forEachDefinition } = require('../../model/csnUtils');
|
|
6
5
|
const { attachPathOnPartialCSN } = require('./attachPath');
|
|
7
6
|
|
|
@@ -50,18 +49,16 @@ const { isNotNull, setNotNull, setUpNotNull } = function () {
|
|
|
50
49
|
* During the OData transformations in flat-mode, all structured elements will be flattened.
|
|
51
50
|
* This module performs the complete flattening.
|
|
52
51
|
* It also provides information to the reference flattener: elements produced for specific path in the CSN structure.
|
|
53
|
-
* Each generated element gets hidden attributes:
|
|
54
|
-
* - $viaTransform - states that the element was generated during transformation
|
|
55
|
-
* - _flatElementNameWithDots - names in the element path concatenated with dot
|
|
56
52
|
* @param {CSN.Model} csn CSN-object to flatten
|
|
57
53
|
* @param {*} csnUtils instances of utility functions
|
|
58
54
|
* @param {*} options
|
|
59
55
|
* @param {*} referenceFlattener
|
|
60
56
|
* @param {Function} error
|
|
57
|
+
* @param {Function} isExternalServiceMember returns true for an artifact that is part of an external service
|
|
61
58
|
*/
|
|
62
|
-
function flattenCSN(csn, csnUtils, options, referenceFlattener, error) {
|
|
59
|
+
function flattenCSN(csn, csnUtils, options, referenceFlattener, error, isExternalServiceMember) {
|
|
63
60
|
forEachDefinition(csn, (def, defName, propertyName, path) =>
|
|
64
|
-
flattenDefinition(def, path, csnUtils, options, referenceFlattener, error));
|
|
61
|
+
flattenDefinition(def, path, csnUtils, options, referenceFlattener, error), { skipArtifact: isExternalServiceMember });
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
/**
|
|
@@ -77,17 +74,22 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
|
|
|
77
74
|
if (definition.kind !== 'entity' && definition.kind !== 'view')
|
|
78
75
|
return;
|
|
79
76
|
|
|
80
|
-
let { newFlatElements } = flattenStructure(definition, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
77
|
+
let { newFlatElements } = flattenStructure(definition.elements, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
81
78
|
|
|
82
79
|
attachPathOnPartialCSN(newFlatElements, definitionPath.concat('elements'));
|
|
83
|
-
|
|
84
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
|
+
}
|
|
85
87
|
} // flattenDefinition
|
|
86
88
|
|
|
87
89
|
/**
|
|
88
90
|
* Flattens structured element by calling element flattener for each structured child.
|
|
89
91
|
* Returns a dictionary containing all the new elements for the given structure.
|
|
90
|
-
* @param {*}
|
|
92
|
+
* @param {*} dictionary to flatten
|
|
91
93
|
* @param {CSN.Path} path the path of the structure in the CSN tree
|
|
92
94
|
* @param {*} csnUtils
|
|
93
95
|
* @param {Function} error Error message function
|
|
@@ -96,14 +98,14 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
|
|
|
96
98
|
* @param {*} [newFlatElements]
|
|
97
99
|
* @param {boolean} [isTopLevelElement] states if this is a top level element
|
|
98
100
|
*/
|
|
99
|
-
function flattenStructure(
|
|
101
|
+
function flattenStructure(dictionary, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
|
|
100
102
|
newFlatElements = Object.create(null), isTopLevelElement = true, isParentNotNull = false) {
|
|
101
103
|
|
|
102
|
-
if (!isTopLevelElement) addPropsForPropagationFromElement(
|
|
104
|
+
if (!isTopLevelElement) addPropsForPropagationFromElement(dictionary);
|
|
103
105
|
|
|
104
106
|
let generatedNewFlatElementsNames = []; // holds the names of all new child elements of the structure
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
dictionary && Object.entries(dictionary).forEach(([elementName, element]) => {
|
|
107
109
|
let currPath = path.concat('elements', elementName);
|
|
108
110
|
|
|
109
111
|
if (isTopLevelElement) {
|
|
@@ -121,8 +123,8 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
|
|
|
121
123
|
addPropsForPropagationFromElement(element);
|
|
122
124
|
|
|
123
125
|
// if the child element is structured itself -> needs to be flattened
|
|
124
|
-
const
|
|
125
|
-
let result = flattenStructure(
|
|
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());
|
|
126
128
|
generatedNewFlatElementsNames.push(...result.generatedNewFlatElementsNames); // accomulate names of produced elements
|
|
127
129
|
|
|
128
130
|
} else { // when we do not need to flat, this is scalar or empty (cds-compiler#4337) -> needs to be registered in referenceFlattener
|
|
@@ -130,7 +132,6 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
|
|
|
130
132
|
let elementNameWithDots = elementPathInStructure.concat(elementName).join('.');
|
|
131
133
|
addNewElementToResult(element, newElementName, elementNameWithDots, currPath);
|
|
132
134
|
}
|
|
133
|
-
|
|
134
135
|
});
|
|
135
136
|
|
|
136
137
|
if (referenceFlattener) {
|
|
@@ -138,31 +139,29 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
|
|
|
138
139
|
}
|
|
139
140
|
return { newFlatElements, generatedNewFlatElementsNames };
|
|
140
141
|
|
|
141
|
-
|
|
142
142
|
// adds newly created element into the final dictionary of elements
|
|
143
143
|
function addNewElementToResult(element, elementName, elementNameWithDots, path) {
|
|
144
144
|
if (newFlatElements[elementName]) {
|
|
145
145
|
error(null, path, `Generated element ${elementName} conflicts with other generated element`);
|
|
146
146
|
} else {
|
|
147
|
-
let
|
|
147
|
+
let newPath = path.slice(0, 2).concat('elements', elementName);
|
|
148
|
+
let newElement = createNewElement(element, elementNameWithDots, newPath);
|
|
148
149
|
newFlatElements[elementName] = newElement;
|
|
149
150
|
generatedNewFlatElementsNames.push(elementName);
|
|
150
151
|
|
|
151
152
|
if (referenceFlattener) {
|
|
152
|
-
let newPath = path.slice(0, 2).concat('elements', elementName);
|
|
153
153
|
referenceFlattener.registerElementTransition(path, newPath);
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
} // addNewElementToResult
|
|
157
157
|
|
|
158
158
|
// creates new element by copying the properties of the originating element
|
|
159
|
-
function createNewElement(element, elementNameWithDots) {
|
|
159
|
+
function createNewElement(element, elementNameWithDots, path) {
|
|
160
160
|
let newElement = cloneCsn(element, options);
|
|
161
161
|
if (!isTopLevelElement) propagatePropsToElement(newElement);
|
|
162
162
|
if (isNotNull() === undefined) delete newElement.notNull;
|
|
163
|
-
if (!isTopLevelElement) {
|
|
164
|
-
|
|
165
|
-
setProp(newElement, '_flatElementNameWithDots', elementNameWithDots);
|
|
163
|
+
if (!isTopLevelElement && referenceFlattener) {
|
|
164
|
+
referenceFlattener.setElementNameWithDots(path, elementNameWithDots);
|
|
166
165
|
}
|
|
167
166
|
return newElement;
|
|
168
167
|
} // createNewElement
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
forEachDefinition, forEachMemberRecursively,
|
|
5
|
+
isBuiltinType, cloneCsnDictionary,
|
|
6
6
|
} = require('../../model/csnUtils');
|
|
7
7
|
const { isArtifactInSomeService, isArtifactInService } = require('./utils');
|
|
8
8
|
|
|
9
|
-
function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
|
|
9
|
+
function expandToFinalBaseType(csn, transformers, csnUtils, services, options, isExternalServiceMember) {
|
|
10
10
|
const isV4 = options.toOdata.version === 'v4';
|
|
11
11
|
forEachDefinition(csn, (def, defName) => {
|
|
12
12
|
// Unravel derived type chains to final one for elements, actions, action parameters (propagating annotations)
|
|
@@ -60,7 +60,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
|
|
|
60
60
|
if (def.kind === 'type' && def.items && isArtifactInSomeService(defName, services)) {
|
|
61
61
|
expandFirstLevelOfArrayed(def);
|
|
62
62
|
}
|
|
63
|
-
});
|
|
63
|
+
}, { skipArtifact: isExternalServiceMember });
|
|
64
64
|
|
|
65
65
|
// In case we have in the model something like:
|
|
66
66
|
// type Foo: array of Bar; type Bar: { qux: Integer };
|
|
@@ -70,7 +70,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
|
|
|
70
70
|
if (def.items.type && !isBuiltinType(def.items.type)) {
|
|
71
71
|
let finalType = csnUtils.getFinalTypeDef(def.items.type);
|
|
72
72
|
if (csnUtils.isStructured(finalType)) {
|
|
73
|
-
def.items.elements =
|
|
73
|
+
def.items.elements = cloneCsnDictionary(finalType.elements, options);
|
|
74
74
|
delete def.items.type;
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const { setProp } = require('../../base/model');
|
|
10
10
|
const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
|
|
11
|
-
const { cloneCsn, isBuiltinType, forEachDefinition, forEachMember } = require('../../model/csnUtils');
|
|
11
|
+
const { cloneCsn, isBuiltinType, forEachDefinition, forEachMember, cloneCsnDictionary } = require('../../model/csnUtils');
|
|
12
12
|
const { copyAnnotations } = require('../../model/csnUtils');
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -25,7 +25,6 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
25
25
|
// collect in this variable all the newly exposed types
|
|
26
26
|
const exposedStructTypes = [];
|
|
27
27
|
const schemas = Object.create(null);
|
|
28
|
-
|
|
29
28
|
// walk through the definitions of the given CSN and expose types where needed
|
|
30
29
|
forEachDefinition(csn, (def, defName, propertyName, path) => {
|
|
31
30
|
// we do expose types only for definition from inside services
|
|
@@ -33,11 +32,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
33
32
|
if (serviceName) {
|
|
34
33
|
if (['type', 'entity', 'view'].includes(def.kind)) {
|
|
35
34
|
forEachMember(def, (element, elementName, propertyName, path) => {
|
|
36
|
-
if (
|
|
35
|
+
if (['elements', 'params'].includes(propertyName)) {
|
|
37
36
|
const artificialtName = `${isMultiSchema ?
|
|
38
37
|
defNameWithoutServiceOrContextName(defName, serviceName)
|
|
39
38
|
: defNameWithoutServiceOrContextName(defName, serviceName).replace(/\./g, '_')}_${elementName}`;
|
|
40
|
-
exposeTypeOf(element, elementName, defName, serviceName, artificialtName, path);
|
|
39
|
+
exposeTypeOf(element, element.key || propertyName === 'params', elementName, defName, serviceName, artificialtName, path);
|
|
41
40
|
}
|
|
42
41
|
}, path);
|
|
43
42
|
}
|
|
@@ -64,11 +63,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
64
63
|
* @param {string} artificialName
|
|
65
64
|
* @param {CSN.Path} path
|
|
66
65
|
*/
|
|
67
|
-
function exposeTypeOf(node, memberName, defName, service, artificialName, path) {
|
|
66
|
+
function exposeTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
|
|
68
67
|
if (isArrayed(node))
|
|
69
|
-
exposeArrayOfTypeOf(node, memberName, defName, service, artificialName, path);
|
|
68
|
+
exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path);
|
|
70
69
|
else if (csnUtils.isStructured(node))
|
|
71
|
-
exposeStructTypeOf(node, memberName, defName, service, artificialName, path);
|
|
70
|
+
exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path);
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
/**
|
|
@@ -90,12 +89,12 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
90
89
|
function exposeTypesOfAction(action, actionName, defName, service, path) {
|
|
91
90
|
if (action.returns) {
|
|
92
91
|
const artificialName = `return_${actionName.replace(/\./g, '_')}`;
|
|
93
|
-
exposeTypeOf(action.returns, actionName, defName, service, artificialName, path.concat(['returns']));
|
|
92
|
+
exposeTypeOf(action.returns, false, actionName, defName, service, artificialName, path.concat(['returns']));
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
action.params && Object.entries(action.params).forEach(([paramName, param]) => {
|
|
97
96
|
const artificialName = `param_${actionName.replace(/\./g, '_')}_${paramName}`;
|
|
98
|
-
exposeTypeOf(param, actionName, defName, service, artificialName, path.concat(['params', paramName]));
|
|
97
|
+
exposeTypeOf(param, false, actionName, defName, service, artificialName, path.concat(['params', paramName]));
|
|
99
98
|
});
|
|
100
99
|
}
|
|
101
100
|
|
|
@@ -108,9 +107,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
108
107
|
* @param {String} service
|
|
109
108
|
* @param {String} artificialName
|
|
110
109
|
*/
|
|
111
|
-
function exposeStructTypeOf(node, memberName, defName, service, artificialName, path, parentName) {
|
|
110
|
+
function exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path, parentName) {
|
|
111
|
+
if (node.items) exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path);
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
// start conservative, assume we're in a named type
|
|
114
|
+
let isAnonymous = false;
|
|
114
115
|
|
|
115
116
|
if (isExposableStructure(node)) {
|
|
116
117
|
let typeDef = node.type ? csnUtils.getCsnDef(node.type) : /* structure|anonymous type */ node;
|
|
@@ -124,6 +125,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
124
125
|
// and the new target. Consequently, we now have both type and elements properties in this case, and the elements should be taken as a priority
|
|
125
126
|
// as the correct target is there and no longer in the type definition
|
|
126
127
|
let newTypeElements = (node.type && node.elements) ? node.elements : typeDef.elements;
|
|
128
|
+
// if node and typeDef are identical, we're anonymous
|
|
129
|
+
isAnonymous = node === typeDef;
|
|
130
|
+
// if we've left the anonymous world, we're no longer in a key def
|
|
131
|
+
if (!isAnonymous)
|
|
132
|
+
isKey = false;
|
|
127
133
|
|
|
128
134
|
let newType = exposeStructType(newTypeFullName, newTypeElements, memberName, path);
|
|
129
135
|
if (!newType) {
|
|
@@ -138,6 +144,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
138
144
|
newType.elements && Object.entries(newType.elements).forEach(([elemName, newElem]) => {
|
|
139
145
|
if (node.elements && node.elements[elemName].$location) setProp(newElem, '$location', node.elements[elemName].$location);
|
|
140
146
|
exposeStructTypeOf(newElem,
|
|
147
|
+
isKey,
|
|
141
148
|
memberName,
|
|
142
149
|
typeDef.kind === 'type' ? node.type : defName,
|
|
143
150
|
service,
|
|
@@ -167,7 +174,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
167
174
|
/**
|
|
168
175
|
* Calculate the new type name that will be exposed in multi schema,
|
|
169
176
|
* in case that the element has a named type.
|
|
170
|
-
*
|
|
177
|
+
*
|
|
171
178
|
* @param {string} typeName type of the element
|
|
172
179
|
* @param {string} service current service name
|
|
173
180
|
*/
|
|
@@ -196,7 +203,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
196
203
|
/**
|
|
197
204
|
* Calculate the new type name that will be exposed in multi schema,
|
|
198
205
|
* in case that the element has an anonymous type.
|
|
199
|
-
*
|
|
206
|
+
*
|
|
200
207
|
* @param {string} typeName type of the element
|
|
201
208
|
* @param {string} parentName name of the parent def holding the element
|
|
202
209
|
*/
|
|
@@ -212,7 +219,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
212
219
|
|
|
213
220
|
/**
|
|
214
221
|
* Tf does not exists, create a context with the given name in the CSN
|
|
215
|
-
* @param {string} name
|
|
222
|
+
* @param {string} name
|
|
216
223
|
*/
|
|
217
224
|
function createSchema(name) {
|
|
218
225
|
schemas[`${name}`] = { kind: 'schema', name };
|
|
@@ -251,6 +258,9 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
251
258
|
error(null, path, `"${elemName}": Element name conflicts with existing element`);
|
|
252
259
|
}
|
|
253
260
|
let cloned = cloneCsn(element, options);
|
|
261
|
+
// if this was an anonymous sub element of a key, mark it as not nullable
|
|
262
|
+
if(isAnonymous && isKey && !cloned.key && cloned.notNull === undefined)
|
|
263
|
+
cloned.notNull = true;
|
|
254
264
|
type.elements[elemName] = cloned;
|
|
255
265
|
});
|
|
256
266
|
|
|
@@ -265,12 +275,12 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
265
275
|
|
|
266
276
|
// If a member is of type "array of <named type|anonymous type>", we expose the arrayed type,
|
|
267
277
|
// like we expose structures in structured mode
|
|
268
|
-
function exposeArrayOfTypeOf(node, memberName, defName, service, artificialName, path) {
|
|
278
|
+
function exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
|
|
269
279
|
// if anonymously defined in place -> we always expose the type
|
|
270
280
|
// this would be definition like 'elem: array of { ... }'
|
|
271
281
|
// and we use the artificial name for the new type name
|
|
272
282
|
if (node.items && !node.type) {
|
|
273
|
-
exposeStructTypeOf(node.items, memberName, defName, service, artificialName, path.concat('items'));
|
|
283
|
+
exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path.concat('items'));
|
|
274
284
|
}
|
|
275
285
|
// we can have both of the 'type' and 'items' in the cases:
|
|
276
286
|
// 1. 'elem: Foo' and 'type Foo: array of Baz' and 'type Baz: { ... }'
|
|
@@ -323,7 +333,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
323
333
|
if (def.items.type && !isBuiltinType(def.items.type)) {
|
|
324
334
|
let finalType = csnUtils.getFinalTypeDef(def.items.type);
|
|
325
335
|
if (csnUtils.isStructured(finalType)) {
|
|
326
|
-
if (!def.items.elements) def.items.elements =
|
|
336
|
+
if (!def.items.elements) def.items.elements = cloneCsnDictionary(finalType.elements, options);
|
|
327
337
|
delete def.items.type;
|
|
328
338
|
}
|
|
329
339
|
}
|
|
@@ -22,7 +22,7 @@ function isManagedAssociationElement(artifact) {
|
|
|
22
22
|
return artifact.target !== undefined && artifact.on === undefined;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function forEachManagedAssociation(csn, callback) {
|
|
25
|
+
function forEachManagedAssociation(csn, callback, isExternalServiceMember) {
|
|
26
26
|
|
|
27
27
|
forEachDefinition(csn, (def) => {
|
|
28
28
|
forEachMemberRecursively(def, (element) => {
|
|
@@ -30,7 +30,7 @@ function forEachManagedAssociation(csn, callback) {
|
|
|
30
30
|
callback(element)
|
|
31
31
|
}
|
|
32
32
|
})
|
|
33
|
-
})
|
|
33
|
+
}, { skipArtifact: isExternalServiceMember });
|
|
34
34
|
|
|
35
35
|
}
|
|
36
36
|
|