@sap/cds-compiler 4.6.0 → 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 +44 -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/populate.js +5 -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 +2 -1
- 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
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { isEdmPropertyRendered, isBuiltinType } = require('../../model/csnUtils');
|
|
3
|
+
const { isEdmPropertyRendered, isBuiltinType, transformExpression } = require('../../model/csnUtils');
|
|
4
4
|
const edmUtils = require('../edmUtils.js');
|
|
5
5
|
const oDataDictionary = require('../../gen/Dictionary.json');
|
|
6
6
|
const preprocessAnnotations = require('./preprocessAnnotations.js');
|
|
7
7
|
const { forEachDefinition } = require('../../model/csnUtils');
|
|
8
|
+
// const { csnRefs } = require('../../model/csnRefs');
|
|
8
9
|
const { isBetaEnabled, setProp } = require('../../base/model.js');
|
|
9
10
|
const { xpr2edmJson, getEdmJsonHandler } = require('./edmJson.js');
|
|
10
11
|
const { vocabularyDefinitions } = require('./vocabularyDefinitions.js');
|
|
@@ -18,14 +19,16 @@ const { EdmPathTypeMap } = require('../EdmPrimitiveTypeDefinitions.js');
|
|
|
18
19
|
* dictReplacement: for test purposes, replaces the standard oDataDictionary
|
|
19
20
|
*/
|
|
20
21
|
function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
21
|
-
Edm
|
|
22
|
+
Edm, options, messageFunctions, mergedVocDefs = vocabularyDefinitions ) {
|
|
22
23
|
const gAnnosArray = []; // global variable where we store all the generated annotations
|
|
23
24
|
const usedExperimentalTerms = {}; // take note of all experimental annos that have been used
|
|
24
25
|
const usedDeprecatedTerms = {}; // take note of all deprecated annos that have been used
|
|
25
26
|
|
|
26
27
|
const { v } = options;
|
|
27
|
-
const { message } = messageFunctions;
|
|
28
|
+
const { message, error } = messageFunctions;
|
|
28
29
|
const { handleEdmJson } = getEdmJsonHandler(Edm, options, messageFunctions, handleTerm);
|
|
30
|
+
// const { inspectRef } = csnRefs(reqDefs);
|
|
31
|
+
|
|
29
32
|
const [ userDefinedTermDict, allKnownVocabularies ] = createUserDefinedTermDictionary();
|
|
30
33
|
|
|
31
34
|
|
|
@@ -34,7 +37,7 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
34
37
|
const whatsMyTermNamespace = anno => allKnownVocabularies.reduce((rc, ns) => (!rc && anno && anno.startsWith(`@${ns}.`) ? ns : rc), undefined);
|
|
35
38
|
|
|
36
39
|
// annotation preprocessing
|
|
37
|
-
preprocessAnnotations.preprocessAnnotations(reqDefs, serviceName, options);
|
|
40
|
+
preprocessAnnotations.preprocessAnnotations(reqDefs, serviceName, options, messageFunctions);
|
|
38
41
|
|
|
39
42
|
// we take note of which vocabularies are actually used in a service in order to avoid
|
|
40
43
|
// producing useless references; reset everything to "unused"
|
|
@@ -54,57 +57,27 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
54
57
|
{ name: serviceName, '#': 'service' } );
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
// qualified with #$parameters or if its applicable to an EntitySet or Singleton
|
|
59
|
-
forEachDefinition(reqDefs, (object) => {
|
|
60
|
-
if (object.$isParamEntity && object._origin) {
|
|
61
|
-
for (const attr in object._origin) {
|
|
62
|
-
if (attr[0] === '@') {
|
|
63
|
-
const [ prefix, innerAnnotation ] = attr.split('.@');
|
|
64
|
-
const ns = whatsMyTermNamespace(prefix);
|
|
65
|
-
if (ns) {
|
|
66
|
-
const steps = prefix.replace(`@${ns}.`, '').split('.');
|
|
67
|
-
const paramAnnoParts = steps[0].split('#$parameters');
|
|
68
|
-
const dictTerm = getDictTerm(`${ns}.${paramAnnoParts[0]}`, options);
|
|
69
|
-
if (paramAnnoParts.length > 1 || [ 'Singleton', 'EntitySet' ].some(y => dictTerm?.AppliesTo?.includes(y))) {
|
|
70
|
-
steps[0] = `@${ns}.${paramAnnoParts.join('')}`;
|
|
71
|
-
let newAnno = steps.join('.');
|
|
72
|
-
if (innerAnnotation)
|
|
73
|
-
newAnno += `.@${innerAnnotation}`;
|
|
74
|
-
edmUtils.assignAnnotation(object, newAnno, object._origin[attr]);
|
|
75
|
-
// delete original annotation only if it was qualified with $parameters
|
|
76
|
-
if (paramAnnoParts.length > 1)
|
|
77
|
-
delete object._origin[attr];
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
});
|
|
60
|
+
assignParameterAnnotations();
|
|
84
61
|
|
|
85
62
|
// Crawl over the csn and trigger the annotation translation for all kinds
|
|
86
63
|
// of annotated things.
|
|
87
64
|
// Note: only works for single service
|
|
88
65
|
// Note: we assume that all objects ly flat in the service, i.e. objName always
|
|
89
66
|
// looks like <service name, can contain dots>.<id>
|
|
90
|
-
forEachDefinition(reqDefs, (
|
|
91
|
-
if (
|
|
92
|
-
const location = [ 'definitions',
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// handle the annotations of the object's actions
|
|
102
|
-
handleBoundActions(objName, object, location);
|
|
103
|
-
}
|
|
67
|
+
forEachDefinition(reqDefs, (def, defName) => {
|
|
68
|
+
if (defName === serviceName || defName.startsWith(`${serviceName}.`)) {
|
|
69
|
+
const location = [ 'definitions', defName ];
|
|
70
|
+
// the <objName> is not the carrier name for <objName>Type
|
|
71
|
+
// and sometimes the object.name doesn't have a service prefix
|
|
72
|
+
if (def.name && def.name.startsWith(`${serviceName}.`))
|
|
73
|
+
defName = def.name;
|
|
74
|
+
if (def.kind === 'action' || def.kind === 'function')
|
|
75
|
+
handleAction(defName, def, null, location);
|
|
76
|
+
else
|
|
77
|
+
handleDefinition(defName, def, location);
|
|
104
78
|
}
|
|
105
79
|
});
|
|
106
80
|
|
|
107
|
-
|
|
108
81
|
// filter out empty <Annotations...> elements
|
|
109
82
|
// add references for the used vocabularies
|
|
110
83
|
return {
|
|
@@ -123,6 +96,77 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
123
96
|
return v && v[0];
|
|
124
97
|
}
|
|
125
98
|
|
|
99
|
+
function assignParameterAnnotations() {
|
|
100
|
+
// Copy annotations from origin to parameter entity if it's
|
|
101
|
+
// qualified with #$parameters or if its applicable to an EntitySet or Singleton
|
|
102
|
+
const scopeCheck = {
|
|
103
|
+
ref: (elemref, prop, xpr, path) => {
|
|
104
|
+
if (scopeCheck.scope === 'param' && (!elemref.param || (xpr[0].id || xpr[0]) === '$self'))
|
|
105
|
+
error('odata-anno-xpr-ref', path, { elemref, anno: scopeCheck.anno, '#': 'notaparam' });
|
|
106
|
+
if (scopeCheck.scope === 'type' && elemref.param)
|
|
107
|
+
error('odata-anno-xpr-ref', path, { elemref, anno: scopeCheck.anno, '#': 'notaneelement' });
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
const checkDict = (dict, scope) => {
|
|
111
|
+
if (dict) {
|
|
112
|
+
scopeCheck.scope = scope;
|
|
113
|
+
Object.values(dict).forEach((carrier) => {
|
|
114
|
+
const knownAnnos = filterKnownAnnotations(carrier);
|
|
115
|
+
knownAnnos.forEach((pn) => {
|
|
116
|
+
scopeCheck.anno = pn;
|
|
117
|
+
transformExpression(carrier, pn, scopeCheck, carrier.$path);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const checkDef = (def, scope) => {
|
|
123
|
+
scopeCheck.scope = scope;
|
|
124
|
+
const knownAnnos = filterKnownAnnotations(def);
|
|
125
|
+
knownAnnos.forEach((pn) => {
|
|
126
|
+
scopeCheck.anno = pn;
|
|
127
|
+
transformExpression(def, pn, scopeCheck, def.$path);
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
forEachDefinition(reqDefs, (object) => {
|
|
131
|
+
if (object.$isParamEntity && object._origin) {
|
|
132
|
+
// check for correct paths
|
|
133
|
+
if (object._origin.$paramAnnoProxies) {
|
|
134
|
+
object.$eltAnnoProxies = object._origin.$paramAnnoProxies;
|
|
135
|
+
object._origin.$paramAnnoProxies = null;
|
|
136
|
+
checkDict(object.$eltAnnoProxies, 'param');
|
|
137
|
+
}
|
|
138
|
+
checkDict(object._origin.$eltAnnoProxies, 'type');
|
|
139
|
+
checkDict(object.elements, 'param');
|
|
140
|
+
checkDict(object._origin.elements, 'type');
|
|
141
|
+
|
|
142
|
+
scopeCheck.scope = 'param';
|
|
143
|
+
Object.keys(object._origin).forEach((attr) => {
|
|
144
|
+
if (attr[0] === '@') {
|
|
145
|
+
scopeCheck.anno = attr;
|
|
146
|
+
const [ prefix, innerAnnotation ] = attr.split('.@');
|
|
147
|
+
const ns = whatsMyTermNamespace(prefix);
|
|
148
|
+
if (ns) {
|
|
149
|
+
const steps = prefix.replace(`@${ns}.`, '').split('.');
|
|
150
|
+
const paramAnnoParts = steps[0].split('#$parameters');
|
|
151
|
+
const dictTerm = getDictTerm(`${ns}.${paramAnnoParts[0]}`, options);
|
|
152
|
+
if (paramAnnoParts.length > 1 || [ 'Singleton', 'EntitySet' ].some(y => dictTerm?.AppliesTo?.includes(y))) {
|
|
153
|
+
steps[0] = `@${ns}.${paramAnnoParts.join('')}`;
|
|
154
|
+
let newAnno = steps.join('.');
|
|
155
|
+
if (innerAnnotation)
|
|
156
|
+
newAnno += `.@${innerAnnotation}`;
|
|
157
|
+
edmUtils.assignAnnotation(object, newAnno, object._origin[attr]);
|
|
158
|
+
transformExpression(object._origin, attr, scopeCheck, object._origin.$path);
|
|
159
|
+
if (paramAnnoParts.length > 1)
|
|
160
|
+
delete object._origin[attr];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
checkDef(object._origin, 'type');
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
126
170
|
/*
|
|
127
171
|
Mapping annotated thing in cds/csn => annotated thing in edmx:
|
|
128
172
|
|
|
@@ -160,20 +204,27 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
160
204
|
like above, but append "/<parameter" to the Target
|
|
161
205
|
*/
|
|
162
206
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
207
|
+
function handleDefinition( defName, def, location ) {
|
|
208
|
+
// definition bound annotations
|
|
209
|
+
handleAnnotations(defName, def, location);
|
|
210
|
+
// definition bound element annotations
|
|
211
|
+
if (def.$eltAnnoProxies) {
|
|
212
|
+
Object.entries(def.$eltAnnoProxies).forEach(([ elemPath, element ]) => {
|
|
213
|
+
const edmTargetName = `${defName}/${elemPath}`;
|
|
214
|
+
handleAnnotations(edmTargetName, element, element.$path);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
// element bound annotations
|
|
218
|
+
if (def.elements) {
|
|
219
|
+
Object.entries(def.elements).forEach(([ elemName, element ]) => {
|
|
220
|
+
const edmTargetName = `${defName}/${elemName}`;
|
|
221
|
+
const eLocation = [ ...location, 'elements', elemName ];
|
|
222
|
+
handleAnnotations(edmTargetName, element, eLocation);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
// bound actions
|
|
226
|
+
if (def.actions)
|
|
227
|
+
handleBoundActions(defName, def, location);
|
|
177
228
|
}
|
|
178
229
|
|
|
179
230
|
// Annotations for actions and functions (and their parameters)
|
|
@@ -188,8 +239,6 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
188
239
|
// in: cObjectname : qualified name of the object that holds the actions
|
|
189
240
|
// cObject : the object itself
|
|
190
241
|
function handleBoundActions( cObjectname, cObject, location ) {
|
|
191
|
-
if (!cObject.actions)
|
|
192
|
-
return;
|
|
193
242
|
// get service name: remove last part of the object name
|
|
194
243
|
// only works if all objects ly flat in the service
|
|
195
244
|
const nameParts = cObjectname.split('.');
|
|
@@ -293,12 +342,12 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
293
342
|
// Filter unknown toplevel annotations
|
|
294
343
|
// Final filtering of all annotations is done in handleTerm
|
|
295
344
|
|
|
296
|
-
let knownAnnos = filterKnownAnnotations();
|
|
345
|
+
let knownAnnos = filterKnownAnnotations(carrier);
|
|
297
346
|
if (knownAnnos.length === 0)
|
|
298
347
|
return;
|
|
299
348
|
|
|
300
349
|
if (rewriteInnerAnnotations()) {
|
|
301
|
-
knownAnnos = filterKnownAnnotations();
|
|
350
|
+
knownAnnos = filterKnownAnnotations(carrier);
|
|
302
351
|
if (knownAnnos.length === 0)
|
|
303
352
|
return;
|
|
304
353
|
}
|
|
@@ -350,47 +399,6 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
350
399
|
gAnnosArray.push(annotations);
|
|
351
400
|
}
|
|
352
401
|
|
|
353
|
-
function filterKnownAnnotations() {
|
|
354
|
-
const annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
|
|
355
|
-
const nullWhitelist = [ '@Core.OperationAvailable' ];
|
|
356
|
-
const knownAnnosP = annoNames.filter((n) => {
|
|
357
|
-
const tns = whatsMyTermNamespace(n);
|
|
358
|
-
return tns &&
|
|
359
|
-
(mergedVocDefs[tns] && !mergedVocDefs[tns].$ignore ||
|
|
360
|
-
!mergedVocDefs[tns]);
|
|
361
|
-
}).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
362
|
-
if (isBetaEnabled(options, 'odataTerms')) {
|
|
363
|
-
// Extend knownAnnos with the in-service term definitions
|
|
364
|
-
annoNames.forEach((an) => {
|
|
365
|
-
const paths = an.slice(1).split('.');
|
|
366
|
-
const hasNSPrefix = paths[0] === serviceName;
|
|
367
|
-
if (!hasNSPrefix)
|
|
368
|
-
paths.splice(0, 0, serviceName);
|
|
369
|
-
|
|
370
|
-
const fqName = `@${paths.join('.')}`;
|
|
371
|
-
const i = paths[1].indexOf('#');
|
|
372
|
-
const termNameWithoutQualifiers = i > 0 ? paths[1].substring(0, i) : paths[1];
|
|
373
|
-
const def = reqDefs.definitions[`${paths[0]}.${termNameWithoutQualifiers}`];
|
|
374
|
-
// if there is a term definition inside the service and the
|
|
375
|
-
// annotation value is != null, then add the annotation to the list
|
|
376
|
-
// of known annotations
|
|
377
|
-
if (def?.kind === 'annotation' && carrier[an] !== null) {
|
|
378
|
-
// Subsequent annotation handler code expects that first path segment
|
|
379
|
-
// is the Vocabulary namespace. The ad-hoc namespace is the service
|
|
380
|
-
// name itself.
|
|
381
|
-
// For service S an annotation assignment could be addressed
|
|
382
|
-
// relative or absolute to the service @S.foo or @foo
|
|
383
|
-
if (!hasNSPrefix) {
|
|
384
|
-
carrier[fqName] = carrier[an];
|
|
385
|
-
delete carrier[an];
|
|
386
|
-
}
|
|
387
|
-
knownAnnosP.push(fqName);
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
return knownAnnosP;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
402
|
// construct a function that is used to add an <Annotation ...> to the
|
|
395
403
|
// respective collector array
|
|
396
404
|
// this function is specific to the actual carrier, following the mapping rules given above
|
|
@@ -486,7 +494,7 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
486
494
|
// this might be more precise if handleAnnotation would know more about the carrier
|
|
487
495
|
testToStandardEdmTargetP = (x => (x
|
|
488
496
|
? [ 'Parameter', 'Property' ].some(y => x.includes(y) ||
|
|
489
|
-
carrier
|
|
497
|
+
carrier.$isCollection && x.includes('Collection'))
|
|
490
498
|
: true));
|
|
491
499
|
}
|
|
492
500
|
}
|
|
@@ -1448,6 +1456,48 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
1448
1456
|
}
|
|
1449
1457
|
}
|
|
1450
1458
|
|
|
1459
|
+
function filterKnownAnnotations( carrier ) {
|
|
1460
|
+
const annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
|
|
1461
|
+
const nullWhitelist = [ '@Core.OperationAvailable' ];
|
|
1462
|
+
const knownAnnosP = annoNames.filter((n) => {
|
|
1463
|
+
const tns = whatsMyTermNamespace(n);
|
|
1464
|
+
return tns &&
|
|
1465
|
+
(mergedVocDefs[tns] && !mergedVocDefs[tns].$ignore ||
|
|
1466
|
+
!mergedVocDefs[tns]);
|
|
1467
|
+
}).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
1468
|
+
if (isBetaEnabled(options, 'odataTerms')) {
|
|
1469
|
+
// Extend knownAnnos with the in-service term definitions
|
|
1470
|
+
annoNames.forEach((an) => {
|
|
1471
|
+
const paths = an.slice(1).split('.');
|
|
1472
|
+
const hasNSPrefix = paths[0] === serviceName;
|
|
1473
|
+
if (!hasNSPrefix)
|
|
1474
|
+
paths.splice(0, 0, serviceName);
|
|
1475
|
+
|
|
1476
|
+
const fqName = `@${paths.join('.')}`;
|
|
1477
|
+
const i = paths[1].indexOf('#');
|
|
1478
|
+
const termNameWithoutQualifiers = i > 0 ? paths[1].substring(0, i) : paths[1];
|
|
1479
|
+
const def = reqDefs.definitions[`${paths[0]}.${termNameWithoutQualifiers}`];
|
|
1480
|
+
// if there is a term definition inside the service and the
|
|
1481
|
+
// annotation value is != null, then add the annotation to the list
|
|
1482
|
+
// of known annotations
|
|
1483
|
+
if (def?.kind === 'annotation' && carrier[an] !== null) {
|
|
1484
|
+
// Subsequent annotation handler code expects that first path segment
|
|
1485
|
+
// is the Vocabulary namespace. The ad-hoc namespace is the service
|
|
1486
|
+
// name itself.
|
|
1487
|
+
// For service S an annotation assignment could be addressed
|
|
1488
|
+
// relative or absolute to the service @S.foo or @foo
|
|
1489
|
+
if (!hasNSPrefix) {
|
|
1490
|
+
carrier[fqName] = carrier[an];
|
|
1491
|
+
delete carrier[an];
|
|
1492
|
+
}
|
|
1493
|
+
knownAnnosP.push(fqName);
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
return knownAnnosP;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
|
|
1451
1501
|
//-------------------------------------------------------------------------------------------------
|
|
1452
1502
|
// Dictionary access
|
|
1453
1503
|
//-------------------------------------------------------------------------------------------------
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { makeMessageFunction } = require('../../base/messages.js');
|
|
4
3
|
const { forEachDefinition, forEachGeneric } = require('../../model/csnUtils.js');
|
|
5
4
|
|
|
6
5
|
|
|
@@ -14,8 +13,8 @@ const { forEachDefinition, forEachGeneric } = require('../../model/csnUtils.js')
|
|
|
14
13
|
* try to proceed with the processing as good as possible.
|
|
15
14
|
*
|
|
16
15
|
*/
|
|
17
|
-
function preprocessAnnotations( csn, serviceName, options ) {
|
|
18
|
-
const { message } =
|
|
16
|
+
function preprocessAnnotations( csn, serviceName, options, messageFunctions ) {
|
|
17
|
+
const { message } = messageFunctions;
|
|
19
18
|
const fkSeparator = '_';
|
|
20
19
|
|
|
21
20
|
resolveShortcuts();
|
|
@@ -237,20 +236,18 @@ function preprocessAnnotations( csn, serviceName, options ) {
|
|
|
237
236
|
// or shortcut expansion array of paths
|
|
238
237
|
// OR
|
|
239
238
|
// the (single) non-key string field, if there is one
|
|
240
|
-
let
|
|
239
|
+
let stringFields = [];
|
|
241
240
|
const Identification = vlEntity['@UI.Identification'];
|
|
242
241
|
if (Identification && Identification[0] && Identification[0]['=']) {
|
|
243
|
-
|
|
242
|
+
stringFields.push(Identification[0]['=']);
|
|
244
243
|
}
|
|
245
244
|
else if (Identification && Identification[0] && Identification[0].Value && Identification[0].Value['=']) {
|
|
246
|
-
|
|
245
|
+
stringFields.push(Identification[0].Value['=']);
|
|
247
246
|
}
|
|
248
247
|
else {
|
|
249
|
-
|
|
248
|
+
stringFields = Object.keys(vlEntity.elements).filter(
|
|
250
249
|
x => !vlEntity.elements[x].key && vlEntity.elements[x].type === 'cds.String'
|
|
251
250
|
);
|
|
252
|
-
if (stringFields.length === 1)
|
|
253
|
-
textField = stringFields[0];
|
|
254
251
|
}
|
|
255
252
|
|
|
256
253
|
// explicitly provided parameters win
|
|
@@ -261,12 +258,12 @@ function preprocessAnnotations( csn, serviceName, options ) {
|
|
|
261
258
|
LocalDataProperty: { '=': localDataProp },
|
|
262
259
|
ValueListProperty: valueListProp,
|
|
263
260
|
} ];
|
|
264
|
-
|
|
265
|
-
parameters
|
|
261
|
+
stringFields.forEach((n) => {
|
|
262
|
+
parameters.push({
|
|
266
263
|
$Type: 'Common.ValueListParameterDisplayOnly',
|
|
267
|
-
ValueListProperty:
|
|
268
|
-
};
|
|
269
|
-
}
|
|
264
|
+
ValueListProperty: n,
|
|
265
|
+
});
|
|
266
|
+
});
|
|
270
267
|
}
|
|
271
268
|
|
|
272
269
|
const newObj = Object.create( Object.getPrototypeOf(carrier) );
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -11,7 +11,7 @@ const { initializeModel } = require('./edmPreprocessor.js');
|
|
|
11
11
|
const translate = require('./annotations/genericTranslation.js');
|
|
12
12
|
const { setProp, isBetaEnabled } = require('../base/model');
|
|
13
13
|
const {
|
|
14
|
-
|
|
14
|
+
isEdmPropertyRendered, isBuiltinType, getUtils,
|
|
15
15
|
} = require('../model/csnUtils');
|
|
16
16
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
17
17
|
const {
|
|
@@ -20,7 +20,8 @@ const {
|
|
|
20
20
|
EdmPrimitiveTypeMap,
|
|
21
21
|
} = require('./EdmPrimitiveTypeDefinitions.js');
|
|
22
22
|
const { getEdm } = require('./edm.js');
|
|
23
|
-
|
|
23
|
+
const { cloneFullCsn } = require('../model/cloneCsn');
|
|
24
|
+
const { forEach, forEachValue } = require('../utils/objectUtils.js');
|
|
24
25
|
/*
|
|
25
26
|
OData V2 spec 06/01/2017 PDF version is available here:
|
|
26
27
|
https://msdn.microsoft.com/en-us/library/dd541474.aspx
|
|
@@ -46,7 +47,7 @@ function csn2edm( _csn, serviceName, _options, messageFunctions ) {
|
|
|
46
47
|
*/
|
|
47
48
|
function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
48
49
|
// get us a fresh model copy that we can work with
|
|
49
|
-
const csn =
|
|
50
|
+
const csn = cloneFullCsn(_csn, _options);
|
|
50
51
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
51
52
|
messageFunctions.setModel(csn);
|
|
52
53
|
|
|
@@ -85,6 +86,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
85
86
|
return rc;
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
const reqDefsUtils = getUtils(reqDefs);
|
|
90
|
+
|
|
88
91
|
if (serviceNames === undefined)
|
|
89
92
|
serviceNames = options.serviceNames;
|
|
90
93
|
if (serviceNames) {
|
|
@@ -198,7 +201,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
198
201
|
|
|
199
202
|
if (options.isV4()) {
|
|
200
203
|
// Add additional schema containers as sub contexts to the service
|
|
201
|
-
|
|
204
|
+
forEach(allSchemas, (fqName, art) => {
|
|
202
205
|
if (serviceCsn.name === whatsMyServiceRootName(fqName) &&
|
|
203
206
|
fqName.startsWith(`${serviceCsn.name}.`)) {
|
|
204
207
|
if (art.kind === 'reference')
|
|
@@ -250,7 +253,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
250
253
|
*/
|
|
251
254
|
service._children.forEach((c) => {
|
|
252
255
|
if (c._ec) {
|
|
253
|
-
|
|
256
|
+
forEach(c._ec._registry, ( setName, arr ) => {
|
|
254
257
|
if (arr.length > 1) {
|
|
255
258
|
error(null, null, {
|
|
256
259
|
name: c._edmAttributes.Namespace,
|
|
@@ -258,7 +261,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
258
261
|
names: arr.map(a => a.getDuplicateMessage()),
|
|
259
262
|
}, 'Namespace $(NAME): Duplicate entries in EntityContainer with Name=$(ID) for $(NAMES)');
|
|
260
263
|
}
|
|
261
|
-
})
|
|
264
|
+
});
|
|
262
265
|
}
|
|
263
266
|
});
|
|
264
267
|
// Create annotations and distribute into Schemas, merge vocabulary cross refs into xServiceRefs
|
|
@@ -266,7 +269,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
266
269
|
|
|
267
270
|
// Finally add cross service references into the EDM and extract the targetSchemaNames
|
|
268
271
|
// for the type cross check
|
|
269
|
-
|
|
272
|
+
forEachValue(xServiceRefs, (ref) => {
|
|
270
273
|
const r = new Edm.Reference(v, ref.ref);
|
|
271
274
|
r.append(new Edm.Include(v, ref.inc));
|
|
272
275
|
edm._defaultRefs.push(r);
|
|
@@ -305,7 +308,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
305
308
|
|
|
306
309
|
// Sort definitions into their schema container
|
|
307
310
|
function populateSchemas( schemas ) {
|
|
308
|
-
|
|
311
|
+
forEach(reqDefs.definitions, ( fqName, art ) => {
|
|
309
312
|
// Identify service members by their definition name only, this allows
|
|
310
313
|
// to let the internal object.name have the sub-schema name.
|
|
311
314
|
// With nested services we must do a longest path match and check whether
|
|
@@ -412,7 +415,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
412
415
|
|
|
413
416
|
// create the complex types
|
|
414
417
|
edmUtils.foreach(schemaCsn.definitions,
|
|
415
|
-
a => edmUtils.isStructuredType(a) && a.name.startsWith(schemaNamePrefix),
|
|
418
|
+
a => edmUtils.isStructuredType(a) && a.name.startsWith(schemaNamePrefix) && !a.$ignoreInAPI,
|
|
416
419
|
[ createComplexType, markRendered ]);
|
|
417
420
|
|
|
418
421
|
if (options.isV4()) {
|
|
@@ -470,7 +473,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
470
473
|
Schema._children.splice(Schema._children.indexOf(Schema._ec), 1);
|
|
471
474
|
|
|
472
475
|
|
|
473
|
-
|
|
476
|
+
forEach(NamesInSchemaXRef, ( name, refs ) => {
|
|
474
477
|
if (refs.length > 1) {
|
|
475
478
|
error(null, [ 'definitions', `${Schema._edmAttributes.Namespace}.${name}` ], { name: Schema._edmAttributes.Namespace },
|
|
476
479
|
'Duplicate name in Schema $(NAME)');
|
|
@@ -485,7 +488,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
485
488
|
const isSingleton = edmUtils.isSingleton(entityCsn) && options.isV4();
|
|
486
489
|
const [ properties, hasStream ] = createProperties(entityCsn);
|
|
487
490
|
|
|
488
|
-
const location = [ 'definitions', entityCsn.name ];
|
|
491
|
+
const location = reqDefs.definitions[entityCsn.name] ? [ 'definitions', entityCsn.name ] : entityCsn.$path;
|
|
489
492
|
const type = `${schema.name}.${EntityTypeName}`;
|
|
490
493
|
if (properties.length === 0)
|
|
491
494
|
warning(null, location, { type }, 'EDM EntityType $(TYPE) has no properties');
|
|
@@ -504,7 +507,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
504
507
|
if (p._edmAttributes.Name === EntityTypeName)
|
|
505
508
|
message('odata-spec-violation-property-name', pLoc, { meta: entityCsn.kind });
|
|
506
509
|
|
|
507
|
-
if (options.isV2() && p
|
|
510
|
+
if (options.isV2() && p.$isCollection && !p._csn.target)
|
|
508
511
|
message('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
509
512
|
|
|
510
513
|
if (!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name)) {
|
|
@@ -554,7 +557,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
554
557
|
|
|
555
558
|
// put actions behind entity types in Schema/EntityContainer
|
|
556
559
|
if (entityCsn.actions) {
|
|
557
|
-
|
|
560
|
+
forEach(entityCsn.actions, ( n, a ) => {
|
|
558
561
|
if (options.isV4())
|
|
559
562
|
createActionV4(a, n, entityCsn);
|
|
560
563
|
else
|
|
@@ -585,7 +588,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
585
588
|
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
|
|
586
589
|
|
|
587
590
|
if (options.isV2()) {
|
|
588
|
-
if (p
|
|
591
|
+
if (p.$isCollection && !p._csn.target)
|
|
589
592
|
message('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
590
593
|
|
|
591
594
|
if (p._csn.target)
|
|
@@ -612,7 +615,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
612
615
|
const streamProps = [];
|
|
613
616
|
|
|
614
617
|
if (elementsCsn.elements) {
|
|
615
|
-
|
|
618
|
+
forEach(elementsCsn.elements, ( elementName, elementCsn ) => {
|
|
616
619
|
if (!elementCsn._edmParentCsn)
|
|
617
620
|
setProp(elementCsn, '_edmParentCsn', edmParentCsn);
|
|
618
621
|
|
|
@@ -683,10 +686,10 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
683
686
|
// derived types are already resolved to base types
|
|
684
687
|
const attributes = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
|
|
685
688
|
if (!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
686
|
-
message('odata-spec-violation-id',
|
|
689
|
+
message('odata-spec-violation-id', typeCsn.$path, { id: attributes.Name });
|
|
687
690
|
|
|
688
691
|
const typeDef = new Edm.TypeDefinition(v, attributes, typeCsn );
|
|
689
|
-
edmTypeCompatibilityCheck(typeDef,
|
|
692
|
+
edmTypeCompatibilityCheck(typeDef, typeCsn.$path);
|
|
690
693
|
Schema.append(typeDef);
|
|
691
694
|
}
|
|
692
695
|
|
|
@@ -700,6 +703,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
700
703
|
? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
|
|
701
704
|
: [ 'definitions', actionCsn.name ];
|
|
702
705
|
|
|
706
|
+
|
|
703
707
|
if (!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
704
708
|
message('odata-spec-violation-id', location, { id: attributes.Name });
|
|
705
709
|
|
|
@@ -779,7 +783,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
779
783
|
|
|
780
784
|
// Parameter Nodes
|
|
781
785
|
if (actionCsn.params) {
|
|
782
|
-
|
|
786
|
+
forEach(actionCsn.params, ( parameterName, parameterCsn ) => {
|
|
783
787
|
const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
|
|
784
788
|
const pLoc = [ ...location, 'params', p._edmAttributes.Name ];
|
|
785
789
|
if (!edmUtils.isODataSimpleIdentifier(parameterName))
|
|
@@ -857,7 +861,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
857
861
|
}
|
|
858
862
|
|
|
859
863
|
// is this still required?
|
|
860
|
-
|
|
864
|
+
forEach(actionCsn, ( key, val ) => {
|
|
861
865
|
if (key.match(/^@sap\./))
|
|
862
866
|
functionImport.setXml( { [`sap:${key.slice(5).replace(/\./g, '-')}`]: val });
|
|
863
867
|
});
|
|
@@ -888,7 +892,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
888
892
|
!edmUtils.isStructuredType(csn.definitions[param._type]))
|
|
889
893
|
message('odata-spec-violation-param', pLoc, { version: '2.0' });
|
|
890
894
|
|
|
891
|
-
if (param
|
|
895
|
+
if (param.$isCollection)
|
|
892
896
|
message('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
893
897
|
|
|
894
898
|
functionImport.append(param);
|
|
@@ -924,7 +928,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
924
928
|
message('odata-spec-violation-type-unknown', returnsLoc, { type });
|
|
925
929
|
}
|
|
926
930
|
}
|
|
927
|
-
if (action.returns
|
|
931
|
+
if (action.returns.$isCollection)
|
|
928
932
|
type = `Collection(${type})`;
|
|
929
933
|
}
|
|
930
934
|
else {
|
|
@@ -1038,7 +1042,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
1038
1042
|
NamesInSchemaXRef[assocName].push(edmAssociation);
|
|
1039
1043
|
|
|
1040
1044
|
// Add ReferentialConstraints if any
|
|
1041
|
-
if (!navigationProperty
|
|
1045
|
+
if (!navigationProperty.$isCollection && Object.keys(constraints.constraints).length > 0) {
|
|
1042
1046
|
// A managed composition is treated as association
|
|
1043
1047
|
if (navigationProperty._csn.type === 'cds.Composition' && navigationProperty._csn.on) {
|
|
1044
1048
|
edmAssociation.append(Edm.ReferentialConstraint.createV2(v,
|
|
@@ -1068,8 +1072,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
1068
1072
|
|
|
1069
1073
|
// generate the Edm.Annotations tree and append it to the corresponding schema
|
|
1070
1074
|
function addAnnotations2XServiceRefs( ) {
|
|
1071
|
-
|
|
1072
|
-
options.getFinalTypeInfo = getFinalTypeInfo;
|
|
1075
|
+
options.getFinalTypeInfo = reqDefsUtils.getFinalTypeInfo;
|
|
1073
1076
|
const { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions, mergedVocabularies);
|
|
1074
1077
|
// distribute edm:Annotations into the schemas
|
|
1075
1078
|
// Distribute each anno into Schema
|