@sap/cds-compiler 2.13.8 → 3.0.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 +155 -1594
  2. package/bin/cdsc.js +144 -66
  3. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  4. package/doc/CHANGELOG_BETA.md +3 -4
  5. package/doc/CHANGELOG_DEPRECATED.md +35 -1
  6. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  7. package/doc/Versioning.md +20 -1
  8. package/lib/api/.eslintrc.json +2 -2
  9. package/lib/api/main.js +237 -122
  10. package/lib/api/options.js +17 -88
  11. package/lib/api/validate.js +12 -16
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +152 -37
  14. package/lib/base/messages.js +145 -83
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +19 -0
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +11 -32
  19. package/lib/checks/arrayOfs.js +1 -34
  20. package/lib/checks/cdsPersistence.js +1 -0
  21. package/lib/checks/elements.js +6 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/queryNoDbArtifacts.js +2 -1
  25. package/lib/checks/selectItems.js +5 -1
  26. package/lib/checks/types.js +4 -2
  27. package/lib/checks/utils.js +2 -2
  28. package/lib/checks/validator.js +4 -5
  29. package/lib/compiler/assert-consistency.js +16 -10
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +98 -9
  32. package/lib/compiler/checks.js +22 -70
  33. package/lib/compiler/define.js +61 -13
  34. package/lib/compiler/extend.js +79 -14
  35. package/lib/compiler/finalize-parse-cdl.js +46 -29
  36. package/lib/compiler/index.js +100 -37
  37. package/lib/compiler/moduleLayers.js +7 -0
  38. package/lib/compiler/populate.js +19 -18
  39. package/lib/compiler/propagator.js +7 -4
  40. package/lib/compiler/resolve.js +297 -234
  41. package/lib/compiler/shared.js +107 -102
  42. package/lib/compiler/tweak-assocs.js +16 -11
  43. package/lib/compiler/utils.js +5 -0
  44. package/lib/edm/annotations/genericTranslation.js +93 -21
  45. package/lib/edm/csn2edm.js +230 -115
  46. package/lib/edm/edm.js +305 -226
  47. package/lib/edm/edmPreprocessor.js +509 -438
  48. package/lib/edm/edmUtils.js +31 -45
  49. package/lib/gen/Dictionary.json +98 -22
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +10 -30
  52. package/lib/gen/language.tokens +105 -114
  53. package/lib/gen/languageLexer.interp +1 -34
  54. package/lib/gen/languageLexer.js +889 -1007
  55. package/lib/gen/languageLexer.tokens +95 -106
  56. package/lib/gen/languageParser.js +20786 -22199
  57. package/lib/json/csnVersion.js +10 -11
  58. package/lib/json/from-csn.js +59 -51
  59. package/lib/json/to-csn.js +10 -10
  60. package/lib/language/antlrParser.js +2 -2
  61. package/lib/language/docCommentParser.js +62 -39
  62. package/lib/language/errorStrategy.js +52 -40
  63. package/lib/language/genericAntlrParser.js +348 -229
  64. package/lib/language/language.g4 +629 -653
  65. package/lib/language/multiLineStringParser.js +14 -42
  66. package/lib/language/textUtils.js +44 -0
  67. package/lib/main.d.ts +46 -43
  68. package/lib/main.js +108 -79
  69. package/lib/model/csnRefs.js +34 -7
  70. package/lib/model/csnUtils.js +337 -332
  71. package/lib/model/enrichCsn.js +1 -0
  72. package/lib/model/revealInternalProperties.js +30 -10
  73. package/lib/model/sortViews.js +32 -31
  74. package/lib/modelCompare/compare.js +6 -6
  75. package/lib/optionProcessor.js +73 -46
  76. package/lib/render/.eslintrc.json +1 -1
  77. package/lib/render/DuplicateChecker.js +4 -7
  78. package/lib/render/manageConstraints.js +70 -2
  79. package/lib/render/toCdl.js +1042 -882
  80. package/lib/render/toHdbcds.js +195 -245
  81. package/lib/render/toRename.js +44 -22
  82. package/lib/render/toSql.js +225 -241
  83. package/lib/render/utils/common.js +145 -15
  84. package/lib/render/utils/sql.js +20 -19
  85. package/lib/sql-identifier.js +6 -0
  86. package/lib/transform/db/.eslintrc.json +4 -3
  87. package/lib/transform/db/associations.js +2 -2
  88. package/lib/transform/db/cdsPersistence.js +5 -15
  89. package/lib/transform/db/constraints.js +4 -2
  90. package/lib/transform/db/expansion.js +22 -16
  91. package/lib/transform/db/flattening.js +109 -80
  92. package/lib/transform/db/transformExists.js +7 -7
  93. package/lib/transform/db/views.js +9 -6
  94. package/lib/transform/draft/.eslintrc.json +2 -2
  95. package/lib/transform/draft/db.js +6 -6
  96. package/lib/transform/draft/odata.js +6 -7
  97. package/lib/transform/forHanaNew.js +62 -48
  98. package/lib/transform/forOdataNew.js +49 -50
  99. package/lib/transform/localized.js +31 -20
  100. package/lib/transform/odata/toFinalBaseType.js +16 -14
  101. package/lib/transform/odata/typesExposure.js +146 -198
  102. package/lib/transform/odata/utils.js +1 -38
  103. package/lib/transform/transformUtilsNew.js +67 -84
  104. package/lib/transform/translateAssocsToJoins.js +7 -3
  105. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  106. package/lib/transform/universalCsn/coreComputed.js +16 -9
  107. package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
  108. package/lib/utils/file.js +3 -3
  109. package/lib/utils/moduleResolve.js +13 -6
  110. package/lib/utils/timetrace.js +20 -21
  111. package/package.json +35 -4
  112. package/share/messages/message-explanations.json +2 -1
  113. package/share/messages/syntax-expected-integer.md +37 -0
  114. package/doc/ApiMigration.md +0 -237
  115. package/doc/CommandLineMigration.md +0 -58
  116. package/doc/ErrorMessages.md +0 -175
  117. package/doc/FioriAnnotations.md +0 -94
  118. package/doc/ODataTransformation.md +0 -273
  119. package/lib/backends.js +0 -529
  120. package/lib/fix_antlr4-8_warning.js +0 -56
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -296
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. 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(serviceRootNames.length === 0) {
58
+ return [serviceRoots, Object.create(null), reqDefs, whatsMyServiceRootName, fallBackSchemaName, options];
74
59
  }
75
60
 
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++;
61
+ if(requestedServiceNames === undefined)
62
+ requestedServiceNames = options.serviceNames;
63
+ if(requestedServiceNames === undefined) {
64
+ requestedServiceNames = serviceRootNames;
85
65
  }
86
66
 
87
- if(serviceRootNames.length === 0) {
88
- return [serviceRoots, Object.create(null), whatsMyServiceRootName, autoexposeSchemaName, options];
67
+ function isMyServiceRequested(n) {
68
+ return requestedServiceNames.includes(whatsMyServiceRootName(n));
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 });
124
-
125
- // First attach names to all definitions (and actions/params) in the model
126
- // elements are done in initializeStruct
127
- forEachDefinition(csn, attachNameProperty);
109
+ const schemas = typesExposure(csn, whatsMyServiceRootName, requestedServiceNames,
110
+ fallBackSchemaName, options, csnUtils, { error });
128
111
 
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,94 @@ function initializeModel(csn, _options, messageFunctions)
136
119
  }
137
120
 
138
121
  if(schemaNames.length) {
139
- Object.values(serviceRoots).forEach(initializeService);
140
- // Set myServiceName for later reference and indication of a service member
141
- // First attach names to all definitions in the model
142
- // 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 ]);
122
+ forEachDefinition(csn, [
123
+ attachNameProperty,
124
+ (def, defName) => {
125
+ const mySchemaName = whatsMySchemaName(defName);
126
+ mySchemaName && setProp(def, '$mySchemaName', mySchemaName);
127
+ if(isMyServiceRequested(defName))
128
+ reqDefs.definitions[defName] = def;
129
+ },
130
+ linkAssociationTarget ]);
131
+ // initialize requested services
132
+ const skip = { skipArtifact: (_def, defName) => !isMyServiceRequested(defName) };
133
+ forEachDefinition({ definitions: serviceRoots }, initService, skip);
146
134
  // Create data structures for containments
147
- forEachDefinition(csn, initializeContainments);
135
+ forEachDefinition(reqDefs, initContainments);
148
136
  // Initialize entities with parameters (add Parameter entity)
149
- forEachDefinition(csn, initializeParameterizedEntityOrView);
137
+ forEachDefinition(reqDefs, initParameterizedEntityOrView);
150
138
  // Initialize structures
151
- forEachDefinition(csn, initializeStructure);
139
+ forEachDefinition(csn, initStructure);
152
140
  // Initialize associations after _parent linking
153
- forEachDefinition(csn, prepareConstraints);
141
+ forEachDefinition(reqDefs, initConstraints);
154
142
  // Mute V4 elements depending on constraint preparation
155
143
  if(options.isV4())
156
- forEachDefinition(csn, ignoreProperties);
144
+ forEachDefinition(reqDefs, ignoreProperties);
157
145
  // calculate constraints based on ignoreProperties and prepareConstraints
158
- forEachDefinition(csn, finalizeConstraints);
146
+ forEachDefinition(reqDefs, finalizeConstraints);
159
147
  // convert exposed types into cross schema references if required
160
148
  // must be run before proxy exposure to avoid potential reference collisions
161
149
  convertExposedTypesOfOtherServicesIntoCrossReferences();
162
150
  // create association target proxies
163
151
  // Decide if an entity set needs to be constructed or not
164
- forEachDefinition(csn, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
152
+ forEachDefinition(reqDefs, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
165
153
  // finalize proxy creation
166
154
  mergeProxiesIntoModel();
167
155
 
168
156
  if(options.isV4())
169
- forEachDefinition(csn, initializeEdmNavPropBindingTargets);
157
+ forEachDefinition(reqDefs, initEdmNavPropBindingTargets);
170
158
 
171
159
  // Things that can be done in one pass
172
160
  // Create edmKeyRefPaths
173
161
  // Create NavigationPropertyBindings, requires determineEntitySet
174
162
  // Map /** doc comments */ to @CoreDescription
175
- // Artifact identifier spec compliance check (should be run last)
176
- forEachDefinition(csn, [ initializeEdmKeyRefPaths, initializeEdmNavPropBindingPaths,
177
- initializeEdmTypesAndDescription, checkArtifactIdentifierAndBoundActions ]);
163
+ forEachDefinition(reqDefs, [
164
+ initEdmKeyRefPaths,
165
+ initEdmNavPropBindingPaths,
166
+ initEdmTypesAndDescription
167
+ ]);
178
168
  }
179
- return [serviceRoots, schemas, whatsMyServiceRootName, autoexposeSchemaName, options];
169
+ return [serviceRoots, schemas, reqDefs, whatsMyServiceRootName, fallBackSchemaName, options];
180
170
 
181
171
  //////////////////////////////////////////////////////////////////////
182
172
  //
183
173
  // Service initialization starts here
184
174
  //
185
175
 
176
+ function getAnOverviewOnTheServices(csn) {
177
+ const defs = csn.definitions || {};
178
+ const serviceRoots = Object.create(null);
179
+ for(const defName in defs) {
180
+ const def = defs[defName];
181
+ if(def && def.kind === 'service')
182
+ serviceRoots[defName] = Object.assign(def, { name: defName });
183
+ }
184
+
185
+ // first of all we need to know about all 'real' user defined services
186
+ const serviceRootNames = Object.keys(serviceRoots).sort((a,b)=>b.length-a.length);
187
+
188
+ function whatsMyServiceRootName(n, self=true) {
189
+ return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
190
+ }
191
+
192
+ // find a globally unambiguous schema name to collect all top level 'root' types
193
+ // TODO: work on service basis (this requires post exposure renaming)
194
+ let fallBackSchemaName = 'root';
195
+ let i = 1;
196
+ while (Object.keys(defs).some(artName => {
197
+ const p = artName.split('.');
198
+ return p.length === 2 && p[0] === fallBackSchemaName;
199
+ })) {
200
+ fallBackSchemaName = 'root' + i++;
201
+ }
202
+
203
+ return [
204
+ serviceRoots,
205
+ serviceRootNames,
206
+ fallBackSchemaName,
207
+ whatsMyServiceRootName ];
208
+ }
209
+
186
210
  /*
187
211
  Replace dots in sub-service and sub-context definitions with underscores to be
188
212
  Odata ID compliant.
@@ -194,9 +218,10 @@ function initializeModel(csn, _options, messageFunctions)
194
218
  // Find the first definition above the current definition or undefined otherwise.
195
219
  // Definition can either be a context or a service
196
220
  function getRootDef(name) {
221
+ const scopeKinds = {'service':1, 'context':1};
197
222
  let pos = name.lastIndexOf('.');
198
223
  name = pos < 0 ? undefined : name.substring(0, pos);
199
- while (name && !['service', 'context'].includes(csn.definitions[name] && csn.definitions[name].kind)) {
224
+ while (name && !((csn.definitions[name] && csn.definitions[name].kind) in scopeKinds)) {
200
225
  pos = name.lastIndexOf('.');
201
226
  name = pos < 0 ? undefined : name.substring(0, pos);
202
227
  }
@@ -205,8 +230,9 @@ function initializeModel(csn, _options, messageFunctions)
205
230
 
206
231
  const dotEntityNameMap = Object.create(null);
207
232
  const dotTypeNameMap = Object.create(null);
233
+ const kinds = {'entity':1, 'type':1, 'action':1, 'function':1};
208
234
  forEachDefinition(csn, (def, defName) => {
209
- if(['entity', 'type', 'action', 'function'].includes(def.kind)) {
235
+ if(def.kind in kinds) {
210
236
  const rootDef = getRootDef(defName);
211
237
  // if this definition has a root def and the root def is not the service/schema name
212
238
  // => service C { type D.E }, replace the prefix dots with underscores
@@ -276,7 +302,7 @@ function initializeModel(csn, _options, messageFunctions)
276
302
  const myServiceRoot = whatsMyServiceRootName(defName);
277
303
  const mySchemaPrefix = getSchemaPrefix(defName);
278
304
  if(myServiceRoot && options.isV4() &&
279
- /*(options.toOdata.odataProxies || options.toOdata.odataXServiceRefs) && options.isStructFormat && */
305
+ /*(options.odataProxies || options.odataXServiceRefs) && options.isStructFormat && */
280
306
  defName !== myServiceRoot && myServiceRoot !== mySchemaPrefix) {
281
307
  const service = { kind: 'service', name: mySchemaPrefix };
282
308
  serviceRoots[mySchemaPrefix] = service;
@@ -303,24 +329,14 @@ function initializeModel(csn, _options, messageFunctions)
303
329
  }
304
330
 
305
331
  // 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);
332
+ function initService(serviceRoot) {
333
+ setSAPSpecificV2AnnotationsToEntityContainer(options, serviceRoot);
318
334
  }
319
335
 
320
336
  // link association target to association and add @odata.contained to compositions in V4
321
337
  function linkAssociationTarget(struct) {
322
338
  forEachMemberRecursively(struct, (element, name, prop, subpath) => {
323
- if(isAssociationOrComposition(element) && !element._ignore) {
339
+ if(isAssociationOrComposition(element)) {
324
340
  if(!element._target) {
325
341
  let target = csn.definitions[element.target];
326
342
  if(target) {
@@ -361,7 +377,7 @@ function initializeModel(csn, _options, messageFunctions)
361
377
  // non-containment rendering. If containment rendering is active, the containee has no
362
378
  // entity set. Instead try to rewrite the annotation in such a way that it is effective
363
379
  // on the containment navigation property.
364
- function initializeContainments(container) {
380
+ function initContainments(container) {
365
381
  if(container.kind === 'entity') {
366
382
  forEachMemberRecursively(container, initContainments,
367
383
  [], true, { elementsOnly: true });
@@ -424,7 +440,7 @@ function initializeModel(csn, _options, messageFunctions)
424
440
  // must be called.
425
441
  // As a param entity is a potential proxy candidate, this split must be performed on
426
442
  // all definitions
427
- function initializeParameterizedEntityOrView(entityCsn, entityName) {
443
+ function initParameterizedEntityOrView(entityCsn, entityName) {
428
444
 
429
445
  if(!isParameterizedEntity(entityCsn))
430
446
  return;
@@ -441,13 +457,71 @@ function initializeModel(csn, _options, messageFunctions)
441
457
  // Backlink Navigation Property "Parameters" to <ViewName>Parameters
442
458
 
443
459
  // 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';
460
+ const typeEntityName = entityName + 'Type';
461
+ const typeEntitySetName = entityName + 'Set';
462
+ const parameterToTypeAssocName = 'Set';
463
+ const typeToParameterAssocName = 'Parameters';
449
464
  let hasBacklink = true;
450
465
 
466
+
467
+ // create the Parameter Definition
468
+ const parameterCsn = createParameterEntity(entityCsn, entityName, false);
469
+
470
+ // create the Type Definition
471
+ // modify the original parameter entity with backlink and new name
472
+ if(csn.definitions[typeEntityName])
473
+ error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: typeEntityName });
474
+ else {
475
+ csn.definitions[typeEntityName] = entityCsn;
476
+ reqDefs.definitions[typeEntityName] = entityCsn;
477
+ delete csn.definitions[entityCsn.name];
478
+ delete reqDefs.definitions[entityCsn.name];
479
+ entityCsn.name = typeEntityName;
480
+ }
481
+ setProp(entityCsn, '$entitySetName', typeEntitySetName);
482
+ // add backlink association
483
+ if(hasBacklink) {
484
+ entityCsn.elements[typeToParameterAssocName] = {
485
+ name: typeToParameterAssocName,
486
+ target: parameterCsn.name,
487
+ type: 'cds.Association',
488
+ on: [ { ref: [ 'Parameters', 'Set' ] }, '=', { ref: [ '$self' ] } ]
489
+ };
490
+ setProp(entityCsn.elements[typeToParameterAssocName], '_selfReferences', []);
491
+ setProp(entityCsn.elements[typeToParameterAssocName], '_target', parameterCsn);
492
+ setProp(entityCsn.elements[typeToParameterAssocName], '$path',
493
+ [ 'definitions', typeEntityName, 'elements', typeToParameterAssocName ] );
494
+
495
+ // rewrite $path
496
+ if(entityCsn.$path)
497
+ entityCsn.$path[1] = typeEntityName;
498
+ forEachMemberRecursively(entityCsn, (member) => {
499
+ if(member.$path)
500
+ member.$path[1] = typeEntityName;
501
+ });
502
+ }
503
+
504
+ /*
505
+ <EntitySet Name="ZRHA_TEST_CDSSet" EntityType="ZRHA_TEST_CDS_CDS.ZRHA_TEST_CDSType" sap:creatable="false" sap:updatable="false"
506
+ sap:deletable="false" sap:addressable="false" sap:content-version="1"/>
507
+ */
508
+ assignProp(entityCsn, '_SetAttributes',
509
+ {'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.addressable': false });
510
+
511
+ // redirect inbound associations/compositions to the parameter entity
512
+ Object.keys(entityCsn.$sources || {}).forEach(n => {
513
+ // preserve the original target for constraint calculation
514
+ setProp(entityCsn.$sources[n], '_originalTarget', entityCsn.$sources[n]._target);
515
+ entityCsn.$sources[n]._target = parameterCsn;
516
+ entityCsn.$sources[n].target = parameterCsn.name;
517
+ });
518
+ rewriteContainmentAnnotations(parameterCsn, entityCsn, parameterToTypeAssocName);
519
+ }
520
+
521
+ function createParameterEntity(entityCsn, entityName, isProxy) {
522
+ const parameterEntityName = entityName + 'Parameters';
523
+ const parameterToTypeAssocName = 'Set';
524
+
451
525
  // Construct the parameter entity
452
526
  const parameterCsn = {
453
527
  name: parameterEntityName,
@@ -455,8 +529,8 @@ function initializeModel(csn, _options, messageFunctions)
455
529
  elements: Object.create(null),
456
530
  '@sap.semantics': 'parameters',
457
531
  };
458
- setProp(parameterCsn, '$entitySetName', entityName);
459
-
532
+ if(!isProxy)
533
+ setProp(parameterCsn, '$entitySetName', entityName);
460
534
  if(entityCsn.$location){
461
535
  assignProp(parameterCsn, '$location', entityCsn.$location);
462
536
  }
@@ -482,9 +556,10 @@ function initializeModel(csn, _options, messageFunctions)
482
556
  entityCsn._containerEntity = [ parameterCsn ];
483
557
 
484
558
  forEachGeneric(entityCsn, 'params', (p,n) => {
485
- let elt = cloneCsn(p, options);
559
+ let elt = cloneCsnNonDict(p, options);
486
560
  elt.name = n;
487
561
  delete elt.kind;
562
+ setProp(elt, '$path', [ 'definitions', parameterEntityName, 'elements', n ]);
488
563
  elt.key = true; // params become primary key in parameter entity
489
564
  /*
490
565
  Spec meeting decision 28.02.22:
@@ -500,81 +575,35 @@ function initializeModel(csn, _options, messageFunctions)
500
575
  parameterCsn.elements[n] = elt;
501
576
  });
502
577
  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,
578
+ initContainments(parameterCsn);
579
+ // add assoc to result set, FIXME: is the cardinality correct?
580
+ if(!isProxy) {
581
+ parameterCsn.elements[parameterToTypeAssocName] = {
582
+ '@odata.contained': true,
583
+ name: parameterToTypeAssocName,
584
+ target: entityCsn.name,
541
585
  type: 'cds.Association',
542
- on: [ { ref: [ 'Parameters', 'Set' ] }, '=', { ref: [ '$self' ] } ]
586
+ cardinality: { src: 1, min: 0, max: '*' }
543
587
  };
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
- });
588
+ setProp(parameterCsn.elements[parameterToTypeAssocName], '_target', entityCsn);
589
+ setProp(parameterCsn.elements[parameterToTypeAssocName], '$path',
590
+ [ 'definitions', parameterEntityName, 'elements', parameterToTypeAssocName ] );
556
591
  }
592
+ // rewrite $path
593
+ setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
557
594
 
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);
595
+ // proxies are registered in model separately
596
+ if(!isProxy) {
597
+ if(csn.definitions[parameterCsn.name])
598
+ error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: parameterCsn.name });
599
+ else {
600
+ csn.definitions[parameterCsn.name] = parameterCsn;
601
+ reqDefs.definitions[parameterCsn.name] = parameterCsn;
602
+ }
603
+ }
604
+ return parameterCsn;
575
605
  }
576
606
 
577
-
578
607
  function initElement(element, name, struct) {
579
608
  setProp(element, 'name', name)
580
609
  setProp(element, '_parent', struct);
@@ -600,7 +629,7 @@ function initializeModel(csn, _options, messageFunctions)
600
629
  }
601
630
 
602
631
  // Initialize a structured artifact
603
- function initializeStructure(def) {
632
+ function initStructure(def) {
604
633
 
605
634
  // Don't operate on any structured types other than type and entity
606
635
  // such as events and aspects
@@ -611,24 +640,13 @@ function initializeModel(csn, _options, messageFunctions)
611
640
  let validFrom = [], validKey = [];
612
641
 
613
642
  // Iterate all struct elements
614
- forEachMemberRecursively(def.items || def, (element, elementName, prop, path = [], construct) => {
643
+ forEachMemberRecursively(def.items || def, (element, elementName, prop, _path = [], construct) => {
615
644
  if(prop !== 'elements')
616
645
  return;
617
646
 
618
647
  initElement(element, elementName, construct);
619
648
 
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
649
+ // collect temporal information
632
650
  if(element['@cds.valid.key']) {
633
651
  validKey.push(element);
634
652
  }
@@ -662,7 +680,7 @@ function initializeModel(csn, _options, messageFunctions)
662
680
  applyAppSpecificLateCsnTransformationOnElement(options, element, def, error);
663
681
  }, [], true, { elementsOnly: true });
664
682
 
665
- if(!isDeprecatedEnabled(options, 'v1KeysForTemporal')) {
683
+ if(!isDeprecatedEnabled(options, '_v1KeysForTemporal')) {
666
684
  // if artifact has a cds.valid.key mention it as @Core.AlternateKey
667
685
  if(validKey.length) {
668
686
  let altKeys = [{ Key: [] }];
@@ -708,18 +726,19 @@ function initializeModel(csn, _options, messageFunctions)
708
726
  }
709
727
 
710
728
  // Prepare the associations for the subsequent steps
711
- function prepareConstraints(struct) {
712
- if(!isStructuredArtifact(struct))
729
+ function initConstraints(def) {
730
+ if(!isStructuredArtifact(def))
713
731
  return;
714
732
 
715
- forEachMemberRecursively(struct.items || struct, (element) => {
716
- if (isAssociationOrComposition(element) && !element._ignore) {
733
+ forEachMemberRecursively(def.items || def, initConstraintsOnAssoc, [], true, { elementsOnly: true });
734
+ }
735
+ function initConstraintsOnAssoc(element) {
736
+ if (isAssociationOrComposition(element) && !element._constraints) {
717
737
  // setup the constraints object
718
- setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
738
+ setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
719
739
  // and crack the ON condition
720
- resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
721
- }
722
- }, [], true, { elementsOnly: true });
740
+ resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
741
+ }
723
742
  }
724
743
 
725
744
  /*
@@ -766,7 +785,7 @@ function initializeModel(csn, _options, messageFunctions)
766
785
  delete struct.$keys[element.name];
767
786
  }
768
787
  }
769
- // deprecated unmanagedUpInComponent:
788
+ // deprecated._unmanagedUpInComponent:
770
789
  // Only in containment:
771
790
  // Ignore this (foreign key) elment if renderForeignKeys is false
772
791
  if(options.odataContainment && element['@odata.containment.ignore']) {
@@ -791,38 +810,39 @@ function initializeModel(csn, _options, messageFunctions)
791
810
  It may be that now a number of properties are not rendered and cannot act as constraints (see isConstraintCandidate())
792
811
  in edmUtils
793
812
  */
794
- function finalizeConstraints(struct) {
795
- if(!isStructuredArtifact(struct))
813
+ function finalizeConstraints(def) {
814
+ if(!isStructuredArtifact(def))
796
815
  return;
797
816
 
798
- forEachMemberRecursively(struct.items || struct, (element) => {
799
- if (isAssociationOrComposition(element) && !element._ignore) {
800
- finalizeReferentialConstraints(csn, element, options, info);
817
+ forEachMemberRecursively(def.items || def, finalizeConstraintsOnAssoc, [], true, { elementsOnly: true });
818
+ }
819
+ function finalizeConstraintsOnAssoc(element) {
820
+ if (isAssociationOrComposition(element) && !element._ignore && element._constraints) {
821
+ finalizeReferentialConstraints(csn, element, options, info);
801
822
 
802
- if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
823
+ if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
803
824
  // 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) {
825
+ if(element._constraints._partnerCsn.cardinality) {
805
826
  // if the forward association has set a src cardinality and it deviates from the backlink target cardinality raise a warning
806
827
  // 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) {
828
+ if(element._constraints._partnerCsn.cardinality.src) {
829
+ let srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
830
+ let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
831
+ if(options.isV2() && srcMult !== newMult) {
811
832
  // 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;
833
+ 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
834
  }
819
835
  }
820
836
  else {
821
- element._constraints._partnerCsn.cardinality = { src: element.cardinality.max };
837
+ // .. but only if the original assoc hasn't set src yet
838
+ element._constraints._partnerCsn.cardinality.src = element.cardinality.max;
822
839
  }
823
840
  }
841
+ else {
842
+ element._constraints._partnerCsn.cardinality = { src: element.cardinality.max };
843
+ }
824
844
  }
825
- }, [], true, { elementsOnly: true });
845
+ }
826
846
  }
827
847
 
828
848
  /*
@@ -830,7 +850,7 @@ function initializeModel(csn, _options, messageFunctions)
830
850
  sub artifacts exposed by the initial type exposure
831
851
  */
832
852
  function convertExposedTypesOfOtherServicesIntoCrossReferences() {
833
- if(options.toOdata.odataXServiceRefs && options.isV4()) {
853
+ if(options.odataXServiceRefs && options.isV4()) {
834
854
  serviceRootNames.forEach(srn => {
835
855
  schemaNames.forEach(fqSchemaName => {
836
856
  if(fqSchemaName.startsWith(srn + '.')) {
@@ -838,8 +858,10 @@ function initializeModel(csn, _options, messageFunctions)
838
858
  if(serviceRootNames.includes(targetSchemaName)) {
839
859
  // remove all definitions starting with < fqSchemaName >. and add a schema reference
840
860
  Object.keys(csn.definitions).forEach(dn => {
841
- if(dn.startsWith(fqSchemaName)) // this includes the fqSchemaName context
861
+ if(dn.startsWith(fqSchemaName)) {// this includes the fqSchemaName context
842
862
  delete csn.definitions[dn];
863
+ delete reqDefs.definitions[dn];
864
+ }
843
865
  });
844
866
  if(!schemas[fqSchemaName])
845
867
  schemaNames.push(fqSchemaName);
@@ -870,10 +892,9 @@ function initializeModel(csn, _options, messageFunctions)
870
892
 
871
893
  If option odataExtReferences is used, 'root' proxies are still created.
872
894
 
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.
895
+ If the association leading to the proxy candidate refers to associations either directly
896
+ or indirectly (via structured elements), these dependent entity types are (recursively) exposed
897
+ (or referenced) as well to keep the navigation graph in tact.
877
898
  */
878
899
  function exposeTargetsAsProxiesOrSchemaRefs(struct) {
879
900
  if(struct.kind === 'context' || struct.kind === 'service' || struct.$proxy)
@@ -886,7 +907,7 @@ function initializeModel(csn, _options, messageFunctions)
886
907
  // if this artifact is a service member check its associations
887
908
  if(globalSchemaPrefix) {
888
909
  forEachGeneric(struct.items || struct, 'elements', element => {
889
- if(!isAssociationOrComposition(element) || element._ignore || element['@odata.navigable'] === false)
910
+ if(!isAssociationOrComposition(element) || element['@odata.navigable'] === false)
890
911
  return;
891
912
  /*
892
913
  * Consider everything @cds.autoexpose: falsy to be a proxy candidate for now
@@ -923,16 +944,18 @@ function initializeModel(csn, _options, messageFunctions)
923
944
  const targetSchemaName = element._target.$mySchemaName;
924
945
  if(isProxyRequired(element)) {
925
946
  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 */) {
947
+ (options.odataProxies || options.odataXServiceRefs) &&
948
+ // must be a managed association with keys OR an unambiguous backlink
949
+ element.keys ||
950
+ (element.on && element._constraints.selfs.length === 1 && element._constraints.termCount === 1)
951
+ ) {
929
952
  // reuse proxy if available
930
953
  let proxy = getProxyForTargetOf(element);
931
954
  if(!proxy) {
932
- if(targetSchemaName && options.toOdata.odataXServiceRefs) {
933
- proxy = createSchemaRefFor(element, targetSchemaName);
955
+ if(targetSchemaName && options.odataXServiceRefs) {
956
+ proxy = createSchemaRefFor(targetSchemaName);
934
957
  }
935
- else if(options.toOdata.odataProxies) {
958
+ else if(options.odataProxies) {
936
959
  proxy = createProxyFor(element, targetSchemaName);
937
960
  }
938
961
  proxy = registerProxy(proxy, element);
@@ -941,14 +964,16 @@ function initializeModel(csn, _options, messageFunctions)
941
964
  // if a proxy was either already created or could be created and
942
965
  // if it's a 'real' proxy, link the _target to it and remove constraints
943
966
  // otherwise proxy is a schema reference, then do nothing
967
+ setProp(element, '$noPartner', true);
944
968
  element._constraints.constraints = Object.create(null);
945
969
  if(proxy.kind === 'entity') {
970
+ if(!proxy.$isParamEntity)
971
+ populateProxyElements(element, proxy, getForeignKeyDefinitions(element));
946
972
  element._target = proxy;
947
- populateProxyElements(element, proxy, getForeignKeyDefinitions(element, ['definitions', struct.name, 'elements', element.name]));
948
973
  }
949
974
  else {
950
- // fake the target to be proxy
951
- setProp(element._target, '$externalRef', true);
975
+ // No navigation property bindings on external references
976
+ setProp(element, '$externalRef', true);
952
977
  }
953
978
  }
954
979
  else {
@@ -968,11 +993,11 @@ function initializeModel(csn, _options, messageFunctions)
968
993
  }
969
994
 
970
995
  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)');
996
+ warning('odata-navigation', ['definitions', struct.name, 'elements', elt.name],
997
+ { target: elt._target.name, service: globalSchemaPrefix });
973
998
  }
974
999
 
975
- function createSchemaRefFor(assoc, targetSchemaName) {
1000
+ function createSchemaRefFor(targetSchemaName) {
976
1001
  let ref = csn.definitions[globalSchemaPrefix + '.' + targetSchemaName];
977
1002
  if(!ref) {
978
1003
  ref = createSchemaRef(targetSchemaName);
@@ -987,11 +1012,23 @@ function initializeModel(csn, _options, messageFunctions)
987
1012
  // if it is required in multiple services. The service schema name is prepended upon registration
988
1013
  const proxySchemaName = targetSchemaName || getSchemaPrefix(assoc._target.name);
989
1014
 
990
- // 1) construct the proxy definition
991
- // proxyShortName: strip the serviceName and replace '.' with '_'
992
- const proxyShortName = assoc._target.name.replace(proxySchemaName + '.', '').replace(/\./g, '_');
1015
+ // if the target is a parameter entity, it's easy just create the parameter stub
1016
+ const isParamProxy = isParameterizedEntity(assoc._target);
1017
+
1018
+ // 1) construct the proxy definition
1019
+ // proxyDefinitionName: strip the serviceName and replace '.' with '_'
1020
+ let defName =
1021
+ `${assoc._target.name.replace(proxySchemaName + '.', '').replace(/\./g, '_')}`;
1022
+
993
1023
  // fullName: Prepend serviceName and if in same service add '_proxy'
994
- const proxy = { name: proxySchemaName + '.' + proxyShortName, kind: 'entity', $proxy: true, elements: Object.create(null) };
1024
+ const proxy = isParamProxy
1025
+ ? createParameterEntity(assoc._target, proxySchemaName + '.' + defName, true)
1026
+ : { name: proxySchemaName + '.' + defName, kind: 'entity', elements: Object.create(null) };
1027
+
1028
+ // Final proxyShortName for all further processing
1029
+ const proxyShortName = defName + (isParamProxy ? 'Parameters' : '');
1030
+
1031
+ setProp(proxy, '$proxy', true);
995
1032
  setProp(proxy, '$mySchemaName', proxySchemaName);
996
1033
  setProp(proxy, '$proxyShortName', proxyShortName);
997
1034
  setProp(proxy, '$keys', Object.create(null));
@@ -1004,53 +1041,66 @@ function initializeModel(csn, _options, messageFunctions)
1004
1041
  });
1005
1042
 
1006
1043
  // 2) create the elements and $keys
1007
- populateProxyElements(assoc, proxy, assoc._target.$keys);
1044
+ if(isParamProxy) {
1045
+ // Reset param proxy elements to expose element tree
1046
+ const elements = proxy.elements;
1047
+ proxy.elements = Object.create(null);
1048
+ populateProxyElements(assoc, proxy, elements);
1049
+ }
1050
+ else
1051
+ populateProxyElements(assoc, proxy, assoc._target.$keys);
1008
1052
  return proxy;
1009
1053
 
1010
1054
  }
1011
1055
 
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) : [];
1056
+ // Return top level foreign key element definitions. The full top level
1057
+ // element is exposed instead of merging partial trees into the exposed type
1058
+ // def structure.
1059
+ function getForeignKeyDefinitions(e) {
1060
+ return e.keys ? e.keys.map(fk => e._target.elements[fk.ref[0]]) : [];
1018
1061
  }
1019
- // copy over the primary keys of the target and trigger the type exposure
1062
+
1063
+ // copy over the primary keys of the target and trigger the type exposure
1064
+ // if the element already exists we assume it was fully exposed
1020
1065
  function populateProxyElements(assoc, proxy, elements) {
1021
1066
  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
1067
+ if (isEdmPropertyRendered(e, options)) {
1068
+ let newElt = proxy.elements[e.name];
1069
+ if(!newElt) {
1070
+ if(csnUtils.isAssocOrComposition(e.type)) {
1071
+ if(!e.on && e.keys) {
1072
+ if(options.odataNoTransitiveProxies)
1073
+ newElt = convertManagedAssocIntoStruct(e);
1074
+ else
1029
1075
  newElt = createProxyOrSchemaRefForManagedAssoc(e);
1030
- }
1031
- else {
1032
- info(null, ['definitions', struct.name, 'elements', assoc.name],
1076
+ }
1077
+ else {
1078
+ info(null, ['definitions', struct.name, 'elements', assoc.name],
1033
1079
  { name: proxy.nname, target: assoc._target.name },
1034
1080
  'Unmanaged associations are not supported as primary keys for proxy entity type $(NAME) of unexposed association target $(TARGET)');
1081
+ }
1035
1082
  }
1036
- }
1037
- else {
1038
- newElt = cloneCsn(e, options);
1039
- }
1040
- if(newElt) {
1041
- initElement(newElt, e.name, proxy);
1042
- if(isStructured(newElt)) {
1083
+ else {
1084
+ newElt = Object.create(null);
1085
+ Object.keys(e).forEach(prop => newElt[prop] = e[prop])
1086
+ }
1087
+ if(newElt) {
1088
+ initElement(newElt, e.name, proxy);
1089
+ proxy.elements[newElt.name] = newElt;
1090
+
1091
+ if(csnUtils.isStructured(newElt)) {
1043
1092
  // 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
1093
+ // proxy schema. If omitted, this exposure defaults to 'root', in case API flavor
1094
+ // of the day changes...
1095
+ exposeStructTypeForProxyOf(proxy, newElt, proxy.$proxyShortName + '_' + newElt.name,
1096
+ proxy.$mySchemaName, newElt.key, !!(newElt.key && newElt.elements));
1097
+ }
1098
+ if(newElt.key)
1099
+ proxy.$keys[newElt.name] = newElt;
1048
1100
  }
1049
- proxy.elements[newElt.name] = newElt;
1050
- if(newElt.key)
1051
- proxy.$keys[newElt.name] = newElt;
1052
1101
  }
1053
1102
  }
1103
+
1054
1104
  });
1055
1105
  // 3) sort the exposed types so that they appear lexicographically ordered in the EDM
1056
1106
  proxy.$exposedTypes = Object.keys(proxy.$exposedTypes).sort().reduce((dict, tn) => {
@@ -1062,78 +1112,90 @@ function initializeModel(csn, _options, messageFunctions)
1062
1112
  // anonymous or has a definition outside of 'service'), create an equivalent type in 'service', either
1063
1113
  // using the type's name or (if anonymous) 'artificialName', and make 'node' use that type instead.
1064
1114
  // Complain if there is an error.
1065
- function exposeStructTypeForProxyOf(proxy, node, artificialName, typeSchemaName=autoexposeSchemaName) {
1066
- const isNotInProtNS = node.type ? !isBuiltinType(node.type) : true;
1115
+ // isKey: Indicates top level element is key or not
1116
+ // forceToNotNull: if top level element is key, recursively set all anonymously exposed elements
1117
+ // to notNull until the first named type is exposed.
1118
+ function exposeStructTypeForProxyOf(proxy, node, artificialName,
1119
+ typeSchemaName=fallBackSchemaName,
1120
+ isKey, forceToNotNull) {
1121
+
1122
+ if(node.type && isBuiltinType(node.type))
1123
+ return;
1124
+
1067
1125
  // Always expose types referred to by a proxy, never reuse an eventually existing type
1068
1126
  // as the nested elements must all be not nullable
1069
- if (isNotInProtNS) {
1070
- let typeDef = node.type ? csn.definitions[node.type] : /* anonymous type */ node;
1127
+ // elements have precedence over type
1128
+ const typeDef = !node.elements && node.type ? csn.definitions[node.type] : node;
1071
1129
 
1072
- if (typeDef) {
1073
- let typeClone;
1130
+ if (typeDef) {
1131
+ let typeClone;
1074
1132
  // the type clone must be produced for each service as this type may
1075
1133
  // produce references and/or proxies into multiple services
1076
1134
  // (but only once per service, therefore cache it).
1077
- if(typeDef.$proxyTypes && typeDef.$proxyTypes[globalSchemaPrefix]) {
1135
+ if(typeDef.$proxyTypes && typeDef.$proxyTypes[globalSchemaPrefix]) {
1078
1136
  // if type has been exposed in a schema use this type
1079
- typeClone = typeDef.$proxyTypes[globalSchemaPrefix];
1080
- }
1081
- else {
1137
+ typeClone = typeDef.$proxyTypes[globalSchemaPrefix];
1138
+ }
1139
+ else {
1082
1140
  // Set the correct name
1083
- let typeId = artificialName; // the artificialName has no namespace, it's the element
1084
- if(node.type) {
1141
+ let typeId = artificialName; // the artificialName has no namespace, it's the element
1142
+ if(node.type) {
1085
1143
  // 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, '_');
1144
+ typeSchemaName = typeDef.$mySchemaName || getSchemaPrefix(node.type);
1145
+ typeId = node.type.replace(typeSchemaName + '.', '').replace(/\./g, '_');
1088
1146
  // strip the service root of that type (if any)
1089
- const myServiceRootName = whatsMyServiceRootName(typeSchemaName);
1090
- if(myServiceRootName)
1091
- typeSchemaName = typeSchemaName.replace(myServiceRootName + '.', '');
1092
- }
1147
+ const myServiceRootName = whatsMyServiceRootName(typeSchemaName);
1148
+ if(myServiceRootName)
1149
+ typeSchemaName = typeSchemaName.replace(myServiceRootName + '.', '');
1150
+ }
1093
1151
 
1094
- if(isStructuredArtifact(typeDef)) {
1095
- typeClone = cloneStructTypeForProxy(typeSchemaName, `${typeSchemaName}.${typeId}`, typeDef);
1096
- if(typeClone) {
1152
+ if(isStructuredArtifact(typeDef)) {
1153
+ // pull forceNotNull to false for named types and non-key nodes
1154
+ // only toplevel nodes (elements) can be key
1155
+ forceToNotNull = !!(forceToNotNull && isKey && node.elements && !node.type);
1156
+
1157
+ typeClone = cloneStructTypeForProxy(typeSchemaName, `${typeSchemaName}.${typeId}`, typeDef);
1158
+ if(typeClone) {
1097
1159
  // Recurse into elements of 'type' (if any)
1098
- typeClone.elements && Object.entries(typeClone.elements).forEach( ([elemName, elem]) => {
1160
+ typeClone.elements && Object.entries(typeClone.elements).forEach(([elemName, elem]) => {
1099
1161
  // if this is a foreign key elment, we must check whether or not the association
1100
1162
  // has been exposed as proxy. If it has not been exposed, no further structured
1101
1163
  // types must be exposed as 'Proxy_' types.
1102
1164
 
1103
1165
  // TODO: expose types of assoc.keys and don't rely on exposed foreign keys
1104
- if(!elem['@odata.foreignKey4'] ||
1166
+ if(!elem['@odata.foreignKey4'] ||
1105
1167
  (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
- }
1168
+ exposeStructTypeForProxyOf(proxy, elem, `${typeId}_${elemName}`,
1169
+ typeSchemaName, isKey, forceToNotNull);
1170
+ });
1171
+ if(!typeDef.$proxyTypes)
1172
+ typeDef.$proxyTypes = Object.create(null);
1173
+ typeDef.$proxyTypes[globalSchemaPrefix] = typeClone;
1112
1174
  }
1113
- else {
1175
+ }
1176
+ else {
1114
1177
  // FUTURE: expose scalar type definition as well
1115
- }
1116
1178
  }
1117
- if(typeClone) {
1179
+ }
1180
+ if(typeClone) {
1118
1181
  // register the type clone at the proxy
1119
1182
  // Reminder: Each proxy receives a full set of type clones, even if the types are shared
1120
1183
  // (no scattered type clone caching). registerProxy() checks if a clone needs to be added to
1121
1184
  // csn.definitions.
1122
- proxy.$exposedTypes[typeClone.name] = typeClone;
1185
+ proxy.$exposedTypes[typeClone.name] = typeClone;
1123
1186
 
1124
1187
  // set the node's new type name
1125
- node.type = typeClone.name;
1188
+ node.type = typeClone.name;
1126
1189
  // the key path generator must use the type clone directly, because it can't resolve
1127
1190
  // the type clone in the CSN (its name is the final name and not the definition name).
1128
- setProp(node, '_type', typeClone);
1191
+ setProp(node, '_type', typeClone);
1129
1192
  // Hack alert:
1130
1193
  // beta feature 'subElemRedirections' (now the default in v2) adds elements to the node by
1131
1194
  // default, without we must do it to get the primary key tuple calculation correct.
1132
1195
  // Remember: node.type is the service local type name (not prepended by the service name),
1133
1196
  // so it can't be resolved in definitions later on
1134
- if(typeClone.elements)
1135
- node.elements = typeClone.elements;
1136
- }
1197
+ if(typeClone.elements)
1198
+ node.elements = typeClone.elements;
1137
1199
  }
1138
1200
  }
1139
1201
 
@@ -1151,12 +1213,13 @@ function initializeModel(csn, _options, messageFunctions)
1151
1213
  if(!elem.target) {
1152
1214
  type.elements[elemName] = Object.create(null);
1153
1215
  Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
1154
- type.elements[elemName].notNull = true;
1155
1216
  }
1156
1217
  else if(elem.keys && !elem.on) {
1157
1218
  // a primary key can never be an unmanaged association
1158
1219
  type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
1159
1220
  }
1221
+ if(forceToNotNull)
1222
+ type.elements[elemName].notNull = true;
1160
1223
  setProp(type.elements[elemName], 'name', elem.name);
1161
1224
  });
1162
1225
  return type;
@@ -1166,7 +1229,7 @@ function initializeModel(csn, _options, messageFunctions)
1166
1229
  // Convert a managed association into a structured type and
1167
1230
  // eliminate nested foreign key associations
1168
1231
  function convertManagedAssocIntoStruct(e) {
1169
- let newElt = cloneCsn(e, options);
1232
+ let newElt = cloneCsnNonDict(e, options);
1170
1233
  newElt.elements = Object.create(null);
1171
1234
  // remove all unwanted garbage
1172
1235
  delete newElt.keys;
@@ -1177,14 +1240,15 @@ function initializeModel(csn, _options, messageFunctions)
1177
1240
  let keys = (!e._target.$isParamEntity && e.keys) ||
1178
1241
  Object.keys(e._target.$keys).map(k => { return { ref: [k] } });
1179
1242
  keys.forEach(k => {
1180
- let art = e._target || getCsnDef(e.target);
1243
+ let art = e._target || csnUtils.getCsnDef(e.target);
1181
1244
  for(let ps of k.ref) {
1182
1245
  art = art.elements[ps];
1183
1246
  }
1184
1247
  // art is in the target side, clone it and remove key property
1185
- let cloneArt = cloneCsn(art, options);
1248
+ let cloneArt = cloneCsnNonDict(art, options);
1186
1249
  setProp(cloneArt, 'name', art.name);
1187
- cloneArt.notNull = true;
1250
+ if(e.key)
1251
+ cloneArt.notNull = true;
1188
1252
  delete cloneArt.key;
1189
1253
  newElt.elements[art.name] = cloneArt;
1190
1254
  });
@@ -1200,18 +1264,19 @@ function initializeModel(csn, _options, messageFunctions)
1200
1264
  function createProxyOrSchemaRefForManagedAssoc(e) {
1201
1265
 
1202
1266
  let proxy = e._target;
1203
- let newElt = cloneCsn(e, options);
1267
+ let newElt = cloneCsnNonDict(e, options);
1204
1268
 
1205
1269
  if(isProxyRequired(e)) {
1206
1270
  proxy = getProxyForTargetOf(e);
1207
1271
  if(!proxy) {
1208
1272
  // option odataXServiceRefs has precedence over odataProxies
1209
- if(e._target.$mySchemaName && options.toOdata.odataXServiceRefs) {
1210
- proxy = createSchemaRefFor(e, e._target.$mySchemaName);
1273
+ if(e._target.$mySchemaName && options.odataXServiceRefs) {
1274
+ proxy = createSchemaRefFor(e._target.$mySchemaName);
1211
1275
  }
1212
- else if(options.toOdata.odataProxies) {
1276
+ else if(options.odataProxies) {
1213
1277
  proxy = createProxyFor(e, e._target.$mySchemaName);
1214
- populateProxyElements(e, proxy, getForeignKeyDefinitions(e));
1278
+ if(!e._target.$isParamEntity)
1279
+ populateProxyElements(e, proxy, getForeignKeyDefinitions(e));
1215
1280
  }
1216
1281
  proxy = registerProxy(proxy, e);
1217
1282
  }
@@ -1227,7 +1292,10 @@ function initializeModel(csn, _options, messageFunctions)
1227
1292
  setProp(newElt, '$exposed', true);
1228
1293
  // _target must be set with (original) in case
1229
1294
  // a schema ref has been created
1295
+ setProp(newElt, '$noPartner', true);
1230
1296
  setProp(newElt, '_target', e._target);
1297
+ initConstraintsOnAssoc(e);
1298
+ finalizeConstraintsOnAssoc(e);
1231
1299
  setProp(newElt, '_constraints', e._constraints);
1232
1300
  setProp(newElt, '_selfReferences', []);
1233
1301
  if(proxy.kind === 'entity') {
@@ -1322,11 +1390,13 @@ function initializeModel(csn, _options, messageFunctions)
1322
1390
  const alreadyRegistered = csn.definitions[fqProxyName];
1323
1391
  if(!alreadyRegistered) {
1324
1392
  csn.definitions[fqProxyName] = proxy;
1393
+ reqDefs.definitions[fqProxyName] = proxy;
1325
1394
  setProp(proxy, '$path', ['definitions', fqProxyName]);
1326
1395
  Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
1327
1396
  const fqtn = proxy.$globalSchemaPrefix + '.' + tn;
1328
1397
  if(csn.definitions[fqtn] === undefined) {
1329
1398
  csn.definitions[fqtn] = v;
1399
+ reqDefs.definitions[fqtn] = v;
1330
1400
  setProp(v, '$path', ['definitions', fqtn]);
1331
1401
  }
1332
1402
  });
@@ -1372,28 +1442,25 @@ function initializeModel(csn, _options, messageFunctions)
1372
1442
  and do not render associations (this will include the foreign keys of
1373
1443
  the _isToContainer association).
1374
1444
  */
1375
- function initializeEdmKeyRefPaths(struct) {
1376
- if(struct.$mySchemaName && struct.$keys) {
1377
- setProp(struct, '$edmKeyPaths', []);
1445
+ function initEdmKeyRefPaths(def) {
1446
+ if(def.$keys) {
1447
+ setProp(def, '$edmKeyPaths', []);
1378
1448
  // 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) => {
1449
+ foreach(def.$keys, k => !(k._isToContainer && k._selfReferences.length), (k, kn) => {
1380
1450
  if(isEdmPropertyRendered(k, options) &&
1381
1451
  !(options.isV2() && k['@Core.MediaType'])) {
1382
1452
  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));
1453
+ // This is structured OData ONLY
1454
+ // if the foreign keys are explicitly requested, ignore associations and use the flat foreign keys instead
1455
+ if(!options.renderForeignKeys || (options.renderForeignKeys && !k.target))
1456
+ def.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
1390
1457
  }
1391
1458
  // In v2/v4 flat, associations are never rendered
1392
1459
  else if(!k.target) {
1393
- struct.$edmKeyPaths.push([kn]);
1460
+ def.$edmKeyPaths.push([kn]);
1394
1461
  }
1395
1462
  // check toplevel key for spec violations
1396
- checkKeySpecViolations(k, ['definitions', struct.name, 'elements', k.name]);
1463
+ checkKeySpecViolations(k, ['definitions', def.name, 'elements', k.name]);
1397
1464
  }
1398
1465
  });
1399
1466
  }
@@ -1410,6 +1477,8 @@ function initializeModel(csn, _options, messageFunctions)
1410
1477
  */
1411
1478
  function produceKeyRefPaths(eltCsn, prefix) {
1412
1479
  const keyPaths = [];
1480
+ // we want to point to the element in the entity which is the first path step
1481
+ const location = def.$path.concat(['elements']).concat(prefix.split('/')[0]);
1413
1482
  if(!isEdmPropertyRendered(eltCsn, options)) {
1414
1483
  // let annos = Object.keys(eltCsn).filter(a=>a[0]==='@').join(', ');
1415
1484
  // warning(null, ['definitions', struct.name, 'elements', eltCsn.name ],
@@ -1418,7 +1487,7 @@ function initializeModel(csn, _options, messageFunctions)
1418
1487
  }
1419
1488
  // OData requires all elements along the path to be nullable: false (that is either key or notNull)
1420
1489
 
1421
- const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
1490
+ const finalType = csnUtils.getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
1422
1491
  const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
1423
1492
  (finalType && (finalType.elements || finalType.items && finalType.items.elements));
1424
1493
  if(elements) {
@@ -1428,8 +1497,6 @@ function initializeModel(csn, _options, messageFunctions)
1428
1497
  keyPaths.push(...newRefs);
1429
1498
  // check path step key for spec violations
1430
1499
  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
1500
  checkKeySpecViolations(elt, location, pathSegment);
1434
1501
  }
1435
1502
  });
@@ -1444,12 +1511,15 @@ function initializeModel(csn, _options, messageFunctions)
1444
1511
  // use the primary keys of the target
1445
1512
  let keys = (!eltCsn._target.$isParamEntity && eltCsn.keys) ||
1446
1513
  Object.keys(eltCsn._target.$keys).map(k => { return { ref: [k] } });
1514
+ let pathSegment = prefix
1447
1515
  keys.forEach(k => {
1448
- let art = eltCsn._target || getCsnDef(eltCsn.target);
1516
+ let art = eltCsn._target || csnUtils.getCsnDef(eltCsn.target);
1449
1517
  for(let ps of k.ref) {
1450
1518
  art = art.elements[ps];
1519
+ pathSegment += '/' + art.name
1520
+ checkKeySpecViolations(art, location, pathSegment);
1451
1521
  if(art.type && !isBuiltinType(art.type)) {
1452
- art = art._type || getCsnDef(art.type);
1522
+ art = art._type || csnUtils.getCsnDef(art.type);
1453
1523
  }
1454
1524
  }
1455
1525
  keyPaths.push(...produceKeyRefPaths(art, prefix + options.pathDelimiter + k.ref.join(options.pathDelimiter)));
@@ -1465,13 +1535,13 @@ function initializeModel(csn, _options, messageFunctions)
1465
1535
  // Nullability
1466
1536
  if((!elt.key && (elt.notNull === undefined || elt.notNull === false)) ||
1467
1537
  elt.key && (elt.notNull !== undefined && elt.notNull === false)) {
1468
- error('odata-spec-violation-key-null', location,
1538
+ message('odata-spec-violation-key-null', location,
1469
1539
  {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1470
1540
  }
1471
1541
  // many
1472
- let type = elt.items || elt.type && !isBuiltinType(elt.type) && getFinalTypeDef(elt.type).items;
1542
+ let type = elt.items || elt.type && !isBuiltinType(elt.type) && csnUtils.getFinalTypeDef(elt.type).items;
1473
1543
  if(type) {
1474
- error('odata-spec-violation-key-array', location,
1544
+ message('odata-spec-violation-key-array', location,
1475
1545
  {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1476
1546
  }
1477
1547
  // type
@@ -1483,10 +1553,10 @@ function initializeModel(csn, _options, messageFunctions)
1483
1553
  // V2 allows any Edm.PrimitiveType (even Double and Binary), V4 is more specific:
1484
1554
  if(options.isV4() && type && !isAssociationOrComposition(type) && isBuiltinType(type.type)) {
1485
1555
  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)) {
1556
+ const legalEdmTypes = {
1557
+ 'Edm.Boolean':1, 'Edm.Byte':1, 'Edm.Date':1, 'Edm.DateTimeOffset':1, 'Edm.Decimal':1, 'Edm.Duration':1,
1558
+ 'Edm.Guid':1, 'Edm.Int16':1, 'Edm.Int32':1, 'Edm.Int64':1, 'Edm.SByte':1, 'Edm.String':1, 'Edm.TimeOfDay':1 };
1559
+ if(!(edmType in legalEdmTypes)) {
1490
1560
  warning('odata-spec-violation-key-type', location,
1491
1561
  {name: pathSegment, type: type.type, id: edmType, '#': pathSegment ? 'std' : 'scalar'});
1492
1562
  }
@@ -1522,10 +1592,10 @@ function initializeModel(csn, _options, messageFunctions)
1522
1592
  Path="items/subitems/subitems/up_" Target="Header/items/subitems"/>
1523
1593
  Path="items/subitems/subitems/toG" Target="G"/>
1524
1594
  */
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);
1595
+ function initEdmNavPropBindingTargets(def) {
1596
+ if(def.$hasEntitySet) {
1597
+ forEachGeneric(def.items || def, 'elements', (element) => {
1598
+ produceTargetPath([edmUtils.getBaseName(def.name)], element, def);
1529
1599
  });
1530
1600
  }
1531
1601
 
@@ -1537,7 +1607,14 @@ function initializeModel(csn, _options, messageFunctions)
1537
1607
  if(isAssociationOrComposition(elt) && !elt.$touched) {
1538
1608
  if(!elt._target.$edmTgtPaths)
1539
1609
  setProp(elt._target, '$edmTgtPaths', []);
1540
- if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
1610
+ // drill into target only if
1611
+ // 1) target has no entity set and this assoc is not going to the container
1612
+ // 2) current definition and target are the same (cycle)
1613
+ // 3) it's no external reference
1614
+ if(!elt.$externalRef &&
1615
+ !elt._target.$hasEntitySet &&
1616
+ !elt._isToContainer &&
1617
+ curDef !== elt._target) {
1541
1618
  // follow elements in the target but avoid cycles
1542
1619
  setProp(elt, '$touched', true);
1543
1620
  elt._target.$edmTgtPaths.push(newPrefix);
@@ -1556,13 +1633,13 @@ function initializeModel(csn, _options, messageFunctions)
1556
1633
  }
1557
1634
  }
1558
1635
 
1559
- function initializeEdmNavPropBindingPaths(struct) {
1560
- if(options.isV4() && struct.$mySchemaName && struct.$hasEntitySet) {
1636
+ function initEdmNavPropBindingPaths(def) {
1637
+ if(options.isV4() &&def.$hasEntitySet) {
1561
1638
  let npbs = [];
1562
- forEachGeneric(struct.items || struct, 'elements', (element) => {
1563
- npbs = npbs.concat(produceNavigationPath(element, struct));
1639
+ forEachGeneric(def.items || def, 'elements', (element) => {
1640
+ npbs = npbs.concat(produceNavigationPath(element, def));
1564
1641
  });
1565
- setProp(struct, '$edmNPBs', npbs);
1642
+ setProp(def, '$edmNPBs', npbs);
1566
1643
  }
1567
1644
 
1568
1645
  // collect all paths originating from this element that end up in an entity set
@@ -1576,7 +1653,11 @@ function initializeModel(csn, _options, messageFunctions)
1576
1653
  // drill into target only if
1577
1654
  // 1) target has no entity set and this assoc is not going to the container
1578
1655
  // 2) current definition and target are the same (cycle)
1579
- if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
1656
+ // 3) it's no external reference
1657
+ if(!elt.$externalRef &&
1658
+ !elt._target.$hasEntitySet &&
1659
+ !elt._isToContainer &&
1660
+ curDef !== elt._target) {
1580
1661
  // follow elements in the target but avoid cycles
1581
1662
  setProp(elt, '$touched', true);
1582
1663
  Object.values(elt._target.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, elt._target)));
@@ -1586,16 +1667,18 @@ function initializeModel(csn, _options, messageFunctions)
1586
1667
  // end point reached but must not be an external reference nor a proxy nor a composition itself
1587
1668
  // last assoc step must not be to-n and target a singleton
1588
1669
  let p = undefined;
1589
- if (!elt._target.$externalRef &&
1590
- !(edmUtils.isToMany(elt) && edmUtils.isSingleton(elt._target) && options.isV4())) {
1670
+ if (!elt.$externalRef &&
1671
+ !(edmUtils.isToMany(elt) &&
1672
+ edmUtils.isSingleton(elt._target) &&
1673
+ options.isV4())) {
1591
1674
  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];
1675
+ p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(def.name)) || elt._target.$edmTgtPaths[0];
1593
1676
  }
1594
1677
  else if(elt._target.$hasEntitySet) {
1595
1678
  const baseName = edmUtils.getBaseName(elt._target.$entitySetName || elt._target.name);
1596
1679
  // if own struct and target have a set they either are in the same $mySchemaName or not
1597
1680
  // if target is in another schema, target the full qualified entity set
1598
- p = (elt._target.$mySchemaName === struct.$mySchemaName) ?
1681
+ p = (elt._target.$mySchemaName === def.$mySchemaName) ?
1599
1682
  [ baseName ] : [elt._target.$mySchemaName + '.EntityContainer', baseName];
1600
1683
  }
1601
1684
  if(p) {
@@ -1625,28 +1708,28 @@ function initializeModel(csn, _options, messageFunctions)
1625
1708
  }
1626
1709
  }
1627
1710
 
1628
- function determineEntitySet(struct) {
1711
+ function determineEntitySet(def) {
1629
1712
  // if this is an entity or a view, determine if an entity set is required or not
1630
1713
  // 1) must not be a proxy and not a containee in V4
1631
1714
  // 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);
1715
+ if(def.$hasEntitySet === undefined) {
1716
+ const hasEntitySet = isEntity(def) && !(options.isV4() && edmUtils.isContainee(def)) && !def.$proxy;
1717
+ setProp(def, '$hasEntitySet', hasEntitySet);
1635
1718
  }
1636
1719
  }
1637
1720
 
1638
- function initializeEdmTypesAndDescription(artifact) {
1721
+ function initEdmTypesAndDescription(def) {
1639
1722
  // 1. let all doc props become @Core.Descriptions
1640
1723
  // 2. mark a member that will become a collection
1641
1724
  // 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 => {
1725
+ assignAnnotation(def, '@Core.Description', def.doc);
1726
+ markCollection(def);
1727
+ mapCdsToEdmProp(def);
1728
+ if (def.returns) {
1729
+ markCollection(def.returns);
1730
+ mapCdsToEdmProp(def.returns);
1731
+ }
1732
+ forEachMemberRecursively(def,member => {
1650
1733
  assignAnnotation(member, '@Core.Description', member.doc);
1651
1734
  markCollection(member);
1652
1735
  mapCdsToEdmProp(member);
@@ -1656,7 +1739,6 @@ function initializeModel(csn, _options, messageFunctions)
1656
1739
  mapCdsToEdmProp(member.returns);
1657
1740
  }
1658
1741
  });
1659
-
1660
1742
  // mark members that need to be rendered as collections
1661
1743
  function markCollection(obj) {
1662
1744
  const items = obj.items || csn.definitions[obj.type] && csn.definitions[obj.type].items;
@@ -1673,19 +1755,37 @@ function initializeModel(csn, _options, messageFunctions)
1673
1755
  // Checks section starts here
1674
1756
  //
1675
1757
 
1758
+ // eslint-disable-next-line no-unused-vars
1759
+ function resolveForeignKeyRefs() {
1760
+ forEachDefinition(csn, (def, defName) => {
1761
+ let currPath = ['definitions', defName ];
1762
+ forEachMemberRecursively(def, (construct, _constructName, _prop, path) => {
1763
+ if(construct.target && construct.keys) {
1764
+ construct.keys.forEach((fk, i) => {
1765
+ setProp(fk, '_artifact', csnUtils.inspectRef([...path, 'keys', i]).art);
1766
+ });
1767
+ }
1768
+ }, currPath, true, { elementsOnly: true });
1769
+ });
1770
+ }
1771
+
1772
+
1676
1773
  function inboundQualificationChecks() {
1677
- forEachDefinition(csn, [ checkChainedArray ]);
1774
+ forEachDefinition(csn, [ attach$path, checkChainedArray ]);
1678
1775
  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.
1776
+ throwWithAnyError();
1777
+
1778
+ // attach $path to all
1779
+ function attach$path(def, defName) {
1780
+ setProp(def, '$path', [ 'definitions', defName ]);
1781
+ forEachMemberRecursively(def,
1782
+ (member, _memberName, _prop, path) => {
1783
+ setProp(member, '$path', path);
1784
+ }, [ 'definitions', defName ]);
1785
+ }
1786
+
1687
1787
  function checkChainedArray(def, defName) {
1688
- if (!whatsMyServiceRootName(defName))
1788
+ if (!isMyServiceRequested(defName))
1689
1789
  return;
1690
1790
  let currPath = ['definitions', defName];
1691
1791
  checkIfItemsOfItems(def, undefined, undefined, currPath);
@@ -1695,13 +1795,13 @@ function initializeModel(csn, _options, messageFunctions)
1695
1795
  const constructType = csnUtils.effectiveType(construct);
1696
1796
  if (constructType.items) {
1697
1797
  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');
1798
+ message('chained-array-of', path);
1699
1799
  return;
1700
1800
  }
1701
1801
 
1702
1802
  const itemsType = csnUtils.effectiveType(constructType.items);
1703
1803
  if (itemsType.items)
1704
- error('chained-array-of', path, '"Array of"/"many" must not be chained with another "array of"/"many" inside a service');
1804
+ message('chained-array-of', path);
1705
1805
  }
1706
1806
  }
1707
1807
  }
@@ -1709,7 +1809,7 @@ function initializeModel(csn, _options, messageFunctions)
1709
1809
  function checkNestedContextsAndServices() {
1710
1810
  !isBetaEnabled(options, 'nestedServices') && serviceRootNames.forEach(sn => {
1711
1811
  const parent = whatsMyServiceRootName(sn, false);
1712
- if(parent && parent !== sn) {
1812
+ if(parent && requestedServiceNames.includes(parent) && parent !== sn) {
1713
1813
  message( 'service-nested-service', [ 'definitions', sn ], { art: parent },
1714
1814
  'A service can\'t be nested within a service $(ART)' );
1715
1815
  }
@@ -1718,7 +1818,7 @@ function initializeModel(csn, _options, messageFunctions)
1718
1818
  Object.entries(csn.definitions).forEach(([fqName, art]) => {
1719
1819
  if(art.kind === 'context') {
1720
1820
  const parent = whatsMyServiceRootName(fqName);
1721
- if(parent) {
1821
+ if(requestedServiceNames.includes(parent)) {
1722
1822
  message( 'service-nested-context', [ 'definitions', fqName ], { art: parent },
1723
1823
  'A context can\'t be nested within a service $(ART)' );
1724
1824
  }
@@ -1727,54 +1827,6 @@ function initializeModel(csn, _options, messageFunctions)
1727
1827
  }
1728
1828
  }
1729
1829
 
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
1830
  //
1779
1831
  // Checks Secition ends here
1780
1832
  //
@@ -1863,7 +1915,29 @@ function initializeModel(csn, _options, messageFunctions)
1863
1915
  }, { });
1864
1916
  // if dictionary has entries, add them to navPropEnty
1865
1917
  if(Object.keys(o).length) {
1866
- navPropEntry[prop] = o;
1918
+ // ReadRestrictions may have sub type ReadByKeyRestrictions { Description, LongDescription }
1919
+ // chop annotations into dictionaries
1920
+ if(prefix === '@Capabilities.ReadRestrictions' &&
1921
+ Object.keys(o).some(k => k.startsWith('ReadByKeyRestrictions.'))) {
1922
+ const no = {};
1923
+ Object.entries(o).forEach(([k,v]) => {
1924
+ const [head, ...tail] = k.split('.');
1925
+ if(head === 'ReadByKeyRestrictions' && tail.length) {
1926
+ if(!no['ReadByKeyRestrictions'])
1927
+ no['ReadByKeyRestrictions'] = {};
1928
+ // Don't try to add entry into non object
1929
+ if(typeof no['ReadByKeyRestrictions'] === 'object')
1930
+ no['ReadByKeyRestrictions'][tail.join('.')] = v;
1931
+ }
1932
+ else {
1933
+ no[k] = v;
1934
+ }
1935
+ });
1936
+ navPropEntry[prop] = no;
1937
+ }
1938
+ else {
1939
+ navPropEntry[prop] = o;
1940
+ }
1867
1941
  newEntry = true;
1868
1942
  }
1869
1943
  }
@@ -1885,21 +1959,17 @@ function initializeModel(csn, _options, messageFunctions)
1885
1959
 
1886
1960
  function mapCdsToEdmProp(obj) {
1887
1961
  if (obj.type && isBuiltinType(obj.type) && !isAssociationOrComposition(obj) && !obj.targetAspect) {
1888
- let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
1962
+ let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
1889
1963
  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']);
1964
+ } else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.type)))) {
1965
+ let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType'], obj.$path);
1892
1966
  assignProp(obj, '_edmType', edmType);
1893
1967
  }
1894
1968
  // 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))) {
1896
- let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
1969
+ else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.items.type))) {
1970
+ let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
1897
1971
  assignProp(obj, '_edmType', edmType);
1898
1972
  }
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
1973
  }
1904
1974
 
1905
1975
  function ComputedDefaultValue(member) {
@@ -2286,4 +2356,5 @@ function assignProp(obj, prop, value) {
2286
2356
 
2287
2357
  module.exports = {
2288
2358
  initializeModel,
2359
+ assignAnnotation
2289
2360
  }