@sap/cds-compiler 2.15.8 → 3.1.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.
Files changed (127) hide show
  1. package/CHANGELOG.md +102 -1590
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +61 -46
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  6. package/doc/CHANGELOG_BETA.md +26 -5
  7. package/doc/CHANGELOG_DEPRECATED.md +55 -1
  8. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  9. package/doc/Versioning.md +20 -1
  10. package/lib/api/.eslintrc.json +2 -2
  11. package/lib/api/main.js +282 -156
  12. package/lib/api/options.js +17 -88
  13. package/lib/api/validate.js +6 -10
  14. package/lib/base/keywords.js +280 -110
  15. package/lib/base/message-registry.js +85 -25
  16. package/lib/base/messages.js +119 -89
  17. package/lib/base/model.js +46 -2
  18. package/lib/base/optionProcessorHelper.js +53 -21
  19. package/lib/checks/actionsFunctions.js +15 -12
  20. package/lib/checks/annotationsOData.js +1 -1
  21. package/lib/checks/cdsPersistence.js +1 -0
  22. package/lib/checks/elements.js +6 -6
  23. package/lib/checks/invalidTarget.js +1 -1
  24. package/lib/checks/nonexpandableStructured.js +1 -1
  25. package/lib/checks/queryNoDbArtifacts.js +2 -1
  26. package/lib/checks/selectItems.js +101 -15
  27. package/lib/checks/types.js +7 -8
  28. package/lib/checks/utils.js +2 -2
  29. package/lib/checks/validator.js +3 -3
  30. package/lib/compiler/assert-consistency.js +78 -21
  31. package/lib/compiler/base.js +6 -4
  32. package/lib/compiler/builtins.js +177 -10
  33. package/lib/compiler/checks.js +1 -1
  34. package/lib/compiler/define.js +28 -23
  35. package/lib/compiler/extend.js +75 -18
  36. package/lib/compiler/finalize-parse-cdl.js +25 -18
  37. package/lib/compiler/index.js +27 -11
  38. package/lib/compiler/moduleLayers.js +7 -0
  39. package/lib/compiler/populate.js +26 -39
  40. package/lib/compiler/propagator.js +12 -7
  41. package/lib/compiler/resolve.js +207 -236
  42. package/lib/compiler/shared.js +100 -93
  43. package/lib/compiler/tweak-assocs.js +13 -20
  44. package/lib/compiler/utils.js +20 -6
  45. package/lib/edm/annotations/preprocessAnnotations.js +12 -13
  46. package/lib/edm/csn2edm.js +35 -37
  47. package/lib/edm/edm.js +22 -13
  48. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  49. package/lib/edm/edmInboundChecks.js +85 -0
  50. package/lib/edm/edmPreprocessor.js +338 -689
  51. package/lib/edm/edmUtils.js +97 -67
  52. package/lib/gen/Dictionary.json +29 -9
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +8 -31
  55. package/lib/gen/language.tokens +105 -114
  56. package/lib/gen/languageLexer.interp +1 -34
  57. package/lib/gen/languageLexer.js +892 -1007
  58. package/lib/gen/languageLexer.tokens +95 -106
  59. package/lib/gen/languageParser.js +20629 -22474
  60. package/lib/inspect/.eslintrc.json +4 -0
  61. package/lib/inspect/index.js +14 -0
  62. package/lib/inspect/inspectModelStatistics.js +81 -0
  63. package/lib/inspect/inspectPropagation.js +189 -0
  64. package/lib/inspect/inspectUtils.js +44 -0
  65. package/lib/json/from-csn.js +74 -69
  66. package/lib/json/to-csn.js +17 -14
  67. package/lib/language/antlrParser.js +2 -2
  68. package/lib/language/docCommentParser.js +61 -38
  69. package/lib/language/errorStrategy.js +52 -40
  70. package/lib/language/genericAntlrParser.js +424 -292
  71. package/lib/language/language.g4 +604 -687
  72. package/lib/language/multiLineStringParser.js +14 -42
  73. package/lib/language/textUtils.js +44 -0
  74. package/lib/main.d.ts +28 -42
  75. package/lib/main.js +104 -81
  76. package/lib/model/api.js +1 -1
  77. package/lib/model/csnRefs.js +57 -30
  78. package/lib/model/csnUtils.js +189 -287
  79. package/lib/model/revealInternalProperties.js +32 -10
  80. package/lib/model/sortViews.js +32 -31
  81. package/lib/modelCompare/compare.js +3 -0
  82. package/lib/optionProcessor.js +91 -57
  83. package/lib/render/.eslintrc.json +1 -1
  84. package/lib/render/DuplicateChecker.js +4 -7
  85. package/lib/render/manageConstraints.js +70 -2
  86. package/lib/render/toCdl.js +387 -367
  87. package/lib/render/toHdbcds.js +20 -16
  88. package/lib/render/toRename.js +44 -22
  89. package/lib/render/toSql.js +81 -59
  90. package/lib/render/utils/common.js +16 -3
  91. package/lib/render/utils/sql.js +20 -19
  92. package/lib/sql-identifier.js +6 -0
  93. package/lib/transform/db/.eslintrc.json +3 -2
  94. package/lib/transform/db/associations.js +43 -35
  95. package/lib/transform/db/cdsPersistence.js +5 -16
  96. package/lib/transform/db/constraints.js +1 -1
  97. package/lib/transform/db/expansion.js +7 -6
  98. package/lib/transform/db/flattening.js +16 -18
  99. package/lib/transform/db/transformExists.js +7 -5
  100. package/lib/transform/db/views.js +3 -3
  101. package/lib/transform/draft/.eslintrc.json +2 -2
  102. package/lib/transform/draft/db.js +6 -6
  103. package/lib/transform/draft/odata.js +6 -7
  104. package/lib/transform/forHanaNew.js +30 -24
  105. package/lib/transform/forOdataNew.js +14 -16
  106. package/lib/transform/localized.js +35 -25
  107. package/lib/transform/odata/toFinalBaseType.js +10 -10
  108. package/lib/transform/odata/typesExposure.js +17 -8
  109. package/lib/transform/odata/utils.js +1 -38
  110. package/lib/transform/transformUtilsNew.js +63 -77
  111. package/lib/transform/translateAssocsToJoins.js +2 -2
  112. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  113. package/lib/transform/universalCsn/coreComputed.js +11 -6
  114. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  115. package/lib/utils/file.js +31 -21
  116. package/lib/utils/moduleResolve.js +0 -1
  117. package/lib/utils/timetrace.js +20 -21
  118. package/package.json +34 -4
  119. package/share/messages/syntax-expected-integer.md +9 -8
  120. package/doc/ApiMigration.md +0 -237
  121. package/doc/CommandLineMigration.md +0 -58
  122. package/doc/ErrorMessages.md +0 -175
  123. package/doc/FioriAnnotations.md +0 -94
  124. package/doc/ODataTransformation.md +0 -273
  125. package/lib/backends.js +0 -529
  126. package/lib/checks/unknownMagic.js +0 -41
  127. package/lib/fix_antlr4-8_warning.js +0 -56
@@ -1,26 +1,18 @@
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, forEachMemberRecursively,
5
- isEdmPropertyRendered, getUtils, cloneCsnNonDict, isBuiltinType } = require('../model/csnUtils');
4
+ const {
5
+ forEachDefinition, forEachGeneric, forEachMemberRecursively,
6
+ isEdmPropertyRendered, getUtils, cloneCsnNonDict, cloneCsnDictionary,
7
+ isBuiltinType, applyTransformationsOnNonDictionary
8
+ } = require('../model/csnUtils');
6
9
  const edmUtils = require('./edmUtils.js');
10
+ const edmAnnoPreproc = require('./edmAnnoPreprocessor.js');
11
+ const { inboundQualificationChecks } = require('./edmInboundChecks.js');
7
12
  const typesExposure = require('../transform/odata/typesExposure');
8
13
  const expandCSNToFinalBaseType = require('../transform/odata/toFinalBaseType');
9
14
 
10
- const {
11
- intersect,
12
- validateOptions,
13
- foreach,
14
- forAll,
15
- isAssociationOrComposition,
16
- isComposition,
17
- isStructuredArtifact,
18
- isParameterizedEntity,
19
- resolveOnConditionAndPrepareConstraints,
20
- finalizeReferentialConstraints,
21
- isEntity,
22
- getSchemaPrefix,
23
- } = require('./edmUtils.js');
15
+ const NavResAnno = '@Capabilities.NavigationRestrictions.RestrictedProperties';
24
16
 
25
17
  /**
26
18
  * edmPreprocessor warms up the model so that it can be converted into an EDM document and
@@ -34,9 +26,9 @@ const {
34
26
  */
35
27
  function initializeModel(csn, _options, messageFunctions, requestedServiceNames=undefined)
36
28
  {
37
- const { info, warning, error, message, throwWithAnyError } = messageFunctions;
29
+ const { info, warning, error, message } = messageFunctions;
38
30
 
39
- let csnUtils = getUtils(csn);
31
+ const csnUtils = getUtils(csn);
40
32
 
41
33
  // proxies are merged into the final model after all proxy elements are collected
42
34
  const proxyCache = [];
@@ -47,13 +39,17 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
47
39
 
48
40
 
49
41
  // make sure options are complete
50
- let options = validateOptions(_options);
42
+ let options = edmUtils.validateOptions(_options);
51
43
 
52
44
  const [ serviceRoots,
53
45
  serviceRootNames,
54
46
  fallBackSchemaName,
55
47
  whatsMyServiceRootName ] = getAnOverviewOnTheServices(csn);
56
48
 
49
+ if(serviceRootNames.length === 0) {
50
+ return [serviceRoots, Object.create(null), reqDefs, whatsMyServiceRootName, fallBackSchemaName, options];
51
+ }
52
+
57
53
  if(requestedServiceNames === undefined)
58
54
  requestedServiceNames = options.serviceNames;
59
55
  if(requestedServiceNames === undefined) {
@@ -64,12 +60,9 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
64
60
  return requestedServiceNames.includes(whatsMyServiceRootName(n));
65
61
  }
66
62
 
67
- if(serviceRootNames.length === 0) {
68
- return [serviceRoots, Object.create(null), reqDefs, whatsMyServiceRootName, fallBackSchemaName, options];
69
- }
70
-
71
63
  // Structural CSN inbound QA checks
72
- inboundQualificationChecks();
64
+ inboundQualificationChecks(csn, options, messageFunctions,
65
+ serviceRootNames, requestedServiceNames, isMyServiceRequested, whatsMyServiceRootName);
73
66
  // not needed at the moment
74
67
  // resolveForeignKeyRefs();
75
68
 
@@ -85,7 +78,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
85
78
 
86
79
  /*
87
80
  Final base type expansion is required here when:
88
- 1) The input CSN was already transformed for V4 but shall be rendered in V2 and the
81
+ 1) The input CSN was already transformed for V4 but shall be rendered in V2 and the
89
82
  edmx generator is called directly (bypassing OData transformation)
90
83
  2) The input CSN was already transformed for V4 and persisted (all non-enumerables are
91
84
  stripped of)
@@ -119,20 +112,16 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
119
112
  }
120
113
 
121
114
  if(schemaNames.length) {
122
- // First attach names to all definitions (and actions/params) in the model
123
- // elements are done in initializeStruct
124
- // Set myServiceName for later reference and indication of a service member
125
- // First attach names to all definitions in the model and fill reqDefs
126
- // Link association targets and spray @odata.contained over untagged compositions
127
115
  forEachDefinition(csn, [
128
116
  attachNameProperty,
129
117
  (def, defName) => {
130
118
  const mySchemaName = whatsMySchemaName(defName);
131
119
  mySchemaName && setProp(def, '$mySchemaName', mySchemaName);
132
- if(isMyServiceRequested(defName))
120
+ if(isMyServiceRequested(defName) && def.kind !== 'aspect')
133
121
  reqDefs.definitions[defName] = def;
134
122
  },
135
- linkAssociationTarget ]);
123
+ linkAssociationTarget
124
+ ]);
136
125
  // initialize requested services
137
126
  const skip = { skipArtifact: (_def, defName) => !isMyServiceRequested(defName) };
138
127
  forEachDefinition({ definitions: serviceRoots }, initService, skip);
@@ -147,23 +136,32 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
147
136
  // Mute V4 elements depending on constraint preparation
148
137
  if(options.isV4())
149
138
  forEachDefinition(reqDefs, ignoreProperties);
150
- // calculate constraints based on ignoreProperties and prepareConstraints
139
+ // calculate constraints based on ignoreProperties and initConstraints
151
140
  forEachDefinition(reqDefs, finalizeConstraints);
152
141
  // convert exposed types into cross schema references if required
153
142
  // must be run before proxy exposure to avoid potential reference collisions
154
143
  convertExposedTypesOfOtherServicesIntoCrossReferences();
155
- // create association target proxies
144
+ // create association target proxies (v4)
156
145
  // Decide if an entity set needs to be constructed or not
157
- forEachDefinition(reqDefs, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
146
+ forEachDefinition(reqDefs, [
147
+ exposeTargetsAsProxiesOrSchemaRefs,
148
+ determineEntitySet
149
+ ]);
158
150
  // finalize proxy creation
159
151
  mergeProxiesIntoModel();
160
152
 
153
+ // Calculate NavPropBinding Target paths
154
+ // Rewrite @Capabilities for containment mode
161
155
  if(options.isV4())
162
- forEachDefinition(reqDefs, initEdmNavPropBindingTargets);
156
+ forEachDefinition(reqDefs, [
157
+ initEdmNavPropBindingTargets,
158
+ rewriteContainmentAnnotations,
159
+ annotateOptionalActFuncParams
160
+ ]);
163
161
 
164
162
  // Things that can be done in one pass
165
163
  // Create edmKeyRefPaths
166
- // Create NavigationPropertyBindings, requires determineEntitySet
164
+ // Create V4 NavigationPropertyBindings, requires determineEntitySet & initEdmNavPropBindingTargets
167
165
  // Map /** doc comments */ to @CoreDescription
168
166
  forEachDefinition(reqDefs, [
169
167
  initEdmKeyRefPaths,
@@ -171,7 +169,14 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
171
169
  initEdmTypesAndDescription
172
170
  ]);
173
171
  }
174
- return [serviceRoots, schemas, reqDefs, whatsMyServiceRootName, fallBackSchemaName, options];
172
+ return [
173
+ serviceRoots,
174
+ schemas,
175
+ reqDefs,
176
+ whatsMyServiceRootName,
177
+ fallBackSchemaName,
178
+ options
179
+ ];
175
180
 
176
181
  //////////////////////////////////////////////////////////////////////
177
182
  //
@@ -241,7 +246,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
241
246
  const rootDef = getRootDef(defName);
242
247
  // if this definition has a root def and the root def is not the service/schema name
243
248
  // => service C { type D.E }, replace the prefix dots with underscores
244
- if(rootDef && defName !== rootDef && rootDef !== getSchemaPrefix(defName)) {
249
+ if(rootDef && defName !== rootDef && rootDef !== edmUtils.getSchemaPrefix(defName)) {
245
250
  let newDefName = rootDef + '.' + defName.replace(rootDef + '.', '').replace(/\./g, '_');
246
251
  // store renamed types in correlation maps for later renaming
247
252
  if(def.kind === 'entity')
@@ -305,9 +310,9 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
305
310
  forEachDefinition(csn, (def, defName) => {
306
311
  if(def.kind !== 'service') {
307
312
  const myServiceRoot = whatsMyServiceRootName(defName);
308
- const mySchemaPrefix = getSchemaPrefix(defName);
313
+ const mySchemaPrefix = edmUtils.getSchemaPrefix(defName);
309
314
  if(myServiceRoot && options.isV4() &&
310
- /*(options.toOdata.odataProxies || options.toOdata.odataXServiceRefs) && options.isStructFormat && */
315
+ /*(options.odataProxies || options.odataXServiceRefs) && options.isStructFormat && */
311
316
  defName !== myServiceRoot && myServiceRoot !== mySchemaPrefix) {
312
317
  const service = { kind: 'service', name: mySchemaPrefix };
313
318
  serviceRoots[mySchemaPrefix] = service;
@@ -319,50 +324,48 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
319
324
  }
320
325
 
321
326
  function attachNameProperty(def, defName) {
322
- assignProp (def, 'name', defName);
327
+ edmUtils.assignProp (def, 'name', defName);
323
328
  // Attach name to bound actions, functions and parameters
324
329
  forEachGeneric(def, 'actions', (a, n) => {
325
- assignProp(a, 'name', n);
330
+ edmUtils.assignProp(a, 'name', n);
326
331
  forEachGeneric(a, 'params', (p, n) => {
327
- assignProp(p, 'name', n);
332
+ edmUtils.assignProp(p, 'name', n);
328
333
  });
329
334
  });
330
335
  // Attach name unbound action parameters
331
336
  forEachGeneric(def, 'params', (p,n) => {
332
- assignProp(p, 'name', n);
337
+ edmUtils.assignProp(p, 'name', n);
333
338
  });
334
339
  }
335
340
 
336
341
  // initialize the service itself
337
342
  function initService(serviceRoot) {
338
- setSAPSpecificV2AnnotationsToEntityContainer(options, serviceRoot);
343
+ edmAnnoPreproc.setSAPSpecificV2AnnotationsToEntityContainer(options, serviceRoot);
339
344
  }
340
345
 
341
346
  // link association target to association and add @odata.contained to compositions in V4
342
347
  function linkAssociationTarget(struct) {
343
348
  forEachMemberRecursively(struct, (element, name, prop, subpath) => {
344
- if(isAssociationOrComposition(element) && !element._ignore) {
345
- if(!element._target) {
346
- let target = csn.definitions[element.target];
347
- if(target) {
348
- setProp(element, '_target', target);
349
+ if(element.target && !element._target) {
350
+ let target = csn.definitions[element.target];
351
+ if(target) {
352
+ setProp(element, '_target', target);
349
353
  // If target has parameters, xref assoc at target for redirection
350
- if(isParameterizedEntity(target)) {
351
- if(!target.$sources) {
352
- setProp(target, '$sources', Object.create(null));
353
- }
354
- target.$sources[struct.name + '.' + name] = element;
354
+ if(edmUtils.isParameterizedEntity(target)) {
355
+ if(!target.$sources) {
356
+ setProp(target, '$sources', Object.create(null));
355
357
  }
358
+ target.$sources[struct.name + '.' + name] = element;
356
359
  }
357
- else {
358
- error(null, subpath, { target: element.target }, "Target $(TARGET) can't be found in the model");
359
- }
360
+ }
361
+ else {
362
+ error(null, subpath, { target: element.target }, "Target $(TARGET) can't be found in the model");
360
363
  }
361
364
  }
362
365
  // in V4 tag all compositions to be containments
363
366
  if(options.odataContainment &&
364
367
  options.isV4() &&
365
- isComposition(element) &&
368
+ edmUtils.isComposition(element) &&
366
369
  element['@odata.contained'] === undefined) {
367
370
  element['@odata.contained'] = true;
368
371
  }
@@ -372,7 +375,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
372
375
  // Perform checks and add attributes for "contained" sub-entities:
373
376
  // - A container is recognized by having an association/composition annotated with '@odata.contained'.
374
377
  // - All targets of such associations ("containees") are marked with a property
375
- // '_containerEntity: []', having as value an array of container names (i.e. of entities
378
+ // '$containerNames: []', having as value an array of container names (i.e. of entities
376
379
  // that have a '@odata.contained' association pointing to the containee). Note that this
377
380
  // may be multiple entities, possibly including the container itself.
378
381
  // - All associations in the containee pointing back to the container are marked with
@@ -382,35 +385,41 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
382
385
  // non-containment rendering. If containment rendering is active, the containee has no
383
386
  // entity set. Instead try to rewrite the annotation in such a way that it is effective
384
387
  // on the containment navigation property.
388
+ // $containeeAssoications stores the containees (children/outbound edges)
389
+ // $containerNames stores the containers (parents/inbound edges)
390
+
385
391
  function initContainments(container) {
386
392
  if(container.kind === 'entity') {
393
+ if(!container.$containeeAssociations)
394
+ setProp(container, '$containeeAssociations', []);
387
395
  forEachMemberRecursively(container, initContainments,
388
- [], true, { elementsOnly: true });
396
+ [], true, { pathWithoutProp: true, elementsOnly: true });
389
397
  }
390
398
 
391
- function initContainments(elt, eltName) {
392
- if(isAssociationOrComposition(elt) && elt['@odata.contained'] && !elt._ignore) {
399
+ function initContainments(elt, _memberName, _prop, path) {
400
+ if(elt.target && elt['@odata.contained'] && !elt._ignore) {
401
+ // store all containment associations, required to create the containment paths later on
402
+ container.$containeeAssociations.push( { assoc: elt, path });
393
403
  // Let the containee know its container
394
404
  // (array because the contanee may contained more then once)
395
405
  let containee = elt._target;
396
- if (!containee._containerEntity)
397
- setProp(containee, '_containerEntity', []);
406
+ if (!containee.$containerNames)
407
+ setProp(containee, '$containerNames', []);
398
408
  // add container only once per containee
399
- if (!containee._containerEntity.includes(container.name))
400
- containee._containerEntity.push(container.name);
409
+ if (!containee.$containerNames.includes(container.name))
410
+ containee.$containerNames.push(container.name);
401
411
  // Mark associations in the containee pointing to the container (i.e. to this entity)
402
412
  forEachMemberRecursively(containee, markToContainer,
403
- [], true, { elementsOnly: true });
404
- rewriteContainmentAnnotations(container, containee, eltName);
413
+ [ 'definitions', containee.name ], true, { elementsOnly: true });
405
414
  }
406
- else {
415
+ else if(elt.type && !elt.elements) {
407
416
  // try to find elements to drill down further
408
- while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
417
+ while(elt && !isBuiltinType(elt.type) && !elt.elements) {
409
418
  elt = csn.definitions[elt.type];
410
419
  }
411
420
  if(elt && elt.elements) {
412
421
  forEachMemberRecursively(elt, initContainments,
413
- [], true, { elementsOnly: true });
422
+ path, true, { pathWithoutProp: true, elementsOnly: true });
414
423
  }
415
424
  }
416
425
  }
@@ -441,13 +450,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
441
450
  // Containment processing must take place before because it might be that this
442
451
  // artifact with parameters is already contained. In such a case the existing
443
452
  // containment chain must be propagated and reused. This requires that the
444
- // containment data structures must be manually added here and rewriteContainmentAnnotations()
445
- // must be called.
453
+ // containment data structures must be manually added here.
446
454
  // As a param entity is a potential proxy candidate, this split must be performed on
447
455
  // all definitions
448
456
  function initParameterizedEntityOrView(entityCsn, entityName) {
449
457
 
450
- if(!isParameterizedEntity(entityCsn))
458
+ if(!edmUtils.isParameterizedEntity(entityCsn))
451
459
  return;
452
460
 
453
461
  // Naming rules for aggregated views with parameters
@@ -464,7 +472,6 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
464
472
  // this code can be extended for aggregated views
465
473
  const typeEntityName = entityName + 'Type';
466
474
  const typeEntitySetName = entityName + 'Set';
467
- const parameterToTypeAssocName = 'Set';
468
475
  const typeToParameterAssocName = 'Parameters';
469
476
  let hasBacklink = true;
470
477
 
@@ -510,7 +517,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
510
517
  <EntitySet Name="ZRHA_TEST_CDSSet" EntityType="ZRHA_TEST_CDS_CDS.ZRHA_TEST_CDSType" sap:creatable="false" sap:updatable="false"
511
518
  sap:deletable="false" sap:addressable="false" sap:content-version="1"/>
512
519
  */
513
- assignProp(entityCsn, '_SetAttributes',
520
+ edmUtils.assignProp(entityCsn, '_SetAttributes',
514
521
  {'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.addressable': false });
515
522
 
516
523
  // redirect inbound associations/compositions to the parameter entity
@@ -520,7 +527,6 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
520
527
  entityCsn.$sources[n]._target = parameterCsn;
521
528
  entityCsn.$sources[n].target = parameterCsn.name;
522
529
  });
523
- rewriteContainmentAnnotations(parameterCsn, entityCsn, parameterToTypeAssocName);
524
530
  }
525
531
 
526
532
  function createParameterEntity(entityCsn, entityName, isProxy) {
@@ -537,7 +543,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
537
543
  if(!isProxy)
538
544
  setProp(parameterCsn, '$entitySetName', entityName);
539
545
  if(entityCsn.$location){
540
- assignProp(parameterCsn, '$location', entityCsn.$location);
546
+ edmUtils.assignProp(parameterCsn, '$location', entityCsn.$location);
541
547
  }
542
548
 
543
549
  /*
@@ -545,21 +551,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
545
551
  sap:deletable="false" sap:pageable="false" sap:content-version="1"/>
546
552
  */
547
553
 
548
- assignProp(parameterCsn, '_SetAttributes',
554
+ edmUtils.assignProp(parameterCsn, '_SetAttributes',
549
555
  {'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.pageable': false });
550
556
 
551
557
  setProp(parameterCsn, '$isParamEntity', true);
552
558
  setProp(parameterCsn, '$mySchemaName', entityCsn.$mySchemaName);
553
559
 
554
- // propagate containment information, if containment is recursive, use parameterCsn.name as _containerEntity
555
- if(entityCsn._containerEntity) {
556
- setProp(parameterCsn, '_containerEntity', []);
557
- for(const c of entityCsn._containerEntity) {
558
- parameterCsn._containerEntity.push((c === entityCsn.name) ? parameterCsn.name : c);
559
- }
560
- }
561
- entityCsn._containerEntity = [ parameterCsn ];
562
-
563
560
  forEachGeneric(entityCsn, 'params', (p,n) => {
564
561
  let elt = cloneCsnNonDict(p, options);
565
562
  elt.name = n;
@@ -574,9 +571,9 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
574
571
  and so they are in CAP (all view parameters become primary keys which are not null).
575
572
  */
576
573
  if(options.isV2())
577
- assignAnnotation(elt, '@sap.parameter', 'mandatory');
574
+ edmUtils.assignAnnotation(elt, '@sap.parameter', 'mandatory');
578
575
  else
579
- assignAnnotation(elt, '@Common.FieldControl', { '#': 'Mandatory' });
576
+ edmUtils.assignAnnotation(elt, '@Common.FieldControl', { '#': 'Mandatory' });
580
577
  parameterCsn.elements[n] = elt;
581
578
  });
582
579
  linkAssociationTarget(parameterCsn);
@@ -594,7 +591,26 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
594
591
  setProp(parameterCsn.elements[parameterToTypeAssocName], '$path',
595
592
  [ 'definitions', parameterEntityName, 'elements', parameterToTypeAssocName ] );
596
593
  }
597
- // rewrite $path
594
+
595
+ // initialize containment
596
+ // propagate containment information, if containment is recursive, use parameterCsn.name as $containerNames
597
+ if(entityCsn.$containerNames) {
598
+ if(!parameterCsn.$containerNames)
599
+ setProp(parameterCsn, '$containerNames', []);
600
+ for(const c of entityCsn.$containerNames) {
601
+ parameterCsn.$containerNames.push((c === entityCsn.name) ? parameterCsn.name : c);
602
+ }
603
+ }
604
+ entityCsn.$containerNames = [ parameterCsn ];
605
+
606
+ if(!parameterCsn.$containeeAssociations)
607
+ setProp(parameterCsn, '$containeeAssociations', [ ]);
608
+ parameterCsn.$containeeAssociations.push(
609
+ { assoc: parameterCsn.elements[parameterToTypeAssocName],
610
+ path: [ parameterToTypeAssocName ]
611
+ });
612
+
613
+ // rewrite $path
598
614
  setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
599
615
 
600
616
  // proxies are registered in model separately
@@ -638,14 +654,14 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
638
654
 
639
655
  // Don't operate on any structured types other than type and entity
640
656
  // such as events and aspects
641
- if(!isStructuredArtifact(def))
657
+ if(!edmUtils.isStructuredArtifact(def))
642
658
  return;
643
659
 
644
660
  let keys = Object.create(null);
645
661
  let validFrom = [], validKey = [];
646
662
 
647
663
  // Iterate all struct elements
648
- forEachMemberRecursively(def.items || def, (element, elementName, prop, _path = [], construct) => {
664
+ forEachMemberRecursively(def.items || def, (element, elementName, prop, _path, construct) => {
649
665
  if(prop !== 'elements')
650
666
  return;
651
667
 
@@ -660,37 +676,39 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
660
676
  }
661
677
  //forward annotations from managed association element to its foreign keys
662
678
  const elements = construct.items && construct.items.elements || construct.elements;
663
- forAll(elements[element['@odata.foreignKey4']], (attr, attrName) => {
679
+ const fk = elements[element['@odata.foreignKey4']];
680
+ for(const attrName in fk) {
681
+ const attr = fk[attrName];
664
682
  if(attrName[0] === '@') {
665
683
  element[attrName] = attr;
666
684
  }
667
- });
685
+ }
668
686
  // and eventually remove some afterwards:)
669
687
  if(options.isV2())
670
- setSAPSpecificV2AnnotationsToAssociation(element);
688
+ edmAnnoPreproc.setSAPSpecificV2AnnotationsToAssociation(element);
671
689
 
672
690
  // initialize an association
673
- if(isAssociationOrComposition(element)) {
691
+ if(element.target) {
674
692
  // in case this is a forward assoc, store the backlink partners here, _selfReferences.length > 1 => error
675
- assignProp(element, '_selfReferences', []);
676
- assignProp(element._target, '$proxies', []);
693
+ edmUtils.assignProp(element, '_selfReferences', []);
694
+ edmUtils.assignProp(element._target, '$proxies', []);
677
695
  // $abspath is used as partner path
678
- assignProp(element, '$abspath', $path2path(element.$path));
696
+ edmUtils.assignProp(element, '$abspath', $path2path(element.$path));
679
697
  }
680
698
 
681
699
  // Collect keys
682
700
  if (element.key) {
683
701
  keys[elementName] = element;
684
702
  }
685
- applyAppSpecificLateCsnTransformationOnElement(options, element, def, error);
703
+ edmAnnoPreproc.applyAppSpecificLateCsnTransformationOnElement(options, element, def, error);
686
704
  }, [], true, { elementsOnly: true });
687
705
 
688
- if(!isDeprecatedEnabled(options, 'v1KeysForTemporal')) {
706
+ if(!isDeprecatedEnabled(options, '_v1KeysForTemporal')) {
689
707
  // if artifact has a cds.valid.key mention it as @Core.AlternateKey
690
708
  if(validKey.length) {
691
709
  let altKeys = [{ Key: [] }];
692
710
  validKey.forEach(vk => altKeys[0].Key.push( { Name: vk.name, Alias: vk.name } ) );
693
- assignAnnotation(def, '@Core.AlternateKeys', altKeys);
711
+ edmUtils.assignAnnotation(def, '@Core.AlternateKeys', altKeys);
694
712
  }
695
713
  }
696
714
  else {
@@ -699,14 +717,14 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
699
717
  // @Core.AlternateKeys: [{ Key: [ { Name: 'slID', Alias: 'slID' }, { Name: 'validFrom', Alias: 'validFrom'} ] }]
700
718
  if(validKey.length) {
701
719
  let altKeys = [{ Key: [] }];
702
- forAll(keys, (k, kn) => {
720
+ Object.entries(([kn, k]) => {
703
721
  altKeys[0].Key.push( { Name: kn, Alias: kn } );
704
722
  delete k.key;
705
723
  });
706
724
  validFrom.forEach(e => {
707
725
  altKeys[0].Key.push( { Name: e.name, Alias: e.name } );
708
726
  });
709
- assignAnnotation(def, '@Core.AlternateKeys', altKeys);
727
+ edmUtils.assignAnnotation(def, '@Core.AlternateKeys', altKeys);
710
728
  keys = Object.create(null);
711
729
  validKey.forEach(e => {
712
730
  e.key = true;
@@ -722,27 +740,27 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
722
740
  }
723
741
 
724
742
  // prepare the structure itself
725
- if(isEntity(def)) {
726
- assignProp(def, '_SetAttributes', Object.create(null));
727
- assignProp(def, '$keys', keys);
728
- applyAppSpecificLateCsnTransformationOnStructure(options, def, error);
729
- setSAPSpecificV2AnnotationsToEntitySet(options, def);
743
+ if(def.kind === 'entity') {
744
+ edmUtils.assignProp(def, '_SetAttributes', Object.create(null));
745
+ edmUtils.assignProp(def, '$keys', keys);
746
+ edmAnnoPreproc.applyAppSpecificLateCsnTransformationOnStructure(options, def, error);
747
+ edmAnnoPreproc.setSAPSpecificV2AnnotationsToEntitySet(options, def);
730
748
  }
731
749
  }
732
750
 
733
751
  // Prepare the associations for the subsequent steps
734
752
  function initConstraints(def) {
735
- if(!isStructuredArtifact(def))
753
+ if(!edmUtils.isStructuredArtifact(def))
736
754
  return;
737
755
 
738
756
  forEachMemberRecursively(def.items || def, initConstraintsOnAssoc, [], true, { elementsOnly: true });
739
757
  }
740
758
  function initConstraintsOnAssoc(element) {
741
- if (isAssociationOrComposition(element) && !element._ignore && !element._constraints) {
759
+ if (element.target && !element._constraints) {
742
760
  // setup the constraints object
743
761
  setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
744
762
  // and crack the ON condition
745
- resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
763
+ edmUtils.resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
746
764
  }
747
765
  }
748
766
 
@@ -759,7 +777,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
759
777
  4) All of this can be revoked with options.renderForeignKeys.
760
778
  */
761
779
  function ignoreProperties(struct) {
762
- if(!isStructuredArtifact(struct))
780
+ if(!edmUtils.isStructuredArtifact(struct))
763
781
  return;
764
782
 
765
783
  forEachMemberRecursively(struct.items || struct, (element) => {
@@ -783,19 +801,19 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
783
801
  */
784
802
  if((!isContainerAssoc && !isEdmPropertyRendered(element, options)) ||
785
803
  (isContainerAssoc && !options.renderForeignKeys))
786
- assignAnnotation(element, '@cds.api.ignore', true);
804
+ edmUtils.assignAnnotation(element, '@cds.api.ignore', true);
787
805
  // Only in containment:
788
806
  // If this element is a foreign key and if it is rendered, remove it from the key ref vector
789
807
  else if(options.odataContainment && isContainerAssoc && options.renderForeignKeys) {
790
808
  delete struct.$keys[element.name];
791
809
  }
792
810
  }
793
- // deprecated unmanagedUpInComponent:
811
+ // deprecated._unmanagedUpInComponent:
794
812
  // Only in containment:
795
813
  // Ignore this (foreign key) elment if renderForeignKeys is false
796
814
  if(options.odataContainment && element['@odata.containment.ignore']) {
797
815
  if(!options.renderForeignKeys)
798
- assignAnnotation(element, '@cds.api.ignore', true);
816
+ edmUtils.assignAnnotation(element, '@cds.api.ignore', true);
799
817
  else
800
818
  // If foreign keys shall be rendered, remove it from key ref vector
801
819
  delete struct.$keys[element.name];
@@ -805,7 +823,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
805
823
  else if(element['@odata.containment.ignore'] && options.odataContainment && !options.renderForeignKeys) {
806
824
  // if this is an explicitly containment ignore tagged association,
807
825
  // ignore it if option odataContainment is true and no foreign keys should be rendered
808
- assignAnnotation(element, '@odata.navigable', false);
826
+ edmUtils.assignAnnotation(element, '@odata.navigable', false);
809
827
  }
810
828
  }, [], true, { elementsOnly: true });
811
829
  }
@@ -816,14 +834,14 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
816
834
  in edmUtils
817
835
  */
818
836
  function finalizeConstraints(def) {
819
- if(!isStructuredArtifact(def))
837
+ if(!edmUtils.isStructuredArtifact(def))
820
838
  return;
821
839
 
822
840
  forEachMemberRecursively(def.items || def, finalizeConstraintsOnAssoc, [], true, { elementsOnly: true });
823
841
  }
824
842
  function finalizeConstraintsOnAssoc(element) {
825
- if (isAssociationOrComposition(element) && !element._ignore && element._constraints) {
826
- finalizeReferentialConstraints(csn, element, options, info);
843
+ if (element.target && !element._ignore && element._constraints) {
844
+ edmUtils.finalizeReferentialConstraints(csn, element, options, info);
827
845
 
828
846
  if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
829
847
  // if this is a partnership and this assoc has a set target cardinality, assign it as source cardinality to the partner
@@ -855,7 +873,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
855
873
  sub artifacts exposed by the initial type exposure
856
874
  */
857
875
  function convertExposedTypesOfOtherServicesIntoCrossReferences() {
858
- if(options.toOdata.odataXServiceRefs && options.isV4()) {
876
+ if(options.odataXServiceRefs && options.isV4()) {
859
877
  serviceRootNames.forEach(srn => {
860
878
  schemaNames.forEach(fqSchemaName => {
861
879
  if(fqSchemaName.startsWith(srn + '.')) {
@@ -912,7 +930,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
912
930
  // if this artifact is a service member check its associations
913
931
  if(globalSchemaPrefix) {
914
932
  forEachGeneric(struct.items || struct, 'elements', element => {
915
- if(!isAssociationOrComposition(element) || element._ignore || element['@odata.navigable'] === false)
933
+ if(!element.target || element['@odata.navigable'] === false)
916
934
  return;
917
935
  /*
918
936
  * Consider everything @cds.autoexpose: falsy to be a proxy candidate for now
@@ -920,7 +938,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
920
938
  /*
921
939
  if(element._target['@cds.autoexpose'] === false) {
922
940
  // :TODO: Also _ignore foreign keys to association?
923
- foreach(struct.elements,
941
+ edmUtils.foreach(struct.elements,
924
942
  e =>
925
943
  e['@odata.foreignKey4'] === element.name,
926
944
  e => e._ignore = true);
@@ -948,23 +966,27 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
948
966
 
949
967
  const targetSchemaName = element._target.$mySchemaName;
950
968
  if(isProxyRequired(element)) {
951
- if(options.isV4() &&
952
- (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs) &&
953
- // must be a managed association with keys OR an unambiguous backlink
954
- element.keys ||
955
- (element.on && element._constraints.selfs.length === 1 && element._constraints.termCount === 1)
956
- ) {
969
+ if(options.isV4() && (options.odataProxies || options.odataXServiceRefs)) {
970
+ // must be a managed association with keys OR an unambiguous backlink to become a proxy
971
+ let assocOK = element.keys ||
972
+ (element.on && element._constraints.selfs.length === 1 && element._constraints.termCount === 1);
957
973
  // reuse proxy if available
958
974
  let proxy = getProxyForTargetOf(element);
959
975
  if(!proxy) {
960
- if(targetSchemaName && options.toOdata.odataXServiceRefs) {
976
+ if(targetSchemaName && options.odataXServiceRefs) {
961
977
  proxy = createSchemaRefFor(targetSchemaName);
962
978
  }
963
- else if(options.toOdata.odataProxies) {
979
+ // create a proxy for a 'good' association only
980
+ else if(options.odataProxies && assocOK) {
964
981
  proxy = createProxyFor(element, targetSchemaName);
965
982
  }
966
983
  proxy = registerProxy(proxy, element);
967
- }
984
+ } else if(!assocOK) {
985
+ // if there is already a proxy (generated by a 'good' association)
986
+ // and this association is not a good one, don't expose this association.
987
+ muteNavProp(element, 'oncond');
988
+ return;
989
+ }
968
990
  if(proxy) {
969
991
  // if a proxy was either already created or could be created and
970
992
  // if it's a 'real' proxy, link the _target to it and remove constraints
@@ -982,24 +1004,22 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
982
1004
  }
983
1005
  }
984
1006
  else {
985
- // no proxy: no navigation
986
- assignAnnotation(element, '@odata.navigable', false);
987
- noNavPropMsg(element);
1007
+ muteNavProp(element, assocOK ? 'std' : 'oncond');
1008
+ return;
988
1009
  }
989
1010
  }
990
- // ok schema names are different, now check if external wants to link back into its service schema
991
1011
  else {
992
- assignAnnotation(element, '@odata.navigable', false);
993
- noNavPropMsg(element);
1012
+ muteNavProp(element);
994
1013
  return;
995
1014
  }
996
1015
  }
997
1016
  });
998
1017
  }
999
1018
 
1000
- function noNavPropMsg(elt) {
1019
+ function muteNavProp(elt, msg='std') {
1020
+ edmUtils.assignAnnotation(elt, '@odata.navigable', false);
1001
1021
  warning('odata-navigation', ['definitions', struct.name, 'elements', elt.name],
1002
- { target: elt._target.name, service: globalSchemaPrefix });
1022
+ { target: elt._target.name, service: globalSchemaPrefix, '#': msg });
1003
1023
  }
1004
1024
 
1005
1025
  function createSchemaRefFor(targetSchemaName) {
@@ -1015,16 +1035,16 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1015
1035
  // If target is outside any service expose it in service of source entity
1016
1036
  // The proxySchemaName is not prepended with the service schema name to allow to share the proxy
1017
1037
  // if it is required in multiple services. The service schema name is prepended upon registration
1018
- const proxySchemaName = targetSchemaName || getSchemaPrefix(assoc._target.name);
1038
+ const proxySchemaName = targetSchemaName || edmUtils.getSchemaPrefix(assoc._target.name);
1019
1039
 
1020
1040
  // if the target is a parameter entity, it's easy just create the parameter stub
1021
- const isParamProxy = isParameterizedEntity(assoc._target);
1041
+ const isParamProxy = edmUtils.isParameterizedEntity(assoc._target);
1022
1042
 
1023
1043
  // 1) construct the proxy definition
1024
1044
  // proxyDefinitionName: strip the serviceName and replace '.' with '_'
1025
1045
  let defName =
1026
1046
  `${assoc._target.name.replace(proxySchemaName + '.', '').replace(/\./g, '_')}`;
1027
-
1047
+
1028
1048
  // fullName: Prepend serviceName and if in same service add '_proxy'
1029
1049
  const proxy = isParamProxy
1030
1050
  ? createParameterEntity(assoc._target, proxySchemaName + '.' + defName, true)
@@ -1041,7 +1061,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1041
1061
  setProp(proxy, '$exposedTypes', Object.create(null));
1042
1062
  // copy all annotations of the target to the proxy
1043
1063
  Object.entries(assoc._target).forEach(([k, v]) => {
1044
- if(k[0] === '@')
1064
+ if(k[0] === '@' && k !== '@open')
1045
1065
  proxy[k] = v;
1046
1066
  });
1047
1067
 
@@ -1068,13 +1088,13 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1068
1088
  // copy over the primary keys of the target and trigger the type exposure
1069
1089
  // if the element already exists we assume it was fully exposed
1070
1090
  function populateProxyElements(assoc, proxy, elements) {
1071
- forAll(elements, e => {
1091
+ Object.values(elements).forEach(e => {
1072
1092
  if (isEdmPropertyRendered(e, options)) {
1073
1093
  let newElt = proxy.elements[e.name];
1074
1094
  if(!newElt) {
1075
1095
  if(csnUtils.isAssocOrComposition(e.type)) {
1076
1096
  if(!e.on && e.keys) {
1077
- if(options.toOdata.odataNoTransitiveProxies)
1097
+ if(options.odataNoTransitiveProxies)
1078
1098
  newElt = convertManagedAssocIntoStruct(e);
1079
1099
  else
1080
1100
  newElt = createProxyOrSchemaRefForManagedAssoc(e);
@@ -1122,7 +1142,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1122
1142
  // to notNull until the first named type is exposed.
1123
1143
  function exposeStructTypeForProxyOf(proxy, node, artificialName,
1124
1144
  typeSchemaName=fallBackSchemaName,
1125
- isKey, forceToNotNull) {
1145
+ isKey = false, forceToNotNull = false) {
1126
1146
 
1127
1147
  if(node.type && isBuiltinType(node.type))
1128
1148
  return;
@@ -1146,7 +1166,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1146
1166
  let typeId = artificialName; // the artificialName has no namespace, it's the element
1147
1167
  if(node.type) {
1148
1168
  // same as for proxies, use schema or namespace, 'root' is last resort
1149
- typeSchemaName = typeDef.$mySchemaName || getSchemaPrefix(node.type);
1169
+ typeSchemaName = typeDef.$mySchemaName || edmUtils.getSchemaPrefix(node.type);
1150
1170
  typeId = node.type.replace(typeSchemaName + '.', '').replace(/\./g, '_');
1151
1171
  // strip the service root of that type (if any)
1152
1172
  const myServiceRootName = whatsMyServiceRootName(typeSchemaName);
@@ -1154,7 +1174,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1154
1174
  typeSchemaName = typeSchemaName.replace(myServiceRootName + '.', '');
1155
1175
  }
1156
1176
 
1157
- if(isStructuredArtifact(typeDef)) {
1177
+ if(edmUtils.isStructuredArtifact(typeDef)) {
1158
1178
  // pull forceNotNull to false for named types and non-key nodes
1159
1179
  // only toplevel nodes (elements) can be key
1160
1180
  forceToNotNull = !!(forceToNotNull && isKey && node.elements && !node.type);
@@ -1213,6 +1233,8 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1213
1233
  };
1214
1234
  setProp(type, '$mySchemaName', typeSchemaName);
1215
1235
  setProp(type, '$exposedBy', 'proxyExposure');
1236
+ if(typeDef['@open'] !== undefined)
1237
+ type['@open'] = typeDef['@open'];
1216
1238
 
1217
1239
  typeDef.elements && Object.entries(typeDef.elements).forEach( ([elemName, elem]) => {
1218
1240
  if(!elem.target) {
@@ -1275,10 +1297,10 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1275
1297
  proxy = getProxyForTargetOf(e);
1276
1298
  if(!proxy) {
1277
1299
  // option odataXServiceRefs has precedence over odataProxies
1278
- if(e._target.$mySchemaName && options.toOdata.odataXServiceRefs) {
1300
+ if(e._target.$mySchemaName && options.odataXServiceRefs) {
1279
1301
  proxy = createSchemaRefFor(e._target.$mySchemaName);
1280
1302
  }
1281
- else if(options.toOdata.odataProxies) {
1303
+ else if(options.odataProxies) {
1282
1304
  proxy = createProxyFor(e, e._target.$mySchemaName);
1283
1305
  if(!e._target.$isParamEntity)
1284
1306
  populateProxyElements(e, proxy, getForeignKeyDefinitions(e));
@@ -1289,7 +1311,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1289
1311
  if(proxy === undefined) {
1290
1312
  proxy = e._target;
1291
1313
  // no proxy: no navigation
1292
- assignAnnotation(newElt, '@odata.navigable', false);
1314
+ edmUtils.assignAnnotation(newElt, '@odata.navigable', false);
1293
1315
  }
1294
1316
  // either the proxy has exposed the type or
1295
1317
  // the assoc doesn't need to be exposed, so don't
@@ -1355,7 +1377,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1355
1377
  const fqProxyName = proxy.$globalSchemaPrefix + '.' + proxy.name;
1356
1378
 
1357
1379
  if(!element._target.$cachedProxy)
1358
- assignProp(element._target, '$cachedProxy', Object.create(null));
1380
+ edmUtils.assignProp(element._target, '$cachedProxy', Object.create(null));
1359
1381
  if(getProxyForTargetOf(element)) {
1360
1382
  info(null, ['definitions', struct.name, 'elements', element.name],
1361
1383
  { name: fqProxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
@@ -1371,10 +1393,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1371
1393
 
1372
1394
  function mergeProxiesIntoModel() {
1373
1395
  proxyCache.forEach(proxy => {
1396
+
1374
1397
  const fqProxyName = proxy.$globalSchemaPrefix + '.' + proxy.name;
1375
1398
  const fqSchemaName = proxy.$globalSchemaPrefix + '.' + proxy.$mySchemaName;
1376
1399
 
1377
1400
  if(proxy.kind === 'entity') {
1401
+ finalizeProxyContainments(proxy);
1378
1402
  // collect all schemas even for newly exposed types
1379
1403
  // (that may reside in another subcontext schema), but only once
1380
1404
  const schemaSet = new Set();
@@ -1384,7 +1408,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1384
1408
  // don't forget to prepend the global namespace prefix
1385
1409
  // schemas are ordered in csn2edm.js for each service
1386
1410
  Object.keys(proxy.$exposedTypes).forEach(t =>
1387
- schemaSet.add(proxy.$globalSchemaPrefix + '.' + getSchemaPrefix(t)));
1411
+ schemaSet.add(proxy.$globalSchemaPrefix + '.' + edmUtils.getSchemaPrefix(t)));
1388
1412
  schemaSet.forEach(schemaName => {
1389
1413
  if(!schemas[schemaName]) {
1390
1414
  schemas[schemaName] = { kind: 'schema', name: schemaName };
@@ -1405,6 +1429,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1405
1429
  setProp(v, '$path', ['definitions', fqtn]);
1406
1430
  }
1407
1431
  });
1432
+
1408
1433
  // default location is not always correct in case proxy has been created by a nested assoc
1409
1434
  // as foreign key targeting another proxy association
1410
1435
  let loc = ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name];
@@ -1432,6 +1457,26 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1432
1457
  });
1433
1458
  // sort the global schemaNames array
1434
1459
  schemaNames.sort((a,b) => b.length-a.length);
1460
+
1461
+ function finalizeProxyContainments(proxy) {
1462
+ // initialise containments after all exposed types are collected
1463
+ // AND remove unfullfillable NavRestrictions
1464
+ initContainments(proxy);
1465
+ const assocPaths = proxy.$containeeAssociations.map(entry => entry.path.join('.'));
1466
+ const newNpr = [];
1467
+ const npr = proxy[NavResAnno];
1468
+ npr && npr.forEach(np => {
1469
+ const npath = np.NavigationProperty && np.NavigationProperty['='];
1470
+ if(npath && assocPaths.includes(npath))
1471
+ newNpr.push(np);
1472
+ });
1473
+ if(newNpr.length) {
1474
+ proxy[NavResAnno] = newNpr;
1475
+ }
1476
+ else {
1477
+ delete proxy[NavResAnno]
1478
+ }
1479
+ }
1435
1480
  }
1436
1481
 
1437
1482
  /*
@@ -1451,7 +1496,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1451
1496
  if(def.$keys) {
1452
1497
  setProp(def, '$edmKeyPaths', []);
1453
1498
  // for all key elements that shouldn't be ignored produce the paths
1454
- foreach(def.$keys, k => !k._ignore && !(k._isToContainer && k._selfReferences.length), (k, kn) => {
1499
+ edmUtils.foreach(def.$keys, k => !(k._isToContainer && k._selfReferences.length), (k, kn) => {
1455
1500
  if(isEdmPropertyRendered(k, options) &&
1456
1501
  !(options.isV2() && k['@Core.MediaType'])) {
1457
1502
  if(options.isV4() && options.isStructFormat) {
@@ -1556,7 +1601,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1556
1601
 
1557
1602
  // check for legal scalar types, proxy exposed structured types are not resolvable in CSN
1558
1603
  // V2 allows any Edm.PrimitiveType (even Double and Binary), V4 is more specific:
1559
- if(options.isV4() && type && !isAssociationOrComposition(type) && isBuiltinType(type.type)) {
1604
+ if(options.isV4() && type && !type.target && isBuiltinType(type.type)) {
1560
1605
  const edmType = edmUtils.mapCdsToEdmType(type);
1561
1606
  const legalEdmTypes = {
1562
1607
  'Edm.Boolean':1, 'Edm.Byte':1, 'Edm.Date':1, 'Edm.DateTimeOffset':1, 'Edm.Decimal':1, 'Edm.Duration':1,
@@ -1609,7 +1654,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1609
1654
  if(isEdmPropertyRendered(elt, options)) {
1610
1655
  // Assoc can never be a derived TypeDefinition, no need to
1611
1656
  // unroll derived type chains for assocs
1612
- if(isAssociationOrComposition(elt) && !elt.$touched) {
1657
+ if(elt.target && !elt.$visited) {
1613
1658
  if(!elt._target.$edmTgtPaths)
1614
1659
  setProp(elt._target, '$edmTgtPaths', []);
1615
1660
  // drill into target only if
@@ -1621,10 +1666,10 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1621
1666
  !elt._isToContainer &&
1622
1667
  curDef !== elt._target) {
1623
1668
  // follow elements in the target but avoid cycles
1624
- setProp(elt, '$touched', true);
1669
+ setProp(elt, '$visited', true);
1625
1670
  elt._target.$edmTgtPaths.push(newPrefix);
1626
1671
  Object.values(elt._target.elements).forEach(e => produceTargetPath(newPrefix, e, elt._target));
1627
- delete elt.$touched;
1672
+ delete elt.$visited;
1628
1673
  }
1629
1674
  }
1630
1675
  else {
@@ -1654,7 +1699,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1654
1699
  if(isEdmPropertyRendered(elt, options)) {
1655
1700
  // Assoc can never be a derived TypeDefinition, no need to
1656
1701
  // unroll derived type chains for assocs
1657
- if(isAssociationOrComposition(elt) && !elt.$touched) {
1702
+ if(elt.target && !elt.$visited) {
1658
1703
  // drill into target only if
1659
1704
  // 1) target has no entity set and this assoc is not going to the container
1660
1705
  // 2) current definition and target are the same (cycle)
@@ -1664,9 +1709,9 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1664
1709
  !elt._isToContainer &&
1665
1710
  curDef !== elt._target) {
1666
1711
  // follow elements in the target but avoid cycles
1667
- setProp(elt, '$touched', true);
1712
+ setProp(elt, '$visited', true);
1668
1713
  Object.values(elt._target.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, elt._target)));
1669
- delete elt.$touched;
1714
+ delete elt.$visited;
1670
1715
  }
1671
1716
  else if(!(options.odataContainment && options.isV4() && elt['@odata.contained'])) {
1672
1717
  // end point reached but must not be an external reference nor a proxy nor a composition itself
@@ -1718,7 +1763,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1718
1763
  // 1) must not be a proxy and not a containee in V4
1719
1764
  // No annos are rendered for non-existing EntitySet targets.
1720
1765
  if(def.$hasEntitySet === undefined) {
1721
- const hasEntitySet = isEntity(def) && !(options.isV4() && edmUtils.isContainee(def)) && !def.$proxy;
1766
+ const hasEntitySet = def.kind === 'entity' && !(options.isV4() && edmUtils.isContainee(def)) && !def.$proxy;
1722
1767
  setProp(def, '$hasEntitySet', hasEntitySet);
1723
1768
  }
1724
1769
  }
@@ -1727,7 +1772,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1727
1772
  // 1. let all doc props become @Core.Descriptions
1728
1773
  // 2. mark a member that will become a collection
1729
1774
  // 3. assign the edm primitive type to elements, to be used in the rendering later
1730
- assignAnnotation(def, '@Core.Description', def.doc);
1775
+ edmUtils.assignAnnotation(def, '@Core.Description', def.doc);
1731
1776
  markCollection(def);
1732
1777
  mapCdsToEdmProp(def);
1733
1778
  if (def.returns) {
@@ -1735,7 +1780,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1735
1780
  mapCdsToEdmProp(def.returns);
1736
1781
  }
1737
1782
  forEachMemberRecursively(def,member => {
1738
- assignAnnotation(member, '@Core.Description', member.doc);
1783
+ edmUtils.assignAnnotation(member, '@Core.Description', member.doc);
1739
1784
  markCollection(member);
1740
1785
  mapCdsToEdmProp(member);
1741
1786
  ComputedDefaultValue(member);
@@ -1748,98 +1793,143 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1748
1793
  function markCollection(obj) {
1749
1794
  const items = obj.items || csn.definitions[obj.type] && csn.definitions[obj.type].items;
1750
1795
  if (items) {
1751
- assignProp(obj, '_NotNullCollection', items.notNull !== undefined ? items.notNull : true);
1752
- assignProp(obj, '_isCollection', true);
1796
+ edmUtils.assignProp(obj, '_NotNullCollection', items.notNull !== undefined ? items.notNull : true);
1797
+ edmUtils.assignProp(obj, '_isCollection', true);
1753
1798
  }
1754
1799
  }
1755
1800
  }
1756
1801
 
1802
+ // If containment in V4 is active, annotations that would be assigned to the containees
1803
+ // entity set are not renderable anymore. In such a case try to reassign the annotations to
1804
+ // the containment navigation property.
1805
+ // Today only Capabilities.*Restrictions are known to be remapped as there exists a CDS
1806
+ // short cut annotation @readonly that gets expanded and can be safely remapped.
1807
+ function rewriteContainmentAnnotations(rootContainer) {
1757
1808
 
1758
- //////////////////////////////////////////////////////////////////////
1759
- //
1760
- // Checks section starts here
1761
- //
1809
+ // meaningless for non-entities and proxies
1810
+ if(rootContainer.kind !== 'entity' || rootContainer.$proxy)
1811
+ return;
1762
1812
 
1763
- // eslint-disable-next-line no-unused-vars
1764
- function resolveForeignKeyRefs() {
1765
- forEachDefinition(csn, (def, defName) => {
1766
- let currPath = ['definitions', defName ];
1767
- forEachMemberRecursively(def, (construct, _constructName, _prop, path) => {
1768
- if(construct.target && construct.keys) {
1769
- construct.keys.forEach((fk, i) => {
1770
- setProp(fk, '_artifact', csnUtils.inspectRef([...path, 'keys', i]).art);
1771
- });
1772
- }
1773
- }, currPath, true, { elementsOnly: true });
1774
- });
1775
- }
1813
+ const isRecursiveContainment =
1814
+ !!(rootContainer.$containerNames && rootContainer.$containeeAssociations &&
1815
+ rootContainer.$containerNames.length === 1 &&
1816
+ rootContainer.$containeeAssociations.some(entry => rootContainer.$containerNames.includes(entry.assoc.target)));
1776
1817
 
1818
+ // Root nodes are not contained
1819
+ const isRootNode =
1820
+ !!(!rootContainer.$containerNames ||
1821
+ rootContainer.$containerNames && rootContainer.$containerNames.length === 0);
1777
1822
 
1778
- function inboundQualificationChecks() {
1779
- forEachDefinition(csn, [ attach$path, checkChainedArray ]);
1780
- checkNestedContextsAndServices();
1781
- throwWithAnyError();
1823
+ if(!isRecursiveContainment && !isRootNode)
1824
+ return;
1782
1825
 
1783
- // attach $path to all
1784
- function attach$path(def, defName) {
1785
- setProp(def, '$path', [ 'definitions', defName ]);
1786
- forEachMemberRecursively(def,
1787
- (member, _memberName, _prop, path) => {
1788
- setProp(member, '$path', path);
1789
- }, [ 'definitions', defName ]);
1790
- }
1826
+ const rootRestrictions = [];
1827
+ addContainmentAnnotationsRecursively([], rootContainer);
1828
+ if(rootRestrictions.length)
1829
+ rootContainer[NavResAnno] = rootRestrictions;
1830
+
1831
+ function addContainmentAnnotationsRecursively(prefix, container) {
1832
+ if(container.$containeeAssociations) {
1833
+ // copy or create container restrictions, don't modify original
1834
+ const localRestrictions = container[NavResAnno] ?
1835
+ cloneCsnDictionary(container[NavResAnno], options) : []
1836
+
1837
+ setProp(container, '$visited', true);
1838
+ container.$containeeAssociations.forEach(entry => {
1839
+ const { assoc, path } = entry;
1840
+ const containee = assoc._target;
1841
+
1842
+ if(isMyServiceRequested(containee.name) || containee.$proxy) {
1843
+ const localAssocPath = path.join('.');
1844
+ const props = Object.entries(containee);
1845
+ ['@Capabilities.DeleteRestrictions',
1846
+ '@Capabilities.InsertRestrictions',
1847
+ '@Capabilities.UpdateRestrictions',
1848
+ '@Capabilities.ReadRestrictions'].forEach(c =>
1849
+ edmUtils.mergeIntoNavPropRestrictions(c, localAssocPath, props, localRestrictions));
1850
+
1851
+
1852
+ if(!containee.$visited) {
1853
+ addContainmentAnnotationsRecursively(prefix.concat(path), containee);
1854
+ }
1855
+ }
1856
+ });
1791
1857
 
1792
- function checkChainedArray(def, defName) {
1793
- if (!isMyServiceRequested(defName))
1794
- return;
1795
- let currPath = ['definitions', defName];
1796
- checkIfItemsOfItems(def, undefined, undefined, currPath);
1797
- forEachMemberRecursively(def, checkIfItemsOfItems, currPath);
1798
-
1799
- function checkIfItemsOfItems(construct, _constructName, _prop, path) {
1800
- const constructType = csnUtils.effectiveType(construct);
1801
- if (constructType.items) {
1802
- if (constructType.items.items) {
1803
- message('chained-array-of', path);
1804
- return;
1858
+ // prefix all paths in the navPropEntry with the NavigationPropertyPath
1859
+ localRestrictions.forEach((p, i) => {
1860
+ if(p.NavigationProperty && p.NavigationProperty['='] && typeof p.NavigationProperty['='] === 'string') {
1861
+ const lp = [ ...prefix, p.NavigationProperty['=']].join('.');
1862
+ applyTransformationsOnNonDictionary(localRestrictions, i, {
1863
+ "=": (parent, prop, value) => {
1864
+ parent[prop] = [lp, value].join('.');
1865
+ }
1866
+ });
1867
+ // reset NavigationPropertyPath
1868
+ p.NavigationProperty['='] = lp;
1805
1869
  }
1870
+ });
1871
+ rootRestrictions.unshift(...localRestrictions);
1872
+ delete container.$visited;
1873
+ }
1874
+ }
1875
+ }
1806
1876
 
1807
- const itemsType = csnUtils.effectiveType(constructType.items);
1808
- if (itemsType.items)
1809
- message('chained-array-of', path);
1810
- }
1877
+ /*
1878
+ V4 Only:
1879
+ An action/function parameter is optional if
1880
+ 1) it is explicitly annotated to be optional
1881
+ 2) it has a default value (including null), regardless of it's nullability
1882
+ 3) it has NO default value but is nullable (the implicit default value is null)
1883
+
1884
+ If a mandatory parameter (not null no default value) appears after an optional
1885
+ parameter, a warning is raised, Core.OptionalParameter requires that all optional
1886
+ parameters appear rightmost.
1887
+ */
1888
+ function annotateOptionalActFuncParams(def, defName) {
1889
+ if(!isBetaEnabled(options, 'optionalActionFunctionParameters'))
1890
+ return;
1891
+ // return if there is nothing to do
1892
+ const loc = [ 'definitions', defName ]
1893
+ if(def.kind === 'function' || def.kind === 'action')
1894
+ iterateParams(def, loc.concat('params'));
1895
+ if(def.actions) {
1896
+ for(const an in def.actions) {
1897
+ const a = def.actions[an];
1898
+ iterateParams(a, loc.concat(['actions', an, 'params' ]));
1811
1899
  }
1812
1900
  }
1813
1901
 
1814
- function checkNestedContextsAndServices() {
1815
- !isBetaEnabled(options, 'nestedServices') && serviceRootNames.forEach(sn => {
1816
- const parent = whatsMyServiceRootName(sn, false);
1817
- if(parent && requestedServiceNames.includes(parent) && parent !== sn) {
1818
- message( 'service-nested-service', [ 'definitions', sn ], { art: parent },
1819
- 'A service can\'t be nested within a service $(ART)' );
1902
+ function iterateParams(def, loc) {
1903
+ let lastOptPos = 0;
1904
+ let i = 0;
1905
+ for(const pn in def.params) {
1906
+ const p = def.params[pn];
1907
+ // user assigned annotation, don't touch it
1908
+ if(Object.keys(p).some(a => a.startsWith('@Core.OptionalParameter') && p[a] !== null)) {
1909
+ lastOptPos = i;
1820
1910
  }
1821
- });
1822
-
1823
- Object.entries(csn.definitions).forEach(([fqName, art]) => {
1824
- if(art.kind === 'context') {
1825
- const parent = whatsMyServiceRootName(fqName);
1826
- if(requestedServiceNames.includes(parent)) {
1827
- message( 'service-nested-context', [ 'definitions', fqName ], { art: parent },
1828
- 'A context can\'t be nested within a service $(ART)' );
1829
- }
1911
+ // default value automatically makes param optional
1912
+ else if(p.default) {
1913
+ if(p.default.val)
1914
+ edmUtils.assignAnnotation(p, '@Core.OptionalParameter.DefaultValue', p.default.val);
1915
+ else
1916
+ edmUtils.assignAnnotation(p, '@Core.OptionalParameter.$Type', '');
1917
+ lastOptPos = i;
1830
1918
  }
1831
- });
1919
+ // if no default is available, nullable makes param optional
1920
+ else if(!p.notNull) {
1921
+ edmUtils.assignAnnotation(p, '@Core.OptionalParameter.$Type', '');
1922
+ lastOptPos = i;
1923
+ }
1924
+ // check if mandatory param follows optional param
1925
+ else if(lastOptPos < i) {
1926
+ warning('odata-parameter-order', loc.concat(pn));
1927
+ }
1928
+ i++;
1929
+ }
1832
1930
  }
1833
1931
  }
1834
1932
 
1835
- //
1836
- // Checks Secition ends here
1837
- //
1838
- //////////////////////////////////////////////////////////////////////
1839
-
1840
-
1841
-
1842
-
1843
1933
  //////////////////////////////////////////////////////////////////////
1844
1934
  //
1845
1935
  // Helper section starts here
@@ -1884,96 +1974,18 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1884
1974
  }
1885
1975
  }
1886
1976
 
1887
-
1888
- // If containment in V4 is active, annotations that would be assigned to the containees
1889
- // entity set are not renderable anymore. In such a case try to reassign the annotations to
1890
- // the containment navigation property.
1891
- // Today only Capabilities.*Restrictions are known to be remapped as there exists a CDS
1892
- // short cut annotation @readonly that gets expanded and can be safely remapped.
1893
- function rewriteContainmentAnnotations(container, containee, assocName) {
1894
- // rectify Restrictions to NavigationRestrictions
1895
- if(options.isV4()) {
1896
- let navPropEntry;
1897
- let hasEntry = false;
1898
- let newEntry = false;
1899
- const anno = '@Capabilities.NavigationRestrictions.RestrictedProperties';
1900
- let resProps = container[anno];
1901
- // merge into existing anno, if available
1902
- if(resProps) {
1903
- navPropEntry = resProps.find(p => p.NavigationProperty && p.NavigationProperty['='] === assocName);
1904
- hasEntry = !!navPropEntry;
1905
- }
1906
- if(!navPropEntry) {
1907
- navPropEntry = { NavigationProperty: { '=': assocName } };
1908
- }
1909
-
1910
- const props = Object.entries(containee);
1911
-
1912
- const merge = (prefix) => {
1913
- const prop = prefix.split('.')[1];
1914
- // don't overwrite existing restrictions
1915
- if(!navPropEntry[prop]) {
1916
- // Filter properties with prefix and reduce them into a new dictionary
1917
- const o = props.filter(p => p[0].startsWith(prefix+'.')).reduce((a,c) => {
1918
- a[c[0].replace(prefix+'.', '')] = c[1];
1919
- return a;
1920
- }, { });
1921
- // if dictionary has entries, add them to navPropEnty
1922
- if(Object.keys(o).length) {
1923
- // ReadRestrictions may have sub type ReadByKeyRestrictions { Description, LongDescription }
1924
- // chop annotations into dictionaries
1925
- if(prefix === '@Capabilities.ReadRestrictions' &&
1926
- Object.keys(o).some(k => k.startsWith('ReadByKeyRestrictions.'))) {
1927
- const no = {};
1928
- Object.entries(o).forEach(([k,v]) => {
1929
- const [head, ...tail] = k.split('.');
1930
- if(head === 'ReadByKeyRestrictions' && tail.length) {
1931
- if(!no['ReadByKeyRestrictions'])
1932
- no['ReadByKeyRestrictions'] = {};
1933
- // Don't try to add entry into non object
1934
- if(typeof no['ReadByKeyRestrictions'] === 'object')
1935
- no['ReadByKeyRestrictions'][tail.join('.')] = v;
1936
- }
1937
- else {
1938
- no[k] = v;
1939
- }
1940
- });
1941
- navPropEntry[prop] = no;
1942
- }
1943
- else {
1944
- navPropEntry[prop] = o;
1945
- }
1946
- newEntry = true;
1947
- }
1948
- }
1949
- }
1950
- merge('@Capabilities.DeleteRestrictions');
1951
- merge('@Capabilities.InsertRestrictions');
1952
- merge('@Capabilities.UpdateRestrictions');
1953
- merge('@Capabilities.ReadRestrictions');
1954
-
1955
- if(newEntry) {
1956
- if(!hasEntry) {
1957
- if(!resProps)
1958
- resProps = container[anno] = [ ];
1959
- resProps.push(navPropEntry);
1960
- }
1961
- }
1962
- }
1963
- }
1964
-
1965
1977
  function mapCdsToEdmProp(obj) {
1966
- if (obj.type && isBuiltinType(obj.type) && !isAssociationOrComposition(obj) && !obj.targetAspect) {
1967
- let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
1968
- assignProp(obj, '_edmType', edmType);
1978
+ if (obj.type && isBuiltinType(obj.type) && !obj.target && !obj.targetAspect) {
1979
+ let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
1980
+ edmUtils.assignProp(obj, '_edmType', edmType);
1969
1981
  } else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.type)))) {
1970
- let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType'], obj.$path);
1971
- assignProp(obj, '_edmType', edmType);
1982
+ let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType'], obj.$path);
1983
+ edmUtils.assignProp(obj, '_edmType', edmType);
1972
1984
  }
1973
1985
  // This is the special case when we have array of array, but will not be supported in the future
1974
1986
  else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.items.type))) {
1975
- let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
1976
- assignProp(obj, '_edmType', edmType);
1987
+ let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
1988
+ edmUtils.assignProp(obj, '_edmType', edmType);
1977
1989
  }
1978
1990
  }
1979
1991
 
@@ -1991,375 +2003,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1991
2003
  }
1992
2004
  // it is a computed value if it is not a simple value or an annotation
1993
2005
  if(!((def.val !== undefined && !noTailExpr) || def['#'])) {
1994
- assignAnnotation(member, '@Core.ComputedDefaultValue', true);
1995
- }
1996
- }
1997
- }
1998
- }
1999
-
2000
-
2001
-
2002
-
2003
- /*
2004
- * Late application specific transformations
2005
- * At present there are two transformation targets: Structure and Element
2006
- * These transformations are available today:
2007
- *
2008
- * Analytical Scenario:
2009
- * If a structure is annotated with @Aggregation.ApplySupported.PropertyRestrictions
2010
- * then a number of annotation rewrites are done to this structure and to the
2011
- * elements of this structure
2012
- * Also the key properties of all structure elements are removed and a new
2013
- * artificial key element 'key _ID : String' is inserted at first position of
2014
- * the elements dictionary
2015
- *
2016
- * PDM (Personal Data Management)
2017
- * Planned but not yet implemented annotation rewriting (pending to finalization)
2018
- * /
2019
-
2020
- /* eslint max-statements-per-line:off */
2021
-
2022
- function mapAnnotationAssignment(artifact, parent, mappingDictionary)
2023
- {
2024
- let props = intersect(Object.keys(mappingDictionary), Object.keys(artifact));
2025
- // now start the substitution
2026
- props.forEach(prop => {
2027
- let [ mapping, value, remove_original ] = mappingDictionary[prop];
2028
- if(mapping instanceof Function)
2029
- {
2030
- mapping(artifact, parent, prop);
2031
- }
2032
- else
2033
- {
2034
- assignAnnotation(artifact, mapping, value || artifact[prop]['='] || artifact[prop]);
2035
- }
2036
-
2037
- if(remove_original)
2038
- delete artifact[prop];
2039
- });
2040
- }
2041
-
2042
- function applyAppSpecificLateCsnTransformationOnElement(options, element, struct, error)
2043
- {
2044
- if(options.isV2())
2045
- {
2046
- if(struct['@Aggregation.ApplySupported.PropertyRestrictions'])
2047
- {
2048
- mapAnnotationAssignment(element, struct, AnalyticalAnnotations());
2049
- }
2050
- mapAnnotationAssignment(element, struct, PDMSemantics());
2051
- }
2052
-
2053
- // etag requires Core.OptimisticConcurrency to be set in V4 (cap/issues#2641)
2054
- // Oliver Heinrich mentions in the issue that the Okra runtime must be set to a
2055
- // concurrent runtime mode by the caller, if the annotation is added this late,
2056
- // it doesn't appear in the forOData processed CSN, meaning that the
2057
- // runtime cannot set that okra flag (alternatively the runtime has to search
2058
- // for @[odata|cds].etag annotations...
2059
- if(options.isV4())
2060
- {
2061
- if(element['@odata.etag'] == true || element['@cds.etag'] == true) {
2062
- // don't put element name into collection as per advice from Ralf Handl, as
2063
- // no runtime is interested in the property itself, it is sufficient to mark
2064
- // the entity set.
2065
- assignAnnotation(struct, '@Core.OptimisticConcurrency',
2066
- (struct['@Core.OptimisticConcurrency'] || [])/*.push(element.name)*/);
2067
- }
2068
- }
2069
-
2070
- // nested functions begin
2071
- function PDMSemantics()
2072
- {
2073
- /*
2074
- let dict = Object.create(null);
2075
-
2076
- dict['@PDM.xxx1'] = [ '@sap.pdm-semantics' ];
2077
- dict['@PDM.xxx2'] = [ '@sap.pdm-propery' ];
2078
- dict['@PDM.xxx3'] = [ '@sap.pdm-display-sq-no' ];
2079
- dict['@PDM.xxx4'] = [ '@sap.pdm-record-identifier' ];
2080
- dict['@PDM.xxx5'] = [ '@sap.pdm-field-group' ];
2081
- dict['@PDM.xxx6'] = [ '@sap.pdm-mask-find-pattern' ];
2082
- dict['@PDM.xxx7'] = [ '@sap.pdm-mask-replacement-pattern' ];
2083
- dict['@PDM.xxx8'] = [ '@sap.deletable' ];
2084
- dict['@PDM.xxx8'] = [ '@sap.updatable' ];
2085
-
2086
- // respect flattened annotation $value
2087
- Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
2088
- */
2089
- return Object.create(null);
2090
- }
2091
-
2092
- function AnalyticalAnnotations()
2093
- {
2094
- function mapCommonAttributes(element, struct, prop)
2095
- {
2096
- let CommonAttributes = element[prop];
2097
- if(!Array.isArray(CommonAttributes)) {
2098
- error(null, ['definitions', struct.name, 'elements', element.name],
2099
- { anno: '@Common.attribute', code: JSON.stringify(CommonAttributes) },
2100
- `Expect array value for $(ANNOTATION): $(CODE)`);
2101
- return;
2006
+ edmUtils.assignAnnotation(member, '@Core.ComputedDefaultValue', true);
2102
2007
  }
2103
-
2104
- let targets = intersect(CommonAttributes, Object.keys(struct.elements));
2105
- targets.forEach(tgt => {
2106
- assignAnnotation(struct.elements[tgt], '@sap.attribute-for', element.name);
2107
- });
2108
2008
  }
2109
-
2110
- function mapContextDefiningProperties(element, struct, prop)
2111
- {
2112
- let ContextDefiningProperties = element[prop];
2113
- if(!Array.isArray(ContextDefiningProperties)) {
2114
- error(null, ['definitions', struct.name, 'elements', element.name],
2115
- { anno: '@Aggregation.ContextDefiningProperties', code: JSON.stringify(ContextDefiningProperties) },
2116
- `Expect array value for $(ANNOTATION): $(CODE)`);
2117
- return;
2118
- }
2119
- if(ContextDefiningProperties.length > 0)
2120
- assignAnnotation(element, '@sap.super-ordinate', ContextDefiningProperties[ContextDefiningProperties.length-1]);
2121
- }
2122
-
2123
- let dict = Object.create(null);
2124
- //analytics term definition unknown, lower case
2125
- dict['@Analytics.Measure'] = [ '@sap.aggregation-role', 'measure' ];
2126
- dict['@Analytics.Dimension'] = [ '@sap.aggregation-role', 'dimension' ];
2127
- dict['@Semantics.currencyCode'] = [ '@sap.semantics', 'currency-code', true ];
2128
- dict['@Semantics.unitOfMeasure'] = [ '@sap.semantics', 'unit-of-measure', true ];
2129
-
2130
- dict['@Measures.ISOCurrency'] = [ '@sap.unit' ];
2131
- dict['@Measures.Unit'] = [ '@sap.unit' ];
2132
-
2133
- dict['@Common.Label'] = [ '@sap.label' ];
2134
- dict['@Common.Text'] = [ '@sap.text' ];
2135
- dict['@Aggregation.ContextDefiningProperties'] = [ mapContextDefiningProperties ];
2136
- dict['@Common.Attributes'] = [ mapCommonAttributes ];
2137
-
2138
- // respect flattened annotation $value
2139
- Object.entries(dict).forEach(([k, v]) => dict[k+'.$value'] = v);
2140
- return dict;
2141
- }
2142
- }
2143
-
2144
- function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error)
2145
- {
2146
- if(options.isV2())
2147
- {
2148
- if(struct['@Aggregation.ApplySupported.PropertyRestrictions'])
2149
- {
2150
- transformAnalyticalModel(struct);
2151
- mapAnnotationAssignment(struct, undefined, AnalyticalAnnotations());
2152
- }
2153
- }
2154
-
2155
- // nested functions begin
2156
- function transformAnalyticalModel(struct)
2157
- {
2158
- let keyName = 'ID__';
2159
- if(struct == undefined || struct.elements == undefined || struct.elements[keyName] != undefined)
2160
- return;
2161
-
2162
- // remove key prop from elements, add new key to elements
2163
- let elements = Object.create(null);
2164
- let key = { name: keyName, key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false, '@UI.Hidden': true };
2165
- elements[keyName] = key;
2166
- setProp(struct, '$keys',{ [keyName] : key } );
2167
- forEachGeneric(struct.items || struct, 'elements', (e,n) =>
2168
- {
2169
- if(e.key) delete e.key;
2170
- elements[n] = e;
2171
- });
2172
- struct.elements = elements;
2173
- }
2174
-
2175
- function AnalyticalAnnotations()
2176
- {
2177
- function mapFilterRestrictions(struct, parent, prop)
2178
- {
2179
- let stringDict = Object.create(null);
2180
- stringDict['SingleValue'] = 'single-value';
2181
- stringDict['MultiValue'] = 'multi-value';
2182
- stringDict['SingleRange'] = 'interval';
2183
-
2184
- let filterRestrictions = struct[prop];
2185
- if(!Array.isArray(filterRestrictions)) {
2186
- error(null, ['definitions', struct.name ],
2187
- { anno: '@Capabilities.FilterRestrictions.FilterExpressionRestrictions',
2188
- code: JSON.stringify(filterRestrictions) },
2189
- `Expect array value for $(ANNOTATION): $(CODE)`);
2190
- return;
2191
- }
2192
- filterRestrictions.forEach(v => {
2193
- let e = struct.elements[v.Property];
2194
- if(e)
2195
- assignAnnotation(e, '@sap.filter-restriction', stringDict[v.AllowedExpressions]);
2196
- });
2197
- }
2198
-
2199
- function mapRequiredProperties(struct, parent, prop)
2200
- {
2201
- let requiredProperties = struct[prop];
2202
- if(!Array.isArray(requiredProperties)) {
2203
- error(null, ['definitions', struct.name],
2204
- { anno: '@Capabilities.FilterRestrictions.RequiredProperties',
2205
- code: JSON.stringify(requiredProperties) },
2206
- `Expect array value for $(ANNOTATION): $(CODE)`);
2207
- return;
2208
- }
2209
-
2210
- let props = intersect(Object.keys(struct.elements), requiredProperties)
2211
- props.forEach(p => {
2212
- assignAnnotation(struct.elements[p], '@sap.required-in-filter', true);
2213
- });
2214
- }
2215
-
2216
- function mapRequiresFilter(struct, parent, prop)
2217
- {
2218
- let requiresFilter = struct[prop];
2219
- if(requiresFilter)
2220
- assignAnnotation(struct._SetAttributes, '@sap.requires-filter', requiresFilter);
2221
- }
2222
-
2223
- // Entity Props
2224
- let dict = Object.create(null);
2225
- dict['@Aggregation.ApplySupported.PropertyRestrictions'] = [ '@sap.semantics', 'aggregate' ];
2226
- dict['@Common.Label'] = [ '@sap.label' ];
2227
- dict['@Capabilities.FilterRestrictions.RequiresFilter'] = [ mapRequiresFilter ];
2228
- dict['@Capabilities.FilterRestrictions.RequiredProperties'] = [ mapRequiredProperties ];
2229
- dict['@Capabilities.FilterRestrictions.FilterExpressionRestrictions'] = [ mapFilterRestrictions ];
2230
-
2231
- // respect flattened annotation $value
2232
- Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
2233
-
2234
- return dict;
2235
- }
2236
- }
2237
-
2238
- function setSAPSpecificV2AnnotationsToEntityContainer(options, carrier) {
2239
- if(!options.isV2())
2240
- return;
2241
- // documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0#SAPAnnotationsforODataVersion2.0-Elementedm:EntityContainer
2242
- const SetAttributes = {
2243
- // EntityContainer only
2244
- '@sap.supported.formats' : addToSetAttr,
2245
- '@sap.use.batch': addToSetAttr,
2246
- '@sap.message.scope.supported': addToSetAttr,
2247
- };
2248
-
2249
- Object.entries(carrier).forEach(([p, v]) => {
2250
- (SetAttributes[p] || function() { /* no-op */ })(carrier, p, v);
2251
- });
2252
-
2253
- function addToSetAttr(carrier, propName, propValue, removeFromType=true) {
2254
- assignProp(carrier, '_SetAttributes', Object.create(null));
2255
- assignAnnotation(carrier._SetAttributes, propName, propValue);
2256
- if(removeFromType) {
2257
- delete carrier[propName];
2258
- }
2259
- }
2260
- }
2261
-
2262
- function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
2263
- if(!options.isV2())
2264
- return;
2265
- // documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0#SAPAnnotationsforODataVersion2.0-Elementedm:EntitySet
2266
- const SetAttributes = {
2267
- // EntitySet, EntityType
2268
- '@sap.label' : (s,pn, pv) => { addToSetAttr(s, pn, pv, false); },
2269
- '@sap.semantics': checkSemantics,
2270
- // EntitySet only
2271
- '@sap.creatable' : addToSetAttr,
2272
- '@sap.updatable' : addToSetAttr,
2273
- '@sap.deletable': addToSetAttr,
2274
- '@sap.updatable.path': addToSetAttr,
2275
- '@sap.deletable.path': addToSetAttr,
2276
- '@sap.searchable' : addToSetAttr,
2277
- '@sap.pagable': addToSetAttr,
2278
- '@sap.topable': addToSetAttr,
2279
- '@sap.countable': addToSetAttr,
2280
- '@sap.addressable': addToSetAttr,
2281
- '@sap.requires.filter': addToSetAttr,
2282
- '@sap.change.tracking': addToSetAttr,
2283
- '@sap.maxpagesize': addToSetAttr,
2284
- '@sap.delta.link.validity': addToSetAttr,
2285
- };
2286
-
2287
- Object.entries(carrier).forEach(([p, v]) => {
2288
- (SetAttributes[p] || function() { /* no-op */ })(carrier, p, v);
2289
- });
2290
-
2291
- function addToSetAttr(carrier, propName, propValue, removeFromType=true) {
2292
- assignProp(carrier, '_SetAttributes', Object.create(null));
2293
- assignAnnotation(carrier._SetAttributes, propName, propValue);
2294
- if(removeFromType) {
2295
- delete carrier[propName];
2296
- }
2297
- }
2298
-
2299
- function checkSemantics(struct, propName, propValue) {
2300
- if(propValue === 'timeseries' || propValue === 'aggregate') {
2301
- // aggregate is forwarded to Set and must remain on Type
2302
- addToSetAttr(struct, propName, propValue, propValue !== 'aggregate');
2303
- }
2304
- }
2305
- }
2306
-
2307
- function setSAPSpecificV2AnnotationsToAssociation(carrier) {
2308
- // documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0
2309
- const SetAttributes = {
2310
- // Applicable to NavProp and foreign keys, add to AssociationSet
2311
- '@sap.creatable' : (c, pn, pv) => { addToAssociationSet(c, pn, pv, false); },
2312
- // Not applicable to NavProp, applicable to foreign keys, add to AssociationSet
2313
- '@sap.updatable' : addToAssociationSet,
2314
- // Not applicable to NavProp, not applicable to foreign key, add to AssociationSet
2315
- '@sap.deletable': (c, pn, pv) => {
2316
- addToAssociationSet(c, pn, pv);
2317
- removeFromForeignKey(c, pn);
2318
- },
2319
- // applicable to NavProp, not applicable to foreign keys, not applicable to AssociationSet
2320
- '@sap.creatable.path': removeFromForeignKey,
2321
- '@sap.filterable': removeFromForeignKey,
2322
- };
2323
-
2324
- Object.entries(carrier).forEach(([p, v]) => {
2325
- (SetAttributes[p] || function() {/* no-op */})(carrier, p, v);
2326
- });
2327
-
2328
- function addToAssociationSet(carrier, propName, propValue, removeFromType=true) {
2329
- if(isAssociationOrComposition(carrier)) {
2330
- assignProp(carrier, '_SetAttributes', Object.create(null));
2331
- assignAnnotation(carrier._SetAttributes, propName, propValue);
2332
- if(removeFromType) {
2333
- delete carrier[propName];
2334
- }
2335
- }
2336
- }
2337
-
2338
- function removeFromForeignKey(carrier, propName) {
2339
- if(carrier['@odata.foreignKey4'] && carrier[propName] !== undefined) {
2340
- delete carrier[propName];
2341
- }
2342
- }
2343
- }
2344
-
2345
- // Assign but not overwrite annotation
2346
- function assignAnnotation(node, name, value) {
2347
- if(value !== undefined &&
2348
- name !== undefined && name[0] === '@' &&
2349
- (node[name] === undefined ||
2350
- node[name] && node[name] === null)) {
2351
- node[name] = value;
2352
- }
2353
- }
2354
-
2355
- // Set non enumerable property if it doesn't exist yet
2356
- function assignProp(obj, prop, value) {
2357
- if(obj[prop] === undefined) {
2358
- setProp(obj, prop, value);
2359
2009
  }
2360
2010
  }
2361
2011
 
2362
2012
  module.exports = {
2363
2013
  initializeModel,
2364
- assignAnnotation
2365
2014
  }