@sap/cds-compiler 3.7.2 → 3.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -4
- package/bin/cdsc.js +3 -0
- package/doc/CHANGELOG_ARCHIVE.md +6 -6
- package/doc/CHANGELOG_BETA.md +15 -0
- package/doc/DeprecatedOptions_v2.md +1 -1
- package/doc/NameResolution.md +1 -1
- package/lib/api/main.js +61 -22
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/dictionaries.js +5 -3
- package/lib/base/keywords.js +2 -0
- package/lib/base/message-registry.js +64 -22
- package/lib/base/messages.js +12 -7
- package/lib/base/model.js +3 -2
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/defaultValues.js +1 -1
- package/lib/checks/hasPersistedElements.js +1 -1
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/onConditions.js +9 -6
- package/lib/checks/sql-snippets.js +2 -2
- package/lib/checks/types.js +1 -2
- package/lib/compiler/assert-consistency.js +25 -6
- package/lib/compiler/base.js +51 -2
- package/lib/compiler/builtins.js +15 -6
- package/lib/compiler/checks.js +4 -4
- package/lib/compiler/define.js +59 -80
- package/lib/compiler/extend.js +717 -498
- package/lib/compiler/finalize-parse-cdl.js +4 -3
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/kick-start.js +2 -2
- package/lib/compiler/populate.js +17 -9
- package/lib/compiler/propagator.js +12 -5
- package/lib/compiler/resolve.js +26 -173
- package/lib/compiler/shared.js +20 -58
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/genericTranslation.js +124 -46
- package/lib/edm/csn2edm.js +22 -1
- package/lib/edm/edmPreprocessor.js +41 -21
- package/lib/gen/Dictionary.json +4 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageLexer.js +1 -1
- package/lib/gen/languageParser.js +4844 -4508
- package/lib/inspect/inspectPropagation.js +20 -36
- package/lib/json/from-csn.js +56 -7
- package/lib/json/to-csn.js +71 -110
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +49 -9
- package/lib/language/language.g4 +106 -83
- package/lib/language/textUtils.js +13 -0
- package/lib/main.d.ts +43 -3
- package/lib/main.js +4 -2
- package/lib/model/csnRefs.js +19 -4
- package/lib/model/csnUtils.js +11 -74
- package/lib/model/revealInternalProperties.js +3 -0
- package/lib/optionProcessor.js +3 -0
- package/lib/render/toCdl.js +203 -104
- package/lib/render/toHdbcds.js +0 -1
- package/lib/render/toRename.js +14 -51
- package/lib/transform/braceExpression.js +6 -0
- package/lib/transform/db/rewriteCalculatedElements.js +55 -14
- package/lib/transform/forOdataNew.js +20 -15
- package/lib/transform/forRelationalDB.js +21 -14
- package/lib/transform/parseExpr.js +2 -0
- package/lib/transform/transformUtilsNew.js +36 -9
- package/lib/transform/translateAssocsToJoins.js +11 -4
- package/lib/transform/universalCsn/coreComputed.js +15 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +2 -1
|
@@ -5,7 +5,7 @@ const oDataDictionary = require('../../gen/Dictionary.json');
|
|
|
5
5
|
const preprocessAnnotations = require('./preprocessAnnotations.js');
|
|
6
6
|
const { forEachDefinition } = require('../../model/csnUtils');
|
|
7
7
|
const { forEach } = require('../../utils/objectUtils');
|
|
8
|
-
const { isBetaEnabled } = require('../../base/model.js');
|
|
8
|
+
const { isBetaEnabled, setProp } = require('../../base/model.js');
|
|
9
9
|
|
|
10
10
|
/*
|
|
11
11
|
OASIS: https://github.com/oasis-tcs/odata-vocabularies/tree/master/vocabularies
|
|
@@ -147,10 +147,23 @@ const vocabularyDefinitions = {
|
|
|
147
147
|
'inc': { Alias: 'Validation', Namespace: 'Org.OData.Validation.V1' },
|
|
148
148
|
'int': { filename: 'Validation.xml' }
|
|
149
149
|
},
|
|
150
|
+
/* unvalidated vocabularies below here:
|
|
151
|
+
A vocabulary is unvalidated if it doesn't have an int.filename property as this indicates that
|
|
152
|
+
the vocabulary is added to the validation dictionary
|
|
153
|
+
Example:
|
|
154
|
+
'Org.Snafu.V1': {
|
|
155
|
+
'ref': { Uri: 'https://snafu.org/snafu.xml' },
|
|
156
|
+
'inc': { Alias: 'Snafu', Namespace: 'Org.Snafu.V1' },
|
|
157
|
+
},
|
|
158
|
+
*/
|
|
150
159
|
};
|
|
151
160
|
|
|
152
|
-
|
|
153
|
-
|
|
161
|
+
/* create inverted voc definitions list to allow addressing full qualified vocabularies
|
|
162
|
+
Object.entries(vocabularyDefinitions).forEach(([n, v]) => {
|
|
163
|
+
if(!vocabularyDefinitions[v.inc.Namespace])
|
|
164
|
+
vocabularyDefinitions[v.inc.Namespace] = vocabularyDefinitions[n];
|
|
165
|
+
});
|
|
166
|
+
*/
|
|
154
167
|
/**************************************************************************************************
|
|
155
168
|
* csn2annotationEdm
|
|
156
169
|
*
|
|
@@ -158,24 +171,16 @@ const knownVocabularies = Object.keys(vocabularyDefinitions);
|
|
|
158
171
|
* v - array with two boolean entries, first is for v2, second is for v4
|
|
159
172
|
* dictReplacement: for test purposes, replaces the standard oDataDictionary
|
|
160
173
|
*/
|
|
161
|
-
function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
174
|
+
function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
175
|
+
Edm = undefined, options=undefined, messageFunctions=undefined, mergedVocDefs=vocabularyDefinitions) {
|
|
162
176
|
// global variable where we store all the generated annotations
|
|
163
177
|
const g_annosArray = [];
|
|
164
178
|
|
|
165
179
|
const { message } = messageFunctions;
|
|
166
180
|
|
|
167
|
-
const [
|
|
181
|
+
const [ userDefinedTermDict, allKnownVocabularies ] = createUserDefinedTermDictionary();
|
|
168
182
|
|
|
169
|
-
|
|
170
|
-
allKnownVocabularies is the union of knownVocabularies & adhoc $mySchemaName annotation definition
|
|
171
|
-
this allows to identify namespace prefixes of arbitrary length.
|
|
172
|
-
|
|
173
|
-
If in the future, the full Oasis vocabulary names shall be made usable for assignments
|
|
174
|
-
eg. @com.sap.vocabularies.Common.v1.Label, all that needs to be done is to x-link the
|
|
175
|
-
vocabulary dictionary whith the full ns as key:
|
|
176
|
-
vocabularyDefinitions['@com.sap.vocabularies.Common.v1'] = vocabularyDefinitions['Common']
|
|
177
|
-
*/
|
|
178
|
-
allKnownVocabularies.push(...knownVocabularies);
|
|
183
|
+
allKnownVocabularies.push(...Object.keys(mergedVocDefs));
|
|
179
184
|
allKnownVocabularies.sort((a,b) => b.length-a.length);
|
|
180
185
|
const whatsMyTermNamespace = function(anno) {
|
|
181
186
|
return allKnownVocabularies.reduce((rc, ns) => !rc && anno && anno.startsWith('@' + ns + '.') ? ns : rc, undefined);
|
|
@@ -189,13 +194,21 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
189
194
|
|
|
190
195
|
// we take note of which vocabularies are actually used in a service in order to avoid
|
|
191
196
|
// producing useless references; reset everything to "unused"
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
197
|
+
for(const n in mergedVocDefs) {
|
|
198
|
+
mergedVocDefs[n].used = false;
|
|
199
|
+
delete mergedVocDefs[n].$ignore;
|
|
200
|
+
}
|
|
195
201
|
|
|
196
202
|
// These vocabularies are always added for the runtimes
|
|
197
|
-
|
|
198
|
-
|
|
203
|
+
mergedVocDefs['Common'].used = true;
|
|
204
|
+
mergedVocDefs['Core'].used = true;
|
|
205
|
+
|
|
206
|
+
const vocDef = mergedVocDefs[serviceName];
|
|
207
|
+
if(vocDef && vocDef.$optVocRef) {
|
|
208
|
+
setProp(vocDef, '$ignore', true);
|
|
209
|
+
message('odata-anno-vocref', null,
|
|
210
|
+
{ name: serviceName, '#': 'service' } );
|
|
211
|
+
}
|
|
199
212
|
|
|
200
213
|
// provide functions for dictionary lookup
|
|
201
214
|
// use closure to avoid making "dict" and "experimental" global variables
|
|
@@ -210,15 +223,16 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
210
223
|
// - issue a warning if the term is flagged as "experimental"
|
|
211
224
|
getDictTerm: function(termName, msg) {
|
|
212
225
|
const dictTerm = (dict.terms[termName] ||
|
|
213
|
-
|
|
214
|
-
|
|
226
|
+
userDefinedTermDict.terms[serviceName + '.' + termName] ||
|
|
227
|
+
userDefinedTermDict.terms[termName]);
|
|
215
228
|
// register vocabulary usage if possible
|
|
216
229
|
const vocName = termName.slice(0, termName.indexOf('.'));
|
|
217
|
-
|
|
218
|
-
|
|
230
|
+
const vocDef = mergedVocDefs[vocName];
|
|
231
|
+
if(vocDef && !vocDef.$ignore)
|
|
232
|
+
vocDef.used = true;
|
|
219
233
|
else if(dictTerm?.$myServiceRoot &&
|
|
220
|
-
|
|
221
|
-
|
|
234
|
+
userDefinedTermDict.xrefs[dictTerm?.$myServiceRoot])
|
|
235
|
+
userDefinedTermDict.xrefs[dictTerm.$myServiceRoot].used = true;
|
|
222
236
|
if (dictTerm) {
|
|
223
237
|
// issue message for usage of experimental Terms, but only once per Term
|
|
224
238
|
if (dictTerm['$experimental'] && !experimental[termName]) {
|
|
@@ -237,13 +251,14 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
237
251
|
// in addition, note usage of the respective vocabulary
|
|
238
252
|
getDictType: function (typeName) {
|
|
239
253
|
let dictType = (dict.types[typeName] ||
|
|
240
|
-
|
|
241
|
-
|
|
254
|
+
userDefinedTermDict.types[serviceName + '.' + typeName] ||
|
|
255
|
+
userDefinedTermDict.types[typeName]);
|
|
242
256
|
if (dictType) {
|
|
243
257
|
// register usage of vocabulary
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
258
|
+
const vocName = typeName.slice(0, typeName.indexOf('.'));
|
|
259
|
+
const vocDef = mergedVocDefs[vocName];
|
|
260
|
+
if(vocDef && !vocDef.$ignore)
|
|
261
|
+
vocDef.used = true;
|
|
247
262
|
}
|
|
248
263
|
return dictType;
|
|
249
264
|
}
|
|
@@ -275,12 +290,13 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
275
290
|
}
|
|
276
291
|
});
|
|
277
292
|
|
|
293
|
+
|
|
278
294
|
// filter out empty <Annotations...> elements
|
|
279
295
|
// add references for the used vocabularies
|
|
280
296
|
return {
|
|
281
297
|
annos: g_annosArray,
|
|
282
|
-
usedVocabularies: Object.values(
|
|
283
|
-
xrefs: Object.values(
|
|
298
|
+
usedVocabularies: Object.values(mergedVocDefs).filter(v => v.used),
|
|
299
|
+
xrefs: Object.values(userDefinedTermDict.xrefs).filter(v => v.used).map(v => v.$myServiceRoot)
|
|
284
300
|
};
|
|
285
301
|
|
|
286
302
|
//-------------------------------------------------------------------------------------------------
|
|
@@ -503,10 +519,14 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
503
519
|
function filterKnownAnnotations() {
|
|
504
520
|
const annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
|
|
505
521
|
const nullWhitelist = [ '@Core.OperationAvailable' ];
|
|
506
|
-
const knownAnnos = annoNames.filter(
|
|
522
|
+
const knownAnnos = annoNames.filter(n => {
|
|
523
|
+
const tns = whatsMyTermNamespace(n);
|
|
524
|
+
return tns &&
|
|
525
|
+
(mergedVocDefs[tns] && !mergedVocDefs[tns].$ignore ||
|
|
526
|
+
!mergedVocDefs[tns]);
|
|
527
|
+
}).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
|
|
507
528
|
if(isBetaEnabled(options, 'odataTerms')) {
|
|
508
529
|
// Extend knownAnnos with the in-service term definitions
|
|
509
|
-
const adhoc = [];
|
|
510
530
|
annoNames.forEach(an => {
|
|
511
531
|
const paths = an.slice(1).split('.');
|
|
512
532
|
const hasNSPrefix = paths[0] === serviceName;
|
|
@@ -530,10 +550,9 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
530
550
|
carrier[fqName] = carrier[an];
|
|
531
551
|
delete carrier[an];
|
|
532
552
|
}
|
|
533
|
-
|
|
553
|
+
knownAnnos.push(fqName);
|
|
534
554
|
}
|
|
535
555
|
});
|
|
536
|
-
knownAnnos.push(...adhoc);
|
|
537
556
|
}
|
|
538
557
|
return knownAnnos;
|
|
539
558
|
}
|
|
@@ -868,17 +887,20 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
868
887
|
newAnno.setEdmAttribute('Term', termNameWithoutQualifiers);
|
|
869
888
|
newAnno.setEdmAttribute('Qualifier', qualifier);
|
|
870
889
|
}
|
|
871
|
-
|
|
890
|
+
// get the type of the term from the dictionary
|
|
872
891
|
let termTypeName = null;
|
|
873
892
|
const dictTerm = getDictTerm(termNameWithoutQualifiers, msg);
|
|
874
893
|
if (dictTerm) {
|
|
875
894
|
termTypeName = dictTerm.Type;
|
|
876
895
|
}
|
|
877
896
|
else {
|
|
878
|
-
message
|
|
897
|
+
// message if term is completely unknown or if vocabulary is unchecked
|
|
898
|
+
const vocDef = mergedVocDefs[whatsMyTermNamespace('@'+termNameWithoutQualifiers)];
|
|
899
|
+
if((vocDef?.int && vocDef?.int?.filename) || !vocDef)
|
|
900
|
+
message('odata-anno-def', msg.location,{ anno: termNameWithoutQualifiers });
|
|
879
901
|
}
|
|
880
902
|
|
|
881
|
-
|
|
903
|
+
// handle the annotation value and put the result into the <Annotation ...> tag just created above
|
|
882
904
|
handleValue(annoValue, newAnno, termNameWithoutQualifiers, termTypeName, msg);
|
|
883
905
|
}
|
|
884
906
|
return newAnno;
|
|
@@ -1240,7 +1262,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1240
1262
|
if (dTypeName && !isComplexType(dTypeName)) {
|
|
1241
1263
|
if (!getDictType(dTypeName) && !isPrimitiveType(dTypeName) && !isCollection(dTypeName))
|
|
1242
1264
|
message('odata-anno-dict', msg.location,
|
|
1243
|
-
{ anno: msg.anno(), type: dTypeName
|
|
1265
|
+
{ anno: msg.anno(), type: dTypeName });
|
|
1244
1266
|
else
|
|
1245
1267
|
message('odata-anno-value', msg.location,
|
|
1246
1268
|
{ anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'struct' });
|
|
@@ -1280,7 +1302,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1280
1302
|
// Dictionary Type, render in XML only for backward compatibility
|
|
1281
1303
|
newRecord.setXml( { Type: actualTypeName });
|
|
1282
1304
|
const vocName = actualTypeName.slice(0, actualTypeName.indexOf('.'));
|
|
1283
|
-
const vocDef =
|
|
1305
|
+
const vocDef = mergedVocDefs[vocName];
|
|
1284
1306
|
// Set full qualified type in JSON
|
|
1285
1307
|
// TODO: Adhoc type x-ref URIs (only if abstract types are allowed in CDS)
|
|
1286
1308
|
if(vocDef)
|
|
@@ -1552,7 +1574,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1552
1574
|
}
|
|
1553
1575
|
|
|
1554
1576
|
/**
|
|
1555
|
-
* translate vocabulary definitions into
|
|
1577
|
+
* translate vocabulary definitions into a userDefinedTermDict
|
|
1556
1578
|
* with the same structure as the global jsonDictionary that
|
|
1557
1579
|
* contains all official term and type definitions.
|
|
1558
1580
|
*
|
|
@@ -1561,7 +1583,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1561
1583
|
*
|
|
1562
1584
|
* @returns [object, Array<object>]
|
|
1563
1585
|
*/
|
|
1564
|
-
function
|
|
1586
|
+
function createUserDefinedTermDictionary() {
|
|
1565
1587
|
const allKnownVocabularies = [];
|
|
1566
1588
|
const dict = { terms: {}, types: {}, xrefs: {} };
|
|
1567
1589
|
|
|
@@ -1572,7 +1594,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1572
1594
|
let dictDef = oDataDictionary.terms[termName];
|
|
1573
1595
|
if(dictDef) {
|
|
1574
1596
|
message('odata-anno-dict', ['vocabularies', termName],
|
|
1575
|
-
{ anno: termName, '#': 'redefinition' } );
|
|
1597
|
+
{ anno: termName, string: 'annotation', '#': 'redefinition' } );
|
|
1576
1598
|
}
|
|
1577
1599
|
else if(!dictDef) {
|
|
1578
1600
|
const annoDef = csnVocabularies[termName];
|
|
@@ -1799,8 +1821,64 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1799
1821
|
|
|
1800
1822
|
}
|
|
1801
1823
|
|
|
1824
|
+
function mergeVocRefs(options, message) {
|
|
1825
|
+
/* Merge options.odataVocRefs into vocabularyDefinitions and
|
|
1826
|
+
create a csn2edm stack local dictionary.
|
|
1827
|
+
odataVocRefs is an object, each property is the
|
|
1828
|
+
annotation prefix (as in mergedVocDefs), the value
|
|
1829
|
+
is an object { Alias, Namespace, Uri }, this way
|
|
1830
|
+
the definitions are unique and duplicate entries to address
|
|
1831
|
+
the annotation via alias and namespace is possible (see
|
|
1832
|
+
inverted index of mergedVocDefs above)
|
|
1833
|
+
*/
|
|
1834
|
+
const mergedVocDefs = Object.assign({}, vocabularyDefinitions);
|
|
1835
|
+
const reqProps = ['Alias', 'Namespace', 'Uri'];
|
|
1836
|
+
if(options.odataVocRefs) {
|
|
1837
|
+
const vocRefs = options.odataVocRefs;
|
|
1838
|
+
if (typeof vocRefs === 'object' && !Array.isArray(vocRefs)) {
|
|
1839
|
+
Object.entries(vocRefs).forEach(([id, def]) => {
|
|
1840
|
+
let defOk = true;
|
|
1841
|
+
reqProps.forEach(name => {
|
|
1842
|
+
if(!def[name] || typeof def[name] !== 'string') {
|
|
1843
|
+
message('odata-anno-vocref', null,
|
|
1844
|
+
{ id, name, '#': 'malformed' } );
|
|
1845
|
+
defOk = false;
|
|
1846
|
+
}
|
|
1847
|
+
else if(name === 'Alias' && !edmUtils.isODataSimpleIdentifier(def[name])) {
|
|
1848
|
+
message('odata-spec-violation-id', null, { id:name, value: def[name], '#': 'vocrefalias' });
|
|
1849
|
+
defOk = false;
|
|
1850
|
+
}
|
|
1851
|
+
});
|
|
1852
|
+
if(defOk) {
|
|
1853
|
+
const vocDef = mergedVocDefs[id];
|
|
1854
|
+
if(vocDef && !vocDef.$optVocRef) {
|
|
1855
|
+
message('odata-anno-vocref', null,
|
|
1856
|
+
{ id, type: mergedVocDefs[id].inc.Namespace, '#': 'redef' } );
|
|
1857
|
+
}
|
|
1858
|
+
else {
|
|
1859
|
+
if(id !== def.Alias) {
|
|
1860
|
+
message('odata-anno-vocref', null,
|
|
1861
|
+
{ id, name: def.Alias } );
|
|
1862
|
+
}
|
|
1863
|
+
else {
|
|
1864
|
+
// no int.filename => no validation
|
|
1865
|
+
const vocDef = {
|
|
1866
|
+
ref: { Uri: def.Uri },
|
|
1867
|
+
inc: { Alias: def.Alias, Namespace: def.Namespace }
|
|
1868
|
+
};
|
|
1869
|
+
setProp(vocDef, '$optVocRef', true);
|
|
1870
|
+
mergedVocDefs[id] = vocDef;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
return mergedVocDefs;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1802
1880
|
//-------------------------------------------------------------------------------------------------
|
|
1803
1881
|
//-------------------------------------------------------------------------------------------------
|
|
1804
1882
|
//-------------------------------------------------------------------------------------------------
|
|
1805
1883
|
|
|
1806
|
-
module.exports = {
|
|
1884
|
+
module.exports = { vocabularyDefinitions, csn2annotationEdm, mergeVocRefs };
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -51,6 +51,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
51
51
|
whatsMyServiceRootName,
|
|
52
52
|
fallBackSchemaName,
|
|
53
53
|
options ] = initializeModel(csn, _options, messageFunctions, serviceNames);
|
|
54
|
+
|
|
55
|
+
const mergedVocabularies = translate.mergeVocRefs(options, message);
|
|
54
56
|
|
|
55
57
|
const Edm = getEdm(options, messageFunctions);
|
|
56
58
|
|
|
@@ -78,6 +80,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
78
80
|
services[serviceCsn.name] = createEdm(serviceCsn);
|
|
79
81
|
return services; }, rc);
|
|
80
82
|
}
|
|
83
|
+
|
|
81
84
|
throwWithError();
|
|
82
85
|
return rc;
|
|
83
86
|
|
|
@@ -423,6 +426,24 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
423
426
|
}
|
|
424
427
|
});
|
|
425
428
|
|
|
429
|
+
/*
|
|
430
|
+
Remove EntityContainer if empty
|
|
431
|
+
V4 spec says:
|
|
432
|
+
Chapter 5 Element edm:Schema
|
|
433
|
+
It MAY contain elements [...], edm:EntityContainer, [...].
|
|
434
|
+
Chapter 13 Element edm:EntityContainer
|
|
435
|
+
The edm:EntityContainer MUST contain one or more edm:EntitySet, edm:Singleton, edm:ActionImport, or edm:FunctionImport elements.
|
|
436
|
+
|
|
437
|
+
The first sentence in chapter 13 is:
|
|
438
|
+
Each metadata document used to describe an OData service MUST define exactly one entity container.
|
|
439
|
+
|
|
440
|
+
This sentence expresses that an OData SERVICE must contain an entity container, but an EDMX is not required to have a container.
|
|
441
|
+
Therefore it is absolutely legal and necessary to remove an empty container from the IR!
|
|
442
|
+
*/
|
|
443
|
+
if(Schema._ec && Schema._ec._children.length === 0) {
|
|
444
|
+
Schema._children.splice(Schema._children.indexOf(Schema._ec), 1);
|
|
445
|
+
}
|
|
446
|
+
|
|
426
447
|
Object.entries(NamesInSchemaXRef).forEach(([name, refs]) => {
|
|
427
448
|
if(refs.length > 1) {
|
|
428
449
|
error(null, ['definitions', `${Schema._edmAttributes.Namespace}.${name}`], { name: Schema._edmAttributes.Namespace },
|
|
@@ -1026,7 +1047,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
1026
1047
|
|
|
1027
1048
|
// generate the Edm.Annotations tree and append it to the corresponding schema
|
|
1028
1049
|
function addAnnotations(xServiceRefs) {
|
|
1029
|
-
let { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions);
|
|
1050
|
+
let { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions, mergedVocabularies);
|
|
1030
1051
|
// distribute edm:Annotations into the schemas
|
|
1031
1052
|
// Distribute each anno into Schema
|
|
1032
1053
|
annos.forEach(anno => {
|
|
@@ -820,8 +820,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
820
820
|
(isContainerAssoc && !options.renderForeignKeys))
|
|
821
821
|
edmUtils.assignAnnotation(element, '@cds.api.ignore', true);
|
|
822
822
|
// Only in containment:
|
|
823
|
-
// If this element is a foreign key and if it is rendered, remove it from the key ref vector
|
|
824
|
-
else if(options.odataContainment &&
|
|
823
|
+
// If this element is a foreign key and if it is rendered, remove it from the key ref vector (if available)
|
|
824
|
+
else if(options.odataContainment &&
|
|
825
|
+
isContainerAssoc &&
|
|
826
|
+
options.renderForeignKeys &&
|
|
827
|
+
struct.$keys) {
|
|
825
828
|
delete struct.$keys[element.name];
|
|
826
829
|
}
|
|
827
830
|
}
|
|
@@ -830,8 +833,8 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
830
833
|
if(options.odataContainment && element['@odata.containment.ignore']) {
|
|
831
834
|
if(!options.renderForeignKeys)
|
|
832
835
|
edmUtils.assignAnnotation(element, '@cds.api.ignore', true);
|
|
833
|
-
else
|
|
834
|
-
// If foreign keys shall be rendered, remove it from key ref vector
|
|
836
|
+
else if(struct.$keys)
|
|
837
|
+
// If foreign keys shall be rendered, remove it from key ref vector (if available)
|
|
835
838
|
delete struct.$keys[element.name];
|
|
836
839
|
}
|
|
837
840
|
}
|
|
@@ -1560,7 +1563,10 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1560
1563
|
Object.entries(elements).forEach(([eltName, elt]) => {
|
|
1561
1564
|
if(!elt.$visited) {
|
|
1562
1565
|
setProp(elt, '$visited', true);
|
|
1563
|
-
|
|
1566
|
+
let newRefs = [];
|
|
1567
|
+
// if the foreign keys are explicitly requested, ignore associations and use the flat foreign key instead
|
|
1568
|
+
if(!options.renderForeignKeys || (options.renderForeignKeys && !elt.target))
|
|
1569
|
+
newRefs = produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName, path);
|
|
1564
1570
|
if(newRefs.length) {
|
|
1565
1571
|
keyPaths.push(...newRefs);
|
|
1566
1572
|
// check path step key for spec violations
|
|
@@ -1855,33 +1861,47 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1855
1861
|
if(typeDef?.enum) {
|
|
1856
1862
|
const enumValue = [];
|
|
1857
1863
|
for(const enumSymbol in typeDef.enum) {
|
|
1858
|
-
let enumSymbolDef = typeDef.enum[enumSymbol];
|
|
1859
1864
|
const result = { '@Core.SymbolicName': enumSymbol };
|
|
1860
|
-
|
|
1865
|
+
let enumSymbolDef = typeDef.enum[enumSymbol];
|
|
1866
|
+
while(enumSymbolDef && !enumSymbolDef.$visited && enumSymbolDef['#']) {
|
|
1867
|
+
setProp(enumSymbolDef, '$visited', true);
|
|
1861
1868
|
enumSymbolDef = typeDef.enum[enumSymbolDef['#']];
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1869
|
+
}
|
|
1870
|
+
// reset visited
|
|
1871
|
+
for(const es in typeDef.enum)
|
|
1872
|
+
delete typeDef.enum[es].$visited;
|
|
1873
|
+
|
|
1874
|
+
if(enumSymbolDef) {
|
|
1875
|
+
if(enumSymbolDef.val !== undefined) {
|
|
1876
|
+
// 'null' value is represented spec conform as empty record in AllowedValues collection
|
|
1877
|
+
result.Value = enumSymbolDef.val;
|
|
1866
1878
|
enumValue.push(result);
|
|
1867
1879
|
}
|
|
1868
|
-
else
|
|
1869
|
-
|
|
1870
|
-
|
|
1880
|
+
else {
|
|
1881
|
+
if(typeDef.type === 'cds.String') {
|
|
1882
|
+
// the symbol is used as value for type 'cds.String'
|
|
1883
|
+
result.Value = enumSymbol;
|
|
1884
|
+
enumValue.push(result);
|
|
1885
|
+
}
|
|
1886
|
+
else if(node.kind !== 'annotation') {
|
|
1887
|
+
// omit the entry and warn
|
|
1888
|
+
warning('odata-enum-missing-value', path,
|
|
1889
|
+
{ name: enumSymbol, anno: '@Valiation.AllowedValues', type: typeDef.type },
|
|
1890
|
+
'Expected enum element $(NAME) of type $(TYPE) to have a value, not added to $(ANNO)');
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
else { // enumSymbolDef not found
|
|
1895
|
+
// omit the entry and warn
|
|
1896
|
+
warning('odata-enum-missing-value', path,
|
|
1871
1897
|
{ name: enumSymbol, anno: '@Valiation.AllowedValues', type: typeDef.type },
|
|
1872
1898
|
'Expected enum element $(NAME) of type $(TYPE) to have a value, not added to $(ANNO)');
|
|
1873
1899
|
}
|
|
1874
|
-
else {
|
|
1875
|
-
// 'null' value is represented spec conform as empty record in AllowedValues collection
|
|
1876
|
-
//if(enumSymbolDef.val !== null)
|
|
1877
|
-
result.Value = enumSymbolDef.val;
|
|
1878
|
-
enumValue.push(result);
|
|
1879
|
-
}
|
|
1880
1900
|
|
|
1881
1901
|
// Can't rely that @description has already been renamed to @Core.Description
|
|
1882
1902
|
// Eval description according to precedence (doc comment must be considered already in Odata transformer
|
|
1883
1903
|
// as in contrast to the other doc commments as it is used to annotate the @Validation.AllowedValues)
|
|
1884
|
-
const desc = enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc;
|
|
1904
|
+
const desc = enumSymbolDef ? enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc : undefined;
|
|
1885
1905
|
if (desc)
|
|
1886
1906
|
result['@Core.Description'] = desc;
|
|
1887
1907
|
}
|
package/lib/gen/Dictionary.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
7b8ead4c652cf564b5ba45f94d2c45af
|