@sap/cds-compiler 2.5.2 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +235 -9
- package/bin/cdsc.js +44 -27
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +37 -3
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +37 -123
- package/lib/api/options.js +27 -15
- package/lib/api/validate.js +34 -9
- package/lib/backends.js +9 -89
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +73 -11
- package/lib/base/messages.js +86 -30
- package/lib/base/model.js +6 -6
- package/lib/base/optionProcessorHelper.js +56 -22
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- 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 +25 -7
- package/lib/checks/selectItems.js +29 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +41 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +60 -7
- package/lib/compiler/assert-consistency.js +23 -7
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +30 -1
- package/lib/compiler/checks.js +8 -5
- package/lib/compiler/definer.js +157 -133
- package/lib/compiler/index.js +89 -31
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +375 -185
- package/lib/compiler/shared.js +49 -202
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +104 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +388 -146
- package/lib/edm/edmUtils.js +104 -34
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +28 -1
- package/lib/gen/language.tokens +79 -69
- package/lib/gen/languageLexer.interp +28 -1
- package/lib/gen/languageLexer.js +879 -805
- package/lib/gen/languageLexer.tokens +71 -62
- package/lib/gen/languageParser.js +5330 -4300
- package/lib/json/from-csn.js +110 -52
- package/lib/json/to-csn.js +434 -120
- package/lib/language/antlrParser.js +15 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +93 -26
- package/lib/language/language.g4 +172 -31
- package/lib/main.d.ts +216 -19
- package/lib/main.js +32 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +413 -149
- package/lib/model/csnUtils.js +286 -75
- package/lib/model/enrichCsn.js +50 -6
- package/lib/model/revealInternalProperties.js +22 -5
- package/lib/modelCompare/compare.js +39 -21
- package/lib/optionProcessor.js +35 -18
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +9 -6
- package/lib/render/toCdl.js +121 -36
- package/lib/render/toHdbcds.js +148 -98
- package/lib/render/toSql.js +114 -43
- package/lib/render/utils/common.js +8 -13
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/assertUnique.js +5 -6
- package/lib/transform/db/constraints.js +281 -106
- package/lib/transform/db/draft.js +11 -8
- package/lib/transform/db/expansion.js +584 -0
- package/lib/transform/db/flattening.js +341 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +345 -65
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +131 -793
- package/lib/transform/forOdataNew.js +30 -24
- package/lib/transform/localized.js +39 -10
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +60 -39
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +19 -18
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +144 -78
- package/lib/transform/translateAssocsToJoins.js +22 -27
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +5 -14
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
- package/lib/json/walker.js +0 -26
- package/lib/transform/sqlite +0 -0
- 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);
|
|
@@ -56,7 +54,7 @@ function initializeModel(csn, _options)
|
|
|
56
54
|
let options = validateOptions(_options);
|
|
57
55
|
|
|
58
56
|
// Fetch service definitions
|
|
59
|
-
const serviceRoots = Object.keys(csn.definitions).reduce((serviceRoots, artName) => {
|
|
57
|
+
const serviceRoots = Object.keys(csn.definitions || {}).reduce((serviceRoots, artName) => {
|
|
60
58
|
const art = csn.definitions[artName];
|
|
61
59
|
if(art.kind === 'service') {
|
|
62
60
|
serviceRoots[artName] = Object.assign(art, { name: artName });
|
|
@@ -69,6 +67,9 @@ function initializeModel(csn, _options)
|
|
|
69
67
|
function whatsMyServiceRootName(n, self=true) {
|
|
70
68
|
return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
|
|
71
69
|
}
|
|
70
|
+
if(serviceRootNames.length === 0) {
|
|
71
|
+
return [serviceRoots, Object.create(null), whatsMyServiceRootName, options];
|
|
72
|
+
}
|
|
72
73
|
|
|
73
74
|
// Structural CSN inbound QA checks
|
|
74
75
|
inboundQualificationChecks();
|
|
@@ -142,14 +143,18 @@ function initializeModel(csn, _options)
|
|
|
142
143
|
// must be run before proxy exposure to avoid potential reference collisions
|
|
143
144
|
convertExposedTypesOfOtherServicesIntoCrossReferences();
|
|
144
145
|
// create association target proxies
|
|
145
|
-
|
|
146
|
+
// Decide if an entity set needs to be constructed or not
|
|
147
|
+
forEachDefinition(csn, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
|
|
148
|
+
if(options.isV4())
|
|
149
|
+
forEachDefinition(csn, initializeEdmNavPropBindingTargets);
|
|
146
150
|
|
|
147
151
|
// Things that can be done in one pass
|
|
148
152
|
// Create edmKeyRefPaths
|
|
149
|
-
//
|
|
153
|
+
// Create NavigationPropertyBindings, requires determineEntitySet
|
|
150
154
|
// Map /** doc comments */ to @CoreDescription
|
|
151
155
|
// Artifact identifier spec compliance check (should be run last)
|
|
152
|
-
forEachDefinition(csn, [ initializeEdmKeyRefPaths,
|
|
156
|
+
forEachDefinition(csn, [ initializeEdmKeyRefPaths, initializeEdmNavPropBindingPaths,
|
|
157
|
+
initializeEdmTypesAndDescription, checkArtifactIdentifierAndBoundActions ]);
|
|
153
158
|
}
|
|
154
159
|
return [serviceRoots, schemas, whatsMyServiceRootName, options];
|
|
155
160
|
|
|
@@ -216,6 +221,9 @@ function initializeModel(csn, _options)
|
|
|
216
221
|
if(member.target && dotEntityNameMap[member.target]) {
|
|
217
222
|
member.target = dotEntityNameMap[member.target];
|
|
218
223
|
}
|
|
224
|
+
if(member.$path && dotEntityNameMap[member.$path[1]]) {
|
|
225
|
+
member.$path[1] = dotEntityNameMap[member.$path[1]]
|
|
226
|
+
}
|
|
219
227
|
_rewriteReferencesInActions(member);
|
|
220
228
|
});
|
|
221
229
|
// handle unbound action/function and params in views
|
|
@@ -334,33 +342,57 @@ function initializeModel(csn, _options)
|
|
|
334
342
|
// entity set. Instead try to rewrite the annotation in such a way that it is effective
|
|
335
343
|
// on the containment navigation property.
|
|
336
344
|
function initializeContainments(container) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
345
|
+
if(['entity', 'view'].includes(container.kind)) {
|
|
346
|
+
forEachMemberRecursively(container, initContainments,
|
|
347
|
+
[], true, { elementsOnly: true });
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function initContainments(elt, eltName) {
|
|
351
|
+
if(isAssociationOrComposition(elt) && elt['@odata.contained'] && !elt._ignore) {
|
|
340
352
|
// Let the containee know its container
|
|
341
353
|
// (array because the contanee may contained more then once)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
354
|
+
let containee = elt._target;
|
|
355
|
+
if (!containee._containerEntity)
|
|
356
|
+
setProp(containee, '_containerEntity', []);
|
|
346
357
|
// add container only once per containee
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
358
|
+
if (!containee._containerEntity.includes(container.name))
|
|
359
|
+
containee._containerEntity.push(container.name);
|
|
360
|
+
// Mark associations in the containee pointing to the container (i.e. to this entity)
|
|
361
|
+
forEachMemberRecursively(containee, markToContainer,
|
|
362
|
+
[], true, { elementsOnly: true });
|
|
363
|
+
rewriteContainmentAnnotations(container, containee, eltName);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
// try to find elements to drill down further
|
|
367
|
+
while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
|
|
368
|
+
elt = csn.definitions[elt.type];
|
|
369
|
+
}
|
|
370
|
+
if(elt && elt.elements) {
|
|
371
|
+
forEachMemberRecursively(elt, initContainments,
|
|
372
|
+
[], true, { elementsOnly: true });
|
|
361
373
|
}
|
|
362
374
|
}
|
|
363
|
-
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function markToContainer(elt) {
|
|
378
|
+
if(elt._target && elt._target.name) {
|
|
379
|
+
// If this is an association that points to the container (but is not by itself contained,
|
|
380
|
+
// which would indicate the top role in a hierarchy) mark it with '_isToContainer'
|
|
381
|
+
if(elt._target.name === container.name && !elt['odata.contained']) {
|
|
382
|
+
setProp(elt, '_isToContainer', true);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
// try to find elements to drill down further
|
|
387
|
+
while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
|
|
388
|
+
elt = csn.definitions[elt.type];
|
|
389
|
+
}
|
|
390
|
+
if(elt && elt.elements) {
|
|
391
|
+
forEachMemberRecursively(elt, markToContainer,
|
|
392
|
+
[], true, { elementsOnly: true });
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
364
396
|
}
|
|
365
397
|
|
|
366
398
|
// Split an entity with parameters into two entity types with their entity sets,
|
|
@@ -417,7 +449,6 @@ function initializeModel(csn, _options)
|
|
|
417
449
|
assignProp(parameterCsn, '_SetAttributes',
|
|
418
450
|
{'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.pageable': false });
|
|
419
451
|
|
|
420
|
-
assignProp(parameterCsn, '$keys', Object.create(null));
|
|
421
452
|
setProp(parameterCsn, '$isParamEntity', true);
|
|
422
453
|
setProp(parameterCsn, '$mySchemaName', entityCsn.$mySchemaName);
|
|
423
454
|
|
|
@@ -435,9 +466,10 @@ function initializeModel(csn, _options)
|
|
|
435
466
|
elt.name = n;
|
|
436
467
|
delete elt.kind;
|
|
437
468
|
elt.key = true; // params become primary key in parameter entity
|
|
438
|
-
parameterCsn
|
|
469
|
+
parameterCsn.elements[n] = elt;
|
|
439
470
|
});
|
|
440
|
-
|
|
471
|
+
linkAssociationTarget(parameterCsn);
|
|
472
|
+
initializeContainments(parameterCsn);
|
|
441
473
|
// add assoc to result set, FIXME: is the cardinality correct?
|
|
442
474
|
parameterCsn.elements[parameterToOriginalAssocName] = {
|
|
443
475
|
'@odata.contained': true,
|
|
@@ -447,6 +479,16 @@ function initializeModel(csn, _options)
|
|
|
447
479
|
cardinality: { src: 1, min: 0, max: '*' }
|
|
448
480
|
};
|
|
449
481
|
setProp(parameterCsn.elements[parameterToOriginalAssocName], '_target', entityCsn);
|
|
482
|
+
setProp(parameterCsn.elements[parameterToOriginalAssocName], '$path',
|
|
483
|
+
[ 'definitions', parameterEntityName, 'elements', parameterToOriginalAssocName ] );
|
|
484
|
+
|
|
485
|
+
// rewrite $path
|
|
486
|
+
setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
|
|
487
|
+
forEachMemberRecursively(parameterCsn, (member) => {
|
|
488
|
+
if(member.$path)
|
|
489
|
+
member.$path[1] = parameterEntityName;
|
|
490
|
+
});
|
|
491
|
+
|
|
450
492
|
|
|
451
493
|
csn.definitions[parameterCsn.name] = parameterCsn;
|
|
452
494
|
// modify the original parameter entity with backlink and new name
|
|
@@ -454,7 +496,6 @@ function initializeModel(csn, _options)
|
|
|
454
496
|
delete csn.definitions[entityCsn.name];
|
|
455
497
|
entityCsn.name = originalEntityName;
|
|
456
498
|
setProp(entityCsn, '$entitySetName', originalEntitySetName);
|
|
457
|
-
|
|
458
499
|
// add backlink association
|
|
459
500
|
if(hasBacklink) {
|
|
460
501
|
entityCsn.elements[backlinkAssocName] = {
|
|
@@ -465,6 +506,16 @@ function initializeModel(csn, _options)
|
|
|
465
506
|
};
|
|
466
507
|
setProp(entityCsn.elements[backlinkAssocName], '_selfReferences', []);
|
|
467
508
|
setProp(entityCsn.elements[backlinkAssocName], '_target', parameterCsn);
|
|
509
|
+
setProp(entityCsn.elements[backlinkAssocName], '$path',
|
|
510
|
+
[ 'definitions', originalEntityName, 'elements', backlinkAssocName ] );
|
|
511
|
+
|
|
512
|
+
// rewrite $path
|
|
513
|
+
if(entityCsn.$path)
|
|
514
|
+
entityCsn.$path[1] = originalEntityName;
|
|
515
|
+
forEachMemberRecursively(entityCsn, (member) => {
|
|
516
|
+
if(member.$path)
|
|
517
|
+
member.$path[1] = originalEntityName;
|
|
518
|
+
});
|
|
468
519
|
}
|
|
469
520
|
|
|
470
521
|
/*
|
|
@@ -492,6 +543,24 @@ function initializeModel(csn, _options)
|
|
|
492
543
|
setProp(element, '_parent', struct);
|
|
493
544
|
}
|
|
494
545
|
|
|
546
|
+
// convert $path to path starting at main artifact
|
|
547
|
+
function $path2path(p) {
|
|
548
|
+
const path = [];
|
|
549
|
+
let env = csn;
|
|
550
|
+
for (let i = 0; p && env && i < p.length; i++) {
|
|
551
|
+
const ps = p[i];
|
|
552
|
+
env = env[ps];
|
|
553
|
+
if (env && env.constructor === Object) {
|
|
554
|
+
path.push(ps);
|
|
555
|
+
if(env.items)
|
|
556
|
+
env = env.items;
|
|
557
|
+
if(env.type && !isBuiltinType(env.type) && !env.elements)
|
|
558
|
+
env = csn.definitions[env.type];
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return path;
|
|
562
|
+
}
|
|
563
|
+
|
|
495
564
|
// Initialize a structured artifact
|
|
496
565
|
function initializeStructure(def) {
|
|
497
566
|
|
|
@@ -503,16 +572,17 @@ function initializeModel(csn, _options)
|
|
|
503
572
|
let keys = Object.create(null);
|
|
504
573
|
let validFrom = [], validKey = [];
|
|
505
574
|
|
|
506
|
-
let structParent = def.items || def;
|
|
507
|
-
|
|
508
575
|
// Iterate all struct elements
|
|
509
|
-
|
|
510
|
-
|
|
576
|
+
forEachMemberRecursively(def.items || def, (element, elementName, prop, path = [], construct) => {
|
|
577
|
+
if(!['elements'].includes(prop))
|
|
578
|
+
return;
|
|
579
|
+
|
|
580
|
+
initElement(element, elementName, construct);
|
|
511
581
|
|
|
512
582
|
if(!['event', 'aspect'].includes(def.kind)) {
|
|
513
583
|
if(element._parent && element._parent.$mySchemaName) {
|
|
514
584
|
if(!isODataSimpleIdentifier(elementName)) {
|
|
515
|
-
signalIllegalIdentifier(elementName, ['definitions', def.name
|
|
585
|
+
signalIllegalIdentifier(elementName, ['definitions', def.name].concat(path));
|
|
516
586
|
} else if (options.isV2() && /^(_|[0-9])/.test(elementName) && ['view', 'entity'].includes(element._parent.kind)) {
|
|
517
587
|
// FIXME: Rewrite signalIllegalIdentifier function to be more flexible
|
|
518
588
|
error(null, ['definitions', def.name, 'elements', elementName], { prop: elementName[0] },
|
|
@@ -527,24 +597,24 @@ function initializeModel(csn, _options)
|
|
|
527
597
|
if(element['@cds.valid.from']) {
|
|
528
598
|
validFrom.push(element);
|
|
529
599
|
}
|
|
600
|
+
//forward annotations from managed association element to its foreign keys
|
|
601
|
+
const elements = construct.items && construct.items.elements || construct.elements;
|
|
602
|
+
forAll(elements[element['@odata.foreignKey4']], (attr, attrName) => {
|
|
603
|
+
if(attrName[0] === '@') {
|
|
604
|
+
element[attrName] = attr;
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
// and eventually remove some afterwards:)
|
|
608
|
+
if(options.isV2())
|
|
609
|
+
setSAPSpecificV2AnnotationsToAssociation(element);
|
|
530
610
|
|
|
531
611
|
// initialize an association
|
|
532
612
|
if(isAssociationOrComposition(element)) {
|
|
533
613
|
// in case this is a forward assoc, store the backlink partners here, _selfReferences.length > 1 => error
|
|
534
614
|
assignProp(element, '_selfReferences', []);
|
|
535
615
|
assignProp(element._target, '$proxies', []);
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if(element.keys && options.isFlatFormat) {
|
|
539
|
-
for(let fk of element.keys) {
|
|
540
|
-
forAll(element, (attr, attrName) => {
|
|
541
|
-
if(attrName[0] === '@' && fk.$generatedFieldName)
|
|
542
|
-
def.elements[fk.$generatedFieldName][attrName] = attr;
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
// and afterwards eventually remove some :)
|
|
547
|
-
setSAPSpecificV2AnnotationsToAssociation(options, element, def);
|
|
616
|
+
// $abspath is used as partner path
|
|
617
|
+
assignProp(element, '$abspath', $path2path(element.$path));
|
|
548
618
|
}
|
|
549
619
|
|
|
550
620
|
// Collect keys
|
|
@@ -552,7 +622,7 @@ function initializeModel(csn, _options)
|
|
|
552
622
|
keys[elementName] = element;
|
|
553
623
|
}
|
|
554
624
|
applyAppSpecificLateCsnTransformationOnElement(options, element, def, error);
|
|
555
|
-
});
|
|
625
|
+
}, [], true, { elementsOnly: true });
|
|
556
626
|
|
|
557
627
|
if(!isDeprecatedEnabled(options, 'v1KeysForTemporal')) {
|
|
558
628
|
// if artifact has a cds.valid.key mention it as @Core.AlternateKey
|
|
@@ -604,14 +674,14 @@ function initializeModel(csn, _options)
|
|
|
604
674
|
if(!isStructuredArtifact(struct))
|
|
605
675
|
return;
|
|
606
676
|
|
|
607
|
-
|
|
677
|
+
forEachMemberRecursively(struct.items || struct, (element) => {
|
|
608
678
|
if (isAssociationOrComposition(element) && !element._ignore) {
|
|
609
679
|
// setup the constraints object
|
|
610
680
|
setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
|
|
611
681
|
// and crack the ON condition
|
|
612
|
-
resolveOnConditionAndPrepareConstraints(element, messageFunctions);
|
|
682
|
+
resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
|
|
613
683
|
}
|
|
614
|
-
});
|
|
684
|
+
}, [], true, { elementsOnly: true });
|
|
615
685
|
}
|
|
616
686
|
|
|
617
687
|
/*
|
|
@@ -627,13 +697,16 @@ function initializeModel(csn, _options)
|
|
|
627
697
|
4) All of this can be revoked with options.renderForeignKeys.
|
|
628
698
|
*/
|
|
629
699
|
function ignoreProperties(struct) {
|
|
630
|
-
|
|
700
|
+
if(!isStructuredArtifact(struct))
|
|
701
|
+
return;
|
|
702
|
+
|
|
703
|
+
forEachMemberRecursively(struct.items || struct, (element) => {
|
|
631
704
|
if(!element.target) {
|
|
632
705
|
if(element['@odata.foreignKey4']) {
|
|
633
706
|
let isContainerAssoc = false;
|
|
634
|
-
let elements = struct.elements;
|
|
707
|
+
let elements = (struct.items || struct).elements;
|
|
635
708
|
let assoc = undefined;
|
|
636
|
-
|
|
709
|
+
const paths = element['@odata.foreignKey4'].split('.')
|
|
637
710
|
for(let p of paths) {
|
|
638
711
|
assoc = elements[p];
|
|
639
712
|
if(assoc) // could be that the @odata.foreignKey4 was propagated...
|
|
@@ -672,7 +745,7 @@ function initializeModel(csn, _options)
|
|
|
672
745
|
// ignore it if option odataContainment is true and no foreign keys should be rendered
|
|
673
746
|
assignAnnotation(element, '@odata.navigable', false);
|
|
674
747
|
}
|
|
675
|
-
});
|
|
748
|
+
}, [], true, { elementsOnly: true });
|
|
676
749
|
}
|
|
677
750
|
|
|
678
751
|
/*
|
|
@@ -684,9 +757,9 @@ function initializeModel(csn, _options)
|
|
|
684
757
|
if(!isStructuredArtifact(struct))
|
|
685
758
|
return;
|
|
686
759
|
|
|
687
|
-
|
|
760
|
+
forEachMemberRecursively(struct.items || struct, (element) => {
|
|
688
761
|
if (isAssociationOrComposition(element) && !element._ignore) {
|
|
689
|
-
finalizeReferentialConstraints(element, options,
|
|
762
|
+
finalizeReferentialConstraints(csn, element, options, info);
|
|
690
763
|
|
|
691
764
|
if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
|
|
692
765
|
// if this is a partnership and this assoc has a set target cardinality, assign it as source cardinality to the partner
|
|
@@ -711,7 +784,7 @@ function initializeModel(csn, _options)
|
|
|
711
784
|
}
|
|
712
785
|
}
|
|
713
786
|
}
|
|
714
|
-
});
|
|
787
|
+
}, [], true, { elementsOnly: true });
|
|
715
788
|
}
|
|
716
789
|
|
|
717
790
|
/*
|
|
@@ -774,7 +847,7 @@ function initializeModel(csn, _options)
|
|
|
774
847
|
const globalSchemaPrefix = whatsMyServiceRootName(struct.$mySchemaName);
|
|
775
848
|
// if this artifact is a service member check its associations
|
|
776
849
|
if(globalSchemaPrefix) {
|
|
777
|
-
forEachGeneric(struct, 'elements', element => {
|
|
850
|
+
forEachGeneric(struct.items || struct, 'elements', element => {
|
|
778
851
|
if(!isAssociationOrComposition(element) || element._ignore || element['@odata.navigable'] === false)
|
|
779
852
|
return;
|
|
780
853
|
/*
|
|
@@ -832,7 +905,7 @@ function initializeModel(csn, _options)
|
|
|
832
905
|
}
|
|
833
906
|
else {
|
|
834
907
|
// fake the target to be proxy
|
|
835
|
-
element._target
|
|
908
|
+
setProp(element._target, '$externalRef', true);
|
|
836
909
|
}
|
|
837
910
|
}
|
|
838
911
|
else {
|
|
@@ -881,7 +954,6 @@ function initializeModel(csn, _options)
|
|
|
881
954
|
setProp(proxy, '$keys', Object.create(null));
|
|
882
955
|
setProp(proxy, '$hasEntitySet', false);
|
|
883
956
|
setProp(proxy, '$exposedTypes', Object.create(null));
|
|
884
|
-
|
|
885
957
|
// copy all annotations of the target to the proxy
|
|
886
958
|
Object.entries(assoc._target).forEach(([k, v]) => {
|
|
887
959
|
if(k[0] === '@')
|
|
@@ -1028,10 +1100,12 @@ function initializeModel(csn, _options)
|
|
|
1028
1100
|
if(!elem.target) {
|
|
1029
1101
|
type.elements[elemName] = Object.create(null);
|
|
1030
1102
|
Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
|
|
1103
|
+
type.elements[elemName].notNull = true;
|
|
1031
1104
|
}
|
|
1032
1105
|
else {
|
|
1033
1106
|
type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
|
|
1034
1107
|
}
|
|
1108
|
+
setProp(type.elements[elemName], 'name', elem.name);
|
|
1035
1109
|
});
|
|
1036
1110
|
return type;
|
|
1037
1111
|
}
|
|
@@ -1058,6 +1132,7 @@ function initializeModel(csn, _options)
|
|
|
1058
1132
|
// art is in the target side, clone it and remove key property
|
|
1059
1133
|
let cloneArt = cloneCsn(art, options);
|
|
1060
1134
|
setProp(cloneArt, 'name', art.name);
|
|
1135
|
+
cloneArt.notNull = true;
|
|
1061
1136
|
delete cloneArt.key;
|
|
1062
1137
|
newElt.elements[art.name] = cloneArt;
|
|
1063
1138
|
});
|
|
@@ -1147,14 +1222,14 @@ function initializeModel(csn, _options)
|
|
|
1147
1222
|
function registerProxy(proxy, element) {
|
|
1148
1223
|
if(proxy === undefined)
|
|
1149
1224
|
return undefined;
|
|
1150
|
-
const
|
|
1151
|
-
const
|
|
1225
|
+
const fqProxyName = globalSchemaPrefix + '.' + proxy.name;
|
|
1226
|
+
const fqSchemaName = globalSchemaPrefix + '.' + proxy.$mySchemaName;
|
|
1152
1227
|
|
|
1153
1228
|
if(!element._target.$cachedProxy)
|
|
1154
1229
|
assignProp(element._target, '$cachedProxy', Object.create(null));
|
|
1155
1230
|
if(getProxyForTargetOf(element)) {
|
|
1156
1231
|
info(null, ['definitions', struct.name, 'elements', element.name],
|
|
1157
|
-
{ name:
|
|
1232
|
+
{ name: fqProxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
|
|
1158
1233
|
}
|
|
1159
1234
|
else
|
|
1160
1235
|
element._target.$cachedProxy[globalSchemaPrefix] = proxy;
|
|
@@ -1164,7 +1239,7 @@ function initializeModel(csn, _options)
|
|
|
1164
1239
|
// (that may reside in another subcontext schema), but only once
|
|
1165
1240
|
const schemaSet = new Set();
|
|
1166
1241
|
// start with the schema name for the proxy
|
|
1167
|
-
schemaSet.add(
|
|
1242
|
+
schemaSet.add(fqSchemaName);
|
|
1168
1243
|
// followed by all namespaces that are potentially exposed by the exposed types
|
|
1169
1244
|
// don't forget to prepend the global namespace prefix
|
|
1170
1245
|
// schemas are ordered in csn2edm.js for each service
|
|
@@ -1177,12 +1252,16 @@ function initializeModel(csn, _options)
|
|
|
1177
1252
|
}
|
|
1178
1253
|
});
|
|
1179
1254
|
/** @type {object} */
|
|
1180
|
-
const alreadyRegistered = csn.definitions[
|
|
1255
|
+
const alreadyRegistered = csn.definitions[fqProxyName]
|
|
1181
1256
|
if(!alreadyRegistered) {
|
|
1182
|
-
csn.definitions[
|
|
1257
|
+
csn.definitions[fqProxyName] = proxy;
|
|
1258
|
+
setProp(proxy, '$path', ['definitions', fqProxyName]);
|
|
1183
1259
|
Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
|
|
1184
|
-
|
|
1185
|
-
|
|
1260
|
+
const fqtn = globalSchemaPrefix + '.' + tn;
|
|
1261
|
+
if(csn.definitions[fqtn] === undefined) {
|
|
1262
|
+
csn.definitions[fqtn] = v;
|
|
1263
|
+
setProp(v, '$path', ['definitions', fqtn]);
|
|
1264
|
+
}
|
|
1186
1265
|
});
|
|
1187
1266
|
info(null, ['definitions', element._parent.name, 'elements', element.name],
|
|
1188
1267
|
{ name: proxy.name }, 'Created proxy EDM entity type $(NAME)');
|
|
@@ -1190,16 +1269,16 @@ function initializeModel(csn, _options)
|
|
|
1190
1269
|
else if(alreadyRegistered && !alreadyRegistered.$proxy &&
|
|
1191
1270
|
!['entity', 'view'].includes(alreadyRegistered.kind)) {
|
|
1192
1271
|
warning(null, ['definitions', element._parent.name, 'elements', element.name],
|
|
1193
|
-
{ name:
|
|
1272
|
+
{ name: fqProxyName, kind: alreadyRegistered.kind },
|
|
1194
1273
|
'No proxy EDM entity type created due to name collision with $(NAME) of kind $(KIND)');
|
|
1195
1274
|
return undefined;
|
|
1196
1275
|
}
|
|
1197
1276
|
}
|
|
1198
1277
|
else {
|
|
1199
1278
|
// it's a service reference, just add that reference proxy
|
|
1200
|
-
if(!schemas[
|
|
1201
|
-
schemas[
|
|
1202
|
-
schemaNames.push(
|
|
1279
|
+
if(!schemas[fqSchemaName]) {
|
|
1280
|
+
schemas[fqSchemaName] = proxy;
|
|
1281
|
+
schemaNames.push(fqSchemaName);
|
|
1203
1282
|
info(null, ['definitions', struct.name, 'elements', element.name],
|
|
1204
1283
|
{ name: proxy.name }, 'Created EDM namespace reference $(NAME)');
|
|
1205
1284
|
}
|
|
@@ -1244,11 +1323,8 @@ function initializeModel(csn, _options)
|
|
|
1244
1323
|
else if(!k.target) {
|
|
1245
1324
|
struct.$edmKeyPaths.push([kn]);
|
|
1246
1325
|
}
|
|
1247
|
-
//
|
|
1248
|
-
|
|
1249
|
-
const pathToElement = ['definitions', struct.name, 'elements', k.name];
|
|
1250
|
-
signalErrorForNullableKey(pathToElement);
|
|
1251
|
-
}
|
|
1326
|
+
// check toplevel key for spec violations
|
|
1327
|
+
checkKeySpecViolations(k, ['definitions', struct.name, 'elements', k.name]);
|
|
1252
1328
|
}
|
|
1253
1329
|
});
|
|
1254
1330
|
}
|
|
@@ -1264,7 +1340,7 @@ function initializeModel(csn, _options)
|
|
|
1264
1340
|
If element is of scalar type, return it as an array.
|
|
1265
1341
|
*/
|
|
1266
1342
|
function produceKeyRefPaths(eltCsn, prefix) {
|
|
1267
|
-
|
|
1343
|
+
const keyPaths = [];
|
|
1268
1344
|
if(!isEdmPropertyRendered(eltCsn, options)) {
|
|
1269
1345
|
// let annos = Object.keys(eltCsn).filter(a=>a[0]==='@').join(', ');
|
|
1270
1346
|
// warning(null, ['definitions', struct.name, 'elements', eltCsn.name ],
|
|
@@ -1272,15 +1348,20 @@ function initializeModel(csn, _options)
|
|
|
1272
1348
|
return keyPaths;
|
|
1273
1349
|
}
|
|
1274
1350
|
// OData requires all elements along the path to be nullable: false (that is either key or notNull)
|
|
1275
|
-
|
|
1351
|
+
|
|
1352
|
+
const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
|
|
1353
|
+
const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
|
|
1354
|
+
(finalType && (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 || elt.type && !isBuiltinType(elt.type) && 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
|
+
}
|
|
1320
1424
|
}
|
|
1321
|
-
|
|
1425
|
+
}
|
|
1322
1426
|
}
|
|
1323
1427
|
}
|
|
1324
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));
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
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
|
|
@@ -1532,38 +1765,51 @@ function initializeModel(csn, _options)
|
|
|
1532
1765
|
// short cut annotation @readonly that gets expanded and can be safely remapped.
|
|
1533
1766
|
function rewriteContainmentAnnotations(container, containee, assocName) {
|
|
1534
1767
|
// rectify Restrictions to NavigationRestrictions
|
|
1535
|
-
if(options.isV4()
|
|
1536
|
-
let
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
navRestr.RestrictedProperties[0].DeleteRestrictions =
|
|
1546
|
-
{ 'Deletable': containee['@Capabilities.DeleteRestrictions.Deletable'] };
|
|
1547
|
-
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;
|
|
1548
1778
|
}
|
|
1549
|
-
if(
|
|
1550
|
-
|
|
1551
|
-
{ 'Insertable': containee['@Capabilities.InsertRestrictions.Insertable'] };
|
|
1552
|
-
hasRestrictions = true;
|
|
1779
|
+
if(!navPropEntry) {
|
|
1780
|
+
navPropEntry = { NavigationProperty: { '=': assocName } };
|
|
1553
1781
|
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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
|
+
}
|
|
1558
1800
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
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
|
+
}
|
|
1564
1812
|
}
|
|
1565
|
-
if(hasRestrictions)
|
|
1566
|
-
container['@Capabilities.NavigationRestrictions'] = navRestr;
|
|
1567
1813
|
}
|
|
1568
1814
|
}
|
|
1569
1815
|
|
|
@@ -1772,7 +2018,7 @@ function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error
|
|
|
1772
2018
|
let key = { name: keyName, key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false, '@UI.Hidden': true };
|
|
1773
2019
|
elements[keyName] = key;
|
|
1774
2020
|
setProp(struct, '$keys',{ [keyName] : key } );
|
|
1775
|
-
forEachGeneric(struct, 'elements', (e,n) =>
|
|
2021
|
+
forEachGeneric(struct.items || struct, 'elements', (e,n) =>
|
|
1776
2022
|
{
|
|
1777
2023
|
if(e.key) delete e.key;
|
|
1778
2024
|
elements[n] = e;
|
|
@@ -1912,19 +2158,17 @@ function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
|
|
|
1912
2158
|
}
|
|
1913
2159
|
}
|
|
1914
2160
|
|
|
1915
|
-
function setSAPSpecificV2AnnotationsToAssociation(
|
|
1916
|
-
if(!options.isV2())
|
|
1917
|
-
return;
|
|
2161
|
+
function setSAPSpecificV2AnnotationsToAssociation(carrier) {
|
|
1918
2162
|
// documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0
|
|
1919
2163
|
const SetAttributes = {
|
|
1920
2164
|
// Applicable to NavProp and foreign keys, add to AssociationSet
|
|
1921
|
-
'@sap.creatable' : (
|
|
2165
|
+
'@sap.creatable' : (c, pn, pv) => { addToAssociationSet(c, pn, pv, false); },
|
|
1922
2166
|
// Not applicable to NavProp, applicable to foreign keys, add to AssociationSet
|
|
1923
|
-
'@sap.updatable' :
|
|
2167
|
+
'@sap.updatable' : addToAssociationSet,
|
|
1924
2168
|
// Not applicable to NavProp, not applicable to foreign key, add to AssociationSet
|
|
1925
|
-
'@sap.deletable': (
|
|
1926
|
-
|
|
1927
|
-
removeFromForeignKey(
|
|
2169
|
+
'@sap.deletable': (c, pn, pv) => {
|
|
2170
|
+
addToAssociationSet(c, pn, pv);
|
|
2171
|
+
removeFromForeignKey(c, pn);
|
|
1928
2172
|
},
|
|
1929
2173
|
// applicable to NavProp, not applicable to foreign keys, not applicable to AssociationSet
|
|
1930
2174
|
'@sap.creatable.path': removeFromForeignKey,
|
|
@@ -1932,24 +2176,22 @@ function setSAPSpecificV2AnnotationsToAssociation(options, carrier, struct) {
|
|
|
1932
2176
|
};
|
|
1933
2177
|
|
|
1934
2178
|
Object.entries(carrier).forEach(([p, v]) => {
|
|
1935
|
-
(SetAttributes[p] || function() {/* no-op */})(
|
|
2179
|
+
(SetAttributes[p] || function() {/* no-op */})(carrier, p, v);
|
|
1936
2180
|
});
|
|
1937
2181
|
|
|
1938
|
-
function
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
2182
|
+
function addToAssociationSet(carrier, propName, propValue, removeFromType=true) {
|
|
2183
|
+
if(isAssociationOrComposition(carrier)) {
|
|
2184
|
+
assignProp(carrier, '_SetAttributes', Object.create(null));
|
|
2185
|
+
assignAnnotation(carrier._SetAttributes, propName, propValue);
|
|
2186
|
+
if(removeFromType) {
|
|
2187
|
+
delete carrier[propName];
|
|
2188
|
+
}
|
|
1943
2189
|
}
|
|
1944
2190
|
}
|
|
1945
2191
|
|
|
1946
|
-
function removeFromForeignKey(
|
|
1947
|
-
if(carrier.
|
|
1948
|
-
|
|
1949
|
-
if(e['@odata.foreignKey4'] === carrier.name) {
|
|
1950
|
-
delete e[propName];
|
|
1951
|
-
}
|
|
1952
|
-
});
|
|
2192
|
+
function removeFromForeignKey(carrier, propName) {
|
|
2193
|
+
if(carrier['@odata.foreignKey4'] && carrier[propName] !== undefined) {
|
|
2194
|
+
delete carrier[propName];
|
|
1953
2195
|
}
|
|
1954
2196
|
}
|
|
1955
2197
|
}
|