@sap/cds-compiler 2.12.0 → 2.13.6
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 +110 -15
- package/bin/cdsc.js +13 -13
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_BETA.md +13 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +28 -63
- package/lib/api/options.js +3 -3
- package/lib/api/validate.js +0 -5
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +25 -4
- package/lib/base/messages.js +16 -26
- package/lib/base/model.js +2 -63
- package/lib/base/optionProcessorHelper.js +158 -123
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +27 -26
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +4 -7
- package/lib/compiler/assert-consistency.js +5 -3
- package/lib/compiler/builtins.js +8 -6
- package/lib/compiler/checks.js +14 -3
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +32 -13
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +111 -46
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +64 -37
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +197 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +5 -9
- package/lib/edm/annotations/preprocessAnnotations.js +2 -2
- package/lib/edm/csn2edm.js +9 -8
- package/lib/edm/edm.js +11 -12
- package/lib/edm/edmPreprocessor.js +137 -73
- package/lib/edm/edmUtils.js +116 -22
- package/lib/gen/Dictionary.json +10 -3
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +9 -1
- package/lib/gen/language.tokens +86 -83
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +860 -833
- package/lib/gen/languageLexer.tokens +78 -75
- package/lib/gen/languageParser.js +5282 -4265
- package/lib/json/from-csn.js +12 -1
- package/lib/json/to-csn.js +126 -66
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +76 -3
- package/lib/language/language.g4 +297 -130
- package/lib/language/multiLineStringParser.js +5 -5
- package/lib/main.d.ts +468 -59
- package/lib/main.js +35 -9
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +225 -156
- package/lib/model/csnUtils.js +192 -223
- package/lib/model/enrichCsn.js +70 -29
- package/lib/model/revealInternalProperties.js +27 -6
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +5 -4
- package/lib/render/manageConstraints.js +35 -32
- package/lib/render/toCdl.js +73 -288
- package/lib/render/toHdbcds.js +25 -23
- package/lib/render/toSql.js +98 -41
- package/lib/render/utils/common.js +5 -10
- package/lib/render/utils/sql.js +4 -3
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +103 -305
- package/lib/transform/db/cdsPersistence.js +2 -2
- package/lib/transform/db/constraints.js +55 -52
- package/lib/transform/db/expansion.js +46 -24
- package/lib/transform/db/flattening.js +553 -102
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/transformExists.js +59 -6
- package/lib/transform/db/views.js +5 -4
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +6 -5
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +67 -183
- package/lib/transform/forOdataNew.js +17 -171
- package/lib/transform/localized.js +34 -19
- package/lib/transform/odata/generateForeignKeyElements.js +1 -1
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +36 -22
- package/lib/transform/translateAssocsToJoins.js +2 -19
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/objectUtils.js +30 -0
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2361
- package/lib/compiler/resolver.js +0 -3079
- package/lib/transform/universalCsnEnricher.js +0 -237
|
@@ -43,19 +43,23 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
43
43
|
|
|
44
44
|
const csnUtils = getUtils(csn);
|
|
45
45
|
const {
|
|
46
|
+
inspectRef,
|
|
46
47
|
getCsnDef,
|
|
47
48
|
getFinalTypeDef,
|
|
48
49
|
isStructured,
|
|
49
50
|
isAssocOrComposition,
|
|
50
51
|
} = getUtils(csn);
|
|
51
52
|
|
|
53
|
+
// proxies are merged into the final model after all proxy elements are collected
|
|
54
|
+
const proxyCache = [];
|
|
52
55
|
|
|
53
56
|
// make sure options are complete
|
|
54
57
|
let options = validateOptions(_options);
|
|
55
58
|
|
|
56
59
|
// Fetch service definitions
|
|
57
|
-
const
|
|
58
|
-
|
|
60
|
+
const allDefs = Object.entries(csn.definitions||{});
|
|
61
|
+
|
|
62
|
+
const serviceRoots = allDefs.reduce((serviceRoots, [artName, art]) => {
|
|
59
63
|
if(art.kind === 'service') {
|
|
60
64
|
serviceRoots[artName] = Object.assign(art, { name: artName });
|
|
61
65
|
}
|
|
@@ -64,11 +68,24 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
64
68
|
|
|
65
69
|
// first of all we need to know about all 'real' user defined services
|
|
66
70
|
const serviceRootNames = Object.keys(serviceRoots).sort((a,b)=>b.length-a.length);
|
|
71
|
+
|
|
67
72
|
function whatsMyServiceRootName(n, self=true) {
|
|
68
73
|
return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
|
|
69
74
|
}
|
|
75
|
+
|
|
76
|
+
// find a globally unambiguous schema name to collect all top level 'root' types
|
|
77
|
+
// TODO: work on service basis (this requires post exposure renaming)
|
|
78
|
+
let autoexposeSchemaName = 'root';
|
|
79
|
+
let i = 1;
|
|
80
|
+
while (allDefs.some(([artName, _art]) => {
|
|
81
|
+
const p = artName.split('.');
|
|
82
|
+
return p.length === 2 && p[0] === autoexposeSchemaName;
|
|
83
|
+
})) {
|
|
84
|
+
autoexposeSchemaName = 'root' + i++;
|
|
85
|
+
}
|
|
86
|
+
|
|
70
87
|
if(serviceRootNames.length === 0) {
|
|
71
|
-
return [serviceRoots, Object.create(null), whatsMyServiceRootName, options];
|
|
88
|
+
return [serviceRoots, Object.create(null), whatsMyServiceRootName, autoexposeSchemaName, options];
|
|
72
89
|
}
|
|
73
90
|
|
|
74
91
|
// Structural CSN inbound QA checks
|
|
@@ -103,7 +120,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
103
120
|
service), the namespace (including (sub-)contexts) or as last resort (if the type name
|
|
104
121
|
has no prefix path) a 'root' namespace.
|
|
105
122
|
*/
|
|
106
|
-
const schemas = typesExposure(csn, whatsMyServiceRootName, options, csnUtils, { error });
|
|
123
|
+
const schemas = typesExposure(csn, whatsMyServiceRootName, autoexposeSchemaName, options, csnUtils, { error });
|
|
107
124
|
|
|
108
125
|
// First attach names to all definitions (and actions/params) in the model
|
|
109
126
|
// elements are done in initializeStruct
|
|
@@ -145,6 +162,9 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
145
162
|
// create association target proxies
|
|
146
163
|
// Decide if an entity set needs to be constructed or not
|
|
147
164
|
forEachDefinition(csn, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
|
|
165
|
+
// finalize proxy creation
|
|
166
|
+
mergeProxiesIntoModel();
|
|
167
|
+
|
|
148
168
|
if(options.isV4())
|
|
149
169
|
forEachDefinition(csn, initializeEdmNavPropBindingTargets);
|
|
150
170
|
|
|
@@ -156,7 +176,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
156
176
|
forEachDefinition(csn, [ initializeEdmKeyRefPaths, initializeEdmNavPropBindingPaths,
|
|
157
177
|
initializeEdmTypesAndDescription, checkArtifactIdentifierAndBoundActions ]);
|
|
158
178
|
}
|
|
159
|
-
return [serviceRoots, schemas, whatsMyServiceRootName, options];
|
|
179
|
+
return [serviceRoots, schemas, whatsMyServiceRootName, autoexposeSchemaName, options];
|
|
160
180
|
|
|
161
181
|
//////////////////////////////////////////////////////////////////////
|
|
162
182
|
//
|
|
@@ -186,16 +206,16 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
186
206
|
const dotEntityNameMap = Object.create(null);
|
|
187
207
|
const dotTypeNameMap = Object.create(null);
|
|
188
208
|
forEachDefinition(csn, (def, defName) => {
|
|
189
|
-
if(['entity', '
|
|
209
|
+
if(['entity', 'type', 'action', 'function'].includes(def.kind)) {
|
|
190
210
|
const rootDef = getRootDef(defName);
|
|
191
211
|
// if this definition has a root def and the root def is not the service/schema name
|
|
192
212
|
// => service C { type D.E }, replace the prefix dots with underscores
|
|
193
213
|
if(rootDef && defName !== rootDef && rootDef !== getSchemaPrefix(defName)) {
|
|
194
214
|
let newDefName = rootDef + '.' + defName.replace(rootDef + '.', '').replace(/\./g, '_');
|
|
195
215
|
// store renamed types in correlation maps for later renaming
|
|
196
|
-
if(
|
|
216
|
+
if(def.kind === 'entity')
|
|
197
217
|
dotEntityNameMap[defName] = newDefName;
|
|
198
|
-
if(
|
|
218
|
+
if(def.kind === 'type')
|
|
199
219
|
dotTypeNameMap[defName] = newDefName;
|
|
200
220
|
// rename in csn.definitions
|
|
201
221
|
const art = csn.definitions[newDefName];
|
|
@@ -252,7 +272,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
252
272
|
*/
|
|
253
273
|
function splitDottedDefinitionsIntoSeparateServices() {
|
|
254
274
|
forEachDefinition(csn, (def, defName) => {
|
|
255
|
-
if(
|
|
275
|
+
if(def.kind !== 'service') {
|
|
256
276
|
const myServiceRoot = whatsMyServiceRootName(defName);
|
|
257
277
|
const mySchemaPrefix = getSchemaPrefix(defName);
|
|
258
278
|
if(myServiceRoot && options.isV4() &&
|
|
@@ -342,7 +362,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
342
362
|
// entity set. Instead try to rewrite the annotation in such a way that it is effective
|
|
343
363
|
// on the containment navigation property.
|
|
344
364
|
function initializeContainments(container) {
|
|
345
|
-
if(
|
|
365
|
+
if(container.kind === 'entity') {
|
|
346
366
|
forEachMemberRecursively(container, initContainments,
|
|
347
367
|
[], true, { elementsOnly: true });
|
|
348
368
|
}
|
|
@@ -466,6 +486,17 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
466
486
|
elt.name = n;
|
|
467
487
|
delete elt.kind;
|
|
468
488
|
elt.key = true; // params become primary key in parameter entity
|
|
489
|
+
/*
|
|
490
|
+
Spec meeting decision 28.02.22:
|
|
491
|
+
Annotation @sap.parameter allows two values "mandatory"/"optional".
|
|
492
|
+
Question was how to deal with incompatible "optional".
|
|
493
|
+
Only "mandatory" is allowed because in RAP all parameters are NOT NULL
|
|
494
|
+
and so they are in CAP (all view parameters become primary keys which are not null).
|
|
495
|
+
*/
|
|
496
|
+
if(options.isV2())
|
|
497
|
+
assignAnnotation(elt, '@sap.parameter', 'mandatory');
|
|
498
|
+
else
|
|
499
|
+
assignAnnotation(elt, '@Common.FieldControl', { '#': 'Mandatory' });
|
|
469
500
|
parameterCsn.elements[n] = elt;
|
|
470
501
|
});
|
|
471
502
|
linkAssociationTarget(parameterCsn);
|
|
@@ -581,16 +612,16 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
581
612
|
|
|
582
613
|
// Iterate all struct elements
|
|
583
614
|
forEachMemberRecursively(def.items || def, (element, elementName, prop, path = [], construct) => {
|
|
584
|
-
if(
|
|
615
|
+
if(prop !== 'elements')
|
|
585
616
|
return;
|
|
586
617
|
|
|
587
618
|
initElement(element, elementName, construct);
|
|
588
619
|
|
|
589
|
-
if(
|
|
620
|
+
if(def.kind !== 'event' && def.kind !== 'aspect') {
|
|
590
621
|
if(element._parent && element._parent.$mySchemaName) {
|
|
591
622
|
if(!isODataSimpleIdentifier(elementName)) {
|
|
592
623
|
signalIllegalIdentifier(elementName, ['definitions', def.name].concat(path));
|
|
593
|
-
} else if (options.isV2() && /^(_|[0-9])/.test(elementName) &&
|
|
624
|
+
} else if (options.isV2() && /^(_|[0-9])/.test(elementName) && element._parent.kind === 'entity') {
|
|
594
625
|
// FIXME: Rewrite signalIllegalIdentifier function to be more flexible
|
|
595
626
|
error(null, ['definitions', def.name, 'elements', elementName], { prop: elementName[0] },
|
|
596
627
|
'Element names must not start with $(PROP) for OData V2');
|
|
@@ -845,7 +876,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
845
876
|
proxies.
|
|
846
877
|
*/
|
|
847
878
|
function exposeTargetsAsProxiesOrSchemaRefs(struct) {
|
|
848
|
-
if(
|
|
879
|
+
if(struct.kind === 'context' || struct.kind === 'service' || struct.$proxy)
|
|
849
880
|
return;
|
|
850
881
|
|
|
851
882
|
// globalSchemaPrefix is the prefix for all proxy registrations and must not change
|
|
@@ -880,6 +911,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
880
911
|
// If the target is in another schema, check if both the source and the target share the same service name.
|
|
881
912
|
// If they share the same service name, then it is just a cross schema navigation within the same EDM, no
|
|
882
913
|
// proxy required.
|
|
914
|
+
// association must be managed and not unmanaged
|
|
883
915
|
|
|
884
916
|
// odataProxies (P) and odataXServiceRefs (X) are evalutated as follows:
|
|
885
917
|
// P | X | Action
|
|
@@ -890,7 +922,10 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
890
922
|
|
|
891
923
|
const targetSchemaName = element._target.$mySchemaName;
|
|
892
924
|
if(isProxyRequired(element)) {
|
|
893
|
-
if(options.isV4() &&
|
|
925
|
+
if(options.isV4() &&
|
|
926
|
+
(options.toOdata.odataProxies || options.toOdata.odataXServiceRefs)
|
|
927
|
+
// must be a managed association with keys and no ON condition
|
|
928
|
+
/* && element.keys && !element.on */) {
|
|
894
929
|
// reuse proxy if available
|
|
895
930
|
let proxy = getProxyForTargetOf(element);
|
|
896
931
|
if(!proxy) {
|
|
@@ -909,6 +944,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
909
944
|
element._constraints.constraints = Object.create(null);
|
|
910
945
|
if(proxy.kind === 'entity') {
|
|
911
946
|
element._target = proxy;
|
|
947
|
+
populateProxyElements(element, proxy, getForeignKeyDefinitions(element, ['definitions', struct.name, 'elements', element.name]));
|
|
912
948
|
}
|
|
913
949
|
else {
|
|
914
950
|
// fake the target to be proxy
|
|
@@ -933,7 +969,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
933
969
|
|
|
934
970
|
function noNavPropMsg(elt) {
|
|
935
971
|
warning(null, ['definitions', struct.name, 'elements', elt.name],
|
|
936
|
-
{ target: elt._target.name }, 'No OData navigation property generated, target $(TARGET) is outside
|
|
972
|
+
{ target: elt._target.name, service: globalSchemaPrefix }, 'No OData navigation property generated, target $(TARGET) is outside of service $(SERVICE)');
|
|
937
973
|
}
|
|
938
974
|
|
|
939
975
|
function createSchemaRefFor(assoc, targetSchemaName) {
|
|
@@ -954,10 +990,10 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
954
990
|
// 1) construct the proxy definition
|
|
955
991
|
// proxyShortName: strip the serviceName and replace '.' with '_'
|
|
956
992
|
const proxyShortName = assoc._target.name.replace(proxySchemaName + '.', '').replace(/\./g, '_');
|
|
957
|
-
|
|
958
|
-
const
|
|
959
|
-
const proxy = { name: fullName, kind: 'entity', $proxy: true, elements: Object.create(null) };
|
|
993
|
+
// fullName: Prepend serviceName and if in same service add '_proxy'
|
|
994
|
+
const proxy = { name: proxySchemaName + '.' + proxyShortName, kind: 'entity', $proxy: true, elements: Object.create(null) };
|
|
960
995
|
setProp(proxy, '$mySchemaName', proxySchemaName);
|
|
996
|
+
setProp(proxy, '$proxyShortName', proxyShortName);
|
|
961
997
|
setProp(proxy, '$keys', Object.create(null));
|
|
962
998
|
setProp(proxy, '$hasEntitySet', false);
|
|
963
999
|
setProp(proxy, '$exposedTypes', Object.create(null));
|
|
@@ -968,59 +1004,67 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
968
1004
|
});
|
|
969
1005
|
|
|
970
1006
|
// 2) create the elements and $keys
|
|
971
|
-
populateProxyElements(proxy, assoc._target.$keys);
|
|
972
|
-
// 3) sort the exposed types so that they appear lexicographically ordered in the EDM
|
|
973
|
-
proxy.$exposedTypes = Object.keys(proxy.$exposedTypes).sort().reduce((dict, tn) => {
|
|
974
|
-
dict[tn] = proxy.$exposedTypes[tn];
|
|
975
|
-
return dict
|
|
976
|
-
}, Object.create(null));
|
|
977
|
-
|
|
1007
|
+
populateProxyElements(assoc, proxy, assoc._target.$keys);
|
|
978
1008
|
return proxy;
|
|
979
1009
|
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// return array of foreign key defs in the target
|
|
1013
|
+
function getForeignKeyDefinitions(e, path=undefined) {
|
|
1014
|
+
// populate foreign keys, if a key can't be resolved -> parametertype redirects, ignore them
|
|
1015
|
+
return e.keys ? e.keys.map((_fk, i) => {
|
|
1016
|
+
return inspectRef([...(path || e.$path), 'keys', i]).art;
|
|
1017
|
+
}).filter(fk=>fk) : [];
|
|
1018
|
+
}
|
|
980
1019
|
// copy over the primary keys of the target and trigger the type exposure
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1020
|
+
function populateProxyElements(assoc, proxy, elements) {
|
|
1021
|
+
forAll(elements, e => {
|
|
1022
|
+
if (isEdmPropertyRendered(e, options) && !proxy.elements[e.name]) {
|
|
1023
|
+
let newElt = undefined;
|
|
1024
|
+
if(isAssocOrComposition(e.type)) {
|
|
1025
|
+
if(!e.on && e.keys) {
|
|
1026
|
+
if(options.toOdata.odataNoTransitiveProxies)
|
|
1027
|
+
newElt = convertManagedAssocIntoStruct(e);
|
|
1028
|
+
else
|
|
990
1029
|
newElt = createProxyOrSchemaRefForManagedAssoc(e);
|
|
991
|
-
}
|
|
992
|
-
else {
|
|
993
|
-
info(null, ['definitions', struct.name, 'elements', assoc.name],
|
|
994
|
-
{ name: fullName, target: assoc._target.name },
|
|
995
|
-
'Unmanaged associations are not supported as primary keys for proxy entity type $(NAME) of unexposed association target $(TARGET)');
|
|
996
|
-
}
|
|
997
1030
|
}
|
|
998
1031
|
else {
|
|
999
|
-
|
|
1032
|
+
info(null, ['definitions', struct.name, 'elements', assoc.name],
|
|
1033
|
+
{ name: proxy.nname, target: assoc._target.name },
|
|
1034
|
+
'Unmanaged associations are not supported as primary keys for proxy entity type $(NAME) of unexposed association target $(TARGET)');
|
|
1000
1035
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
newElt = cloneCsn(e, options);
|
|
1039
|
+
}
|
|
1040
|
+
if(newElt) {
|
|
1041
|
+
initElement(newElt, e.name, proxy);
|
|
1042
|
+
if(isStructured(newElt)) {
|
|
1004
1043
|
// argument proxySchemaName forces an anonymous type definition for newElt into the
|
|
1005
1044
|
// proxy schema. If omitted, this exposure defaults to 'root', in case API flavor of the day
|
|
1006
1045
|
// changes...
|
|
1007
|
-
|
|
1046
|
+
exposeStructTypeForProxyOf(proxy, newElt, proxy.$proxyShortName + '_' + newElt.name, proxy.$mySchemaName);
|
|
1008
1047
|
// elements of newElt are required for key ref paths
|
|
1009
|
-
}
|
|
1010
|
-
// all elements must become primary key
|
|
1011
|
-
proxy.$keys[e.name] = proxy.elements[newElt.name] = newElt;
|
|
1012
1048
|
}
|
|
1049
|
+
proxy.elements[newElt.name] = newElt;
|
|
1050
|
+
if(newElt.key)
|
|
1051
|
+
proxy.$keys[newElt.name] = newElt;
|
|
1013
1052
|
}
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
// 3) sort the exposed types so that they appear lexicographically ordered in the EDM
|
|
1056
|
+
proxy.$exposedTypes = Object.keys(proxy.$exposedTypes).sort().reduce((dict, tn) => {
|
|
1057
|
+
dict[tn] = proxy.$exposedTypes[tn];
|
|
1058
|
+
return dict
|
|
1059
|
+
}, Object.create(null));
|
|
1016
1060
|
|
|
1017
1061
|
// If 'node' exists and has a structured type that is not exposed in 'service', (because the type is
|
|
1018
1062
|
// anonymous or has a definition outside of 'service'), create an equivalent type in 'service', either
|
|
1019
1063
|
// using the type's name or (if anonymous) 'artificialName', and make 'node' use that type instead.
|
|
1020
1064
|
// Complain if there is an error.
|
|
1021
|
-
function exposeStructTypeForProxyOf(proxy, node, artificialName, typeSchemaName=
|
|
1065
|
+
function exposeStructTypeForProxyOf(proxy, node, artificialName, typeSchemaName=autoexposeSchemaName) {
|
|
1022
1066
|
const isNotInProtNS = node.type ? !isBuiltinType(node.type) : true;
|
|
1023
|
-
// Always expose types referred to by a proxy, never reuse an eventually
|
|
1067
|
+
// Always expose types referred to by a proxy, never reuse an eventually existing type
|
|
1024
1068
|
// as the nested elements must all be not nullable
|
|
1025
1069
|
if (isNotInProtNS) {
|
|
1026
1070
|
let typeDef = node.type ? csn.definitions[node.type] : /* anonymous type */ node;
|
|
@@ -1052,7 +1096,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1052
1096
|
if(typeClone) {
|
|
1053
1097
|
// Recurse into elements of 'type' (if any)
|
|
1054
1098
|
typeClone.elements && Object.entries(typeClone.elements).forEach( ([elemName, elem]) => {
|
|
1055
|
-
// if this is a foreign key elment, we must check
|
|
1099
|
+
// if this is a foreign key elment, we must check whether or not the association
|
|
1056
1100
|
// has been exposed as proxy. If it has not been exposed, no further structured
|
|
1057
1101
|
// types must be exposed as 'Proxy_' types.
|
|
1058
1102
|
|
|
@@ -1109,7 +1153,8 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1109
1153
|
Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
|
|
1110
1154
|
type.elements[elemName].notNull = true;
|
|
1111
1155
|
}
|
|
1112
|
-
else {
|
|
1156
|
+
else if(elem.keys && !elem.on) {
|
|
1157
|
+
// a primary key can never be an unmanaged association
|
|
1113
1158
|
type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
|
|
1114
1159
|
}
|
|
1115
1160
|
setProp(type.elements[elemName], 'name', elem.name);
|
|
@@ -1166,6 +1211,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1166
1211
|
}
|
|
1167
1212
|
else if(options.toOdata.odataProxies) {
|
|
1168
1213
|
proxy = createProxyFor(e, e._target.$mySchemaName);
|
|
1214
|
+
populateProxyElements(e, proxy, getForeignKeyDefinitions(e));
|
|
1169
1215
|
}
|
|
1170
1216
|
proxy = registerProxy(proxy, e);
|
|
1171
1217
|
}
|
|
@@ -1229,8 +1275,11 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1229
1275
|
function registerProxy(proxy, element) {
|
|
1230
1276
|
if(proxy === undefined)
|
|
1231
1277
|
return undefined;
|
|
1232
|
-
|
|
1233
|
-
|
|
1278
|
+
|
|
1279
|
+
setProp(proxy, '$globalSchemaPrefix', globalSchemaPrefix);
|
|
1280
|
+
setProp(proxy, '$origin', element);
|
|
1281
|
+
|
|
1282
|
+
const fqProxyName = proxy.$globalSchemaPrefix + '.' + proxy.name;
|
|
1234
1283
|
|
|
1235
1284
|
if(!element._target.$cachedProxy)
|
|
1236
1285
|
assignProp(element._target, '$cachedProxy', Object.create(null));
|
|
@@ -1238,8 +1287,19 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1238
1287
|
info(null, ['definitions', struct.name, 'elements', element.name],
|
|
1239
1288
|
{ name: fqProxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
|
|
1240
1289
|
}
|
|
1241
|
-
else
|
|
1290
|
+
else {
|
|
1291
|
+
determineEntitySet(proxy);
|
|
1292
|
+
proxyCache.push(proxy);
|
|
1242
1293
|
element._target.$cachedProxy[globalSchemaPrefix] = proxy;
|
|
1294
|
+
}
|
|
1295
|
+
return proxy;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function mergeProxiesIntoModel() {
|
|
1300
|
+
proxyCache.forEach(proxy => {
|
|
1301
|
+
const fqProxyName = proxy.$globalSchemaPrefix + '.' + proxy.name;
|
|
1302
|
+
const fqSchemaName = proxy.$globalSchemaPrefix + '.' + proxy.$mySchemaName;
|
|
1243
1303
|
|
|
1244
1304
|
if(proxy.kind === 'entity') {
|
|
1245
1305
|
// collect all schemas even for newly exposed types
|
|
@@ -1251,7 +1311,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1251
1311
|
// don't forget to prepend the global namespace prefix
|
|
1252
1312
|
// schemas are ordered in csn2edm.js for each service
|
|
1253
1313
|
Object.keys(proxy.$exposedTypes).forEach(t =>
|
|
1254
|
-
schemaSet.add(globalSchemaPrefix + '.' + getSchemaPrefix(t)));
|
|
1314
|
+
schemaSet.add(proxy.$globalSchemaPrefix + '.' + getSchemaPrefix(t)));
|
|
1255
1315
|
schemaSet.forEach(schemaName => {
|
|
1256
1316
|
if(!schemas[schemaName]) {
|
|
1257
1317
|
schemas[schemaName] = { kind: 'schema', name: schemaName };
|
|
@@ -1259,25 +1319,29 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1259
1319
|
}
|
|
1260
1320
|
});
|
|
1261
1321
|
/** @type {object} */
|
|
1262
|
-
const alreadyRegistered = csn.definitions[fqProxyName]
|
|
1322
|
+
const alreadyRegistered = csn.definitions[fqProxyName];
|
|
1263
1323
|
if(!alreadyRegistered) {
|
|
1264
1324
|
csn.definitions[fqProxyName] = proxy;
|
|
1265
1325
|
setProp(proxy, '$path', ['definitions', fqProxyName]);
|
|
1266
1326
|
Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
|
|
1267
|
-
const fqtn = globalSchemaPrefix + '.' + tn;
|
|
1327
|
+
const fqtn = proxy.$globalSchemaPrefix + '.' + tn;
|
|
1268
1328
|
if(csn.definitions[fqtn] === undefined) {
|
|
1269
1329
|
csn.definitions[fqtn] = v;
|
|
1270
1330
|
setProp(v, '$path', ['definitions', fqtn]);
|
|
1271
1331
|
}
|
|
1272
1332
|
});
|
|
1273
|
-
|
|
1333
|
+
// default location is not always correct in case proxy has been created by a nested assoc
|
|
1334
|
+
// as foreign key targeting another proxy association
|
|
1335
|
+
let loc = ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name];
|
|
1336
|
+
if(proxy.$origin._parent.$path)
|
|
1337
|
+
loc = [...proxy.$origin._parent.$path, 'elements', proxy.$origin.name]
|
|
1338
|
+
info(null, loc,
|
|
1274
1339
|
{ name: proxy.name }, 'Created proxy EDM entity type $(NAME)');
|
|
1275
1340
|
}
|
|
1276
1341
|
else if(alreadyRegistered && !alreadyRegistered.$proxy &&
|
|
1277
|
-
|
|
1278
|
-
warning('odata-definition-exists', ['definitions',
|
|
1342
|
+
alreadyRegistered.kind !== 'entity') {
|
|
1343
|
+
warning('odata-definition-exists', ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name],
|
|
1279
1344
|
{ '#': 'proxy', name: fqProxyName, kind: alreadyRegistered.kind });
|
|
1280
|
-
return undefined;
|
|
1281
1345
|
}
|
|
1282
1346
|
}
|
|
1283
1347
|
else {
|
|
@@ -1285,15 +1349,14 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1285
1349
|
if(!schemas[fqSchemaName]) {
|
|
1286
1350
|
schemas[fqSchemaName] = proxy;
|
|
1287
1351
|
schemaNames.push(fqSchemaName);
|
|
1288
|
-
info(null, ['definitions',
|
|
1352
|
+
info(null, ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name],
|
|
1289
1353
|
{ name: proxy.name }, 'Created EDM namespace reference $(NAME)');
|
|
1290
1354
|
}
|
|
1291
1355
|
// don't error on duplicate schemas, if it's already present then all is good....
|
|
1292
1356
|
}
|
|
1357
|
+
});
|
|
1293
1358
|
// sort the global schemaNames array
|
|
1294
|
-
|
|
1295
|
-
return proxy;
|
|
1296
|
-
}
|
|
1359
|
+
schemaNames.sort((a,b) => b.length-a.length);
|
|
1297
1360
|
}
|
|
1298
1361
|
|
|
1299
1362
|
/*
|
|
@@ -1737,12 +1800,13 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1737
1800
|
// serviceRef.push('');
|
|
1738
1801
|
if(serviceRef[serviceRef.length-1] !== '$metadata')
|
|
1739
1802
|
serviceRef.push('$metadata');
|
|
1740
|
-
|
|
1803
|
+
let sc = { kind: 'reference',
|
|
1741
1804
|
name: targetSchemaName,
|
|
1742
1805
|
ref: { Uri: serviceRef.join('/') },
|
|
1743
|
-
inc: { Namespace: targetSchemaName }
|
|
1744
|
-
$mySchemaName: targetSchemaName,
|
|
1806
|
+
inc: { Namespace: targetSchemaName }
|
|
1745
1807
|
};
|
|
1808
|
+
setProp(sc, '$mySchemaName', targetSchemaName);
|
|
1809
|
+
return sc;
|
|
1746
1810
|
|
|
1747
1811
|
/**
|
|
1748
1812
|
* Resolve a service endpoint path to mount it to as follows...
|
|
@@ -2158,7 +2222,7 @@ function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
|
|
|
2158
2222
|
}
|
|
2159
2223
|
|
|
2160
2224
|
function checkSemantics(struct, propName, propValue) {
|
|
2161
|
-
if(
|
|
2225
|
+
if(propValue === 'timeseries' || propValue === 'aggregate') {
|
|
2162
2226
|
// aggregate is forwarded to Set and must remain on Type
|
|
2163
2227
|
addToSetAttr(struct, propName, propValue, propValue !== 'aggregate');
|
|
2164
2228
|
}
|