@sap/cds-compiler 5.5.2 → 5.6.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 +12 -1
- package/bin/cdsse.js +3 -0
- package/lib/base/message-registry.js +2 -0
- package/lib/compiler/builtins.js +2 -0
- package/lib/edm/annotations/vocabularyDefinitions.js +6 -0
- package/lib/gen/BaseParser.js +115 -76
- package/lib/gen/CdlParser.js +839 -841
- package/lib/gen/Dictionary.json +185 -6
- package/lib/parsers/AstBuildingParser.js +63 -28
- package/lib/parsers/CdlGrammar.g4 +36 -22
- package/lib/transform/db/expansion.js +3 -0
- package/lib/transform/forOdata.js +1 -1
- package/lib/transform/odata/createForeignKeys.js +92 -9
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { isBuiltinType } = require('../../base/builtins');
|
|
4
4
|
const { setProp } = require('../../base/model');
|
|
5
|
-
const { applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
|
|
5
|
+
const { transformAnnotationExpression, applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
|
|
6
6
|
const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
|
|
7
7
|
|
|
8
8
|
function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iterateOptions = {}) {
|
|
@@ -60,6 +60,8 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
60
60
|
if (options.transformation === 'effective')
|
|
61
61
|
delete element.default;
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
adaptAnnotationsRefs(generatedForeignKeys, csnUtils);
|
|
63
65
|
setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk[0]));
|
|
64
66
|
orderedElements.push(...generatedForeignKeys);
|
|
65
67
|
|
|
@@ -69,9 +71,9 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
69
71
|
elementsAccumulator[name] = element;
|
|
70
72
|
return elementsAccumulator;
|
|
71
73
|
}, Object.create(null));
|
|
72
|
-
}
|
|
74
|
+
}
|
|
73
75
|
|
|
74
|
-
function createForeignKeysForElement(csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
|
|
76
|
+
function createForeignKeysForElement(csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0, originalkey = {} ) {
|
|
75
77
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
76
78
|
const isInspectRefResult = !Array.isArray(path);
|
|
77
79
|
|
|
@@ -109,7 +111,16 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
|
|
|
109
111
|
const continuePath = getContinuePath([ 'keys', keyIndex ]);
|
|
110
112
|
const alias = key.as || implicitAs(key.ref);
|
|
111
113
|
const result = csnUtils.inspectRef(continuePath);
|
|
112
|
-
|
|
114
|
+
let gfks = createForeignKeysForElement(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1,
|
|
115
|
+
lvl === 0 ? { ref: key.ref, as: key.as, $path: key.$path, $originalKeyRef: key.$originalKeyRef } : originalkey);
|
|
116
|
+
if (lvl === 0) {
|
|
117
|
+
gfks.forEach(gfk => copyAnnotations(key, gfk[1]));
|
|
118
|
+
// once applied -> remove the annotations from the keys array, to keep the OData CSN size as small as possible
|
|
119
|
+
Object.keys(key).forEach( prop => {
|
|
120
|
+
if (prop[0] === '@') delete key[prop]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
fks = fks.concat(gfks);
|
|
113
124
|
});
|
|
114
125
|
}
|
|
115
126
|
// the element is a structure
|
|
@@ -118,7 +129,7 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
|
|
|
118
129
|
// Skip already produced foreign keys
|
|
119
130
|
if (!elem['@odata.foreignKey4']) {
|
|
120
131
|
const continuePath = getContinuePath([ 'elements', elemName ]);
|
|
121
|
-
fks = fks.concat(createForeignKeysForElement(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
|
|
132
|
+
fks = fks.concat(createForeignKeysForElement(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1, originalkey));
|
|
122
133
|
}
|
|
123
134
|
});
|
|
124
135
|
}
|
|
@@ -130,7 +141,7 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
|
|
|
130
141
|
if (element[prop] !== undefined)
|
|
131
142
|
newFk[prop] = element[prop];
|
|
132
143
|
});
|
|
133
|
-
return [ [ prefix, newFk ] ];
|
|
144
|
+
return [ [ prefix, newFk, originalkey ] ];
|
|
134
145
|
}
|
|
135
146
|
|
|
136
147
|
fks.forEach((fk) => {
|
|
@@ -140,9 +151,13 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
|
|
|
140
151
|
if (lvl === 0) {
|
|
141
152
|
if (options.transformation !== 'effective')
|
|
142
153
|
fk[1]['@odata.foreignKey4'] = prefix;
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
154
|
+
|
|
155
|
+
const allowedOverwriteAnnotationNames = ['@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`)];
|
|
156
|
+
const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !allowedOverwriteAnnotationNames.includes(pn) && !findAnnotationExpression(element, pn));
|
|
157
|
+
copyAnnotations(element, fk[1], false, {}, validAnnoNames);
|
|
158
|
+
const overwriteAnnoNames = Object.keys(element).filter(pn => allowedOverwriteAnnotationNames.includes(pn) && !findAnnotationExpression(element, pn));
|
|
159
|
+
copyAnnotations(element, fk[1], true, {}, overwriteAnnoNames);
|
|
160
|
+
|
|
146
161
|
// propagate not null to final foreign key
|
|
147
162
|
for (const prop of [ 'notNull', 'key' ]) {
|
|
148
163
|
if (element[prop] !== undefined)
|
|
@@ -175,6 +190,74 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
|
|
|
175
190
|
return [ ...path, ...additions ];
|
|
176
191
|
}
|
|
177
192
|
}
|
|
193
|
+
|
|
194
|
+
function adaptAnnotationsRefs(generatedForeignKeys, csnUtils) {
|
|
195
|
+
let reportedErrorsForAnnoPath = {};
|
|
196
|
+
generatedForeignKeys.forEach(gfk => {
|
|
197
|
+
Object.entries(gfk[1]).forEach(([key, value]) => {
|
|
198
|
+
if (key[0] !== '@') return;
|
|
199
|
+
|
|
200
|
+
transformAnnotationExpression(gfk[1], key, {
|
|
201
|
+
ref: (_parent, _prop, ref, path, _p, _ppn, ctx) => {
|
|
202
|
+
if (ref[0] !== '$self') {
|
|
203
|
+
|
|
204
|
+
const { art } = csnUtils.inspectRef(getOriginatingKeyPath(gfk, path));
|
|
205
|
+
if (csnUtils.isManagedAssociation(art)) {
|
|
206
|
+
if (!reportedErrorsForAnnoPath[path]) {
|
|
207
|
+
error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
|
|
208
|
+
reportedErrorsForAnnoPath[path] = true;
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
const gfkForRef = findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref);
|
|
212
|
+
if (gfkForRef.length === 1) {
|
|
213
|
+
ref[0] = gfkForRef[0][0];
|
|
214
|
+
|
|
215
|
+
if (ctx?.annoExpr?.['=']) {
|
|
216
|
+
ctx.annoExpr['='] = true;
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
// check if the annotation reference points to a structure that has been expanded,
|
|
220
|
+
// if so -> report an error
|
|
221
|
+
const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk[2].$originalKeyRef), ref);
|
|
222
|
+
// references to expanded structures in flat mode will be found in the $originalKeyRef
|
|
223
|
+
// and in strucred mode more than one match will be found in the generated foreign keys
|
|
224
|
+
if ((foundInOriginalRef.length || gfkForRef.length > 1) && !reportedErrorsForAnnoPath[path]) {
|
|
225
|
+
error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
|
|
226
|
+
reportedErrorsForAnnoPath[path] = true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}, value?.$path?.slice(0, value.$path.length - 1));
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// During tuple expansion, the key ref object looses the $path, therefore
|
|
237
|
+
// it needs to be extracted from the anno path
|
|
238
|
+
function getOriginatingKeyPath(gfk, path) {
|
|
239
|
+
return gfk[2].$path || path.slice(0, path.findIndex(ps => ps[0] === '@'));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Loops through the generated foreign keys for this entity
|
|
243
|
+
// and filters the ones, which were created for this specific
|
|
244
|
+
// key ref. In case there are more than one foreign keys found,
|
|
245
|
+
// that means the key ref points to a structured element/managed assoc
|
|
246
|
+
function findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref) {
|
|
247
|
+
return generatedForeignKeys.filter(gfk => {
|
|
248
|
+
return (ref.join() === (gfk[2].as || implicitAs(gfk[2].ref)));
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Tuple expansion is performed before the generation of the foreign keys and the original(unexpanded) key ref
|
|
253
|
+
// is stored in the property $originalKeyRef. Here we try to evaluate whether the reference in the annotation
|
|
254
|
+
// points to a structure that has been expanded.
|
|
255
|
+
function findOriginalRef(generatedForeignKeys, ref) {
|
|
256
|
+
return generatedForeignKeys.filter(gfk => {
|
|
257
|
+
return (ref.join() === (gfk[2].$originalKeyRef.as || implicitAs(gfk[2].$originalKeyRef.ref)));
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
178
261
|
}
|
|
179
262
|
|
|
180
263
|
module.exports = createForeignKeyElements;
|