@sap/cds-compiler 3.8.0 → 3.9.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 (79) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +26 -5
  4. package/lib/api/.eslintrc.json +3 -2
  5. package/lib/api/options.js +3 -1
  6. package/lib/api/validate.js +1 -1
  7. package/lib/base/message-registry.js +27 -18
  8. package/lib/base/messages.js +6 -1
  9. package/lib/base/model.js +2 -2
  10. package/lib/checks/.eslintrc.json +1 -0
  11. package/lib/checks/actionsFunctions.js +6 -6
  12. package/lib/checks/annotationsOData.js +1 -1
  13. package/lib/checks/elements.js +28 -17
  14. package/lib/checks/foreignKeys.js +1 -1
  15. package/lib/checks/invalidTarget.js +1 -1
  16. package/lib/checks/onConditions.js +11 -6
  17. package/lib/checks/queryNoDbArtifacts.js +1 -1
  18. package/lib/checks/types.js +1 -1
  19. package/lib/checks/utils.js +1 -1
  20. package/lib/checks/validator.js +3 -2
  21. package/lib/compiler/assert-consistency.js +8 -3
  22. package/lib/compiler/base.js +19 -13
  23. package/lib/compiler/builtins.js +7 -0
  24. package/lib/compiler/checks.js +73 -6
  25. package/lib/compiler/define.js +10 -5
  26. package/lib/compiler/extend.js +924 -1709
  27. package/lib/compiler/finalize-parse-cdl.js +1 -1
  28. package/lib/compiler/generate.js +838 -0
  29. package/lib/compiler/index.js +2 -0
  30. package/lib/compiler/populate.js +2 -2
  31. package/lib/compiler/propagator.js +20 -8
  32. package/lib/compiler/resolve.js +3 -3
  33. package/lib/compiler/shared.js +11 -6
  34. package/lib/edm/annotations/genericTranslation.js +6 -6
  35. package/lib/edm/csn2edm.js +1 -1
  36. package/lib/edm/edm.js +25 -11
  37. package/lib/edm/edmPreprocessor.js +47 -23
  38. package/lib/edm/edmUtils.js +37 -9
  39. package/lib/gen/Dictionary.json +5 -7
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +3 -1
  42. package/lib/gen/language.tokens +24 -23
  43. package/lib/gen/languageLexer.interp +4 -1
  44. package/lib/gen/languageLexer.js +792 -784
  45. package/lib/gen/languageLexer.tokens +12 -11
  46. package/lib/gen/languageParser.js +3944 -3865
  47. package/lib/json/from-csn.js +27 -6
  48. package/lib/json/to-csn.js +10 -6
  49. package/lib/language/antlrParser.js +11 -3
  50. package/lib/language/genericAntlrParser.js +4 -2
  51. package/lib/language/language.g4 +32 -24
  52. package/lib/model/csnRefs.js +15 -7
  53. package/lib/model/csnUtils.js +41 -76
  54. package/lib/modelCompare/utils/.eslintrc.json +1 -1
  55. package/lib/optionProcessor.js +7 -4
  56. package/lib/render/.eslintrc.json +1 -1
  57. package/lib/render/toCdl.js +244 -168
  58. package/lib/render/toHdbcds.js +18 -10
  59. package/lib/render/toSql.js +24 -2
  60. package/lib/transform/db/.eslintrc.json +4 -3
  61. package/lib/transform/db/cdsPersistence.js +1 -1
  62. package/lib/transform/db/expansion.js +11 -6
  63. package/lib/transform/db/flattening.js +22 -15
  64. package/lib/transform/db/rewriteCalculatedElements.js +50 -29
  65. package/lib/transform/db/temporal.js +1 -1
  66. package/lib/transform/db/views.js +1 -1
  67. package/lib/transform/draft/db.js +1 -1
  68. package/lib/transform/draft/odata.js +3 -4
  69. package/lib/transform/forOdataNew.js +5 -6
  70. package/lib/transform/forRelationalDB.js +7 -7
  71. package/lib/transform/odata/toFinalBaseType.js +6 -6
  72. package/lib/transform/odata/typesExposure.js +12 -3
  73. package/lib/transform/odata/utils.js +3 -0
  74. package/lib/transform/transformUtilsNew.js +11 -26
  75. package/lib/transform/translateAssocsToJoins.js +9 -9
  76. package/lib/transform/universalCsn/.eslintrc.json +3 -2
  77. package/lib/transform/universalCsn/coreComputed.js +1 -1
  78. package/lib/transform/universalCsn/universalCsnEnricher.js +6 -4
  79. package/package.json +1 -1
@@ -24,6 +24,7 @@ const { fns } = require('./shared');
24
24
  const define = require('./define');
25
25
  const finalizeParseCdl = require('./finalize-parse-cdl');
26
26
  const extend = require('./extend');
27
+ const generate = require('./generate');
27
28
  const kickStart = require('./kick-start');
28
29
  const populate = require('./populate');
29
30
  const resolve = require('./resolve');
@@ -463,6 +464,7 @@ function compileDoX( model ) {
463
464
  return model;
464
465
  }
465
466
  extend( model );
467
+ generate( model );
466
468
  kickStart( model );
467
469
  populate( model );
468
470
 
@@ -61,7 +61,7 @@ function populate( model ) {
61
61
  resolvePath,
62
62
  attachAndEmitValidNames,
63
63
  initArtifact,
64
- chooseAnnotationsInArtifact,
64
+ extendArtifactBefore,
65
65
  extendArtifactAfter,
66
66
  } = model.$functions;
67
67
  model.$volatileFunctions.environment = environment;
@@ -213,7 +213,7 @@ function populate( model ) {
213
213
  chain.reverse();
214
214
  for (const a of chain) {
215
215
  // Without type, value.path or _origin at beginning, link to itself:
216
- chooseAnnotationsInArtifact( a );
216
+ extendArtifactBefore( a );
217
217
  art = populateArtifact( a, art ) || a;
218
218
  if (a.elements$ || a.enum$)
219
219
  mergeSpecifiedElementsOrEnum( a );
@@ -13,6 +13,7 @@ const {
13
13
  forEachMember,
14
14
  forEachGeneric,
15
15
  isDeprecatedEnabled,
16
+ isBetaEnabled,
16
17
  } = require( '../base/model');
17
18
  const {
18
19
  setLink,
@@ -42,9 +43,9 @@ function propagate( model ) {
42
43
  '@cds.external': never,
43
44
  '@cds.redirection.target': never,
44
45
  '@fiori.draft.enabled': onlyViaArtifact,
45
- '@': annotation, // always except in 'returns' and 'items'
46
- doc: annotation, // always except in 'returns' and 'items'
47
- default: withKind, // always except in 'returns' and 'items'
46
+ '@': annotation, // always except in 'items'
47
+ doc: annotation, // always except in 'items'
48
+ default: withKind, // always except in 'items'
48
49
  virtual,
49
50
  notNull,
50
51
  targetElement: onlyViaParent, // in foreign keys
@@ -72,10 +73,11 @@ function propagate( model ) {
72
73
  const { options } = model;
73
74
  // eslint-disable-next-line max-len
74
75
  const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
76
+ const { warning, throwWithError } = model.$messageFunctions;
77
+ const propagateToReturns = isBetaEnabled( options, 'v4preview' );
75
78
 
76
79
  forEachDefinition( model, run );
77
80
 
78
- const { warning, throwWithError } = model.$messageFunctions;
79
81
  // TODO: move 'virtual' handling/checks to resolver if
80
82
  // 'deprecated.oldVirtualNotNullPropagation' is gone
81
83
  if (!oldVirtualNotNullPropagation) // check would always be right, but to be ultra compatible…
@@ -105,6 +107,14 @@ function propagate( model ) {
105
107
  chain.push({ target, source: target.value._artifact });
106
108
  if (checkAndSetStatus( target.value._artifact ))
107
109
  news.push(target.value._artifact);
110
+
111
+ if (target.value?._artifact.$inferred !== 'include') {
112
+ // If the referred to element is not inferred, it is a new one and not the original.
113
+ // The new one was not originally referred to => error;
114
+ warning( 'ref-unexpected-override', [ target.name.location, target ],
115
+ { id: target.name.id, target: target.value?._artifact },
116
+ 'Calculated element $(ID) does not originally refer to $(TARGET)' );
117
+ }
108
118
  }
109
119
  chain.push( { target, source: origin } );
110
120
  if (checkAndSetStatus( origin ))
@@ -267,14 +277,16 @@ function propagate( model ) {
267
277
 
268
278
  function onlyViaArtifact( prop, target, source ) {
269
279
  const from = viewFromPrimary( target )?.path;
270
- // do not propagate from member / if follow assoc in from:
271
- if (!(from ? from[from.length - 1]._artifact : source)._main)
280
+ // do not propagate from member / if follow assoc in from or into `returns` of actions (v4)
281
+ if (!(from ? from[from.length - 1]._artifact : source)._main &&
282
+ !(propagateToReturns && target._parent && target._parent.returns === target))
272
283
  annotation( prop, target, source );
273
284
  }
274
285
 
275
286
  function withKind( prop, target, source ) {
276
- if (target.kind && (!target._parent || target._parent.returns !== target))
277
- always( prop, target, source ); // not in 'returns' and 'items'
287
+ if (target.kind &&
288
+ (propagateToReturns || !target._parent || target._parent.returns !== target))
289
+ always(prop, target, source); // not in 'items'
278
290
  }
279
291
 
280
292
  function notNull( prop, target, source, viaType ) {
@@ -81,7 +81,7 @@ function resolve( model ) {
81
81
  } = model.$messageFunctions;
82
82
  const {
83
83
  resolvePath,
84
- lateExtensions,
84
+ createRemainingAnnotateStatements,
85
85
  effectiveType,
86
86
  getOrigin,
87
87
  resolveType,
@@ -117,7 +117,7 @@ function resolve( model ) {
117
117
  forEachDefinition( model, resolveRefs );
118
118
  forEachGeneric( model, 'vocabularies', resolveRefs );
119
119
  // create “super” ANNOTATE statements for annotations on unknown artifacts:
120
- lateExtensions();
120
+ createRemainingAnnotateStatements();
121
121
  // report cyclic dependencies:
122
122
  detectCycles( model.definitions, ( user, art, location ) => {
123
123
  if (location) {
@@ -1128,7 +1128,7 @@ function pathNavigation( ref ) {
1128
1128
  return { item, tableAlias: root };
1129
1129
  if (root.kind !== '$tableAlias' || ref.path.length < 2)
1130
1130
  return {}; // should not happen
1131
- return { navigation: root.elements[item.id], item, tableAlias: root };
1131
+ return { navigation: root.elements?.[item.id], item, tableAlias: root };
1132
1132
  }
1133
1133
 
1134
1134
  module.exports = resolve;
@@ -57,7 +57,8 @@ function fns( model ) {
57
57
  artItemsCount: Number.MAX_SAFE_INTEGER,
58
58
  undefinedDef: 'anno-undefined-def',
59
59
  undefinedArt: 'anno-undefined-art',
60
- allowAutoexposed: true,
60
+ allowAutoexposed: true, // TODO: think about Info/Warning
61
+ noMessageForLocalized: true, // TODO: should we issue a Debug message for code completion?
61
62
  },
62
63
  type: { // TODO: more detailed later (e.g. for enum base type?)
63
64
  envFn: artifactsEnv,
@@ -116,7 +117,9 @@ function fns( model ) {
116
117
  typeOf: { next: '_$next', dollar: true }, // TODO: disallow in var
117
118
  // TODO: dep for (explicit+implicit!) foreign keys
118
119
  targetElement: { next: '__none_', assoc: false, dollar: false },
119
- filter: { next: '_$next', lexical: 'main', dollar: 'none' },
120
+ filter: {
121
+ next: '_$next', lexical: 'main', dollar: 'none', escape: 'param',
122
+ },
120
123
  default: {
121
124
  next: '_$next',
122
125
  dollar: true,
@@ -735,10 +738,12 @@ function fns( model ) {
735
738
  }
736
739
  else if (env.$frontend && env.$frontend !== 'cdl' || spec.global) {
737
740
  // IDE can inspect <model>.definitions - provide null for valid
738
- signalNotFound( spec.undefinedDef || 'ref-undefined-def', [ head.location, user ],
739
- valid, { art: head.id } );
741
+ if (!spec.noMessageForLocalized || !head.id.startsWith( 'localized.' )) {
742
+ signalNotFound( spec.undefinedDef || 'ref-undefined-def', [ head.location, user ],
743
+ valid, { art: head.id } );
744
+ }
740
745
  }
741
- else {
746
+ else if (!spec.noMessageForLocalized || head.id !== 'localized') {
742
747
  signalNotFound( spec.undefinedArt || 'ref-undefined-art', [ head.location, user ],
743
748
  valid, { name: head.id } );
744
749
  }
@@ -817,7 +822,7 @@ function fns( model ) {
817
822
  setTargetReferenceKey( orig.name.id, item );
818
823
  }
819
824
  art = sub;
820
- if (spec.envFn && (!artItemsCount || item === last) &&
825
+ if (spec.envFn && !spec.allowAutoexposed && (!artItemsCount || item === last) &&
821
826
  art && art.$inferred === 'autoexposed' && !user.$inferred) {
822
827
  // Depending on the processing sequence, the following could be a
823
828
  // simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
@@ -1821,10 +1821,10 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1821
1821
 
1822
1822
  }
1823
1823
 
1824
- function mergeVocRefs(options, message) {
1825
- /* Merge options.odataVocRefs into vocabularyDefinitions and
1824
+ function mergeOdataVocabularies(options, message) {
1825
+ /* Merge options.odataVocabularies into vocabularyDefinitions and
1826
1826
  create a csn2edm stack local dictionary.
1827
- odataVocRefs is an object, each property is the
1827
+ odataVocabularies is an object, each property is the
1828
1828
  annotation prefix (as in mergedVocDefs), the value
1829
1829
  is an object { Alias, Namespace, Uri }, this way
1830
1830
  the definitions are unique and duplicate entries to address
@@ -1833,8 +1833,8 @@ function mergeVocRefs(options, message) {
1833
1833
  */
1834
1834
  const mergedVocDefs = Object.assign({}, vocabularyDefinitions);
1835
1835
  const reqProps = ['Alias', 'Namespace', 'Uri'];
1836
- if(options.odataVocRefs) {
1837
- const vocRefs = options.odataVocRefs;
1836
+ if(options.odataVocabularies) {
1837
+ const vocRefs = options.odataVocabularies;
1838
1838
  if (typeof vocRefs === 'object' && !Array.isArray(vocRefs)) {
1839
1839
  Object.entries(vocRefs).forEach(([id, def]) => {
1840
1840
  let defOk = true;
@@ -1881,4 +1881,4 @@ function mergeVocRefs(options, message) {
1881
1881
  //-------------------------------------------------------------------------------------------------
1882
1882
  //-------------------------------------------------------------------------------------------------
1883
1883
 
1884
- module.exports = { vocabularyDefinitions, csn2annotationEdm, mergeVocRefs };
1884
+ module.exports = { vocabularyDefinitions, csn2annotationEdm, mergeOdataVocabularies };
@@ -52,7 +52,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
52
52
  fallBackSchemaName,
53
53
  options ] = initializeModel(csn, _options, messageFunctions, serviceNames);
54
54
 
55
- const mergedVocabularies = translate.mergeVocRefs(options, message);
55
+ const mergedVocabularies = translate.mergeOdataVocabularies(options, message);
56
56
 
57
57
  const Edm = getEdm(options, messageFunctions);
58
58
 
package/lib/edm/edm.js CHANGED
@@ -3,7 +3,6 @@
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');
7
6
 
8
7
  // facet definitions, optional could either be true or array of edm types
9
8
  // remove indicates wether or not the canonic facet shall be removed when applying @odata.Type
@@ -766,7 +765,7 @@ function getEdm(options, messageFunctions) {
766
765
  class ComplexType extends TypeBase {
767
766
  constructor(v, details, csn) {
768
767
  super(v, details, csn);
769
- if(this.v4 && !!csn['@open'] && isBetaEnabled(options, 'odataOpenType')) {
768
+ if(this.v4 && !!csn['@open']) {
770
769
  this._edmAttributes['OpenType'] = true;
771
770
  }
772
771
  }
@@ -813,6 +812,13 @@ function getEdm(options, messageFunctions) {
813
812
  this._keys = new Key(v, csn.$edmKeyPaths);
814
813
  else
815
814
  this._keys = undefined;
815
+
816
+ if(options.odataOpenapiHints) {
817
+ if(csn['@cds.autoexpose'])
818
+ this.setJSON({ '@cds.autoexpose': true });
819
+ if(csn['@cds.autoexposed'])
820
+ this.setJSON({ '@cds.autoexposed': true });
821
+ }
816
822
  }
817
823
 
818
824
  innerXML(indent)
@@ -826,6 +832,9 @@ function getEdm(options, messageFunctions) {
826
832
  toJSONattributes(json)
827
833
  {
828
834
  super.toJSONattributes(json);
835
+ this._jsonOnlyAttributes && Object.entries(this._jsonOnlyAttributes).forEach(([p, v]) => {
836
+ json[p[0] === '@' ? p : '$' + p] = v;
837
+ });
829
838
  if(this._keys)
830
839
  {
831
840
  json['$Key'] = this._keys.toJSON();
@@ -1082,7 +1091,6 @@ function getEdm(options, messageFunctions) {
1082
1091
 
1083
1092
  let [src, tgt] = edmUtils.determineMultiplicity(csn._constraints._partnerCsn || csn);
1084
1093
  csn._constraints._multiplicity = csn._constraints._partnerCsn ? [tgt, src] : [src, tgt];
1085
-
1086
1094
  this._type = attributes.Type;
1087
1095
  this._isCollection = this.isToMany();
1088
1096
  this._targetCsn = csn._target;
@@ -1108,7 +1116,7 @@ function getEdm(options, messageFunctions) {
1108
1116
  ? csn._selfReferences[0]
1109
1117
  : csn._constraints._partnerCsn
1110
1118
  : undefined;
1111
- if(partner && partner['@odata.navigable'] !== false) {
1119
+ if(partner && partner['@odata.navigable'] !== false && this._csn._edmParentCsn.kind !== 'type') {
1112
1120
  // $abspath[0] is main entity
1113
1121
  this._edmAttributes.Partner = partner.$abspath.slice(1).join(options.pathDelimiter);
1114
1122
  }
@@ -1139,6 +1147,8 @@ function getEdm(options, messageFunctions) {
1139
1147
  // store Nullable=false and evaluate in determineMultiplicity()
1140
1148
  delete this._edmAttributes.Nullable;
1141
1149
  }
1150
+ // A nav prop has no default value
1151
+ delete this._edmAttributes.DefaultValue;
1142
1152
 
1143
1153
  // store NavProp reference in the model for bidirectional $Partner tagging (done in getReferentialConstraints())
1144
1154
  csn._NavigationProperty = this;
@@ -1154,9 +1164,16 @@ function getEdm(options, messageFunctions) {
1154
1164
  let nodeCsn = csn || this._csn;
1155
1165
  // Set Nullable=false only if 'NOT NULL' was specified in the model
1156
1166
  // Do not derive Nullable=false from key attribute.
1157
- // If an unmanaged association has a cardinality min === max === 1 => Nullable=false
1158
- // If unmanaged assoc has min > 0 and max > 1 => target is 'Collection()' => Nullable is not applicable
1159
- return (nodeCsn.notNull === true || (nodeCsn.on && nodeCsn.cardinality && nodeCsn.cardinality.min === 1 && nodeCsn.cardinality.max === 1));
1167
+ // OR if an association has cardinality.min > 0
1168
+ // If this is a backlink ($self = <from>.<to>) _partnerCsn.cardinality.srcmin > 0 if available
1169
+ // notNull is evaluated for non assoc elements only!
1170
+ // A managed association with unspecified cardinality that is to not null
1171
+ // is effectively a to-min-1 relationship as there must be a value for
1172
+ // the foreign keys (they are not null as well).
1173
+ // During the foreign key generation the minimum cardinality of such an association
1174
+ // is set to 1 as this property is available in the OData CSN.
1175
+ const tgtCard = edmUtils.getEffectiveTargetCardinality(nodeCsn);
1176
+ return (nodeCsn.notNull === true && !nodeCsn.target || tgtCard.min > 0);
1160
1177
  }
1161
1178
  isToMany() {
1162
1179
  return (this._isCollection || this._csn._constraints._multiplicity[1] === '*');
@@ -1200,16 +1217,13 @@ function getEdm(options, messageFunctions) {
1200
1217
  // V4 referential constraints!
1201
1218
  addReferentialConstraintNodes()
1202
1219
  {
1203
- // flip the constrains if this is a $self partner
1204
1220
  let _constraints = this._csn._constraints;
1205
- let [i,j] = [0,1];
1206
1221
  if(this._csn._constraints._partnerCsn) {
1207
1222
  _constraints = this._csn._constraints._partnerCsn._constraints;
1208
- [i,j] = [1,0];
1209
1223
  }
1210
1224
  _constraints.constraints && Object.values(_constraints.constraints).forEach(c =>
1211
1225
  this.append(new ReferentialConstraint(this._v,
1212
- { Property: c[i].join(options.pathDelimiter), ReferencedProperty: c[j].join(options.pathDelimiter) } ) )
1226
+ { Property: c[0].join(options.pathDelimiter), ReferencedProperty: c[1].join(options.pathDelimiter) } ) )
1213
1227
  );
1214
1228
  }
1215
1229
  }
@@ -4,7 +4,7 @@ const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model')
4
4
  const {
5
5
  forEachDefinition, forEachGeneric, forEachMemberRecursively,
6
6
  isEdmPropertyRendered, getUtils, cloneCsnNonDict,
7
- isBuiltinType, applyTransformations, cloneAnnotationValue
7
+ isBuiltinType, applyTransformations, cloneAnnotationValue, cardinality2str,
8
8
  } = require('../model/csnUtils');
9
9
  const edmUtils = require('./edmUtils.js');
10
10
  const edmAnnoPreproc = require('./edmAnnoPreprocessor.js');
@@ -378,7 +378,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
378
378
  // in V4 tag all compositions to be containments
379
379
  if(options.odataContainment &&
380
380
  options.isV4() &&
381
- edmUtils.isComposition(element) &&
381
+ csnUtils.isComposition(element) &&
382
382
  element['@odata.contained'] === undefined) {
383
383
  element['@odata.contained'] = true;
384
384
  }
@@ -862,28 +862,37 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
862
862
  if (element.target && !element._ignore && element._constraints) {
863
863
  edmUtils.finalizeReferentialConstraints(csn, element, options, info);
864
864
 
865
- if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
865
+ if(element._constraints?._partnerCsn) {
866
866
  // if this is a partnership and this assoc has a set target cardinality, assign it as source cardinality to the partner
867
867
  if(element._constraints._partnerCsn.cardinality) {
868
868
  // if the forward association has set a src cardinality and it deviates from the backlink target cardinality raise a warning
869
869
  // in V2 only, in V4 the source cardinality is rendered implicitly at the Type property
870
870
  if(element._constraints._partnerCsn.cardinality.src) {
871
- let srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
872
- let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
873
- if(options.isV2() && srcMult !== newMult) {
874
- // Association 'E_toF': Multiplicity of Role='E' defined to '*', conflicting with target multiplicity '0..1' from
875
- warning(null, null, `Source cardinality "${element._constraints._partnerCsn.cardinality.src}" of "${element._constraints._partnerCsn._parent.name}/${element._constraints._partnerCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`);
871
+ const srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
872
+ const newMult =
873
+ (element.cardinality?.min == 1 && element.cardinality?.max == 1)
874
+ ? '1'
875
+ : (element.cardinality?.max === '*' || element.cardinality?.max > 1)
876
+ ? '*'
877
+ : '0..1';
878
+ if(srcMult !== newMult) {
879
+ warning(null, element.$path, `Explicit source cardinality "${srcMult}" of "${element._constraints._partnerCsn._parent.name}/${element._constraints._partnerCsn.name}" conflicts with target cardinality "${newMult}"`);
876
880
  }
877
881
  }
878
882
  else {
879
883
  // .. but only if the original assoc hasn't set src yet
880
- element._constraints._partnerCsn.cardinality.src = element.cardinality.max;
884
+ element._constraints._partnerCsn.cardinality.src = element.cardinality?.max ? element.cardinality.max : 1;
885
+ if(element.cardinality?.min !== undefined && element._constraints._partnerCsn.cardinality?.srcmin === undefined)
886
+ element._constraints._partnerCsn.cardinality.srcmin = element.cardinality.min;
881
887
  }
882
888
  }
883
889
  else {
884
- element._constraints._partnerCsn.cardinality = { src: element.cardinality.max };
890
+ element._constraints._partnerCsn.cardinality = { src: element.cardinality?.max ? element.cardinality.max : 1 };
891
+ if(element.cardinality?.min !== undefined)
892
+ element._constraints._partnerCsn.cardinality.srcmin = element.cardinality.min;
885
893
  }
886
894
  }
895
+ setProp(element._constraints, '$finalized', true);
887
896
  }
888
897
  }
889
898
 
@@ -1111,7 +1120,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1111
1120
  if (isEdmPropertyRendered(e, options)) {
1112
1121
  let newElt = proxy.elements[e.name];
1113
1122
  if(!newElt) {
1114
- if(csnUtils.isAssocOrComposition(e.type)) {
1123
+ if(csnUtils.isAssocOrComposition(e)) {
1115
1124
  if(!e.on && e.keys) {
1116
1125
  if(options.odataNoTransitiveProxies)
1117
1126
  newElt = convertManagedAssocIntoStruct(e);
@@ -1264,8 +1273,16 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1264
1273
  // a primary key can never be an unmanaged association
1265
1274
  type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
1266
1275
  }
1267
- if(forceToNotNull)
1268
- type.elements[elemName].notNull = true;
1276
+ if(forceToNotNull) {
1277
+ const newElt = type.elements[elemName];
1278
+ if(newElt.target) {
1279
+ if (newElt.cardinality === undefined)
1280
+ newElt.cardinality = {};
1281
+ newElt.cardinality.min = 1;
1282
+ }
1283
+ // if odata-spec-violation-key-null is checking on min>1, this can be an else
1284
+ newElt.notNull = true;
1285
+ }
1269
1286
  setProp(type.elements[elemName], 'name', elem.name);
1270
1287
  });
1271
1288
  return type;
@@ -1556,7 +1573,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1556
1573
 
1557
1574
  let elements = eltCsn.elements || eltCsn.items?.elements;
1558
1575
  if (!elements) {
1559
- const finalType = csnUtils.getFinalBaseTypeWithProps(eltCsn.items?.type || eltCsn.type);
1576
+ const finalType = csnUtils.getFinalTypeInfo(eltCsn.items?.type || eltCsn.type);
1560
1577
  elements = finalType?.elements || finalType?.items?.elements;
1561
1578
  }
1562
1579
  if (elements) {
@@ -1612,10 +1629,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1612
1629
 
1613
1630
  function checkKeySpecViolations(elt, location, pathSegment) {
1614
1631
  // Nullability
1615
- if((!elt.key && (elt.notNull === undefined || elt.notNull === false)) ||
1616
- elt.key && (elt.notNull !== undefined && elt.notNull === false)) {
1632
+ const eltDef = elt.items || elt;
1633
+ if((!elt.key && (eltDef.notNull === undefined || eltDef.notNull === false)) ||
1634
+ elt.key && (eltDef.notNull !== undefined && eltDef.notNull === false)) {
1617
1635
  message('odata-spec-violation-key-null', location,
1618
- {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1636
+ {name: pathSegment ? pathSegment : elt.name, '#': !pathSegment ? 'std' : 'scalar'});
1619
1637
  }
1620
1638
  // many is either directly on elements or on the type
1621
1639
  // due to added proxy types it might be that the type can't be found in definitions
@@ -1623,11 +1641,17 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1623
1641
  (elt.type &&
1624
1642
  !isBuiltinType(elt.type) &&
1625
1643
  csn.definitions[elt.type] &&
1626
- csnUtils.getFinalBaseTypeWithProps(elt.type).items);
1627
- if(type) {
1644
+ csnUtils.getFinalTypeInfo(elt.type).items);
1645
+ if(type ||
1646
+ (options.odataFormat !== 'flat' && !options.odataForeignKeys) &&
1647
+ elt.cardinality?.max && elt.cardinality.max !== 1) {
1628
1648
  // many primary key can be induced by a many parameter of a view
1629
1649
  message('odata-spec-violation-key-array', location,
1630
- {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1650
+ {
1651
+ name: pathSegment ? pathSegment : elt.name,
1652
+ value: cardinality2str(elt),
1653
+ '#': elt.target ? 'assoc' : 'std'
1654
+ });
1631
1655
  }
1632
1656
  // type
1633
1657
  if(!elt.elements) {
@@ -1870,7 +1894,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1870
1894
  // reset visited
1871
1895
  for(const es in typeDef.enum)
1872
1896
  delete typeDef.enum[es].$visited;
1873
-
1897
+
1874
1898
  if(enumSymbolDef) {
1875
1899
  if(enumSymbolDef.val !== undefined) {
1876
1900
  // 'null' value is represented spec conform as empty record in AllowedValues collection
@@ -2072,12 +2096,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
2072
2096
  if (obj.type && isBuiltinType(obj.type) && !obj.target && !obj.targetAspect) {
2073
2097
  let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
2074
2098
  edmUtils.assignProp(obj, '_edmType', edmType);
2075
- } else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.getFinalBaseTypeWithProps(obj.items.type)?.type))) {
2099
+ } else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.getFinalTypeInfo(obj.items.type)?.type))) {
2076
2100
  let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType'], obj.$path);
2077
2101
  edmUtils.assignProp(obj, '_edmType', edmType);
2078
2102
  }
2079
2103
  // This is the special case when we have array of array, but will not be supported in the future
2080
- else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.getFinalBaseTypeWithProps(obj.items.items.type)?.type)) {
2104
+ else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.getFinalTypeInfo(obj.items.items.type)?.type)) {
2081
2105
  let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
2082
2106
  edmUtils.assignProp(obj, '_edmType', edmType);
2083
2107
  }
@@ -61,11 +61,6 @@ function isContainee(artifact) {
61
61
  return (artifact.$containerNames && (artifact.$containerNames.length > 1 || artifact.$containerNames[0] != artifact.name));
62
62
  }
63
63
 
64
- function isComposition(artifact) {
65
- return (artifact.type === 'cds.Composition' || artifact.type === 'Composition') &&
66
- artifact.target != undefined;
67
- }
68
-
69
64
  // Return true if the association 'assoc' has cardinality 'to-many'
70
65
  function isToMany(assoc) {
71
66
  if (!assoc.cardinality) {
@@ -462,13 +457,46 @@ function determineMultiplicity(csn)
462
457
  if(!csn.cardinality.max)
463
458
  csn.cardinality.max = 1;
464
459
 
465
- let srcCardinality = (csn.cardinality.src == 1) ? (isAssoc ? '0..1' : '1') : '*';
466
- let tgtCardinality = (csn.cardinality.max > 1 || csn.cardinality.max === '*') ? '*' :
467
- (csn.cardinality.min == 1) ? '1' : '0..1';
460
+ const srcCardinality =
461
+ (csn.cardinality.src == 1)
462
+ ? (!isAssoc || csn.cardinality.srcmin == 1)
463
+ ? '1'
464
+ : '0..1'
465
+ : '*';
466
+ const tgtCardinality =
467
+ (csn.cardinality.max > 1 || csn.cardinality.max === '*')
468
+ ? '*'
469
+ : (csn.cardinality.min == 1)
470
+ ? '1'
471
+ : '0..1';
468
472
 
469
473
  return [srcCardinality, tgtCardinality];
470
474
  }
471
475
 
476
+ // return effective target cardinality
477
+ // If csn is a backlink, return the source cardinality (including srcmin/src) from
478
+ // the forward association
479
+ // This function works only after finalizeConstraints
480
+ function getEffectiveTargetCardinality(csn) {
481
+ const rc = { min: 0, max: 1 };
482
+ if(!csn._constraints || !csn._constraints.$finalized)
483
+ throw new CompilerAssertion('_constraints missing or not finalized: "' + csn.name);
484
+ // partner (forward) cardinality has precedence
485
+ if(csn._constraints._partnerCsn) {
486
+ if(csn._constraints._partnerCsn.cardinality?.srcmin)
487
+ rc.min = csn._constraints._partnerCsn.cardinality.srcmin;
488
+ if(csn._constraints._partnerCsn.cardinality?.src)
489
+ rc.max = csn._constraints._partnerCsn.cardinality.src;
490
+ }
491
+ else if(csn.cardinality) {
492
+ if(csn.cardinality.min)
493
+ rc.min = csn.cardinality.min;
494
+ if(csn.cardinality.max)
495
+ rc.max = csn.cardinality.max
496
+ }
497
+ return rc;
498
+ }
499
+
472
500
  function mapCdsToEdmType(csn, messageFunctions, isV2=false, isMediaType=false, location=undefined)
473
501
  {
474
502
  if(location === undefined)
@@ -869,7 +897,6 @@ module.exports = {
869
897
  intersect,
870
898
  foreach,
871
899
  isContainee,
872
- isComposition,
873
900
  isToMany,
874
901
  isSingleton,
875
902
  isStructuredType,
@@ -879,6 +906,7 @@ module.exports = {
879
906
  resolveOnConditionAndPrepareConstraints,
880
907
  finalizeReferentialConstraints,
881
908
  determineMultiplicity,
909
+ getEffectiveTargetCardinality,
882
910
  mapCdsToEdmType,
883
911
  addTypeFacets,
884
912
  isODataSimpleIdentifier,
@@ -1518,7 +1518,7 @@
1518
1518
  "ODM.root": {
1519
1519
  "Type": "Core.Tag",
1520
1520
  "AppliesTo": [
1521
- "EntitySet"
1521
+ "EntityType"
1522
1522
  ],
1523
1523
  "$experimental": true
1524
1524
  },
@@ -1526,15 +1526,13 @@
1526
1526
  "Type": "Edm.PropertyPath",
1527
1527
  "AppliesTo": [
1528
1528
  "EntityType"
1529
- ],
1530
- "$experimental": true
1529
+ ]
1531
1530
  },
1532
1531
  "ODM.entityName": {
1533
1532
  "Type": "Edm.String",
1534
1533
  "AppliesTo": [
1535
1534
  "EntityType"
1536
- ],
1537
- "$experimental": true
1535
+ ]
1538
1536
  },
1539
1537
  "Offline.ClientOnly": {
1540
1538
  "Type": "Offline.ClientOnlyType",
@@ -2866,9 +2864,9 @@
2866
2864
  "BaseType": "Common.ValueListParameter",
2867
2865
  "Properties": {
2868
2866
  "Constant": "Edm.PrimitiveType",
2867
+ "InitialValueIsSignificant": "Edm.Boolean",
2869
2868
  "ValueListProperty": "Edm.String"
2870
- },
2871
- "$experimental": true
2869
+ }
2872
2870
  },
2873
2871
  "Common.ValueListParameterInOut": {
2874
2872
  "$kind": "ComplexType",
@@ -1 +1 @@
1
- 371decdc6899d80e3420038878ed211c
1
+ 78bdabed1311fe8b3b0921bfc5300ece