@sap/cds-compiler 2.12.0 → 2.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +221 -15
  2. package/bin/cdsc.js +125 -50
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +47 -84
  8. package/lib/api/options.js +5 -6
  9. package/lib/api/validate.js +6 -11
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +114 -18
  16. package/lib/base/messages.js +101 -90
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +177 -123
  19. package/lib/checks/annotationsOData.js +12 -33
  20. package/lib/checks/arrayOfs.js +1 -34
  21. package/lib/checks/cdsPersistence.js +2 -1
  22. package/lib/checks/enricher.js +17 -1
  23. package/lib/checks/invalidTarget.js +3 -1
  24. package/lib/checks/managedWithoutKeys.js +3 -1
  25. package/lib/checks/selectItems.js +4 -4
  26. package/lib/checks/sql-snippets.js +27 -26
  27. package/lib/checks/types.js +1 -1
  28. package/lib/checks/validator.js +6 -11
  29. package/lib/compiler/assert-consistency.js +6 -3
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +19 -6
  32. package/lib/compiler/checks.js +23 -60
  33. package/lib/compiler/cycle-detector.js +1 -1
  34. package/lib/compiler/define.js +1151 -0
  35. package/lib/compiler/extend.js +1000 -0
  36. package/lib/compiler/finalize-parse-cdl.js +237 -0
  37. package/lib/compiler/index.js +107 -39
  38. package/lib/compiler/kick-start.js +190 -0
  39. package/lib/compiler/moduleLayers.js +4 -4
  40. package/lib/compiler/populate.js +1227 -0
  41. package/lib/compiler/propagator.js +114 -46
  42. package/lib/compiler/resolve.js +1521 -0
  43. package/lib/compiler/shared.js +126 -65
  44. package/lib/compiler/tweak-assocs.js +535 -0
  45. package/lib/compiler/utils.js +197 -33
  46. package/lib/edm/.eslintrc.json +5 -0
  47. package/lib/edm/annotations/genericTranslation.js +38 -24
  48. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  49. package/lib/edm/csn2edm.js +219 -100
  50. package/lib/edm/edm.js +302 -230
  51. package/lib/edm/edmPreprocessor.js +554 -419
  52. package/lib/edm/edmUtils.js +138 -44
  53. package/lib/gen/Dictionary.json +100 -19
  54. package/lib/gen/language.checksum +1 -1
  55. package/lib/gen/language.interp +11 -1
  56. package/lib/gen/language.tokens +86 -83
  57. package/lib/gen/languageLexer.interp +10 -1
  58. package/lib/gen/languageLexer.js +860 -833
  59. package/lib/gen/languageLexer.tokens +78 -75
  60. package/lib/gen/languageParser.js +5765 -4480
  61. package/lib/json/csnVersion.js +10 -11
  62. package/lib/json/from-csn.js +15 -3
  63. package/lib/json/to-csn.js +126 -68
  64. package/lib/language/docCommentParser.js +4 -4
  65. package/lib/language/genericAntlrParser.js +123 -5
  66. package/lib/language/language.g4 +355 -156
  67. package/lib/language/multiLineStringParser.js +5 -5
  68. package/lib/main.d.ts +486 -59
  69. package/lib/main.js +41 -9
  70. package/lib/model/api.js +3 -1
  71. package/lib/model/csnRefs.js +252 -156
  72. package/lib/model/csnUtils.js +384 -297
  73. package/lib/model/enrichCsn.js +71 -29
  74. package/lib/model/revealInternalProperties.js +29 -8
  75. package/lib/model/sortViews.js +2 -1
  76. package/lib/modelCompare/compare.js +23 -18
  77. package/lib/optionProcessor.js +63 -26
  78. package/lib/render/manageConstraints.js +35 -32
  79. package/lib/render/toCdl.js +897 -947
  80. package/lib/render/toHdbcds.js +205 -257
  81. package/lib/render/toSql.js +264 -225
  82. package/lib/render/utils/common.js +136 -25
  83. package/lib/render/utils/sql.js +4 -3
  84. package/lib/render/utils/stringEscapes.js +111 -0
  85. package/lib/sql-identifier.js +1 -1
  86. package/lib/transform/.eslintrc.json +5 -0
  87. package/lib/transform/db/.eslintrc.json +3 -1
  88. package/lib/transform/db/applyTransformations.js +35 -12
  89. package/lib/transform/db/assertUnique.js +1 -1
  90. package/lib/transform/db/associations.js +104 -306
  91. package/lib/transform/db/cdsPersistence.js +2 -2
  92. package/lib/transform/db/constraints.js +58 -53
  93. package/lib/transform/db/expansion.js +60 -33
  94. package/lib/transform/db/flattening.js +582 -104
  95. package/lib/transform/db/groupByOrderBy.js +3 -1
  96. package/lib/transform/db/transformExists.js +66 -13
  97. package/lib/transform/db/views.js +11 -7
  98. package/lib/transform/draft/.eslintrc.json +38 -0
  99. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  100. package/lib/transform/draft/odata.js +227 -0
  101. package/lib/transform/forHanaNew.js +109 -208
  102. package/lib/transform/forOdataNew.js +59 -212
  103. package/lib/transform/localized.js +46 -26
  104. package/lib/transform/odata/toFinalBaseType.js +85 -11
  105. package/lib/transform/odata/typesExposure.js +147 -199
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +44 -33
  108. package/lib/transform/translateAssocsToJoins.js +3 -20
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +172 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/moduleResolve.js +13 -6
  114. package/lib/utils/objectUtils.js +30 -0
  115. package/package.json +1 -1
  116. package/share/messages/README.md +26 -0
  117. package/share/messages/message-explanations.json +2 -1
  118. package/share/messages/syntax-expected-integer.md +37 -0
  119. package/lib/compiler/definer.js +0 -2361
  120. package/lib/compiler/resolver.js +0 -3079
  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 -290
  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
  128. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -7,13 +7,14 @@ const NAVPROP_TRENNER = '_';
7
7
  const VALUELIST_NAVPROP_PREFIX = '';
8
8
 
9
9
  const edmUtils = require('./edmUtils.js')
10
- const { initializeModel } = require('./edmPreprocessor.js');
10
+ const { initializeModel, assignAnnotation } = require('./edmPreprocessor.js');
11
11
  const translate = require('./annotations/genericTranslation.js');
12
12
  const { setProp } = require('../base/model');
13
- const { cloneCsn, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
13
+ const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
14
14
  const { checkCSNVersion } = require('../json/csnVersion');
15
15
  const { makeMessageFunction } = require('../base/messages');
16
-
16
+ const { EdmTypeFacetMap, EdmTypeFacetNames, EdmPrimitiveTypeMap, getEdm } = require('./edm.js');
17
+
17
18
  /*
18
19
  OData V2 spec 06/01/2017 PDF version is available from here:
19
20
  https://msdn.microsoft.com/en-us/library/dd541474.aspx
@@ -25,11 +26,11 @@ function csn2edm(_csn, serviceName, _options) {
25
26
 
26
27
  function csn2edmAll(_csn, _options, serviceNames=undefined) {
27
28
  // get us a fresh model copy that we can work with
28
- const csn = cloneCsn(_csn, _options);
29
+ const csn = cloneCsnNonDict(_csn, _options);
29
30
 
30
31
  // use original options for messages; cloned CSN for semantic location
31
32
  const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
32
- const { info, warning, error, message, throwWithError } = messageFunctions;
33
+ const { info, warning, error, message, throwWithAnyError } = messageFunctions;
33
34
  checkCSNVersion(csn, _options);
34
35
 
35
36
  let rc = Object.create(null);
@@ -42,12 +43,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
42
43
  setProp(csn.meta, 'options', _csn.meta.options);
43
44
  }
44
45
 
45
- let [ allServices,
46
+ const [ allServices,
46
47
  allSchemas,
48
+ reqDefs,
47
49
  whatsMyServiceRootName,
48
- options ] = initializeModel(csn, _options, messageFunctions);
50
+ fallBackSchemaName,
51
+ options ] = initializeModel(csn, _options, messageFunctions, serviceNames);
49
52
 
50
- const Edm = require('./edm.js')(options, error);
53
+ const Edm = getEdm(options, messageFunctions);
51
54
 
52
55
  const v = options.v;
53
56
  if(Object.keys(allServices).length === 0) {
@@ -73,7 +76,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
73
76
  services[serviceCsn.name] = createEdm(serviceCsn);
74
77
  return services; }, rc);
75
78
  }
76
- throwWithError();
79
+ throwWithAnyError();
77
80
  return rc;
78
81
 
79
82
  //--------------------------------------------------------------------------------
@@ -188,9 +191,9 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
188
191
  fqSchemaXRef.sort((a,b) => b.length-a.length);
189
192
 
190
193
  // Bring the schemas in alphabetical order, service first, root last
191
- const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== 'root' && n !== serviceCsn.name).sort();
192
- if(subSchemaDictionary.root)
193
- sortedSchemaNames.push('root');
194
+ const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== fallBackSchemaName && n !== serviceCsn.name).sort();
195
+ if(subSchemaDictionary[fallBackSchemaName])
196
+ sortedSchemaNames.push(fallBackSchemaName);
194
197
 
195
198
  // Finally create the schemas and register them in the service.
196
199
  LeadSchema = createSchema(subSchemaDictionary[serviceCsn.name]);
@@ -220,7 +223,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
220
223
  service._children.forEach(c => {
221
224
  c._ec && Object.entries(c._ec._registry).forEach((([setName, arr]) => {
222
225
  if(arr.length > 1) {
223
- error(null, null, { name: c.Namespace, id: setName },
226
+ error(null, null, { name: c._edmAttributes.Namespace, id: setName },
224
227
  `Namespace $(NAME): Duplicate entries in EntityContainer with Name=$(ID) for ${arr.map(a =>a.getDuplicateMessage()).join(', ')} `);
225
228
  }
226
229
  }));
@@ -231,10 +234,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
231
234
 
232
235
  // Sort definitions into their schema container
233
236
  function populateSchemas(schemas) {
234
- Object.entries(csn.definitions).forEach(([fqName, art]) => {
237
+ Object.entries(reqDefs.definitions).forEach(([fqName, art]) => {
235
238
  // Identify service members by their definition name only, this allows
236
239
  // to let the internal object.name have the sub-schema name.
237
- // With nested services we must do a longest path match and check wether
240
+ // With nested services we must do a longest path match and check whether
238
241
  // the current definition belongs to the current toplevel service definition.
239
242
 
240
243
  // Definition is to be considered if
@@ -246,7 +249,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
246
249
  // not marked to be ignored as schema member
247
250
  if(mySchemaName &&
248
251
  serviceCsn.name === whatsMyServiceRootName(fqName, false) &&
249
- ![ 'context', 'service' ].includes(art.kind)) {
252
+ art.kind !== 'context' && art.kind !== 'service') {
250
253
 
251
254
  // Strip the toplevel serviceName from object.name
252
255
  // except if the schema name is the service name itself.
@@ -300,10 +303,18 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
300
303
 
301
304
  // Same check for alias (if supported by us)
302
305
  const reservedNames = ['Edm', 'odata', 'System', 'Transient'];
303
- if(reservedNames.includes(schema.name)) {
304
- warning('odata-spec-violation-namespace',
305
- [ 'definitions', schema.name ], { names: reservedNames });
306
+ const loc = ['definitions', schema.name];
307
+ if(reservedNames.includes(schema.name))
308
+ warning('odata-spec-violation-namespace', loc, { names: reservedNames });
309
+ if (schema.name.length > 511)
310
+ warning('odata-spec-violation-namespace', loc, { '#': 'length' });
311
+ else {
312
+ schema.name.split('.').forEach(id => {
313
+ if (!edmUtils.isODataSimpleIdentifier(id))
314
+ message('odata-spec-violation-id', loc, { id });
315
+ });
306
316
  }
317
+
307
318
  /** @type {any} */
308
319
  const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);
309
320
  const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);
@@ -342,10 +353,11 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
342
353
 
343
354
  // fetch all exising children names in a map
344
355
  const NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {
345
- if(acc[cur.Name] === undefined) {
346
- acc[cur.Name] = [ cur ];
356
+ const name = cur._edmAttributes.Name;
357
+ if(acc[name] === undefined) {
358
+ acc[name] = [ cur ];
347
359
  } else {
348
- acc[cur.Name].push(cur);
360
+ acc[name].push(cur);
349
361
  }
350
362
  return acc;
351
363
  }, Object.create(null) );
@@ -367,12 +379,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
367
379
  }
368
380
  if(Schema._children.length === 0) {
369
381
  // FIXME: Location for sub schemas?
370
- warning(null, ['definitions', Schema.Namespace], { name: Schema.Namespace }, 'Schema $(NAME) is empty');
382
+ warning(null, ['definitions', Schema._edmAttributes.Namespace], { name: Schema._edmAttributes.Namespace }, 'Schema $(NAME) is empty');
371
383
  }
372
384
 
373
385
  Object.entries(NamesInSchemaXRef).forEach(([name, refs]) => {
374
386
  if(refs.length > 1) {
375
- error(null, ['definitions', `${Schema.Namespace}.${name}`], { name: Schema.Namespace },
387
+ error(null, ['definitions', `${Schema._edmAttributes.Namespace}.${name}`], { name: Schema._edmAttributes.Namespace },
376
388
  'Duplicate name in Schema $(NAME)');
377
389
  }
378
390
  });
@@ -388,21 +400,29 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
388
400
 
389
401
  const loc = ['definitions', entityCsn.name];
390
402
  const type = `${schema.name}.${EntityTypeName}`;
391
- if(properties.length === 0) {
403
+ if(properties.length === 0)
392
404
  warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no properties');
393
- } else if(entityCsn.$edmKeyPaths.length === 0) {
405
+ else if(entityCsn.$edmKeyPaths.length === 0)
394
406
  message('odata-spec-violation-no-key', loc);
395
- }
407
+
408
+ if(!edmUtils.isODataSimpleIdentifier(EntityTypeName))
409
+ message('odata-spec-violation-id', loc, { id: EntityTypeName });
410
+
396
411
  properties.forEach(p => {
397
- const pLoc = [...loc, 'elements', p.Name];
398
- if(!p[p._typeName]) {
399
- message('odata-spec-violation-type', pLoc);
400
- }
401
- if(p.Name === EntityTypeName) {
412
+ const pLoc = [...loc, 'elements', p._edmAttributes.Name];
413
+ edmTypeCompatibilityCheck(p, pLoc);
414
+ if(p._edmAttributes.Name === EntityTypeName)
402
415
  warning('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
403
- }
404
- if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn)) {
405
- warning('odata-spec-violation-array', pLoc, { api: 'OData V2' });
416
+
417
+ if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
418
+ warning('odata-spec-violation-array', pLoc, { version: '2.0' });
419
+
420
+ if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
421
+ message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
422
+ else if (options.isV2() && /^(_|[0-9])/.test(p._edmAttributes.Name)) {
423
+ // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
424
+ error('odata-spec-violation-id', pLoc,
425
+ { prop: p._edmAttributes.Name[0], id: p._edmAttributes.Name, version: '2.0', '#': 'v2firstchar' });
406
426
  }
407
427
  });
408
428
 
@@ -410,8 +430,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
410
430
  const attributes = { Name : EntityTypeName };
411
431
 
412
432
  // CDXCORE-CDXCORE-173
413
- if(options.isV2() && hasStream)
414
- attributes['m:HasStream'] = hasStream;
433
+ if(options.isV2() && hasStream) {
434
+ attributes['m:HasStream'] = true;
435
+ assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
436
+ }
415
437
 
416
438
  Schema.append(new Edm.EntityType(v, attributes, properties, entityCsn));
417
439
 
@@ -423,7 +445,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
423
445
  if(edmUtils.isSingleton(entityCsn) && options.isV4()) {
424
446
  containerEntry = new Edm.Singleton(v, { Name: EntitySetName, Type: fullQualified(EntityTypeName) }, entityCsn);
425
447
  if(entityCsn['@odata.singleton.nullable'])
426
- containerEntry.Nullable= true;
448
+ containerEntry._edmAttributes.Nullable= true;
427
449
  }
428
450
  else {
429
451
  containerEntry = new Edm.EntitySet(v, { Name: EntitySetName, EntityType: fullQualified(EntityTypeName) }, entityCsn);
@@ -446,14 +468,19 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
446
468
  }
447
469
 
448
470
  // add bound/unbound actions/functions for V4
449
- function createActionV4(actionCsn, name, entityCsn=undefined)
471
+ function createActionV4(actionCsn, _name, entityCsn=undefined)
450
472
  {
451
473
  const iAmAnAction = actionCsn.kind === 'action';
452
-
453
474
  const actionName = edmUtils.getBaseName(actionCsn.name);
454
-
455
475
  const attributes = { Name: actionName, IsBound : false };
456
476
 
477
+ const loc = entityCsn
478
+ ? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
479
+ : [ 'definitions', actionCsn.name ];
480
+
481
+ if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
482
+ message('odata-spec-violation-id', loc, { id: attributes.Name });
483
+
457
484
  if(!iAmAnAction)
458
485
  attributes.IsComposable = false;
459
486
 
@@ -467,7 +494,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
467
494
 
468
495
  if(entityCsn != undefined)
469
496
  {
470
- actionNode.IsBound = true;
497
+ actionNode.setEdmAttribute('IsBound', true);
471
498
  const bpType = fullQualified(entityCsn.name);
472
499
  // Binding Parameter: 'in' at first position in sequence, this is decisive!
473
500
  if(actionCsn['@cds.odata.bindingparameter.collection'])
@@ -488,7 +515,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
488
515
  const definition = schemaCsn.definitions[rt];
489
516
  if(definition && definition.kind === 'entity' && !definition.abstract)
490
517
  {
491
- actionImport.EntitySet = edmUtils.getBaseName(rt);
518
+ actionImport.setEdmAttribute('EntitySet', edmUtils.getBaseName(rt));
492
519
  }
493
520
  }
494
521
  EntityContainer.register(actionImport);
@@ -496,15 +523,22 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
496
523
 
497
524
  // Parameter Nodes
498
525
  edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
499
- actionNode.append(new Edm.Parameter(v, { Name: parameterName }, parameterCsn ));
526
+ const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
527
+ const pLoc = [ ...loc, 'params', p._edmAttributes.Name ];
528
+ if(!edmUtils.isODataSimpleIdentifier(parameterName))
529
+ message('odata-spec-violation-id', pLoc, { id: parameterName });
530
+
531
+ edmTypeCompatibilityCheck(p, pLoc);
532
+ actionNode.append(p);
500
533
  });
501
534
 
502
535
  // return type if any
503
536
  if(actionCsn.returns) {
504
537
  actionNode._returnType = new Edm.ReturnType(v, actionCsn.returns);
538
+ edmTypeCompatibilityCheck(actionNode._returnType, [ ...loc, 'returns' ]);
505
539
  // if binding type matches return type add attribute EntitySetPath
506
540
  if(entityCsn != undefined && fullQualified(entityCsn.name) === actionNode._returnType._type) {
507
- actionNode.EntitySetPath = bpName;
541
+ actionNode.setEdmAttribute('EntitySetPath', bpName);
508
542
  }
509
543
  }
510
544
  Schema.addAction(actionNode);
@@ -514,7 +548,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
514
548
  function createActionV2(actionCsn, name, entityCsn=undefined)
515
549
  {
516
550
  /** @type {object} */
517
- const functionImport = new Edm.FunctionImport(v, { Name: name.replace(schemaNamePrefix, '') } );
551
+ const attributes = { Name: name.replace(schemaNamePrefix, '') };
552
+ const functionImport = new Edm.FunctionImport(v, attributes );
518
553
 
519
554
  // inserted now to maintain attribute order with old odata generator...
520
555
  /*
@@ -527,19 +562,25 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
527
562
  in the spec and advised mention it as in V4
528
563
  */
529
564
 
530
- const actLoc = ['definitions', ...(entityCsn ? [entityCsn.name, 'actions', actionCsn.name] : [actionCsn.name])];
565
+ const loc = entityCsn
566
+ ? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
567
+ : [ 'definitions', actionCsn.name ];
568
+
569
+ if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
570
+ message('odata-spec-violation-id', loc, { id: attributes.Name });
571
+
531
572
  const rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
532
573
  if(rt) // add EntitySet attribute only if return type is an entity
533
574
  {
534
575
  const defintion = schemaCsn.definitions[rt];
535
576
  if(defintion && edmUtils.isEntity(defintion))
536
577
  {
537
- functionImport.EntitySet = rt.replace(schemaNamePrefix, '');
578
+ functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));
538
579
  }
539
580
  }
540
581
 
541
582
  if(actionCsn.returns)
542
- functionImport.ReturnType = getReturnType(actionCsn);
583
+ functionImport.setEdmAttribute('ReturnType', getReturnType(actionCsn));
543
584
 
544
585
  if(actionCsn.kind === 'function')
545
586
  functionImport.setXml( {'m:HttpMethod': 'GET' });
@@ -550,7 +591,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
550
591
  {
551
592
  // Make bound function names always unique as per Ralf's recommendation
552
593
  functionImport.setXml( {'sap:action-for': fullQualified(entityCsn.name) } );
553
- functionImport.Name = entityCsn.name.replace(schemaNamePrefix, '') + '_' + functionImport.Name;
594
+ const name = entityCsn.name.replace(schemaNamePrefix, '') + '_' + functionImport._edmAttributes.Name;
595
+ functionImport.setEdmAttribute('Name', name);
554
596
 
555
597
  // Binding Parameter: Primary Keys at first position in sequence, this is decisive!
556
598
  // V2 XML: Nullable=false is set because we reuse the primary key property for the parameter
@@ -572,14 +614,22 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
572
614
  // V2 XML spec does only mention default Nullable=true for Properties not for Parameters so omitting Nullable=true let
573
615
  // the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
574
616
  edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
575
- const paramLoc = [...actLoc, 'params', parameterName];
617
+ const pLoc = [...loc, 'params', parameterName];
576
618
  const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
577
- if(!param._type.startsWith('Edm.') && !edmUtils.isStructuredType(csn.definitions[param._type])) {
578
- warning('odata-spec-violation-param', paramLoc, { api: 'OData V2' });
579
- }
580
- if(param._isCollection) {
581
- warning('odata-spec-violation-array', paramLoc, { api: 'OData V2' });
582
- }
619
+ edmTypeCompatibilityCheck(param, pLoc);
620
+ if(!edmUtils.isODataSimpleIdentifier(parameterName))
621
+ message('odata-spec-violation-id', pLoc, { id: parameterName });
622
+
623
+ // only scalar or structured type in V2 (not entity)
624
+ if(param._type &&
625
+ !param._type.startsWith('Edm.') &&
626
+ csn.definitions[param._type] &&
627
+ !edmUtils.isStructuredType(csn.definitions[param._type]))
628
+ warning('odata-spec-violation-param', pLoc, { version: '2.0' });
629
+
630
+ if(param._isCollection)
631
+ warning('odata-spec-violation-array', pLoc, { version: '2.0' });
632
+
583
633
  functionImport.append(param);
584
634
  });
585
635
 
@@ -589,19 +639,33 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
589
639
  function getReturnType(action)
590
640
  {
591
641
  // it is safe to assume that either type or items.type are set
642
+ const returnsLoc = [ ...loc, 'returns'];
592
643
  const returns = action.returns.items || action.returns;
593
644
  let type = returns.type;
594
- if(type){
595
- if(!isBuiltinType(type) && !['entity', 'view', 'type'].includes(csn.definitions[type].kind)){
596
- const returnsLoc = [ ...actLoc, 'returns'];
597
- warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, api: 'OData V2' });
645
+ if (type) {
646
+ if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
647
+ warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });
598
648
  }
599
- type = edmUtils.mapCdsToEdmType(returns, messageFunctions, options.isV2());
649
+ else if(isBuiltinType(type)) {
650
+ type = edmUtils.mapCdsToEdmType(returns, messageFunctions, true);
651
+ if(type) {
652
+ const td = EdmPrimitiveTypeMap[type];
653
+ if(td && !td.v2) {
654
+ message('odata-spec-violation-type', returnsLoc,
655
+ { type, version: '2.0', '#': 'incompatible' });
656
+ }
657
+ }
658
+ else {
659
+ message('odata-spec-violation-type-unknown', returnsLoc, { type });
660
+ }
661
+ }
662
+ if(action.returns._isCollection)
663
+ type = `Collection(${type})`
664
+ }
665
+ else {
666
+ // type is missing
667
+ message('odata-spec-violation-type', returnsLoc);
600
668
  }
601
-
602
- if(action.returns._isCollection)
603
- type = `Collection(${type})`
604
-
605
669
  return type;
606
670
  }
607
671
  }
@@ -609,14 +673,16 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
609
673
  /**
610
674
  * @param {object} elementsCsn
611
675
  * @param {object} edmParentCsn
612
- * @returns {[object[], boolean]} Returns a [ [ Edm Properties ], boolean hasStream ]:
676
+ * @returns {[object[], any]} Returns a [ [ Edm Properties ], boolean hasStream ]:
613
677
  * array of Edm Properties
614
- * boolean hasStream : true if at least one element has @Core.MediaType assignment
678
+ * hasStream : value of @Core.MediaType assignment
615
679
  */
616
680
  function createProperties(elementsCsn, edmParentCsn=elementsCsn)
617
681
  {
618
682
  const props = [];
619
683
  let hasStream = false;
684
+ const streamProps = [];
685
+
620
686
  edmUtils.forAll(elementsCsn.elements, (elementCsn, elementName) =>
621
687
  {
622
688
  if(elementCsn._edmParentCsn == undefined)
@@ -652,12 +718,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
652
718
  // CDXCORE-CDXCORE-173
653
719
  // V2: filter @Core.MediaType
654
720
  if ( options.isV2() && elementCsn['@Core.MediaType']) {
721
+ hasStream = elementCsn['@Core.MediaType'];
722
+ delete elementCsn['@Core.MediaType'];
655
723
  // CDXCORE-CDXCORE-177:
656
724
  // V2: don't render element but add attribute 'm:HasStream="true' to EntityType
657
725
  // V4: render property type 'Edm.Stream'
658
- hasStream = true;
659
- info(null, ['definitions', elementsCsn.name], { name: elementsCsn.name, id: elementName, anno: '@Core.MediaType' },
660
- '$(NAME): Property $(ID) annotated with $(ANNO) is removed from EDM in OData V2');
726
+ streamProps.push(elementName);
661
727
 
662
728
  } else {
663
729
  props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
@@ -666,6 +732,16 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
666
732
  }
667
733
 
668
734
  });
735
+ if(options.isV2()) {
736
+ if(streamProps.length > 1) {
737
+ error(null, ['definitions', elementsCsn.name], { names: streamProps, version: '2.0', anno: '@Core.MediaType' },
738
+ `Expected only one element to be annotated with $(ANNO) for OData $(VERSION) but found $(NAMES)`);
739
+ }
740
+ else if(streamProps.length === 1) {
741
+ info(null, ['definitions', elementsCsn.name], { id: streamProps[0], version: '2.0', anno: '@Core.MediaType' },
742
+ 'Property $(ID) annotated with $(ANNO) is removed from EDM for OData $(VERSION)');
743
+ }
744
+ }
669
745
  return [ props, hasStream ];
670
746
  }
671
747
 
@@ -680,24 +756,27 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
680
756
  const loc = ['definitions', structuredTypeCsn.name];
681
757
 
682
758
  if(properties.length === 0) {
683
- warning(null, ['definitions', structuredTypeCsn.name], { name: structuredTypeCsn.name },
759
+ warning(null, loc, { name: structuredTypeCsn.name },
684
760
  'EDM ComplexType $(NAME) has no properties');
685
761
  }
762
+ if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
763
+ message('odata-spec-violation-id', loc, { id: attributes.Name });
764
+
686
765
  properties.forEach(p => {
687
- const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p.Name ];
688
- if(!p[p._typeName]) {
689
- message('odata-spec-violation-type', pLoc);
690
- }
691
- if(p.Name === complexType.Name) {
766
+ const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p._edmAttributes.Name ];
767
+ edmTypeCompatibilityCheck(p, pLoc);
768
+ if(p._edmAttributes.Name === complexType._edmAttributes.Name)
692
769
  warning('odata-spec-violation-property-name', pLoc, { kind: structuredTypeCsn.kind });
693
- }
770
+
771
+ if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
772
+ message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
773
+
694
774
  if(options.isV2()) {
695
- if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn)) {
696
- warning('odata-spec-violation-array', pLoc, { api: 'OData V2' });
697
- }
698
- if(edmUtils.isAssociationOrComposition(p._csn)) {
699
- warning('odata-spec-violation-assoc', pLoc, { api: 'OData V2' });
700
- }
775
+ if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
776
+ warning('odata-spec-violation-array', pLoc, { version: '2.0' });
777
+
778
+ if(edmUtils.isAssociationOrComposition(p._csn))
779
+ warning('odata-spec-violation-assoc', pLoc, { version: '2.0' });
701
780
  }
702
781
  });
703
782
 
@@ -711,8 +790,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
711
790
  function createTypeDefinition(typeCsn)
712
791
  {
713
792
  // derived types are already resolved to base types
714
- const props = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
715
- const typeDef = new Edm.TypeDefinition(v, props, typeCsn );
793
+ const attributes = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
794
+ if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
795
+ message('odata-spec-violation-id', ['definitions', typeCsn.name], { id: attributes.Name });
796
+
797
+ const typeDef = new Edm.TypeDefinition(v, attributes, typeCsn );
798
+ edmTypeCompatibilityCheck(typeDef, [ 'definitions', typeCsn.name ]);
716
799
  Schema.append(typeDef);
717
800
  }
718
801
 
@@ -734,7 +817,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
734
817
  {
735
818
  let constraints = navigationProperty._csn._constraints;
736
819
  let parentName = navigationProperty._csn._edmParentCsn.name.replace(schemaNamePrefix, '');
737
- let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
820
+ let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty._edmAttributes.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
738
821
  let assocName = plainAssocName;
739
822
  let i = 1;
740
823
  while(NamesInSchemaXRef[assocName] !== undefined) {
@@ -742,7 +825,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
742
825
  }
743
826
 
744
827
  let fromRole = parentName;
745
- let toRole = navigationProperty.Type.replace(schemaAliasPrefix, ''); // <= navprops type should be prefixed with alias
828
+ let toRole = navigationProperty._edmAttributes.Type.replace(schemaAliasPrefix, ''); // <= navprops type should be prefixed with alias
746
829
 
747
830
  let fromEntityType = fromRole;
748
831
  let toEntityType = toRole;
@@ -762,17 +845,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
762
845
  }
763
846
 
764
847
  // add V2 attributes to navigationProperty
765
- navigationProperty.Relationship = fullQualified(assocName);
766
- navigationProperty.FromRole = fromRole;
767
- navigationProperty.ToRole = toRole;
848
+ navigationProperty.setEdmAttribute('Relationship', fullQualified(assocName));
849
+ navigationProperty.setEdmAttribute('FromRole', fromRole);
850
+ navigationProperty.setEdmAttribute('ToRole', toRole);
768
851
 
769
852
  // remove V4 attributes
770
- if(navigationProperty.Type != undefined)
771
- delete navigationProperty.Type;
772
- if(navigationProperty.Partner != undefined)
773
- delete navigationProperty.Partner;
774
- if(navigationProperty.ContainsTarget != undefined)
775
- delete navigationProperty.ContainsTarget;
853
+ navigationProperty.removeEdmAttribute('Type');
854
+ navigationProperty.removeEdmAttribute('Partner');
855
+ navigationProperty.removeEdmAttribute('ContainsTarget');
776
856
 
777
857
  /*
778
858
  If NavigationProperty is a backlink association (constraints._originAssocCsn is set), then there are two options:
@@ -798,7 +878,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
798
878
  assocName = plainAssocName + '_' + i++;
799
879
  }
800
880
 
801
- navigationProperty.Relationship = fullQualified(assocName)
881
+ navigationProperty.setEdmAttribute('Relationship', fullQualified(assocName));
802
882
 
803
883
  reuseAssoc = !!forwardAssocCsn._NavigationProperty;
804
884
  constraints = forwardAssocCsn._constraints;
@@ -857,13 +937,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
857
937
  // distribute edm:Annotations into the schemas
858
938
  // Distribute each anno into Schema
859
939
  annos.forEach(anno => {
860
- let targetSchema = whatsMySchemaName(anno.Target);
940
+ let targetSchema = whatsMySchemaName(anno._edmAttributes.Target);
861
941
  // if no target schema has been found, it's a service annotation that applies to the service schema
862
942
  if(targetSchema === undefined)
863
943
  targetSchema = serviceCsn.name;
864
944
  if(targetSchema) {
865
945
  if(targetSchema !== serviceCsn.name) {
866
- anno.Target = anno.Target.replace(serviceCsn.name + '.', '');
946
+ const newTarget = anno._edmAttributes.Target.replace(serviceCsn.name + '.', '');
947
+ anno.setEdmAttribute('Target', newTarget);
867
948
  }
868
949
  edm._service._schemas[targetSchema]._annotations.push(anno);
869
950
  }
@@ -876,6 +957,44 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
876
957
  edm._defaultRefs.push(r);
877
958
  })
878
959
  }
960
+
961
+ function edmTypeCompatibilityCheck(p, pLoc) {
962
+ const edmType = p._type;
963
+ if(!edmType) {
964
+ message('odata-spec-violation-type', pLoc);
965
+ }
966
+ else if(p._scalarType) {
967
+ const td = EdmPrimitiveTypeMap[edmType];
968
+ if(td) {
969
+ // The renderer/type mapper doesn't/shouldn't produce incompatible types and facets.
970
+ // Only the unknown type warning may be triggered by an unknown @odata.Type override.
971
+ if(td.v2 !== p.v2 && td.v4 !== p.v4)
972
+ message('odata-spec-violation-type', pLoc,
973
+ { type:edmType, version: (p.v4 ? '4.0' : '2.0'), '#': 'incompatible' });
974
+ EdmTypeFacetNames.forEach(name => {
975
+ const facet = EdmTypeFacetMap[name];
976
+ const optional =
977
+ (facet.optional !== undefined)
978
+ ? (Array.isArray(facet.optional)
979
+ ? facet.optional.includes(edmType)
980
+ : facet.optional)
981
+ : false;
982
+
983
+ // facet is not in attributes
984
+ // facet is member of type definition and mandatory
985
+ // node and facet version match
986
+ if(!p._edmAttributes[name] && td[name] && !optional && (p.v2 === facet.v2 || p.v4 === facet.v4)) {
987
+ message('odata-spec-violation-type', pLoc,
988
+ { type:edmType, name, version: (p.v4 ? '4.0' : '2.0'), '#': 'facet' });
989
+ }
990
+ });
991
+ }
992
+ else {
993
+ message('odata-spec-violation-type-unknown', pLoc,
994
+ { type:edmType });
995
+ }
996
+ }
997
+ }
879
998
  }
880
999
  }
881
1000
  module.exports = { csn2edm, csn2edmAll };