@sap/cds-compiler 3.6.2 → 3.8.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 +109 -1
- package/README.md +3 -0
- package/bin/cdsc.js +12 -5
- package/doc/CHANGELOG_ARCHIVE.md +6 -6
- package/doc/CHANGELOG_BETA.md +35 -2
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/doc/DeprecatedOptions_v2.md +1 -1
- package/doc/NameResolution.md +1 -1
- package/lib/api/main.js +63 -23
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/dictionaries.js +15 -3
- package/lib/base/keywords.js +2 -0
- package/lib/base/message-registry.js +120 -34
- package/lib/base/messages.js +51 -27
- package/lib/base/model.js +4 -2
- package/lib/base/shuffle.js +2 -1
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/defaultValues.js +1 -1
- package/lib/checks/elements.js +29 -1
- package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/onConditions.js +15 -9
- package/lib/checks/sql-snippets.js +2 -2
- package/lib/checks/types.js +5 -1
- package/lib/checks/validator.js +7 -3
- package/lib/compiler/assert-consistency.js +42 -26
- package/lib/compiler/base.js +50 -4
- package/lib/compiler/builtins.js +17 -8
- package/lib/compiler/checks.js +241 -246
- package/lib/compiler/define.js +113 -146
- package/lib/compiler/extend.js +889 -383
- package/lib/compiler/finalize-parse-cdl.js +5 -58
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/kick-start.js +7 -8
- package/lib/compiler/populate.js +297 -293
- package/lib/compiler/propagator.js +27 -18
- package/lib/compiler/resolve.js +146 -463
- package/lib/compiler/shared.js +36 -79
- package/lib/compiler/tweak-assocs.js +30 -28
- package/lib/compiler/utils.js +31 -5
- package/lib/edm/annotations/genericTranslation.js +131 -59
- package/lib/edm/annotations/preprocessAnnotations.js +3 -0
- package/lib/edm/csn2edm.js +22 -5
- package/lib/edm/edm.js +6 -4
- package/lib/edm/edmAnnoPreprocessor.js +1 -0
- package/lib/edm/edmPreprocessor.js +42 -26
- package/lib/gen/Dictionary.json +38 -2
- 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 +4828 -4472
- package/lib/inspect/inspectPropagation.js +20 -34
- package/lib/json/from-csn.js +140 -44
- package/lib/json/to-csn.js +114 -122
- package/lib/language/errorStrategy.js +2 -0
- package/lib/language/genericAntlrParser.js +156 -36
- package/lib/language/language.g4 +100 -58
- 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 +15 -3
- package/lib/model/csnUtils.js +12 -74
- package/lib/model/revealInternalProperties.js +4 -2
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +3 -0
- package/lib/render/manageConstraints.js +5 -2
- package/lib/render/toCdl.js +216 -104
- package/lib/render/toHdbcds.js +2 -9
- package/lib/render/toRename.js +14 -51
- package/lib/render/toSql.js +4 -3
- package/lib/render/utils/common.js +9 -5
- package/lib/transform/braceExpression.js +6 -0
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/expansion.js +2 -0
- package/lib/transform/db/flattening.js +37 -36
- package/lib/transform/db/rewriteCalculatedElements.js +600 -0
- package/lib/transform/db/transformExists.js +4 -0
- package/lib/transform/db/views.js +40 -37
- package/lib/transform/forOdataNew.js +20 -15
- package/lib/transform/forRelationalDB.js +58 -41
- package/lib/transform/odata/typesExposure.js +50 -15
- package/lib/transform/parseExpr.js +16 -8
- package/lib/transform/transformUtilsNew.js +42 -14
- package/lib/transform/translateAssocsToJoins.js +60 -37
- 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 [
|
|
168
|
-
|
|
169
|
-
/*
|
|
170
|
-
allKnownVocabularies is the union of knownVocabularies & adhoc $mySchemaName annotation definition
|
|
171
|
-
this allows to identify namespace prefixes of arbitrary length.
|
|
181
|
+
const [ userDefinedTermDict, allKnownVocabularies ] = createUserDefinedTermDictionary();
|
|
172
182
|
|
|
173
|
-
|
|
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;
|
|
@@ -1179,21 +1201,15 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1179
1201
|
}
|
|
1180
1202
|
}
|
|
1181
1203
|
else if (typeof value === 'number') {
|
|
1182
|
-
if (isComplexType(resolvedType)
|
|
1204
|
+
if (isComplexType(resolvedType) ||
|
|
1205
|
+
resolvedType === 'Edm.PropertyPath' ||
|
|
1206
|
+
resolvedType === 'Edm.Boolean') {
|
|
1183
1207
|
message('odata-anno-value', msg.location,
|
|
1184
|
-
|
|
1208
|
+
{ anno: msg.anno(), value, type: resolvedType });
|
|
1185
1209
|
}
|
|
1186
1210
|
else if (resolvedType === 'Edm.String') {
|
|
1187
1211
|
typeName = 'String';
|
|
1188
1212
|
}
|
|
1189
|
-
else if (resolvedType === 'Edm.PropertyPath') {
|
|
1190
|
-
message('odata-anno-value', msg.location,
|
|
1191
|
-
{ anno: msg.anno(), value, type: resolvedType });
|
|
1192
|
-
}
|
|
1193
|
-
else if (resolvedType === 'Edm.Boolean') {
|
|
1194
|
-
message('odata-anno-value', msg.location,
|
|
1195
|
-
{ anno: msg.anno(), value, type: resolvedType });
|
|
1196
|
-
}
|
|
1197
1213
|
else if (resolvedType === 'Edm.Decimal') {
|
|
1198
1214
|
typeName = 'Decimal';
|
|
1199
1215
|
}
|
|
@@ -1204,18 +1220,18 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1204
1220
|
//typeName = Number.isInteger(val) ? 'Int' : 'Float';
|
|
1205
1221
|
if(Number.isInteger(value)) {
|
|
1206
1222
|
typeName = 'Int';
|
|
1207
|
-
if(resolvedType ==
|
|
1223
|
+
if(resolvedType == null || resolvedType === 'Edm.PrimitiveType' || !resolvedType.startsWith('Edm.'))
|
|
1208
1224
|
resolvedType = 'Edm.Int64';
|
|
1209
1225
|
}
|
|
1210
1226
|
else {
|
|
1211
1227
|
typeName = 'Float';
|
|
1212
|
-
if(resolvedType ==
|
|
1228
|
+
if(resolvedType == null || resolvedType === 'Edm.PrimitiveType'|| !resolvedType.startsWith('Edm.'))
|
|
1213
1229
|
resolvedType = 'Edm.Double';
|
|
1214
1230
|
}
|
|
1215
1231
|
}
|
|
1216
1232
|
}
|
|
1217
1233
|
else if (value === null)
|
|
1218
|
-
if((resolvedType == null || resolvedType
|
|
1234
|
+
if((resolvedType == null || resolvedType === 'Edm.PrimitiveType') && typeName === 'String') {
|
|
1219
1235
|
resolvedType = 'Edm.String';
|
|
1220
1236
|
}
|
|
1221
1237
|
else {
|
|
@@ -1246,7 +1262,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1246
1262
|
if (dTypeName && !isComplexType(dTypeName)) {
|
|
1247
1263
|
if (!getDictType(dTypeName) && !isPrimitiveType(dTypeName) && !isCollection(dTypeName))
|
|
1248
1264
|
message('odata-anno-dict', msg.location,
|
|
1249
|
-
{ anno: msg.anno(), type: dTypeName
|
|
1265
|
+
{ anno: msg.anno(), type: dTypeName });
|
|
1250
1266
|
else
|
|
1251
1267
|
message('odata-anno-value', msg.location,
|
|
1252
1268
|
{ anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'struct' });
|
|
@@ -1286,7 +1302,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1286
1302
|
// Dictionary Type, render in XML only for backward compatibility
|
|
1287
1303
|
newRecord.setXml( { Type: actualTypeName });
|
|
1288
1304
|
const vocName = actualTypeName.slice(0, actualTypeName.indexOf('.'));
|
|
1289
|
-
const vocDef =
|
|
1305
|
+
const vocDef = mergedVocDefs[vocName];
|
|
1290
1306
|
// Set full qualified type in JSON
|
|
1291
1307
|
// TODO: Adhoc type x-ref URIs (only if abstract types are allowed in CDS)
|
|
1292
1308
|
if(vocDef)
|
|
@@ -1558,7 +1574,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1558
1574
|
}
|
|
1559
1575
|
|
|
1560
1576
|
/**
|
|
1561
|
-
* translate vocabulary definitions into
|
|
1577
|
+
* translate vocabulary definitions into a userDefinedTermDict
|
|
1562
1578
|
* with the same structure as the global jsonDictionary that
|
|
1563
1579
|
* contains all official term and type definitions.
|
|
1564
1580
|
*
|
|
@@ -1567,7 +1583,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1567
1583
|
*
|
|
1568
1584
|
* @returns [object, Array<object>]
|
|
1569
1585
|
*/
|
|
1570
|
-
function
|
|
1586
|
+
function createUserDefinedTermDictionary() {
|
|
1571
1587
|
const allKnownVocabularies = [];
|
|
1572
1588
|
const dict = { terms: {}, types: {}, xrefs: {} };
|
|
1573
1589
|
|
|
@@ -1578,7 +1594,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1578
1594
|
let dictDef = oDataDictionary.terms[termName];
|
|
1579
1595
|
if(dictDef) {
|
|
1580
1596
|
message('odata-anno-dict', ['vocabularies', termName],
|
|
1581
|
-
{ anno: termName, '#': 'redefinition' } );
|
|
1597
|
+
{ anno: termName, string: 'annotation', '#': 'redefinition' } );
|
|
1582
1598
|
}
|
|
1583
1599
|
else if(!dictDef) {
|
|
1584
1600
|
const annoDef = csnVocabularies[termName];
|
|
@@ -1805,8 +1821,64 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1805
1821
|
|
|
1806
1822
|
}
|
|
1807
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
|
+
|
|
1808
1880
|
//-------------------------------------------------------------------------------------------------
|
|
1809
1881
|
//-------------------------------------------------------------------------------------------------
|
|
1810
1882
|
//-------------------------------------------------------------------------------------------------
|
|
1811
1883
|
|
|
1812
|
-
module.exports = {
|
|
1884
|
+
module.exports = { vocabularyDefinitions, csn2annotationEdm, mergeVocRefs };
|
|
@@ -63,6 +63,9 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
63
63
|
artifact.elements && Object.entries(artifact.elements).forEach(([elementName, element]) => {
|
|
64
64
|
handleAnnotations(artifactName, elementName, element, [ ...location, 'elements', elementName ]);
|
|
65
65
|
});
|
|
66
|
+
artifact.params && Object.entries(artifact.params).forEach(([paramName, param]) => {
|
|
67
|
+
handleAnnotations(artifactName, paramName, param, [ ...location, 'actions', artifactName, 'params', paramName ]);
|
|
68
|
+
});
|
|
66
69
|
forEachGeneric(artifact, 'actions', (action, actionName) => {
|
|
67
70
|
action.params && Object.entries(action.params).forEach(([paramName, param]) => {
|
|
68
71
|
handleAnnotations(actionName, paramName, param, [ ...location, 'actions', actionName, 'params', paramName ]);
|
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 },
|
|
@@ -518,10 +539,6 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
518
539
|
const properties = createProperties(elementsCsn, structuredTypeCsn)[0];
|
|
519
540
|
const loc = ['definitions', structuredTypeCsn.name];
|
|
520
541
|
|
|
521
|
-
if(properties.length === 0) {
|
|
522
|
-
warning(null, loc, { name: structuredTypeCsn.name },
|
|
523
|
-
'EDM ComplexType $(NAME) has no properties');
|
|
524
|
-
}
|
|
525
542
|
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
526
543
|
message('odata-spec-violation-id', loc, { id: attributes.Name });
|
|
527
544
|
|
|
@@ -1030,7 +1047,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
1030
1047
|
|
|
1031
1048
|
// generate the Edm.Annotations tree and append it to the corresponding schema
|
|
1032
1049
|
function addAnnotations(xServiceRefs) {
|
|
1033
|
-
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);
|
|
1034
1051
|
// distribute edm:Annotations into the schemas
|
|
1035
1052
|
// Distribute each anno into Schema
|
|
1036
1053
|
annos.forEach(anno => {
|
package/lib/edm/edm.js
CHANGED
|
@@ -749,7 +749,7 @@ function getEdm(options, messageFunctions) {
|
|
|
749
749
|
this._edmAttributes && Object.entries(this._edmAttributes).forEach(([p, v]) => {
|
|
750
750
|
if (p !== 'Name' && p !== this._typeName
|
|
751
751
|
// remove this line if Nullable=true becomes default
|
|
752
|
-
&& !(p === 'Nullable' && v
|
|
752
|
+
&& !(p === 'Nullable' && !v))
|
|
753
753
|
{
|
|
754
754
|
json[p[0] === '@' ? p : '$' + p] = v;
|
|
755
755
|
}
|
|
@@ -763,7 +763,7 @@ function getEdm(options, messageFunctions) {
|
|
|
763
763
|
}
|
|
764
764
|
}
|
|
765
765
|
|
|
766
|
-
class ComplexType extends TypeBase {
|
|
766
|
+
class ComplexType extends TypeBase {
|
|
767
767
|
constructor(v, details, csn) {
|
|
768
768
|
super(v, details, csn);
|
|
769
769
|
if(this.v4 && !!csn['@open'] && isBetaEnabled(options, 'odataOpenType')) {
|
|
@@ -979,6 +979,7 @@ function getEdm(options, messageFunctions) {
|
|
|
979
979
|
// TIPHANACDS-4180
|
|
980
980
|
if(this.v2)
|
|
981
981
|
{
|
|
982
|
+
// eslint-disable-next-line sonarjs/no-redundant-boolean
|
|
982
983
|
if(csn['@odata.etag'] == true || csn['@cds.etag'] == true)
|
|
983
984
|
this._edmAttributes.ConcurrencyMode='Fixed'
|
|
984
985
|
|
|
@@ -1101,8 +1102,8 @@ function getEdm(options, messageFunctions) {
|
|
|
1101
1102
|
delete this._edmAttributes.Nullable;
|
|
1102
1103
|
}
|
|
1103
1104
|
// we have exactly one selfReference or the default partner
|
|
1104
|
-
let partner =
|
|
1105
|
-
!csn.$noPartner ?
|
|
1105
|
+
let partner =
|
|
1106
|
+
!csn.$noPartner ?
|
|
1106
1107
|
csn._selfReferences.length === 1
|
|
1107
1108
|
? csn._selfReferences[0]
|
|
1108
1109
|
: csn._constraints._partnerCsn
|
|
@@ -1121,6 +1122,7 @@ function getEdm(options, messageFunctions) {
|
|
|
1121
1122
|
See csn2edm.createParmeterizedEntityTypeAndSet() for details
|
|
1122
1123
|
2) ContainsTarget stems from the @odata.contained annotation
|
|
1123
1124
|
*/
|
|
1125
|
+
// eslint-disable-next-line sonarjs/no-redundant-boolean
|
|
1124
1126
|
if(csn['@odata.contained'] == true || csn.containsTarget) {
|
|
1125
1127
|
this._edmAttributes.ContainsTarget = true;
|
|
1126
1128
|
}
|
|
@@ -70,6 +70,7 @@ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct
|
|
|
70
70
|
// for @[odata|cds].etag annotations...
|
|
71
71
|
if(options.isV4())
|
|
72
72
|
{
|
|
73
|
+
// eslint-disable-next-line sonarjs/no-redundant-boolean
|
|
73
74
|
if(element['@odata.etag'] == true || element['@cds.etag'] == true) {
|
|
74
75
|
// don't put element name into collection as per advice from Ralf Handl, as
|
|
75
76
|
// no runtime is interested in the property itself, it is sufficient to mark
|
|
@@ -100,10 +100,6 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
100
100
|
|
|
101
101
|
/*
|
|
102
102
|
Enrich the CSN by de-anonymizing and exposing types that are required to make the service self contained.
|
|
103
|
-
Type exposure will add additional schema contexts and group the exposed types in these contexts.
|
|
104
|
-
Contexts either represent another service (if the type to be exposed resides in that
|
|
105
|
-
service), the namespace (including (sub-)contexts) or as last resort (if the type name
|
|
106
|
-
has no prefix path) a 'root' namespace.
|
|
107
103
|
*/
|
|
108
104
|
const schemas = typesExposure(csn, whatsMyServiceRootName, requestedServiceNames,
|
|
109
105
|
fallBackSchemaName, options, csnUtils, { error });
|
|
@@ -824,8 +820,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
824
820
|
(isContainerAssoc && !options.renderForeignKeys))
|
|
825
821
|
edmUtils.assignAnnotation(element, '@cds.api.ignore', true);
|
|
826
822
|
// Only in containment:
|
|
827
|
-
// If this element is a foreign key and if it is rendered, remove it from the key ref vector
|
|
828
|
-
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) {
|
|
829
828
|
delete struct.$keys[element.name];
|
|
830
829
|
}
|
|
831
830
|
}
|
|
@@ -834,8 +833,8 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
834
833
|
if(options.odataContainment && element['@odata.containment.ignore']) {
|
|
835
834
|
if(!options.renderForeignKeys)
|
|
836
835
|
edmUtils.assignAnnotation(element, '@cds.api.ignore', true);
|
|
837
|
-
else
|
|
838
|
-
// 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)
|
|
839
838
|
delete struct.$keys[element.name];
|
|
840
839
|
}
|
|
841
840
|
}
|
|
@@ -1564,7 +1563,10 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1564
1563
|
Object.entries(elements).forEach(([eltName, elt]) => {
|
|
1565
1564
|
if(!elt.$visited) {
|
|
1566
1565
|
setProp(elt, '$visited', true);
|
|
1567
|
-
|
|
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);
|
|
1568
1570
|
if(newRefs.length) {
|
|
1569
1571
|
keyPaths.push(...newRefs);
|
|
1570
1572
|
// check path step key for spec violations
|
|
@@ -1859,33 +1861,47 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1859
1861
|
if(typeDef?.enum) {
|
|
1860
1862
|
const enumValue = [];
|
|
1861
1863
|
for(const enumSymbol in typeDef.enum) {
|
|
1862
|
-
let enumSymbolDef = typeDef.enum[enumSymbol];
|
|
1863
1864
|
const result = { '@Core.SymbolicName': enumSymbol };
|
|
1864
|
-
|
|
1865
|
+
let enumSymbolDef = typeDef.enum[enumSymbol];
|
|
1866
|
+
while(enumSymbolDef && !enumSymbolDef.$visited && enumSymbolDef['#']) {
|
|
1867
|
+
setProp(enumSymbolDef, '$visited', true);
|
|
1865
1868
|
enumSymbolDef = typeDef.enum[enumSymbolDef['#']];
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
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;
|
|
1870
1878
|
enumValue.push(result);
|
|
1871
1879
|
}
|
|
1872
|
-
else
|
|
1873
|
-
|
|
1874
|
-
|
|
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,
|
|
1875
1897
|
{ name: enumSymbol, anno: '@Valiation.AllowedValues', type: typeDef.type },
|
|
1876
1898
|
'Expected enum element $(NAME) of type $(TYPE) to have a value, not added to $(ANNO)');
|
|
1877
1899
|
}
|
|
1878
|
-
else {
|
|
1879
|
-
// 'null' value is represented spec conform as empty record in AllowedValues collection
|
|
1880
|
-
//if(enumSymbolDef.val !== null)
|
|
1881
|
-
result.Value = enumSymbolDef.val;
|
|
1882
|
-
enumValue.push(result);
|
|
1883
|
-
}
|
|
1884
1900
|
|
|
1885
1901
|
// Can't rely that @description has already been renamed to @Core.Description
|
|
1886
1902
|
// Eval description according to precedence (doc comment must be considered already in Odata transformer
|
|
1887
1903
|
// as in contrast to the other doc commments as it is used to annotate the @Validation.AllowedValues)
|
|
1888
|
-
const desc = enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc;
|
|
1904
|
+
const desc = enumSymbolDef ? enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc : undefined;
|
|
1889
1905
|
if (desc)
|
|
1890
1906
|
result['@Core.Description'] = desc;
|
|
1891
1907
|
}
|
|
@@ -1933,7 +1949,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1933
1949
|
const localRestrictions = container[NavResAnno] ?
|
|
1934
1950
|
cloneAnnotationValue(container[NavResAnno]) : []
|
|
1935
1951
|
|
|
1936
|
-
// prefix the existing navigation property
|
|
1952
|
+
// prefix the existing navigation property restrictions on the container
|
|
1937
1953
|
if(prefix.length) {
|
|
1938
1954
|
localRestrictions.forEach(npe => {
|
|
1939
1955
|
if(npe.NavigationProperty &&
|