@sap/cds-compiler 4.6.2 → 4.7.4
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 +37 -0
- package/bin/cds_update_identifiers.js +6 -2
- package/bin/cdsc.js +1 -1
- package/doc/CHANGELOG_ARCHIVE.md +9 -9
- package/doc/CHANGELOG_BETA.md +6 -0
- package/lib/api/main.js +56 -9
- package/lib/api/options.js +6 -3
- package/lib/api/validate.js +20 -29
- package/lib/base/message-registry.js +27 -3
- package/lib/base/messages.js +8 -3
- package/lib/base/model.js +2 -0
- package/lib/checks/dbFeatureFlags.js +28 -0
- package/lib/checks/elements.js +81 -13
- package/lib/checks/enricher.js +3 -2
- package/lib/checks/validator.js +38 -4
- package/lib/compiler/assert-consistency.js +4 -4
- package/lib/compiler/checks.js +5 -4
- package/lib/compiler/define.js +2 -2
- package/lib/compiler/generate.js +2 -1
- package/lib/compiler/propagator.js +3 -11
- package/lib/compiler/shared.js +2 -1
- package/lib/compiler/tweak-assocs.js +43 -24
- package/lib/edm/annotations/edmJson.js +3 -0
- package/lib/edm/annotations/genericTranslation.js +156 -106
- package/lib/edm/annotations/preprocessAnnotations.js +11 -14
- package/lib/edm/csn2edm.js +27 -24
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +135 -37
- package/lib/edm/edmUtils.js +20 -7
- package/lib/gen/Dictionary.json +1 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +9 -11
- package/lib/gen/languageParser.js +5942 -5446
- package/lib/json/to-csn.js +7 -114
- package/lib/language/genericAntlrParser.js +106 -48
- package/lib/model/cloneCsn.js +203 -0
- package/lib/model/csnRefs.js +11 -3
- package/lib/model/csnUtils.js +42 -85
- package/lib/optionProcessor.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +133 -88
- package/lib/render/toHdbcds.js +1 -5
- package/lib/render/toSql.js +7 -9
- package/lib/render/utils/common.js +9 -16
- package/lib/transform/addTenantFields.js +277 -102
- package/lib/transform/db/applyTransformations.js +14 -9
- package/lib/transform/db/backlinks.js +2 -1
- package/lib/transform/db/constraints.js +60 -82
- package/lib/transform/db/expansion.js +6 -6
- package/lib/transform/db/featureFlags.js +5 -0
- package/lib/transform/db/flattening.js +4 -4
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/rewriteCalculatedElements.js +2 -2
- package/lib/transform/db/transformExists.js +12 -0
- package/lib/transform/db/views.js +5 -2
- package/lib/transform/draft/odata.js +7 -6
- package/lib/transform/effective/associations.js +2 -1
- package/lib/transform/effective/main.js +3 -2
- package/lib/transform/effective/types.js +6 -3
- package/lib/transform/forOdata.js +39 -24
- package/lib/transform/forRelationalDB.js +34 -27
- package/lib/transform/localized.js +29 -9
- package/lib/transform/odata/flattening.js +419 -0
- package/lib/transform/odata/toFinalBaseType.js +95 -15
- package/lib/transform/odata/typesExposure.js +9 -7
- package/lib/transform/transformUtils.js +7 -6
- package/lib/transform/translateAssocsToJoins.js +3 -3
- package/lib/utils/objectUtils.js +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isBuiltinType, isMagicVariable, forEachDefinition,
|
|
4
|
+
copyAnnotations, forEachMemberRecursively,
|
|
5
|
+
transformExpression, findAnnotationExpression } = require('../../model/csnUtils');
|
|
6
|
+
const transformUtils = require('../transformUtils');
|
|
7
|
+
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
8
|
+
const { applyTransformationsOnDictionary,
|
|
9
|
+
applyTransformationsOnNonDictionary } = require('../db/applyTransformations.js');
|
|
10
|
+
const { linkForeignKeyAnnotationExtensionsToAssociation,
|
|
11
|
+
handleManagedAssociationsAndCreateForeignKeys } = require('../db/flattening');
|
|
12
|
+
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
13
|
+
const { forEach } = require('../../utils/objectUtils');
|
|
14
|
+
|
|
15
|
+
function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternalServiceMember, error, csnUtils, options) {
|
|
16
|
+
|
|
17
|
+
const isExprAnno = (obj, pn) => {
|
|
18
|
+
return isBetaEnabled(options, 'odataPathsInAnnotationExpressions') && findAnnotationExpression(obj, pn);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
forEachDefinition(csn, (def, defName) => {
|
|
22
|
+
if(def.kind === 'entity' && !isExternalServiceMember(def, defName)) {
|
|
23
|
+
['elements', 'params'].forEach(dictName => {
|
|
24
|
+
const dict = def[dictName];
|
|
25
|
+
if(dict) {
|
|
26
|
+
const csnPath = ['definitions', defName, dictName];
|
|
27
|
+
const orderedElementList = [];
|
|
28
|
+
|
|
29
|
+
forEach(dict, (eltName, elt) => {
|
|
30
|
+
const location = [ ...csnPath, eltName ];
|
|
31
|
+
const resolvedElt = (elt.type && !elt.elements) ? csnUtils.getFinalTypeInfo(elt.type) : elt;
|
|
32
|
+
if(resolvedElt.elements) {
|
|
33
|
+
const flattenedSubTree = recurseIntoElement(dictName, elt, resolvedElt,
|
|
34
|
+
!!elt.notNull, location, [ defName, eltName ]);
|
|
35
|
+
|
|
36
|
+
flattenedSubTree.forEach(([flatEltName, flatElt]) => {
|
|
37
|
+
if (dict[flatEltName] || orderedElementList.some(elt => elt[0] === flatEltName))
|
|
38
|
+
error('name-duplicate-element', location,
|
|
39
|
+
{ '#': 'flatten-element-exist', name: flatEltName });
|
|
40
|
+
propagateToFlatElem(elt, flatElt);
|
|
41
|
+
rewriteOnCondition(flatElt, flattenedSubTree);
|
|
42
|
+
orderedElementList.push([flatEltName, flatElt]);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const flatElt = cloneElt(dictName, elt, location, [ defName, eltName ]);
|
|
47
|
+
orderedElementList.push([eltName, flatElt]);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const flatDict = orderedElementList.reduce((elements, [ flatEltName, flatElt ]) => {
|
|
52
|
+
if(flatElt.items) {
|
|
53
|
+
// rewrite annotation paths inside items.elements
|
|
54
|
+
forEachMemberRecursively(flatElt.items, (elt, _eltName, _prop, path) => {
|
|
55
|
+
const exprAnnos = Object.keys(elt).filter(pn => isExprAnno(elt, pn));
|
|
56
|
+
flattenAndPrefixExprPaths(elt, exprAnnos, elt.$path, path, 0, true);
|
|
57
|
+
}, [ flatEltName ], true, { pathWithoutProp: true } );
|
|
58
|
+
}
|
|
59
|
+
setProp(flatElt, '$path', [ ...csnPath, flatEltName ]);
|
|
60
|
+
elements[flatEltName] = flatElt;
|
|
61
|
+
return elements;
|
|
62
|
+
}, Object.create(null));
|
|
63
|
+
setProp(def, `$${dictName}`, flatDict);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
const flatAnnos = Object.create(null);
|
|
67
|
+
const annoNames = copyAnnotations(def, flatAnnos).filter(an => isExprAnno(def, an));
|
|
68
|
+
flattenAndPrefixExprPaths(flatAnnos, annoNames, [ 'definitions', defName ], [ defName ], 0);
|
|
69
|
+
setProp(def, '$flatAnnotations', flatAnnos);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
function recurseIntoElement(scope, elt, resolvedElt, rootPathIsNotNull, location, rootPrefix = [], typeIdx = 0) {
|
|
74
|
+
const eltName = rootPrefix[rootPrefix.length-1];
|
|
75
|
+
if(!resolvedElt.elements) {
|
|
76
|
+
const flatElt = cloneElt(scope, elt, location, rootPrefix, typeIdx);
|
|
77
|
+
if(rootPathIsNotNull)
|
|
78
|
+
flatElt.notNull = true;
|
|
79
|
+
else
|
|
80
|
+
delete flatElt.notNull;
|
|
81
|
+
return [[ eltName, flatElt ]];
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
let flattenedSubTree = [];
|
|
85
|
+
forEach(resolvedElt.elements, (childName, child) => {
|
|
86
|
+
resolvedElt = child;
|
|
87
|
+
if(child.type && !child.elements) {
|
|
88
|
+
resolvedElt = csnUtils.getFinalTypeInfo(child.type);
|
|
89
|
+
if(resolvedElt.elements)
|
|
90
|
+
typeIdx = rootPrefix.length + 1;
|
|
91
|
+
}
|
|
92
|
+
flattenedSubTree = flattenedSubTree.concat(recurseIntoElement(scope, child, resolvedElt,
|
|
93
|
+
!!(child.notNull && rootPathIsNotNull),
|
|
94
|
+
[... location, 'elements', childName],
|
|
95
|
+
[ ...rootPrefix, childName ], typeIdx));
|
|
96
|
+
});
|
|
97
|
+
// 1) rename, 2) filter duplicates and finalize new elements
|
|
98
|
+
const duplicateDict = Object.create(null);
|
|
99
|
+
return flattenedSubTree.map(([flatEltName, flatElt]) => {
|
|
100
|
+
return [ `${eltName}_${flatEltName}`, flatElt ];
|
|
101
|
+
}).filter(([name, flatElt]) =>{
|
|
102
|
+
if(duplicateDict[name]) {
|
|
103
|
+
error('name-duplicate-element', location,
|
|
104
|
+
{ '#': 'flatten-element-gen', name });
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
propagateToFlatElem(elt, flatElt, rootPrefix, typeIdx);
|
|
109
|
+
duplicateDict[name] = flatElt;
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function propagateToFlatElem(elt, flatElt, rootPrefix = [], typeIdx = 0) {
|
|
117
|
+
// Copy annotations from struct
|
|
118
|
+
// (not overwriting, because deep annotations should have precedence).
|
|
119
|
+
// This has historic reasons. We don't copy doc-comments because copying annotations
|
|
120
|
+
// is questionable to begin with. Only selected annotations should have been copied,
|
|
121
|
+
// if at all.
|
|
122
|
+
// When flattening structured elements for OData don't propagate the odata.Type annotations
|
|
123
|
+
// as these would falsify the flattened elements.
|
|
124
|
+
|
|
125
|
+
// TODO: directly refer to edmPrimitiveType facets
|
|
126
|
+
const excludes = {
|
|
127
|
+
'@odata.Type': 1,
|
|
128
|
+
'@odata.Scale': 1,
|
|
129
|
+
'@odata.Precision': 1,
|
|
130
|
+
'@odata.MaxLength': 1,
|
|
131
|
+
'@odata.SRID': 1,
|
|
132
|
+
'@odata.FixedLength': 1,
|
|
133
|
+
'@odata.Collation': 1,
|
|
134
|
+
'@odata.Unicode': 1,
|
|
135
|
+
};
|
|
136
|
+
// TODO: copy only those expression annotations that have no path into unreachable subtree, starting
|
|
137
|
+
// of typePathRoot (which could be some completely different path)
|
|
138
|
+
const exprAnnoNames = copyAnnotations(elt, flatElt, false, excludes).filter(pn => isExprAnno(flatElt, pn));
|
|
139
|
+
flattenAndPrefixExprPaths(flatElt, exprAnnoNames, elt.$path, rootPrefix, typeIdx);
|
|
140
|
+
// Copy selected type properties
|
|
141
|
+
['key', 'virtual', 'masked', 'viaAll', 'localized'].forEach(p => {
|
|
142
|
+
if (elt[p] != null)
|
|
143
|
+
flatElt[p] = elt[p];
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Copy the original element
|
|
148
|
+
function cloneElt(scope, elt, location, rootPrefix = [], typeIdx = 0) {
|
|
149
|
+
const flatElt = cloneCsnNonDict(elt,
|
|
150
|
+
{ ...options,
|
|
151
|
+
hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ]
|
|
152
|
+
} );
|
|
153
|
+
|
|
154
|
+
// needed for @cds.persistence.name
|
|
155
|
+
setProp(flatElt, '$defPath', rootPrefix);
|
|
156
|
+
setProp(flatElt, '$scope', scope);
|
|
157
|
+
retypeCloneWithFinalBaseType(flatElt, location);
|
|
158
|
+
|
|
159
|
+
const [ nonAnnoProps, exprAnnoProps ] = Object.keys(flatElt).reduce((acc, pn) => {
|
|
160
|
+
if(pn[0] !== '@' && pn !== 'value')
|
|
161
|
+
acc[0].push(pn);
|
|
162
|
+
if(isExprAnno(flatElt, pn) || pn === 'value')
|
|
163
|
+
acc[1].push(pn);
|
|
164
|
+
return acc;
|
|
165
|
+
}, [[],[]]);
|
|
166
|
+
// transform all non annotation properties for that flat element with the generic transformer
|
|
167
|
+
// we don't know what's inside the element clone (like anonymous sub elements behind 'items' etc.)
|
|
168
|
+
nonAnnoProps.forEach(pn => applyTransformationsOnNonDictionary(flatElt, pn, refFlattener, {}, elt.$path || location))
|
|
169
|
+
adaptRefs.forEach(fn => fn());
|
|
170
|
+
adaptRefs.length = 0;
|
|
171
|
+
// flatten and prefix annotations and 'value' paths
|
|
172
|
+
flattenAndPrefixExprPaths(flatElt, exprAnnoProps, elt.$path || location, rootPrefix, typeIdx);
|
|
173
|
+
return flatElt;
|
|
174
|
+
|
|
175
|
+
function retypeCloneWithFinalBaseType(elt, location) {
|
|
176
|
+
if(elt.type &&
|
|
177
|
+
!isBuiltinType(elt.type) &&
|
|
178
|
+
!isODataV4BuiltinFromService(elt.type, location)
|
|
179
|
+
&& !isItemsType(elt.type)) {
|
|
180
|
+
let resolvedType = csnUtils.getFinalTypeInfo(elt);
|
|
181
|
+
|
|
182
|
+
delete resolvedType.kind;
|
|
183
|
+
if(resolvedType.items)
|
|
184
|
+
delete resolvedType.type;
|
|
185
|
+
|
|
186
|
+
if(elt.items) {
|
|
187
|
+
if(resolvedType.items) {
|
|
188
|
+
elt.items = resolvedType;
|
|
189
|
+
delete elt.items.type;
|
|
190
|
+
}
|
|
191
|
+
else
|
|
192
|
+
elt.items.type = resolvedType;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
if(resolvedType.items) {
|
|
196
|
+
elt.items = resolvedType.items;
|
|
197
|
+
delete elt.type;
|
|
198
|
+
}
|
|
199
|
+
else
|
|
200
|
+
elt.type = resolvedType;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function isItemsType(typeName) {
|
|
205
|
+
let typeDef = csn.definitions[typeName];
|
|
206
|
+
return !!typeDef?.items;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function isODataV4BuiltinFromService( typeName, path ) {
|
|
210
|
+
if (options.odataVersion === 'v2' || typeof typeName !== 'string')
|
|
211
|
+
return false;
|
|
212
|
+
|
|
213
|
+
const typeServiceName = csnUtils.getServiceName(typeName);
|
|
214
|
+
let finalBaseType = csnUtils.getFinalTypeInfo(typeName).type;
|
|
215
|
+
if(!isBuiltinType(finalBaseType)) {
|
|
216
|
+
const typeDef = csn.definitions[finalBaseType];
|
|
217
|
+
finalBaseType = typeDef?.items?.type || typeDef?.type;
|
|
218
|
+
}
|
|
219
|
+
// we need the service of the current definition
|
|
220
|
+
const currDefServiceName = csnUtils.getServiceName(path[1]);
|
|
221
|
+
return typeServiceName === currDefServiceName && isBuiltinType(finalBaseType);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// The path rewriting must be done with the current CSN path of that exact annotation location
|
|
227
|
+
// in the element tree and done exactly after the initial copy, (otherwise, csnRefs is not able
|
|
228
|
+
// to locate the relative location of the path expression in this element).
|
|
229
|
+
// At this time both annotations and values must be rewritten.
|
|
230
|
+
//
|
|
231
|
+
// Later, the query can/must be rewritten as long as a flat OData CSN is published
|
|
232
|
+
// but this then operates on the entity/view which has all struct infos available
|
|
233
|
+
function flattenAndPrefixExprPaths(carrier, propNames, csnPath, rootPrefix, typeIdx, refParentIsItems = false) {
|
|
234
|
+
refFlattener.$refParentIsItems = refParentIsItems;
|
|
235
|
+
const refCheck = {
|
|
236
|
+
ref: (parent, prop, xpr, path) => {
|
|
237
|
+
const { art } = inspectRef(path);
|
|
238
|
+
const ft = csnUtils.getFinalTypeInfo(art?.type);
|
|
239
|
+
if(!isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
|
|
240
|
+
error('odata-anno-xpr-ref', path, { elemref: parent, name: refCheck.eltLocationStr, anno: refCheck.anno, '#': 'flatten_builtin' });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
refCheck.eltLocationStr = (function() {
|
|
246
|
+
const [head, ...tail ] = rootPrefix;
|
|
247
|
+
return `${head}:${tail.join('.')}`;
|
|
248
|
+
})();
|
|
249
|
+
|
|
250
|
+
const absolutifier = {
|
|
251
|
+
ref : (parent, prop, xpr) => {
|
|
252
|
+
const head = xpr[0].id || xpr[0];
|
|
253
|
+
if(typeIdx > 0 && head === '$self' && !isMagicVariable(head)) {
|
|
254
|
+
const [xprHead, ...xprTail] = xpr.slice(1, xpr.length);
|
|
255
|
+
parent[prop] = [ rootPrefix.slice(1, typeIdx).concat(xprHead).join('_'), ...xprTail];
|
|
256
|
+
if(carrier.$scope === 'params')
|
|
257
|
+
parent.param = true;
|
|
258
|
+
else
|
|
259
|
+
parent[prop].unshift('$self');
|
|
260
|
+
}
|
|
261
|
+
else if(rootPrefix.length > 2 && head !== '$self' && !parent.param && !isMagicVariable(head)) {
|
|
262
|
+
const [xprHead, ...xprTail] = xpr;
|
|
263
|
+
if(!refParentIsItems)
|
|
264
|
+
parent[prop] = [ rootPrefix.slice(1, -1).concat(xprHead).join('_'), ...xprTail];
|
|
265
|
+
else
|
|
266
|
+
parent[prop] = [ ...rootPrefix.slice(0, rootPrefix.length-1), ...xpr];
|
|
267
|
+
if(carrier.$scope === 'params')
|
|
268
|
+
parent.param = true;
|
|
269
|
+
else
|
|
270
|
+
parent[prop].unshift('$self');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
propNames.forEach(pn => {
|
|
276
|
+
refCheck.anno = pn;
|
|
277
|
+
transformExpression(carrier, pn, [ refCheck, refFlattener ], csnPath);
|
|
278
|
+
});
|
|
279
|
+
adaptRefs.forEach(fn => fn());
|
|
280
|
+
adaptRefs.length = 0;
|
|
281
|
+
refFlattener.$refParentIsItems = false;
|
|
282
|
+
propNames.forEach(pn => {
|
|
283
|
+
transformExpression(carrier, pn, absolutifier, csnPath)
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// TODO: This should be part of the generic path rewriting algorithm
|
|
288
|
+
// Primitive approach here does not take into account absolute paths via $self etc...
|
|
289
|
+
function rewriteOnCondition(flatElt, flattenedSubTree) {
|
|
290
|
+
if (flatElt.on) {
|
|
291
|
+
// unmanaged relations can't be primary key
|
|
292
|
+
delete flatElt.key;
|
|
293
|
+
// Make refs resolvable by fixing the first ref step
|
|
294
|
+
transformExpression(flatElt, 'on', {
|
|
295
|
+
ref: (parent, prop, xpr) => {
|
|
296
|
+
const prefix = flatElt.$defPath.slice(1, -1).join('_');
|
|
297
|
+
const possibleFlatName = `${prefix}_${xpr[0]}`;
|
|
298
|
+
if (flattenedSubTree.find(entry => entry[0] === possibleFlatName))
|
|
299
|
+
xpr[0] = possibleFlatName;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Rewrite all structured references in the model such that it matches their flat counterparts
|
|
308
|
+
* @param {CSN.Model} csn
|
|
309
|
+
* @param {CSN.Options} options
|
|
310
|
+
* @param {WeakMap} resolved Cache for resolved refs
|
|
311
|
+
* @param {string} pathDelimiter
|
|
312
|
+
* @param {object} iterateOptions
|
|
313
|
+
*/
|
|
314
|
+
function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, iterateOptions = {} ) {
|
|
315
|
+
|
|
316
|
+
// All anno path flattening is already done, don't do it on locations where we don't want it!
|
|
317
|
+
forEachDefinition(csn, (def, defName) => {
|
|
318
|
+
if(def.kind === 'entity') {
|
|
319
|
+
['query', 'projection'].forEach(dictName => {
|
|
320
|
+
applyTransformationsOnNonDictionary(csn.definitions[defName], dictName, refFlattener, iterateOptions, [ 'definitions', defName ]);
|
|
321
|
+
})
|
|
322
|
+
if(csn.definitions[defName].actions)
|
|
323
|
+
applyTransformationsOnDictionary(csn.definitions[defName].actions, refFlattener, iterateOptions, [ 'definitions', defName, 'actions' ]);
|
|
324
|
+
}
|
|
325
|
+
/*
|
|
326
|
+
TODO: In real hybrid flat/struct OData transformation, ref rewriting in types must not be done
|
|
327
|
+
together with non-rewriting ON condition refs during foreign key creation
|
|
328
|
+
*/
|
|
329
|
+
if(['type'].includes(def.kind)) {
|
|
330
|
+
applyTransformationsOnNonDictionary(csn.definitions, defName, refFlattener, iterateOptions, [ 'definitions' ]);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
adaptRefs.forEach(fn => fn());
|
|
334
|
+
adaptRefs.length = 0;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, pathDelimiter) {
|
|
338
|
+
const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
339
|
+
/**
|
|
340
|
+
* For each step of the links, check if there is a type reference.
|
|
341
|
+
* If there is, resolve it and store the result in a WeakMap.
|
|
342
|
+
*
|
|
343
|
+
* @param {Array} [links]
|
|
344
|
+
* @todo seems too hacky
|
|
345
|
+
* @returns {WeakMap} A WeakMap where a link is the key and the type is the value
|
|
346
|
+
*/
|
|
347
|
+
function resolveLinkTypes( links = [] ) {
|
|
348
|
+
const resolvedLinkTypes = new WeakMap();
|
|
349
|
+
links.forEach((link) => {
|
|
350
|
+
const { art } = link;
|
|
351
|
+
if (art && art.type)
|
|
352
|
+
resolvedLinkTypes.set(link, effectiveType(art));
|
|
353
|
+
});
|
|
354
|
+
return resolvedLinkTypes;
|
|
355
|
+
}
|
|
356
|
+
const adaptRefs = [];
|
|
357
|
+
const transformer = {
|
|
358
|
+
$refParentIsItems: false,
|
|
359
|
+
ref: (parent, prop, ref, path) => {
|
|
360
|
+
const { links, art, scope } = inspectRef(path);
|
|
361
|
+
const resolvedLinkTypes = resolveLinkTypes(links);
|
|
362
|
+
setProp(parent, '$path', [ ...path ]);
|
|
363
|
+
const lastRef = ref[ref.length - 1];
|
|
364
|
+
const fn = () => {
|
|
365
|
+
const scopedPath = [ ...parent.$path ];
|
|
366
|
+
// TODO: If foreign key annotations should be assigned via
|
|
367
|
+
// full path into target, uncomment this line and
|
|
368
|
+
// comment/remove setProp in expansion.js
|
|
369
|
+
// setProp(parent, '$structRef', parent.ref);
|
|
370
|
+
parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes, transformer.$refParentIsItems);
|
|
371
|
+
resolved.set(parent, { links, art, scope });
|
|
372
|
+
// Explicitly set implicit alias for things that are now flattened - but only in columns
|
|
373
|
+
// TODO: Can this be done elegantly during expand phase already?
|
|
374
|
+
if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
|
|
375
|
+
if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
|
|
376
|
+
delete parent.as;
|
|
377
|
+
delete parent.$implicitAlias;
|
|
378
|
+
}
|
|
379
|
+
// To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
|
|
380
|
+
else if (parent.ref[parent.ref.length - 1] !== lastRef &&
|
|
381
|
+
(insideColumns(scopedPath) || insideKeys(scopedPath)) &&
|
|
382
|
+
!parent.as) {
|
|
383
|
+
parent.as = lastRef;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Return true if the path points inside columns
|
|
388
|
+
*
|
|
389
|
+
* @param {CSN.Path} path
|
|
390
|
+
* @returns {boolean}
|
|
391
|
+
*/
|
|
392
|
+
function insideColumns( path ) {
|
|
393
|
+
return path.length >= 3 && (path[path.length - 3] === 'SELECT' || path[path.length - 3] === 'projection') && path[path.length - 2] === 'columns';
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Return true if the path points inside keys
|
|
397
|
+
*
|
|
398
|
+
* @param {CSN.Path} path
|
|
399
|
+
* @returns {boolean}
|
|
400
|
+
*/
|
|
401
|
+
function insideKeys( path ) {
|
|
402
|
+
return path.length >= 3 && path[path.length - 2] === 'keys' && typeof path[path.length - 1] === 'number';
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
// adapt queries later
|
|
406
|
+
adaptRefs.push(fn);
|
|
407
|
+
},
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return { adaptRefs, transformer, inspectRef, effectiveType };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
module.exports = {
|
|
414
|
+
allInOneFlattening,
|
|
415
|
+
flattenAllStructStepsInRefs,
|
|
416
|
+
linkForeignKeyAnnotationExtensionsToAssociation,
|
|
417
|
+
handleManagedAssociationsAndCreateForeignKeys,
|
|
418
|
+
getStructRefFlatteningTransformer
|
|
419
|
+
};
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { isBetaEnabled } = require('../../base/model');
|
|
3
|
+
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
4
4
|
const {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
transformExpression,
|
|
6
|
+
forEachDefinition,
|
|
7
|
+
forEachGeneric,
|
|
8
|
+
forEachMemberRecursively,
|
|
9
|
+
isBuiltinType,
|
|
10
|
+
findAnnotationExpression,
|
|
7
11
|
} = require('../../model/csnUtils');
|
|
8
12
|
const { isArtifactInSomeService, isArtifactInService } = require('./utils');
|
|
13
|
+
const { cloneCsnDict, cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
9
14
|
|
|
10
|
-
function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
|
|
15
|
+
function expandToFinalBaseType(csn, transformers, csnUtils, services, options, error) {
|
|
11
16
|
const isV4 = options.odataVersion === 'v4';
|
|
12
17
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
13
18
|
forEachDefinition(csn, (def, defName) => {
|
|
14
19
|
// Unravel derived type chains to final one for elements, actions, action parameters (propagating annotations)
|
|
15
|
-
forEachMemberRecursively(def, (member) => {
|
|
20
|
+
forEachMemberRecursively(def, (member, _memberName) => {
|
|
16
21
|
expandToFinalBaseType(member, defName);
|
|
17
22
|
expandToFinalBaseType(member.items, defName);
|
|
18
23
|
expandToFinalBaseType(member.returns, defName);
|
|
@@ -85,7 +90,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
|
|
|
85
90
|
if (def.items.type && !isBuiltinType(def.items.type)) {
|
|
86
91
|
let finalBaseType = csnUtils.getFinalTypeInfo(def.items.type);
|
|
87
92
|
if (finalBaseType?.elements) {
|
|
88
|
-
def.items.elements =
|
|
93
|
+
def.items.elements = cloneCsnDict(finalBaseType.elements, options);
|
|
89
94
|
delete def.items.type;
|
|
90
95
|
}
|
|
91
96
|
}
|
|
@@ -138,7 +143,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
|
|
|
138
143
|
transformers.toFinalBaseType(node);
|
|
139
144
|
}
|
|
140
145
|
else if (csnUtils.isStructured(finalBaseType)) {
|
|
141
|
-
cloneElements(finalBaseType);
|
|
146
|
+
cloneElements(node, finalBaseType);
|
|
142
147
|
}
|
|
143
148
|
else if (node.type && node.items)
|
|
144
149
|
delete node.type;
|
|
@@ -169,8 +174,8 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
|
|
|
169
174
|
transformers.toFinalBaseType(node);
|
|
170
175
|
// node.type = finalType;
|
|
171
176
|
}
|
|
172
|
-
else if (node.type
|
|
173
|
-
cloneElements(finalBaseType);
|
|
177
|
+
else if (node.type.ref) {
|
|
178
|
+
cloneElements(node, finalBaseType);
|
|
174
179
|
}
|
|
175
180
|
}
|
|
176
181
|
}
|
|
@@ -189,26 +194,78 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
|
|
|
189
194
|
}
|
|
190
195
|
}
|
|
191
196
|
|
|
192
|
-
function cloneElements(finalBaseType) {
|
|
193
|
-
// cloneCsn only works correctly if we start "from the top"
|
|
197
|
+
function cloneElements(node, finalBaseType) {
|
|
194
198
|
let clone;
|
|
195
199
|
// do the clone only if really needed
|
|
196
200
|
if((finalBaseType.items && !node.items) ||
|
|
197
|
-
(finalBaseType.elements && !node.elements))
|
|
198
|
-
|
|
201
|
+
(finalBaseType.elements && !node.elements)) {
|
|
202
|
+
// use the 'real' type, don't clone a clone
|
|
203
|
+
let _type = node._type;
|
|
204
|
+
while(_type._type && !_type.items && !_type.elements)
|
|
205
|
+
_type = _type._type;
|
|
206
|
+
clone = cloneCsnNonDict(_type, { ...options, hiddenPropertiesToClone: [ '_type' ] });
|
|
207
|
+
fitClonedElementsIntoParent(clone, node, _type.$path);
|
|
208
|
+
}
|
|
199
209
|
if (finalBaseType.items) {
|
|
200
210
|
delete node.type;
|
|
201
211
|
if(!node.items)
|
|
202
|
-
Object.assign(node, { items: clone.
|
|
212
|
+
Object.assign(node, { items: clone.items });
|
|
203
213
|
}
|
|
204
214
|
if (finalBaseType.elements) {
|
|
205
215
|
if(!finalBaseType.items)
|
|
206
216
|
delete node.type;
|
|
207
217
|
if(!node.elements)
|
|
208
|
-
Object.assign(node, { elements: clone.
|
|
218
|
+
Object.assign(node, { elements: clone.elements });
|
|
209
219
|
}
|
|
210
220
|
}
|
|
211
221
|
|
|
222
|
+
function fitClonedElementsIntoParent(clone, node, typeRefCsnPath) {
|
|
223
|
+
const f = (p) => {
|
|
224
|
+
const [h, ...t ] = p;
|
|
225
|
+
return `${h}:${t.join('.')}`;
|
|
226
|
+
}
|
|
227
|
+
const typeRefStr = f(node.type.ref);
|
|
228
|
+
const typeRefRootPath = $path2path(typeRefCsnPath);
|
|
229
|
+
forEachMemberRecursively(clone, (elt, eltName, prop, location) => {
|
|
230
|
+
const [ xprANames /*nxprANames */ ] = Object.keys(elt).reduce((acc, pn) => {
|
|
231
|
+
if (pn[0] === '@')
|
|
232
|
+
acc[findAnnotationExpression(elt, pn) ? 0 : 1].push(pn);
|
|
233
|
+
return acc;
|
|
234
|
+
}, [ [], [] ]);
|
|
235
|
+
const usingPositionStr = f($path2path(location));
|
|
236
|
+
const eltRootPath = $path2path(elt.$path);
|
|
237
|
+
xprANames.forEach(xprAName => {
|
|
238
|
+
//let isSubTreeSpan = true;
|
|
239
|
+
transformExpression(elt, xprAName, {
|
|
240
|
+
ref: (parent, prop, xpr, csnPath) => {
|
|
241
|
+
let prefixMatch = true;
|
|
242
|
+
const head = xpr[0].id || xpr[0];
|
|
243
|
+
if (head === '$self') {
|
|
244
|
+
for (let i = 1; i < typeRefRootPath.length && prefixMatch; i++){
|
|
245
|
+
prefixMatch = (xpr[i].id || xpr[i]) === typeRefRootPath[i];
|
|
246
|
+
}
|
|
247
|
+
if(prefixMatch && xpr.length > typeRefRootPath.length) {
|
|
248
|
+
parent[prop] = [ ...xpr.slice(eltRootPath.length-1)];
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
error('odata-anno-xpr-ref', csnPath,
|
|
252
|
+
{ anno: xprAName, elemref: xpr.join('.'), name: usingPositionStr, code: typeRefStr });
|
|
253
|
+
//isSubTreeSpan = false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
}, elt.$path);
|
|
258
|
+
// if(!isSubTreeSpan) {
|
|
259
|
+
// // remove annotation and sub annotations from cloned element
|
|
260
|
+
// delete elt[xprAName];
|
|
261
|
+
// nxprANames.filter(an => an.startsWith(`${xprAName}.`)).forEach((nxprAName) => {
|
|
262
|
+
// delete elt[nxprAName];
|
|
263
|
+
// });
|
|
264
|
+
// }
|
|
265
|
+
});
|
|
266
|
+
setProp(elt, '$path', location);
|
|
267
|
+
}, node.$path);
|
|
268
|
+
}
|
|
212
269
|
/*
|
|
213
270
|
Check, if a type needs to be expanded into the service
|
|
214
271
|
|
|
@@ -227,6 +284,29 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
|
|
|
227
284
|
const isInCurServ = isArtifactInService(node.type, currService);
|
|
228
285
|
return !isV4 || !(isBuiltin && !isAssoc && isInCurServ);
|
|
229
286
|
}
|
|
287
|
+
|
|
288
|
+
// convert $path to path starting at main artifact
|
|
289
|
+
function $path2path( p ) {
|
|
290
|
+
const path = [];
|
|
291
|
+
let env = csn;
|
|
292
|
+
for (let i = 0; p && env && i < p.length; i++) {
|
|
293
|
+
const ps = p[i];
|
|
294
|
+
env = env[ps];
|
|
295
|
+
if (env && env.constructor === Object) {
|
|
296
|
+
path.push(ps);
|
|
297
|
+
// jump over many items but not if this is an element
|
|
298
|
+
if (env.items) {
|
|
299
|
+
env = env.items;
|
|
300
|
+
if (p[i + 1] === 'items')
|
|
301
|
+
i++;
|
|
302
|
+
}
|
|
303
|
+
if (env.type && !isBuiltinType(env.type) && !env.elements)
|
|
304
|
+
env = csn.definitions[env.type];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return path;
|
|
308
|
+
}
|
|
309
|
+
|
|
230
310
|
}
|
|
231
311
|
}
|
|
232
312
|
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
10
10
|
const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
|
|
11
|
-
const { getNamespace, copyAnnotations,
|
|
12
|
-
|
|
13
|
-
forEachDefinition, forEachMember, forEachGeneric } = require('../../model/csnUtils');
|
|
11
|
+
const { getNamespace, copyAnnotations, isBuiltinType, findAnnotationExpression,
|
|
12
|
+
forEachDefinition, forEachMember, forEachGeneric, isEdmPropertyRendered } = require('../../model/csnUtils');
|
|
14
13
|
const { CompilerAssertion } = require('../../base/error');
|
|
14
|
+
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* A given CDS model is a set of n definitions D = {v_1, ..., v_n } spanning a type dependency
|
|
@@ -144,7 +144,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
144
144
|
* @param {String} serviceName
|
|
145
145
|
* @param {String} newTypeName
|
|
146
146
|
*/
|
|
147
|
-
function exposeTypeOf(node, isKey, memberName, defName, serviceName, newTypeName, path, parentName, isTermDef=false) {
|
|
147
|
+
function exposeTypeOf(node, isKey, memberName, defName, serviceName, newTypeName, path, parentName, isTermDef=false, ignoreInAPI=false) {
|
|
148
148
|
const { isExposable, typeDef, typeName, elements, isAnonymous } = isTypeExposable();
|
|
149
149
|
if (isExposable) {
|
|
150
150
|
// this is the name used to register the new type in csn.definitions
|
|
@@ -183,10 +183,12 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
183
183
|
if(elements) {
|
|
184
184
|
newType = createNewStructType(elements);
|
|
185
185
|
// if using node enforces open/closed, set it on type
|
|
186
|
-
if(node['@open'] !== undefined)
|
|
186
|
+
if (node['@open'] !== undefined)
|
|
187
187
|
newType['@open'] = node['@open']
|
|
188
188
|
if (node.$location)
|
|
189
189
|
setProp(newType, '$location', node.$location);
|
|
190
|
+
ignoreInAPI ||= !isEdmPropertyRendered(node, options);
|
|
191
|
+
setProp(newType, '$ignoreInAPI', ignoreInAPI);
|
|
190
192
|
|
|
191
193
|
csn.definitions[fullQualifiedNewTypeName] = newType;
|
|
192
194
|
exposedTypes[fullQualifiedNewTypeName] = 1;
|
|
@@ -198,7 +200,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
198
200
|
defName = typeDef.kind === 'type' ? typeName : defName;
|
|
199
201
|
{
|
|
200
202
|
const { isExposable, typeDef, typeName } = exposeTypeOf(newElem, isKey, elemName, defName, serviceName,
|
|
201
|
-
getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName, isTermDef);
|
|
203
|
+
getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName, isTermDef, ignoreInAPI);
|
|
202
204
|
// if the type for the newElem was not exposed it may be a scalar type def from an external service that hasn't
|
|
203
205
|
// been caught by expandToFinalBaseType() (forODataNew must not modify external imported services)
|
|
204
206
|
if(!isExposable && isBuiltinType(typeName) && !isBuiltinType((newElem.items?.type || newElem.type))) {
|
|
@@ -223,7 +225,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
223
225
|
// expression annos and their sub annotations are not propagated to type
|
|
224
226
|
let [ xprANames, nxprANames ] = Object.keys(typeDef).reduce((acc, pn) => {
|
|
225
227
|
if (pn[0] === '@')
|
|
226
|
-
acc[
|
|
228
|
+
acc[findAnnotationExpression(typeDef, pn) ? 0 : 1].push(pn);
|
|
227
229
|
return acc;
|
|
228
230
|
}, [ [], [] ]);
|
|
229
231
|
nxprANames = nxprANames.filter(an => !xprANames.some(ean => an.startsWith(`${ean}.`)));
|