@sap/cds-compiler 2.13.6 → 2.15.4

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 (78) hide show
  1. package/CHANGELOG.md +128 -4
  2. package/bin/cdsc.js +112 -37
  3. package/lib/api/main.js +20 -22
  4. package/lib/api/options.js +2 -3
  5. package/lib/api/validate.js +6 -6
  6. package/lib/base/message-registry.js +92 -17
  7. package/lib/base/messages.js +85 -64
  8. package/lib/base/optionProcessorHelper.js +19 -0
  9. package/lib/checks/annotationsOData.js +11 -32
  10. package/lib/checks/arrayOfs.js +1 -34
  11. package/lib/checks/validator.js +2 -4
  12. package/lib/compiler/assert-consistency.js +1 -0
  13. package/lib/compiler/base.js +1 -0
  14. package/lib/compiler/builtins.js +11 -0
  15. package/lib/compiler/checks.js +22 -70
  16. package/lib/compiler/define.js +59 -11
  17. package/lib/compiler/extend.js +20 -3
  18. package/lib/compiler/finalize-parse-cdl.js +26 -20
  19. package/lib/compiler/index.js +75 -26
  20. package/lib/compiler/populate.js +6 -5
  21. package/lib/compiler/propagator.js +4 -1
  22. package/lib/compiler/resolve.js +104 -16
  23. package/lib/compiler/shared.js +61 -27
  24. package/lib/compiler/tweak-assocs.js +7 -1
  25. package/lib/edm/annotations/genericTranslation.js +93 -21
  26. package/lib/edm/csn2edm.js +216 -98
  27. package/lib/edm/edm.js +305 -226
  28. package/lib/edm/edmPreprocessor.js +499 -423
  29. package/lib/edm/edmUtils.js +22 -22
  30. package/lib/gen/Dictionary.json +98 -22
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +3 -1
  33. package/lib/gen/languageParser.js +4636 -4368
  34. package/lib/json/csnVersion.js +10 -11
  35. package/lib/json/from-csn.js +3 -2
  36. package/lib/json/to-csn.js +0 -2
  37. package/lib/language/docCommentParser.js +2 -2
  38. package/lib/language/genericAntlrParser.js +47 -2
  39. package/lib/language/language.g4 +59 -27
  40. package/lib/main.d.ts +19 -1
  41. package/lib/main.js +6 -0
  42. package/lib/model/csnRefs.js +33 -6
  43. package/lib/model/csnUtils.js +193 -75
  44. package/lib/model/enrichCsn.js +1 -0
  45. package/lib/model/revealInternalProperties.js +2 -2
  46. package/lib/modelCompare/compare.js +6 -6
  47. package/lib/optionProcessor.js +62 -26
  48. package/lib/render/toCdl.js +844 -679
  49. package/lib/render/toHdbcds.js +189 -243
  50. package/lib/render/toSql.js +180 -198
  51. package/lib/render/utils/common.js +131 -15
  52. package/lib/transform/db/.eslintrc.json +1 -1
  53. package/lib/transform/db/associations.js +2 -2
  54. package/lib/transform/db/constraints.js +3 -1
  55. package/lib/transform/db/expansion.js +15 -10
  56. package/lib/transform/db/flattening.js +95 -68
  57. package/lib/transform/db/transformExists.js +7 -7
  58. package/lib/transform/db/views.js +6 -3
  59. package/lib/transform/forHanaNew.js +43 -26
  60. package/lib/transform/forOdataNew.js +43 -42
  61. package/lib/transform/localized.js +12 -7
  62. package/lib/transform/odata/toFinalBaseType.js +8 -6
  63. package/lib/transform/odata/typesExposure.js +145 -197
  64. package/lib/transform/transformUtilsNew.js +9 -12
  65. package/lib/transform/translateAssocsToJoins.js +5 -1
  66. package/lib/transform/universalCsn/coreComputed.js +5 -3
  67. package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
  68. package/lib/utils/moduleResolve.js +13 -6
  69. package/package.json +1 -1
  70. package/share/messages/message-explanations.json +2 -1
  71. package/share/messages/syntax-expected-integer.md +37 -0
  72. package/lib/transform/odata/attachPath.js +0 -96
  73. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  74. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  75. package/lib/transform/odata/referenceFlattener.js +0 -296
  76. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  77. package/lib/transform/odata/structuralPath.js +0 -72
  78. package/lib/transform/odata/structureFlattener.js +0 -171
@@ -2,7 +2,7 @@
2
2
  /* eslint max-statements-per-line:off */
3
3
  const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
4
4
  const { forEachDefinition, forEachGeneric, forEachMemberRecursively,
5
- isEdmPropertyRendered, getUtils, cloneCsn, isBuiltinType } = require('../model/csnUtils');
5
+ isEdmPropertyRendered, getUtils, cloneCsnNonDict, isBuiltinType } = require('../model/csnUtils');
6
6
  const edmUtils = require('./edmUtils.js');
7
7
  const typesExposure = require('../transform/odata/typesExposure');
8
8
  const expandCSNToFinalBaseType = require('../transform/odata/toFinalBaseType');
@@ -18,10 +18,8 @@ const {
18
18
  isParameterizedEntity,
19
19
  resolveOnConditionAndPrepareConstraints,
20
20
  finalizeReferentialConstraints,
21
- isODataSimpleIdentifier,
22
21
  isEntity,
23
22
  getSchemaPrefix,
24
- isActionOrFunction
25
23
  } = require('./edmUtils.js');
26
24
 
27
25
  /**
@@ -34,62 +32,46 @@ const {
34
32
  * @param {CSN.Model} csn
35
33
  * @param {object} _options
36
34
  */
37
- function initializeModel(csn, _options, messageFunctions)
35
+ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=undefined)
38
36
  {
39
- if (!_options)
40
- throw Error('Please debug me: initializeModel must be invoked with options');
37
+ const { info, warning, error, message, throwWithAnyError } = messageFunctions;
41
38
 
42
- const { info, warning, error, message, throwWithError } = messageFunctions;
43
-
44
- const csnUtils = getUtils(csn);
45
- const {
46
- inspectRef,
47
- getCsnDef,
48
- getFinalTypeDef,
49
- isStructured,
50
- isAssocOrComposition,
51
- } = getUtils(csn);
39
+ let csnUtils = getUtils(csn);
52
40
 
53
41
  // proxies are merged into the final model after all proxy elements are collected
54
42
  const proxyCache = [];
43
+ // iterarte only over those definitions that need to be preprocessed
44
+ // instead of mangling through the whole model each time
45
+ // preprocess steps removing adding to the model must co-modify this map
46
+ const reqDefs = { definitions: Object.create(null) };
47
+
55
48
 
56
49
  // make sure options are complete
57
50
  let options = validateOptions(_options);
58
51
 
59
- // Fetch service definitions
60
- const allDefs = Object.entries(csn.definitions||{});
61
-
62
- const serviceRoots = allDefs.reduce((serviceRoots, [artName, art]) => {
63
- if(art.kind === 'service') {
64
- serviceRoots[artName] = Object.assign(art, { name: artName });
65
- }
66
- return serviceRoots;
67
- }, Object.create(null) );
52
+ const [ serviceRoots,
53
+ serviceRootNames,
54
+ fallBackSchemaName,
55
+ whatsMyServiceRootName ] = getAnOverviewOnTheServices(csn);
68
56
 
69
- // first of all we need to know about all 'real' user defined services
70
- const serviceRootNames = Object.keys(serviceRoots).sort((a,b)=>b.length-a.length);
71
-
72
- function whatsMyServiceRootName(n, self=true) {
73
- return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
57
+ if(requestedServiceNames === undefined)
58
+ requestedServiceNames = options.serviceNames;
59
+ if(requestedServiceNames === undefined) {
60
+ requestedServiceNames = serviceRootNames;
74
61
  }
75
62
 
76
- // find a globally unambiguous schema name to collect all top level 'root' types
77
- // TODO: work on service basis (this requires post exposure renaming)
78
- let autoexposeSchemaName = 'root';
79
- let i = 1;
80
- while (allDefs.some(([artName, _art]) => {
81
- const p = artName.split('.');
82
- return p.length === 2 && p[0] === autoexposeSchemaName;
83
- })) {
84
- autoexposeSchemaName = 'root' + i++;
63
+ function isMyServiceRequested(n) {
64
+ return requestedServiceNames.includes(whatsMyServiceRootName(n));
85
65
  }
86
66
 
87
67
  if(serviceRootNames.length === 0) {
88
- return [serviceRoots, Object.create(null), whatsMyServiceRootName, autoexposeSchemaName, options];
68
+ return [serviceRoots, Object.create(null), reqDefs, whatsMyServiceRootName, fallBackSchemaName, options];
89
69
  }
90
70
 
91
71
  // Structural CSN inbound QA checks
92
72
  inboundQualificationChecks();
73
+ // not needed at the moment
74
+ // resolveForeignKeyRefs();
93
75
 
94
76
  if(isBetaEnabled(options, undefined)) {
95
77
  splitDottedDefinitionsIntoSeparateServices();
@@ -102,10 +84,13 @@ function initializeModel(csn, _options, messageFunctions)
102
84
  renameDottedDefinitionsInsideServiceOrContext();
103
85
 
104
86
  /*
105
- In order to cover the scenario when the renderer is called with option V2 directly, there is
106
- the need to expand to final base type when the CSN was already transformed, bur for V4.
107
- The logic will run also when the CSN was persisted on a file system and the non-enumerable
108
- properties are lost. Also, will be execute when called with cdsc.
87
+ 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
89
+ edmx generator is called directly (bypassing OData transformation)
90
+ 2) The input CSN was already transformed for V4 and persisted (all non-enumerables are
91
+ stripped of)
92
+ 3) call via cdsc
93
+
109
94
  At the end of the day, this module must be called only here, in the renderer and removed
110
95
  as a step in the OData transformer with the goal to have a protocol agnostic OData CSN.
111
96
  */
@@ -113,20 +98,18 @@ function initializeModel(csn, _options, messageFunctions)
113
98
  const { toFinalBaseType }= require('../transform/transformUtilsNew').getTransformers(csn, options);
114
99
  expandCSNToFinalBaseType(csn, { toFinalBaseType }, csnUtils, serviceRootNames, options);
115
100
  }
101
+
116
102
  /*
117
- Enrich the CSN by de-anonymizing and exposing of required types that are defined outside services.
103
+ Enrich the CSN by de-anonymizing and exposing types that are required to make the service self contained.
118
104
  Type exposure will add additional schema contexts and group the exposed types in these contexts.
119
105
  contexts either represent another service (if the type to be exposed resides in that
120
106
  service), the namespace (including (sub-)contexts) or as last resort (if the type name
121
107
  has no prefix path) a 'root' namespace.
122
108
  */
123
- const schemas = typesExposure(csn, whatsMyServiceRootName, autoexposeSchemaName, options, csnUtils, { error });
109
+ const schemas = typesExposure(csn, whatsMyServiceRootName, requestedServiceNames,
110
+ fallBackSchemaName, options, csnUtils, { error });
124
111
 
125
- // First attach names to all definitions (and actions/params) in the model
126
- // elements are done in initializeStruct
127
- forEachDefinition(csn, attachNameProperty);
128
-
129
- // next, we must get an overview about all schemas (including the services)
112
+ // Get an overview about all schemas (including the services)
130
113
  const schemaNames = [...serviceRootNames];
131
114
  schemaNames.push(...Object.keys(schemas));
132
115
  // sort schemas in reverse order to allow longest match in whatsMySchemaName function
@@ -136,53 +119,99 @@ function initializeModel(csn, _options, messageFunctions)
136
119
  }
137
120
 
138
121
  if(schemaNames.length) {
139
- Object.values(serviceRoots).forEach(initializeService);
122
+ // First attach names to all definitions (and actions/params) in the model
123
+ // elements are done in initializeStruct
140
124
  // Set myServiceName for later reference and indication of a service member
141
- // First attach names to all definitions in the model
125
+ // First attach names to all definitions in the model and fill reqDefs
142
126
  // Link association targets and spray @odata.contained over untagged compositions
143
- forEachDefinition(csn, [ (def, defName) => {
144
- const mySchemaName = whatsMySchemaName(defName);
145
- mySchemaName && setProp(def, '$mySchemaName', mySchemaName) }, linkAssociationTarget ]);
127
+ forEachDefinition(csn, [
128
+ attachNameProperty,
129
+ (def, defName) => {
130
+ const mySchemaName = whatsMySchemaName(defName);
131
+ mySchemaName && setProp(def, '$mySchemaName', mySchemaName);
132
+ if(isMyServiceRequested(defName))
133
+ reqDefs.definitions[defName] = def;
134
+ },
135
+ linkAssociationTarget ]);
136
+ // initialize requested services
137
+ const skip = { skipArtifact: (_def, defName) => !isMyServiceRequested(defName) };
138
+ forEachDefinition({ definitions: serviceRoots }, initService, skip);
146
139
  // Create data structures for containments
147
- forEachDefinition(csn, initializeContainments);
140
+ forEachDefinition(reqDefs, initContainments);
148
141
  // Initialize entities with parameters (add Parameter entity)
149
- forEachDefinition(csn, initializeParameterizedEntityOrView);
142
+ forEachDefinition(reqDefs, initParameterizedEntityOrView);
150
143
  // Initialize structures
151
- forEachDefinition(csn, initializeStructure);
144
+ forEachDefinition(csn, initStructure);
152
145
  // Initialize associations after _parent linking
153
- forEachDefinition(csn, prepareConstraints);
146
+ forEachDefinition(reqDefs, initConstraints);
154
147
  // Mute V4 elements depending on constraint preparation
155
148
  if(options.isV4())
156
- forEachDefinition(csn, ignoreProperties);
149
+ forEachDefinition(reqDefs, ignoreProperties);
157
150
  // calculate constraints based on ignoreProperties and prepareConstraints
158
- forEachDefinition(csn, finalizeConstraints);
151
+ forEachDefinition(reqDefs, finalizeConstraints);
159
152
  // convert exposed types into cross schema references if required
160
153
  // must be run before proxy exposure to avoid potential reference collisions
161
154
  convertExposedTypesOfOtherServicesIntoCrossReferences();
162
155
  // create association target proxies
163
156
  // Decide if an entity set needs to be constructed or not
164
- forEachDefinition(csn, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
157
+ forEachDefinition(reqDefs, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
165
158
  // finalize proxy creation
166
159
  mergeProxiesIntoModel();
167
160
 
168
161
  if(options.isV4())
169
- forEachDefinition(csn, initializeEdmNavPropBindingTargets);
162
+ forEachDefinition(reqDefs, initEdmNavPropBindingTargets);
170
163
 
171
164
  // Things that can be done in one pass
172
165
  // Create edmKeyRefPaths
173
166
  // Create NavigationPropertyBindings, requires determineEntitySet
174
167
  // Map /** doc comments */ to @CoreDescription
175
- // Artifact identifier spec compliance check (should be run last)
176
- forEachDefinition(csn, [ initializeEdmKeyRefPaths, initializeEdmNavPropBindingPaths,
177
- initializeEdmTypesAndDescription, checkArtifactIdentifierAndBoundActions ]);
168
+ forEachDefinition(reqDefs, [
169
+ initEdmKeyRefPaths,
170
+ initEdmNavPropBindingPaths,
171
+ initEdmTypesAndDescription
172
+ ]);
178
173
  }
179
- return [serviceRoots, schemas, whatsMyServiceRootName, autoexposeSchemaName, options];
174
+ return [serviceRoots, schemas, reqDefs, whatsMyServiceRootName, fallBackSchemaName, options];
180
175
 
181
176
  //////////////////////////////////////////////////////////////////////
182
177
  //
183
178
  // Service initialization starts here
184
179
  //
185
180
 
181
+ function getAnOverviewOnTheServices(csn) {
182
+ const defs = csn.definitions || {};
183
+ const serviceRoots = Object.create(null);
184
+ for(const defName in defs) {
185
+ const def = defs[defName];
186
+ if(def && def.kind === 'service')
187
+ serviceRoots[defName] = Object.assign(def, { name: defName });
188
+ }
189
+
190
+ // first of all we need to know about all 'real' user defined services
191
+ const serviceRootNames = Object.keys(serviceRoots).sort((a,b)=>b.length-a.length);
192
+
193
+ function whatsMyServiceRootName(n, self=true) {
194
+ return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
195
+ }
196
+
197
+ // find a globally unambiguous schema name to collect all top level 'root' types
198
+ // TODO: work on service basis (this requires post exposure renaming)
199
+ let fallBackSchemaName = 'root';
200
+ let i = 1;
201
+ while (Object.keys(defs).some(artName => {
202
+ const p = artName.split('.');
203
+ return p.length === 2 && p[0] === fallBackSchemaName;
204
+ })) {
205
+ fallBackSchemaName = 'root' + i++;
206
+ }
207
+
208
+ return [
209
+ serviceRoots,
210
+ serviceRootNames,
211
+ fallBackSchemaName,
212
+ whatsMyServiceRootName ];
213
+ }
214
+
186
215
  /*
187
216
  Replace dots in sub-service and sub-context definitions with underscores to be
188
217
  Odata ID compliant.
@@ -194,9 +223,10 @@ function initializeModel(csn, _options, messageFunctions)
194
223
  // Find the first definition above the current definition or undefined otherwise.
195
224
  // Definition can either be a context or a service
196
225
  function getRootDef(name) {
226
+ const scopeKinds = {'service':1, 'context':1};
197
227
  let pos = name.lastIndexOf('.');
198
228
  name = pos < 0 ? undefined : name.substring(0, pos);
199
- while (name && !['service', 'context'].includes(csn.definitions[name] && csn.definitions[name].kind)) {
229
+ while (name && !((csn.definitions[name] && csn.definitions[name].kind) in scopeKinds)) {
200
230
  pos = name.lastIndexOf('.');
201
231
  name = pos < 0 ? undefined : name.substring(0, pos);
202
232
  }
@@ -205,8 +235,9 @@ function initializeModel(csn, _options, messageFunctions)
205
235
 
206
236
  const dotEntityNameMap = Object.create(null);
207
237
  const dotTypeNameMap = Object.create(null);
238
+ const kinds = {'entity':1, 'type':1, 'action':1, 'function':1};
208
239
  forEachDefinition(csn, (def, defName) => {
209
- if(['entity', 'type', 'action', 'function'].includes(def.kind)) {
240
+ if(def.kind in kinds) {
210
241
  const rootDef = getRootDef(defName);
211
242
  // if this definition has a root def and the root def is not the service/schema name
212
243
  // => service C { type D.E }, replace the prefix dots with underscores
@@ -303,18 +334,8 @@ function initializeModel(csn, _options, messageFunctions)
303
334
  }
304
335
 
305
336
  // initialize the service itself
306
- function initializeService(service) {
307
- // check service name
308
- if (service.name.length > 511) {
309
- error(null, ['definitions', service.name], 'OData namespace must not exceed 511 characters' );
310
- }
311
- const simpleIdentifiers = service.name.split('.');
312
- simpleIdentifiers.forEach((identifier) => {
313
- if (!isODataSimpleIdentifier(identifier)) {
314
- signalIllegalIdentifier(identifier, ['definitions', service.name]);
315
- }
316
- });
317
- setSAPSpecificV2AnnotationsToEntityContainer(options, service);
337
+ function initService(serviceRoot) {
338
+ setSAPSpecificV2AnnotationsToEntityContainer(options, serviceRoot);
318
339
  }
319
340
 
320
341
  // link association target to association and add @odata.contained to compositions in V4
@@ -361,7 +382,7 @@ function initializeModel(csn, _options, messageFunctions)
361
382
  // non-containment rendering. If containment rendering is active, the containee has no
362
383
  // entity set. Instead try to rewrite the annotation in such a way that it is effective
363
384
  // on the containment navigation property.
364
- function initializeContainments(container) {
385
+ function initContainments(container) {
365
386
  if(container.kind === 'entity') {
366
387
  forEachMemberRecursively(container, initContainments,
367
388
  [], true, { elementsOnly: true });
@@ -424,7 +445,7 @@ function initializeModel(csn, _options, messageFunctions)
424
445
  // must be called.
425
446
  // As a param entity is a potential proxy candidate, this split must be performed on
426
447
  // all definitions
427
- function initializeParameterizedEntityOrView(entityCsn, entityName) {
448
+ function initParameterizedEntityOrView(entityCsn, entityName) {
428
449
 
429
450
  if(!isParameterizedEntity(entityCsn))
430
451
  return;
@@ -441,13 +462,71 @@ function initializeModel(csn, _options, messageFunctions)
441
462
  // Backlink Navigation Property "Parameters" to <ViewName>Parameters
442
463
 
443
464
  // this code can be extended for aggregated views
444
- const parameterEntityName = entityName + 'Parameters';
445
- const originalEntityName = entityName + 'Type';
446
- const originalEntitySetName = entityName + 'Set';
447
- const parameterToOriginalAssocName = 'Set';
448
- const backlinkAssocName = 'Parameters';
465
+ const typeEntityName = entityName + 'Type';
466
+ const typeEntitySetName = entityName + 'Set';
467
+ const parameterToTypeAssocName = 'Set';
468
+ const typeToParameterAssocName = 'Parameters';
449
469
  let hasBacklink = true;
450
470
 
471
+
472
+ // create the Parameter Definition
473
+ const parameterCsn = createParameterEntity(entityCsn, entityName, false);
474
+
475
+ // create the Type Definition
476
+ // modify the original parameter entity with backlink and new name
477
+ if(csn.definitions[typeEntityName])
478
+ error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: typeEntityName });
479
+ else {
480
+ csn.definitions[typeEntityName] = entityCsn;
481
+ reqDefs.definitions[typeEntityName] = entityCsn;
482
+ delete csn.definitions[entityCsn.name];
483
+ delete reqDefs.definitions[entityCsn.name];
484
+ entityCsn.name = typeEntityName;
485
+ }
486
+ setProp(entityCsn, '$entitySetName', typeEntitySetName);
487
+ // add backlink association
488
+ if(hasBacklink) {
489
+ entityCsn.elements[typeToParameterAssocName] = {
490
+ name: typeToParameterAssocName,
491
+ target: parameterCsn.name,
492
+ type: 'cds.Association',
493
+ on: [ { ref: [ 'Parameters', 'Set' ] }, '=', { ref: [ '$self' ] } ]
494
+ };
495
+ setProp(entityCsn.elements[typeToParameterAssocName], '_selfReferences', []);
496
+ setProp(entityCsn.elements[typeToParameterAssocName], '_target', parameterCsn);
497
+ setProp(entityCsn.elements[typeToParameterAssocName], '$path',
498
+ [ 'definitions', typeEntityName, 'elements', typeToParameterAssocName ] );
499
+
500
+ // rewrite $path
501
+ if(entityCsn.$path)
502
+ entityCsn.$path[1] = typeEntityName;
503
+ forEachMemberRecursively(entityCsn, (member) => {
504
+ if(member.$path)
505
+ member.$path[1] = typeEntityName;
506
+ });
507
+ }
508
+
509
+ /*
510
+ <EntitySet Name="ZRHA_TEST_CDSSet" EntityType="ZRHA_TEST_CDS_CDS.ZRHA_TEST_CDSType" sap:creatable="false" sap:updatable="false"
511
+ sap:deletable="false" sap:addressable="false" sap:content-version="1"/>
512
+ */
513
+ assignProp(entityCsn, '_SetAttributes',
514
+ {'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.addressable': false });
515
+
516
+ // redirect inbound associations/compositions to the parameter entity
517
+ Object.keys(entityCsn.$sources || {}).forEach(n => {
518
+ // preserve the original target for constraint calculation
519
+ setProp(entityCsn.$sources[n], '_originalTarget', entityCsn.$sources[n]._target);
520
+ entityCsn.$sources[n]._target = parameterCsn;
521
+ entityCsn.$sources[n].target = parameterCsn.name;
522
+ });
523
+ rewriteContainmentAnnotations(parameterCsn, entityCsn, parameterToTypeAssocName);
524
+ }
525
+
526
+ function createParameterEntity(entityCsn, entityName, isProxy) {
527
+ const parameterEntityName = entityName + 'Parameters';
528
+ const parameterToTypeAssocName = 'Set';
529
+
451
530
  // Construct the parameter entity
452
531
  const parameterCsn = {
453
532
  name: parameterEntityName,
@@ -455,8 +534,8 @@ function initializeModel(csn, _options, messageFunctions)
455
534
  elements: Object.create(null),
456
535
  '@sap.semantics': 'parameters',
457
536
  };
458
- setProp(parameterCsn, '$entitySetName', entityName);
459
-
537
+ if(!isProxy)
538
+ setProp(parameterCsn, '$entitySetName', entityName);
460
539
  if(entityCsn.$location){
461
540
  assignProp(parameterCsn, '$location', entityCsn.$location);
462
541
  }
@@ -482,9 +561,10 @@ function initializeModel(csn, _options, messageFunctions)
482
561
  entityCsn._containerEntity = [ parameterCsn ];
483
562
 
484
563
  forEachGeneric(entityCsn, 'params', (p,n) => {
485
- let elt = cloneCsn(p, options);
564
+ let elt = cloneCsnNonDict(p, options);
486
565
  elt.name = n;
487
566
  delete elt.kind;
567
+ setProp(elt, '$path', [ 'definitions', parameterEntityName, 'elements', n ]);
488
568
  elt.key = true; // params become primary key in parameter entity
489
569
  /*
490
570
  Spec meeting decision 28.02.22:
@@ -500,81 +580,35 @@ function initializeModel(csn, _options, messageFunctions)
500
580
  parameterCsn.elements[n] = elt;
501
581
  });
502
582
  linkAssociationTarget(parameterCsn);
503
- initializeContainments(parameterCsn);
504
- // add assoc to result set, FIXME: is the cardinality correct?
505
- parameterCsn.elements[parameterToOriginalAssocName] = {
506
- '@odata.contained': true,
507
- name: parameterToOriginalAssocName,
508
- target: entityCsn.name,
509
- type: 'cds.Association',
510
- cardinality: { src: 1, min: 0, max: '*' }
511
- };
512
- setProp(parameterCsn.elements[parameterToOriginalAssocName], '_target', entityCsn);
513
- setProp(parameterCsn.elements[parameterToOriginalAssocName], '$path',
514
- [ 'definitions', parameterEntityName, 'elements', parameterToOriginalAssocName ] );
515
-
516
- // rewrite $path
517
- setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
518
- forEachMemberRecursively(parameterCsn, (member) => {
519
- if(member.$path)
520
- member.$path[1] = parameterEntityName;
521
- });
522
-
523
- if(csn.definitions[parameterCsn.name])
524
- error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: parameterCsn.name });
525
- else
526
- csn.definitions[parameterCsn.name] = parameterCsn;
527
- // modify the original parameter entity with backlink and new name
528
- if(csn.definitions[originalEntityName])
529
- error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: originalEntityName });
530
- else {
531
- csn.definitions[originalEntityName] = entityCsn;
532
- delete csn.definitions[entityCsn.name];
533
- entityCsn.name = originalEntityName;
534
- }
535
- setProp(entityCsn, '$entitySetName', originalEntitySetName);
536
- // add backlink association
537
- if(hasBacklink) {
538
- entityCsn.elements[backlinkAssocName] = {
539
- name: backlinkAssocName,
540
- target: parameterCsn.name,
583
+ initContainments(parameterCsn);
584
+ // add assoc to result set, FIXME: is the cardinality correct?
585
+ if(!isProxy) {
586
+ parameterCsn.elements[parameterToTypeAssocName] = {
587
+ '@odata.contained': true,
588
+ name: parameterToTypeAssocName,
589
+ target: entityCsn.name,
541
590
  type: 'cds.Association',
542
- on: [ { ref: [ 'Parameters', 'Set' ] }, '=', { ref: [ '$self' ] } ]
591
+ cardinality: { src: 1, min: 0, max: '*' }
543
592
  };
544
- setProp(entityCsn.elements[backlinkAssocName], '_selfReferences', []);
545
- setProp(entityCsn.elements[backlinkAssocName], '_target', parameterCsn);
546
- setProp(entityCsn.elements[backlinkAssocName], '$path',
547
- [ 'definitions', originalEntityName, 'elements', backlinkAssocName ] );
548
-
549
- // rewrite $path
550
- if(entityCsn.$path)
551
- entityCsn.$path[1] = originalEntityName;
552
- forEachMemberRecursively(entityCsn, (member) => {
553
- if(member.$path)
554
- member.$path[1] = originalEntityName;
555
- });
593
+ setProp(parameterCsn.elements[parameterToTypeAssocName], '_target', entityCsn);
594
+ setProp(parameterCsn.elements[parameterToTypeAssocName], '$path',
595
+ [ 'definitions', parameterEntityName, 'elements', parameterToTypeAssocName ] );
556
596
  }
597
+ // rewrite $path
598
+ setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
557
599
 
558
- /*
559
- <EntitySet Name="ZRHA_TEST_CDSSet" EntityType="ZRHA_TEST_CDS_CDS.ZRHA_TEST_CDSType" sap:creatable="false" sap:updatable="false"
560
- sap:deletable="false" sap:addressable="false" sap:content-version="1"/>
561
- */
562
- assignProp(entityCsn, '_SetAttributes',
563
- {'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.addressable': false });
564
-
565
-
566
-
567
- // redirect inbound associations/compositions to the parameter entity
568
- Object.keys(entityCsn.$sources || {}).forEach(n => {
569
- // preserve the original target for constraint calculation
570
- setProp(entityCsn.$sources[n], '_originalTarget', entityCsn.$sources[n]._target);
571
- entityCsn.$sources[n]._target = parameterCsn;
572
- entityCsn.$sources[n].target = parameterCsn.name;
573
- });
574
- rewriteContainmentAnnotations(parameterCsn, entityCsn, parameterToOriginalAssocName);
600
+ // proxies are registered in model separately
601
+ if(!isProxy) {
602
+ if(csn.definitions[parameterCsn.name])
603
+ error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: parameterCsn.name });
604
+ else {
605
+ csn.definitions[parameterCsn.name] = parameterCsn;
606
+ reqDefs.definitions[parameterCsn.name] = parameterCsn;
607
+ }
608
+ }
609
+ return parameterCsn;
575
610
  }
576
611
 
577
-
578
612
  function initElement(element, name, struct) {
579
613
  setProp(element, 'name', name)
580
614
  setProp(element, '_parent', struct);
@@ -600,7 +634,7 @@ function initializeModel(csn, _options, messageFunctions)
600
634
  }
601
635
 
602
636
  // Initialize a structured artifact
603
- function initializeStructure(def) {
637
+ function initStructure(def) {
604
638
 
605
639
  // Don't operate on any structured types other than type and entity
606
640
  // such as events and aspects
@@ -611,24 +645,13 @@ function initializeModel(csn, _options, messageFunctions)
611
645
  let validFrom = [], validKey = [];
612
646
 
613
647
  // Iterate all struct elements
614
- forEachMemberRecursively(def.items || def, (element, elementName, prop, path = [], construct) => {
648
+ forEachMemberRecursively(def.items || def, (element, elementName, prop, _path = [], construct) => {
615
649
  if(prop !== 'elements')
616
650
  return;
617
651
 
618
652
  initElement(element, elementName, construct);
619
653
 
620
- if(def.kind !== 'event' && def.kind !== 'aspect') {
621
- if(element._parent && element._parent.$mySchemaName) {
622
- if(!isODataSimpleIdentifier(elementName)) {
623
- signalIllegalIdentifier(elementName, ['definitions', def.name].concat(path));
624
- } else if (options.isV2() && /^(_|[0-9])/.test(elementName) && element._parent.kind === 'entity') {
625
- // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
626
- error(null, ['definitions', def.name, 'elements', elementName], { prop: elementName[0] },
627
- 'Element names must not start with $(PROP) for OData V2');
628
- }
629
- }
630
- }
631
- // collect temporal information
654
+ // collect temporal information
632
655
  if(element['@cds.valid.key']) {
633
656
  validKey.push(element);
634
657
  }
@@ -708,18 +731,19 @@ function initializeModel(csn, _options, messageFunctions)
708
731
  }
709
732
 
710
733
  // Prepare the associations for the subsequent steps
711
- function prepareConstraints(struct) {
712
- if(!isStructuredArtifact(struct))
734
+ function initConstraints(def) {
735
+ if(!isStructuredArtifact(def))
713
736
  return;
714
737
 
715
- forEachMemberRecursively(struct.items || struct, (element) => {
716
- if (isAssociationOrComposition(element) && !element._ignore) {
738
+ forEachMemberRecursively(def.items || def, initConstraintsOnAssoc, [], true, { elementsOnly: true });
739
+ }
740
+ function initConstraintsOnAssoc(element) {
741
+ if (isAssociationOrComposition(element) && !element._ignore && !element._constraints) {
717
742
  // setup the constraints object
718
- setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
743
+ setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
719
744
  // and crack the ON condition
720
- resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
721
- }
722
- }, [], true, { elementsOnly: true });
745
+ resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
746
+ }
723
747
  }
724
748
 
725
749
  /*
@@ -791,38 +815,39 @@ function initializeModel(csn, _options, messageFunctions)
791
815
  It may be that now a number of properties are not rendered and cannot act as constraints (see isConstraintCandidate())
792
816
  in edmUtils
793
817
  */
794
- function finalizeConstraints(struct) {
795
- if(!isStructuredArtifact(struct))
818
+ function finalizeConstraints(def) {
819
+ if(!isStructuredArtifact(def))
796
820
  return;
797
821
 
798
- forEachMemberRecursively(struct.items || struct, (element) => {
799
- if (isAssociationOrComposition(element) && !element._ignore) {
800
- finalizeReferentialConstraints(csn, element, options, info);
822
+ forEachMemberRecursively(def.items || def, finalizeConstraintsOnAssoc, [], true, { elementsOnly: true });
823
+ }
824
+ function finalizeConstraintsOnAssoc(element) {
825
+ if (isAssociationOrComposition(element) && !element._ignore && element._constraints) {
826
+ finalizeReferentialConstraints(csn, element, options, info);
801
827
 
802
- if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
828
+ if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
803
829
  // if this is a partnership and this assoc has a set target cardinality, assign it as source cardinality to the partner
804
- if(element._constraints._partnerCsn.cardinality) {
830
+ if(element._constraints._partnerCsn.cardinality) {
805
831
  // if the forward association has set a src cardinality and it deviates from the backlink target cardinality raise a warning
806
832
  // in V2 only, in V4 the source cardinality is rendered implicitly at the Type property
807
- if(element._constraints._partnerCsn.cardinality.src) {
808
- let srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
809
- let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
810
- if(options.isV2() && srcMult !== newMult) {
833
+ if(element._constraints._partnerCsn.cardinality.src) {
834
+ let srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
835
+ let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
836
+ if(options.isV2() && srcMult !== newMult) {
811
837
  // Association 'E_toF': Multiplicity of Role='E' defined to '*', conflicting with target multiplicity '0..1' from
812
- warning(null, null, `Source cardinality "${element._constraints._partnerCsn.cardinality.src}" of "${element._constraints._partnerCsn._parent.name}/${element._constraints._partnerCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`);
813
- }
814
- }
815
- else {
816
- // .. but only if the original assoc hasn't set src yet
817
- element._constraints._partnerCsn.cardinality.src = element.cardinality.max;
838
+ warning(null, null, `Source cardinality "${element._constraints._partnerCsn.cardinality.src}" of "${element._constraints._partnerCsn._parent.name}/${element._constraints._partnerCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`);
818
839
  }
819
840
  }
820
841
  else {
821
- element._constraints._partnerCsn.cardinality = { src: element.cardinality.max };
842
+ // .. but only if the original assoc hasn't set src yet
843
+ element._constraints._partnerCsn.cardinality.src = element.cardinality.max;
822
844
  }
823
845
  }
846
+ else {
847
+ element._constraints._partnerCsn.cardinality = { src: element.cardinality.max };
848
+ }
824
849
  }
825
- }, [], true, { elementsOnly: true });
850
+ }
826
851
  }
827
852
 
828
853
  /*
@@ -838,8 +863,10 @@ function initializeModel(csn, _options, messageFunctions)
838
863
  if(serviceRootNames.includes(targetSchemaName)) {
839
864
  // remove all definitions starting with < fqSchemaName >. and add a schema reference
840
865
  Object.keys(csn.definitions).forEach(dn => {
841
- if(dn.startsWith(fqSchemaName)) // this includes the fqSchemaName context
866
+ if(dn.startsWith(fqSchemaName)) {// this includes the fqSchemaName context
842
867
  delete csn.definitions[dn];
868
+ delete reqDefs.definitions[dn];
869
+ }
843
870
  });
844
871
  if(!schemas[fqSchemaName])
845
872
  schemaNames.push(fqSchemaName);
@@ -870,10 +897,9 @@ function initializeModel(csn, _options, messageFunctions)
870
897
 
871
898
  If option odataExtReferences is used, 'root' proxies are still created.
872
899
 
873
- If an entity type which is a proxy candidate has a managed association as primary key,
874
- all dependent entity types are exposed (or referenced) as well to keep the navigation
875
- graph in tact. This effectively will expose the transitive primary key closure of all
876
- proxies.
900
+ If the association leading to the proxy candidate refers to associations either directly
901
+ or indirectly (via structured elements), these dependent entity types are (recursively) exposed
902
+ (or referenced) as well to keep the navigation graph in tact.
877
903
  */
878
904
  function exposeTargetsAsProxiesOrSchemaRefs(struct) {
879
905
  if(struct.kind === 'context' || struct.kind === 'service' || struct.$proxy)
@@ -923,14 +949,16 @@ function initializeModel(csn, _options, messageFunctions)
923
949
  const targetSchemaName = element._target.$mySchemaName;
924
950
  if(isProxyRequired(element)) {
925
951
  if(options.isV4() &&
926
- (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs)
927
- // must be a managed association with keys and no ON condition
928
- /* && element.keys && !element.on */) {
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
+ ) {
929
957
  // reuse proxy if available
930
958
  let proxy = getProxyForTargetOf(element);
931
959
  if(!proxy) {
932
960
  if(targetSchemaName && options.toOdata.odataXServiceRefs) {
933
- proxy = createSchemaRefFor(element, targetSchemaName);
961
+ proxy = createSchemaRefFor(targetSchemaName);
934
962
  }
935
963
  else if(options.toOdata.odataProxies) {
936
964
  proxy = createProxyFor(element, targetSchemaName);
@@ -941,14 +969,16 @@ function initializeModel(csn, _options, messageFunctions)
941
969
  // if a proxy was either already created or could be created and
942
970
  // if it's a 'real' proxy, link the _target to it and remove constraints
943
971
  // otherwise proxy is a schema reference, then do nothing
972
+ setProp(element, '$noPartner', true);
944
973
  element._constraints.constraints = Object.create(null);
945
974
  if(proxy.kind === 'entity') {
975
+ if(!proxy.$isParamEntity)
976
+ populateProxyElements(element, proxy, getForeignKeyDefinitions(element));
946
977
  element._target = proxy;
947
- populateProxyElements(element, proxy, getForeignKeyDefinitions(element, ['definitions', struct.name, 'elements', element.name]));
948
978
  }
949
979
  else {
950
- // fake the target to be proxy
951
- setProp(element._target, '$externalRef', true);
980
+ // No navigation property bindings on external references
981
+ setProp(element, '$externalRef', true);
952
982
  }
953
983
  }
954
984
  else {
@@ -968,11 +998,11 @@ function initializeModel(csn, _options, messageFunctions)
968
998
  }
969
999
 
970
1000
  function noNavPropMsg(elt) {
971
- warning(null, ['definitions', struct.name, 'elements', elt.name],
972
- { target: elt._target.name, service: globalSchemaPrefix }, 'No OData navigation property generated, target $(TARGET) is outside of service $(SERVICE)');
1001
+ warning('odata-navigation', ['definitions', struct.name, 'elements', elt.name],
1002
+ { target: elt._target.name, service: globalSchemaPrefix });
973
1003
  }
974
1004
 
975
- function createSchemaRefFor(assoc, targetSchemaName) {
1005
+ function createSchemaRefFor(targetSchemaName) {
976
1006
  let ref = csn.definitions[globalSchemaPrefix + '.' + targetSchemaName];
977
1007
  if(!ref) {
978
1008
  ref = createSchemaRef(targetSchemaName);
@@ -987,11 +1017,23 @@ function initializeModel(csn, _options, messageFunctions)
987
1017
  // if it is required in multiple services. The service schema name is prepended upon registration
988
1018
  const proxySchemaName = targetSchemaName || getSchemaPrefix(assoc._target.name);
989
1019
 
990
- // 1) construct the proxy definition
991
- // proxyShortName: strip the serviceName and replace '.' with '_'
992
- const proxyShortName = assoc._target.name.replace(proxySchemaName + '.', '').replace(/\./g, '_');
1020
+ // if the target is a parameter entity, it's easy just create the parameter stub
1021
+ const isParamProxy = isParameterizedEntity(assoc._target);
1022
+
1023
+ // 1) construct the proxy definition
1024
+ // proxyDefinitionName: strip the serviceName and replace '.' with '_'
1025
+ let defName =
1026
+ `${assoc._target.name.replace(proxySchemaName + '.', '').replace(/\./g, '_')}`;
1027
+
993
1028
  // fullName: Prepend serviceName and if in same service add '_proxy'
994
- const proxy = { name: proxySchemaName + '.' + proxyShortName, kind: 'entity', $proxy: true, elements: Object.create(null) };
1029
+ const proxy = isParamProxy
1030
+ ? createParameterEntity(assoc._target, proxySchemaName + '.' + defName, true)
1031
+ : { name: proxySchemaName + '.' + defName, kind: 'entity', elements: Object.create(null) };
1032
+
1033
+ // Final proxyShortName for all further processing
1034
+ const proxyShortName = defName + (isParamProxy ? 'Parameters' : '');
1035
+
1036
+ setProp(proxy, '$proxy', true);
995
1037
  setProp(proxy, '$mySchemaName', proxySchemaName);
996
1038
  setProp(proxy, '$proxyShortName', proxyShortName);
997
1039
  setProp(proxy, '$keys', Object.create(null));
@@ -1004,53 +1046,66 @@ function initializeModel(csn, _options, messageFunctions)
1004
1046
  });
1005
1047
 
1006
1048
  // 2) create the elements and $keys
1007
- populateProxyElements(assoc, proxy, assoc._target.$keys);
1049
+ if(isParamProxy) {
1050
+ // Reset param proxy elements to expose element tree
1051
+ const elements = proxy.elements;
1052
+ proxy.elements = Object.create(null);
1053
+ populateProxyElements(assoc, proxy, elements);
1054
+ }
1055
+ else
1056
+ populateProxyElements(assoc, proxy, assoc._target.$keys);
1008
1057
  return proxy;
1009
1058
 
1010
1059
  }
1011
1060
 
1012
- // return array of foreign key defs in the target
1013
- function getForeignKeyDefinitions(e, path=undefined) {
1014
- // populate foreign keys, if a key can't be resolved -> parametertype redirects, ignore them
1015
- return e.keys ? e.keys.map((_fk, i) => {
1016
- return inspectRef([...(path || e.$path), 'keys', i]).art;
1017
- }).filter(fk=>fk) : [];
1061
+ // Return top level foreign key element definitions. The full top level
1062
+ // element is exposed instead of merging partial trees into the exposed type
1063
+ // def structure.
1064
+ function getForeignKeyDefinitions(e) {
1065
+ return e.keys ? e.keys.map(fk => e._target.elements[fk.ref[0]]) : [];
1018
1066
  }
1019
- // copy over the primary keys of the target and trigger the type exposure
1067
+
1068
+ // copy over the primary keys of the target and trigger the type exposure
1069
+ // if the element already exists we assume it was fully exposed
1020
1070
  function populateProxyElements(assoc, proxy, elements) {
1021
1071
  forAll(elements, e => {
1022
- if (isEdmPropertyRendered(e, options) && !proxy.elements[e.name]) {
1023
- let newElt = undefined;
1024
- if(isAssocOrComposition(e.type)) {
1025
- if(!e.on && e.keys) {
1026
- if(options.toOdata.odataNoTransitiveProxies)
1027
- newElt = convertManagedAssocIntoStruct(e);
1028
- else
1072
+ if (isEdmPropertyRendered(e, options)) {
1073
+ let newElt = proxy.elements[e.name];
1074
+ if(!newElt) {
1075
+ if(csnUtils.isAssocOrComposition(e.type)) {
1076
+ if(!e.on && e.keys) {
1077
+ if(options.toOdata.odataNoTransitiveProxies)
1078
+ newElt = convertManagedAssocIntoStruct(e);
1079
+ else
1029
1080
  newElt = createProxyOrSchemaRefForManagedAssoc(e);
1030
- }
1031
- else {
1032
- info(null, ['definitions', struct.name, 'elements', assoc.name],
1081
+ }
1082
+ else {
1083
+ info(null, ['definitions', struct.name, 'elements', assoc.name],
1033
1084
  { name: proxy.nname, target: assoc._target.name },
1034
1085
  'Unmanaged associations are not supported as primary keys for proxy entity type $(NAME) of unexposed association target $(TARGET)');
1086
+ }
1035
1087
  }
1036
- }
1037
- else {
1038
- newElt = cloneCsn(e, options);
1039
- }
1040
- if(newElt) {
1041
- initElement(newElt, e.name, proxy);
1042
- if(isStructured(newElt)) {
1088
+ else {
1089
+ newElt = Object.create(null);
1090
+ Object.keys(e).forEach(prop => newElt[prop] = e[prop])
1091
+ }
1092
+ if(newElt) {
1093
+ initElement(newElt, e.name, proxy);
1094
+ proxy.elements[newElt.name] = newElt;
1095
+
1096
+ if(csnUtils.isStructured(newElt)) {
1043
1097
  // argument proxySchemaName forces an anonymous type definition for newElt into the
1044
- // proxy schema. If omitted, this exposure defaults to 'root', in case API flavor of the day
1045
- // changes...
1046
- exposeStructTypeForProxyOf(proxy, newElt, proxy.$proxyShortName + '_' + newElt.name, proxy.$mySchemaName);
1047
- // elements of newElt are required for key ref paths
1098
+ // proxy schema. If omitted, this exposure defaults to 'root', in case API flavor
1099
+ // of the day changes...
1100
+ exposeStructTypeForProxyOf(proxy, newElt, proxy.$proxyShortName + '_' + newElt.name,
1101
+ proxy.$mySchemaName, newElt.key, !!(newElt.key && newElt.elements));
1102
+ }
1103
+ if(newElt.key)
1104
+ proxy.$keys[newElt.name] = newElt;
1048
1105
  }
1049
- proxy.elements[newElt.name] = newElt;
1050
- if(newElt.key)
1051
- proxy.$keys[newElt.name] = newElt;
1052
1106
  }
1053
1107
  }
1108
+
1054
1109
  });
1055
1110
  // 3) sort the exposed types so that they appear lexicographically ordered in the EDM
1056
1111
  proxy.$exposedTypes = Object.keys(proxy.$exposedTypes).sort().reduce((dict, tn) => {
@@ -1062,78 +1117,90 @@ function initializeModel(csn, _options, messageFunctions)
1062
1117
  // anonymous or has a definition outside of 'service'), create an equivalent type in 'service', either
1063
1118
  // using the type's name or (if anonymous) 'artificialName', and make 'node' use that type instead.
1064
1119
  // Complain if there is an error.
1065
- function exposeStructTypeForProxyOf(proxy, node, artificialName, typeSchemaName=autoexposeSchemaName) {
1066
- const isNotInProtNS = node.type ? !isBuiltinType(node.type) : true;
1120
+ // isKey: Indicates top level element is key or not
1121
+ // forceToNotNull: if top level element is key, recursively set all anonymously exposed elements
1122
+ // to notNull until the first named type is exposed.
1123
+ function exposeStructTypeForProxyOf(proxy, node, artificialName,
1124
+ typeSchemaName=fallBackSchemaName,
1125
+ isKey, forceToNotNull) {
1126
+
1127
+ if(node.type && isBuiltinType(node.type))
1128
+ return;
1129
+
1067
1130
  // Always expose types referred to by a proxy, never reuse an eventually existing type
1068
1131
  // as the nested elements must all be not nullable
1069
- if (isNotInProtNS) {
1070
- let typeDef = node.type ? csn.definitions[node.type] : /* anonymous type */ node;
1132
+ // elements have precedence over type
1133
+ const typeDef = !node.elements && node.type ? csn.definitions[node.type] : node;
1071
1134
 
1072
- if (typeDef) {
1073
- let typeClone;
1135
+ if (typeDef) {
1136
+ let typeClone;
1074
1137
  // the type clone must be produced for each service as this type may
1075
1138
  // produce references and/or proxies into multiple services
1076
1139
  // (but only once per service, therefore cache it).
1077
- if(typeDef.$proxyTypes && typeDef.$proxyTypes[globalSchemaPrefix]) {
1140
+ if(typeDef.$proxyTypes && typeDef.$proxyTypes[globalSchemaPrefix]) {
1078
1141
  // if type has been exposed in a schema use this type
1079
- typeClone = typeDef.$proxyTypes[globalSchemaPrefix];
1080
- }
1081
- else {
1142
+ typeClone = typeDef.$proxyTypes[globalSchemaPrefix];
1143
+ }
1144
+ else {
1082
1145
  // Set the correct name
1083
- let typeId = artificialName; // the artificialName has no namespace, it's the element
1084
- if(node.type) {
1146
+ let typeId = artificialName; // the artificialName has no namespace, it's the element
1147
+ if(node.type) {
1085
1148
  // same as for proxies, use schema or namespace, 'root' is last resort
1086
- typeSchemaName = typeDef.$mySchemaName || getSchemaPrefix(node.type);
1087
- typeId = node.type.replace(typeSchemaName + '.', '').replace(/\./g, '_');
1149
+ typeSchemaName = typeDef.$mySchemaName || getSchemaPrefix(node.type);
1150
+ typeId = node.type.replace(typeSchemaName + '.', '').replace(/\./g, '_');
1088
1151
  // strip the service root of that type (if any)
1089
- const myServiceRootName = whatsMyServiceRootName(typeSchemaName);
1090
- if(myServiceRootName)
1091
- typeSchemaName = typeSchemaName.replace(myServiceRootName + '.', '');
1092
- }
1152
+ const myServiceRootName = whatsMyServiceRootName(typeSchemaName);
1153
+ if(myServiceRootName)
1154
+ typeSchemaName = typeSchemaName.replace(myServiceRootName + '.', '');
1155
+ }
1156
+
1157
+ if(isStructuredArtifact(typeDef)) {
1158
+ // pull forceNotNull to false for named types and non-key nodes
1159
+ // only toplevel nodes (elements) can be key
1160
+ forceToNotNull = !!(forceToNotNull && isKey && node.elements && !node.type);
1093
1161
 
1094
- if(isStructuredArtifact(typeDef)) {
1095
- typeClone = cloneStructTypeForProxy(typeSchemaName, `${typeSchemaName}.${typeId}`, typeDef);
1096
- if(typeClone) {
1162
+ typeClone = cloneStructTypeForProxy(typeSchemaName, `${typeSchemaName}.${typeId}`, typeDef);
1163
+ if(typeClone) {
1097
1164
  // Recurse into elements of 'type' (if any)
1098
- typeClone.elements && Object.entries(typeClone.elements).forEach( ([elemName, elem]) => {
1165
+ typeClone.elements && Object.entries(typeClone.elements).forEach(([elemName, elem]) => {
1099
1166
  // if this is a foreign key elment, we must check whether or not the association
1100
1167
  // has been exposed as proxy. If it has not been exposed, no further structured
1101
1168
  // types must be exposed as 'Proxy_' types.
1102
1169
 
1103
1170
  // TODO: expose types of assoc.keys and don't rely on exposed foreign keys
1104
- if(!elem['@odata.foreignKey4'] ||
1171
+ if(!elem['@odata.foreignKey4'] ||
1105
1172
  (elem['@odata.foreignKey4'] && !typeClone.elements[elem['@odata.foreignKey4']].$exposed))
1106
- exposeStructTypeForProxyOf(proxy, elem, `${typeId}_${elemName}`, typeSchemaName);
1107
- });
1108
- if(!typeDef.$proxyTypes)
1109
- typeDef.$proxyTypes = Object.create(null);
1110
- typeDef.$proxyTypes[globalSchemaPrefix] = typeClone;
1111
- }
1173
+ exposeStructTypeForProxyOf(proxy, elem, `${typeId}_${elemName}`,
1174
+ typeSchemaName, isKey, forceToNotNull);
1175
+ });
1176
+ if(!typeDef.$proxyTypes)
1177
+ typeDef.$proxyTypes = Object.create(null);
1178
+ typeDef.$proxyTypes[globalSchemaPrefix] = typeClone;
1112
1179
  }
1113
- else {
1180
+ }
1181
+ else {
1114
1182
  // FUTURE: expose scalar type definition as well
1115
- }
1116
1183
  }
1117
- if(typeClone) {
1184
+ }
1185
+ if(typeClone) {
1118
1186
  // register the type clone at the proxy
1119
1187
  // Reminder: Each proxy receives a full set of type clones, even if the types are shared
1120
1188
  // (no scattered type clone caching). registerProxy() checks if a clone needs to be added to
1121
1189
  // csn.definitions.
1122
- proxy.$exposedTypes[typeClone.name] = typeClone;
1190
+ proxy.$exposedTypes[typeClone.name] = typeClone;
1123
1191
 
1124
1192
  // set the node's new type name
1125
- node.type = typeClone.name;
1193
+ node.type = typeClone.name;
1126
1194
  // the key path generator must use the type clone directly, because it can't resolve
1127
1195
  // the type clone in the CSN (its name is the final name and not the definition name).
1128
- setProp(node, '_type', typeClone);
1196
+ setProp(node, '_type', typeClone);
1129
1197
  // Hack alert:
1130
1198
  // beta feature 'subElemRedirections' (now the default in v2) adds elements to the node by
1131
1199
  // default, without we must do it to get the primary key tuple calculation correct.
1132
1200
  // Remember: node.type is the service local type name (not prepended by the service name),
1133
1201
  // so it can't be resolved in definitions later on
1134
- if(typeClone.elements)
1135
- node.elements = typeClone.elements;
1136
- }
1202
+ if(typeClone.elements)
1203
+ node.elements = typeClone.elements;
1137
1204
  }
1138
1205
  }
1139
1206
 
@@ -1151,12 +1218,13 @@ function initializeModel(csn, _options, messageFunctions)
1151
1218
  if(!elem.target) {
1152
1219
  type.elements[elemName] = Object.create(null);
1153
1220
  Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
1154
- type.elements[elemName].notNull = true;
1155
1221
  }
1156
1222
  else if(elem.keys && !elem.on) {
1157
1223
  // a primary key can never be an unmanaged association
1158
1224
  type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
1159
1225
  }
1226
+ if(forceToNotNull)
1227
+ type.elements[elemName].notNull = true;
1160
1228
  setProp(type.elements[elemName], 'name', elem.name);
1161
1229
  });
1162
1230
  return type;
@@ -1166,7 +1234,7 @@ function initializeModel(csn, _options, messageFunctions)
1166
1234
  // Convert a managed association into a structured type and
1167
1235
  // eliminate nested foreign key associations
1168
1236
  function convertManagedAssocIntoStruct(e) {
1169
- let newElt = cloneCsn(e, options);
1237
+ let newElt = cloneCsnNonDict(e, options);
1170
1238
  newElt.elements = Object.create(null);
1171
1239
  // remove all unwanted garbage
1172
1240
  delete newElt.keys;
@@ -1177,14 +1245,15 @@ function initializeModel(csn, _options, messageFunctions)
1177
1245
  let keys = (!e._target.$isParamEntity && e.keys) ||
1178
1246
  Object.keys(e._target.$keys).map(k => { return { ref: [k] } });
1179
1247
  keys.forEach(k => {
1180
- let art = e._target || getCsnDef(e.target);
1248
+ let art = e._target || csnUtils.getCsnDef(e.target);
1181
1249
  for(let ps of k.ref) {
1182
1250
  art = art.elements[ps];
1183
1251
  }
1184
1252
  // art is in the target side, clone it and remove key property
1185
- let cloneArt = cloneCsn(art, options);
1253
+ let cloneArt = cloneCsnNonDict(art, options);
1186
1254
  setProp(cloneArt, 'name', art.name);
1187
- cloneArt.notNull = true;
1255
+ if(e.key)
1256
+ cloneArt.notNull = true;
1188
1257
  delete cloneArt.key;
1189
1258
  newElt.elements[art.name] = cloneArt;
1190
1259
  });
@@ -1200,18 +1269,19 @@ function initializeModel(csn, _options, messageFunctions)
1200
1269
  function createProxyOrSchemaRefForManagedAssoc(e) {
1201
1270
 
1202
1271
  let proxy = e._target;
1203
- let newElt = cloneCsn(e, options);
1272
+ let newElt = cloneCsnNonDict(e, options);
1204
1273
 
1205
1274
  if(isProxyRequired(e)) {
1206
1275
  proxy = getProxyForTargetOf(e);
1207
1276
  if(!proxy) {
1208
1277
  // option odataXServiceRefs has precedence over odataProxies
1209
1278
  if(e._target.$mySchemaName && options.toOdata.odataXServiceRefs) {
1210
- proxy = createSchemaRefFor(e, e._target.$mySchemaName);
1279
+ proxy = createSchemaRefFor(e._target.$mySchemaName);
1211
1280
  }
1212
1281
  else if(options.toOdata.odataProxies) {
1213
1282
  proxy = createProxyFor(e, e._target.$mySchemaName);
1214
- populateProxyElements(e, proxy, getForeignKeyDefinitions(e));
1283
+ if(!e._target.$isParamEntity)
1284
+ populateProxyElements(e, proxy, getForeignKeyDefinitions(e));
1215
1285
  }
1216
1286
  proxy = registerProxy(proxy, e);
1217
1287
  }
@@ -1227,7 +1297,10 @@ function initializeModel(csn, _options, messageFunctions)
1227
1297
  setProp(newElt, '$exposed', true);
1228
1298
  // _target must be set with (original) in case
1229
1299
  // a schema ref has been created
1300
+ setProp(newElt, '$noPartner', true);
1230
1301
  setProp(newElt, '_target', e._target);
1302
+ initConstraintsOnAssoc(e);
1303
+ finalizeConstraintsOnAssoc(e);
1231
1304
  setProp(newElt, '_constraints', e._constraints);
1232
1305
  setProp(newElt, '_selfReferences', []);
1233
1306
  if(proxy.kind === 'entity') {
@@ -1322,11 +1395,13 @@ function initializeModel(csn, _options, messageFunctions)
1322
1395
  const alreadyRegistered = csn.definitions[fqProxyName];
1323
1396
  if(!alreadyRegistered) {
1324
1397
  csn.definitions[fqProxyName] = proxy;
1398
+ reqDefs.definitions[fqProxyName] = proxy;
1325
1399
  setProp(proxy, '$path', ['definitions', fqProxyName]);
1326
1400
  Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
1327
1401
  const fqtn = proxy.$globalSchemaPrefix + '.' + tn;
1328
1402
  if(csn.definitions[fqtn] === undefined) {
1329
1403
  csn.definitions[fqtn] = v;
1404
+ reqDefs.definitions[fqtn] = v;
1330
1405
  setProp(v, '$path', ['definitions', fqtn]);
1331
1406
  }
1332
1407
  });
@@ -1372,28 +1447,25 @@ function initializeModel(csn, _options, messageFunctions)
1372
1447
  and do not render associations (this will include the foreign keys of
1373
1448
  the _isToContainer association).
1374
1449
  */
1375
- function initializeEdmKeyRefPaths(struct) {
1376
- if(struct.$mySchemaName && struct.$keys) {
1377
- setProp(struct, '$edmKeyPaths', []);
1450
+ function initEdmKeyRefPaths(def) {
1451
+ if(def.$keys) {
1452
+ setProp(def, '$edmKeyPaths', []);
1378
1453
  // for all key elements that shouldn't be ignored produce the paths
1379
- foreach(struct.$keys, k => !k._ignore && !(k._isToContainer && k._selfReferences.length), (k, kn) => {
1454
+ foreach(def.$keys, k => !k._ignore && !(k._isToContainer && k._selfReferences.length), (k, kn) => {
1380
1455
  if(isEdmPropertyRendered(k, options) &&
1381
1456
  !(options.isV2() && k['@Core.MediaType'])) {
1382
1457
  if(options.isV4() && options.isStructFormat) {
1383
- // This is structured OData ONLY
1384
- // if the foreign keys are explicitly requested, ignore associations and use the flat foreign keys instead
1385
- if(options.renderForeignKeys && !k.target)
1386
- struct.$edmKeyPaths.push([kn]);
1387
- // else produce paths (isEdmPropertyRendered() has filtered @odata.foreignKey4 already)
1388
- else if(!options.renderForeignKeys)
1389
- struct.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
1458
+ // This is structured OData ONLY
1459
+ // if the foreign keys are explicitly requested, ignore associations and use the flat foreign keys instead
1460
+ if(!options.renderForeignKeys || (options.renderForeignKeys && !k.target))
1461
+ def.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
1390
1462
  }
1391
1463
  // In v2/v4 flat, associations are never rendered
1392
1464
  else if(!k.target) {
1393
- struct.$edmKeyPaths.push([kn]);
1465
+ def.$edmKeyPaths.push([kn]);
1394
1466
  }
1395
1467
  // check toplevel key for spec violations
1396
- checkKeySpecViolations(k, ['definitions', struct.name, 'elements', k.name]);
1468
+ checkKeySpecViolations(k, ['definitions', def.name, 'elements', k.name]);
1397
1469
  }
1398
1470
  });
1399
1471
  }
@@ -1410,6 +1482,8 @@ function initializeModel(csn, _options, messageFunctions)
1410
1482
  */
1411
1483
  function produceKeyRefPaths(eltCsn, prefix) {
1412
1484
  const keyPaths = [];
1485
+ // we want to point to the element in the entity which is the first path step
1486
+ const location = def.$path.concat(['elements']).concat(prefix.split('/')[0]);
1413
1487
  if(!isEdmPropertyRendered(eltCsn, options)) {
1414
1488
  // let annos = Object.keys(eltCsn).filter(a=>a[0]==='@').join(', ');
1415
1489
  // warning(null, ['definitions', struct.name, 'elements', eltCsn.name ],
@@ -1418,7 +1492,7 @@ function initializeModel(csn, _options, messageFunctions)
1418
1492
  }
1419
1493
  // OData requires all elements along the path to be nullable: false (that is either key or notNull)
1420
1494
 
1421
- const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
1495
+ const finalType = csnUtils.getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
1422
1496
  const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
1423
1497
  (finalType && (finalType.elements || finalType.items && finalType.items.elements));
1424
1498
  if(elements) {
@@ -1428,8 +1502,6 @@ function initializeModel(csn, _options, messageFunctions)
1428
1502
  keyPaths.push(...newRefs);
1429
1503
  // check path step key for spec violations
1430
1504
  const pathSegment = `${prefix}/${eltName}`;
1431
- // we want to point to the element in the entity which is the first path step
1432
- const location = struct.$path.concat(['elements']).concat(pathSegment.split('/')[0]);
1433
1505
  checkKeySpecViolations(elt, location, pathSegment);
1434
1506
  }
1435
1507
  });
@@ -1444,12 +1516,15 @@ function initializeModel(csn, _options, messageFunctions)
1444
1516
  // use the primary keys of the target
1445
1517
  let keys = (!eltCsn._target.$isParamEntity && eltCsn.keys) ||
1446
1518
  Object.keys(eltCsn._target.$keys).map(k => { return { ref: [k] } });
1519
+ let pathSegment = prefix
1447
1520
  keys.forEach(k => {
1448
- let art = eltCsn._target || getCsnDef(eltCsn.target);
1521
+ let art = eltCsn._target || csnUtils.getCsnDef(eltCsn.target);
1449
1522
  for(let ps of k.ref) {
1450
1523
  art = art.elements[ps];
1524
+ pathSegment += '/' + art.name
1525
+ checkKeySpecViolations(art, location, pathSegment);
1451
1526
  if(art.type && !isBuiltinType(art.type)) {
1452
- art = art._type || getCsnDef(art.type);
1527
+ art = art._type || csnUtils.getCsnDef(art.type);
1453
1528
  }
1454
1529
  }
1455
1530
  keyPaths.push(...produceKeyRefPaths(art, prefix + options.pathDelimiter + k.ref.join(options.pathDelimiter)));
@@ -1465,13 +1540,13 @@ function initializeModel(csn, _options, messageFunctions)
1465
1540
  // Nullability
1466
1541
  if((!elt.key && (elt.notNull === undefined || elt.notNull === false)) ||
1467
1542
  elt.key && (elt.notNull !== undefined && elt.notNull === false)) {
1468
- error('odata-spec-violation-key-null', location,
1543
+ message('odata-spec-violation-key-null', location,
1469
1544
  {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1470
1545
  }
1471
1546
  // many
1472
- let type = elt.items || elt.type && !isBuiltinType(elt.type) && getFinalTypeDef(elt.type).items;
1547
+ let type = elt.items || elt.type && !isBuiltinType(elt.type) && csnUtils.getFinalTypeDef(elt.type).items;
1473
1548
  if(type) {
1474
- error('odata-spec-violation-key-array', location,
1549
+ message('odata-spec-violation-key-array', location,
1475
1550
  {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1476
1551
  }
1477
1552
  // type
@@ -1483,10 +1558,10 @@ function initializeModel(csn, _options, messageFunctions)
1483
1558
  // V2 allows any Edm.PrimitiveType (even Double and Binary), V4 is more specific:
1484
1559
  if(options.isV4() && type && !isAssociationOrComposition(type) && isBuiltinType(type.type)) {
1485
1560
  const edmType = edmUtils.mapCdsToEdmType(type);
1486
- const legalEdmTypes = [
1487
- 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Duration',
1488
- 'Edm.Guid', 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte', 'Edm.String', 'Edm.TimeOfDay' ];
1489
- if(!legalEdmTypes.includes(edmType)) {
1561
+ const legalEdmTypes = {
1562
+ 'Edm.Boolean':1, 'Edm.Byte':1, 'Edm.Date':1, 'Edm.DateTimeOffset':1, 'Edm.Decimal':1, 'Edm.Duration':1,
1563
+ 'Edm.Guid':1, 'Edm.Int16':1, 'Edm.Int32':1, 'Edm.Int64':1, 'Edm.SByte':1, 'Edm.String':1, 'Edm.TimeOfDay':1 };
1564
+ if(!(edmType in legalEdmTypes)) {
1490
1565
  warning('odata-spec-violation-key-type', location,
1491
1566
  {name: pathSegment, type: type.type, id: edmType, '#': pathSegment ? 'std' : 'scalar'});
1492
1567
  }
@@ -1522,10 +1597,10 @@ function initializeModel(csn, _options, messageFunctions)
1522
1597
  Path="items/subitems/subitems/up_" Target="Header/items/subitems"/>
1523
1598
  Path="items/subitems/subitems/toG" Target="G"/>
1524
1599
  */
1525
- function initializeEdmNavPropBindingTargets(struct) {
1526
- if(options.isV4() && struct.$mySchemaName && struct.$hasEntitySet) {
1527
- forEachGeneric(struct.items || struct, 'elements', (element) => {
1528
- produceTargetPath([edmUtils.getBaseName(struct.name)], element, struct);
1600
+ function initEdmNavPropBindingTargets(def) {
1601
+ if(def.$hasEntitySet) {
1602
+ forEachGeneric(def.items || def, 'elements', (element) => {
1603
+ produceTargetPath([edmUtils.getBaseName(def.name)], element, def);
1529
1604
  });
1530
1605
  }
1531
1606
 
@@ -1537,7 +1612,14 @@ function initializeModel(csn, _options, messageFunctions)
1537
1612
  if(isAssociationOrComposition(elt) && !elt.$touched) {
1538
1613
  if(!elt._target.$edmTgtPaths)
1539
1614
  setProp(elt._target, '$edmTgtPaths', []);
1540
- if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
1615
+ // drill into target only if
1616
+ // 1) target has no entity set and this assoc is not going to the container
1617
+ // 2) current definition and target are the same (cycle)
1618
+ // 3) it's no external reference
1619
+ if(!elt.$externalRef &&
1620
+ !elt._target.$hasEntitySet &&
1621
+ !elt._isToContainer &&
1622
+ curDef !== elt._target) {
1541
1623
  // follow elements in the target but avoid cycles
1542
1624
  setProp(elt, '$touched', true);
1543
1625
  elt._target.$edmTgtPaths.push(newPrefix);
@@ -1556,13 +1638,13 @@ function initializeModel(csn, _options, messageFunctions)
1556
1638
  }
1557
1639
  }
1558
1640
 
1559
- function initializeEdmNavPropBindingPaths(struct) {
1560
- if(options.isV4() && struct.$mySchemaName && struct.$hasEntitySet) {
1641
+ function initEdmNavPropBindingPaths(def) {
1642
+ if(options.isV4() &&def.$hasEntitySet) {
1561
1643
  let npbs = [];
1562
- forEachGeneric(struct.items || struct, 'elements', (element) => {
1563
- npbs = npbs.concat(produceNavigationPath(element, struct));
1644
+ forEachGeneric(def.items || def, 'elements', (element) => {
1645
+ npbs = npbs.concat(produceNavigationPath(element, def));
1564
1646
  });
1565
- setProp(struct, '$edmNPBs', npbs);
1647
+ setProp(def, '$edmNPBs', npbs);
1566
1648
  }
1567
1649
 
1568
1650
  // collect all paths originating from this element that end up in an entity set
@@ -1576,7 +1658,11 @@ function initializeModel(csn, _options, messageFunctions)
1576
1658
  // drill into target only if
1577
1659
  // 1) target has no entity set and this assoc is not going to the container
1578
1660
  // 2) current definition and target are the same (cycle)
1579
- if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
1661
+ // 3) it's no external reference
1662
+ if(!elt.$externalRef &&
1663
+ !elt._target.$hasEntitySet &&
1664
+ !elt._isToContainer &&
1665
+ curDef !== elt._target) {
1580
1666
  // follow elements in the target but avoid cycles
1581
1667
  setProp(elt, '$touched', true);
1582
1668
  Object.values(elt._target.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, elt._target)));
@@ -1586,16 +1672,18 @@ function initializeModel(csn, _options, messageFunctions)
1586
1672
  // end point reached but must not be an external reference nor a proxy nor a composition itself
1587
1673
  // last assoc step must not be to-n and target a singleton
1588
1674
  let p = undefined;
1589
- if (!elt._target.$externalRef &&
1590
- !(edmUtils.isToMany(elt) && edmUtils.isSingleton(elt._target) && options.isV4())) {
1675
+ if (!elt.$externalRef &&
1676
+ !(edmUtils.isToMany(elt) &&
1677
+ edmUtils.isSingleton(elt._target) &&
1678
+ options.isV4())) {
1591
1679
  if(elt._target.$edmTgtPaths && elt._target.$edmTgtPaths.length) {
1592
- p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(struct.name)) || elt._target.$edmTgtPaths[0];
1680
+ p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(def.name)) || elt._target.$edmTgtPaths[0];
1593
1681
  }
1594
1682
  else if(elt._target.$hasEntitySet) {
1595
1683
  const baseName = edmUtils.getBaseName(elt._target.$entitySetName || elt._target.name);
1596
1684
  // if own struct and target have a set they either are in the same $mySchemaName or not
1597
1685
  // if target is in another schema, target the full qualified entity set
1598
- p = (elt._target.$mySchemaName === struct.$mySchemaName) ?
1686
+ p = (elt._target.$mySchemaName === def.$mySchemaName) ?
1599
1687
  [ baseName ] : [elt._target.$mySchemaName + '.EntityContainer', baseName];
1600
1688
  }
1601
1689
  if(p) {
@@ -1625,28 +1713,28 @@ function initializeModel(csn, _options, messageFunctions)
1625
1713
  }
1626
1714
  }
1627
1715
 
1628
- function determineEntitySet(struct) {
1716
+ function determineEntitySet(def) {
1629
1717
  // if this is an entity or a view, determine if an entity set is required or not
1630
1718
  // 1) must not be a proxy and not a containee in V4
1631
1719
  // No annos are rendered for non-existing EntitySet targets.
1632
- if(struct.$mySchemaName && struct.$hasEntitySet === undefined) {
1633
- const hasEntitySet = isEntity(struct) && !(options.isV4() && edmUtils.isContainee(struct)) && !struct.$proxy;
1634
- setProp(struct, '$hasEntitySet', hasEntitySet);
1720
+ if(def.$hasEntitySet === undefined) {
1721
+ const hasEntitySet = isEntity(def) && !(options.isV4() && edmUtils.isContainee(def)) && !def.$proxy;
1722
+ setProp(def, '$hasEntitySet', hasEntitySet);
1635
1723
  }
1636
1724
  }
1637
1725
 
1638
- function initializeEdmTypesAndDescription(artifact) {
1726
+ function initEdmTypesAndDescription(def) {
1639
1727
  // 1. let all doc props become @Core.Descriptions
1640
1728
  // 2. mark a member that will become a collection
1641
1729
  // 3. assign the edm primitive type to elements, to be used in the rendering later
1642
- assignAnnotation(artifact, '@Core.Description', artifact.doc);
1643
- markCollection(artifact);
1644
- mapCdsToEdmProp(artifact);
1645
- if (artifact.returns) {
1646
- markCollection(artifact.returns);
1647
- mapCdsToEdmProp(artifact.returns);
1648
- }
1649
- forEachMemberRecursively(artifact,member => {
1730
+ assignAnnotation(def, '@Core.Description', def.doc);
1731
+ markCollection(def);
1732
+ mapCdsToEdmProp(def);
1733
+ if (def.returns) {
1734
+ markCollection(def.returns);
1735
+ mapCdsToEdmProp(def.returns);
1736
+ }
1737
+ forEachMemberRecursively(def,member => {
1650
1738
  assignAnnotation(member, '@Core.Description', member.doc);
1651
1739
  markCollection(member);
1652
1740
  mapCdsToEdmProp(member);
@@ -1656,7 +1744,6 @@ function initializeModel(csn, _options, messageFunctions)
1656
1744
  mapCdsToEdmProp(member.returns);
1657
1745
  }
1658
1746
  });
1659
-
1660
1747
  // mark members that need to be rendered as collections
1661
1748
  function markCollection(obj) {
1662
1749
  const items = obj.items || csn.definitions[obj.type] && csn.definitions[obj.type].items;
@@ -1673,19 +1760,37 @@ function initializeModel(csn, _options, messageFunctions)
1673
1760
  // Checks section starts here
1674
1761
  //
1675
1762
 
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
+ }
1776
+
1777
+
1676
1778
  function inboundQualificationChecks() {
1677
- forEachDefinition(csn, [ checkChainedArray ]);
1779
+ forEachDefinition(csn, [ attach$path, checkChainedArray ]);
1678
1780
  checkNestedContextsAndServices();
1679
- throwWithError();
1680
-
1681
- // NOTE: This is a copy of ./libs/checks/arrayOfs.js//checkChainedArray
1682
- // as long as validators cannot operate on OData processed CSN.
1683
- // TODO: Remove this code.
1684
- // Not possible at the moment, because running this at the beginning of
1685
- // the renderer does not work because the enricher can't handle certain
1686
- // OData specifics.
1781
+ throwWithAnyError();
1782
+
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
+ }
1791
+
1687
1792
  function checkChainedArray(def, defName) {
1688
- if (!whatsMyServiceRootName(defName))
1793
+ if (!isMyServiceRequested(defName))
1689
1794
  return;
1690
1795
  let currPath = ['definitions', defName];
1691
1796
  checkIfItemsOfItems(def, undefined, undefined, currPath);
@@ -1695,13 +1800,13 @@ function initializeModel(csn, _options, messageFunctions)
1695
1800
  const constructType = csnUtils.effectiveType(construct);
1696
1801
  if (constructType.items) {
1697
1802
  if (constructType.items.items) {
1698
- error('chained-array-of', path, '"Array of"/"many" must not be chained with another "array of"/"many" inside a service');
1803
+ message('chained-array-of', path);
1699
1804
  return;
1700
1805
  }
1701
1806
 
1702
1807
  const itemsType = csnUtils.effectiveType(constructType.items);
1703
1808
  if (itemsType.items)
1704
- error('chained-array-of', path, '"Array of"/"many" must not be chained with another "array of"/"many" inside a service');
1809
+ message('chained-array-of', path);
1705
1810
  }
1706
1811
  }
1707
1812
  }
@@ -1709,7 +1814,7 @@ function initializeModel(csn, _options, messageFunctions)
1709
1814
  function checkNestedContextsAndServices() {
1710
1815
  !isBetaEnabled(options, 'nestedServices') && serviceRootNames.forEach(sn => {
1711
1816
  const parent = whatsMyServiceRootName(sn, false);
1712
- if(parent && parent !== sn) {
1817
+ if(parent && requestedServiceNames.includes(parent) && parent !== sn) {
1713
1818
  message( 'service-nested-service', [ 'definitions', sn ], { art: parent },
1714
1819
  'A service can\'t be nested within a service $(ART)' );
1715
1820
  }
@@ -1718,7 +1823,7 @@ function initializeModel(csn, _options, messageFunctions)
1718
1823
  Object.entries(csn.definitions).forEach(([fqName, art]) => {
1719
1824
  if(art.kind === 'context') {
1720
1825
  const parent = whatsMyServiceRootName(fqName);
1721
- if(parent) {
1826
+ if(requestedServiceNames.includes(parent)) {
1722
1827
  message( 'service-nested-context', [ 'definitions', fqName ], { art: parent },
1723
1828
  'A context can\'t be nested within a service $(ART)' );
1724
1829
  }
@@ -1727,54 +1832,6 @@ function initializeModel(csn, _options, messageFunctions)
1727
1832
  }
1728
1833
  }
1729
1834
 
1730
- /**
1731
- *
1732
- * @param {String} identifier the illegal identifier
1733
- * @param {CSN.Path} path
1734
- */
1735
- function signalIllegalIdentifier(identifier, path) {
1736
- error(null, path, { id: identifier },
1737
- 'OData identifier $(ID) must start with a letter or underscore, followed by at most 127 letters, underscores or digits'
1738
- );
1739
- }
1740
-
1741
-
1742
- // { '#': this.csnUtils.isComposition(member.type) ? 'cmp' : 'std' },
1743
- // {
1744
- // std: 'An association can\'t have cardinality "to many" without an ON-condition',
1745
- // cmp: 'A composition can\'t have cardinality "to many" without an ON-condition',
1746
- // }
1747
-
1748
- // Check the artifact identifier for compliance with the odata specification
1749
- function checkArtifactIdentifierAndBoundActions(artifact) {
1750
- if(artifact.$mySchemaName) {
1751
- const artifactName = artifact.name.replace(`${artifact.$mySchemaName }.`, '');
1752
- // if the artifact has bound actions, check the action identifiers and their param identifiers to be OData compliant
1753
- if(artifact.actions) {
1754
- Object.keys(artifact.actions).forEach(identifier => checkActionOrFunctionIdentifier(artifact.actions[identifier], identifier))
1755
- }
1756
-
1757
- // if the artifact is an unbound function check it's identifer
1758
- if(isActionOrFunction(artifact)){
1759
- checkActionOrFunctionIdentifier(artifact, artifactName);
1760
- } else if(![ 'service', 'context', 'event', 'aspect' ].includes(artifact.kind) && !isODataSimpleIdentifier(artifactName)) {
1761
- signalIllegalIdentifier(artifactName, ['definitions', artifact.name]);
1762
- }
1763
- }
1764
-
1765
- function checkActionOrFunctionIdentifier(actionOrFunction, actionOrFunctionName) {
1766
- if(!isODataSimpleIdentifier(actionOrFunctionName)){
1767
- signalIllegalIdentifier(actionOrFunctionName, actionOrFunction.$path);
1768
- }
1769
- if(actionOrFunction.params) {
1770
- forEachGeneric(actionOrFunction, 'params', (param) => {
1771
- if(!isODataSimpleIdentifier(param.name)){
1772
- signalIllegalIdentifier(param.name, param.$path);
1773
- }
1774
- });
1775
- }
1776
- }
1777
- }
1778
1835
  //
1779
1836
  // Checks Secition ends here
1780
1837
  //
@@ -1863,7 +1920,29 @@ function initializeModel(csn, _options, messageFunctions)
1863
1920
  }, { });
1864
1921
  // if dictionary has entries, add them to navPropEnty
1865
1922
  if(Object.keys(o).length) {
1866
- navPropEntry[prop] = o;
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
+ }
1867
1946
  newEntry = true;
1868
1947
  }
1869
1948
  }
@@ -1887,19 +1966,15 @@ function initializeModel(csn, _options, messageFunctions)
1887
1966
  if (obj.type && isBuiltinType(obj.type) && !isAssociationOrComposition(obj) && !obj.targetAspect) {
1888
1967
  let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
1889
1968
  assignProp(obj, '_edmType', edmType);
1890
- } else if (obj._isCollection && (obj.items && isBuiltinType(getFinalTypeDef(obj.items.type)))) {
1891
- let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
1969
+ } 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);
1892
1971
  assignProp(obj, '_edmType', edmType);
1893
1972
  }
1894
1973
  // This is the special case when we have array of array, but will not be supported in the future
1895
- else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(getFinalTypeDef(obj.items.items.type))) {
1974
+ else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.items.type))) {
1896
1975
  let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
1897
1976
  assignProp(obj, '_edmType', edmType);
1898
1977
  }
1899
-
1900
- // check against the value of the @odata.Type annotation
1901
- if (obj['@odata.Type'] && !['Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.String'].includes(obj['@odata.Type']))
1902
- info(null, obj.$location, { type: obj['@odata.Type'] }, "@odata.Type: $(TYPE) is ignored, only Edm.String and Edm.Int[16,32,64] are allowed");
1903
1978
  }
1904
1979
 
1905
1980
  function ComputedDefaultValue(member) {
@@ -2286,4 +2361,5 @@ function assignProp(obj, prop, value) {
2286
2361
 
2287
2362
  module.exports = {
2288
2363
  initializeModel,
2364
+ assignAnnotation
2289
2365
  }