@sap/cds-compiler 3.5.2 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -1
- package/bin/cdsc.js +14 -6
- package/doc/CHANGELOG_ARCHIVE.md +10 -10
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/lib/api/main.js +32 -55
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/message-registry.js +104 -32
- package/lib/base/messages.js +277 -212
- package/lib/base/model.js +33 -22
- package/lib/base/optionProcessorHelper.js +9 -2
- package/lib/base/shuffle.js +50 -0
- package/lib/checks/actionsFunctions.js +37 -20
- package/lib/checks/foreignKeys.js +13 -6
- package/lib/checks/nonexpandableStructured.js +1 -2
- package/lib/checks/onConditions.js +21 -19
- package/lib/checks/parameters.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -0
- package/lib/checks/types.js +16 -22
- package/lib/compiler/assert-consistency.js +31 -28
- package/lib/compiler/builtins.js +20 -4
- package/lib/compiler/checks.js +72 -63
- package/lib/compiler/define.js +396 -314
- package/lib/compiler/extend.js +55 -49
- package/lib/compiler/index.js +5 -0
- package/lib/compiler/populate.js +28 -11
- package/lib/compiler/propagator.js +2 -1
- package/lib/compiler/resolve.js +29 -20
- package/lib/compiler/shared.js +15 -10
- package/lib/compiler/utils.js +7 -7
- package/lib/edm/annotations/genericTranslation.js +51 -46
- package/lib/edm/annotations/preprocessAnnotations.js +39 -42
- package/lib/edm/csn2edm.js +69 -21
- package/lib/edm/edm.js +2 -2
- package/lib/edm/edmInboundChecks.js +6 -8
- package/lib/edm/edmPreprocessor.js +88 -80
- package/lib/edm/edmUtils.js +6 -15
- package/lib/gen/Dictionary.json +81 -13
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4680 -4484
- package/lib/inspect/inspectModelStatistics.js +2 -1
- package/lib/inspect/inspectPropagation.js +2 -1
- package/lib/json/from-csn.js +131 -78
- package/lib/json/to-csn.js +39 -23
- package/lib/language/antlrParser.js +0 -3
- package/lib/language/docCommentParser.js +7 -3
- package/lib/language/errorStrategy.js +3 -2
- package/lib/language/genericAntlrParser.js +96 -41
- package/lib/language/language.g4 +112 -128
- package/lib/language/multiLineStringParser.js +2 -1
- package/lib/main.d.ts +115 -2
- package/lib/main.js +16 -3
- package/lib/model/csnRefs.js +3 -3
- package/lib/model/csnUtils.js +109 -179
- package/lib/model/enrichCsn.js +13 -8
- package/lib/model/revealInternalProperties.js +4 -3
- package/lib/optionProcessor.js +23 -3
- package/lib/render/manageConstraints.js +11 -15
- package/lib/render/toCdl.js +144 -47
- package/lib/render/toHdbcds.js +22 -22
- package/lib/render/toRename.js +3 -4
- package/lib/render/toSql.js +29 -20
- package/lib/render/utils/delta.js +3 -1
- package/lib/render/utils/sql.js +3 -16
- package/lib/transform/db/associations.js +6 -6
- package/lib/transform/db/cdsPersistence.js +3 -3
- package/lib/transform/db/constraints.js +8 -8
- package/lib/transform/db/expansion.js +4 -4
- package/lib/transform/db/flattening.js +12 -15
- package/lib/transform/db/temporal.js +4 -3
- package/lib/transform/db/transformExists.js +2 -1
- package/lib/transform/draft/db.js +7 -7
- package/lib/transform/forOdataNew.js +15 -4
- package/lib/transform/forRelationalDB.js +53 -39
- package/lib/transform/odata/toFinalBaseType.js +106 -82
- package/lib/transform/odata/typesExposure.js +26 -17
- package/lib/transform/odata/utils.js +1 -1
- package/lib/transform/parseExpr.js +1 -1
- package/lib/transform/transformUtilsNew.js +33 -10
- package/lib/transform/translateAssocsToJoins.js +8 -7
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -4
- package/lib/utils/timetrace.js +2 -2
- package/package.json +1 -2
|
@@ -159,13 +159,6 @@ const knownVocabularies = Object.keys(vocabularyDefinitions);
|
|
|
159
159
|
* dictReplacement: for test purposes, replaces the standard oDataDictionary
|
|
160
160
|
*/
|
|
161
161
|
function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefined, options=undefined, messageFunctions=undefined) {
|
|
162
|
-
|
|
163
|
-
if(!Edm)
|
|
164
|
-
throw new Error('Please debug me: csn2annotationsEdm must be invoked with Edm');
|
|
165
|
-
if(!options)
|
|
166
|
-
throw new Error('Please debug me: csn2annotationsEdm must be invoked with options');
|
|
167
|
-
if(!messageFunctions)
|
|
168
|
-
throw new Error('Please debug me: csn2annotationsEdm must be invoked with messageFunctions');
|
|
169
162
|
// global variable where we store all the generated annotations
|
|
170
163
|
const g_annosArray = [];
|
|
171
164
|
|
|
@@ -223,7 +216,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
223
216
|
const vocName = termName.slice(0, termName.indexOf('.'));
|
|
224
217
|
if(vocabularyDefinitions[vocName])
|
|
225
218
|
vocabularyDefinitions[vocName].used = true;
|
|
226
|
-
else if(dictTerm?.$myServiceRoot &&
|
|
219
|
+
else if(dictTerm?.$myServiceRoot &&
|
|
227
220
|
adhocDictionary.xrefs[dictTerm?.$myServiceRoot])
|
|
228
221
|
adhocDictionary.xrefs[dictTerm.$myServiceRoot].used = true;
|
|
229
222
|
if (dictTerm) {
|
|
@@ -407,7 +400,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
407
400
|
function relParList() {
|
|
408
401
|
// we rely on the order of params in the csn being the correct one
|
|
409
402
|
const params = [];
|
|
410
|
-
if (entityNameIfBound) {
|
|
403
|
+
if (entityNameIfBound && !(cAction.kind === 'function' && cAction.$hasBindingParam)) {
|
|
411
404
|
params.push(cAction['@cds.odata.bindingparameter.collection'] ? 'Collection(' + entityNameIfBound + ')' : entityNameIfBound);
|
|
412
405
|
}
|
|
413
406
|
if (cAction.kind === 'function') {
|
|
@@ -457,7 +450,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
457
450
|
|
|
458
451
|
// Filter unknown toplevel annotations
|
|
459
452
|
// Final filtering of all annotations is done in handleTerm
|
|
460
|
-
|
|
453
|
+
|
|
461
454
|
let knownAnnos = filterKnownAnnotations();
|
|
462
455
|
if (knownAnnos.length === 0) return;
|
|
463
456
|
|
|
@@ -815,8 +808,8 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
815
808
|
const fullTermName = voc + '.' + term;
|
|
816
809
|
|
|
817
810
|
// msg is "semantic" location message used for messages
|
|
818
|
-
const msg = {
|
|
819
|
-
fullTermName,
|
|
811
|
+
const msg = {
|
|
812
|
+
fullTermName,
|
|
820
813
|
stack: [],
|
|
821
814
|
location: [ ...location, '@' + fullTermName ],
|
|
822
815
|
};
|
|
@@ -832,7 +825,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
832
825
|
const dictTerm = getDictTerm(termName, msg); // message for unknown term was already issued in handleTerm
|
|
833
826
|
if(!addAnnotationFunc(anno, dictTerm && dictTerm.AppliesTo)) {
|
|
834
827
|
if(dictTerm && dictTerm.AppliesTo) {
|
|
835
|
-
message('odata-anno-def', location,
|
|
828
|
+
message('odata-anno-def', location,
|
|
836
829
|
{ anno: termName, rawvalues: dictTerm.AppliesTo, '#': 'notapplied' });
|
|
837
830
|
}
|
|
838
831
|
}
|
|
@@ -1268,33 +1261,39 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1268
1261
|
message('odata-anno-type', msg.location,
|
|
1269
1262
|
{ anno: msg.anno(), type: actualTypeName, '#': 'unknown' });
|
|
1270
1263
|
// explicitly mentioned type, render in XML and JSON
|
|
1271
|
-
newRecord.
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
// this type is abstract
|
|
1275
|
-
message('odata-anno-type', msg.location,
|
|
1276
|
-
{ anno: msg.anno(), type: actualTypeName, '#': 'abstract' });
|
|
1277
|
-
if(dTypeName)
|
|
1278
|
-
actualTypeName = dTypeName;
|
|
1279
|
-
// set to definition name and render in XML and JSON
|
|
1280
|
-
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1281
|
-
}
|
|
1282
|
-
else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {
|
|
1283
|
-
// this type doesn't fit the expected one
|
|
1284
|
-
message('odata-anno-type', msg.location,
|
|
1285
|
-
{ anno: msg.anno(),
|
|
1286
|
-
type: actualTypeName,
|
|
1287
|
-
name: dTypeName,
|
|
1288
|
-
code: '$Type',
|
|
1289
|
-
'#': 'derived' });
|
|
1290
|
-
actualTypeName = dTypeName;
|
|
1291
|
-
// explicitly mentioned type, render in XML and JSON
|
|
1292
|
-
newRecord.setEdmAttribute('Type', actualTypeName);
|
|
1264
|
+
newRecord.setXml({ 'Type': actualTypeName});
|
|
1265
|
+
// unknown dictionary type: can't fully qualify it
|
|
1266
|
+
newRecord.setJSON({ 'Type': actualTypeName});
|
|
1293
1267
|
}
|
|
1294
1268
|
else {
|
|
1295
|
-
|
|
1269
|
+
if (isAbstractType(actualTypeName)) {
|
|
1270
|
+
// this type is abstract
|
|
1271
|
+
message('odata-anno-type', msg.location,
|
|
1272
|
+
{ anno: msg.anno(), type: actualTypeName, code: '$Type', '#': 'abstract' });
|
|
1273
|
+
if(dTypeName)
|
|
1274
|
+
actualTypeName = dTypeName;
|
|
1275
|
+
}
|
|
1276
|
+
else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {
|
|
1277
|
+
// this type doesn't fit the expected one
|
|
1278
|
+
message('odata-anno-type', msg.location,
|
|
1279
|
+
{ anno: msg.anno(),
|
|
1280
|
+
type: actualTypeName,
|
|
1281
|
+
name: dTypeName,
|
|
1282
|
+
code: '$Type',
|
|
1283
|
+
'#': 'derived' });
|
|
1284
|
+
actualTypeName = dTypeName;
|
|
1285
|
+
}
|
|
1296
1286
|
// Dictionary Type, render in XML only for backward compatibility
|
|
1297
1287
|
newRecord.setXml( { Type: actualTypeName });
|
|
1288
|
+
const vocName = actualTypeName.slice(0, actualTypeName.indexOf('.'));
|
|
1289
|
+
const vocDef = vocabularyDefinitions[vocName];
|
|
1290
|
+
// Set full qualified type in JSON
|
|
1291
|
+
// TODO: Adhoc type x-ref URIs (only if abstract types are allowed in CDS)
|
|
1292
|
+
if(vocDef)
|
|
1293
|
+
newRecord.setJSON( { 'Type': `${vocDef.ref.Uri}#${actualTypeName}` });
|
|
1294
|
+
// don't add short actualTypeName into JSON as this would be wrong for a resolved! type.
|
|
1295
|
+
// A $Type w/o vocDef can only occur for adhoc type defs and these can't be abstract but
|
|
1296
|
+
// are fully resolvable due to their term usage via schema x-ref.
|
|
1298
1297
|
}
|
|
1299
1298
|
}
|
|
1300
1299
|
else if (dTypeName) { // there is an expected type name according to dictionary
|
|
@@ -1308,7 +1307,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1308
1307
|
}
|
|
1309
1308
|
if (isAbstractType(actualTypeName))
|
|
1310
1309
|
message('odata-anno-type', msg.location,
|
|
1311
|
-
{ anno: msg.anno(), type: dTypeName, '#': 'abstract' });
|
|
1310
|
+
{ anno: msg.anno(), type: dTypeName, code: '$Type', '#': 'abstract' });
|
|
1312
1311
|
|
|
1313
1312
|
// Dictionary Type, render in XML only for backward compatibility
|
|
1314
1313
|
newRecord.setXml( { Type: actualTypeName });
|
|
@@ -1458,7 +1457,11 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1458
1457
|
const props = Object.create(null);
|
|
1459
1458
|
Object.entries(obj).forEach(([k, val]) => {
|
|
1460
1459
|
if(k === '@type') {
|
|
1461
|
-
edmNode.
|
|
1460
|
+
edmNode.setJSON({ 'Type': val});
|
|
1461
|
+
// try to shorten full qualified type URI to short type name
|
|
1462
|
+
const parts = val.split('#');
|
|
1463
|
+
const shortTypeName = parts[parts.length-1];
|
|
1464
|
+
edmNode.setXml({ Type: shortTypeName });
|
|
1462
1465
|
}
|
|
1463
1466
|
else {
|
|
1464
1467
|
let child = undefined;
|
|
@@ -1554,14 +1557,16 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
|
|
|
1554
1557
|
}
|
|
1555
1558
|
}
|
|
1556
1559
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1560
|
+
/**
|
|
1561
|
+
* translate vocabulary definitions into an adhoc dictionary
|
|
1562
|
+
* with the same structure as the global jsonDictionary that
|
|
1563
|
+
* contains all official term and type definitions.
|
|
1564
|
+
*
|
|
1565
|
+
* Return the dictionary and an array of schemas to which
|
|
1566
|
+
* the vocabulary definitions belong
|
|
1567
|
+
*
|
|
1568
|
+
* @returns [object, Array<object>]
|
|
1569
|
+
*/
|
|
1565
1570
|
function createAdhocDictionary() {
|
|
1566
1571
|
const allKnownVocabularies = [];
|
|
1567
1572
|
const dict = { terms: {}, types: {}, xrefs: {} };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { makeMessageFunction } = require('../../base/messages.js');
|
|
4
|
-
const { forEachDefinition } = require('../../model/csnUtils.js');
|
|
4
|
+
const { forEachDefinition, forEachGeneric } = require('../../model/csnUtils.js');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
/**************************************************************************************************
|
|
@@ -34,10 +34,10 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
34
34
|
// return value can be null is target has no key
|
|
35
35
|
function getKeyOfTargetOfManagedAssoc(anno, assoc) {
|
|
36
36
|
// assoc.target can be the name of the target or the object itself
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
const targetName = (typeof assoc.target === 'object') ? assoc.target.name : assoc.target;
|
|
38
|
+
const target = (typeof assoc.target === 'object') ? assoc.target : csn.definitions[assoc.target];
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
const keyNames = Object.keys(target.elements).filter(x => target.elements[x].key && !target.elements[x].target);
|
|
41
41
|
if (keyNames.length === 0) {
|
|
42
42
|
keyNames.push('MISSING');
|
|
43
43
|
message('odata-anno-preproc', null, { anno, name: targetName, '#': 'nokey' },
|
|
@@ -45,7 +45,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
45
45
|
}
|
|
46
46
|
else if (keyNames.length > 1)
|
|
47
47
|
message('odata-anno-preproc', null, { anno, name: targetName, '#': 'multkeys' });
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
return keyNames[0];
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -56,48 +56,45 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
56
56
|
|
|
57
57
|
// resolve shortcuts
|
|
58
58
|
function resolveShortcuts() {
|
|
59
|
-
let art = null;
|
|
60
|
-
|
|
61
59
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
62
60
|
const location = [ 'definitions', artifactName ];
|
|
63
61
|
if(artifactName === serviceName || artifactName.startsWith(serviceName + '.')) {
|
|
64
|
-
|
|
65
|
-
handleAnnotations(artifactName, artifact, location);
|
|
62
|
+
handleAnnotations(artifactName, artifactName, artifact, location);
|
|
66
63
|
artifact.elements && Object.entries(artifact.elements).forEach(([elementName, element]) => {
|
|
67
|
-
handleAnnotations(elementName, element, [ ...location, 'elements', elementName ]);
|
|
64
|
+
handleAnnotations(artifactName, elementName, element, [ ...location, 'elements', elementName ]);
|
|
68
65
|
});
|
|
69
|
-
artifact
|
|
66
|
+
forEachGeneric(artifact, 'actions', (action, actionName) => {
|
|
70
67
|
action.params && Object.entries(action.params).forEach(([paramName, param]) => {
|
|
71
|
-
handleAnnotations(paramName, param, [ ...location, 'actions',
|
|
68
|
+
handleAnnotations(actionName, paramName, param, [ ...location, 'actions', actionName, 'params', paramName ]);
|
|
72
69
|
});
|
|
73
70
|
});
|
|
74
71
|
}
|
|
75
72
|
});
|
|
76
73
|
|
|
77
|
-
function handleAnnotations(carrierName, carrier, location) {
|
|
74
|
+
function handleAnnotations(defName, carrierName, carrier, location) {
|
|
78
75
|
|
|
79
76
|
// collect the names of the carrier's annotation properties
|
|
80
|
-
|
|
77
|
+
const annoNames = Object.keys(carrier).filter( x => x[0] === '@')
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
annoNames.forEach(aName => {
|
|
80
|
+
const aNameWithoutQualifier = aName.split('#')[0];
|
|
84
81
|
|
|
85
82
|
// Always - draft annotations, value is action name
|
|
86
83
|
// - v2: prefix with entity name
|
|
87
84
|
// - prefix with service name
|
|
88
|
-
draftAnnotations(
|
|
85
|
+
draftAnnotations(aName, aNameWithoutQualifier);
|
|
89
86
|
|
|
90
87
|
// Always - FixedValueListShortcut
|
|
91
88
|
// expand shortcut form of ValueList annotation
|
|
92
|
-
fixedValueListShortCut(
|
|
89
|
+
fixedValueListShortCut(aNameWithoutQualifier);
|
|
93
90
|
|
|
94
91
|
// Always - TextArrangementReordering
|
|
95
92
|
// convert @Common.TextArrangement annotation that is on same level as Text annotation into a nested annotation
|
|
96
|
-
textArrangementReordering(
|
|
97
|
-
}
|
|
93
|
+
textArrangementReordering(aName, aNameWithoutQualifier);
|
|
94
|
+
});
|
|
98
95
|
|
|
99
96
|
// inner functions
|
|
100
|
-
function draftAnnotations(
|
|
97
|
+
function draftAnnotations(aName, aNameWithoutQualifier) {
|
|
101
98
|
if ((carrier.kind === 'entity') &&
|
|
102
99
|
(aNameWithoutQualifier === '@Common.DraftRoot.PreparationAction' ||
|
|
103
100
|
aNameWithoutQualifier === '@Common.DraftRoot.ActivationAction' ||
|
|
@@ -107,8 +104,9 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
107
104
|
let value = carrier[aName];
|
|
108
105
|
// prefix with service name, if not already done
|
|
109
106
|
if (value === 'draftPrepare' || value === 'draftActivate' || value === 'draftEdit') {
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
// mocha test has no whatsMySchemaName
|
|
108
|
+
const schemaName = options.whatsMySchemaName && options.whatsMySchemaName(carrierName) || serviceName;
|
|
109
|
+
value = carrier[aName] = schemaName + '.' + value;
|
|
112
110
|
}
|
|
113
111
|
// for v2: function imports live inside EntityContainer -> path needs to contain "EntityContainer/"
|
|
114
112
|
// we decided to prefix names of bound action/functions with entity name -> needs to be reflected in path, too
|
|
@@ -119,7 +117,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
119
117
|
}
|
|
120
118
|
}
|
|
121
119
|
|
|
122
|
-
function fixedValueListShortCut(
|
|
120
|
+
function fixedValueListShortCut(anno) {
|
|
123
121
|
if (anno === '@Common.ValueList.entity' ||
|
|
124
122
|
anno === '@Common.ValueList.viaAssociation') {
|
|
125
123
|
|
|
@@ -146,12 +144,12 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
146
144
|
|
|
147
145
|
if (anno === '@Common.ValueList.viaAssociation') {
|
|
148
146
|
// value is expected to be an expression, namely the path to an association of the carrier entity
|
|
149
|
-
|
|
147
|
+
const assocName = carrier['@Common.ValueList.viaAssociation']['='];
|
|
150
148
|
if (!assocName) {
|
|
151
149
|
message('odata-anno-preproc', [...location, anno], { anno, '#': 'viaassoc' });
|
|
152
150
|
return false;
|
|
153
151
|
}
|
|
154
|
-
|
|
152
|
+
const assoc = csn.definitions[defName].elements[assocName];
|
|
155
153
|
if (!assoc || !assoc.target) {
|
|
156
154
|
message('odata-anno-preproc', [...location, anno], { anno, id: assocName, '#': 'noassoc' });
|
|
157
155
|
return false;
|
|
@@ -164,26 +162,25 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
164
162
|
// if both annotations are present, ignore 'entity' and raise a message
|
|
165
163
|
if (annoNames.map(x=>x.split('#')[0]).find(x=>(x==='@Common.ValueList.viaAssociation'))) {
|
|
166
164
|
message('odata-anno-preproc', [...location, anno],
|
|
167
|
-
{
|
|
168
|
-
name: '@Common.ValueList.entity', anno: '@Common.ValueList',
|
|
165
|
+
{
|
|
166
|
+
name: '@Common.ValueList.entity', anno: '@Common.ValueList',
|
|
169
167
|
value: 'entity', code: 'viaAssociation', '#': 'vallistignored'
|
|
170
168
|
});
|
|
171
169
|
return false;
|
|
172
170
|
}
|
|
173
171
|
|
|
174
|
-
|
|
172
|
+
const annoVal = carrier['@Common.ValueList.entity']; // name of value list entity
|
|
175
173
|
if (annoVal['=']) {
|
|
176
174
|
message('odata-anno-preproc', [...location, anno], { anno, '#': 'notastring' },
|
|
177
175
|
);
|
|
178
176
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
// mocha test has no whatsMySchemaName
|
|
178
|
+
const schemaName = options.whatsMySchemaName && options.whatsMySchemaName(defName) || serviceName
|
|
182
179
|
enameShort = annoVal['='] || annoVal;
|
|
183
|
-
enameFull =
|
|
180
|
+
enameFull = schemaName + '.' + enameShort;
|
|
184
181
|
}
|
|
185
182
|
|
|
186
|
-
|
|
183
|
+
const vlEntity = csn.definitions[enameFull]; // (object) value list entity
|
|
187
184
|
if (!vlEntity) {
|
|
188
185
|
message('odata-anno-preproc', [...location, anno ], { anno, id: enameFull, '#': 'notexist' });
|
|
189
186
|
return false;
|
|
@@ -191,7 +188,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
191
188
|
|
|
192
189
|
// label
|
|
193
190
|
// explicitly provided label wins
|
|
194
|
-
|
|
191
|
+
const label = carrier['@Common.ValueList.Label'] ||
|
|
195
192
|
carrier['@Common.Label'] || vlEntity['@Common.Label'] || enameShort;
|
|
196
193
|
|
|
197
194
|
// localDataProp
|
|
@@ -199,14 +196,14 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
199
196
|
// if this is a managed assoc, use fk field instead (if there is a single one)
|
|
200
197
|
let localDataProp = carrierName.split('/').pop();
|
|
201
198
|
if (carrier.target && carrier.on === undefined) {
|
|
202
|
-
localDataProp = localDataProp + fkSeparator +
|
|
199
|
+
localDataProp = localDataProp + fkSeparator +
|
|
203
200
|
getKeyOfTargetOfManagedAssoc(anno, carrier);
|
|
204
201
|
}
|
|
205
202
|
|
|
206
203
|
// if this carrier is a generated foreign key field and the association is marked @cds.api.ignore
|
|
207
204
|
// rename the localDataProp to be 'assocName/key'
|
|
208
205
|
if(carrier['@cds.api.ignore']) {
|
|
209
|
-
|
|
206
|
+
const assocName = carrier['@odata.foreignKey4'];
|
|
210
207
|
if(assocName && options.isV4()) {
|
|
211
208
|
localDataProp = localDataProp.replace(assocName+fkSeparator, assocName+'/');
|
|
212
209
|
}
|
|
@@ -215,7 +212,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
215
212
|
// valueListProp: the (single) key field of the value list entity
|
|
216
213
|
// if no key or multiple keys -> message
|
|
217
214
|
let valueListProp = null;
|
|
218
|
-
|
|
215
|
+
const keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key && !vlEntity.elements[x].target );
|
|
219
216
|
if (keys.length === 0) {
|
|
220
217
|
message('odata-anno-preproc', [...location, anno], { anno, name: enameFull, '#': 'vhlnokey' });
|
|
221
218
|
return false;
|
|
@@ -231,13 +228,13 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
231
228
|
// OR
|
|
232
229
|
// the (single) non-key string field, if there is one
|
|
233
230
|
let textField = null;
|
|
234
|
-
|
|
231
|
+
const Identification = vlEntity['@UI.Identification'];
|
|
235
232
|
if (Identification && Identification[0] && Identification[0]['=']) {
|
|
236
233
|
textField = Identification[0]['='];
|
|
237
234
|
} else if (Identification && Identification[0] && Identification[0]['Value'] && Identification[0]['Value']['=']) {
|
|
238
235
|
textField = Identification[0]['Value']['='];
|
|
239
236
|
} else {
|
|
240
|
-
|
|
237
|
+
const stringFields = Object.keys(vlEntity.elements).filter(
|
|
241
238
|
x => !vlEntity.elements[x].key && vlEntity.elements[x].type === 'cds.String')
|
|
242
239
|
if (stringFields.length === 1)
|
|
243
240
|
textField = stringFields[0];
|
|
@@ -259,7 +256,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
259
256
|
}
|
|
260
257
|
}
|
|
261
258
|
|
|
262
|
-
|
|
259
|
+
const newObj = Object.create( Object.getPrototypeOf(carrier) );
|
|
263
260
|
Object.keys(carrier).forEach( e => {
|
|
264
261
|
if (e === '@Common.ValueList.entity' || e === '@Common.ValueList.viaAssociation') {
|
|
265
262
|
newObj['@Common.ValueList.Label'] = label;
|
|
@@ -289,7 +286,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
289
286
|
}
|
|
290
287
|
}
|
|
291
288
|
|
|
292
|
-
function textArrangementReordering(
|
|
289
|
+
function textArrangementReordering(aName, aNameWithoutQualifier) {
|
|
293
290
|
if (aNameWithoutQualifier === '@Common.TextArrangement') {
|
|
294
291
|
let value = carrier[aName];
|
|
295
292
|
let textAnno = carrier['@Common.Text'];
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -27,6 +27,7 @@ function csn2edm(_csn, serviceName, _options) {
|
|
|
27
27
|
function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
28
28
|
// get us a fresh model copy that we can work with
|
|
29
29
|
const csn = cloneCsnNonDict(_csn, _options);
|
|
30
|
+
const special$self = !csn?.definitions?.$self && '$self';
|
|
30
31
|
|
|
31
32
|
// use original options for messages; cloned CSN for semantic location
|
|
32
33
|
const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
|
|
@@ -459,7 +460,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
459
460
|
|
|
460
461
|
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
|
|
461
462
|
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
|
|
462
|
-
else if (options.isV2() && /^(_
|
|
463
|
+
else if (options.isV2() && /^(_|\d)/.test(p._edmAttributes.Name)) {
|
|
463
464
|
// FIXME: Rewrite signalIllegalIdentifier function to be more flexible
|
|
464
465
|
message('odata-spec-violation-id', pLoc,
|
|
465
466
|
{ prop: p._edmAttributes.Name[0], id: p._edmAttributes.Name, version: '2.0', '#': 'v2firstchar' });
|
|
@@ -662,19 +663,59 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
662
663
|
const actionNode = (iAmAnAction) ? new Edm.Action(v, attributes)
|
|
663
664
|
: new Edm.FunctionDefinition(v, attributes);
|
|
664
665
|
|
|
666
|
+
const bpType = entityCsn ? fullQualified(entityCsn.name) : undefined;
|
|
667
|
+
/*
|
|
668
|
+
Check for binding $self parameter. If available, use this parameter
|
|
669
|
+
instead of artifically created binding parameter (hasBindingParameter).
|
|
670
|
+
The binding parameter remains in the CSN and is rendered as any other
|
|
671
|
+
parameter (including default value/not null/ etc) and acts as annotation carrier.
|
|
672
|
+
*/
|
|
673
|
+
setProp(actionCsn, '$hasBindingParam', false);
|
|
674
|
+
if(actionCsn.params) {
|
|
675
|
+
const entries = Object.entries(actionCsn.params);
|
|
676
|
+
const firstParam = entries[0][1];
|
|
677
|
+
const type = firstParam?.items?.type || firstParam?.type;
|
|
678
|
+
if(type === special$self) {
|
|
679
|
+
actionCsn.$hasBindingParam = true;
|
|
680
|
+
const bpName = entries[0][0];
|
|
681
|
+
if(bpType) {
|
|
682
|
+
if(firstParam.items?.type)
|
|
683
|
+
firstParam.items.type = bpType;
|
|
684
|
+
if(firstParam.type)
|
|
685
|
+
firstParam.type = bpType;
|
|
686
|
+
}
|
|
687
|
+
if(!edmUtils.isODataSimpleIdentifier(bpName))
|
|
688
|
+
message('odata-spec-violation-id', [ ...loc, 'params', bpName ], { id: bpName });
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
665
692
|
// bpName is eventually used later for EntitySetPath
|
|
666
693
|
const bpNameAnno = actionCsn['@cds.odata.bindingparameter.name'];
|
|
667
|
-
|
|
668
|
-
|
|
694
|
+
let bpName = 'in';
|
|
695
|
+
if(bpNameAnno != null) {
|
|
696
|
+
if(typeof bpNameAnno === 'string')
|
|
697
|
+
bpName = bpNameAnno;
|
|
698
|
+
if(typeof bpNameAnno === 'object' && bpNameAnno['='])
|
|
699
|
+
bpName = bpNameAnno['='];
|
|
700
|
+
}
|
|
701
|
+
// No explicit binding parameter, check (user defined) annotation value)
|
|
702
|
+
if(!actionCsn.$hasBindingParam) {
|
|
703
|
+
if(!edmUtils.isODataSimpleIdentifier(bpName))
|
|
704
|
+
message('odata-spec-violation-id', [...loc, '@cds.odata.bindingparameter.name'], { id: bpName });
|
|
705
|
+
if(actionCsn.params && actionCsn.params[bpName]) {
|
|
706
|
+
error('duplicate-definition', [...loc, '@cds.odata.bindingparameter.name'], { '#': 'param', name: bpName });
|
|
707
|
+
}
|
|
708
|
+
}
|
|
669
709
|
if(entityCsn != undefined)
|
|
670
710
|
{
|
|
671
711
|
actionNode.setEdmAttribute('IsBound', true);
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
712
|
+
if(!actionCsn.$hasBindingParam) {
|
|
713
|
+
// Binding Parameter: 'in' at first position in sequence, this is decisive!
|
|
714
|
+
if(actionCsn['@cds.odata.bindingparameter.collection'])
|
|
715
|
+
actionNode.append(new Edm.Parameter(v, { Name: bpName, Type: bpType, Collection:true/*, Nullable: false*/ } ));
|
|
716
|
+
else
|
|
677
717
|
actionNode.append(new Edm.Parameter(v, { Name: bpName, Type: bpType } ));
|
|
718
|
+
}
|
|
678
719
|
}
|
|
679
720
|
else if(EntityContainer)// unbound => produce Action/FunctionImport
|
|
680
721
|
{
|
|
@@ -788,25 +829,33 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
788
829
|
// V2 XML: Parameters that are not explicitly marked as Nullable or NotNullable in the CSN must become Nullable=true
|
|
789
830
|
// V2 XML spec does only mention default Nullable=true for Properties not for Parameters so omitting Nullable=true let
|
|
790
831
|
// the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
|
|
791
|
-
actionCsn.params && Object.entries(actionCsn.params).forEach(([parameterName, parameterCsn]) => {
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
832
|
+
actionCsn.params && Object.entries(actionCsn.params).forEach(([parameterName, parameterCsn], i) => {
|
|
833
|
+
const type = parameterCsn?.items?.type || parameterCsn?.type;
|
|
834
|
+
if(i === 0 && type === special$self) {
|
|
835
|
+
// skip and remove the first parameter if it is a $self binding parameter to
|
|
836
|
+
// omit annotation rendering later on
|
|
837
|
+
delete actionCsn.params[parameterName];
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
const pLoc = [...loc, 'params', parameterName];
|
|
841
|
+
const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
|
|
842
|
+
collectUsedType(parameterCsn);
|
|
843
|
+
edmTypeCompatibilityCheck(param, pLoc);
|
|
844
|
+
if(!edmUtils.isODataSimpleIdentifier(parameterName))
|
|
845
|
+
message('odata-spec-violation-id', pLoc, { id: parameterName });
|
|
798
846
|
|
|
799
847
|
// only scalar or structured type in V2 (not entity)
|
|
800
|
-
|
|
848
|
+
if(param._type &&
|
|
801
849
|
!param._type.startsWith('Edm.') &&
|
|
802
850
|
csn.definitions[param._type] &&
|
|
803
851
|
!edmUtils.isStructuredType(csn.definitions[param._type]))
|
|
804
|
-
|
|
852
|
+
message('odata-spec-violation-param', pLoc, { version: '2.0' });
|
|
805
853
|
|
|
806
|
-
|
|
807
|
-
|
|
854
|
+
if(param._isCollection)
|
|
855
|
+
message('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
808
856
|
|
|
809
|
-
|
|
857
|
+
functionImport.append(param);
|
|
858
|
+
}
|
|
810
859
|
});
|
|
811
860
|
|
|
812
861
|
if(EntityContainer)
|
|
@@ -995,7 +1044,6 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
995
1044
|
}
|
|
996
1045
|
edm._service._schemas[targetSchema]._annotations.push(anno);
|
|
997
1046
|
});
|
|
998
|
-
annos = [];
|
|
999
1047
|
|
|
1000
1048
|
// create service cross reference and merge it into xServiceRefs
|
|
1001
1049
|
xrefs.forEach(xr => {
|
package/lib/edm/edm.js
CHANGED
|
@@ -1380,8 +1380,8 @@ function getEdm(options, messageFunctions) {
|
|
|
1380
1380
|
{
|
|
1381
1381
|
toJSONattributes(json)
|
|
1382
1382
|
{
|
|
1383
|
-
if(this.
|
|
1384
|
-
json['@type'] = this.
|
|
1383
|
+
if(this._jsonOnlyAttributes.Type)
|
|
1384
|
+
json['@type'] = this._jsonOnlyAttributes.Type;
|
|
1385
1385
|
let keys = Object.keys(this._edmAttributes).filter(k => k !== 'Type');
|
|
1386
1386
|
for(const key of keys)
|
|
1387
1387
|
json['$'+key] = this._edmAttributes[key];
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { setProp, isBetaEnabled } = require('../base/model');
|
|
4
|
-
const {
|
|
5
|
-
forEachDefinition, forEachMemberRecursively,
|
|
4
|
+
const {
|
|
5
|
+
forEachDefinition, forEachMemberRecursively,
|
|
6
6
|
} = require('../model/csnUtils');
|
|
7
7
|
|
|
8
8
|
// eslint-disable-next-line no-unused-vars
|
|
9
|
-
function resolveForeignKeyRefs(csn) {
|
|
10
|
-
const csnUtils = getUtils(csn);
|
|
9
|
+
function resolveForeignKeyRefs(csn, csnUtils) {
|
|
11
10
|
forEachDefinition(csn, (def, defName) => {
|
|
12
11
|
let currPath = ['definitions', defName ];
|
|
13
12
|
forEachMemberRecursively(def, (construct, _constructName, _prop, path) => {
|
|
@@ -21,9 +20,8 @@ function resolveForeignKeyRefs(csn) {
|
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
function inboundQualificationChecks(csn, options, messageFunctions,
|
|
25
|
-
serviceRootNames, requestedServiceNames, isMyServiceRequested, whatsMyServiceRootName) {
|
|
26
|
-
const csnUtils = getUtils(csn);
|
|
23
|
+
function inboundQualificationChecks(csn, options, messageFunctions,
|
|
24
|
+
serviceRootNames, requestedServiceNames, isMyServiceRequested, whatsMyServiceRootName, csnUtils) {
|
|
27
25
|
const { message, throwWithError } = messageFunctions;
|
|
28
26
|
|
|
29
27
|
forEachDefinition(csn, [ attach$path, checkChainedArray ]);
|
|
@@ -82,4 +80,4 @@ function inboundQualificationChecks(csn, options, messageFunctions,
|
|
|
82
80
|
}
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
module.exports = { inboundQualificationChecks }
|
|
83
|
+
module.exports = { inboundQualificationChecks }
|