@sap/cds-compiler 4.4.4 → 4.6.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 (82) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/bin/cdsc.js +18 -11
  3. package/bin/cdsv2m.js +7 -5
  4. package/doc/CHANGELOG_BETA.md +22 -0
  5. package/lib/api/main.js +306 -144
  6. package/lib/api/options.js +18 -6
  7. package/lib/api/validate.js +1 -1
  8. package/lib/base/message-registry.js +45 -10
  9. package/lib/base/messages.js +33 -16
  10. package/lib/base/model.js +4 -0
  11. package/lib/base/optionProcessorHelper.js +45 -176
  12. package/lib/checks/annotationsOData.js +49 -0
  13. package/lib/checks/elements.js +32 -34
  14. package/lib/checks/enricher.js +39 -3
  15. package/lib/checks/validator.js +8 -7
  16. package/lib/compiler/assert-consistency.js +40 -17
  17. package/lib/compiler/builtins.js +30 -53
  18. package/lib/compiler/checks.js +46 -14
  19. package/lib/compiler/cycle-detector.js +1 -4
  20. package/lib/compiler/define.js +35 -10
  21. package/lib/compiler/extend.js +21 -7
  22. package/lib/compiler/generate.js +3 -0
  23. package/lib/compiler/populate.js +5 -1
  24. package/lib/compiler/propagator.js +46 -9
  25. package/lib/compiler/resolve.js +94 -35
  26. package/lib/compiler/shared.js +60 -33
  27. package/lib/compiler/tweak-assocs.js +188 -92
  28. package/lib/compiler/utils.js +11 -1
  29. package/lib/edm/annotations/edmJson.js +41 -66
  30. package/lib/edm/annotations/genericTranslation.js +27 -9
  31. package/lib/edm/annotations/preprocessAnnotations.js +2 -3
  32. package/lib/edm/csn2edm.js +28 -11
  33. package/lib/edm/edmInboundChecks.js +58 -15
  34. package/lib/edm/edmPreprocessor.js +12 -16
  35. package/lib/edm/edmUtils.js +5 -2
  36. package/lib/gen/Dictionary.json +10 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +15 -2
  39. package/lib/gen/language.tokens +1 -0
  40. package/lib/gen/languageParser.js +6557 -5618
  41. package/lib/json/from-csn.js +4 -5
  42. package/lib/json/to-csn.js +29 -4
  43. package/lib/language/antlrParser.js +19 -1
  44. package/lib/language/errorStrategy.js +28 -7
  45. package/lib/language/genericAntlrParser.js +118 -24
  46. package/lib/language/textUtils.js +16 -0
  47. package/lib/main.d.ts +28 -3
  48. package/lib/main.js +3 -0
  49. package/lib/model/csnRefs.js +4 -1
  50. package/lib/model/csnUtils.js +20 -14
  51. package/lib/model/revealInternalProperties.js +5 -2
  52. package/lib/optionProcessor.js +23 -22
  53. package/lib/render/manageConstraints.js +13 -29
  54. package/lib/render/toCdl.js +47 -26
  55. package/lib/render/toHdbcds.js +63 -42
  56. package/lib/render/toRename.js +6 -10
  57. package/lib/render/toSql.js +71 -117
  58. package/lib/render/utils/common.js +41 -6
  59. package/lib/transform/.eslintrc.json +9 -1
  60. package/lib/transform/addTenantFields.js +228 -0
  61. package/lib/transform/db/applyTransformations.js +57 -4
  62. package/lib/transform/db/assertUnique.js +4 -4
  63. package/lib/transform/db/backlinks.js +13 -1
  64. package/lib/transform/db/cdsPersistence.js +1 -1
  65. package/lib/transform/db/expansion.js +24 -3
  66. package/lib/transform/db/flattening.js +70 -71
  67. package/lib/transform/db/killAnnotations.js +37 -0
  68. package/lib/transform/db/rewriteCalculatedElements.js +46 -6
  69. package/lib/transform/db/temporal.js +1 -1
  70. package/lib/transform/draft/db.js +2 -16
  71. package/lib/transform/draft/odata.js +3 -3
  72. package/lib/transform/effective/associations.js +3 -5
  73. package/lib/transform/effective/main.js +6 -9
  74. package/lib/transform/forOdata.js +26 -55
  75. package/lib/transform/forRelationalDB.js +38 -18
  76. package/lib/transform/odata/toFinalBaseType.js +3 -3
  77. package/lib/transform/odata/typesExposure.js +14 -5
  78. package/lib/transform/transformUtils.js +47 -34
  79. package/lib/transform/translateAssocsToJoins.js +45 -11
  80. package/lib/transform/universalCsn/coreComputed.js +1 -1
  81. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  82. package/package.json +7 -6
@@ -7,8 +7,8 @@ const {
7
7
  EdmTypeFacetNames,
8
8
  EdmPrimitiveTypeMap,
9
9
  } = require('../EdmPrimitiveTypeDefinitions.js');
10
- const { mapCdsToEdmType } = require('../edmUtils.js');
11
- const { isBuiltinType } = require('../../model/csnUtils');
10
+ const { isBuiltinType, isAnnotationExpression } = require('../../model/csnUtils');
11
+ const { transformExpression } = require('../../transform/db/applyTransformations.js');
12
12
 
13
13
  /**
14
14
  * Translate a given token stream expression into an edmJson representation
@@ -77,9 +77,9 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
77
77
  };
78
78
  //----------------------------------
79
79
  // Error transformer
80
- const notADynExpr = (parent, op, xpr, parentparent, parentprop, txt) => {
80
+ const notADynExpr = (parent, op, xpr, csnPath, parentparent, parentprop, txt) => {
81
81
  error('odata-anno-xpr', location, {
82
- anno, op: txt ??= op, '#': 'notadynexpr',
82
+ anno, op: txt ?? op, '#': 'notadynexpr',
83
83
  });
84
84
  delete parent[op];
85
85
  };
@@ -93,27 +93,27 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
93
93
  //----------------------------------
94
94
  // operators not supported as dynamic expression
95
95
  '.': notADynExpr,
96
- isNull: (p, o) => notADynExpr(p, o, null, null, null, 'is null'),
97
- isNotNull: (p, o) => notADynExpr(p, o, null, null, null, 'is not null'),
96
+ isNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is null'),
97
+ isNotNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is not null'),
98
98
  exists: notADynExpr,
99
99
  '#': notADynExpr,
100
100
  SELECT: notADynExpr,
101
- SET: (p, o) => notADynExpr(p, o, null, null, null, 'UNION'),
101
+ SET: (p, o) => notADynExpr(p, o, null, null, null, null, 'UNION'),
102
102
  like: notADynExpr,
103
103
  new: notADynExpr,
104
104
  };
105
105
 
106
106
  //----------------------------------
107
107
  // list is a $Collection => []
108
- transform.list = (parent, prop, xpr, parentparent, parentprop) => {
108
+ transform.list = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
109
109
  parentparent[parentprop] = xpr.filter(a => a);
110
- applyTransformations(parentparent, transform);
110
+ transformExpression(parentparent, parentprop, transform);
111
111
  };
112
112
  // XPR
113
- transform.xpr = (parent, prop, xpr, parentparent, parentprop) => {
113
+ transform.xpr = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
114
114
  // eliminate 'xpr' node by pulling up xpr node to its parent
115
115
  parentparent[parentprop] = xpr;
116
- applyTransformations(parentparent, transform);
116
+ transformExpression(parentparent, parentprop, transform);
117
117
  };
118
118
  //----------------------------------
119
119
  // CASE
@@ -144,12 +144,14 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
144
144
  curIf.$If.push(caseExpr[i]);
145
145
  parent.$If = edmIf.$If;
146
146
  delete parent.case;
147
- applyTransformations(parent, transform);
147
+ transformExpression(parent, undefined, transform);
148
+ };
149
+ transform.$If = (_parent, _prop, expr) => {
150
+ transformExpression(expr, undefined, transform);
148
151
  };
149
- transform.$If = noOp;
150
152
  //----------------------------------
151
153
  // Cast => $Cast
152
- transform.cast = (parent, prop, castExpr, parentparent, parentprop) => {
154
+ transform.cast = (parent, prop, castExpr, csnPath, parentparent, parentprop) => {
153
155
  const csnType = castExpr[0];
154
156
  // try to resolve to final scalar base type and use that instead of derived type
155
157
  if (!isBuiltinType(csnType.type)) {
@@ -161,7 +163,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
161
163
  });
162
164
  }
163
165
  }
164
- const edmTypeName = mapCdsToEdmType(csnType, messageFunctions, options.isV2(), false, location);
166
+ const edmTypeName = edmUtils.mapCdsToEdmType(csnType, messageFunctions, options.isV2(), false, location);
165
167
  const typeFunc = { func: 'Type', args: [ { val: edmTypeName } ] };
166
168
  const castFunc = { func: '$Cast', args: [ typeFunc, castExpr[1] ] };
167
169
 
@@ -184,7 +186,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
184
186
 
185
187
 
186
188
  parentparent[parentprop] = castFunc;
187
- applyTransformations(parentparent, transform);
189
+ transformExpression(parentparent, parentprop, transform);
188
190
  };
189
191
  //----------------------------------
190
192
  const evalArgs = (argDef, args, propName) => {
@@ -220,7 +222,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
220
222
  evalArgs({ exact }, xpr, prop);
221
223
  parent[opStr] = xpr;
222
224
  delete parent[prop];
223
- applyTransformations(parent, transform);
225
+ transformExpression(parent, undefined, transform);
224
226
  };
225
227
  //----------------------------------
226
228
  // LOGICAL
@@ -249,12 +251,12 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
249
251
  evalArgs({ min: 1 }, xpr[1].list, prop);
250
252
  parent.$In = [ xpr[0], ...xpr[1].list ];
251
253
  delete parent[prop];
252
- applyTransformations(parent, transform);
254
+ transformExpression(parent, undefined, transform);
253
255
  };
254
256
  transform.$In = noOp;
255
- transform.between = (parent, prop, xpr, parentparent, parentprop) => {
257
+ transform.between = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
256
258
  evalArgs({ exact: 2 }, xpr.slice(1), prop);
257
- applyTransformations(xpr, transform);
259
+ transformExpression(xpr, undefined, transform);
258
260
  delete parent[prop];
259
261
  parentparent[parentprop]
260
262
  = {
@@ -264,22 +266,22 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
264
266
  ],
265
267
  };
266
268
  };
267
- transform['||'] = (parent, prop, xpr, parentparent, parentprop) => {
269
+ transform['||'] = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
268
270
  evalArgs({ exact: 2 }, xpr, prop);
269
- applyTransformations(xpr, transform);
271
+ transformExpression(xpr, undefined, transform);
270
272
  delete parent[prop];
271
273
  parentparent[parentprop].$Apply = [ { $Function: 'odata.concat' }, ...xpr ];
272
274
  };
273
275
  //----------------------------------
274
276
  // ARITHMETICAL AND UNARY
275
- transform['+'] = (parent, prop, xpr, parentparent, parentprop) => {
277
+ transform['+'] = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
276
278
  if (Array.isArray(xpr)) {
277
279
  op('$Add')(parent, prop, xpr);
278
280
  }
279
281
  else {
280
282
  delete parent[prop];
281
283
  parentparent[parentprop] = xpr;
282
- applyTransformations(parentparent, transform);
284
+ transformExpression(parentparent, parentprop, transform);
283
285
  }
284
286
  };
285
287
  transform.$Add = noOp;
@@ -295,13 +297,13 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
295
297
  // $DivBy, $Mod are functions
296
298
  //----------------------------------
297
299
  // LITERALS
298
- transform.val = (parent, prop, xpr, parentparent, parentprop) => {
300
+ transform.val = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
299
301
  if (xpr === null)
300
302
  parent.$Null = true;
301
303
  else
302
304
  parentparent[parentprop] = xpr;
303
305
  };
304
- transform.ref = (parent, prop, xpr, parentparent, parentprop) => {
306
+ transform.ref = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
305
307
  if (xpr.some(ps => ps.args || ps.where)) {
306
308
  error('odata-anno-xpr-args', location, {
307
309
  anno, elemref: parent, '#': 'wrongref',
@@ -311,7 +313,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
311
313
  };
312
314
  //----------------------------------
313
315
  // Functions
314
- transform.func = (parent, prop, xpr, parentparent, parentprop) => {
316
+ transform.func = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
315
317
  const rewriteArgs = (argDefs, evalVal = true) => {
316
318
  Object.entries(argDefs).forEach(([ argName, argDef ]) => {
317
319
  const [ foundProps, newArgs ]
@@ -590,7 +592,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
590
592
  delete parent.args;
591
593
  };
592
594
 
593
- const exactArgs = (tgt = parent, x = xpr, count) => {
595
+ const exactArgs = (tgt = parent, x = xpr, count = undefined) => {
594
596
  standard(tgt, x);
595
597
  evalArgs({ exact: count }, tgt[x], xpr);
596
598
  };
@@ -694,7 +696,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
694
696
  // $Record ???
695
697
  $Collection: () => {
696
698
  standard(parentparent, parentprop);
697
- applyTransformations(parentparent, transform);
699
+ transformExpression(parentparent, parentprop, transform);
698
700
  },
699
701
  $Path: () => {
700
702
  oneArg(parent, xpr);
@@ -704,7 +706,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
704
706
  anno, op: `${xpr}(…)`, meta: 'string', '#': 'wrongval_meta',
705
707
  });
706
708
  }
707
- applyTransformations(parentparent, transform);
709
+ transformExpression(parentparent, parentprop, transform);
708
710
  },
709
711
  $Null: () => {
710
712
  parent[xpr] = true;
@@ -724,7 +726,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
724
726
  funcDef.forEach(f => f());
725
727
  else
726
728
  funcDef();
727
- applyTransformations(parent, transform);
729
+ transformExpression(parent, undefined, transform);
728
730
  }
729
731
  else {
730
732
  const funcName = xpr.startsWith('odata.') ? xpr : `odata.${xpr}`;
@@ -740,7 +742,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
740
742
  parentparent[parentprop].$Apply = [ { $Function: funcName }, ...(parent.args || []) ];
741
743
  delete parentparent[parentprop].func;
742
744
  delete parentparent[parentprop].args;
743
- applyTransformations(parentparent, transform);
745
+ transformExpression(parentparent, parentprop, transform);
744
746
  }
745
747
  }
746
748
  else {
@@ -752,41 +754,14 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
752
754
  delete parent[prop];
753
755
  };
754
756
 
755
- return applyTransformations({ annoVal }, {
756
- '=': (parent, prop, xpr, parentparent, parentprop) => {
757
- delete parent['='];
758
- parentparent[parentprop] = applyTransformations({ $edmJson: parseExpr( parent, { array: false }) }, transform);
759
- },
760
- }).annoVal;
761
-
762
- function applyTransformations( parent, customTransformers ) {
763
- function standard( _parent, _prop, node ) {
764
- if (!node || typeof node !== 'object' ||
765
- !{}.propertyIsEnumerable.call( _parent, _prop ) ||
766
- (typeof _prop === 'string' && _prop.startsWith('@')))
767
- return;
768
-
769
-
770
- if (Array.isArray(node)) {
771
- node.forEach( (n, i) => standard( node, i, n ) );
757
+ return transformExpression(carrier, anno, {
758
+ '=': (parent, prop, xpr, csnPath, parentparent, parentprop) => {
759
+ if (isAnnotationExpression(parent)) {
760
+ delete parent['='];
761
+ parentparent[parentprop] = transformExpression({ $edmJson: parseExpr( parent, { array: false }) }, undefined, transform);
772
762
  }
773
- else {
774
- for (const name of Object.getOwnPropertyNames( node )) {
775
- const ct = customTransformers[name];
776
- if (ct) {
777
- if (Array.isArray(ct))
778
- ct.forEach(cti => cti(node, name, node[name], _parent, _prop));
779
- else
780
- ct(node, name, node[name], _parent, _prop);
781
- }
782
- standard(node, name, node[name]);
783
- }
784
- }
785
- }
786
- for (const name of Object.getOwnPropertyNames( parent ))
787
- standard( parent, name, parent[name] );
788
- return parent;
789
- }
763
+ },
764
+ });
790
765
  }
791
766
 
792
767
  // Not everything that can occur in OData annotations can be expressed with
@@ -302,10 +302,10 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
302
302
  if (knownAnnos.length === 0)
303
303
  return;
304
304
  }
305
- if (isBetaEnabled(options, 'annotationExpressions')) {
305
+ if (isBetaEnabled(options, 'odataAnnotationExpressions')) {
306
306
  knownAnnos.forEach((knownAnno) => {
307
307
  if (knownAnno.search(/\.\$edmJson\./g) < 0)
308
- carrier[knownAnno] = xpr2edmJson(carrier, knownAnno, location, options, messageFunctions);
308
+ xpr2edmJson(carrier, knownAnno, location, options, messageFunctions);
309
309
  });
310
310
  }
311
311
 
@@ -676,7 +676,7 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
676
676
 
677
677
  // anno is the full <Annotation Term=...>
678
678
  const anno = handleTerm(fullTermName, prefixTree[voc][term], msg);
679
- if (anno !== undefined) {
679
+ if (!anno?.$isInvalid) {
680
680
  // addAnnotationFunc needs AppliesTo message from dictionary to decide where to put the anno
681
681
  const termName = fullTermName.replace(/#(\w+)$/g, ''); // remove qualifier
682
682
  const dictTerm = getDictTerm(termName, msg); // message for unknown term was already issued in handleTerm
@@ -835,14 +835,16 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
835
835
  }
836
836
  else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
837
837
  // object consists only of properties starting with "@", no $value
838
+ setProp(oTarget, '$isInvalid', true);
838
839
  message('odata-anno-value', msg.location,
839
840
  { anno: msg.anno(), str: 'base', '#': 'nested' } );
840
841
  }
841
842
  else {
842
843
  // regular record
843
844
  if (dTypeIsACollection) {
844
- message('odata-anno-value', msg.location,
845
- { anno: msg.anno(), str: 'structured', '#': 'incompval' });
845
+ message('odata-anno-value', msg.location, {
846
+ anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'incompval',
847
+ });
846
848
  }
847
849
  oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName, dTypeIsACollection, msg));
848
850
  }
@@ -1100,8 +1102,15 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
1100
1102
  }
1101
1103
  }
1102
1104
 
1103
- if ( [ 'Edm.AnnotationPath', 'Edm.ModelElementPath', 'Edm.NavigationPropertyPath', 'Edm.PropertyPath', 'Edm.Path' ].includes(resolvedType) )
1104
- resolvedType = resolvedType.split('.')[1];
1105
+ if ( EdmPathTypeMap[resolvedType] ) {
1106
+ if (resolvedType === 'Edm.AnyPropertyPath') {
1107
+ resolvedType = 'PropertyPath';
1108
+ typeName = resolvedType;
1109
+ }
1110
+ else {
1111
+ resolvedType = resolvedType.split('.')[1];
1112
+ }
1113
+ }
1105
1114
 
1106
1115
  return {
1107
1116
  name: typeName,
@@ -1139,8 +1148,17 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
1139
1148
  actualTypeName = obj.$Type;
1140
1149
  if (!getDictType(actualTypeName)) {
1141
1150
  // this type doesn't exist
1142
- message('odata-anno-type', msg.location,
1143
- { anno: msg.anno(), type: actualTypeName, '#': 'unknown' });
1151
+ if (typeof actualTypeName !== 'string') {
1152
+ actualTypeName = JSON.stringify(obj.$Type);
1153
+ message('odata-anno-type', msg.location,
1154
+ {
1155
+ anno: msg.anno(), code: '$Type', rawvalue: actualTypeName, '#': 'literal',
1156
+ });
1157
+ }
1158
+ else {
1159
+ message('odata-anno-type', msg.location,
1160
+ { anno: msg.anno(), type: actualTypeName, '#': 'unknown' });
1161
+ }
1144
1162
  // explicitly mentioned type, render in XML and JSON
1145
1163
  newRecord.setXml({ Type: actualTypeName });
1146
1164
  // unknown dictionary type: can't fully qualify it
@@ -40,11 +40,10 @@ function preprocessAnnotations( csn, serviceName, options ) {
40
40
  const keyNames = Object.keys(target.elements).filter(x => target.elements[x].key && !target.elements[x].target);
41
41
  if (keyNames.length === 0) {
42
42
  keyNames.push('MISSING');
43
- message('odata-anno-preproc', null, { anno, name: targetName, '#': 'nokey' },
44
- 'target $(NAME) has no key');
43
+ message('odata-anno-preproc', assoc.$path, { anno, name: targetName, '#': 'nokey' } );
45
44
  }
46
45
  else if (keyNames.length > 1) {
47
- message('odata-anno-preproc', null, { anno, name: targetName, '#': 'multkeys' });
46
+ message('odata-anno-preproc', assoc.$path, { anno, name: targetName, '#': 'multkeys' });
48
47
  }
49
48
 
50
49
  return keyNames[0];
@@ -14,7 +14,6 @@ const {
14
14
  cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType, getUtils,
15
15
  } = require('../model/csnUtils');
16
16
  const { checkCSNVersion } = require('../json/csnVersion');
17
- const { makeMessageFunction } = require('../base/messages');
18
17
  const {
19
18
  EdmTypeFacetMap,
20
19
  EdmTypeFacetNames,
@@ -23,21 +22,34 @@ const {
23
22
  const { getEdm } = require('./edm.js');
24
23
 
25
24
  /*
26
- OData V2 spec 06/01/2017 PDF version is available from here:
25
+ OData V2 spec 06/01/2017 PDF version is available here:
27
26
  https://msdn.microsoft.com/en-us/library/dd541474.aspx
28
27
  */
29
28
 
30
- function csn2edm( _csn, serviceName, _options ) {
31
- return csn2edmAll(_csn, _options, [ serviceName ])[serviceName];
29
+ /**
30
+ * @param {CSN.Model} _csn
31
+ * @param {string} serviceName
32
+ * @param {CSN.Options} _options
33
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
34
+ * @return {any}
35
+ */
36
+ function csn2edm( _csn, serviceName, _options, messageFunctions ) {
37
+ return csn2edmAll(_csn, _options, [ serviceName ], messageFunctions)[serviceName];
32
38
  }
33
39
 
34
- function csn2edmAll( _csn, _options, serviceNames = undefined ) {
40
+ /**
41
+ * @param {CSN.Model} _csn
42
+ * @param {CSN.Options} _options
43
+ * @param {string[]|undefined} serviceNames
44
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
45
+ * @return {any}
46
+ */
47
+ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
35
48
  // get us a fresh model copy that we can work with
36
49
  const csn = cloneCsnNonDict(_csn, _options);
37
50
  const special$self = !csn?.definitions?.$self && '$self';
51
+ messageFunctions.setModel(csn);
38
52
 
39
- // use original options for messages; cloned CSN for semantic location
40
- const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
41
53
  const {
42
54
  info, warning, error, message, throwWithError,
43
55
  } = messageFunctions;
@@ -477,7 +489,10 @@ function csn2edmAll( _csn, _options, serviceNames = undefined ) {
477
489
  const type = `${schema.name}.${EntityTypeName}`;
478
490
  if (properties.length === 0)
479
491
  warning(null, location, { type }, 'EDM EntityType $(TYPE) has no properties');
480
- else if (entityCsn.$edmKeyPaths.length === 0 && !isSingleton)
492
+ // only if this entity has an entity set, it is required to have a key
493
+ // this especially covers: 'items: composition of one { data : String; }'
494
+ // "keyless" composition targets in structured containment mode
495
+ else if (entityCsn.$hasEntitySet && entityCsn.$edmKeyPaths.length === 0 && !isSingleton)
481
496
  message('odata-spec-violation-no-key', location);
482
497
 
483
498
  if (!edmUtils.isODataSimpleIdentifier(EntityTypeName))
@@ -627,15 +642,17 @@ function csn2edmAll( _csn, _options, serviceNames = undefined ) {
627
642
  else if (isEdmPropertyRendered(elementCsn, options)) {
628
643
  // CDXCORE-CDXCORE-173
629
644
  // V2: filter @Core.MediaType
630
- if ( options.isV2() && elementCsn['@Core.MediaType']) {
645
+ if (options.isV2() && elementCsn['@Core.MediaType']) {
631
646
  hasStream = elementCsn['@Core.MediaType'];
632
- delete elementCsn['@Core.MediaType'];
647
+ elementCsn['@cds.api.ignore'] = true;
633
648
  // CDXCORE-CDXCORE-177:
634
649
  // V2: don't render element but add attribute 'm:HasStream="true' to EntityType
635
- // V4: render property type 'Edm.Stream'
636
650
  streamProps.push(elementName);
637
651
  }
638
652
  else {
653
+ // V4: render property type 'Edm.Stream' but don't add '@Core.IsURL'
654
+ if ( elementCsn['@Core.MediaType'])
655
+ delete elementCsn['@Core.IsURL'];
639
656
  collectUsedType(elementCsn);
640
657
  props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
641
658
  }
@@ -2,8 +2,9 @@
2
2
 
3
3
  const { setProp, isBetaEnabled } = require('../base/model');
4
4
  const {
5
- forEachDefinition, forEachMemberRecursively,
5
+ forEachDefinition, forEachMemberRecursively, isBuiltinType, getUtils,
6
6
  } = require('../model/csnUtils');
7
+ const { assignAnnotation } = require('./edmUtils.js');
7
8
 
8
9
  // eslint-disable-next-line no-unused-vars
9
10
  function resolveForeignKeyRefs( csn, csnUtils ) {
@@ -22,9 +23,11 @@ function resolveForeignKeyRefs( csn, csnUtils ) {
22
23
 
23
24
  function inboundQualificationChecks( csn, options, messageFunctions,
24
25
  serviceRootNames, requestedServiceNames, isMyServiceRequested, whatsMyServiceRootName, csnUtils ) {
25
- const { message, throwWithError } = messageFunctions;
26
+ const { message, warning, throwWithError } = messageFunctions;
26
27
 
27
- forEachDefinition(csn, [ attach$path, checkProperArrayUsage ]);
28
+ const { getFinalTypeInfo } = getUtils(csn);
29
+
30
+ forEachDefinition(csn, [ attach$path, onServiceMember ]);
28
31
  checkNestedContextsAndServices();
29
32
  throwWithError();
30
33
 
@@ -37,27 +40,67 @@ function inboundQualificationChecks( csn, options, messageFunctions,
37
40
  }, [ 'definitions', defName ]);
38
41
  }
39
42
 
40
- function checkProperArrayUsage( def, defName ) {
43
+ // code that should be run only on service members
44
+ function onServiceMember( def, defName ) {
41
45
  if (!isMyServiceRequested(defName))
42
46
  return;
43
- const currPath = [ 'definitions', defName ];
44
- checkIfItemsOfItems(def, undefined, undefined, currPath);
45
- forEachMemberRecursively(def, checkIfItemsOfItems, currPath);
46
-
47
- function checkIfItemsOfItems( construct, _constructName, _prop, path ) {
48
- const constructType = csnUtils.effectiveType(construct);
49
- if (constructType.items) {
50
- if (constructType.items.target) {
51
- const isComp = constructType.items.type === 'cds.Composition';
47
+
48
+ const location = [ 'definitions', defName ];
49
+ // check items.items
50
+ checkIfItemsOfItems(def, undefined, undefined, location);
51
+ forEachMemberRecursively(def, checkIfItemsOfItems, location);
52
+
53
+ // decorate UUID keys with @Core.ComputedDefaultValue and complain
54
+ // on named type UUID elements that have no such annotation
55
+ const anno = '@Core.ComputedDefaultValue';
56
+ if (def.kind === 'entity' && def.elements) {
57
+ Object.entries(def.elements).forEach(([ eltName, elt ]) => {
58
+ if (elt.key)
59
+ addCoreComputedDefaultValueOnUUIDKeys(elt, [ eltName ], location);
60
+ });
61
+ }
62
+
63
+ function addCoreComputedDefaultValueOnUUIDKeys( elt, eltPath, path ) {
64
+ let type = elt.items?.type || elt.type;
65
+ if (type && !isBuiltinType(type)) {
66
+ type = getFinalTypeInfo(type);
67
+ if (!isBuiltinType(type.type))
68
+ path = [ 'definitions', type.type ];
69
+ }
70
+ else {
71
+ type = elt;
72
+ }
73
+
74
+ if (type.type === 'cds.UUID' && elt['@odata.foreignKey4'] == null) {
75
+ if (path[1] === defName)
76
+ assignAnnotation(elt, anno, true);
77
+ else if (elt[anno] == null)
78
+ warning('odata-key-uuid-default-anno', path, { type: type.type, anno, id: `${defName}:${eltPath.join('.')}` });
79
+ }
80
+ else {
81
+ const elements = type.items?.elements || type.elements;
82
+ if (elements) {
83
+ Object.entries(elements).forEach(([ eltName, subelt ]) => {
84
+ addCoreComputedDefaultValueOnUUIDKeys(subelt, [ ...eltPath, eltName ], [ ...path, 'elements', eltName ]);
85
+ });
86
+ }
87
+ }
88
+ }
89
+
90
+ function checkIfItemsOfItems( member, _memberName, _prop, path ) {
91
+ const memberType = csnUtils.effectiveType(member);
92
+ if (memberType.items) {
93
+ if (memberType.items.target) {
94
+ const isComp = memberType.items.type === 'cds.Composition';
52
95
  message('type-invalid-items', path, { '#': isComp ? 'comp' : 'assoc', prop: 'items' });
53
96
  return;
54
97
  }
55
- if (constructType.items.items) {
98
+ if (memberType.items.items) {
56
99
  message('chained-array-of', path);
57
100
  return;
58
101
  }
59
102
 
60
- const itemsType = csnUtils.effectiveType(constructType.items);
103
+ const itemsType = csnUtils.effectiveType(memberType.items);
61
104
  if (itemsType.items)
62
105
  message('chained-array-of', path);
63
106
  }
@@ -5,7 +5,7 @@ const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model')
5
5
  const {
6
6
  forEachDefinition, forEachGeneric, forEachMemberRecursively,
7
7
  isEdmPropertyRendered, getUtils, cloneCsnNonDict,
8
- isBuiltinType, applyTransformations, cloneAnnotationValue, cardinality2str,
8
+ isBuiltinType, applyTransformations, cloneAnnotationValue, cardinality2str, isAnnotationExpression,
9
9
  } = require('../model/csnUtils');
10
10
  const edmUtils = require('./edmUtils.js');
11
11
  const edmAnnoPreproc = require('./edmAnnoPreprocessor.js');
@@ -97,7 +97,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
97
97
  */
98
98
  if (csn.meta && csn.meta.options && csn.meta.options.odataVersion === 'v4' && options.isV2()) {
99
99
  // eslint-disable-next-line global-require
100
- const { toFinalBaseType } = require('../transform/transformUtils').getTransformers(csn, options);
100
+ const { toFinalBaseType } = require('../transform/transformUtils').getTransformers(csn, options, messageFunctions);
101
101
  expandCSNToFinalBaseType(csn, { toFinalBaseType }, csnUtils, serviceRootNames, options);
102
102
  }
103
103
 
@@ -168,7 +168,6 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
168
168
  initEdmNavPropBindingTargets,
169
169
  pullupCapabilitiesAnnotations,
170
170
  annotateOptionalActFuncParams,
171
- openService,
172
171
  ]);
173
172
  }
174
173
 
@@ -722,11 +721,11 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
722
721
  // FK creation.
723
722
  // The FK creation already propagates the annotations from the association
724
723
  const elements = construct.items && construct.items.elements || construct.elements;
725
- const fk = elements[element['@odata.foreignKey4']];
726
- for (const attrName in fk) {
727
- const attr = fk[attrName];
728
- if (attrName[0] === '@')
729
- edmUtils.assignAnnotation(element, attrName, attr);
724
+ const assoc = elements[element['@odata.foreignKey4']];
725
+ if (assoc) {
726
+ Object.keys(assoc).filter(pn => pn[0] === '@' && !isAnnotationExpression(assoc[pn])).forEach((pn) => {
727
+ edmUtils.assignAnnotation(element, pn, assoc[pn]);
728
+ });
730
729
  }
731
730
  // and eventually remove some afterwards
732
731
  if (options.isV2())
@@ -1075,8 +1074,10 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1075
1074
 
1076
1075
  function muteNavProp( elt, msg = 'std' ) {
1077
1076
  edmUtils.assignAnnotation(elt, '@odata.navigable', false);
1078
- warning('odata-navigation', [ 'definitions', struct.name, 'elements', elt.name ],
1079
- { target: elt._target.name, service: globalSchemaPrefix, '#': msg });
1077
+ if (elt._target['@cds.autoexpose'] !== false) {
1078
+ warning('odata-navigation', [ 'definitions', struct.name, 'elements', elt.name ],
1079
+ { target: elt._target.name, service: globalSchemaPrefix, '#': msg });
1080
+ }
1080
1081
  }
1081
1082
 
1082
1083
  function createSchemaRefFor( targetSchemaName ) {
@@ -2125,7 +2126,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
2125
2126
  optPns.forEach((op) => {
2126
2127
  const type = op.items?.type || op.type;
2127
2128
  if (type !== special$self)
2128
- warning('odata-parameter-order', location.concat(op.name));
2129
+ error('odata-parameter-order', location.concat(op.name));
2129
2130
  });
2130
2131
  optPns = [];
2131
2132
  }
@@ -2134,11 +2135,6 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
2134
2135
  }
2135
2136
  }
2136
2137
 
2137
- function openService( def ) {
2138
- if (options.odataOpenType && !def.$isParamEntity && !def.$proxy)
2139
- edmUtils.assignAnnotation(def, '@open', true);
2140
- }
2141
-
2142
2138
  // ////////////////////////////////////////////////////////////////////
2143
2139
  //
2144
2140
  // Helper section starts here
@@ -551,8 +551,11 @@ function mapCdsToEdmType( csn, messageFunctions, isV2 = false, isMediaType = fal
551
551
  Edm.GeometryCollection
552
552
  */
553
553
  }[cdsType];
554
- if (!edmType)
555
- error(null, location, { type: cdsType }, 'No EDM type available for $(TYPE)');
554
+ if (!edmType) {
555
+ error('ref-unsupported-type', location, { type: cdsType, '#': 'odata' });
556
+ edmType = 'Edm.PrimitiveType';
557
+ }
558
+
556
559
 
557
560
  if (isV2) {
558
561
  if (edmType === 'Edm.Date')
@@ -801,6 +801,13 @@
801
801
  "Parameter"
802
802
  ]
803
803
  },
804
+ "Common.ValueListShowValuesImmediately": {
805
+ "Type": "Core.Tag",
806
+ "AppliesTo": [
807
+ "Annotation"
808
+ ],
809
+ "$experimental": true
810
+ },
804
811
  "Common.ValueListForValidation": {
805
812
  "Type": "Edm.String",
806
813
  "AppliesTo": [
@@ -2069,6 +2076,8 @@
2069
2076
  "Type": "Validation.ConstraintType",
2070
2077
  "AppliesTo": [
2071
2078
  "Property",
2079
+ "NavigationProperty",
2080
+ "Parameter",
2072
2081
  "EntityType",
2073
2082
  "ComplexType"
2074
2083
  ]
@@ -2956,6 +2965,7 @@
2956
2965
  "Common.IntervalType": {
2957
2966
  "$kind": "ComplexType",
2958
2967
  "Properties": {
2968
+ "Label": "Edm.String",
2959
2969
  "LowerBoundary": "Edm.PropertyPath",
2960
2970
  "LowerBoundaryIncluded": "Edm.Boolean",
2961
2971
  "UpperBoundary": "Edm.PropertyPath",