@sap/cds-compiler 3.8.2 → 3.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/bin/cdsc.js +2 -2
- package/doc/CHANGELOG_BETA.md +26 -5
- package/lib/api/.eslintrc.json +3 -2
- package/lib/api/options.js +3 -1
- package/lib/api/validate.js +1 -1
- package/lib/base/message-registry.js +28 -19
- package/lib/base/messages.js +6 -1
- package/lib/base/model.js +2 -2
- package/lib/checks/.eslintrc.json +1 -0
- package/lib/checks/actionsFunctions.js +6 -6
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/elements.js +28 -17
- package/lib/checks/foreignKeys.js +1 -1
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/onConditions.js +11 -6
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/types.js +1 -1
- package/lib/checks/utils.js +1 -1
- package/lib/checks/validator.js +3 -2
- package/lib/compiler/assert-consistency.js +7 -2
- package/lib/compiler/base.js +8 -4
- package/lib/compiler/builtins.js +7 -0
- package/lib/compiler/checks.js +73 -6
- package/lib/compiler/define.js +10 -5
- package/lib/compiler/extend.js +910 -1711
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +838 -0
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +20 -8
- package/lib/compiler/resolve.js +3 -3
- package/lib/compiler/shared.js +3 -1
- package/lib/edm/annotations/genericTranslation.js +18 -8
- package/lib/edm/csn2edm.js +14 -14
- package/lib/edm/edm.js +25 -11
- package/lib/edm/edmPreprocessor.js +47 -23
- package/lib/edm/edmUtils.js +37 -9
- package/lib/gen/Dictionary.json +5 -7
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/language.tokens +24 -23
- package/lib/gen/languageLexer.interp +4 -1
- package/lib/gen/languageLexer.js +792 -784
- package/lib/gen/languageLexer.tokens +12 -11
- package/lib/gen/languageParser.js +3564 -3493
- package/lib/json/from-csn.js +28 -6
- package/lib/json/to-csn.js +10 -6
- package/lib/language/antlrParser.js +11 -3
- package/lib/language/genericAntlrParser.js +2 -1
- package/lib/language/language.g4 +14 -3
- package/lib/model/csnRefs.js +10 -5
- package/lib/model/csnUtils.js +41 -76
- package/lib/modelCompare/utils/.eslintrc.json +1 -1
- package/lib/optionProcessor.js +7 -4
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/toCdl.js +244 -168
- package/lib/render/toHdbcds.js +18 -10
- package/lib/render/toSql.js +24 -2
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +11 -6
- package/lib/transform/db/flattening.js +22 -15
- package/lib/transform/db/rewriteCalculatedElements.js +50 -29
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/views.js +1 -1
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/draft/odata.js +3 -4
- package/lib/transform/forOdataNew.js +5 -6
- package/lib/transform/forRelationalDB.js +7 -7
- package/lib/transform/localized.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +6 -6
- package/lib/transform/odata/typesExposure.js +12 -3
- package/lib/transform/odata/utils.js +3 -0
- package/lib/transform/transformUtilsNew.js +11 -26
- package/lib/transform/translateAssocsToJoins.js +9 -9
- package/lib/transform/universalCsn/.eslintrc.json +3 -2
- package/lib/transform/universalCsn/coreComputed.js +1 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +6 -4
- package/lib/utils/file.js +3 -3
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +1 -1
package/lib/compiler/index.js
CHANGED
|
@@ -24,6 +24,7 @@ const { fns } = require('./shared');
|
|
|
24
24
|
const define = require('./define');
|
|
25
25
|
const finalizeParseCdl = require('./finalize-parse-cdl');
|
|
26
26
|
const extend = require('./extend');
|
|
27
|
+
const generate = require('./generate');
|
|
27
28
|
const kickStart = require('./kick-start');
|
|
28
29
|
const populate = require('./populate');
|
|
29
30
|
const resolve = require('./resolve');
|
|
@@ -463,6 +464,7 @@ function compileDoX( model ) {
|
|
|
463
464
|
return model;
|
|
464
465
|
}
|
|
465
466
|
extend( model );
|
|
467
|
+
generate( model );
|
|
466
468
|
kickStart( model );
|
|
467
469
|
populate( model );
|
|
468
470
|
|
package/lib/compiler/populate.js
CHANGED
|
@@ -61,7 +61,7 @@ function populate( model ) {
|
|
|
61
61
|
resolvePath,
|
|
62
62
|
attachAndEmitValidNames,
|
|
63
63
|
initArtifact,
|
|
64
|
-
|
|
64
|
+
extendArtifactBefore,
|
|
65
65
|
extendArtifactAfter,
|
|
66
66
|
} = model.$functions;
|
|
67
67
|
model.$volatileFunctions.environment = environment;
|
|
@@ -213,7 +213,7 @@ function populate( model ) {
|
|
|
213
213
|
chain.reverse();
|
|
214
214
|
for (const a of chain) {
|
|
215
215
|
// Without type, value.path or _origin at beginning, link to itself:
|
|
216
|
-
|
|
216
|
+
extendArtifactBefore( a );
|
|
217
217
|
art = populateArtifact( a, art ) || a;
|
|
218
218
|
if (a.elements$ || a.enum$)
|
|
219
219
|
mergeSpecifiedElementsOrEnum( a );
|
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
forEachMember,
|
|
14
14
|
forEachGeneric,
|
|
15
15
|
isDeprecatedEnabled,
|
|
16
|
+
isBetaEnabled,
|
|
16
17
|
} = require( '../base/model');
|
|
17
18
|
const {
|
|
18
19
|
setLink,
|
|
@@ -42,9 +43,9 @@ function propagate( model ) {
|
|
|
42
43
|
'@cds.external': never,
|
|
43
44
|
'@cds.redirection.target': never,
|
|
44
45
|
'@fiori.draft.enabled': onlyViaArtifact,
|
|
45
|
-
'@': annotation, // always except in '
|
|
46
|
-
doc: annotation, // always except in '
|
|
47
|
-
default: withKind, // always except in '
|
|
46
|
+
'@': annotation, // always except in 'items'
|
|
47
|
+
doc: annotation, // always except in 'items'
|
|
48
|
+
default: withKind, // always except in 'items'
|
|
48
49
|
virtual,
|
|
49
50
|
notNull,
|
|
50
51
|
targetElement: onlyViaParent, // in foreign keys
|
|
@@ -72,10 +73,11 @@ function propagate( model ) {
|
|
|
72
73
|
const { options } = model;
|
|
73
74
|
// eslint-disable-next-line max-len
|
|
74
75
|
const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
|
|
76
|
+
const { warning, throwWithError } = model.$messageFunctions;
|
|
77
|
+
const propagateToReturns = isBetaEnabled( options, 'v4preview' );
|
|
75
78
|
|
|
76
79
|
forEachDefinition( model, run );
|
|
77
80
|
|
|
78
|
-
const { warning, throwWithError } = model.$messageFunctions;
|
|
79
81
|
// TODO: move 'virtual' handling/checks to resolver if
|
|
80
82
|
// 'deprecated.oldVirtualNotNullPropagation' is gone
|
|
81
83
|
if (!oldVirtualNotNullPropagation) // check would always be right, but to be ultra compatible…
|
|
@@ -105,6 +107,14 @@ function propagate( model ) {
|
|
|
105
107
|
chain.push({ target, source: target.value._artifact });
|
|
106
108
|
if (checkAndSetStatus( target.value._artifact ))
|
|
107
109
|
news.push(target.value._artifact);
|
|
110
|
+
|
|
111
|
+
if (target.value?._artifact.$inferred !== 'include') {
|
|
112
|
+
// If the referred to element is not inferred, it is a new one and not the original.
|
|
113
|
+
// The new one was not originally referred to => error;
|
|
114
|
+
warning( 'ref-unexpected-override', [ target.name.location, target ],
|
|
115
|
+
{ id: target.name.id, target: target.value?._artifact },
|
|
116
|
+
'Calculated element $(ID) does not originally refer to $(TARGET)' );
|
|
117
|
+
}
|
|
108
118
|
}
|
|
109
119
|
chain.push( { target, source: origin } );
|
|
110
120
|
if (checkAndSetStatus( origin ))
|
|
@@ -267,14 +277,16 @@ function propagate( model ) {
|
|
|
267
277
|
|
|
268
278
|
function onlyViaArtifact( prop, target, source ) {
|
|
269
279
|
const from = viewFromPrimary( target )?.path;
|
|
270
|
-
// do not propagate from member / if follow assoc in from
|
|
271
|
-
if (!(from ? from[from.length - 1]._artifact : source)._main
|
|
280
|
+
// do not propagate from member / if follow assoc in from or into `returns` of actions (v4)
|
|
281
|
+
if (!(from ? from[from.length - 1]._artifact : source)._main &&
|
|
282
|
+
!(propagateToReturns && target._parent && target._parent.returns === target))
|
|
272
283
|
annotation( prop, target, source );
|
|
273
284
|
}
|
|
274
285
|
|
|
275
286
|
function withKind( prop, target, source ) {
|
|
276
|
-
if (target.kind &&
|
|
277
|
-
|
|
287
|
+
if (target.kind &&
|
|
288
|
+
(propagateToReturns || !target._parent || target._parent.returns !== target))
|
|
289
|
+
always(prop, target, source); // not in 'items'
|
|
278
290
|
}
|
|
279
291
|
|
|
280
292
|
function notNull( prop, target, source, viaType ) {
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -81,7 +81,7 @@ function resolve( model ) {
|
|
|
81
81
|
} = model.$messageFunctions;
|
|
82
82
|
const {
|
|
83
83
|
resolvePath,
|
|
84
|
-
|
|
84
|
+
createRemainingAnnotateStatements,
|
|
85
85
|
effectiveType,
|
|
86
86
|
getOrigin,
|
|
87
87
|
resolveType,
|
|
@@ -117,7 +117,7 @@ function resolve( model ) {
|
|
|
117
117
|
forEachDefinition( model, resolveRefs );
|
|
118
118
|
forEachGeneric( model, 'vocabularies', resolveRefs );
|
|
119
119
|
// create “super” ANNOTATE statements for annotations on unknown artifacts:
|
|
120
|
-
|
|
120
|
+
createRemainingAnnotateStatements();
|
|
121
121
|
// report cyclic dependencies:
|
|
122
122
|
detectCycles( model.definitions, ( user, art, location ) => {
|
|
123
123
|
if (location) {
|
|
@@ -1128,7 +1128,7 @@ function pathNavigation( ref ) {
|
|
|
1128
1128
|
return { item, tableAlias: root };
|
|
1129
1129
|
if (root.kind !== '$tableAlias' || ref.path.length < 2)
|
|
1130
1130
|
return {}; // should not happen
|
|
1131
|
-
return { navigation: root.elements[item.id], item, tableAlias: root };
|
|
1131
|
+
return { navigation: root.elements?.[item.id], item, tableAlias: root };
|
|
1132
1132
|
}
|
|
1133
1133
|
|
|
1134
1134
|
module.exports = resolve;
|
package/lib/compiler/shared.js
CHANGED
|
@@ -117,7 +117,9 @@ function fns( model ) {
|
|
|
117
117
|
typeOf: { next: '_$next', dollar: true }, // TODO: disallow in var
|
|
118
118
|
// TODO: dep for (explicit+implicit!) foreign keys
|
|
119
119
|
targetElement: { next: '__none_', assoc: false, dollar: false },
|
|
120
|
-
filter: {
|
|
120
|
+
filter: {
|
|
121
|
+
next: '_$next', lexical: 'main', dollar: 'none', escape: 'param',
|
|
122
|
+
},
|
|
121
123
|
default: {
|
|
122
124
|
next: '_$next',
|
|
123
125
|
dollar: true,
|
|
@@ -416,9 +416,19 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
416
416
|
function relParList() {
|
|
417
417
|
// we rely on the order of params in the csn being the correct one
|
|
418
418
|
const params = [];
|
|
419
|
-
if (entityNameIfBound
|
|
420
|
-
|
|
419
|
+
if (entityNameIfBound) {
|
|
420
|
+
// If this is an action and has an explicit binding parameter add it here
|
|
421
|
+
if(cAction.$bindingParam && cAction.kind === 'action') {
|
|
422
|
+
params.push(cAction.$bindingParam.items ? 'Collection(' + entityNameIfBound + ')' : entityNameIfBound);
|
|
423
|
+
}
|
|
424
|
+
// If action/function has no explicit binding parameter add it here
|
|
425
|
+
else if (!cAction.$bindingParam) {
|
|
426
|
+
params.push(cAction['@cds.odata.bindingparameter.collection'] ? 'Collection(' + entityNameIfBound + ')' : entityNameIfBound);
|
|
427
|
+
}
|
|
421
428
|
}
|
|
429
|
+
// In case this is a function the explicit binding parameter is part of
|
|
430
|
+
// the functions params dictionary. Only for functions all parameters must
|
|
431
|
+
// be listed in the annotation target
|
|
422
432
|
if (cAction.kind === 'function') {
|
|
423
433
|
if(cAction.params) {
|
|
424
434
|
cAction.params && Object.values(cAction.params).forEach(p => {
|
|
@@ -1821,10 +1831,10 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1821
1831
|
|
|
1822
1832
|
}
|
|
1823
1833
|
|
|
1824
|
-
function
|
|
1825
|
-
/* Merge options.
|
|
1834
|
+
function mergeOdataVocabularies(options, message) {
|
|
1835
|
+
/* Merge options.odataVocabularies into vocabularyDefinitions and
|
|
1826
1836
|
create a csn2edm stack local dictionary.
|
|
1827
|
-
|
|
1837
|
+
odataVocabularies is an object, each property is the
|
|
1828
1838
|
annotation prefix (as in mergedVocDefs), the value
|
|
1829
1839
|
is an object { Alias, Namespace, Uri }, this way
|
|
1830
1840
|
the definitions are unique and duplicate entries to address
|
|
@@ -1833,8 +1843,8 @@ function mergeVocRefs(options, message) {
|
|
|
1833
1843
|
*/
|
|
1834
1844
|
const mergedVocDefs = Object.assign({}, vocabularyDefinitions);
|
|
1835
1845
|
const reqProps = ['Alias', 'Namespace', 'Uri'];
|
|
1836
|
-
if(options.
|
|
1837
|
-
const vocRefs = options.
|
|
1846
|
+
if(options.odataVocabularies) {
|
|
1847
|
+
const vocRefs = options.odataVocabularies;
|
|
1838
1848
|
if (typeof vocRefs === 'object' && !Array.isArray(vocRefs)) {
|
|
1839
1849
|
Object.entries(vocRefs).forEach(([id, def]) => {
|
|
1840
1850
|
let defOk = true;
|
|
@@ -1881,4 +1891,4 @@ function mergeVocRefs(options, message) {
|
|
|
1881
1891
|
//-------------------------------------------------------------------------------------------------
|
|
1882
1892
|
//-------------------------------------------------------------------------------------------------
|
|
1883
1893
|
|
|
1884
|
-
module.exports = { vocabularyDefinitions, csn2annotationEdm,
|
|
1894
|
+
module.exports = { vocabularyDefinitions, csn2annotationEdm, mergeOdataVocabularies };
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -52,7 +52,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
52
52
|
fallBackSchemaName,
|
|
53
53
|
options ] = initializeModel(csn, _options, messageFunctions, serviceNames);
|
|
54
54
|
|
|
55
|
-
const mergedVocabularies = translate.
|
|
55
|
+
const mergedVocabularies = translate.mergeOdataVocabularies(options, message);
|
|
56
56
|
|
|
57
57
|
const Edm = getEdm(options, messageFunctions);
|
|
58
58
|
|
|
@@ -687,14 +687,15 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
687
687
|
The binding parameter remains in the CSN and is rendered as any other
|
|
688
688
|
parameter (including default value/not null/ etc) and acts as annotation carrier.
|
|
689
689
|
*/
|
|
690
|
-
|
|
690
|
+
|
|
691
|
+
let bpName = 'in';
|
|
691
692
|
if(actionCsn.params) {
|
|
692
693
|
const entries = Object.entries(actionCsn.params);
|
|
693
694
|
const firstParam = entries[0][1];
|
|
694
695
|
const type = firstParam?.items?.type || firstParam?.type;
|
|
695
696
|
if(type === special$self) {
|
|
696
|
-
|
|
697
|
-
|
|
697
|
+
bpName = entries[0][0];
|
|
698
|
+
setProp(actionCsn, '$bindingParam', firstParam);
|
|
698
699
|
if(bpType) {
|
|
699
700
|
if(firstParam.items?.type)
|
|
700
701
|
firstParam.items.type = bpType;
|
|
@@ -707,16 +708,15 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
707
708
|
}
|
|
708
709
|
|
|
709
710
|
// bpName is eventually used later for EntitySetPath
|
|
710
|
-
const bpNameAnno = actionCsn['@cds.odata.bindingparameter.name'];
|
|
711
|
-
let bpName = 'in';
|
|
712
|
-
if(bpNameAnno != null) {
|
|
713
|
-
if(typeof bpNameAnno === 'string')
|
|
714
|
-
bpName = bpNameAnno;
|
|
715
|
-
if(typeof bpNameAnno === 'object' && bpNameAnno['='])
|
|
716
|
-
bpName = bpNameAnno['='];
|
|
717
|
-
}
|
|
718
711
|
// No explicit binding parameter, check (user defined) annotation value)
|
|
719
|
-
if(!actionCsn.$
|
|
712
|
+
if(!actionCsn.$bindingParam) {
|
|
713
|
+
const bpNameAnno = actionCsn['@cds.odata.bindingparameter.name'];
|
|
714
|
+
if(bpNameAnno != null) {
|
|
715
|
+
if(typeof bpNameAnno === 'string')
|
|
716
|
+
bpName = bpNameAnno;
|
|
717
|
+
if(typeof bpNameAnno === 'object' && bpNameAnno['='])
|
|
718
|
+
bpName = bpNameAnno['='];
|
|
719
|
+
}
|
|
720
720
|
if(!edmUtils.isODataSimpleIdentifier(bpName))
|
|
721
721
|
message('odata-spec-violation-id', [...loc, '@cds.odata.bindingparameter.name'], { id: bpName });
|
|
722
722
|
if(actionCsn.params && actionCsn.params[bpName]) {
|
|
@@ -726,7 +726,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
726
726
|
if(entityCsn != undefined)
|
|
727
727
|
{
|
|
728
728
|
actionNode.setEdmAttribute('IsBound', true);
|
|
729
|
-
if(!actionCsn.$
|
|
729
|
+
if(!actionCsn.$bindingParam) {
|
|
730
730
|
// Binding Parameter: 'in' at first position in sequence, this is decisive!
|
|
731
731
|
if(actionCsn['@cds.odata.bindingparameter.collection'])
|
|
732
732
|
actionNode.append(new Edm.Parameter(v, { Name: bpName, Type: bpType, Collection:true/*, Nullable: false*/ } ));
|
package/lib/edm/edm.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const edmUtils = require('./edmUtils.js');
|
|
4
4
|
const { isBuiltinType } = require('../model/csnUtils.js');
|
|
5
5
|
const { forEach } = require('../utils/objectUtils');
|
|
6
|
-
const { isBetaEnabled } = require('../base/model.js');
|
|
7
6
|
|
|
8
7
|
// facet definitions, optional could either be true or array of edm types
|
|
9
8
|
// remove indicates wether or not the canonic facet shall be removed when applying @odata.Type
|
|
@@ -766,7 +765,7 @@ function getEdm(options, messageFunctions) {
|
|
|
766
765
|
class ComplexType extends TypeBase {
|
|
767
766
|
constructor(v, details, csn) {
|
|
768
767
|
super(v, details, csn);
|
|
769
|
-
if(this.v4 && !!csn['@open']
|
|
768
|
+
if(this.v4 && !!csn['@open']) {
|
|
770
769
|
this._edmAttributes['OpenType'] = true;
|
|
771
770
|
}
|
|
772
771
|
}
|
|
@@ -813,6 +812,13 @@ function getEdm(options, messageFunctions) {
|
|
|
813
812
|
this._keys = new Key(v, csn.$edmKeyPaths);
|
|
814
813
|
else
|
|
815
814
|
this._keys = undefined;
|
|
815
|
+
|
|
816
|
+
if(options.odataOpenapiHints) {
|
|
817
|
+
if(csn['@cds.autoexpose'])
|
|
818
|
+
this.setJSON({ '@cds.autoexpose': true });
|
|
819
|
+
if(csn['@cds.autoexposed'])
|
|
820
|
+
this.setJSON({ '@cds.autoexposed': true });
|
|
821
|
+
}
|
|
816
822
|
}
|
|
817
823
|
|
|
818
824
|
innerXML(indent)
|
|
@@ -826,6 +832,9 @@ function getEdm(options, messageFunctions) {
|
|
|
826
832
|
toJSONattributes(json)
|
|
827
833
|
{
|
|
828
834
|
super.toJSONattributes(json);
|
|
835
|
+
this._jsonOnlyAttributes && Object.entries(this._jsonOnlyAttributes).forEach(([p, v]) => {
|
|
836
|
+
json[p[0] === '@' ? p : '$' + p] = v;
|
|
837
|
+
});
|
|
829
838
|
if(this._keys)
|
|
830
839
|
{
|
|
831
840
|
json['$Key'] = this._keys.toJSON();
|
|
@@ -1082,7 +1091,6 @@ function getEdm(options, messageFunctions) {
|
|
|
1082
1091
|
|
|
1083
1092
|
let [src, tgt] = edmUtils.determineMultiplicity(csn._constraints._partnerCsn || csn);
|
|
1084
1093
|
csn._constraints._multiplicity = csn._constraints._partnerCsn ? [tgt, src] : [src, tgt];
|
|
1085
|
-
|
|
1086
1094
|
this._type = attributes.Type;
|
|
1087
1095
|
this._isCollection = this.isToMany();
|
|
1088
1096
|
this._targetCsn = csn._target;
|
|
@@ -1108,7 +1116,7 @@ function getEdm(options, messageFunctions) {
|
|
|
1108
1116
|
? csn._selfReferences[0]
|
|
1109
1117
|
: csn._constraints._partnerCsn
|
|
1110
1118
|
: undefined;
|
|
1111
|
-
if(partner && partner['@odata.navigable'] !== false) {
|
|
1119
|
+
if(partner && partner['@odata.navigable'] !== false && this._csn._edmParentCsn.kind !== 'type') {
|
|
1112
1120
|
// $abspath[0] is main entity
|
|
1113
1121
|
this._edmAttributes.Partner = partner.$abspath.slice(1).join(options.pathDelimiter);
|
|
1114
1122
|
}
|
|
@@ -1139,6 +1147,8 @@ function getEdm(options, messageFunctions) {
|
|
|
1139
1147
|
// store Nullable=false and evaluate in determineMultiplicity()
|
|
1140
1148
|
delete this._edmAttributes.Nullable;
|
|
1141
1149
|
}
|
|
1150
|
+
// A nav prop has no default value
|
|
1151
|
+
delete this._edmAttributes.DefaultValue;
|
|
1142
1152
|
|
|
1143
1153
|
// store NavProp reference in the model for bidirectional $Partner tagging (done in getReferentialConstraints())
|
|
1144
1154
|
csn._NavigationProperty = this;
|
|
@@ -1154,9 +1164,16 @@ function getEdm(options, messageFunctions) {
|
|
|
1154
1164
|
let nodeCsn = csn || this._csn;
|
|
1155
1165
|
// Set Nullable=false only if 'NOT NULL' was specified in the model
|
|
1156
1166
|
// Do not derive Nullable=false from key attribute.
|
|
1157
|
-
//
|
|
1158
|
-
// If
|
|
1159
|
-
|
|
1167
|
+
// OR if an association has cardinality.min > 0
|
|
1168
|
+
// If this is a backlink ($self = <from>.<to>) _partnerCsn.cardinality.srcmin > 0 if available
|
|
1169
|
+
// notNull is evaluated for non assoc elements only!
|
|
1170
|
+
// A managed association with unspecified cardinality that is to not null
|
|
1171
|
+
// is effectively a to-min-1 relationship as there must be a value for
|
|
1172
|
+
// the foreign keys (they are not null as well).
|
|
1173
|
+
// During the foreign key generation the minimum cardinality of such an association
|
|
1174
|
+
// is set to 1 as this property is available in the OData CSN.
|
|
1175
|
+
const tgtCard = edmUtils.getEffectiveTargetCardinality(nodeCsn);
|
|
1176
|
+
return (nodeCsn.notNull === true && !nodeCsn.target || tgtCard.min > 0);
|
|
1160
1177
|
}
|
|
1161
1178
|
isToMany() {
|
|
1162
1179
|
return (this._isCollection || this._csn._constraints._multiplicity[1] === '*');
|
|
@@ -1200,16 +1217,13 @@ function getEdm(options, messageFunctions) {
|
|
|
1200
1217
|
// V4 referential constraints!
|
|
1201
1218
|
addReferentialConstraintNodes()
|
|
1202
1219
|
{
|
|
1203
|
-
// flip the constrains if this is a $self partner
|
|
1204
1220
|
let _constraints = this._csn._constraints;
|
|
1205
|
-
let [i,j] = [0,1];
|
|
1206
1221
|
if(this._csn._constraints._partnerCsn) {
|
|
1207
1222
|
_constraints = this._csn._constraints._partnerCsn._constraints;
|
|
1208
|
-
[i,j] = [1,0];
|
|
1209
1223
|
}
|
|
1210
1224
|
_constraints.constraints && Object.values(_constraints.constraints).forEach(c =>
|
|
1211
1225
|
this.append(new ReferentialConstraint(this._v,
|
|
1212
|
-
{ Property: c[
|
|
1226
|
+
{ Property: c[0].join(options.pathDelimiter), ReferencedProperty: c[1].join(options.pathDelimiter) } ) )
|
|
1213
1227
|
);
|
|
1214
1228
|
}
|
|
1215
1229
|
}
|
|
@@ -4,7 +4,7 @@ const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model')
|
|
|
4
4
|
const {
|
|
5
5
|
forEachDefinition, forEachGeneric, forEachMemberRecursively,
|
|
6
6
|
isEdmPropertyRendered, getUtils, cloneCsnNonDict,
|
|
7
|
-
isBuiltinType, applyTransformations, cloneAnnotationValue
|
|
7
|
+
isBuiltinType, applyTransformations, cloneAnnotationValue, cardinality2str,
|
|
8
8
|
} = require('../model/csnUtils');
|
|
9
9
|
const edmUtils = require('./edmUtils.js');
|
|
10
10
|
const edmAnnoPreproc = require('./edmAnnoPreprocessor.js');
|
|
@@ -378,7 +378,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
378
378
|
// in V4 tag all compositions to be containments
|
|
379
379
|
if(options.odataContainment &&
|
|
380
380
|
options.isV4() &&
|
|
381
|
-
|
|
381
|
+
csnUtils.isComposition(element) &&
|
|
382
382
|
element['@odata.contained'] === undefined) {
|
|
383
383
|
element['@odata.contained'] = true;
|
|
384
384
|
}
|
|
@@ -862,28 +862,37 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
862
862
|
if (element.target && !element._ignore && element._constraints) {
|
|
863
863
|
edmUtils.finalizeReferentialConstraints(csn, element, options, info);
|
|
864
864
|
|
|
865
|
-
if(element._constraints
|
|
865
|
+
if(element._constraints?._partnerCsn) {
|
|
866
866
|
// if this is a partnership and this assoc has a set target cardinality, assign it as source cardinality to the partner
|
|
867
867
|
if(element._constraints._partnerCsn.cardinality) {
|
|
868
868
|
// if the forward association has set a src cardinality and it deviates from the backlink target cardinality raise a warning
|
|
869
869
|
// in V2 only, in V4 the source cardinality is rendered implicitly at the Type property
|
|
870
870
|
if(element._constraints._partnerCsn.cardinality.src) {
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
871
|
+
const srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
|
|
872
|
+
const newMult =
|
|
873
|
+
(element.cardinality?.min == 1 && element.cardinality?.max == 1)
|
|
874
|
+
? '1'
|
|
875
|
+
: (element.cardinality?.max === '*' || element.cardinality?.max > 1)
|
|
876
|
+
? '*'
|
|
877
|
+
: '0..1';
|
|
878
|
+
if(srcMult !== newMult) {
|
|
879
|
+
warning(null, element.$path, `Explicit source cardinality "${srcMult}" of "${element._constraints._partnerCsn._parent.name}/${element._constraints._partnerCsn.name}" conflicts with target cardinality "${newMult}"`);
|
|
876
880
|
}
|
|
877
881
|
}
|
|
878
882
|
else {
|
|
879
883
|
// .. but only if the original assoc hasn't set src yet
|
|
880
|
-
element._constraints._partnerCsn.cardinality.src = element.cardinality.max;
|
|
884
|
+
element._constraints._partnerCsn.cardinality.src = element.cardinality?.max ? element.cardinality.max : 1;
|
|
885
|
+
if(element.cardinality?.min !== undefined && element._constraints._partnerCsn.cardinality?.srcmin === undefined)
|
|
886
|
+
element._constraints._partnerCsn.cardinality.srcmin = element.cardinality.min;
|
|
881
887
|
}
|
|
882
888
|
}
|
|
883
889
|
else {
|
|
884
|
-
element._constraints._partnerCsn.cardinality = { src: element.cardinality.max };
|
|
890
|
+
element._constraints._partnerCsn.cardinality = { src: element.cardinality?.max ? element.cardinality.max : 1 };
|
|
891
|
+
if(element.cardinality?.min !== undefined)
|
|
892
|
+
element._constraints._partnerCsn.cardinality.srcmin = element.cardinality.min;
|
|
885
893
|
}
|
|
886
894
|
}
|
|
895
|
+
setProp(element._constraints, '$finalized', true);
|
|
887
896
|
}
|
|
888
897
|
}
|
|
889
898
|
|
|
@@ -1111,7 +1120,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1111
1120
|
if (isEdmPropertyRendered(e, options)) {
|
|
1112
1121
|
let newElt = proxy.elements[e.name];
|
|
1113
1122
|
if(!newElt) {
|
|
1114
|
-
if(csnUtils.isAssocOrComposition(e
|
|
1123
|
+
if(csnUtils.isAssocOrComposition(e)) {
|
|
1115
1124
|
if(!e.on && e.keys) {
|
|
1116
1125
|
if(options.odataNoTransitiveProxies)
|
|
1117
1126
|
newElt = convertManagedAssocIntoStruct(e);
|
|
@@ -1264,8 +1273,16 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1264
1273
|
// a primary key can never be an unmanaged association
|
|
1265
1274
|
type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
|
|
1266
1275
|
}
|
|
1267
|
-
if(forceToNotNull)
|
|
1268
|
-
type.elements[elemName]
|
|
1276
|
+
if(forceToNotNull) {
|
|
1277
|
+
const newElt = type.elements[elemName];
|
|
1278
|
+
if(newElt.target) {
|
|
1279
|
+
if (newElt.cardinality === undefined)
|
|
1280
|
+
newElt.cardinality = {};
|
|
1281
|
+
newElt.cardinality.min = 1;
|
|
1282
|
+
}
|
|
1283
|
+
// if odata-spec-violation-key-null is checking on min>1, this can be an else
|
|
1284
|
+
newElt.notNull = true;
|
|
1285
|
+
}
|
|
1269
1286
|
setProp(type.elements[elemName], 'name', elem.name);
|
|
1270
1287
|
});
|
|
1271
1288
|
return type;
|
|
@@ -1556,7 +1573,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1556
1573
|
|
|
1557
1574
|
let elements = eltCsn.elements || eltCsn.items?.elements;
|
|
1558
1575
|
if (!elements) {
|
|
1559
|
-
const finalType = csnUtils.
|
|
1576
|
+
const finalType = csnUtils.getFinalTypeInfo(eltCsn.items?.type || eltCsn.type);
|
|
1560
1577
|
elements = finalType?.elements || finalType?.items?.elements;
|
|
1561
1578
|
}
|
|
1562
1579
|
if (elements) {
|
|
@@ -1612,10 +1629,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1612
1629
|
|
|
1613
1630
|
function checkKeySpecViolations(elt, location, pathSegment) {
|
|
1614
1631
|
// Nullability
|
|
1615
|
-
|
|
1616
|
-
|
|
1632
|
+
const eltDef = elt.items || elt;
|
|
1633
|
+
if((!elt.key && (eltDef.notNull === undefined || eltDef.notNull === false)) ||
|
|
1634
|
+
elt.key && (eltDef.notNull !== undefined && eltDef.notNull === false)) {
|
|
1617
1635
|
message('odata-spec-violation-key-null', location,
|
|
1618
|
-
{name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
|
|
1636
|
+
{name: pathSegment ? pathSegment : elt.name, '#': !pathSegment ? 'std' : 'scalar'});
|
|
1619
1637
|
}
|
|
1620
1638
|
// many is either directly on elements or on the type
|
|
1621
1639
|
// due to added proxy types it might be that the type can't be found in definitions
|
|
@@ -1623,11 +1641,17 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1623
1641
|
(elt.type &&
|
|
1624
1642
|
!isBuiltinType(elt.type) &&
|
|
1625
1643
|
csn.definitions[elt.type] &&
|
|
1626
|
-
csnUtils.
|
|
1627
|
-
if(type
|
|
1644
|
+
csnUtils.getFinalTypeInfo(elt.type).items);
|
|
1645
|
+
if(type ||
|
|
1646
|
+
(options.odataFormat !== 'flat' && !options.odataForeignKeys) &&
|
|
1647
|
+
elt.cardinality?.max && elt.cardinality.max !== 1) {
|
|
1628
1648
|
// many primary key can be induced by a many parameter of a view
|
|
1629
1649
|
message('odata-spec-violation-key-array', location,
|
|
1630
|
-
{
|
|
1650
|
+
{
|
|
1651
|
+
name: pathSegment ? pathSegment : elt.name,
|
|
1652
|
+
value: cardinality2str(elt),
|
|
1653
|
+
'#': elt.target ? 'assoc' : 'std'
|
|
1654
|
+
});
|
|
1631
1655
|
}
|
|
1632
1656
|
// type
|
|
1633
1657
|
if(!elt.elements) {
|
|
@@ -1870,7 +1894,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1870
1894
|
// reset visited
|
|
1871
1895
|
for(const es in typeDef.enum)
|
|
1872
1896
|
delete typeDef.enum[es].$visited;
|
|
1873
|
-
|
|
1897
|
+
|
|
1874
1898
|
if(enumSymbolDef) {
|
|
1875
1899
|
if(enumSymbolDef.val !== undefined) {
|
|
1876
1900
|
// 'null' value is represented spec conform as empty record in AllowedValues collection
|
|
@@ -2072,12 +2096,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
2072
2096
|
if (obj.type && isBuiltinType(obj.type) && !obj.target && !obj.targetAspect) {
|
|
2073
2097
|
let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
|
|
2074
2098
|
edmUtils.assignProp(obj, '_edmType', edmType);
|
|
2075
|
-
} else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.
|
|
2099
|
+
} else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.getFinalTypeInfo(obj.items.type)?.type))) {
|
|
2076
2100
|
let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType'], obj.$path);
|
|
2077
2101
|
edmUtils.assignProp(obj, '_edmType', edmType);
|
|
2078
2102
|
}
|
|
2079
2103
|
// This is the special case when we have array of array, but will not be supported in the future
|
|
2080
|
-
else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.
|
|
2104
|
+
else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.getFinalTypeInfo(obj.items.items.type)?.type)) {
|
|
2081
2105
|
let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
|
|
2082
2106
|
edmUtils.assignProp(obj, '_edmType', edmType);
|
|
2083
2107
|
}
|
package/lib/edm/edmUtils.js
CHANGED
|
@@ -61,11 +61,6 @@ function isContainee(artifact) {
|
|
|
61
61
|
return (artifact.$containerNames && (artifact.$containerNames.length > 1 || artifact.$containerNames[0] != artifact.name));
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function isComposition(artifact) {
|
|
65
|
-
return (artifact.type === 'cds.Composition' || artifact.type === 'Composition') &&
|
|
66
|
-
artifact.target != undefined;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
64
|
// Return true if the association 'assoc' has cardinality 'to-many'
|
|
70
65
|
function isToMany(assoc) {
|
|
71
66
|
if (!assoc.cardinality) {
|
|
@@ -462,13 +457,46 @@ function determineMultiplicity(csn)
|
|
|
462
457
|
if(!csn.cardinality.max)
|
|
463
458
|
csn.cardinality.max = 1;
|
|
464
459
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
460
|
+
const srcCardinality =
|
|
461
|
+
(csn.cardinality.src == 1)
|
|
462
|
+
? (!isAssoc || csn.cardinality.srcmin == 1)
|
|
463
|
+
? '1'
|
|
464
|
+
: '0..1'
|
|
465
|
+
: '*';
|
|
466
|
+
const tgtCardinality =
|
|
467
|
+
(csn.cardinality.max > 1 || csn.cardinality.max === '*')
|
|
468
|
+
? '*'
|
|
469
|
+
: (csn.cardinality.min == 1)
|
|
470
|
+
? '1'
|
|
471
|
+
: '0..1';
|
|
468
472
|
|
|
469
473
|
return [srcCardinality, tgtCardinality];
|
|
470
474
|
}
|
|
471
475
|
|
|
476
|
+
// return effective target cardinality
|
|
477
|
+
// If csn is a backlink, return the source cardinality (including srcmin/src) from
|
|
478
|
+
// the forward association
|
|
479
|
+
// This function works only after finalizeConstraints
|
|
480
|
+
function getEffectiveTargetCardinality(csn) {
|
|
481
|
+
const rc = { min: 0, max: 1 };
|
|
482
|
+
if(!csn._constraints || !csn._constraints.$finalized)
|
|
483
|
+
throw new CompilerAssertion('_constraints missing or not finalized: "' + csn.name);
|
|
484
|
+
// partner (forward) cardinality has precedence
|
|
485
|
+
if(csn._constraints._partnerCsn) {
|
|
486
|
+
if(csn._constraints._partnerCsn.cardinality?.srcmin)
|
|
487
|
+
rc.min = csn._constraints._partnerCsn.cardinality.srcmin;
|
|
488
|
+
if(csn._constraints._partnerCsn.cardinality?.src)
|
|
489
|
+
rc.max = csn._constraints._partnerCsn.cardinality.src;
|
|
490
|
+
}
|
|
491
|
+
else if(csn.cardinality) {
|
|
492
|
+
if(csn.cardinality.min)
|
|
493
|
+
rc.min = csn.cardinality.min;
|
|
494
|
+
if(csn.cardinality.max)
|
|
495
|
+
rc.max = csn.cardinality.max
|
|
496
|
+
}
|
|
497
|
+
return rc;
|
|
498
|
+
}
|
|
499
|
+
|
|
472
500
|
function mapCdsToEdmType(csn, messageFunctions, isV2=false, isMediaType=false, location=undefined)
|
|
473
501
|
{
|
|
474
502
|
if(location === undefined)
|
|
@@ -869,7 +897,6 @@ module.exports = {
|
|
|
869
897
|
intersect,
|
|
870
898
|
foreach,
|
|
871
899
|
isContainee,
|
|
872
|
-
isComposition,
|
|
873
900
|
isToMany,
|
|
874
901
|
isSingleton,
|
|
875
902
|
isStructuredType,
|
|
@@ -879,6 +906,7 @@ module.exports = {
|
|
|
879
906
|
resolveOnConditionAndPrepareConstraints,
|
|
880
907
|
finalizeReferentialConstraints,
|
|
881
908
|
determineMultiplicity,
|
|
909
|
+
getEffectiveTargetCardinality,
|
|
882
910
|
mapCdsToEdmType,
|
|
883
911
|
addTypeFacets,
|
|
884
912
|
isODataSimpleIdentifier,
|
package/lib/gen/Dictionary.json
CHANGED
|
@@ -1518,7 +1518,7 @@
|
|
|
1518
1518
|
"ODM.root": {
|
|
1519
1519
|
"Type": "Core.Tag",
|
|
1520
1520
|
"AppliesTo": [
|
|
1521
|
-
"
|
|
1521
|
+
"EntityType"
|
|
1522
1522
|
],
|
|
1523
1523
|
"$experimental": true
|
|
1524
1524
|
},
|
|
@@ -1526,15 +1526,13 @@
|
|
|
1526
1526
|
"Type": "Edm.PropertyPath",
|
|
1527
1527
|
"AppliesTo": [
|
|
1528
1528
|
"EntityType"
|
|
1529
|
-
]
|
|
1530
|
-
"$experimental": true
|
|
1529
|
+
]
|
|
1531
1530
|
},
|
|
1532
1531
|
"ODM.entityName": {
|
|
1533
1532
|
"Type": "Edm.String",
|
|
1534
1533
|
"AppliesTo": [
|
|
1535
1534
|
"EntityType"
|
|
1536
|
-
]
|
|
1537
|
-
"$experimental": true
|
|
1535
|
+
]
|
|
1538
1536
|
},
|
|
1539
1537
|
"Offline.ClientOnly": {
|
|
1540
1538
|
"Type": "Offline.ClientOnlyType",
|
|
@@ -2866,9 +2864,9 @@
|
|
|
2866
2864
|
"BaseType": "Common.ValueListParameter",
|
|
2867
2865
|
"Properties": {
|
|
2868
2866
|
"Constant": "Edm.PrimitiveType",
|
|
2867
|
+
"InitialValueIsSignificant": "Edm.Boolean",
|
|
2869
2868
|
"ValueListProperty": "Edm.String"
|
|
2870
|
-
}
|
|
2871
|
-
"$experimental": true
|
|
2869
|
+
}
|
|
2872
2870
|
},
|
|
2873
2871
|
"Common.ValueListParameterInOut": {
|
|
2874
2872
|
"$kind": "ComplexType",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
78bdabed1311fe8b3b0921bfc5300ece
|