@sap/cds-compiler 2.7.0 → 2.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -0
- package/lib/api/main.js +8 -10
- package/lib/api/options.js +13 -9
- package/lib/api/validate.js +11 -8
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +16 -0
- package/lib/base/messages.js +2 -0
- package/lib/base/model.js +1 -0
- package/lib/checks/onConditions.js +5 -0
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/validator.js +7 -2
- package/lib/compiler/assert-consistency.js +11 -5
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +3 -1
- package/lib/compiler/definer.js +87 -29
- package/lib/compiler/resolver.js +75 -16
- package/lib/compiler/shared.js +29 -9
- package/lib/edm/annotations/genericTranslation.js +182 -186
- package/lib/edm/csn2edm.js +93 -98
- package/lib/edm/edm.js +16 -20
- package/lib/edm/edmPreprocessor.js +274 -83
- package/lib/edm/edmUtils.js +29 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +12 -1
- package/lib/gen/language.tokens +57 -53
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +770 -744
- package/lib/gen/languageLexer.tokens +49 -46
- package/lib/gen/languageParser.js +4727 -4323
- package/lib/json/from-csn.js +52 -23
- package/lib/json/to-csn.js +185 -71
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +9 -0
- package/lib/language/language.g4 +90 -31
- package/lib/main.js +4 -0
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +7 -1
- package/lib/model/csnUtils.js +5 -4
- package/lib/optionProcessor.js +7 -1
- package/lib/render/.eslintrc.json +3 -1
- package/lib/render/toCdl.js +45 -9
- package/lib/render/toHdbcds.js +100 -34
- package/lib/render/toSql.js +12 -4
- package/lib/render/utils/common.js +5 -9
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +14 -4
- package/lib/transform/db/flattening.js +13 -5
- package/lib/transform/db/transformExists.js +252 -58
- package/lib/transform/forHanaNew.js +7 -1
- package/lib/transform/forOdataNew.js +12 -8
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +44 -38
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +13 -10
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +33 -1
- package/lib/transform/translateAssocsToJoins.js +6 -4
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/package.json +1 -1
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/* eslint max-statements-per-line:off */
|
|
3
3
|
const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
|
|
4
|
-
const { forEachDefinition, forEachGeneric,
|
|
4
|
+
const { forEachDefinition, forEachGeneric, forEachMemberRecursively,
|
|
5
5
|
isEdmPropertyRendered, getUtils, cloneCsn, isBuiltinType } = require('../model/csnUtils');
|
|
6
|
-
const { makeMessageFunction } = require('../base/messages');
|
|
7
6
|
const edmUtils = require('./edmUtils.js');
|
|
8
7
|
const typesExposure = require('../transform/odata/typesExposure');
|
|
9
8
|
const expandCSNToFinalBaseType = require('../transform/odata/toFinalBaseType');
|
|
@@ -35,12 +34,11 @@ const {
|
|
|
35
34
|
* @param {CSN.Model} csn
|
|
36
35
|
* @param {object} _options
|
|
37
36
|
*/
|
|
38
|
-
function initializeModel(csn, _options)
|
|
37
|
+
function initializeModel(csn, _options, messageFunctions)
|
|
39
38
|
{
|
|
40
39
|
if (!_options)
|
|
41
40
|
throw Error('Please debug me: initializeModel must be invoked with options');
|
|
42
41
|
|
|
43
|
-
const messageFunctions = makeMessageFunction(csn, _options);
|
|
44
42
|
const { info, warning, error, throwWithError } = messageFunctions;
|
|
45
43
|
|
|
46
44
|
const csnUtils = getUtils(csn);
|
|
@@ -142,14 +140,18 @@ function initializeModel(csn, _options)
|
|
|
142
140
|
// must be run before proxy exposure to avoid potential reference collisions
|
|
143
141
|
convertExposedTypesOfOtherServicesIntoCrossReferences();
|
|
144
142
|
// create association target proxies
|
|
145
|
-
|
|
143
|
+
// Decide if an entity set needs to be constructed or not
|
|
144
|
+
forEachDefinition(csn, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
|
|
145
|
+
if(options.isV4())
|
|
146
|
+
forEachDefinition(csn, initializeEdmNavPropBindingTargets);
|
|
146
147
|
|
|
147
148
|
// Things that can be done in one pass
|
|
148
149
|
// Create edmKeyRefPaths
|
|
149
|
-
//
|
|
150
|
+
// Create NavigationPropertyBindings, requires determineEntitySet
|
|
150
151
|
// Map /** doc comments */ to @CoreDescription
|
|
151
152
|
// Artifact identifier spec compliance check (should be run last)
|
|
152
|
-
forEachDefinition(csn, [ initializeEdmKeyRefPaths,
|
|
153
|
+
forEachDefinition(csn, [ initializeEdmKeyRefPaths, initializeEdmNavPropBindingPaths,
|
|
154
|
+
initializeEdmTypesAndDescription, checkArtifactIdentifierAndBoundActions ]);
|
|
153
155
|
}
|
|
154
156
|
return [serviceRoots, schemas, whatsMyServiceRootName, options];
|
|
155
157
|
|
|
@@ -337,33 +339,57 @@ function initializeModel(csn, _options)
|
|
|
337
339
|
// entity set. Instead try to rewrite the annotation in such a way that it is effective
|
|
338
340
|
// on the containment navigation property.
|
|
339
341
|
function initializeContainments(container) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
342
|
+
if(['entity', 'view'].includes(container.kind)) {
|
|
343
|
+
forEachMemberRecursively(container, initContainments,
|
|
344
|
+
[], true, { elementsOnly: true });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function initContainments(elt, eltName) {
|
|
348
|
+
if(isAssociationOrComposition(elt) && elt['@odata.contained'] && !elt._ignore) {
|
|
343
349
|
// Let the containee know its container
|
|
344
350
|
// (array because the contanee may contained more then once)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
351
|
+
let containee = elt._target;
|
|
352
|
+
if (!containee._containerEntity)
|
|
353
|
+
setProp(containee, '_containerEntity', []);
|
|
349
354
|
// add container only once per containee
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
355
|
+
if (!containee._containerEntity.includes(container.name))
|
|
356
|
+
containee._containerEntity.push(container.name);
|
|
357
|
+
// Mark associations in the containee pointing to the container (i.e. to this entity)
|
|
358
|
+
forEachMemberRecursively(containee, markToContainer,
|
|
359
|
+
[], true, { elementsOnly: true });
|
|
360
|
+
rewriteContainmentAnnotations(container, containee, eltName);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
// try to find elements to drill down further
|
|
364
|
+
while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
|
|
365
|
+
elt = csn.definitions[elt.type];
|
|
366
|
+
}
|
|
367
|
+
if(elt && elt.elements) {
|
|
368
|
+
forEachMemberRecursively(elt, initContainments,
|
|
369
|
+
[], true, { elementsOnly: true });
|
|
364
370
|
}
|
|
365
371
|
}
|
|
366
|
-
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function markToContainer(elt) {
|
|
375
|
+
if(elt._target && elt._target.name) {
|
|
376
|
+
// If this is an association that points to the container (but is not by itself contained,
|
|
377
|
+
// which would indicate the top role in a hierarchy) mark it with '_isToContainer'
|
|
378
|
+
if(elt._target.name === container.name && !elt['odata.contained']) {
|
|
379
|
+
setProp(elt, '_isToContainer', true);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
// try to find elements to drill down further
|
|
384
|
+
while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
|
|
385
|
+
elt = csn.definitions[elt.type];
|
|
386
|
+
}
|
|
387
|
+
if(elt && elt.elements) {
|
|
388
|
+
forEachMemberRecursively(elt, markToContainer,
|
|
389
|
+
[], true, { elementsOnly: true });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
367
393
|
}
|
|
368
394
|
|
|
369
395
|
// Split an entity with parameters into two entity types with their entity sets,
|
|
@@ -420,7 +446,6 @@ function initializeModel(csn, _options)
|
|
|
420
446
|
assignProp(parameterCsn, '_SetAttributes',
|
|
421
447
|
{'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.pageable': false });
|
|
422
448
|
|
|
423
|
-
assignProp(parameterCsn, '$keys', Object.create(null));
|
|
424
449
|
setProp(parameterCsn, '$isParamEntity', true);
|
|
425
450
|
setProp(parameterCsn, '$mySchemaName', entityCsn.$mySchemaName);
|
|
426
451
|
|
|
@@ -438,9 +463,10 @@ function initializeModel(csn, _options)
|
|
|
438
463
|
elt.name = n;
|
|
439
464
|
delete elt.kind;
|
|
440
465
|
elt.key = true; // params become primary key in parameter entity
|
|
441
|
-
parameterCsn
|
|
466
|
+
parameterCsn.elements[n] = elt;
|
|
442
467
|
});
|
|
443
|
-
|
|
468
|
+
linkAssociationTarget(parameterCsn);
|
|
469
|
+
initializeContainments(parameterCsn);
|
|
444
470
|
// add assoc to result set, FIXME: is the cardinality correct?
|
|
445
471
|
parameterCsn.elements[parameterToOriginalAssocName] = {
|
|
446
472
|
'@odata.contained': true,
|
|
@@ -543,16 +569,17 @@ function initializeModel(csn, _options)
|
|
|
543
569
|
let keys = Object.create(null);
|
|
544
570
|
let validFrom = [], validKey = [];
|
|
545
571
|
|
|
546
|
-
let structParent = def.items || def;
|
|
547
|
-
|
|
548
572
|
// Iterate all struct elements
|
|
549
|
-
|
|
550
|
-
|
|
573
|
+
forEachMemberRecursively(def.items || def, (element, elementName, prop, path = [], construct) => {
|
|
574
|
+
if(!['elements'].includes(prop))
|
|
575
|
+
return;
|
|
576
|
+
|
|
577
|
+
initElement(element, elementName, construct);
|
|
551
578
|
|
|
552
579
|
if(!['event', 'aspect'].includes(def.kind)) {
|
|
553
580
|
if(element._parent && element._parent.$mySchemaName) {
|
|
554
581
|
if(!isODataSimpleIdentifier(elementName)) {
|
|
555
|
-
signalIllegalIdentifier(elementName, ['definitions', def.name
|
|
582
|
+
signalIllegalIdentifier(elementName, ['definitions', def.name].concat(path));
|
|
556
583
|
} else if (options.isV2() && /^(_|[0-9])/.test(elementName) && ['view', 'entity'].includes(element._parent.kind)) {
|
|
557
584
|
// FIXME: Rewrite signalIllegalIdentifier function to be more flexible
|
|
558
585
|
error(null, ['definitions', def.name, 'elements', elementName], { prop: elementName[0] },
|
|
@@ -578,10 +605,12 @@ function initializeModel(csn, _options)
|
|
|
578
605
|
|
|
579
606
|
//forward annotations from managed association element to its foreign keys
|
|
580
607
|
if(element.keys && options.isFlatFormat) {
|
|
608
|
+
const elements = construct.items && construct.items.elements || construct.elements;
|
|
581
609
|
for(let fk of element.keys) {
|
|
582
610
|
forAll(element, (attr, attrName) => {
|
|
583
|
-
if(attrName[0] === '@' && fk.$generatedFieldName)
|
|
584
|
-
|
|
611
|
+
if(attrName[0] === '@' && fk.$generatedFieldName && elements && elements[fk.$generatedFieldName]) {
|
|
612
|
+
elements[fk.$generatedFieldName][attrName] = attr;
|
|
613
|
+
}
|
|
585
614
|
});
|
|
586
615
|
}
|
|
587
616
|
}
|
|
@@ -594,7 +623,7 @@ function initializeModel(csn, _options)
|
|
|
594
623
|
keys[elementName] = element;
|
|
595
624
|
}
|
|
596
625
|
applyAppSpecificLateCsnTransformationOnElement(options, element, def, error);
|
|
597
|
-
});
|
|
626
|
+
}, [], true, { elementsOnly: true });
|
|
598
627
|
|
|
599
628
|
if(!isDeprecatedEnabled(options, 'v1KeysForTemporal')) {
|
|
600
629
|
// if artifact has a cds.valid.key mention it as @Core.AlternateKey
|
|
@@ -646,14 +675,14 @@ function initializeModel(csn, _options)
|
|
|
646
675
|
if(!isStructuredArtifact(struct))
|
|
647
676
|
return;
|
|
648
677
|
|
|
649
|
-
|
|
678
|
+
forEachMemberRecursively(struct.items || struct, (element) => {
|
|
650
679
|
if (isAssociationOrComposition(element) && !element._ignore) {
|
|
651
680
|
// setup the constraints object
|
|
652
681
|
setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
|
|
653
682
|
// and crack the ON condition
|
|
654
683
|
resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
|
|
655
684
|
}
|
|
656
|
-
});
|
|
685
|
+
}, [], true, { elementsOnly: true });
|
|
657
686
|
}
|
|
658
687
|
|
|
659
688
|
/*
|
|
@@ -669,13 +698,16 @@ function initializeModel(csn, _options)
|
|
|
669
698
|
4) All of this can be revoked with options.renderForeignKeys.
|
|
670
699
|
*/
|
|
671
700
|
function ignoreProperties(struct) {
|
|
672
|
-
|
|
701
|
+
if(!isStructuredArtifact(struct))
|
|
702
|
+
return;
|
|
703
|
+
|
|
704
|
+
forEachMemberRecursively(struct.items || struct, (element) => {
|
|
673
705
|
if(!element.target) {
|
|
674
706
|
if(element['@odata.foreignKey4']) {
|
|
675
707
|
let isContainerAssoc = false;
|
|
676
|
-
let elements = struct.elements;
|
|
708
|
+
let elements = (struct.items || struct).elements;
|
|
677
709
|
let assoc = undefined;
|
|
678
|
-
|
|
710
|
+
const paths = element['@odata.foreignKey4'].split('.')
|
|
679
711
|
for(let p of paths) {
|
|
680
712
|
assoc = elements[p];
|
|
681
713
|
if(assoc) // could be that the @odata.foreignKey4 was propagated...
|
|
@@ -714,7 +746,7 @@ function initializeModel(csn, _options)
|
|
|
714
746
|
// ignore it if option odataContainment is true and no foreign keys should be rendered
|
|
715
747
|
assignAnnotation(element, '@odata.navigable', false);
|
|
716
748
|
}
|
|
717
|
-
});
|
|
749
|
+
}, [], true, { elementsOnly: true });
|
|
718
750
|
}
|
|
719
751
|
|
|
720
752
|
/*
|
|
@@ -726,7 +758,7 @@ function initializeModel(csn, _options)
|
|
|
726
758
|
if(!isStructuredArtifact(struct))
|
|
727
759
|
return;
|
|
728
760
|
|
|
729
|
-
|
|
761
|
+
forEachMemberRecursively(struct.items || struct, (element) => {
|
|
730
762
|
if (isAssociationOrComposition(element) && !element._ignore) {
|
|
731
763
|
finalizeReferentialConstraints(csn, element, options, info);
|
|
732
764
|
|
|
@@ -753,7 +785,7 @@ function initializeModel(csn, _options)
|
|
|
753
785
|
}
|
|
754
786
|
}
|
|
755
787
|
}
|
|
756
|
-
});
|
|
788
|
+
}, [], true, { elementsOnly: true });
|
|
757
789
|
}
|
|
758
790
|
|
|
759
791
|
/*
|
|
@@ -816,7 +848,7 @@ function initializeModel(csn, _options)
|
|
|
816
848
|
const globalSchemaPrefix = whatsMyServiceRootName(struct.$mySchemaName);
|
|
817
849
|
// if this artifact is a service member check its associations
|
|
818
850
|
if(globalSchemaPrefix) {
|
|
819
|
-
forEachGeneric(struct, 'elements', element => {
|
|
851
|
+
forEachGeneric(struct.items || struct, 'elements', element => {
|
|
820
852
|
if(!isAssociationOrComposition(element) || element._ignore || element['@odata.navigable'] === false)
|
|
821
853
|
return;
|
|
822
854
|
/*
|
|
@@ -874,7 +906,7 @@ function initializeModel(csn, _options)
|
|
|
874
906
|
}
|
|
875
907
|
else {
|
|
876
908
|
// fake the target to be proxy
|
|
877
|
-
element._target
|
|
909
|
+
setProp(element._target, '$externalRef', true);
|
|
878
910
|
}
|
|
879
911
|
}
|
|
880
912
|
else {
|
|
@@ -923,7 +955,6 @@ function initializeModel(csn, _options)
|
|
|
923
955
|
setProp(proxy, '$keys', Object.create(null));
|
|
924
956
|
setProp(proxy, '$hasEntitySet', false);
|
|
925
957
|
setProp(proxy, '$exposedTypes', Object.create(null));
|
|
926
|
-
|
|
927
958
|
// copy all annotations of the target to the proxy
|
|
928
959
|
Object.entries(assoc._target).forEach(([k, v]) => {
|
|
929
960
|
if(k[0] === '@')
|
|
@@ -1070,10 +1101,12 @@ function initializeModel(csn, _options)
|
|
|
1070
1101
|
if(!elem.target) {
|
|
1071
1102
|
type.elements[elemName] = Object.create(null);
|
|
1072
1103
|
Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
|
|
1104
|
+
type.elements[elemName].notNull = true;
|
|
1073
1105
|
}
|
|
1074
1106
|
else {
|
|
1075
1107
|
type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
|
|
1076
1108
|
}
|
|
1109
|
+
setProp(type.elements[elemName], 'name', elem.name);
|
|
1077
1110
|
});
|
|
1078
1111
|
return type;
|
|
1079
1112
|
}
|
|
@@ -1100,6 +1133,7 @@ function initializeModel(csn, _options)
|
|
|
1100
1133
|
// art is in the target side, clone it and remove key property
|
|
1101
1134
|
let cloneArt = cloneCsn(art, options);
|
|
1102
1135
|
setProp(cloneArt, 'name', art.name);
|
|
1136
|
+
cloneArt.notNull = true;
|
|
1103
1137
|
delete cloneArt.key;
|
|
1104
1138
|
newElt.elements[art.name] = cloneArt;
|
|
1105
1139
|
});
|
|
@@ -1189,14 +1223,14 @@ function initializeModel(csn, _options)
|
|
|
1189
1223
|
function registerProxy(proxy, element) {
|
|
1190
1224
|
if(proxy === undefined)
|
|
1191
1225
|
return undefined;
|
|
1192
|
-
const
|
|
1193
|
-
const
|
|
1226
|
+
const fqProxyName = globalSchemaPrefix + '.' + proxy.name;
|
|
1227
|
+
const fqSchemaName = globalSchemaPrefix + '.' + proxy.$mySchemaName;
|
|
1194
1228
|
|
|
1195
1229
|
if(!element._target.$cachedProxy)
|
|
1196
1230
|
assignProp(element._target, '$cachedProxy', Object.create(null));
|
|
1197
1231
|
if(getProxyForTargetOf(element)) {
|
|
1198
1232
|
info(null, ['definitions', struct.name, 'elements', element.name],
|
|
1199
|
-
{ name:
|
|
1233
|
+
{ name: fqProxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
|
|
1200
1234
|
}
|
|
1201
1235
|
else
|
|
1202
1236
|
element._target.$cachedProxy[globalSchemaPrefix] = proxy;
|
|
@@ -1206,7 +1240,7 @@ function initializeModel(csn, _options)
|
|
|
1206
1240
|
// (that may reside in another subcontext schema), but only once
|
|
1207
1241
|
const schemaSet = new Set();
|
|
1208
1242
|
// start with the schema name for the proxy
|
|
1209
|
-
schemaSet.add(
|
|
1243
|
+
schemaSet.add(fqSchemaName);
|
|
1210
1244
|
// followed by all namespaces that are potentially exposed by the exposed types
|
|
1211
1245
|
// don't forget to prepend the global namespace prefix
|
|
1212
1246
|
// schemas are ordered in csn2edm.js for each service
|
|
@@ -1219,12 +1253,16 @@ function initializeModel(csn, _options)
|
|
|
1219
1253
|
}
|
|
1220
1254
|
});
|
|
1221
1255
|
/** @type {object} */
|
|
1222
|
-
const alreadyRegistered = csn.definitions[
|
|
1256
|
+
const alreadyRegistered = csn.definitions[fqProxyName]
|
|
1223
1257
|
if(!alreadyRegistered) {
|
|
1224
|
-
csn.definitions[
|
|
1258
|
+
csn.definitions[fqProxyName] = proxy;
|
|
1259
|
+
setProp(proxy, '$path', ['definitions', fqProxyName]);
|
|
1225
1260
|
Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
|
|
1226
|
-
|
|
1227
|
-
|
|
1261
|
+
const fqtn = globalSchemaPrefix + '.' + tn;
|
|
1262
|
+
if(csn.definitions[fqtn] === undefined) {
|
|
1263
|
+
csn.definitions[fqtn] = v;
|
|
1264
|
+
setProp(v, '$path', ['definitions', fqtn]);
|
|
1265
|
+
}
|
|
1228
1266
|
});
|
|
1229
1267
|
info(null, ['definitions', element._parent.name, 'elements', element.name],
|
|
1230
1268
|
{ name: proxy.name }, 'Created proxy EDM entity type $(NAME)');
|
|
@@ -1232,16 +1270,16 @@ function initializeModel(csn, _options)
|
|
|
1232
1270
|
else if(alreadyRegistered && !alreadyRegistered.$proxy &&
|
|
1233
1271
|
!['entity', 'view'].includes(alreadyRegistered.kind)) {
|
|
1234
1272
|
warning(null, ['definitions', element._parent.name, 'elements', element.name],
|
|
1235
|
-
{ name:
|
|
1273
|
+
{ name: fqProxyName, kind: alreadyRegistered.kind },
|
|
1236
1274
|
'No proxy EDM entity type created due to name collision with $(NAME) of kind $(KIND)');
|
|
1237
1275
|
return undefined;
|
|
1238
1276
|
}
|
|
1239
1277
|
}
|
|
1240
1278
|
else {
|
|
1241
1279
|
// it's a service reference, just add that reference proxy
|
|
1242
|
-
if(!schemas[
|
|
1243
|
-
schemas[
|
|
1244
|
-
schemaNames.push(
|
|
1280
|
+
if(!schemas[fqSchemaName]) {
|
|
1281
|
+
schemas[fqSchemaName] = proxy;
|
|
1282
|
+
schemaNames.push(fqSchemaName);
|
|
1245
1283
|
info(null, ['definitions', struct.name, 'elements', element.name],
|
|
1246
1284
|
{ name: proxy.name }, 'Created EDM namespace reference $(NAME)');
|
|
1247
1285
|
}
|
|
@@ -1286,11 +1324,8 @@ function initializeModel(csn, _options)
|
|
|
1286
1324
|
else if(!k.target) {
|
|
1287
1325
|
struct.$edmKeyPaths.push([kn]);
|
|
1288
1326
|
}
|
|
1289
|
-
//
|
|
1290
|
-
|
|
1291
|
-
const pathToElement = ['definitions', struct.name, 'elements', k.name];
|
|
1292
|
-
signalErrorForNullableKey(pathToElement);
|
|
1293
|
-
}
|
|
1327
|
+
// check toplevel key for spec violations
|
|
1328
|
+
checkKeySpecViolations(k, ['definitions', struct.name, 'elements', k.name]);
|
|
1294
1329
|
}
|
|
1295
1330
|
});
|
|
1296
1331
|
}
|
|
@@ -1306,7 +1341,7 @@ function initializeModel(csn, _options)
|
|
|
1306
1341
|
If element is of scalar type, return it as an array.
|
|
1307
1342
|
*/
|
|
1308
1343
|
function produceKeyRefPaths(eltCsn, prefix) {
|
|
1309
|
-
|
|
1344
|
+
const keyPaths = [];
|
|
1310
1345
|
if(!isEdmPropertyRendered(eltCsn, options)) {
|
|
1311
1346
|
// let annos = Object.keys(eltCsn).filter(a=>a[0]==='@').join(', ');
|
|
1312
1347
|
// warning(null, ['definitions', struct.name, 'elements', eltCsn.name ],
|
|
@@ -1314,15 +1349,19 @@ function initializeModel(csn, _options)
|
|
|
1314
1349
|
return keyPaths;
|
|
1315
1350
|
}
|
|
1316
1351
|
// OData requires all elements along the path to be nullable: false (that is either key or notNull)
|
|
1317
|
-
|
|
1352
|
+
|
|
1353
|
+
const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
|
|
1354
|
+
const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements|| finalType.elements || finalType.items && finalType.items.elements;
|
|
1318
1355
|
if(elements) {
|
|
1319
1356
|
Object.entries(elements).forEach(([eltName, elt]) => {
|
|
1320
|
-
|
|
1321
|
-
if(
|
|
1322
|
-
|
|
1357
|
+
const newRefs = produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName);
|
|
1358
|
+
if(newRefs.length) {
|
|
1359
|
+
keyPaths.push(...newRefs);
|
|
1360
|
+
// check path step key for spec violations
|
|
1361
|
+
const pathSegment = `${prefix}/${eltName}`;
|
|
1323
1362
|
// we want to point to the element in the entity which is the first path step
|
|
1324
|
-
const
|
|
1325
|
-
|
|
1363
|
+
const location = struct.$path.concat(['elements']).concat(pathSegment.split('/')[0]);
|
|
1364
|
+
checkKeySpecViolations(elt, location, pathSegment);
|
|
1326
1365
|
}
|
|
1327
1366
|
});
|
|
1328
1367
|
}
|
|
@@ -1353,17 +1392,169 @@ function initializeModel(csn, _options)
|
|
|
1353
1392
|
return keyPaths;
|
|
1354
1393
|
}
|
|
1355
1394
|
|
|
1356
|
-
function
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1395
|
+
function checkKeySpecViolations(elt, location, pathSegment) {
|
|
1396
|
+
// Nullability
|
|
1397
|
+
if((!elt.key && (elt.notNull === undefined || elt.notNull === false)) ||
|
|
1398
|
+
elt.key && (elt.notNull !== undefined && elt.notNull === false)) {
|
|
1399
|
+
error('odata-spec-violation-key-null', location,
|
|
1400
|
+
{name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
|
|
1401
|
+
}
|
|
1402
|
+
// many
|
|
1403
|
+
let type = elt.items || getFinalTypeDef(elt.type).items;
|
|
1404
|
+
if(type) {
|
|
1405
|
+
error('odata-spec-violation-key-array', location,
|
|
1406
|
+
{name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
|
|
1407
|
+
}
|
|
1408
|
+
// type
|
|
1409
|
+
if(!elt.elements) {
|
|
1410
|
+
if(!type)
|
|
1411
|
+
type = isBuiltinType(elt.type) ? elt : csn.definitions[elt.type];
|
|
1412
|
+
|
|
1413
|
+
// check for legal scalar types, proxy exposed structured types are not resolvable in CSN
|
|
1414
|
+
// V2 allows any Edm.PrimitiveType (even Double and Binary), V4 is more specific:
|
|
1415
|
+
if(options.isV4() && type && !isAssociationOrComposition(type) && isBuiltinType(type.type)) {
|
|
1416
|
+
const edmType = edmUtils.mapCdsToEdmType(type);
|
|
1417
|
+
const legalEdmTypes = [
|
|
1418
|
+
'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Duration',
|
|
1419
|
+
'Edm.Guid', 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte', 'Edm.String', 'Edm.TimeOfDay' ];
|
|
1420
|
+
if(!legalEdmTypes.includes(edmType)) {
|
|
1421
|
+
warning('odata-spec-violation-key-type', location,
|
|
1422
|
+
{name: pathSegment, type: type.type, id: edmType, '#': pathSegment ? 'std' : 'scalar'});
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/*
|
|
1430
|
+
Calculate all reachable entity set paths for a given navigation start point
|
|
1431
|
+
|
|
1432
|
+
Rule: First non-containment association terminates Path, if association is
|
|
1433
|
+
containment enabling assoc, Target is own Struct/ plus the path down to the
|
|
1434
|
+
n-2nd path segment (which is the path to the n-1st implicit entity set).
|
|
1435
|
+
|
|
1436
|
+
Example:
|
|
1437
|
+
entity Header {
|
|
1438
|
+
items: composition of many {
|
|
1439
|
+
toF: association to F;
|
|
1440
|
+
subitems: composition of many {
|
|
1441
|
+
toG: association to G;
|
|
1442
|
+
subitems: composition of many {
|
|
1443
|
+
toG: association to G;
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
Must produce:
|
|
1449
|
+
Path="items/up_" Target="Header"/>
|
|
1450
|
+
Path="items/toF" Target="F"/>
|
|
1451
|
+
Path="items/subitems/up_" Target="Header/items"/>
|
|
1452
|
+
Path="items/subitems/toG" Target="G"/>
|
|
1453
|
+
Path="items/subitems/subitems/up_" Target="Header/items/subitems"/>
|
|
1454
|
+
Path="items/subitems/subitems/toG" Target="G"/>
|
|
1455
|
+
*/
|
|
1456
|
+
function initializeEdmNavPropBindingTargets(struct) {
|
|
1457
|
+
if(options.isV4() && struct.$mySchemaName && struct.$hasEntitySet) {
|
|
1458
|
+
forEachGeneric(struct.items || struct, 'elements', (element) => {
|
|
1459
|
+
produceTargetPath([edmUtils.getBaseName(struct.name)], element, struct);
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
function produceTargetPath(prefix, elt, curDef) {
|
|
1464
|
+
const newPrefix = [...prefix, elt.name];
|
|
1465
|
+
if(isEdmPropertyRendered(elt, options)) {
|
|
1466
|
+
// Assoc can never be a derived TypeDefinition, no need to
|
|
1467
|
+
// unroll derived type chains for assocs
|
|
1468
|
+
if(isAssociationOrComposition(elt) && !elt.$touched) {
|
|
1469
|
+
if(!elt._target.$edmTgtPaths)
|
|
1470
|
+
setProp(elt._target, '$edmTgtPaths', []);
|
|
1471
|
+
if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
|
|
1472
|
+
// follow elements in the target but avoid cycles
|
|
1473
|
+
setProp(elt, '$touched', true);
|
|
1474
|
+
elt._target.$edmTgtPaths.push(newPrefix);
|
|
1475
|
+
Object.values(elt._target.elements).forEach(e => produceTargetPath(newPrefix, e, elt._target));
|
|
1476
|
+
delete elt.$touched;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
else {
|
|
1480
|
+
// try to find elements to drill down further
|
|
1481
|
+
while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
|
|
1482
|
+
elt = csn.definitions[elt.type];
|
|
1483
|
+
}
|
|
1484
|
+
elt && elt.elements && Object.values(elt.elements).forEach(e => produceTargetPath(newPrefix, e, curDef));
|
|
1362
1485
|
}
|
|
1363
|
-
|
|
1486
|
+
}
|
|
1364
1487
|
}
|
|
1365
1488
|
}
|
|
1366
1489
|
|
|
1490
|
+
function initializeEdmNavPropBindingPaths(struct) {
|
|
1491
|
+
if(options.isV4() && struct.$mySchemaName && struct.$hasEntitySet) {
|
|
1492
|
+
let npbs = [];
|
|
1493
|
+
forEachGeneric(struct.items || struct, 'elements', (element) => {
|
|
1494
|
+
npbs = npbs.concat(produceNavigationPath(element, struct));
|
|
1495
|
+
});
|
|
1496
|
+
setProp(struct, '$edmNPBs', npbs);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// collect all paths originating from this element that end up in an entity set
|
|
1500
|
+
function produceNavigationPath(elt, curDef) {
|
|
1501
|
+
let npbs = [];
|
|
1502
|
+
const prefix = elt.name;
|
|
1503
|
+
if(isEdmPropertyRendered(elt, options)) {
|
|
1504
|
+
// Assoc can never be a derived TypeDefinition, no need to
|
|
1505
|
+
// unroll derived type chains for assocs
|
|
1506
|
+
if(isAssociationOrComposition(elt) && !elt.$touched) {
|
|
1507
|
+
// drill into target only if
|
|
1508
|
+
// 1) target has no entity set and this assoc is not going to the container
|
|
1509
|
+
// 2) current definition and target are the same (cycle)
|
|
1510
|
+
if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
|
|
1511
|
+
// follow elements in the target but avoid cycles
|
|
1512
|
+
setProp(elt, '$touched', true);
|
|
1513
|
+
Object.values(elt._target.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, elt._target)));
|
|
1514
|
+
delete elt.$touched;
|
|
1515
|
+
}
|
|
1516
|
+
else if(!(options.odataContainment && options.isV4() && elt['@odata.contained'])) {
|
|
1517
|
+
// end point reached but must not be an external reference nor a proxy nor a composition itself
|
|
1518
|
+
// last assoc step must not be to-n and target a singleton
|
|
1519
|
+
let p = undefined;
|
|
1520
|
+
if (!elt._target.$externalRef &&
|
|
1521
|
+
!(edmUtils.isToMany(elt) && edmUtils.isSingleton(elt._target, options.isV4()))) {
|
|
1522
|
+
if(elt._target.$edmTgtPaths && elt._target.$edmTgtPaths.length) {
|
|
1523
|
+
p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(struct.name)) || elt._target.$edmTgtPaths[0];
|
|
1524
|
+
}
|
|
1525
|
+
else if(elt._target.$hasEntitySet) {
|
|
1526
|
+
const baseName = edmUtils.getBaseName(elt._target.$entitySetName || elt._target.name);
|
|
1527
|
+
// if own struct and target have a set they either are in the same $mySchemaName or not
|
|
1528
|
+
// if target is in another schema, target the full qualified entity set
|
|
1529
|
+
p = (elt._target.$mySchemaName === struct.$mySchemaName) ?
|
|
1530
|
+
[ baseName ] : [elt._target.$mySchemaName + '.EntityContainer', baseName];
|
|
1531
|
+
}
|
|
1532
|
+
if(p) {
|
|
1533
|
+
// if own struct and target have a set they either are in the same $mySchemaName or not
|
|
1534
|
+
// if target is in another schema, target the full qualified entity set
|
|
1535
|
+
const npb = {
|
|
1536
|
+
Path: elt.name,
|
|
1537
|
+
Target: p.join('/')
|
|
1538
|
+
};
|
|
1539
|
+
npbs.push( npb );
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
// Do not prepend prefix here!
|
|
1543
|
+
return npbs;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
else {
|
|
1547
|
+
// try to find elements to drill down further
|
|
1548
|
+
while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
|
|
1549
|
+
elt = csn.definitions[elt.type];
|
|
1550
|
+
}
|
|
1551
|
+
elt && elt.elements && Object.values(elt.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, curDef)));
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
npbs.forEach(p => p.Path = prefix + '/' + p.Path );
|
|
1555
|
+
return npbs;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1367
1558
|
|
|
1368
1559
|
function determineEntitySet(struct) {
|
|
1369
1560
|
// if this is an entity or a view, determine if an entity set is required or not
|
|
@@ -1827,7 +2018,7 @@ function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error
|
|
|
1827
2018
|
let key = { name: keyName, key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false, '@UI.Hidden': true };
|
|
1828
2019
|
elements[keyName] = key;
|
|
1829
2020
|
setProp(struct, '$keys',{ [keyName] : key } );
|
|
1830
|
-
forEachGeneric(struct, 'elements', (e,n) =>
|
|
2021
|
+
forEachGeneric(struct.items || struct, 'elements', (e,n) =>
|
|
1831
2022
|
{
|
|
1832
2023
|
if(e.key) delete e.key;
|
|
1833
2024
|
elements[n] = e;
|