@sap/cds-compiler 2.4.4 → 2.10.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 (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. package/lib/utils/string.js +0 -17
@@ -3,14 +3,14 @@
3
3
  /* eslint max-lines:off */
4
4
  /* eslint max-statements-per-line:off */
5
5
 
6
- let NAVPROP_TRENNER = '_';
7
- let VALUELIST_NAVPROP_PREFIX = '';
6
+ const NAVPROP_TRENNER = '_';
7
+ const VALUELIST_NAVPROP_PREFIX = '';
8
8
 
9
9
  const edmUtils = require('./edmUtils.js')
10
10
  const { initializeModel } = require('./edmPreprocessor.js');
11
11
  const translate = require('./annotations/genericTranslation.js');
12
12
  const { setProp } = require('../base/model');
13
- const { cloneCsn, isEdmPropertyRendered } = require('../model/csnUtils');
13
+ const { cloneCsn, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
14
14
  const { checkCSNVersion } = require('../json/csnVersion');
15
15
  const { makeMessageFunction } = require('../base/messages');
16
16
 
@@ -25,11 +25,11 @@ function csn2edm(_csn, serviceName, _options) {
25
25
 
26
26
  function csn2edmAll(_csn, _options, serviceNames=undefined) {
27
27
  // get us a fresh model copy that we can work with
28
- let csn = cloneCsn(_csn, _options);
28
+ const csn = cloneCsn(_csn, _options);
29
29
 
30
30
  // use original options for messages; cloned CSN for semantic location
31
31
  const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
32
- const { info, warning, error, throwWithError } = messageFunctions;
32
+ const { info, warning, error, message, throwWithError } = messageFunctions;
33
33
  checkCSNVersion(csn, _options);
34
34
 
35
35
  let rc = Object.create(null);
@@ -42,10 +42,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
42
42
  setProp(csn.meta, 'options', _csn.meta.options);
43
43
  }
44
44
 
45
- let [ allServices, allSchemas, whatsMyServiceRootName, options ] = initializeModel(csn, _options);
45
+ let [ allServices,
46
+ allSchemas,
47
+ whatsMyServiceRootName,
48
+ options ] = initializeModel(csn, _options, messageFunctions);
49
+
46
50
  const Edm = require('./edm.js')(options, error);
47
51
 
48
- let v = options.v;
52
+ const v = options.v;
49
53
  if(Object.keys(allServices).length === 0) {
50
54
  info(null, null, `No Services in model`);
51
55
  return rc;
@@ -138,7 +142,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
138
142
  definitions: Object.create(null)
139
143
  }
140
144
  };
141
-
145
+
142
146
  if(options.isV4()) {
143
147
  // tunnel schema xref and servicename in options to edm.Typebase to rectify
144
148
  // type references that are eventually also prefixed with the service schema name.
@@ -222,7 +226,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
222
226
  }));
223
227
  });
224
228
  // Create annotations and distribute into Schemas
225
- translate.csn2annotationEdm(csn, edm, serviceCsn.name, options);
229
+ addAnnotations();
226
230
  return edm
227
231
 
228
232
  // Sort definitions into their schema container
@@ -282,8 +286,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
282
286
 
283
287
  return Object.entries(allSchemas).reduce((references, [fqName, art]) => {
284
288
  // add references
285
- let mySchemaName = whatsMySchemaName(fqName);
286
- if(art.kind === 'reference' && mySchemaName && serviceCsn.name === whatsMyServiceRootName(fqName, false)) {
289
+ if(art.kind === 'reference' && whatsMySchemaName(fqName) && serviceCsn.name === whatsMyServiceRootName(fqName, false)) {
287
290
  fqSchemaXRef.push(fqName);
288
291
  references.push(art);
289
292
  }
@@ -298,8 +301,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
298
301
  // Same check for alias (if supported by us)
299
302
  const reservedNames = ['Edm', 'odata', 'System', 'Transient'];
300
303
  if(reservedNames.includes(schema.name)) {
301
- warning('odata-spec-violation-namespace-name', [ 'definitions', schema.name ], { name: schema.name, names: reservedNames },
302
- 'The EDM namespace $(NAME) must not be one of the reserved values $(NAMES)');
304
+ warning('odata-spec-violation-namespace',
305
+ [ 'definitions', schema.name ], { names: reservedNames });
303
306
  }
304
307
  const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);
305
308
  const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);
@@ -307,7 +310,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
307
310
  const schemaNamePrefix = schema.name + '.'
308
311
  const schemaAliasPrefix = schemaNamePrefix;
309
312
  const schemaCsn = schema;
310
- let navigationProperties = [];
313
+ const navigationProperties = [];
311
314
 
312
315
  /* create the entitytypes and sets
313
316
  Do not create an entity set if:
@@ -337,7 +340,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
337
340
  }
338
341
 
339
342
  // fetch all exising children names in a map
340
- let NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {
343
+ const NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {
341
344
  if(acc[cur.Name] === undefined) {
342
345
  acc[cur.Name] = [ cur ];
343
346
  } else {
@@ -359,8 +362,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
359
362
 
360
363
  // remove EntityContainer if empty
361
364
  if(Schema._ec && Schema._ec._children.length === 0) {
362
- let pos = Schema._children.indexOf(Schema._ec);
363
- Schema._children.splice(pos, 1);
365
+ Schema._children.splice(Schema._children.indexOf(Schema._ec), 1);
364
366
  }
365
367
  if(Schema._children.length === 0) {
366
368
  // FIXME: Location for sub schemas?
@@ -369,8 +371,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
369
371
 
370
372
  Object.entries(NamesInSchemaXRef).forEach(([name, refs]) => {
371
373
  if(refs.length > 1) {
372
- let artifactName = `${Schema.Namespace}.${name}`;
373
- error(null, ['definitions', artifactName], { name: Schema.Namespace },
374
+ error(null, ['definitions', `${Schema.Namespace}.${name}`], { name: Schema.Namespace },
374
375
  'Duplicate name in Schema $(NAME)');
375
376
  }
376
377
  });
@@ -379,10 +380,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
379
380
 
380
381
  function createEntityTypeAndSet(entityCsn)
381
382
  {
382
- let EntityTypeName = entityCsn.name.replace(schemaNamePrefix, '');
383
- let EntitySetName = edmUtils.getBaseName(entityCsn.$entitySetName || entityCsn.name);
383
+ const EntityTypeName = entityCsn.name.replace(schemaNamePrefix, '');
384
+ const EntitySetName = edmUtils.getBaseName(entityCsn.$entitySetName || entityCsn.name);
384
385
 
385
- let [ properties, hasStream ] = createProperties(entityCsn);
386
+ const [ properties, hasStream ] = createProperties(entityCsn);
386
387
 
387
388
  const loc = ['definitions', entityCsn.name];
388
389
  const type = `${schema.name}.${EntityTypeName}`;
@@ -392,16 +393,20 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
392
393
  warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no primary key');
393
394
  }
394
395
  properties.forEach(p => {
396
+ const pLoc = [...loc, 'elements', p.Name];
395
397
  if(!p[p._typeName]) {
396
- warning('odata-spec-violation-attribute', loc, { name: p.Name, prop: p._typeName });
398
+ message('odata-spec-violation-type', pLoc);
397
399
  }
398
400
  if(p.Name === EntityTypeName) {
399
- warning('odata-spec-violation-property-name', loc, { name: p.Name, type: 'EntityType' });
401
+ warning('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
402
+ }
403
+ if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn)) {
404
+ warning('odata-spec-violation-array', pLoc, { api: 'OData V2' });
400
405
  }
401
406
  });
402
407
 
403
408
  // construct EntityType attributes
404
- let attributes = { Name : EntityTypeName };
409
+ const attributes = { Name : EntityTypeName };
405
410
 
406
411
  // CDXCORE-CDXCORE-173
407
412
  if(options.isV2() && hasStream)
@@ -413,11 +418,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
413
418
  {
414
419
  /** @type {object} */
415
420
  let containerEntry;
416
- let singleton = entityCsn['@odata.singleton'];
417
- let hasNullable = entityCsn['@odata.singleton.nullable'] !== undefined &&
418
- entityCsn['@odata.singleton.nullable'] !== null;
419
421
 
420
- if(singleton || ((singleton === undefined || singleton === null) && hasNullable)) {
422
+ if(edmUtils.isSingleton(entityCsn, options.isV4())) {
421
423
  containerEntry = new Edm.Singleton(v, { Name: EntitySetName, Type: fullQualified(EntityTypeName) }, entityCsn);
422
424
  if(entityCsn['@odata.singleton.nullable'])
423
425
  containerEntry.Nullable= true;
@@ -427,15 +429,11 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
427
429
  }
428
430
 
429
431
  // V4: Create NavigationPropertyBinding in EntitySet
430
- // if NavigationProperty is not a Containment and if the target is not a containee
431
- if(options.isV4())
432
- properties.filter(np =>
433
- np instanceof Edm.NavigationProperty &&
434
- // @ts-ignore TypeScript does not recognize these properties on type NavigationProperty
435
- !np.isContainment() && !edmUtils.isContainee(np._targetCsn) && !np._targetCsn.$proxy && !np._targetCsn.$externalRef
436
- ). forEach(np =>
437
- containerEntry.append(np.createNavigationPropertyBinding(schemaNamePrefix)));
438
-
432
+ if(options.isV4()) {
433
+ entityCsn.$edmNPBs.forEach(npb => {
434
+ containerEntry.append(new Edm.NavigationPropertyBinding(v, npb))
435
+ });
436
+ }
439
437
  EntityContainer.register(containerEntry);
440
438
  }
441
439
 
@@ -449,27 +447,27 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
449
447
  // add bound/unbound actions/functions for V4
450
448
  function createActionV4(actionCsn, name, entityCsn=undefined)
451
449
  {
452
- let iAmAnAction = actionCsn.kind === 'action';
450
+ const iAmAnAction = actionCsn.kind === 'action';
453
451
 
454
- let actionName = edmUtils.getBaseName(actionCsn.name);
452
+ const actionName = edmUtils.getBaseName(actionCsn.name);
455
453
 
456
- let attributes = { Name: actionName, IsBound : false };
454
+ const attributes = { Name: actionName, IsBound : false };
457
455
 
458
456
  if(!iAmAnAction)
459
457
  attributes.IsComposable = false;
460
458
 
461
459
  /** @type {object} */
462
- let actionNode = (iAmAnAction) ? new Edm.Action(v, attributes)
460
+ const actionNode = (iAmAnAction) ? new Edm.Action(v, attributes)
463
461
  : new Edm.FunctionDefinition(v, attributes);
464
462
 
465
463
  // bpName is eventually used later for EntitySetPath
466
- let bpNameAnno = actionCsn['@cds.odata.bindingparameter.name'];
467
- let bpName = bpNameAnno !== undefined ? (bpNameAnno['='] || bpNameAnno) : 'in';
464
+ const bpNameAnno = actionCsn['@cds.odata.bindingparameter.name'];
465
+ const bpName = bpNameAnno !== undefined ? (bpNameAnno['='] || bpNameAnno) : 'in';
468
466
 
469
467
  if(entityCsn != undefined)
470
468
  {
471
469
  actionNode.IsBound = true;
472
- let bpType = fullQualified(entityCsn.name);
470
+ const bpType = fullQualified(entityCsn.name);
473
471
  // Binding Parameter: 'in' at first position in sequence, this is decisive!
474
472
  if(actionCsn['@cds.odata.bindingparameter.collection'])
475
473
  actionNode.append(new Edm.Parameter(v, { Name: bpName, Type: bpType, Collection:true } ));
@@ -479,14 +477,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
479
477
  else if(EntityContainer)// unbound => produce Action/FunctionImport
480
478
  {
481
479
  /** @type {object} */
482
- let actionImport = iAmAnAction
480
+ const actionImport = iAmAnAction
483
481
  ? new Edm.ActionImport(v, { Name: actionName, Action : fullQualified(actionName) })
484
482
  : new Edm.FunctionImport(v, { Name: actionName, Function : fullQualified(actionName) });
485
483
 
486
- let rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
484
+ const rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
487
485
  if(rt) // add EntitySet attribute only if return type is a non abstract entity
488
486
  {
489
- let definition = schemaCsn.definitions[rt];
487
+ const definition = schemaCsn.definitions[rt];
490
488
  if(definition && definition.kind === 'entity' && !definition.abstract)
491
489
  {
492
490
  actionImport.EntitySet = edmUtils.getBaseName(rt);
@@ -515,7 +513,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
515
513
  function createActionV2(actionCsn, name, entityCsn=undefined)
516
514
  {
517
515
  /** @type {object} */
518
- let functionImport = new Edm.FunctionImport(v, { Name: name.replace(schemaNamePrefix, '') } );
516
+ const functionImport = new Edm.FunctionImport(v, { Name: name.replace(schemaNamePrefix, '') } );
519
517
 
520
518
  // inserted now to maintain attribute order with old odata generator...
521
519
  /*
@@ -528,10 +526,11 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
528
526
  in the spec and advised mention it as in V4
529
527
  */
530
528
 
531
- let rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
529
+ const actLoc = ['definitions', ...(entityCsn ? [entityCsn.name, 'actions', actionCsn.name] : [actionCsn.name])];
530
+ const rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
532
531
  if(rt) // add EntitySet attribute only if return type is an entity
533
532
  {
534
- let defintion = schemaCsn.definitions[rt];
533
+ const defintion = schemaCsn.definitions[rt];
535
534
  if(defintion && edmUtils.isEntity(defintion))
536
535
  {
537
536
  functionImport.EntitySet = rt.replace(schemaNamePrefix, '');
@@ -572,7 +571,15 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
572
571
  // V2 XML spec does only mention default Nullable=true for Properties not for Parameters so omitting Nullable=true let
573
572
  // the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
574
573
  edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
575
- functionImport.append(new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' ));
574
+ const paramLoc = [...actLoc, 'params', parameterName];
575
+ const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
576
+ if(!param._type.startsWith('Edm.') && !edmUtils.isStructuredType(csn.definitions[param._type])) {
577
+ warning('odata-spec-violation-param', paramLoc, { api: 'OData V2' });
578
+ }
579
+ if(param._isCollection) {
580
+ warning('odata-spec-violation-array', paramLoc, { api: 'OData V2' });
581
+ }
582
+ functionImport.append(param);
576
583
  });
577
584
 
578
585
  if(EntityContainer)
@@ -581,10 +588,15 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
581
588
  function getReturnType(action)
582
589
  {
583
590
  // it is safe to assume that either type or items.type are set
584
- let returns = action.returns.items || action.returns;
591
+ const returns = action.returns.items || action.returns;
585
592
  let type = returns.type;
586
- if(type)
593
+ if(type){
594
+ if(!isBuiltinType(type) && !['entity', 'view', 'type'].includes(csn.definitions[type].kind)){
595
+ const returnsLoc = [ ...actLoc, 'returns'];
596
+ warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, api: 'OData V2' });
597
+ }
587
598
  type = edmUtils.mapCdsToEdmType(returns, messageFunctions, options.isV2());
599
+ }
588
600
 
589
601
  if(action.returns._isCollection)
590
602
  type = `Collection(${type})`
@@ -594,19 +606,20 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
594
606
  }
595
607
 
596
608
  /**
597
- * @param {object} parentCsn
609
+ * @param {object} elementsCsn
610
+ * @param {object} edmParentCsn
598
611
  * @returns {[object[], boolean]} Returns a [ [ Edm Properties ], boolean hasStream ]:
599
612
  * array of Edm Properties
600
613
  * boolean hasStream : true if at least one element has @Core.MediaType assignment
601
614
  */
602
- function createProperties(parentCsn)
615
+ function createProperties(elementsCsn, edmParentCsn=elementsCsn)
603
616
  {
604
- let props = [];
617
+ const props = [];
605
618
  let hasStream = false;
606
- edmUtils.forAll(parentCsn.elements, (elementCsn, elementName) =>
619
+ edmUtils.forAll(elementsCsn.elements, (elementCsn, elementName) =>
607
620
  {
608
- if(elementCsn._parent == undefined)
609
- setProp(elementCsn, '_parent', parentCsn);
621
+ if(elementCsn._edmParentCsn == undefined)
622
+ setProp(elementCsn, '_edmParentCsn', edmParentCsn);
610
623
 
611
624
  if(!elementCsn._ignore) {
612
625
  if(edmUtils.isAssociationOrComposition(elementCsn))
@@ -620,11 +633,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
620
633
  // (undefined !== false) still evaluates to true
621
634
  if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false)
622
635
  {
623
- let navProp = new Edm.NavigationProperty(v, {
636
+ const navProp = new Edm.NavigationProperty(v, {
624
637
  Name: elementName,
625
638
  Type: elementCsn._target.name
626
639
  }, elementCsn);
627
-
628
640
  props.push(navProp);
629
641
  // save the navProp in the global array for late constraint building
630
642
  navigationProperties.push(navProp);
@@ -643,7 +655,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
643
655
  // V2: don't render element but add attribute 'm:HasStream="true' to EntityType
644
656
  // V4: render property type 'Edm.Stream'
645
657
  hasStream = true;
646
- info(null, ['definitions', parentCsn.name], { name: parentCsn.name, id: elementName, anno: '@Core.MediaType' },
658
+ info(null, ['definitions', elementsCsn.name], { name: elementsCsn.name, id: elementName, anno: '@Core.MediaType' },
647
659
  '$(NAME): Property $(ID) annotated with $(ANNO) is removed from EDM in OData V2');
648
660
 
649
661
  } else {
@@ -659,22 +671,32 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
659
671
  function createComplexType(structuredTypeCsn)
660
672
  {
661
673
  // V4 attributes: Name, BaseType, Abstract, OpenType
662
- let attributes = { Name: structuredTypeCsn.name.replace(schemaNamePrefix, '') };
674
+ const attributes = { Name: structuredTypeCsn.name.replace(schemaNamePrefix, '') };
663
675
 
664
- let complexType = new Edm.ComplexType(v, attributes, structuredTypeCsn);
665
- let elementsCsn = structuredTypeCsn.items || structuredTypeCsn;
666
- let properties = createProperties(elementsCsn)[0];
676
+ const complexType = new Edm.ComplexType(v, attributes, structuredTypeCsn);
677
+ const elementsCsn = structuredTypeCsn.items || structuredTypeCsn;
678
+ const properties = createProperties(elementsCsn, structuredTypeCsn)[0];
679
+ const loc = ['definitions', structuredTypeCsn.name];
667
680
 
668
681
  if(properties.length === 0) {
669
682
  warning(null, ['definitions', structuredTypeCsn.name], { name: structuredTypeCsn.name },
670
683
  'EDM ComplexType $(NAME) has no properties');
671
684
  }
672
685
  properties.forEach(p => {
686
+ const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p.Name ];
673
687
  if(!p[p._typeName]) {
674
- warning('odata-spec-violation-attribute', ['definitions', structuredTypeCsn.name], { name: p.Name, prop: p._typeName });
688
+ message('odata-spec-violation-type', pLoc);
675
689
  }
676
690
  if(p.Name === complexType.Name) {
677
- warning('odata-spec-violation-property-name', ['definitions', structuredTypeCsn.name], { name: p.Name, type: complexType.kind });
691
+ warning('odata-spec-violation-property-name', pLoc, { kind: structuredTypeCsn.kind });
692
+ }
693
+ if(options.isV2()) {
694
+ if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn)) {
695
+ warning('odata-spec-violation-array', pLoc, { api: 'OData V2' });
696
+ }
697
+ if(edmUtils.isAssociationOrComposition(p._csn)) {
698
+ warning('odata-spec-violation-assoc', pLoc, { api: 'OData V2' });
699
+ }
678
700
  }
679
701
  });
680
702
 
@@ -688,7 +710,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
688
710
  function createTypeDefinition(typeCsn)
689
711
  {
690
712
  // derived types are already resolved to base types
691
- let props = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
713
+ const props = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
692
714
  const typeDef = new Edm.TypeDefinition(v, props, typeCsn );
693
715
  Schema.append(typeDef);
694
716
  }
@@ -710,7 +732,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
710
732
  function addAssociation(navigationProperty)
711
733
  {
712
734
  let constraints = navigationProperty._csn._constraints;
713
- let parentName = navigationProperty._csn._parent.name.replace(schemaNamePrefix, '');
735
+ let parentName = navigationProperty._csn._edmParentCsn.name.replace(schemaNamePrefix, '');
714
736
  let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
715
737
  let assocName = plainAssocName;
716
738
  let i = 1;
@@ -726,7 +748,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
726
748
 
727
749
  // The entity set name may not be the same as the type name (parameterized entities have
728
750
  // differing set names (<T>Parameters => <T>, <T>Type => <T>Set)
729
- let fromEntitySet = ( navigationProperty._csn._parent.$entitySetName || fromEntityType).replace(schemaNamePrefix, '');
751
+ let fromEntitySet = ( navigationProperty._csn._edmParentCsn.$entitySetName || fromEntityType).replace(schemaNamePrefix, '');
730
752
  let toEntitySet = (navigationProperty._targetCsn.$entitySetName || toEntityType).replace(schemaNamePrefix, '');
731
753
 
732
754
  // from and to roles must be distinguishable (in case of self association entity E { toE: association to E; ... })
@@ -768,7 +790,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
768
790
  [ fromEntityType, toEntityType ] = [ toEntityType, fromEntityType ];
769
791
  [ fromEntitySet, toEntitySet ] = [ toEntitySet, fromEntitySet ];
770
792
 
771
- parentName = forwardAssocCsn._parent.name.replace(schemaNamePrefix, '');
793
+ parentName = forwardAssocCsn._edmParentCsn.name.replace(schemaNamePrefix, '');
772
794
  assocName = plainAssocName = parentName + NAVPROP_TRENNER + forwardAssocCsn.name.replace(VALUELIST_NAVPROP_PREFIX, '');
773
795
  i = 1;
774
796
  while(NamesInSchemaXRef[assocName] !== undefined && !(NamesInSchemaXRef[assocName][0] instanceof Edm.Association)) {
@@ -783,56 +805,37 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
783
805
  }
784
806
 
785
807
  if(reuseAssoc)
786
- {
787
- // Example:
788
- // entity E { key id: Integer; toF: association to F; };
789
- // entity F { key id: Integer; toE: composition of E on toE.toF = $self; };
790
- //
791
- // Consider we're in NavigationProperty 'toE' which is the backlink to F.
792
- // Then forwardAssocCsn is 'E_toF' with two Ends: E, F.
793
- // Backlink F.toE is a composition, making E existentially dependant on F.
794
- // So End E of Association E_toF (which is End[0]) receives Edm.OnDelete.
795
- // Depending on the order of the navigation properties it might be that the
796
- // forward Edm.Association has not yet been produced. In this case Edm.OnDelete
797
- // is parked at the forward NavigationProperty.
798
-
799
- if(!forwardAssocCsn._NavigationProperty._edmAssociation && navigationProperty._csn.type === 'cds.Composition')
800
- {
801
- // TODO: to be specified via @sap.on.delete
802
- forwardAssocCsn._NavigationProperty.set( { _OnDelete: new Edm.OnDelete(v, { Action: 'Cascade' }) } );
803
- }
804
808
  return;
805
- }
806
809
 
807
810
  // Create Association and AssociationSet if this is not a backlink association.
808
811
  // Store association at navigation property because in case the Ends must be modified
809
812
  // later by the partner (backlink) association
810
- navigationProperty._edmAssociation = new Edm.Association(v, { Name: assocName }, navigationProperty,
813
+ const edmAssociation = new Edm.Association(v, { Name: assocName }, navigationProperty,
811
814
  [ fromRole, fullQualified(fromEntityType) ],
812
815
  [ toRole, fullQualified(toEntityType) ],
813
816
  constraints._multiplicity );
814
817
  if(NamesInSchemaXRef[assocName] === undefined) {
815
- NamesInSchemaXRef[assocName] = [ navigationProperty._edmAssociation ];
818
+ NamesInSchemaXRef[assocName] = [ edmAssociation ];
816
819
  }
817
820
  else {
818
- navigationProperty._edmAssociation.push(navigationProperty._edmAssociation);
821
+ NamesInSchemaXRef[assocName].push(edmAssociation);
819
822
  }
820
823
  // Add ReferentialConstraints if any
821
824
  if(!navigationProperty._isCollection && Object.keys(constraints.constraints).length > 0) {
822
825
  // A managed composition is treated as association
823
826
  if(navigationProperty._csn.type === 'cds.Composition' && navigationProperty._csn.on) {
824
- navigationProperty._edmAssociation.append(Edm.ReferentialConstraint.createV2(v,
827
+ edmAssociation.append(Edm.ReferentialConstraint.createV2(v,
825
828
  toRole, fromRole, constraints.constraints));
826
829
  }
827
830
  else {
828
- navigationProperty._edmAssociation.append(Edm.ReferentialConstraint.createV2(v,
831
+ edmAssociation.append(Edm.ReferentialConstraint.createV2(v,
829
832
  fromRole, toRole, constraints.constraints));
830
833
  }
831
834
  }
832
835
 
833
- Schema.append(navigationProperty._edmAssociation);
836
+ Schema.append(edmAssociation);
834
837
  if(EntityContainer && !navigationProperty._targetCsn.$proxy) {
835
- let assocSet = new Edm.AssociationSet(v, { Name: assocName, Association: fullQualified(assocName) },
838
+ const assocSet = new Edm.AssociationSet(v, { Name: assocName, Association: fullQualified(assocName) },
836
839
  fromRole, toRole, fromEntitySet, toEntitySet);
837
840
  if(navigationProperty._csn._SetAttributes)
838
841
  assocSet.setSapVocabularyAsAttributes(navigationProperty._csn._SetAttributes);
@@ -846,6 +849,32 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
846
849
  return schemaAliasPrefix + name.replace(schemaNamePrefix, '')
847
850
  }
848
851
  }
852
+
853
+ // generate the Edm.Annotations tree and append it to the corresponding schema
854
+ function addAnnotations() {
855
+ let { annos, usedVocabularies } = translate.csn2annotationEdm(csn, serviceCsn.name, Edm, options, messageFunctions);
856
+ // distribute edm:Annotations into the schemas
857
+ // Distribute each anno into Schema
858
+ annos.forEach(anno => {
859
+ let targetSchema = whatsMySchemaName(anno.Target);
860
+ // if no target schema has been found, it's a service annotation that applies to the service schema
861
+ if(targetSchema === undefined)
862
+ targetSchema = serviceCsn.name;
863
+ if(targetSchema) {
864
+ if(targetSchema !== serviceCsn.name) {
865
+ anno.Target = anno.Target.replace(serviceCsn.name + '.', '');
866
+ }
867
+ edm._service._schemas[targetSchema]._annotations.push(anno);
868
+ }
869
+ });
870
+ annos = [];
871
+ // add references for the used vocabularies
872
+ usedVocabularies.forEach(voc => {
873
+ let r = new Edm.Reference(v, voc.ref);
874
+ r.append(new Edm.Include(v, voc.inc))
875
+ edm._defaultRefs.push(r);
876
+ })
877
+ }
849
878
  }
850
879
  }
851
880
  module.exports = { csn2edm, csn2edmAll };
package/lib/edm/edm.js CHANGED
@@ -288,7 +288,8 @@ module.exports = function (options, error) {
288
288
  });
289
289
  let json_Annotations = Object.create(null);
290
290
  this._annotations.filter(a => a.Target).forEach(a => json_Annotations[a.Target] = a.toJSON());
291
- json['$Annotations'] = json_Annotations;
291
+ if(Object.keys(json_Annotations).length)
292
+ json['$Annotations'] = json_Annotations;
292
293
  }
293
294
  edmUtils.forAll(this._actions, (actionArray, actionName) => {
294
295
  json[actionName] = [];
@@ -610,7 +611,12 @@ module.exports = function (options, error) {
610
611
  if(scalarType) {
611
612
  this[typeName] = csn._edmType;
612
613
  // CDXCORE-CDXCORE-173 ignore type facets for Edm.Stream
613
- if(this[typeName] !== 'Edm.Stream')
614
+ // cds-compiler/issues/7835: Only set length for Binary as long as it is
615
+ // unclear how many bytes a string character represents.
616
+ // We can't calculate an unambiguous byte stream length for DB dependent
617
+ // multi-byte characters.
618
+ if(!(this[typeName] === 'Edm.Stream' &&
619
+ ![ /*'cds.String',*/ 'cds.Binary'].includes(scalarType.type)))
614
620
  edmUtils.addTypeFacets(this, scalarType);
615
621
  }
616
622
  else {
@@ -996,9 +1002,11 @@ module.exports = function (options, error) {
996
1002
  // values are !null (with other words: a collection must never return [1,2,null,3])
997
1003
  delete this.Nullable;
998
1004
  }
999
- let partner = (csn._selfReferences.length > 0) ? csn._selfReferences[0] : csn._constraints._partnerCsn;
1005
+ // we have exactly one selfReference or the default partner
1006
+ let partner = (!csn.$noPartner && csn._selfReferences.length === 1) ? csn._selfReferences[0] : csn._constraints._partnerCsn;
1000
1007
  if(partner && partner['@odata.navigable'] !== false) {
1001
- this.Partner = partner.name;
1008
+ // $abspath[0] is main entity
1009
+ this.Partner = partner.$abspath.slice(1).join(options.pathDelimiter);
1002
1010
  }
1003
1011
 
1004
1012
  /*
@@ -1084,13 +1092,6 @@ module.exports = function (options, error) {
1084
1092
  return json;
1085
1093
  }
1086
1094
 
1087
- createNavigationPropertyBinding(namespace)
1088
- {
1089
- return new NavigationPropertyBinding(this._v,
1090
- { Path: this.Name, Target: this._csn._target.name.replace(namespace, '') }
1091
- );
1092
- }
1093
-
1094
1095
  // V4 referential constraints!
1095
1096
  addReferentialConstraintNodes()
1096
1097
  {
@@ -1486,17 +1487,13 @@ module.exports = function (options, error) {
1486
1487
  new End(v, { Role: fromRole[0], Type: fromRole[1], Multiplicity: multiplicity[0] } ),
1487
1488
  new End(v, { Role: toRole[0], Type: toRole[1], Multiplicity: multiplicity[1] } ) );
1488
1489
 
1489
- // if eventually a backlink is a composition and this (Forward-)Association has not yet been
1490
- // produced, add the OnDelete property now to the target end.
1491
- // undefined values are not appended
1492
- if(navProp._OnDelete)
1493
- this._end[1].append(navProp._OnDelete);
1490
+ // set Delete:Cascade on composition end
1491
+ if(navProp._csn.type === 'cds.Composition')
1492
+ this._end[0].append(new OnDelete(v, { Action: 'Cascade' }));
1494
1493
 
1495
- // if the NavProp is a composition, add the OnDelete to the source end
1496
- if(navProp._csn.type === 'cds.Composition') {
1497
- // TODO: to be specified via @sap.on.delete
1498
- this._end[0].append(new OnDelete(v, { Action: 'Cascade' } ) );
1499
- }
1494
+ if(navProp._csn._selfReferences && navProp._csn._selfReferences.length &&
1495
+ navProp._csn._selfReferences[0].type === 'cds.Composition')
1496
+ this._end[1].append(new OnDelete(v, { Action: 'Cascade' }));
1500
1497
  }
1501
1498
 
1502
1499
  innerXML(indent)