@sap/cds-compiler 2.15.8 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +102 -1590
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +61 -46
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  6. package/doc/CHANGELOG_BETA.md +26 -5
  7. package/doc/CHANGELOG_DEPRECATED.md +55 -1
  8. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  9. package/doc/Versioning.md +20 -1
  10. package/lib/api/.eslintrc.json +2 -2
  11. package/lib/api/main.js +282 -156
  12. package/lib/api/options.js +17 -88
  13. package/lib/api/validate.js +6 -10
  14. package/lib/base/keywords.js +280 -110
  15. package/lib/base/message-registry.js +85 -25
  16. package/lib/base/messages.js +119 -89
  17. package/lib/base/model.js +46 -2
  18. package/lib/base/optionProcessorHelper.js +53 -21
  19. package/lib/checks/actionsFunctions.js +15 -12
  20. package/lib/checks/annotationsOData.js +1 -1
  21. package/lib/checks/cdsPersistence.js +1 -0
  22. package/lib/checks/elements.js +6 -6
  23. package/lib/checks/invalidTarget.js +1 -1
  24. package/lib/checks/nonexpandableStructured.js +1 -1
  25. package/lib/checks/queryNoDbArtifacts.js +2 -1
  26. package/lib/checks/selectItems.js +101 -15
  27. package/lib/checks/types.js +7 -8
  28. package/lib/checks/utils.js +2 -2
  29. package/lib/checks/validator.js +3 -3
  30. package/lib/compiler/assert-consistency.js +78 -21
  31. package/lib/compiler/base.js +6 -4
  32. package/lib/compiler/builtins.js +177 -10
  33. package/lib/compiler/checks.js +1 -1
  34. package/lib/compiler/define.js +28 -23
  35. package/lib/compiler/extend.js +75 -18
  36. package/lib/compiler/finalize-parse-cdl.js +25 -18
  37. package/lib/compiler/index.js +27 -11
  38. package/lib/compiler/moduleLayers.js +7 -0
  39. package/lib/compiler/populate.js +26 -39
  40. package/lib/compiler/propagator.js +12 -7
  41. package/lib/compiler/resolve.js +207 -236
  42. package/lib/compiler/shared.js +100 -93
  43. package/lib/compiler/tweak-assocs.js +13 -20
  44. package/lib/compiler/utils.js +20 -6
  45. package/lib/edm/annotations/preprocessAnnotations.js +12 -13
  46. package/lib/edm/csn2edm.js +35 -37
  47. package/lib/edm/edm.js +22 -13
  48. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  49. package/lib/edm/edmInboundChecks.js +85 -0
  50. package/lib/edm/edmPreprocessor.js +338 -689
  51. package/lib/edm/edmUtils.js +97 -67
  52. package/lib/gen/Dictionary.json +29 -9
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +8 -31
  55. package/lib/gen/language.tokens +105 -114
  56. package/lib/gen/languageLexer.interp +1 -34
  57. package/lib/gen/languageLexer.js +892 -1007
  58. package/lib/gen/languageLexer.tokens +95 -106
  59. package/lib/gen/languageParser.js +20629 -22474
  60. package/lib/inspect/.eslintrc.json +4 -0
  61. package/lib/inspect/index.js +14 -0
  62. package/lib/inspect/inspectModelStatistics.js +81 -0
  63. package/lib/inspect/inspectPropagation.js +189 -0
  64. package/lib/inspect/inspectUtils.js +44 -0
  65. package/lib/json/from-csn.js +74 -69
  66. package/lib/json/to-csn.js +17 -14
  67. package/lib/language/antlrParser.js +2 -2
  68. package/lib/language/docCommentParser.js +61 -38
  69. package/lib/language/errorStrategy.js +52 -40
  70. package/lib/language/genericAntlrParser.js +424 -292
  71. package/lib/language/language.g4 +604 -687
  72. package/lib/language/multiLineStringParser.js +14 -42
  73. package/lib/language/textUtils.js +44 -0
  74. package/lib/main.d.ts +28 -42
  75. package/lib/main.js +104 -81
  76. package/lib/model/api.js +1 -1
  77. package/lib/model/csnRefs.js +57 -30
  78. package/lib/model/csnUtils.js +189 -287
  79. package/lib/model/revealInternalProperties.js +32 -10
  80. package/lib/model/sortViews.js +32 -31
  81. package/lib/modelCompare/compare.js +3 -0
  82. package/lib/optionProcessor.js +91 -57
  83. package/lib/render/.eslintrc.json +1 -1
  84. package/lib/render/DuplicateChecker.js +4 -7
  85. package/lib/render/manageConstraints.js +70 -2
  86. package/lib/render/toCdl.js +387 -367
  87. package/lib/render/toHdbcds.js +20 -16
  88. package/lib/render/toRename.js +44 -22
  89. package/lib/render/toSql.js +81 -59
  90. package/lib/render/utils/common.js +16 -3
  91. package/lib/render/utils/sql.js +20 -19
  92. package/lib/sql-identifier.js +6 -0
  93. package/lib/transform/db/.eslintrc.json +3 -2
  94. package/lib/transform/db/associations.js +43 -35
  95. package/lib/transform/db/cdsPersistence.js +5 -16
  96. package/lib/transform/db/constraints.js +1 -1
  97. package/lib/transform/db/expansion.js +7 -6
  98. package/lib/transform/db/flattening.js +16 -18
  99. package/lib/transform/db/transformExists.js +7 -5
  100. package/lib/transform/db/views.js +3 -3
  101. package/lib/transform/draft/.eslintrc.json +2 -2
  102. package/lib/transform/draft/db.js +6 -6
  103. package/lib/transform/draft/odata.js +6 -7
  104. package/lib/transform/forHanaNew.js +30 -24
  105. package/lib/transform/forOdataNew.js +14 -16
  106. package/lib/transform/localized.js +35 -25
  107. package/lib/transform/odata/toFinalBaseType.js +10 -10
  108. package/lib/transform/odata/typesExposure.js +17 -8
  109. package/lib/transform/odata/utils.js +1 -38
  110. package/lib/transform/transformUtilsNew.js +63 -77
  111. package/lib/transform/translateAssocsToJoins.js +2 -2
  112. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  113. package/lib/transform/universalCsn/coreComputed.js +11 -6
  114. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  115. package/lib/utils/file.js +31 -21
  116. package/lib/utils/moduleResolve.js +0 -1
  117. package/lib/utils/timetrace.js +20 -21
  118. package/package.json +34 -4
  119. package/share/messages/syntax-expected-integer.md +9 -8
  120. package/doc/ApiMigration.md +0 -237
  121. package/doc/CommandLineMigration.md +0 -58
  122. package/doc/ErrorMessages.md +0 -175
  123. package/doc/FioriAnnotations.md +0 -94
  124. package/doc/ODataTransformation.md +0 -273
  125. package/lib/backends.js +0 -529
  126. package/lib/checks/unknownMagic.js +0 -41
  127. package/lib/fix_antlr4-8_warning.js +0 -56
@@ -7,7 +7,7 @@ const NAVPROP_TRENNER = '_';
7
7
  const VALUELIST_NAVPROP_PREFIX = '';
8
8
 
9
9
  const edmUtils = require('./edmUtils.js')
10
- const { initializeModel, assignAnnotation } = require('./edmPreprocessor.js');
10
+ const { initializeModel } = require('./edmPreprocessor.js');
11
11
  const translate = require('./annotations/genericTranslation.js');
12
12
  const { setProp } = require('../base/model');
13
13
  const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
@@ -326,15 +326,16 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
326
326
 
327
327
  /* create the entitytypes and sets
328
328
  Do not create an entity set if:
329
- V4 containment: _containerEntity is set and not equal with the artifact name
329
+ V4 containment: $containerNames is set and not equal with the artifact name
330
330
  Entity starts with 'localserviceNameized.' or ends with '_localized'
331
331
  */
332
332
  edmUtils.foreach(schemaCsn.definitions,
333
- a => edmUtils.isEntity(a) && !a.abstract && a.name.startsWith(schemaNamePrefix),
333
+ a => a.kind === 'entity' && !a.abstract && a.name.startsWith(schemaNamePrefix),
334
334
  createEntityTypeAndSet
335
335
  );
336
336
  // create unbound actions/functions
337
- edmUtils.foreach(schemaCsn.definitions, a => edmUtils.isActionOrFunction(a) && a.name.startsWith(schemaNamePrefix),
337
+ edmUtils.foreach(schemaCsn.definitions,
338
+ a => (a.kind === 'action' || a.kind === 'function') && a.name.startsWith(schemaNamePrefix),
338
339
  (options.isV4()) ? createActionV4 : createActionV2);
339
340
 
340
341
  // create the complex types
@@ -346,7 +347,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
346
347
  {
347
348
  edmUtils.foreach(schemaCsn.definitions,
348
349
  artifact => edmUtils.isDerivedType(artifact) &&
349
- !edmUtils.isAssociationOrComposition(artifact) &&
350
+ !artifact.target &&
350
351
  artifact.name.startsWith(schemaNamePrefix),
351
352
  createTypeDefinition);
352
353
  }
@@ -395,14 +396,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
395
396
  {
396
397
  const EntityTypeName = entityCsn.name.replace(schemaNamePrefix, '');
397
398
  const EntitySetName = edmUtils.getBaseName(entityCsn.$entitySetName || entityCsn.name);
398
-
399
+ const isSingleton = edmUtils.isSingleton(entityCsn) && options.isV4();
399
400
  const [ properties, hasStream ] = createProperties(entityCsn);
400
401
 
401
402
  const loc = ['definitions', entityCsn.name];
402
403
  const type = `${schema.name}.${EntityTypeName}`;
403
404
  if(properties.length === 0)
404
405
  warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no properties');
405
- else if(entityCsn.$edmKeyPaths.length === 0)
406
+ else if(entityCsn.$edmKeyPaths.length === 0 && !isSingleton)
406
407
  message('odata-spec-violation-no-key', loc);
407
408
 
408
409
  if(!edmUtils.isODataSimpleIdentifier(EntityTypeName))
@@ -414,7 +415,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
414
415
  if(p._edmAttributes.Name === EntityTypeName)
415
416
  warning('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
416
417
 
417
- if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
418
+ if(options.isV2() && p._isCollection && !p._csn.target)
418
419
  warning('odata-spec-violation-array', pLoc, { version: '2.0' });
419
420
 
420
421
  if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
@@ -432,7 +433,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
432
433
  // CDXCORE-CDXCORE-173
433
434
  if(options.isV2() && hasStream) {
434
435
  attributes['m:HasStream'] = true;
435
- assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
436
+ edmUtils.assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
436
437
  }
437
438
 
438
439
  Schema.append(new Edm.EntityType(v, attributes, properties, entityCsn));
@@ -461,7 +462,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
461
462
  }
462
463
 
463
464
  // put actions behind entity types in Schema/EntityContainer
464
- edmUtils.forAll(entityCsn.actions, (a, n) => {
465
+ entityCsn.actions && Object.entries(entityCsn.actions).forEach(([ n, a ]) => {
465
466
  (options.isV4()) ? createActionV4(a, n, entityCsn)
466
467
  : createActionV2(a, n, entityCsn)
467
468
  });
@@ -522,7 +523,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
522
523
  }
523
524
 
524
525
  // Parameter Nodes
525
- edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
526
+ actionCsn.params && Object.entries(actionCsn.params).forEach(([parameterName, parameterCsn]) => {
526
527
  const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
527
528
  const pLoc = [ ...loc, 'params', p._edmAttributes.Name ];
528
529
  if(!edmUtils.isODataSimpleIdentifier(parameterName))
@@ -573,7 +574,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
573
574
  if(rt) // add EntitySet attribute only if return type is an entity
574
575
  {
575
576
  const defintion = schemaCsn.definitions[rt];
576
- if(defintion && edmUtils.isEntity(defintion))
577
+ if(defintion && defintion.kind === 'entity')
577
578
  {
578
579
  functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));
579
580
  }
@@ -597,7 +598,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
597
598
  // Binding Parameter: Primary Keys at first position in sequence, this is decisive!
598
599
  // V2 XML: Nullable=false is set because we reuse the primary key property for the parameter
599
600
  edmUtils.foreach(entityCsn.elements,
600
- elementCsn => elementCsn.key && !edmUtils.isAssociationOrComposition(elementCsn),
601
+ elementCsn => elementCsn.key && !elementCsn.target,
601
602
  (elementCsn, elementName) => {
602
603
  functionImport.append(new Edm.Parameter(v, { Name: elementName }, elementCsn, 'In' ));
603
604
  }
@@ -605,7 +606,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
605
606
  }
606
607
 
607
608
  // is this still required?
608
- edmUtils.forAll(actionCsn, (v, p) => {
609
+ Object.entries(actionCsn).forEach(([p, v]) => {
609
610
  if (p.match(/^@sap\./))
610
611
  functionImport.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : v });
611
612
  });
@@ -613,7 +614,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
613
614
  // V2 XML: Parameters that are not explicitly marked as Nullable or NotNullable in the CSN must become Nullable=true
614
615
  // V2 XML spec does only mention default Nullable=true for Properties not for Parameters so omitting Nullable=true let
615
616
  // the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
616
- edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
617
+ actionCsn.params && Object.entries(actionCsn.params).forEach(([parameterName, parameterCsn]) => {
617
618
  const pLoc = [...loc, 'params', parameterName];
618
619
  const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
619
620
  edmTypeCompatibilityCheck(param, pLoc);
@@ -683,14 +684,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
683
684
  let hasStream = false;
684
685
  const streamProps = [];
685
686
 
686
- edmUtils.forAll(elementsCsn.elements, (elementCsn, elementName) =>
687
+ elementsCsn.elements && Object.entries(elementsCsn.elements).forEach(([elementName, elementCsn]) =>
687
688
  {
688
689
  if(elementCsn._edmParentCsn == undefined)
689
690
  setProp(elementCsn, '_edmParentCsn', edmParentCsn);
690
691
 
691
- if(!elementCsn._ignore) {
692
- if(edmUtils.isAssociationOrComposition(elementCsn))
693
- {
692
+ if(elementCsn.target) {
694
693
  // Foreign keys are part of the generic elementCsn.elements property creation
695
694
 
696
695
  // This is the V4 edmx:NavigationProperty
@@ -698,36 +697,35 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
698
697
 
699
698
  // suppress navprop creation only if @odata.navigable:false is not annotated.
700
699
  // (undefined !== false) still evaluates to true
701
- if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false)
700
+ if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false)
702
701
  {
703
- const navProp = new Edm.NavigationProperty(v, {
704
- Name: elementName,
705
- Type: elementCsn._target.name
706
- }, elementCsn);
707
- props.push(navProp);
702
+ const navProp = new Edm.NavigationProperty(v, {
703
+ Name: elementName,
704
+ Type: elementCsn._target.name
705
+ }, elementCsn);
706
+ props.push(navProp);
708
707
  // save the navProp in the global array for late constraint building
709
- navigationProperties.push(navProp);
710
- }
708
+ navigationProperties.push(navProp);
711
709
  }
710
+ }
712
711
  // render ordinary property if element is NOT ...
713
712
  // 1) ... annotated @cds.api.ignore
714
713
  // 2) ... annotated @odata.foreignKey4 and odataFormat: structured
715
714
 
716
- else if(isEdmPropertyRendered(elementCsn, options))
715
+ else if(isEdmPropertyRendered(elementCsn, options))
717
716
  {
718
717
  // CDXCORE-CDXCORE-173
719
718
  // V2: filter @Core.MediaType
720
- if ( options.isV2() && elementCsn['@Core.MediaType']) {
721
- hasStream = elementCsn['@Core.MediaType'];
722
- delete elementCsn['@Core.MediaType'];
719
+ if ( options.isV2() && elementCsn['@Core.MediaType']) {
720
+ hasStream = elementCsn['@Core.MediaType'];
721
+ delete elementCsn['@Core.MediaType'];
723
722
  // CDXCORE-CDXCORE-177:
724
723
  // V2: don't render element but add attribute 'm:HasStream="true' to EntityType
725
724
  // V4: render property type 'Edm.Stream'
726
- streamProps.push(elementName);
725
+ streamProps.push(elementName);
727
726
 
728
- } else {
729
- props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
730
- }
727
+ } else {
728
+ props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
731
729
  }
732
730
  }
733
731
 
@@ -772,10 +770,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
772
770
  message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
773
771
 
774
772
  if(options.isV2()) {
775
- if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
773
+ if(p._isCollection && !p._csn.target)
776
774
  warning('odata-spec-violation-array', pLoc, { version: '2.0' });
777
775
 
778
- if(edmUtils.isAssociationOrComposition(p._csn))
776
+ if(p._csn.target)
779
777
  warning('odata-spec-violation-assoc', pLoc, { version: '2.0' });
780
778
  }
781
779
  });
package/lib/edm/edm.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const edmUtils = require('./edmUtils.js');
4
4
  const { isBuiltinType } = require('../model/csnUtils.js');
5
5
  const { forEach } = require("../utils/objectUtils");
6
+ const { isBetaEnabled } = require('../base/model.js');
6
7
 
7
8
  // facet definitions, optional could either be true or array of edm types
8
9
  // remove indicates wether or not the canonic facet shall be removed when applying @odata.Type
@@ -239,7 +240,7 @@ function getEdm(options, messageFunctions) {
239
240
  if(csn)
240
241
  {
241
242
  const attr = (useSetAttributes ? csn._SetAttributes : csn);
242
- edmUtils.forAll(attr, (v, p) => {
243
+ attr && Object.entries(attr).forEach(([p, v]) => {
243
244
  if (p.match(/^@sap./))
244
245
  this.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : v } );
245
246
  });
@@ -332,7 +333,7 @@ function getEdm(options, messageFunctions) {
332
333
  if(what==='metadata' || what==='all')
333
334
  {
334
335
  xml += super.innerXML(indent);
335
- edmUtils.forAll(this._actions, actionArray => {
336
+ this._actions && Object.values(this._actions).forEach(actionArray => {
336
337
  actionArray.forEach(action => {
337
338
  xml += action.toXML(indent, what) + '\n'; });
338
339
  });
@@ -350,7 +351,7 @@ function getEdm(options, messageFunctions) {
350
351
  // no $Namespace
351
352
  toJSONattributes(json)
352
353
  {
353
- edmUtils.forAll(this._edmAttributes, (v,p) => {
354
+ this._edmAttributes && Object.entries(this._edmAttributes).forEach(([p, v]) => {
354
355
  if (p !== 'Name' && p !== 'Namespace')
355
356
  json[p[0] === '@' ? p : '$' + p] = v;
356
357
  });
@@ -371,7 +372,7 @@ function getEdm(options, messageFunctions) {
371
372
  if(Object.keys(json_Annotations).length)
372
373
  json['$Annotations'] = json_Annotations;
373
374
  }
374
- edmUtils.forAll(this._actions, (actionArray, actionName) => {
375
+ this._actions && Object.entries(this._actions).forEach(([actionName, actionArray]) => {
375
376
  json[actionName] = [];
376
377
  actionArray.forEach(action => {
377
378
  json[actionName].push(action.toJSON());
@@ -737,7 +738,7 @@ function getEdm(options, messageFunctions) {
737
738
  if(this._type !== 'Edm.String' && this._type) // Edm.String is default)
738
739
  json['$'+this._typeName] = this._type;
739
740
 
740
- edmUtils.forAll(this._edmAttributes, (v,p) => {
741
+ this._edmAttributes && Object.entries(this._edmAttributes).forEach(([p, v]) => {
741
742
  if (p !== 'Name' && p !== this._typeName
742
743
  // remove this line if Nullable=true becomes default
743
744
  && !(p === 'Nullable' && v == false))
@@ -754,7 +755,14 @@ function getEdm(options, messageFunctions) {
754
755
  }
755
756
  }
756
757
 
757
- class ComplexType extends TypeBase { }
758
+ class ComplexType extends TypeBase {
759
+ constructor(v, details, csn) {
760
+ super(v, details, csn);
761
+ if(this.v4 && !!csn['@open'] && isBetaEnabled(options, 'odataOpenType')) {
762
+ this._edmAttributes['OpenType'] = true;
763
+ }
764
+ }
765
+ }
758
766
  class EntityType extends ComplexType
759
767
  {
760
768
  constructor(v, details, properties, csn)
@@ -840,8 +848,8 @@ function getEdm(options, messageFunctions) {
840
848
  super(v, attributes, csn);
841
849
 
842
850
  // array of enum not yet allowed
843
- let enumValues = /*(csn.items && csn.items.enum) ||*/ csn.enum;
844
- edmUtils.forAll(enumValues, (e, en) => {
851
+ const enumValues = /*(csn.items && csn.items.enum) ||*/ csn.enum;
852
+ enumValues && Object.entries(enumValues).forEach(([en, e]) => {
845
853
  this.append(new Member(v, { Name: en, Value: e.val } ));
846
854
  });
847
855
  }
@@ -1177,9 +1185,10 @@ function getEdm(options, messageFunctions) {
1177
1185
  _constraints = this._csn._constraints._partnerCsn._constraints;
1178
1186
  [i,j] = [1,0];
1179
1187
  }
1180
- edmUtils.forAll(_constraints.constraints,
1181
- c => this.append(new ReferentialConstraint(this._v,
1182
- { Property: c[i].join(options.pathDelimiter), ReferencedProperty: c[j].join(options.pathDelimiter) } ) ) );
1188
+ _constraints.constraints && Object.values(_constraints.constraints).forEach(c =>
1189
+ this.append(new ReferentialConstraint(this._v,
1190
+ { Property: c[i].join(options.pathDelimiter), ReferencedProperty: c[j].join(options.pathDelimiter) } ) )
1191
+ );
1183
1192
  }
1184
1193
  }
1185
1194
 
@@ -1502,7 +1511,7 @@ function getEdm(options, messageFunctions) {
1502
1511
  }
1503
1512
  toJSONattributes(json) {
1504
1513
  super.toJSONattributes(json);
1505
- edmUtils.forAll(this._jsonOnlyAttributes, (v,p) => {
1514
+ this._jsonOnlyAttributes && Object.entries(this._jsonOnlyAttributes).forEach(([p, v]) => {
1506
1515
  json[p[0] === '@' ? p : '$' + p] = v;
1507
1516
  });
1508
1517
  return json;
@@ -1606,7 +1615,7 @@ function getEdm(options, messageFunctions) {
1606
1615
  node._d = new Dependent(v, { Role: from } );
1607
1616
  node._p = new Principal(v, { Role: to } );
1608
1617
 
1609
- edmUtils.forAll(c, cv => {
1618
+ c && Object.values(c).forEach(cv => {
1610
1619
  node._d.append(new PropertyRef(v, cv[0].join(options.pathDelimiter)));
1611
1620
  node._p.append(new PropertyRef(v, cv[1].join(options.pathDelimiter)));
1612
1621
  });
@@ -0,0 +1,349 @@
1
+ 'use strict';
2
+
3
+ const edmUtils = require('./edmUtils.js');
4
+ const { setProp } = require('../base/model');
5
+ const { forEachGeneric } = require('../model/csnUtils');
6
+
7
+ /*
8
+ * Late application specific transformations
9
+ * At present there are two transformation targets: Structure and Element
10
+ * These transformations are available today:
11
+ *
12
+ * Analytical Scenario:
13
+ * If a structure is annotated with @Aggregation.ApplySupported.PropertyRestrictions
14
+ * then a number of annotation rewrites are done to this structure and to the
15
+ * elements of this structure
16
+ * Also the key properties of all structure elements are removed and a new
17
+ * artificial key element 'key _ID : String' is inserted at first position of
18
+ * the elements dictionary
19
+ *
20
+ * PDM (Personal Data Management)
21
+ * Planned but not yet implemented annotation rewriting (pending to finalization)
22
+ */
23
+
24
+ /* eslint max-statements-per-line:off */
25
+
26
+ function mapAnnotationAssignment(artifact, parent, mappingDictionary)
27
+ {
28
+ let props = edmUtils.intersect(Object.keys(mappingDictionary), Object.keys(artifact));
29
+ // now start the substitution
30
+ props.forEach(prop => {
31
+ let [ mapping, value, remove_original ] = mappingDictionary[prop];
32
+ if(mapping instanceof Function)
33
+ {
34
+ mapping(artifact, parent, prop);
35
+ }
36
+ else
37
+ {
38
+ edmUtils.assignAnnotation(artifact, mapping, value || artifact[prop]['='] || artifact[prop]);
39
+ }
40
+
41
+ if(remove_original)
42
+ delete artifact[prop];
43
+ });
44
+ }
45
+
46
+ function addToSetAttr(carrier, propName, propValue, removeFromType=true) {
47
+ edmUtils.assignProp(carrier, '_SetAttributes', Object.create(null));
48
+ edmUtils.assignAnnotation(carrier._SetAttributes, propName, propValue);
49
+ if(removeFromType) {
50
+ delete carrier[propName];
51
+ }
52
+ }
53
+
54
+ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct, error)
55
+ {
56
+ if(options.isV2())
57
+ {
58
+ if(struct['@Aggregation.ApplySupported.PropertyRestrictions'])
59
+ {
60
+ mapAnnotationAssignment(element, struct, AnalyticalAnnotations());
61
+ }
62
+ mapAnnotationAssignment(element, struct, PDMSemantics());
63
+ }
64
+
65
+ // etag requires Core.OptimisticConcurrency to be set in V4 (cap/issues#2641)
66
+ // Oliver Heinrich mentions in the issue that the Okra runtime must be set to a
67
+ // concurrent runtime mode by the caller, if the annotation is added this late,
68
+ // it doesn't appear in the forOData processed CSN, meaning that the
69
+ // runtime cannot set that okra flag (alternatively the runtime has to search
70
+ // for @[odata|cds].etag annotations...
71
+ if(options.isV4())
72
+ {
73
+ if(element['@odata.etag'] == true || element['@cds.etag'] == true) {
74
+ // don't put element name into collection as per advice from Ralf Handl, as
75
+ // no runtime is interested in the property itself, it is sufficient to mark
76
+ // the entity set.
77
+ edmUtils.assignAnnotation(struct, '@Core.OptimisticConcurrency',
78
+ (struct['@Core.OptimisticConcurrency'] || [])/*.push(element.name)*/);
79
+ }
80
+ }
81
+
82
+ // nested functions begin
83
+ function PDMSemantics()
84
+ {
85
+ /*
86
+ let dict = Object.create(null);
87
+
88
+ dict['@PDM.xxx1'] = [ '@sap.pdm-semantics' ];
89
+ dict['@PDM.xxx2'] = [ '@sap.pdm-propery' ];
90
+ dict['@PDM.xxx3'] = [ '@sap.pdm-display-sq-no' ];
91
+ dict['@PDM.xxx4'] = [ '@sap.pdm-record-identifier' ];
92
+ dict['@PDM.xxx5'] = [ '@sap.pdm-field-group' ];
93
+ dict['@PDM.xxx6'] = [ '@sap.pdm-mask-find-pattern' ];
94
+ dict['@PDM.xxx7'] = [ '@sap.pdm-mask-replacement-pattern' ];
95
+ dict['@PDM.xxx8'] = [ '@sap.deletable' ];
96
+ dict['@PDM.xxx8'] = [ '@sap.updatable' ];
97
+
98
+ // respect flattened annotation $value
99
+ Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
100
+ */
101
+ return Object.create(null);
102
+ }
103
+
104
+ function AnalyticalAnnotations()
105
+ {
106
+ function mapCommonAttributes(element, struct, prop)
107
+ {
108
+ let CommonAttributes = element[prop];
109
+ if(!Array.isArray(CommonAttributes)) {
110
+ error(null, ['definitions', struct.name, 'elements', element.name],
111
+ { anno: '@Common.attribute', code: JSON.stringify(CommonAttributes) },
112
+ `Expect array value for $(ANNOTATION): $(CODE)`);
113
+ return;
114
+ }
115
+
116
+ let targets = edmUtils.intersect(CommonAttributes, Object.keys(struct.elements));
117
+ targets.forEach(tgt => {
118
+ edmUtils.assignAnnotation(struct.elements[tgt], '@sap.attribute-for', element.name);
119
+ });
120
+ }
121
+
122
+ function mapContextDefiningProperties(element, struct, prop)
123
+ {
124
+ let ContextDefiningProperties = element[prop];
125
+ if(!Array.isArray(ContextDefiningProperties)) {
126
+ error(null, ['definitions', struct.name, 'elements', element.name],
127
+ { anno: '@Aggregation.ContextDefiningProperties', code: JSON.stringify(ContextDefiningProperties) },
128
+ `Expect array value for $(ANNOTATION): $(CODE)`);
129
+ return;
130
+ }
131
+ if(ContextDefiningProperties.length > 0)
132
+ edmUtils.assignAnnotation(element, '@sap.super-ordinate', ContextDefiningProperties[ContextDefiningProperties.length-1]);
133
+ }
134
+
135
+ let dict = Object.create(null);
136
+ //analytics term definition unknown, lower case
137
+ dict['@Analytics.Measure'] = [ '@sap.aggregation-role', 'measure' ];
138
+ dict['@Analytics.Dimension'] = [ '@sap.aggregation-role', 'dimension' ];
139
+ dict['@Semantics.currencyCode'] = [ '@sap.semantics', 'currency-code', true ];
140
+ dict['@Semantics.unitOfMeasure'] = [ '@sap.semantics', 'unit-of-measure', true ];
141
+
142
+ dict['@Measures.ISOCurrency'] = [ '@sap.unit' ];
143
+ dict['@Measures.Unit'] = [ '@sap.unit' ];
144
+
145
+ dict['@Common.Label'] = [ '@sap.label' ];
146
+ dict['@Common.Text'] = [ '@sap.text' ];
147
+ dict['@Aggregation.ContextDefiningProperties'] = [ mapContextDefiningProperties ];
148
+ dict['@Common.Attributes'] = [ mapCommonAttributes ];
149
+
150
+ // respect flattened annotation $value
151
+ Object.entries(dict).forEach(([k, v]) => dict[k+'.$value'] = v);
152
+ return dict;
153
+ }
154
+ }
155
+
156
+ function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error)
157
+ {
158
+ if(options.isV2())
159
+ {
160
+ if(struct['@Aggregation.ApplySupported.PropertyRestrictions'])
161
+ {
162
+ transformAnalyticalModel(struct);
163
+ mapAnnotationAssignment(struct, undefined, AnalyticalAnnotations());
164
+ }
165
+ }
166
+
167
+ // nested functions begin
168
+ function transformAnalyticalModel(struct)
169
+ {
170
+ let keyName = 'ID__';
171
+ if(struct == undefined || struct.elements == undefined || struct.elements[keyName] != undefined)
172
+ return;
173
+
174
+ // remove key prop from elements, add new key to elements
175
+ let elements = Object.create(null);
176
+ let key = { name: keyName, key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false, '@UI.Hidden': true };
177
+ elements[keyName] = key;
178
+ setProp(struct, '$keys',{ [keyName] : key } );
179
+ forEachGeneric(struct.items || struct, 'elements', (e,n) =>
180
+ {
181
+ if(e.key) delete e.key;
182
+ elements[n] = e;
183
+ });
184
+ struct.elements = elements;
185
+ }
186
+
187
+ function AnalyticalAnnotations()
188
+ {
189
+ function mapFilterRestrictions(struct, parent, prop)
190
+ {
191
+ let stringDict = Object.create(null);
192
+ stringDict['SingleValue'] = 'single-value';
193
+ stringDict['MultiValue'] = 'multi-value';
194
+ stringDict['SingleRange'] = 'interval';
195
+
196
+ let filterRestrictions = struct[prop];
197
+ if(!Array.isArray(filterRestrictions)) {
198
+ error(null, ['definitions', struct.name ],
199
+ { anno: '@Capabilities.FilterRestrictions.FilterExpressionRestrictions',
200
+ code: JSON.stringify(filterRestrictions) },
201
+ `Expect array value for $(ANNOTATION): $(CODE)`);
202
+ return;
203
+ }
204
+ filterRestrictions.forEach(v => {
205
+ let e = struct.elements[v.Property];
206
+ if(e)
207
+ edmUtils.assignAnnotation(e, '@sap.filter-restriction', stringDict[v.AllowedExpressions]);
208
+ });
209
+ }
210
+
211
+ function mapRequiredProperties(struct, parent, prop)
212
+ {
213
+ let requiredProperties = struct[prop];
214
+ if(!Array.isArray(requiredProperties)) {
215
+ error(null, ['definitions', struct.name],
216
+ { anno: '@Capabilities.FilterRestrictions.RequiredProperties',
217
+ code: JSON.stringify(requiredProperties) },
218
+ `Expect array value for $(ANNOTATION): $(CODE)`);
219
+ return;
220
+ }
221
+
222
+ let props = edmUtils.intersect(Object.keys(struct.elements), requiredProperties)
223
+ props.forEach(p => {
224
+ edmUtils.assignAnnotation(struct.elements[p], '@sap.required-in-filter', true);
225
+ });
226
+ }
227
+
228
+ function mapRequiresFilter(struct, parent, prop)
229
+ {
230
+ let requiresFilter = struct[prop];
231
+ if(requiresFilter)
232
+ edmUtils.assignAnnotation(struct._SetAttributes, '@sap.requires-filter', requiresFilter);
233
+ }
234
+
235
+ // Entity Props
236
+ let dict = Object.create(null);
237
+ dict['@Aggregation.ApplySupported.PropertyRestrictions'] = [ '@sap.semantics', 'aggregate' ];
238
+ dict['@Common.Label'] = [ '@sap.label' ];
239
+ dict['@Capabilities.FilterRestrictions.RequiresFilter'] = [ mapRequiresFilter ];
240
+ dict['@Capabilities.FilterRestrictions.RequiredProperties'] = [ mapRequiredProperties ];
241
+ dict['@Capabilities.FilterRestrictions.FilterExpressionRestrictions'] = [ mapFilterRestrictions ];
242
+
243
+ // respect flattened annotation $value
244
+ Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
245
+
246
+ return dict;
247
+ }
248
+ }
249
+
250
+ function setSAPSpecificV2AnnotationsToEntityContainer(options, carrier) {
251
+ if(!options.isV2())
252
+ return;
253
+ // documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0#SAPAnnotationsforODataVersion2.0-Elementedm:EntityContainer
254
+ const SetAttributes = {
255
+ // EntityContainer only
256
+ '@sap.supported.formats' : addToSetAttr,
257
+ '@sap.use.batch': addToSetAttr,
258
+ '@sap.message.scope.supported': addToSetAttr,
259
+ };
260
+
261
+ Object.entries(carrier).forEach(([p, v]) => {
262
+ (SetAttributes[p] || function() { /* no-op */ })(carrier, p, v);
263
+ });
264
+ }
265
+
266
+ function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
267
+ if(!options.isV2())
268
+ return;
269
+ // documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0#SAPAnnotationsforODataVersion2.0-Elementedm:EntitySet
270
+ const SetAttributes = {
271
+ // EntitySet, EntityType
272
+ '@sap.label' : (s,pn, pv) => { addToSetAttr(s, pn, pv, false); },
273
+ '@sap.semantics': checkSemantics,
274
+ // EntitySet only
275
+ '@sap.creatable' : addToSetAttr,
276
+ '@sap.updatable' : addToSetAttr,
277
+ '@sap.deletable': addToSetAttr,
278
+ '@sap.updatable.path': addToSetAttr,
279
+ '@sap.deletable.path': addToSetAttr,
280
+ '@sap.searchable' : addToSetAttr,
281
+ '@sap.pagable': addToSetAttr,
282
+ '@sap.topable': addToSetAttr,
283
+ '@sap.countable': addToSetAttr,
284
+ '@sap.addressable': addToSetAttr,
285
+ '@sap.requires.filter': addToSetAttr,
286
+ '@sap.change.tracking': addToSetAttr,
287
+ '@sap.maxpagesize': addToSetAttr,
288
+ '@sap.delta.link.validity': addToSetAttr,
289
+ };
290
+
291
+ Object.entries(carrier).forEach(([p, v]) => {
292
+ (SetAttributes[p] || function() { /* no-op */ })(carrier, p, v);
293
+ });
294
+
295
+ function checkSemantics(struct, propName, propValue) {
296
+ if(propValue === 'timeseries' || propValue === 'aggregate') {
297
+ // aggregate is forwarded to Set and must remain on Type
298
+ addToSetAttr(struct, propName, propValue, propValue !== 'aggregate');
299
+ }
300
+ }
301
+ }
302
+
303
+ function setSAPSpecificV2AnnotationsToAssociation(carrier) {
304
+ // documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0
305
+ const SetAttributes = {
306
+ // Applicable to NavProp and foreign keys, add to AssociationSet
307
+ '@sap.creatable' : (c, pn, pv) => { addToAssociationSet(c, pn, pv, false); },
308
+ // Not applicable to NavProp, applicable to foreign keys, add to AssociationSet
309
+ '@sap.updatable' : addToAssociationSet,
310
+ // Not applicable to NavProp, not applicable to foreign key, add to AssociationSet
311
+ '@sap.deletable': (c, pn, pv) => {
312
+ addToAssociationSet(c, pn, pv);
313
+ removeFromForeignKey(c, pn);
314
+ },
315
+ // applicable to NavProp, not applicable to foreign keys, not applicable to AssociationSet
316
+ '@sap.creatable.path': removeFromForeignKey,
317
+ '@sap.filterable': removeFromForeignKey,
318
+ };
319
+
320
+ Object.entries(carrier).forEach(([p, v]) => {
321
+ (SetAttributes[p] || function() {/* no-op */})(carrier, p, v);
322
+ });
323
+
324
+ function addToAssociationSet(carrier, propName, propValue, removeFromType=true) {
325
+ if(carrier.target) {
326
+ edmUtils.assignProp(carrier, '_SetAttributes', Object.create(null));
327
+ edmUtils.assignAnnotation(carrier._SetAttributes, propName, propValue);
328
+ if(removeFromType) {
329
+ delete carrier[propName];
330
+ }
331
+ }
332
+ }
333
+
334
+ function removeFromForeignKey(carrier, propName) {
335
+ if(carrier['@odata.foreignKey4'] && carrier[propName] !== undefined) {
336
+ delete carrier[propName];
337
+ }
338
+ }
339
+ }
340
+
341
+
342
+
343
+ module.exports = {
344
+ applyAppSpecificLateCsnTransformationOnElement,
345
+ applyAppSpecificLateCsnTransformationOnStructure,
346
+ setSAPSpecificV2AnnotationsToEntityContainer,
347
+ setSAPSpecificV2AnnotationsToEntitySet,
348
+ setSAPSpecificV2AnnotationsToAssociation
349
+ };