@sap/cds-compiler 5.3.2 → 5.4.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 +23 -2
- package/bin/cdsc.js +1 -1
- package/doc/CHANGELOG_BETA.md +2 -2
- package/lib/api/options.js +4 -2
- package/lib/base/builtins.js +0 -10
- package/lib/base/keywords.js +3 -31
- package/lib/base/message-registry.js +23 -5
- package/lib/base/messages.js +1 -1
- package/lib/checks/existsMustEndInAssoc.js +7 -2
- package/lib/checks/foreignKeys.js +12 -7
- package/lib/compiler/assert-consistency.js +11 -3
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +88 -38
- package/lib/compiler/define.js +2 -2
- package/lib/compiler/shared.js +9 -10
- package/lib/compiler/xpr-rewrite.js +11 -0
- package/lib/compiler/xsn-model.js +1 -1
- package/lib/edm/csn2edm.js +2 -0
- package/lib/edm/edm.js +2 -1
- package/lib/edm/edmPreprocessor.js +14 -1
- package/lib/edm/edmUtils.js +17 -2
- package/lib/gen/BaseParser.js +291 -197
- package/lib/gen/CdlParser.js +1631 -1605
- package/lib/gen/Dictionary.json +74 -6
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1808 -1804
- package/lib/language/antlrParser.js +8 -4
- package/lib/language/genericAntlrParser.js +3 -3
- package/lib/model/csnUtils.js +6 -1
- package/lib/optionProcessor.js +4 -0
- package/lib/parsers/AstBuildingParser.js +172 -108
- package/lib/parsers/CdlGrammar.g4 +154 -134
- package/lib/parsers/Lexer.js +3 -3
- package/lib/parsers/identifiers.js +59 -0
- package/lib/render/toCdl.js +5 -5
- package/lib/render/utils/common.js +5 -0
- package/lib/render/utils/delta.js +23 -5
- package/lib/transform/db/expansion.js +2 -1
- package/lib/transform/db/transformExists.js +10 -9
- package/lib/transform/effective/annotations.js +147 -0
- package/lib/transform/effective/main.js +16 -2
- package/lib/transform/forOdata.js +53 -10
- package/lib/transform/forRelationalDB.js +7 -0
- package/lib/transform/odata/createForeignKeys.js +180 -0
- package/lib/transform/odata/flattening.js +135 -19
- package/lib/transform/odata/typesExposure.js +4 -3
- package/lib/transform/transformUtils.js +6 -6
- package/package.json +1 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isBuiltinType } = require('../../base/builtins');
|
|
4
|
+
const { setProp } = require('../../base/model');
|
|
5
|
+
const { applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
|
|
6
|
+
const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
|
|
7
|
+
|
|
8
|
+
function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iterateOptions = {}) {
|
|
9
|
+
|
|
10
|
+
const { error } = messageFunctions;
|
|
11
|
+
|
|
12
|
+
applyTransformations(csn, { elements: createForeignKeysInCsn, params: createForeignKeysInCsn},
|
|
13
|
+
[], Object.assign(iterateOptions, { skip: ['aspect', 'event'], skipStandard: { targetAspect: true } }));
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Process a given elements or params dictionary and create foreign key elements.
|
|
17
|
+
*
|
|
18
|
+
* @param {object} parent The thing HAVING params or elements
|
|
19
|
+
* @param {string} prop
|
|
20
|
+
* @param {object} dict The params or elements thing
|
|
21
|
+
* @param {CSN.Path} path
|
|
22
|
+
*/
|
|
23
|
+
function createForeignKeysInCsn( parent, prop, dict, path ) {
|
|
24
|
+
const orderedElements = [];
|
|
25
|
+
// First, generate the FK elements for given element
|
|
26
|
+
Object.entries(dict).forEach(([elementName, element]) => {
|
|
27
|
+
orderedElements.push([ elementName, element ]);
|
|
28
|
+
if (!csnUtils.isManagedAssociation(element)) return;
|
|
29
|
+
const elementPath = path.concat(prop, elementName);
|
|
30
|
+
const generatedForeignKeys = createForeignKeysForElement(csnUtils, elementPath, element, elementName, csn, options, '_');
|
|
31
|
+
|
|
32
|
+
// Second, finalize the generated FK elements
|
|
33
|
+
const refCount = generatedForeignKeys.reduce((acc, fk) => {
|
|
34
|
+
// count duplicates
|
|
35
|
+
if (acc[fk[0]])
|
|
36
|
+
acc[fk[0]]++;
|
|
37
|
+
else
|
|
38
|
+
acc[fk[0]] = 1;
|
|
39
|
+
|
|
40
|
+
// check for name clash with existing elements
|
|
41
|
+
if (parent[prop][fk[0]] && isDeepEqual(element, parent[prop][fk[0]], true)) {
|
|
42
|
+
// error location is the colliding element
|
|
43
|
+
error('name-duplicate-element', elementPath, { '#': 'flatten-fkey-exists', name: fk[0], art: elementName });
|
|
44
|
+
}
|
|
45
|
+
// attach a proper $path
|
|
46
|
+
setProp(element, '$path', elementPath);
|
|
47
|
+
return acc;
|
|
48
|
+
}, Object.create(null));
|
|
49
|
+
|
|
50
|
+
// set default for single foreign key from association (if available)
|
|
51
|
+
if (element.default?.val !== undefined && generatedForeignKeys.length === 1)
|
|
52
|
+
generatedForeignKeys[0][1].default = element.default;
|
|
53
|
+
|
|
54
|
+
// check for duplicate foreign keys
|
|
55
|
+
Object.entries(refCount).forEach(([ name, occ ]) => {
|
|
56
|
+
if (occ > 1)
|
|
57
|
+
error('name-duplicate-element', elementPath, { '#': 'flatten-fkey-gen', name, art: elementName });
|
|
58
|
+
});
|
|
59
|
+
if (element.keys) {
|
|
60
|
+
if (options.transformation === 'effective')
|
|
61
|
+
delete element.default;
|
|
62
|
+
}
|
|
63
|
+
setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk[0]));
|
|
64
|
+
orderedElements.push(...generatedForeignKeys);
|
|
65
|
+
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
parent[prop] = orderedElements.reduce((elementsAccumulator, [ name, element ]) => {
|
|
69
|
+
elementsAccumulator[name] = element;
|
|
70
|
+
return elementsAccumulator;
|
|
71
|
+
}, Object.create(null));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function createForeignKeysForElement(csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
|
|
75
|
+
const special$self = !csn?.definitions?.$self && '$self';
|
|
76
|
+
const isInspectRefResult = !Array.isArray(path);
|
|
77
|
+
|
|
78
|
+
let fks = [];
|
|
79
|
+
if (!element)
|
|
80
|
+
return fks;
|
|
81
|
+
|
|
82
|
+
let finalElement = element;
|
|
83
|
+
let finalTypeName;
|
|
84
|
+
|
|
85
|
+
// resolve derived type
|
|
86
|
+
if (element.type && !isBuiltinType(element.type) && element.type !== special$self) {
|
|
87
|
+
const tmpElt = csnUtils.effectiveType(element);
|
|
88
|
+
// effective type resolves to structs and enums only but not scalars
|
|
89
|
+
if (Object.keys(tmpElt).length) {
|
|
90
|
+
finalElement = tmpElt;
|
|
91
|
+
finalTypeName = finalElement.$path[1];
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// unwind a derived type chain to a scalar type
|
|
95
|
+
while (finalElement?.type && !isBuiltinType(finalElement?.type)) {
|
|
96
|
+
finalTypeName = finalElement.type;
|
|
97
|
+
finalElement = csn.definitions[finalElement.type];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!finalElement)
|
|
103
|
+
return [];
|
|
104
|
+
|
|
105
|
+
// main if for this function
|
|
106
|
+
// the element is a managed association
|
|
107
|
+
if (csnUtils.isManagedAssociation(finalElement)) {
|
|
108
|
+
finalElement.keys.forEach((key, keyIndex) => {
|
|
109
|
+
const continuePath = getContinuePath([ 'keys', keyIndex ]);
|
|
110
|
+
const alias = key.as || implicitAs(key.ref);
|
|
111
|
+
const result = csnUtils.inspectRef(continuePath);
|
|
112
|
+
fks = fks.concat(createForeignKeysForElement(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// the element is a structure
|
|
116
|
+
else if (finalElement.elements) {
|
|
117
|
+
Object.entries(finalElement.elements).forEach(([ elemName, elem ]) => {
|
|
118
|
+
// Skip already produced foreign keys
|
|
119
|
+
if (!elem['@odata.foreignKey4']) {
|
|
120
|
+
const continuePath = getContinuePath([ 'elements', elemName ]);
|
|
121
|
+
fks = fks.concat(createForeignKeysForElement(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// we have reached a leaf element, create a foreign key
|
|
126
|
+
else if ((finalElement.type == null || isBuiltinType(finalElement.type)) && !finalElement.on) {
|
|
127
|
+
const newFk = Object.create(null);
|
|
128
|
+
[ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
|
|
129
|
+
// copy props from original element to preserve derived types!
|
|
130
|
+
if (element[prop] !== undefined)
|
|
131
|
+
newFk[prop] = element[prop];
|
|
132
|
+
});
|
|
133
|
+
return [ [ prefix, newFk ] ];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fks.forEach((fk) => {
|
|
137
|
+
// prepend current prefix
|
|
138
|
+
fk[0] = `${prefix}${pathDelimiter}${fk[0]}`;
|
|
139
|
+
// if this is the entry association, decorate the final foreign keys with the association props/annos
|
|
140
|
+
if (lvl === 0) {
|
|
141
|
+
if (options.transformation !== 'effective')
|
|
142
|
+
fk[1]['@odata.foreignKey4'] = prefix;
|
|
143
|
+
|
|
144
|
+
const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !findAnnotationExpression(element, pn));
|
|
145
|
+
copyAnnotations(element, fk[1], true, {}, validAnnoNames);
|
|
146
|
+
// propagate not null to final foreign key
|
|
147
|
+
for (const prop of [ 'notNull', 'key' ]) {
|
|
148
|
+
if (element[prop] !== undefined)
|
|
149
|
+
fk[1][prop] = element[prop];
|
|
150
|
+
}
|
|
151
|
+
if (element.$location)
|
|
152
|
+
setProp(fk[1], '$location', element.$location);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
return fks;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get the path to continue resolving references
|
|
159
|
+
*
|
|
160
|
+
* If we are currently inside of a type, we need to start our path fresh from that given type.
|
|
161
|
+
* Otherwise, we would try to resolve .elements on a thing that does not exist.
|
|
162
|
+
*
|
|
163
|
+
* We also respect if we have a previous inspectRef result as our base.
|
|
164
|
+
*
|
|
165
|
+
* @param {Array} additions
|
|
166
|
+
* @returns {CSN.Path}
|
|
167
|
+
*/
|
|
168
|
+
function getContinuePath( additions ) {
|
|
169
|
+
if (csn.definitions[finalElement.type])
|
|
170
|
+
return [ 'definitions', finalElement.type, ...additions ];
|
|
171
|
+
else if (finalTypeName)
|
|
172
|
+
return [ 'definitions', finalTypeName, ...additions ];
|
|
173
|
+
else if (isInspectRefResult)
|
|
174
|
+
return [ path, ...additions ];
|
|
175
|
+
return [ ...path, ...additions ];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = createForeignKeyElements;
|
|
@@ -5,14 +5,16 @@ const { forEachDefinition,
|
|
|
5
5
|
transformExpression, transformAnnotationExpression } = require('../../model/csnUtils');
|
|
6
6
|
const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
|
|
7
7
|
const transformUtils = require('../transformUtils');
|
|
8
|
-
const { setProp } = require('../../base/model');
|
|
8
|
+
const { setProp, forEachGeneric } = require('../../base/model');
|
|
9
9
|
const { applyTransformationsOnDictionary,
|
|
10
10
|
applyTransformationsOnNonDictionary } = require('../db/applyTransformations.js');
|
|
11
11
|
const { handleManagedAssociationsAndCreateForeignKeys } = require('../db/flattening');
|
|
12
12
|
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
13
13
|
const { forEach } = require('../../utils/objectUtils');
|
|
14
|
+
const { assignAnnotation } = require('../../edm/edmUtils');
|
|
14
15
|
|
|
15
|
-
function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternalServiceMember, error, csnUtils, options) {
|
|
16
|
+
function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTypeInfo, isExternalServiceMember, error, csnUtils, options) {
|
|
17
|
+
const allMgdAssocDefs = [];
|
|
16
18
|
forEachDefinition(csn, (def, defName) => {
|
|
17
19
|
if (def.kind === 'entity' && !isExternalServiceMember(def, defName)) {
|
|
18
20
|
['elements', 'params'].forEach(dictName => {
|
|
@@ -27,7 +29,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
27
29
|
let resolvedElt = child;
|
|
28
30
|
let typeIdx = 0;
|
|
29
31
|
if (child.type && !child.elements) {
|
|
30
|
-
resolvedElt =
|
|
32
|
+
resolvedElt = getFinalTypeInfo(child.type);
|
|
31
33
|
if (resolvedElt.elements)
|
|
32
34
|
typeIdx = rootPrefix.length + 1;
|
|
33
35
|
}
|
|
@@ -41,10 +43,12 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
41
43
|
{ '#': 'flatten-element-exist', name: flatEltName });
|
|
42
44
|
propagateToFlatElem(child, flatElt);
|
|
43
45
|
rewriteOnCondition(flatElt, flattenedSubTree);
|
|
46
|
+
adaptManagedAssociationSpecialFields(flatElt, flatEltName, flattenedSubTree);
|
|
44
47
|
orderedElementList.push([flatEltName, flatElt]);
|
|
45
48
|
});
|
|
46
49
|
}
|
|
47
50
|
else {
|
|
51
|
+
// TODO: run adaptManagedAssociationSpecialFields here as well
|
|
48
52
|
const flatElt = cloneElt(dictName, child, location, [ defName, childName ]);
|
|
49
53
|
orderedElementList.push([childName, flatElt]);
|
|
50
54
|
}
|
|
@@ -53,16 +57,26 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
53
57
|
const flatDict = orderedElementList.reduce((elements, [ flatEltName, flatElt ]) => {
|
|
54
58
|
if (flatElt.items) {
|
|
55
59
|
// rewrite annotation paths inside items.elements
|
|
56
|
-
forEachMemberRecursively(flatElt.items, (elt,
|
|
60
|
+
forEachMemberRecursively(flatElt.items, (elt, eltName, _prop, path) => {
|
|
57
61
|
const exprAnnos = Object.keys(elt).filter(pn => pn[0] === '@');
|
|
58
62
|
flattenAndPrefixExprPaths(elt, exprAnnos, elt.$path, path, 0, true);
|
|
63
|
+
if (csnUtils.isManagedAssociation(elt)) {
|
|
64
|
+
allMgdAssocDefs.push(elt);
|
|
65
|
+
}
|
|
59
66
|
}, [ flatEltName ], true, { pathWithoutProp: true } );
|
|
60
67
|
}
|
|
68
|
+
setProp(flatElt, '$pathInStructuredModel', flatElt.$path);
|
|
61
69
|
setProp(flatElt, '$path', [ ...csnPath, flatEltName ]);
|
|
62
70
|
elements[flatEltName] = flatElt;
|
|
63
71
|
return elements;
|
|
64
72
|
}, Object.create(null));
|
|
65
73
|
setProp(def, `$flat${dictName}`, flatDict);
|
|
74
|
+
orderedElementList.reduce(
|
|
75
|
+
(mgdAssocs, [ _flatEltName, flatElt ]) => {
|
|
76
|
+
if(csnUtils.isManagedAssociation(flatElt))
|
|
77
|
+
mgdAssocs.push(flatElt);
|
|
78
|
+
return mgdAssocs;
|
|
79
|
+
}, allMgdAssocDefs);
|
|
66
80
|
}
|
|
67
81
|
});
|
|
68
82
|
// entity annotations
|
|
@@ -126,7 +140,30 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
126
140
|
});
|
|
127
141
|
}
|
|
128
142
|
}
|
|
143
|
+
// loop through types as well in order to collect the managaed associations
|
|
144
|
+
// that reside in types definitions
|
|
145
|
+
if ((def.kind === 'action' || def.kind === 'function' || def.kind === 'type') && !isExternalServiceMember(def, defName)) {
|
|
146
|
+
if (def.kind === 'type' && csnUtils.isManagedAssociation(def))
|
|
147
|
+
allMgdAssocDefs.push(def);
|
|
148
|
+
else
|
|
149
|
+
forEachMemberRecursively(def, (elt, _eltName, _prop, _path) => {
|
|
150
|
+
if (csnUtils.isManagedAssociation(elt)) {
|
|
151
|
+
allMgdAssocDefs.push(elt);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
// loop through actions/functions and action/function parameters as well
|
|
156
|
+
if (def.kind === 'entity' && def.actions && !isExternalServiceMember(def, defName)) {
|
|
157
|
+
forEachGeneric(def, 'actions', (act) => {
|
|
158
|
+
forEachMemberRecursively(act, (elt, _eltName, _prop, _path) => {
|
|
159
|
+
if (csnUtils.isManagedAssociation(elt)) {
|
|
160
|
+
allMgdAssocDefs.push(elt);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
129
165
|
});
|
|
166
|
+
return allMgdAssocDefs;
|
|
130
167
|
|
|
131
168
|
function recurseIntoElement(scope, elt, resolvedElt, rootPathIsNotNull, location, rootPrefix = [], typeIdx = 0) {
|
|
132
169
|
const eltName = rootPrefix[rootPrefix.length-1];
|
|
@@ -143,7 +180,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
143
180
|
forEach(resolvedElt.elements, (childName, child) => {
|
|
144
181
|
resolvedElt = child;
|
|
145
182
|
if (child.type && !child.elements) {
|
|
146
|
-
resolvedElt =
|
|
183
|
+
resolvedElt = getFinalTypeInfo(child.type);
|
|
147
184
|
if (resolvedElt.elements)
|
|
148
185
|
typeIdx = rootPrefix.length + 1;
|
|
149
186
|
}
|
|
@@ -206,14 +243,16 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
206
243
|
function cloneElt(scope, elt, location, rootPrefix = [], typeIdx = 0) {
|
|
207
244
|
const flatElt = cloneCsnNonDict(elt,
|
|
208
245
|
{ ...options,
|
|
209
|
-
hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ]
|
|
246
|
+
hiddenPropertiesToClone: [ '$structRef', '$fkExtensions', '$generatedForeignKeys' ]
|
|
210
247
|
} );
|
|
211
248
|
|
|
212
249
|
// needed for @cds.persistence.name
|
|
213
250
|
setProp(flatElt, '$defPath', rootPrefix);
|
|
214
251
|
setProp(flatElt, '$scope', scope);
|
|
215
252
|
retypeCloneWithFinalBaseType(flatElt, location);
|
|
216
|
-
|
|
253
|
+
if((elt._type || elt).type === 'cds.Map') {
|
|
254
|
+
assignAnnotation(flatElt, '@open', elt['@open'] || true);
|
|
255
|
+
}
|
|
217
256
|
const [ nonAnnoProps, exprAnnoProps ] = Object.keys(flatElt).reduce((acc, pn) => {
|
|
218
257
|
if (pn[0] !== '@' && pn !== 'value')
|
|
219
258
|
acc[0].push(pn);
|
|
@@ -405,6 +444,25 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
405
444
|
});
|
|
406
445
|
}
|
|
407
446
|
}
|
|
447
|
+
|
|
448
|
+
// update the @odata.foreignKey4 and the $generatedForeignKeys array of
|
|
449
|
+
// an association that have been flattened
|
|
450
|
+
function adaptManagedAssociationSpecialFields(flatElt, flatEltName, flattenedSubTree) {
|
|
451
|
+
if (!(flatElt.target || flatElt.keys) && !flatElt.on )
|
|
452
|
+
return;
|
|
453
|
+
const structuredAssocName = flatElt.$defPath[flatElt.$defPath.length - 1];
|
|
454
|
+
const generatedForeignKeysForAssoc = flattenedSubTree
|
|
455
|
+
.filter(se => {
|
|
456
|
+
// compare $defPath without the last element
|
|
457
|
+
let comeFromSameDef = flatElt.$defPath.slice(0, flatElt.$defPath.length - 1).join('.') === se[1].$defPath.slice(0, se[1].$defPath.length - 1).join('.');
|
|
458
|
+
return (comeFromSameDef && (se[1]['@odata.foreignKey4'] && se[1]['@odata.foreignKey4'] === structuredAssocName) && se[0].startsWith(flatEltName))
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
generatedForeignKeysForAssoc.forEach(gfk => gfk[1]['@odata.foreignKey4'] = flatEltName);
|
|
462
|
+
// reassign the generated foreign keys for current assoc in order to assign
|
|
463
|
+
// correct values for $generatedFieldName later on during flattenManagedAssocsAsKeys();
|
|
464
|
+
setProp(flatElt, '$generatedForeignKeys', generatedForeignKeysForAssoc.map(gfk => gfk[0]));
|
|
465
|
+
}
|
|
408
466
|
}
|
|
409
467
|
|
|
410
468
|
function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, inspectRef, effectiveType,
|
|
@@ -494,6 +552,8 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
|
|
|
494
552
|
const { links, art, scope } = inspectRef(path);
|
|
495
553
|
const resolvedLinkTypes = resolveLinkTypes(links);
|
|
496
554
|
setProp(parent, '$path', [ ...path ]);
|
|
555
|
+
if (insideKeys(path))
|
|
556
|
+
setProp(parent, '_art', art);
|
|
497
557
|
const lastRef = ref[ref.length - 1];
|
|
498
558
|
const fn = (suspend = false, suspendPos = 0,
|
|
499
559
|
refFilter = (_parent) => true) => {
|
|
@@ -536,18 +596,19 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
|
|
|
536
596
|
|| path[path.length - 3] === 'projection')
|
|
537
597
|
&& path[path.length - 2] === 'columns';
|
|
538
598
|
}
|
|
539
|
-
/**
|
|
540
|
-
* Return true if the path points inside keys
|
|
541
|
-
*
|
|
542
|
-
* @param {CSN.Path} path
|
|
543
|
-
* @returns {boolean}
|
|
544
|
-
*/
|
|
545
|
-
function insideKeys( path ) {
|
|
546
|
-
return path.length >= 3
|
|
547
|
-
&& path[path.length - 2] === 'keys'
|
|
548
|
-
&& typeof path[path.length - 1] === 'number';
|
|
549
|
-
}
|
|
550
599
|
};
|
|
600
|
+
/**
|
|
601
|
+
* Return true if the path points inside keys
|
|
602
|
+
*
|
|
603
|
+
* @param {CSN.Path} path
|
|
604
|
+
* @returns {boolean}
|
|
605
|
+
*/
|
|
606
|
+
function insideKeys( path ) {
|
|
607
|
+
return path.length >= 3
|
|
608
|
+
&& path[path.length - 2] === 'keys'
|
|
609
|
+
&& typeof path[path.length - 1] === 'number';
|
|
610
|
+
}
|
|
611
|
+
|
|
551
612
|
|
|
552
613
|
// adapt queries later
|
|
553
614
|
if(ctx?.annoExpr?.['=']) {
|
|
@@ -565,9 +626,64 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
|
|
|
565
626
|
return { adaptRefs, transformer, inspectRef, effectiveType };
|
|
566
627
|
}
|
|
567
628
|
|
|
629
|
+
// replace managed associations in key refs with their respective foreing keys
|
|
630
|
+
function replaceManagedAssocsAsKeys(allFlatManagedAssocDefinitions, csnUtils) {
|
|
631
|
+
allFlatManagedAssocDefinitions.forEach( assoc => {
|
|
632
|
+
let finished = false;
|
|
633
|
+
|
|
634
|
+
while (!finished) {
|
|
635
|
+
const newKeys = [];
|
|
636
|
+
finished = processKeys(newKeys);
|
|
637
|
+
assoc.keys = newKeys;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function processKeys(collector) {
|
|
641
|
+
let done = true;
|
|
642
|
+
assoc.keys.forEach( key => {
|
|
643
|
+
const art = key._art;
|
|
644
|
+
if (art && csnUtils.isManagedAssociation(art)) {
|
|
645
|
+
done = false;
|
|
646
|
+
// key._art is the artifact from the structured model, because of that we need to look
|
|
647
|
+
// for the flat representation in the allFlatManagedAssocDefinitions
|
|
648
|
+
allFlatManagedAssocDefinitions.find(fa => (fa.$pathInStructuredModel || fa.$path).join() === art.$path.join())
|
|
649
|
+
.keys.forEach( keyAssocKey => {
|
|
650
|
+
collector.push(cloneAndExtendRef(keyAssocKey, key));
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
else if (art && !art.on){
|
|
654
|
+
if (!key.$generatedFieldName) {
|
|
655
|
+
const flatAssocName = assoc.$path[assoc.$path.length - 1];
|
|
656
|
+
// When we have a definition like type "<type_name>: Association to <target_name>",
|
|
657
|
+
// we do not generate foreign keys in the definition, therefore no $generatedForeignKeys,
|
|
658
|
+
// respectively we do not assign $generatedFieldName
|
|
659
|
+
if (assoc.$generatedForeignKeys) {
|
|
660
|
+
const generatedForeignKey = assoc.$generatedForeignKeys.find(gfk => gfk === `${flatAssocName}_${key.as || key.ref.join('_')}`);
|
|
661
|
+
key.$generatedFieldName = generatedForeignKey;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (key.as && key.as === key.ref[0]) delete key.as;
|
|
665
|
+
collector.push(key);
|
|
666
|
+
}
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
return done;
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
function cloneAndExtendRef(keyAssocKey, key) {
|
|
674
|
+
let newKey = { ref: [`${key.ref.join('_')}_${keyAssocKey.as || keyAssocKey.ref.join('_')}`] };
|
|
675
|
+
setProp(newKey, '_art', keyAssocKey._art);
|
|
676
|
+
if (key.as) {
|
|
677
|
+
newKey.as = `${key.as}_${keyAssocKey.as || keyAssocKey.ref.join('_')}`;
|
|
678
|
+
}
|
|
679
|
+
return newKey;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
568
683
|
module.exports = {
|
|
569
684
|
allInOneFlattening,
|
|
570
685
|
flattenAllStructStepsInRefs,
|
|
571
686
|
handleManagedAssociationsAndCreateForeignKeys,
|
|
572
|
-
getStructRefFlatteningTransformer
|
|
687
|
+
getStructRefFlatteningTransformer,
|
|
688
|
+
replaceManagedAssocsAsKeys
|
|
573
689
|
};
|
|
@@ -64,6 +64,7 @@ const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
|
64
64
|
function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackSchemaName, options, csnUtils, message) {
|
|
65
65
|
const { error } = message;
|
|
66
66
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
67
|
+
|
|
67
68
|
// are we working with OData proxies or cross-service refs
|
|
68
69
|
const isMultiSchema = options.odataVersion === 'v4' && (options.odataProxies || options.odataXServiceRefs);
|
|
69
70
|
// service sub schemas as return value
|
|
@@ -166,7 +167,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
166
167
|
isKey = false;
|
|
167
168
|
// in case this was a named type and if the openness does not match the type definition
|
|
168
169
|
// expose the type as a new one not changing the original definition.
|
|
169
|
-
if(elements && !!node['@open'] !== !!typeDef['@open'])
|
|
170
|
+
if(elements && node['@open'] != null && !!node['@open'] !== !!typeDef['@open'])
|
|
170
171
|
fullQualifiedNewTypeName += node['@open'] ? '_open' : '_closed';
|
|
171
172
|
lastNonAnonymousFQDefName = fullQualifiedNewTypeName;
|
|
172
173
|
}
|
|
@@ -293,10 +294,10 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
293
294
|
if(type) {
|
|
294
295
|
typeName = (type.ref && csnUtils.artifactRef(type)) || type;
|
|
295
296
|
const rc = { isExposable: true, typeDef, typeName, isAnonymous: false };
|
|
296
|
-
if(!isBuiltinType(typeName) && typeName !== special$self) {
|
|
297
|
+
if((!isBuiltinType(typeName) || typeName === 'cds.Map') && typeName !== special$self) {
|
|
297
298
|
rc.typeDef = typeDef = csnUtils.artifactRef(typeName, typeName);
|
|
298
299
|
if(!isArtifactInService(typeName, serviceName)) {
|
|
299
|
-
while(!isBuiltinType(typeName)) {
|
|
300
|
+
while(!isBuiltinType(typeName) || typeDef.$emtpyMapType) {
|
|
300
301
|
typeDef = csnUtils.artifactRef(typeName, typeName);
|
|
301
302
|
if(typeDef !== typeName) {
|
|
302
303
|
// Implementation note: For `type S: T:struct;`, elements from `T:struct` were already propagated to `S`.
|
|
@@ -1332,14 +1332,14 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
1332
1332
|
|
|
1333
1333
|
function isScalarOrNoType(art) {
|
|
1334
1334
|
art = effectiveType(art);
|
|
1335
|
-
if(art) {
|
|
1336
|
-
const type = art.type ||
|
|
1337
|
-
// items in ON
|
|
1335
|
+
if (art) {
|
|
1336
|
+
const type = art.type || art.items?.type;
|
|
1337
|
+
// items in ON-conditions are illegal but this should be checked elsewhere
|
|
1338
1338
|
const elements = art.elements || (art.items && art.items.elements);
|
|
1339
1339
|
// @Core.Computed has no type
|
|
1340
|
-
return(!elements && !type ||
|
|
1341
|
-
|
|
1342
|
-
|
|
1340
|
+
return (!elements && !type ||
|
|
1341
|
+
(type && isBuiltinType(type) &&
|
|
1342
|
+
type !== 'cds.Association' && type !== 'cds.Composition' && type !== 'cds.Map'))
|
|
1343
1343
|
}
|
|
1344
1344
|
return false;
|
|
1345
1345
|
}
|