@sap/cds-compiler 2.4.4 → 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 +241 -1
- package/bin/.eslintrc.json +17 -0
- package/bin/cds_update_identifiers.js +8 -7
- package/bin/cdsc.js +180 -132
- package/bin/cdshi.js +18 -11
- package/bin/cdsse.js +38 -32
- package/bin/cdsv2m.js +8 -7
- package/doc/CHANGELOG_BETA.md +36 -1
- package/lib/api/main.js +81 -100
- package/lib/api/options.js +17 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/location.js +2 -2
- package/lib/base/message-registry.js +66 -4
- package/lib/base/messages.js +84 -27
- package/lib/base/model.js +2 -61
- package/lib/checks/arrayOfs.js +0 -1
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/enricher.js +8 -2
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +27 -9
- package/lib/checks/selectItems.js +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +66 -13
- package/lib/compiler/assert-consistency.js +24 -12
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +101 -39
- package/lib/compiler/index.js +88 -59
- package/lib/compiler/resolver.js +455 -209
- package/lib/compiler/shared.js +57 -33
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +128 -99
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -127
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +74 -28
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +18 -4
- package/lib/gen/language.tokens +124 -118
- package/lib/gen/languageLexer.interp +13 -1
- package/lib/gen/languageLexer.js +870 -839
- package/lib/gen/languageLexer.tokens +116 -111
- package/lib/gen/languageParser.js +5894 -5614
- package/lib/json/from-csn.js +152 -67
- package/lib/json/to-csn.js +334 -135
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +24 -14
- package/lib/language/language.g4 +188 -128
- package/lib/main.d.ts +435 -0
- package/lib/main.js +31 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +463 -187
- package/lib/model/csnUtils.js +280 -136
- package/lib/model/enrichCsn.js +75 -4
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +70 -25
- package/lib/optionProcessor.js +13 -10
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +123 -40
- package/lib/render/toHdbcds.js +156 -65
- package/lib/render/toSql.js +87 -11
- package/lib/render/utils/common.js +55 -9
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/{sql → db}/.eslintrc.json +0 -0
- package/lib/transform/{sql → db}/assertUnique.js +7 -8
- package/lib/transform/{sql → db}/constraints.js +35 -20
- package/lib/transform/db/draft.js +353 -0
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
- package/lib/transform/{sql → db}/helpers.js +0 -0
- package/lib/transform/{sql → db}/transformExists.js +256 -60
- package/lib/transform/forHanaNew.js +216 -765
- package/lib/transform/forOdataNew.js +60 -56
- package/lib/transform/localized.js +48 -26
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
- package/lib/transform/odata/generateForeignKeyElements.js +13 -12
- package/lib/transform/odata/referenceFlattener.js +60 -36
- package/lib/transform/odata/sortByAssociationDependency.js +4 -4
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +21 -22
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +27 -17
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +141 -77
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/timetrace.js +6 -1
- package/package.json +2 -1
- package/lib/base/deepCopy.js +0 -66
- package/lib/json/walker.js +0 -26
- package/lib/utils/string.js +0 -17
|
@@ -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
|
|
|
@@ -216,6 +218,9 @@ function initializeModel(csn, _options)
|
|
|
216
218
|
if(member.target && dotEntityNameMap[member.target]) {
|
|
217
219
|
member.target = dotEntityNameMap[member.target];
|
|
218
220
|
}
|
|
221
|
+
if(member.$path && dotEntityNameMap[member.$path[1]]) {
|
|
222
|
+
member.$path[1] = dotEntityNameMap[member.$path[1]]
|
|
223
|
+
}
|
|
219
224
|
_rewriteReferencesInActions(member);
|
|
220
225
|
});
|
|
221
226
|
// handle unbound action/function and params in views
|
|
@@ -334,33 +339,57 @@ function initializeModel(csn, _options)
|
|
|
334
339
|
// entity set. Instead try to rewrite the annotation in such a way that it is effective
|
|
335
340
|
// on the containment navigation property.
|
|
336
341
|
function initializeContainments(container) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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) {
|
|
340
349
|
// Let the containee know its container
|
|
341
350
|
// (array because the contanee may contained more then once)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
351
|
+
let containee = elt._target;
|
|
352
|
+
if (!containee._containerEntity)
|
|
353
|
+
setProp(containee, '_containerEntity', []);
|
|
346
354
|
// add container only once per containee
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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 });
|
|
361
370
|
}
|
|
362
371
|
}
|
|
363
|
-
}
|
|
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
|
+
}
|
|
364
393
|
}
|
|
365
394
|
|
|
366
395
|
// Split an entity with parameters into two entity types with their entity sets,
|
|
@@ -417,7 +446,6 @@ function initializeModel(csn, _options)
|
|
|
417
446
|
assignProp(parameterCsn, '_SetAttributes',
|
|
418
447
|
{'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.pageable': false });
|
|
419
448
|
|
|
420
|
-
assignProp(parameterCsn, '$keys', Object.create(null));
|
|
421
449
|
setProp(parameterCsn, '$isParamEntity', true);
|
|
422
450
|
setProp(parameterCsn, '$mySchemaName', entityCsn.$mySchemaName);
|
|
423
451
|
|
|
@@ -435,9 +463,10 @@ function initializeModel(csn, _options)
|
|
|
435
463
|
elt.name = n;
|
|
436
464
|
delete elt.kind;
|
|
437
465
|
elt.key = true; // params become primary key in parameter entity
|
|
438
|
-
parameterCsn
|
|
466
|
+
parameterCsn.elements[n] = elt;
|
|
439
467
|
});
|
|
440
|
-
|
|
468
|
+
linkAssociationTarget(parameterCsn);
|
|
469
|
+
initializeContainments(parameterCsn);
|
|
441
470
|
// add assoc to result set, FIXME: is the cardinality correct?
|
|
442
471
|
parameterCsn.elements[parameterToOriginalAssocName] = {
|
|
443
472
|
'@odata.contained': true,
|
|
@@ -447,6 +476,16 @@ function initializeModel(csn, _options)
|
|
|
447
476
|
cardinality: { src: 1, min: 0, max: '*' }
|
|
448
477
|
};
|
|
449
478
|
setProp(parameterCsn.elements[parameterToOriginalAssocName], '_target', entityCsn);
|
|
479
|
+
setProp(parameterCsn.elements[parameterToOriginalAssocName], '$path',
|
|
480
|
+
[ 'definitions', parameterEntityName, 'elements', parameterToOriginalAssocName ] );
|
|
481
|
+
|
|
482
|
+
// rewrite $path
|
|
483
|
+
setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
|
|
484
|
+
forEachMemberRecursively(parameterCsn, (member) => {
|
|
485
|
+
if(member.$path)
|
|
486
|
+
member.$path[1] = parameterEntityName;
|
|
487
|
+
});
|
|
488
|
+
|
|
450
489
|
|
|
451
490
|
csn.definitions[parameterCsn.name] = parameterCsn;
|
|
452
491
|
// modify the original parameter entity with backlink and new name
|
|
@@ -454,7 +493,6 @@ function initializeModel(csn, _options)
|
|
|
454
493
|
delete csn.definitions[entityCsn.name];
|
|
455
494
|
entityCsn.name = originalEntityName;
|
|
456
495
|
setProp(entityCsn, '$entitySetName', originalEntitySetName);
|
|
457
|
-
|
|
458
496
|
// add backlink association
|
|
459
497
|
if(hasBacklink) {
|
|
460
498
|
entityCsn.elements[backlinkAssocName] = {
|
|
@@ -465,6 +503,16 @@ function initializeModel(csn, _options)
|
|
|
465
503
|
};
|
|
466
504
|
setProp(entityCsn.elements[backlinkAssocName], '_selfReferences', []);
|
|
467
505
|
setProp(entityCsn.elements[backlinkAssocName], '_target', parameterCsn);
|
|
506
|
+
setProp(entityCsn.elements[backlinkAssocName], '$path',
|
|
507
|
+
[ 'definitions', originalEntityName, 'elements', backlinkAssocName ] );
|
|
508
|
+
|
|
509
|
+
// rewrite $path
|
|
510
|
+
if(entityCsn.$path)
|
|
511
|
+
entityCsn.$path[1] = originalEntityName;
|
|
512
|
+
forEachMemberRecursively(entityCsn, (member) => {
|
|
513
|
+
if(member.$path)
|
|
514
|
+
member.$path[1] = originalEntityName;
|
|
515
|
+
});
|
|
468
516
|
}
|
|
469
517
|
|
|
470
518
|
/*
|
|
@@ -492,6 +540,24 @@ function initializeModel(csn, _options)
|
|
|
492
540
|
setProp(element, '_parent', struct);
|
|
493
541
|
}
|
|
494
542
|
|
|
543
|
+
// convert $path to path starting at main artifact
|
|
544
|
+
function $path2path(p) {
|
|
545
|
+
const path = [];
|
|
546
|
+
let env = csn;
|
|
547
|
+
for (let i = 0; p && env && i < p.length; i++) {
|
|
548
|
+
const ps = p[i];
|
|
549
|
+
env = env[ps];
|
|
550
|
+
if (env && env.constructor === Object) {
|
|
551
|
+
path.push(ps);
|
|
552
|
+
if(env.items)
|
|
553
|
+
env = env.items;
|
|
554
|
+
if(env.type && !isBuiltinType(env.type) && !env.elements)
|
|
555
|
+
env = csn.definitions[env.type];
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return path;
|
|
559
|
+
}
|
|
560
|
+
|
|
495
561
|
// Initialize a structured artifact
|
|
496
562
|
function initializeStructure(def) {
|
|
497
563
|
|
|
@@ -503,16 +569,17 @@ function initializeModel(csn, _options)
|
|
|
503
569
|
let keys = Object.create(null);
|
|
504
570
|
let validFrom = [], validKey = [];
|
|
505
571
|
|
|
506
|
-
let structParent = def.items || def;
|
|
507
|
-
|
|
508
572
|
// Iterate all struct elements
|
|
509
|
-
|
|
510
|
-
|
|
573
|
+
forEachMemberRecursively(def.items || def, (element, elementName, prop, path = [], construct) => {
|
|
574
|
+
if(!['elements'].includes(prop))
|
|
575
|
+
return;
|
|
576
|
+
|
|
577
|
+
initElement(element, elementName, construct);
|
|
511
578
|
|
|
512
579
|
if(!['event', 'aspect'].includes(def.kind)) {
|
|
513
580
|
if(element._parent && element._parent.$mySchemaName) {
|
|
514
581
|
if(!isODataSimpleIdentifier(elementName)) {
|
|
515
|
-
signalIllegalIdentifier(elementName, ['definitions', def.name
|
|
582
|
+
signalIllegalIdentifier(elementName, ['definitions', def.name].concat(path));
|
|
516
583
|
} else if (options.isV2() && /^(_|[0-9])/.test(elementName) && ['view', 'entity'].includes(element._parent.kind)) {
|
|
517
584
|
// FIXME: Rewrite signalIllegalIdentifier function to be more flexible
|
|
518
585
|
error(null, ['definitions', def.name, 'elements', elementName], { prop: elementName[0] },
|
|
@@ -533,13 +600,17 @@ function initializeModel(csn, _options)
|
|
|
533
600
|
// in case this is a forward assoc, store the backlink partners here, _selfReferences.length > 1 => error
|
|
534
601
|
assignProp(element, '_selfReferences', []);
|
|
535
602
|
assignProp(element._target, '$proxies', []);
|
|
603
|
+
// $abspath is used as partner path
|
|
604
|
+
assignProp(element, '$abspath', $path2path(element.$path));
|
|
536
605
|
|
|
537
606
|
//forward annotations from managed association element to its foreign keys
|
|
538
607
|
if(element.keys && options.isFlatFormat) {
|
|
608
|
+
const elements = construct.items && construct.items.elements || construct.elements;
|
|
539
609
|
for(let fk of element.keys) {
|
|
540
610
|
forAll(element, (attr, attrName) => {
|
|
541
|
-
if(attrName[0] === '@')
|
|
542
|
-
|
|
611
|
+
if(attrName[0] === '@' && fk.$generatedFieldName && elements && elements[fk.$generatedFieldName]) {
|
|
612
|
+
elements[fk.$generatedFieldName][attrName] = attr;
|
|
613
|
+
}
|
|
543
614
|
});
|
|
544
615
|
}
|
|
545
616
|
}
|
|
@@ -552,7 +623,7 @@ function initializeModel(csn, _options)
|
|
|
552
623
|
keys[elementName] = element;
|
|
553
624
|
}
|
|
554
625
|
applyAppSpecificLateCsnTransformationOnElement(options, element, def, error);
|
|
555
|
-
});
|
|
626
|
+
}, [], true, { elementsOnly: true });
|
|
556
627
|
|
|
557
628
|
if(!isDeprecatedEnabled(options, 'v1KeysForTemporal')) {
|
|
558
629
|
// if artifact has a cds.valid.key mention it as @Core.AlternateKey
|
|
@@ -604,14 +675,14 @@ function initializeModel(csn, _options)
|
|
|
604
675
|
if(!isStructuredArtifact(struct))
|
|
605
676
|
return;
|
|
606
677
|
|
|
607
|
-
|
|
678
|
+
forEachMemberRecursively(struct.items || struct, (element) => {
|
|
608
679
|
if (isAssociationOrComposition(element) && !element._ignore) {
|
|
609
680
|
// setup the constraints object
|
|
610
681
|
setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
|
|
611
682
|
// and crack the ON condition
|
|
612
|
-
resolveOnConditionAndPrepareConstraints(element, messageFunctions);
|
|
683
|
+
resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
|
|
613
684
|
}
|
|
614
|
-
});
|
|
685
|
+
}, [], true, { elementsOnly: true });
|
|
615
686
|
}
|
|
616
687
|
|
|
617
688
|
/*
|
|
@@ -627,13 +698,16 @@ function initializeModel(csn, _options)
|
|
|
627
698
|
4) All of this can be revoked with options.renderForeignKeys.
|
|
628
699
|
*/
|
|
629
700
|
function ignoreProperties(struct) {
|
|
630
|
-
|
|
701
|
+
if(!isStructuredArtifact(struct))
|
|
702
|
+
return;
|
|
703
|
+
|
|
704
|
+
forEachMemberRecursively(struct.items || struct, (element) => {
|
|
631
705
|
if(!element.target) {
|
|
632
706
|
if(element['@odata.foreignKey4']) {
|
|
633
707
|
let isContainerAssoc = false;
|
|
634
|
-
let elements = struct.elements;
|
|
708
|
+
let elements = (struct.items || struct).elements;
|
|
635
709
|
let assoc = undefined;
|
|
636
|
-
|
|
710
|
+
const paths = element['@odata.foreignKey4'].split('.')
|
|
637
711
|
for(let p of paths) {
|
|
638
712
|
assoc = elements[p];
|
|
639
713
|
if(assoc) // could be that the @odata.foreignKey4 was propagated...
|
|
@@ -672,7 +746,7 @@ function initializeModel(csn, _options)
|
|
|
672
746
|
// ignore it if option odataContainment is true and no foreign keys should be rendered
|
|
673
747
|
assignAnnotation(element, '@odata.navigable', false);
|
|
674
748
|
}
|
|
675
|
-
});
|
|
749
|
+
}, [], true, { elementsOnly: true });
|
|
676
750
|
}
|
|
677
751
|
|
|
678
752
|
/*
|
|
@@ -684,9 +758,9 @@ function initializeModel(csn, _options)
|
|
|
684
758
|
if(!isStructuredArtifact(struct))
|
|
685
759
|
return;
|
|
686
760
|
|
|
687
|
-
|
|
761
|
+
forEachMemberRecursively(struct.items || struct, (element) => {
|
|
688
762
|
if (isAssociationOrComposition(element) && !element._ignore) {
|
|
689
|
-
finalizeReferentialConstraints(element, options,
|
|
763
|
+
finalizeReferentialConstraints(csn, element, options, info);
|
|
690
764
|
|
|
691
765
|
if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
|
|
692
766
|
// if this is a partnership and this assoc has a set target cardinality, assign it as source cardinality to the partner
|
|
@@ -711,7 +785,7 @@ function initializeModel(csn, _options)
|
|
|
711
785
|
}
|
|
712
786
|
}
|
|
713
787
|
}
|
|
714
|
-
});
|
|
788
|
+
}, [], true, { elementsOnly: true });
|
|
715
789
|
}
|
|
716
790
|
|
|
717
791
|
/*
|
|
@@ -774,7 +848,7 @@ function initializeModel(csn, _options)
|
|
|
774
848
|
const globalSchemaPrefix = whatsMyServiceRootName(struct.$mySchemaName);
|
|
775
849
|
// if this artifact is a service member check its associations
|
|
776
850
|
if(globalSchemaPrefix) {
|
|
777
|
-
forEachGeneric(struct, 'elements', element => {
|
|
851
|
+
forEachGeneric(struct.items || struct, 'elements', element => {
|
|
778
852
|
if(!isAssociationOrComposition(element) || element._ignore || element['@odata.navigable'] === false)
|
|
779
853
|
return;
|
|
780
854
|
/*
|
|
@@ -832,7 +906,7 @@ function initializeModel(csn, _options)
|
|
|
832
906
|
}
|
|
833
907
|
else {
|
|
834
908
|
// fake the target to be proxy
|
|
835
|
-
element._target
|
|
909
|
+
setProp(element._target, '$externalRef', true);
|
|
836
910
|
}
|
|
837
911
|
}
|
|
838
912
|
else {
|
|
@@ -881,7 +955,6 @@ function initializeModel(csn, _options)
|
|
|
881
955
|
setProp(proxy, '$keys', Object.create(null));
|
|
882
956
|
setProp(proxy, '$hasEntitySet', false);
|
|
883
957
|
setProp(proxy, '$exposedTypes', Object.create(null));
|
|
884
|
-
|
|
885
958
|
// copy all annotations of the target to the proxy
|
|
886
959
|
Object.entries(assoc._target).forEach(([k, v]) => {
|
|
887
960
|
if(k[0] === '@')
|
|
@@ -1028,10 +1101,12 @@ function initializeModel(csn, _options)
|
|
|
1028
1101
|
if(!elem.target) {
|
|
1029
1102
|
type.elements[elemName] = Object.create(null);
|
|
1030
1103
|
Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
|
|
1104
|
+
type.elements[elemName].notNull = true;
|
|
1031
1105
|
}
|
|
1032
1106
|
else {
|
|
1033
1107
|
type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
|
|
1034
1108
|
}
|
|
1109
|
+
setProp(type.elements[elemName], 'name', elem.name);
|
|
1035
1110
|
});
|
|
1036
1111
|
return type;
|
|
1037
1112
|
}
|
|
@@ -1058,6 +1133,7 @@ function initializeModel(csn, _options)
|
|
|
1058
1133
|
// art is in the target side, clone it and remove key property
|
|
1059
1134
|
let cloneArt = cloneCsn(art, options);
|
|
1060
1135
|
setProp(cloneArt, 'name', art.name);
|
|
1136
|
+
cloneArt.notNull = true;
|
|
1061
1137
|
delete cloneArt.key;
|
|
1062
1138
|
newElt.elements[art.name] = cloneArt;
|
|
1063
1139
|
});
|
|
@@ -1147,14 +1223,14 @@ function initializeModel(csn, _options)
|
|
|
1147
1223
|
function registerProxy(proxy, element) {
|
|
1148
1224
|
if(proxy === undefined)
|
|
1149
1225
|
return undefined;
|
|
1150
|
-
const
|
|
1151
|
-
const
|
|
1226
|
+
const fqProxyName = globalSchemaPrefix + '.' + proxy.name;
|
|
1227
|
+
const fqSchemaName = globalSchemaPrefix + '.' + proxy.$mySchemaName;
|
|
1152
1228
|
|
|
1153
1229
|
if(!element._target.$cachedProxy)
|
|
1154
1230
|
assignProp(element._target, '$cachedProxy', Object.create(null));
|
|
1155
1231
|
if(getProxyForTargetOf(element)) {
|
|
1156
1232
|
info(null, ['definitions', struct.name, 'elements', element.name],
|
|
1157
|
-
{ name:
|
|
1233
|
+
{ name: fqProxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
|
|
1158
1234
|
}
|
|
1159
1235
|
else
|
|
1160
1236
|
element._target.$cachedProxy[globalSchemaPrefix] = proxy;
|
|
@@ -1164,7 +1240,7 @@ function initializeModel(csn, _options)
|
|
|
1164
1240
|
// (that may reside in another subcontext schema), but only once
|
|
1165
1241
|
const schemaSet = new Set();
|
|
1166
1242
|
// start with the schema name for the proxy
|
|
1167
|
-
schemaSet.add(
|
|
1243
|
+
schemaSet.add(fqSchemaName);
|
|
1168
1244
|
// followed by all namespaces that are potentially exposed by the exposed types
|
|
1169
1245
|
// don't forget to prepend the global namespace prefix
|
|
1170
1246
|
// schemas are ordered in csn2edm.js for each service
|
|
@@ -1177,12 +1253,16 @@ function initializeModel(csn, _options)
|
|
|
1177
1253
|
}
|
|
1178
1254
|
});
|
|
1179
1255
|
/** @type {object} */
|
|
1180
|
-
const alreadyRegistered = csn.definitions[
|
|
1256
|
+
const alreadyRegistered = csn.definitions[fqProxyName]
|
|
1181
1257
|
if(!alreadyRegistered) {
|
|
1182
|
-
csn.definitions[
|
|
1258
|
+
csn.definitions[fqProxyName] = proxy;
|
|
1259
|
+
setProp(proxy, '$path', ['definitions', fqProxyName]);
|
|
1183
1260
|
Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
|
|
1184
|
-
|
|
1185
|
-
|
|
1261
|
+
const fqtn = globalSchemaPrefix + '.' + tn;
|
|
1262
|
+
if(csn.definitions[fqtn] === undefined) {
|
|
1263
|
+
csn.definitions[fqtn] = v;
|
|
1264
|
+
setProp(v, '$path', ['definitions', fqtn]);
|
|
1265
|
+
}
|
|
1186
1266
|
});
|
|
1187
1267
|
info(null, ['definitions', element._parent.name, 'elements', element.name],
|
|
1188
1268
|
{ name: proxy.name }, 'Created proxy EDM entity type $(NAME)');
|
|
@@ -1190,16 +1270,16 @@ function initializeModel(csn, _options)
|
|
|
1190
1270
|
else if(alreadyRegistered && !alreadyRegistered.$proxy &&
|
|
1191
1271
|
!['entity', 'view'].includes(alreadyRegistered.kind)) {
|
|
1192
1272
|
warning(null, ['definitions', element._parent.name, 'elements', element.name],
|
|
1193
|
-
{ name:
|
|
1273
|
+
{ name: fqProxyName, kind: alreadyRegistered.kind },
|
|
1194
1274
|
'No proxy EDM entity type created due to name collision with $(NAME) of kind $(KIND)');
|
|
1195
1275
|
return undefined;
|
|
1196
1276
|
}
|
|
1197
1277
|
}
|
|
1198
1278
|
else {
|
|
1199
1279
|
// it's a service reference, just add that reference proxy
|
|
1200
|
-
if(!schemas[
|
|
1201
|
-
schemas[
|
|
1202
|
-
schemaNames.push(
|
|
1280
|
+
if(!schemas[fqSchemaName]) {
|
|
1281
|
+
schemas[fqSchemaName] = proxy;
|
|
1282
|
+
schemaNames.push(fqSchemaName);
|
|
1203
1283
|
info(null, ['definitions', struct.name, 'elements', element.name],
|
|
1204
1284
|
{ name: proxy.name }, 'Created EDM namespace reference $(NAME)');
|
|
1205
1285
|
}
|
|
@@ -1244,11 +1324,8 @@ function initializeModel(csn, _options)
|
|
|
1244
1324
|
else if(!k.target) {
|
|
1245
1325
|
struct.$edmKeyPaths.push([kn]);
|
|
1246
1326
|
}
|
|
1247
|
-
//
|
|
1248
|
-
|
|
1249
|
-
const pathToElement = ['definitions', struct.name, 'elements', k.name];
|
|
1250
|
-
signalErrorForNullableKey(pathToElement);
|
|
1251
|
-
}
|
|
1327
|
+
// check toplevel key for spec violations
|
|
1328
|
+
checkKeySpecViolations(k, ['definitions', struct.name, 'elements', k.name]);
|
|
1252
1329
|
}
|
|
1253
1330
|
});
|
|
1254
1331
|
}
|
|
@@ -1264,7 +1341,7 @@ function initializeModel(csn, _options)
|
|
|
1264
1341
|
If element is of scalar type, return it as an array.
|
|
1265
1342
|
*/
|
|
1266
1343
|
function produceKeyRefPaths(eltCsn, prefix) {
|
|
1267
|
-
|
|
1344
|
+
const keyPaths = [];
|
|
1268
1345
|
if(!isEdmPropertyRendered(eltCsn, options)) {
|
|
1269
1346
|
// let annos = Object.keys(eltCsn).filter(a=>a[0]==='@').join(', ');
|
|
1270
1347
|
// warning(null, ['definitions', struct.name, 'elements', eltCsn.name ],
|
|
@@ -1272,15 +1349,19 @@ function initializeModel(csn, _options)
|
|
|
1272
1349
|
return keyPaths;
|
|
1273
1350
|
}
|
|
1274
1351
|
// OData requires all elements along the path to be nullable: false (that is either key or notNull)
|
|
1275
|
-
|
|
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;
|
|
1276
1355
|
if(elements) {
|
|
1277
1356
|
Object.entries(elements).forEach(([eltName, elt]) => {
|
|
1278
|
-
|
|
1279
|
-
if(
|
|
1280
|
-
|
|
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}`;
|
|
1281
1362
|
// we want to point to the element in the entity which is the first path step
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1363
|
+
const location = struct.$path.concat(['elements']).concat(pathSegment.split('/')[0]);
|
|
1364
|
+
checkKeySpecViolations(elt, location, pathSegment);
|
|
1284
1365
|
}
|
|
1285
1366
|
});
|
|
1286
1367
|
}
|
|
@@ -1311,17 +1392,169 @@ function initializeModel(csn, _options)
|
|
|
1311
1392
|
return keyPaths;
|
|
1312
1393
|
}
|
|
1313
1394
|
|
|
1314
|
-
function
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
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));
|
|
1320
1485
|
}
|
|
1321
|
-
|
|
1486
|
+
}
|
|
1322
1487
|
}
|
|
1323
1488
|
}
|
|
1324
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
|
+
}
|
|
1325
1558
|
|
|
1326
1559
|
function determineEntitySet(struct) {
|
|
1327
1560
|
// if this is an entity or a view, determine if an entity set is required or not
|
|
@@ -1372,7 +1605,7 @@ function initializeModel(csn, _options)
|
|
|
1372
1605
|
//
|
|
1373
1606
|
|
|
1374
1607
|
function inboundQualificationChecks() {
|
|
1375
|
-
forEachDefinition(csn, [ checkChainedArray
|
|
1608
|
+
forEachDefinition(csn, [ checkChainedArray ]);
|
|
1376
1609
|
checkNestedContextsAndServices();
|
|
1377
1610
|
throwWithError();
|
|
1378
1611
|
|
|
@@ -1404,18 +1637,6 @@ function initializeModel(csn, _options)
|
|
|
1404
1637
|
}
|
|
1405
1638
|
}
|
|
1406
1639
|
|
|
1407
|
-
function checkODataV2Limitations(def, defName) {
|
|
1408
|
-
if (!options.isV2() || !whatsMyServiceRootName(defName))
|
|
1409
|
-
return;
|
|
1410
|
-
|
|
1411
|
-
forEachMemberRecursively(def, (member, _memberName, prop, path) => {
|
|
1412
|
-
if (def.kind && def.kind === 'type' && isAssocOrComposition(member.type))
|
|
1413
|
-
warning('odata-spec-violation-assoc', path, 'User-defined structured types must not contain associations for OData V2');
|
|
1414
|
-
else if (prop === 'elements' && (member.items || (member.type && getFinalTypeDef(member.type).items)))
|
|
1415
|
-
warning('odata-spec-violation-array-of', path, { keyword: 'many/array of' }, 'Predicate $(KEYWORD) not allowed on elements for OData V2');
|
|
1416
|
-
}, ['definitions', defName]);
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
1640
|
function checkNestedContextsAndServices() {
|
|
1420
1641
|
!isBetaEnabled(options, 'nestedServices') && serviceRootNames.forEach(sn => {
|
|
1421
1642
|
const parent = whatsMyServiceRootName(sn, false);
|
|
@@ -1544,38 +1765,51 @@ function initializeModel(csn, _options)
|
|
|
1544
1765
|
// short cut annotation @readonly that gets expanded and can be safely remapped.
|
|
1545
1766
|
function rewriteContainmentAnnotations(container, containee, assocName) {
|
|
1546
1767
|
// rectify Restrictions to NavigationRestrictions
|
|
1547
|
-
if(options.isV4()
|
|
1548
|
-
let
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
navRestr.RestrictedProperties[0].DeleteRestrictions =
|
|
1558
|
-
{ 'Deletable': containee['@Capabilities.DeleteRestrictions.Deletable'] };
|
|
1559
|
-
hasRestrictions = true;
|
|
1768
|
+
if(options.isV4()) {
|
|
1769
|
+
let navPropEntry;
|
|
1770
|
+
let hasEntry = false;
|
|
1771
|
+
let newEntry = false;
|
|
1772
|
+
const anno = '@Capabilities.NavigationRestrictions.RestrictedProperties';
|
|
1773
|
+
let resProps = container[anno];
|
|
1774
|
+
// merge into existing anno, if available
|
|
1775
|
+
if(resProps) {
|
|
1776
|
+
navPropEntry = resProps.find(p => p.NavigationProperty && p.NavigationProperty['='] === assocName);
|
|
1777
|
+
hasEntry = !!navPropEntry;
|
|
1560
1778
|
}
|
|
1561
|
-
if(
|
|
1562
|
-
|
|
1563
|
-
{ 'Insertable': containee['@Capabilities.InsertRestrictions.Insertable'] };
|
|
1564
|
-
hasRestrictions = true;
|
|
1779
|
+
if(!navPropEntry) {
|
|
1780
|
+
navPropEntry = { NavigationProperty: { '=': assocName } };
|
|
1565
1781
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1782
|
+
|
|
1783
|
+
const props = Object.entries(containee);
|
|
1784
|
+
|
|
1785
|
+
const merge = (prefix) => {
|
|
1786
|
+
const prop = prefix.split('.')[1];
|
|
1787
|
+
// don't overwrite existing restrictions
|
|
1788
|
+
if(!navPropEntry[prop]) {
|
|
1789
|
+
// Filter properties with prefix and reduce them into a new dictionary
|
|
1790
|
+
const o = props.filter(p => p[0].startsWith(prefix+'.')).reduce((a,c) => {
|
|
1791
|
+
a[c[0].replace(prefix+'.', '')] = c[1];
|
|
1792
|
+
return a;
|
|
1793
|
+
}, { });
|
|
1794
|
+
// if dictionary has entries, add them to navPropEnty
|
|
1795
|
+
if(Object.keys(o).length) {
|
|
1796
|
+
navPropEntry[prop] = o;
|
|
1797
|
+
newEntry = true;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1570
1800
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1801
|
+
merge('@Capabilities.DeleteRestrictions');
|
|
1802
|
+
merge('@Capabilities.InsertRestrictions');
|
|
1803
|
+
merge('@Capabilities.UpdateRestrictions');
|
|
1804
|
+
merge('@Capabilities.ReadRestrictions');
|
|
1805
|
+
|
|
1806
|
+
if(newEntry) {
|
|
1807
|
+
if(!hasEntry) {
|
|
1808
|
+
if(!resProps)
|
|
1809
|
+
resProps = container[anno] = [ ];
|
|
1810
|
+
resProps.push(navPropEntry);
|
|
1811
|
+
}
|
|
1576
1812
|
}
|
|
1577
|
-
if(hasRestrictions)
|
|
1578
|
-
container['@Capabilities.NavigationRestrictions'] = navRestr;
|
|
1579
1813
|
}
|
|
1580
1814
|
}
|
|
1581
1815
|
|
|
@@ -1784,7 +2018,7 @@ function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error
|
|
|
1784
2018
|
let key = { name: keyName, key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false, '@UI.Hidden': true };
|
|
1785
2019
|
elements[keyName] = key;
|
|
1786
2020
|
setProp(struct, '$keys',{ [keyName] : key } );
|
|
1787
|
-
forEachGeneric(struct, 'elements', (e,n) =>
|
|
2021
|
+
forEachGeneric(struct.items || struct, 'elements', (e,n) =>
|
|
1788
2022
|
{
|
|
1789
2023
|
if(e.key) delete e.key;
|
|
1790
2024
|
elements[n] = e;
|