@sap/cds-compiler 3.1.2 → 3.3.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 (100) hide show
  1. package/CHANGELOG.md +80 -3
  2. package/bin/cdsc.js +1 -1
  3. package/doc/CHANGELOG_BETA.md +18 -0
  4. package/lib/api/main.js +8 -13
  5. package/lib/base/error.js +2 -2
  6. package/lib/base/keywords.js +2 -24
  7. package/lib/base/message-registry.js +43 -14
  8. package/lib/base/messages.js +20 -10
  9. package/lib/base/model.js +1 -1
  10. package/lib/checks/actionsFunctions.js +1 -1
  11. package/lib/checks/annotationsOData.js +2 -2
  12. package/lib/checks/arrayOfs.js +15 -7
  13. package/lib/checks/cdsPersistence.js +1 -1
  14. package/lib/checks/checkForTypes.js +48 -0
  15. package/lib/checks/defaultValues.js +2 -2
  16. package/lib/checks/elements.js +81 -6
  17. package/lib/checks/foreignKeys.js +12 -13
  18. package/lib/checks/invalidTarget.js +10 -11
  19. package/lib/checks/managedInType.js +21 -15
  20. package/lib/checks/nullableKeys.js +1 -1
  21. package/lib/checks/onConditions.js +9 -9
  22. package/lib/checks/parameters.js +21 -0
  23. package/lib/checks/selectItems.js +1 -1
  24. package/lib/checks/types.js +2 -2
  25. package/lib/checks/utils.js +17 -7
  26. package/lib/checks/validator.js +26 -14
  27. package/lib/compiler/assert-consistency.js +13 -6
  28. package/lib/compiler/builtins.js +8 -0
  29. package/lib/compiler/checks.js +40 -33
  30. package/lib/compiler/define.js +50 -44
  31. package/lib/compiler/extend.js +303 -37
  32. package/lib/compiler/kick-start.js +2 -35
  33. package/lib/compiler/populate.js +83 -62
  34. package/lib/compiler/propagator.js +1 -1
  35. package/lib/compiler/resolve.js +61 -104
  36. package/lib/compiler/shared.js +16 -6
  37. package/lib/compiler/tweak-assocs.js +25 -12
  38. package/lib/compiler/utils.js +2 -2
  39. package/lib/edm/annotations/genericTranslation.js +3 -3
  40. package/lib/edm/csn2edm.js +10 -10
  41. package/lib/edm/edm.js +17 -9
  42. package/lib/edm/edmPreprocessor.js +53 -30
  43. package/lib/edm/edmUtils.js +7 -2
  44. package/lib/gen/Dictionary.json +14 -0
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +3 -2
  47. package/lib/gen/languageParser.js +4205 -4100
  48. package/lib/inspect/inspectModelStatistics.js +1 -1
  49. package/lib/inspect/inspectPropagation.js +23 -9
  50. package/lib/json/csnVersion.js +1 -1
  51. package/lib/json/from-csn.js +26 -19
  52. package/lib/json/to-csn.js +47 -5
  53. package/lib/language/antlrParser.js +1 -1
  54. package/lib/language/genericAntlrParser.js +29 -13
  55. package/lib/language/language.g4 +28 -8
  56. package/lib/main.d.ts +3 -6
  57. package/lib/model/.eslintrc.json +13 -0
  58. package/lib/model/api.js +4 -2
  59. package/lib/model/csnRefs.js +74 -47
  60. package/lib/model/csnUtils.js +236 -218
  61. package/lib/model/enrichCsn.js +41 -31
  62. package/lib/model/revealInternalProperties.js +61 -57
  63. package/lib/model/sortViews.js +31 -31
  64. package/lib/modelCompare/compare.js +6 -6
  65. package/lib/optionProcessor.js +5 -0
  66. package/lib/render/manageConstraints.js +2 -2
  67. package/lib/render/toCdl.js +31 -44
  68. package/lib/render/toHdbcds.js +7 -5
  69. package/lib/render/toRename.js +4 -4
  70. package/lib/render/toSql.js +11 -5
  71. package/lib/render/utils/common.js +20 -9
  72. package/lib/render/utils/sql.js +5 -5
  73. package/lib/transform/db/applyTransformations.js +32 -3
  74. package/lib/transform/db/expansion.js +81 -37
  75. package/lib/transform/db/flattening.js +1 -1
  76. package/lib/transform/db/temporal.js +1 -1
  77. package/lib/transform/db/transformExists.js +1 -1
  78. package/lib/transform/forOdataNew.js +10 -7
  79. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +7 -7
  80. package/lib/transform/localized.js +28 -19
  81. package/lib/transform/odata/toFinalBaseType.js +8 -11
  82. package/lib/transform/odata/typesExposure.js +1 -1
  83. package/lib/transform/transformUtilsNew.js +101 -39
  84. package/lib/transform/translateAssocsToJoins.js +5 -4
  85. package/lib/utils/moduleResolve.js +5 -5
  86. package/lib/utils/objectUtils.js +3 -3
  87. package/package.json +2 -2
  88. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  89. package/share/messages/check-proper-type-of.md +4 -4
  90. package/share/messages/check-proper-type.md +2 -2
  91. package/share/messages/duplicate-autoexposed.md +4 -4
  92. package/share/messages/extend-repeated-intralayer.md +4 -5
  93. package/share/messages/extend-unrelated-layer.md +4 -4
  94. package/share/messages/message-explanations.json +3 -1
  95. package/share/messages/redirected-to-ambiguous.md +7 -6
  96. package/share/messages/redirected-to-complex.md +63 -0
  97. package/share/messages/redirected-to-unrelated.md +6 -5
  98. package/share/messages/rewrite-not-supported.md +4 -4
  99. package/share/messages/syntax-expected-integer.md +3 -3
  100. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -534,16 +534,21 @@ function fns( model ) {
534
534
  // if the artifact does not exist. Return a "fresh" artifact for
535
535
  // non-existing external using references if `unchecked` is truthy.
536
536
  function getPathRoot( path, spec, user, env, extDict, msgArt ) {
537
- if (!spec.envFn && user._pathHead) {
538
- // TODO: not necessarily for explicit ON condition in expand
537
+ const head = path[0];
538
+ if (!head || !head.id || !env)
539
+ return undefined; // parse error
540
+ if (!spec.envFn && user._pathHead && head.id.charAt(0) !== '$') {
541
+ if (spec.rootEnv === 'elements') { // ON condition in expand/inline
542
+ let root = user._pathHead;
543
+ while (root.kind === '$inline')
544
+ root = root._parent;
545
+ return root;
546
+ }
539
547
  VolatileFns.environment( user._pathHead ); // make sure _origin is set
540
548
  return user._pathHead._origin;
541
549
  // const { _origin } = user._pathHead;
542
550
  // return (_origin && _origin.kind === '$tableAlias') ? _origin._origin : _origin;
543
551
  }
544
- const head = path[0];
545
- if (!head || !head.id || !env)
546
- return undefined; // parse error
547
552
  // if (head.id === 'k') {console.log(Object.keys(user));throw Error(JSON.stringify(user.name))}
548
553
  // if head._artifact is set or is null then it was already computed once
549
554
  if ('_artifact' in head)
@@ -576,7 +581,7 @@ function fns( model ) {
576
581
  message( 'ref-obsolete-parameters', [ head.location, user ],
577
582
  { code: `$parameters.${ path[1].id }`, newcode: `:${ path[1].id }` },
578
583
  'Obsolete $(CODE) - replace by $(NEWCODE)' );
579
- // TODO: replace it in to-csn correspondingly
584
+ // TODO: replace it in to-csn correspondingly !!!
580
585
  return setArtifactLink( head, r );
581
586
  }
582
587
  }
@@ -685,6 +690,9 @@ function fns( model ) {
685
690
  // search environment (for the first path item) is `arg`. For messages about
686
691
  // missing artifacts (as opposed to elements), provide the `head` (first
687
692
  // element item in the path)
693
+ // TODO - think about setting _navigation for all $navElement – the
694
+ // "ref: ['tabAlias']: inline: […]" handling might be easier
695
+ // (no _pathHead consultation for key prop and renaming support)
688
696
  function getPathItem( path, spec, user, artItemsCount, headArt ) {
689
697
  // let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
690
698
  let art = headArt;
@@ -889,6 +897,8 @@ function fns( model ) {
889
897
  // (TODO: really here?, probably split main artifacts vs returns)
890
898
  // see also lateExtensions() where similar messages are reported
891
899
  function checkAnnotate( construct, art ) {
900
+ // TODO: Handle extend statements properly: Different message for empty extend?
901
+
892
902
  // Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
893
903
  // they can still be applied. Namespace annotations are extracted in to-csn.js
894
904
  // In parseCdl mode USINGs and other unknown references are generated as
@@ -98,6 +98,7 @@ function tweakAssocs( model ) {
98
98
  } );
99
99
  }
100
100
  else {
101
+ // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
101
102
  info( 'assoc-outside-service', [ elem.target.location, elem ],
102
103
  { target },
103
104
  'Association target $(TARGET) is outside any service' );
@@ -283,11 +284,12 @@ function tweakAssocs( model ) {
283
284
  // managed association as sub element not supported yet
284
285
  error( null, [ elem.location, elem ], {},
285
286
  // eslint-disable-next-line max-len
286
- 'Rewriting the ON condition of unmanaged association in sub element is not supported' );
287
+ 'Rewriting the ON-condition of unmanaged association in sub element is not supported' );
287
288
  return;
288
289
  }
289
- const nav = (elem._main && elem._main.query) ? pathNavigation( elem.value )
290
- : { navigation: assoc };
290
+ const nav = (elem._main && elem._main.query && elem.value)
291
+ ? pathNavigation( elem.value ) // redirected source elem or mixin
292
+ : { navigation: assoc }; // redirected user-provided
291
293
  const cond = copyExpr( assoc.on,
292
294
  // replace location in ON except if from mixin element
293
295
  nav.tableAlias && elem.name.location );
@@ -305,7 +307,7 @@ function tweakAssocs( model ) {
305
307
  // For "assoc1.assoc2" and "structelem1.assoc2"
306
308
  if (elem._redirected !== null) { // null = already reported
307
309
  error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
308
- 'The ON condition is not rewritten here - provide an explicit ON condition' );
310
+ 'The ON-condition is not rewritten here - provide an explicit ON-condition' );
309
311
  }
310
312
  return;
311
313
  }
@@ -314,7 +316,7 @@ function tweakAssocs( model ) {
314
316
  }
315
317
  else {
316
318
  // TODO: support that, now that the ON condition is rewritten in the right order
317
- error( null, [ elem.value.location, elem ],
319
+ error( null, [ elem.value.location, elem ], {},
318
320
  'Selecting unmanaged associations from a sub query is not supported' );
319
321
  }
320
322
  cond.$inferred = 'rewrite';
@@ -325,7 +327,6 @@ function tweakAssocs( model ) {
325
327
  // 'assoc' in query or including entity from ON cond of mixin element /
326
328
  // element in included structure / element in source ref/d by table alias.
327
329
 
328
- // TODO: re-check args in references, forbid parameter use for the moment
329
330
  // TODO: complain about $self (unclear semantics)
330
331
  // console.log( info(null, [assoc.name.location, assoc],
331
332
  // { art: expr._artifact, names: expr.path.map(i=>i.id) }, 'A').toString(), expr.path)
@@ -341,13 +342,25 @@ function tweakAssocs( model ) {
341
342
  // { names: expr.path.map(i=>i.id), art: root }, 'TA').toString())
342
343
  if (!root || root._main !== source)
343
344
  return; // not $self or source element
345
+ if (expr.scope === 'param' || root.kind === '$parameters')
346
+ return; // are not allowed anyway - there was an error before
344
347
  const item = expr.path[root.kind === '$self' ? 1 : 0];
345
348
  // console.log('YE', assoc.name, item, root.name, expr.path)
346
- rewritePath( expr, item, assoc,
347
- navProjection( item && tableAlias.elements[item.id], assoc ),
348
- assoc.value.location );
349
+ const elem = navProjection( item && tableAlias.elements[item.id], assoc );
350
+ rewritePath( expr, item, assoc, elem, assoc.value.location );
349
351
  }
350
352
  else if (assoc._main.query) { // from ON cond of mixin element in query
353
+ const root = expr.path[0]._navigation || expr.path[0]._artifact;
354
+ if (expr.scope === 'param' || root?.kind === '$parameters') {
355
+ if (assoc.$errorReported !== 'assoc-unexpected-scope') {
356
+ error( 'assoc-unexpected-scope', [ assoc.value.location, assoc ],
357
+ { id: assoc.value._artifact.name.id },
358
+ // eslint-disable-next-line max-len
359
+ 'Association $(ID) can\'t be projected because its ON-condition refers to a parameter' );
360
+ assoc.$errorReported = 'assoc-unexpected-scope';
361
+ }
362
+ return;
363
+ }
351
364
  const nav = pathNavigation( expr );
352
365
  if (nav.navigation || nav.tableAlias) { // rewrite src elem, mixin, $self[.elem]
353
366
  rewritePath( expr, nav.item, assoc,
@@ -372,9 +385,9 @@ function tweakAssocs( model ) {
372
385
  { art: art && effectiveType( art ) },
373
386
  {
374
387
  // eslint-disable-next-line max-len
375
- std: 'This element is not originally referred to in the ON condition of association $(ART)',
388
+ std: 'This element is not originally referred to in the ON-condition of association $(ART)',
376
389
  // eslint-disable-next-line max-len
377
- element: 'This element is not originally referred to in the ON condition of association $(MEMBER) of $(ART)',
390
+ element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
378
391
  } );
379
392
  }
380
393
  rewritePath( expr, item, assoc, (Array.isArray(elem) ? false : elem), null );
@@ -473,7 +486,7 @@ function tweakAssocs( model ) {
473
486
  // TODO: better (extra message), TODO: do it
474
487
  error( 'query-undefined-element', [ item.location, assoc ], { id: name || item.id },
475
488
  // eslint-disable-next-line max-len
476
- 'Element $(ID) has not been found in the elements of the query; please use REDIRECTED TO with an explicit ON condition' );
489
+ 'Element $(ID) has not been found in the elements of the query; please use REDIRECTED TO with an explicit ON-condition' );
477
490
  return (elem) ? false : null;
478
491
  }
479
492
  }
@@ -33,7 +33,7 @@ function annotationIsFalse( anno ) { // falsy, but not null (u
33
33
  }
34
34
  function annotationHasEllipsis( anno ) {
35
35
  const { val } = anno || {};
36
- return Array.isArray( val ) && val.some( v => v.literal === 'token' && v.val === '...' );
36
+ return Array.isArray( val ) && val.find( v => v.literal === 'token' && v.val === '...' );
37
37
  }
38
38
 
39
39
  /**
@@ -87,7 +87,7 @@ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
87
87
  setMemberParent( elem, name, parent, prop ); // TODO: redef in template
88
88
  setLink( elem, '_origin', origin );
89
89
  // TODO: should we use silent dependencies also for other things, like
90
- // included elements? (Currently for $inferred: 'expand-element' only)
90
+ // included elements? (Currently for $inferred: 'expanded' only)
91
91
  if (silentDep)
92
92
  dependsOnSilent( elem, origin );
93
93
  else
@@ -699,7 +699,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
699
699
  }
700
700
  if (innerAnnotation) {
701
701
  // A voc annotation has two steps (Namespace+Name),
702
- // any furter steps need to be rendered separately
702
+ // any further steps need to be rendered separately
703
703
  const innerAnnoSteps = innerAnnotation.split('.');
704
704
  const tailSteps = innerAnnoSteps.splice(2, innerAnnoSteps.length-2);
705
705
  // prepend annotation prefix (path) to tail steps
@@ -1303,7 +1303,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
1303
1303
  function handleEdmJson(obj, context, exprDef=undefined) {
1304
1304
 
1305
1305
  let edmNode = undefined;
1306
- if(obj === undefined)
1306
+ if(obj === undefined || obj === null)
1307
1307
  return edmNode;
1308
1308
 
1309
1309
  const dynExprs = edmUtils.intersect(dynamicExpressionNames, Object.keys(obj));
@@ -1314,7 +1314,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
1314
1314
  }
1315
1315
 
1316
1316
  if (dynExprs.length === 0) {
1317
- if (typeof obj === 'object' && obj !== null && !Array.isArray(obj) && Object.keys(obj).length === 1) {
1317
+ if (typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 1) {
1318
1318
  const k = Object.keys(obj)[0];
1319
1319
  const val = obj[k];
1320
1320
  edmNode = new Edm.ValueThing(v, k[0] === '$' ? k.slice(1) : k, val );
@@ -305,9 +305,9 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
305
305
  const reservedNames = ['Edm', 'odata', 'System', 'Transient'];
306
306
  const loc = ['definitions', schema.name];
307
307
  if(reservedNames.includes(schema.name))
308
- warning('odata-spec-violation-namespace', loc, { names: reservedNames });
308
+ message('odata-spec-violation-namespace', loc, { names: reservedNames });
309
309
  if (schema.name.length > 511)
310
- warning('odata-spec-violation-namespace', loc, { '#': 'length' });
310
+ message('odata-spec-violation-namespace', loc, { '#': 'length' });
311
311
  else {
312
312
  schema.name.split('.').forEach(id => {
313
313
  if (!edmUtils.isODataSimpleIdentifier(id))
@@ -413,10 +413,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
413
413
  const pLoc = [...loc, 'elements', p._edmAttributes.Name];
414
414
  edmTypeCompatibilityCheck(p, pLoc);
415
415
  if(p._edmAttributes.Name === EntityTypeName)
416
- warning('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
416
+ message('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
417
417
 
418
418
  if(options.isV2() && p._isCollection && !p._csn.target)
419
- warning('odata-spec-violation-array', pLoc, { version: '2.0' });
419
+ message('odata-spec-violation-array', pLoc, { version: '2.0' });
420
420
 
421
421
  if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
422
422
  message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
@@ -626,10 +626,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
626
626
  !param._type.startsWith('Edm.') &&
627
627
  csn.definitions[param._type] &&
628
628
  !edmUtils.isStructuredType(csn.definitions[param._type]))
629
- warning('odata-spec-violation-param', pLoc, { version: '2.0' });
629
+ message('odata-spec-violation-param', pLoc, { version: '2.0' });
630
630
 
631
631
  if(param._isCollection)
632
- warning('odata-spec-violation-array', pLoc, { version: '2.0' });
632
+ message('odata-spec-violation-array', pLoc, { version: '2.0' });
633
633
 
634
634
  functionImport.append(param);
635
635
  });
@@ -645,7 +645,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
645
645
  let type = returns.type;
646
646
  if (type) {
647
647
  if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
648
- warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });
648
+ message('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });
649
649
  }
650
650
  else if(isBuiltinType(type)) {
651
651
  type = edmUtils.mapCdsToEdmType(returns, messageFunctions, true);
@@ -764,17 +764,17 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
764
764
  const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p._edmAttributes.Name ];
765
765
  edmTypeCompatibilityCheck(p, pLoc);
766
766
  if(p._edmAttributes.Name === complexType._edmAttributes.Name)
767
- warning('odata-spec-violation-property-name', pLoc, { kind: structuredTypeCsn.kind });
767
+ message('odata-spec-violation-property-name', pLoc, { kind: structuredTypeCsn.kind });
768
768
 
769
769
  if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
770
770
  message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
771
771
 
772
772
  if(options.isV2()) {
773
773
  if(p._isCollection && !p._csn.target)
774
- warning('odata-spec-violation-array', pLoc, { version: '2.0' });
774
+ message('odata-spec-violation-array', pLoc, { version: '2.0' });
775
775
 
776
776
  if(p._csn.target)
777
- warning('odata-spec-violation-assoc', pLoc, { version: '2.0' });
777
+ message('odata-spec-violation-assoc', pLoc, { version: '2.0' });
778
778
  }
779
779
  });
780
780
 
package/lib/edm/edm.js CHANGED
@@ -10,7 +10,7 @@ const { isBetaEnabled } = require('../base/model.js');
10
10
  const EdmTypeFacetMap = {
11
11
  'MaxLength': { v2: true, v4: true, remove: true, optional: true },
12
12
  'Precision': { v2: true, v4: true, remove: true, optional: true },
13
- 'Scale': { v2: true, v4: true, remove: true, optional: true },
13
+ 'Scale': { v2: true, v4: true, remove: true, optional: true, extra: 'sap:variable-scale' },
14
14
  'SRID': { v4: true, remove: true, optional: true },
15
15
  //'FixedLength': { v2: true },
16
16
  //'Collation': { v2: true },
@@ -116,6 +116,8 @@ function getEdm(options, messageFunctions) {
116
116
  removeEdmAttribute(name) {
117
117
  if (name in this._edmAttributes)
118
118
  delete this._edmAttributes[name];
119
+ if (name in this._xmlOnlyAttributes)
120
+ delete this._xmlOnlyAttributes[name];
119
121
  }
120
122
 
121
123
  /**
@@ -695,14 +697,20 @@ function getEdm(options, messageFunctions) {
695
697
  // produce an unrecoverable error.
696
698
  if(td && (td.v2 === this.v2 || td.v4 === this.v4)) {
697
699
  this.setEdmAttribute(typeName, odataType);
698
- EdmTypeFacetNames.forEach(facet => {
699
- if(EdmTypeFacetMap[facet].remove)
700
- this.removeEdmAttribute(facet);
701
- if(td[facet] !== undefined &&
702
- (EdmTypeFacetMap[facet].v2 === this.v2 ||
703
- EdmTypeFacetMap[facet].v4 === this.v4)
704
- ) {
705
- this.setEdmAttribute(facet, csn['@odata.'+facet]);
700
+ EdmTypeFacetNames.forEach(facetName => {
701
+ const facet = EdmTypeFacetMap[facetName];
702
+ if(facet.remove) {
703
+ this.removeEdmAttribute(facetName);
704
+ this.removeEdmAttribute(facet.extra);
705
+ }
706
+ if(td[facetName] !== undefined &&
707
+ (facet.v2 === this.v2 ||
708
+ facet.v4 === this.v4))
709
+ {
710
+ if(this.v2 && facetName === 'Scale' && csn['@odata.'+facetName] === 'variable')
711
+ this.setXml({ [facet.extra]: true });
712
+ else
713
+ this.setEdmAttribute(facetName, csn['@odata.'+facetName]);
706
714
  }
707
715
  });
708
716
  }
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
  /* eslint max-statements-per-line:off */
3
3
  const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
4
- const {
4
+ const {
5
5
  forEachDefinition, forEachGeneric, forEachMemberRecursively,
6
- isEdmPropertyRendered, getUtils, cloneCsnNonDict, cloneCsnDictionary,
7
- isBuiltinType, applyTransformations
6
+ isEdmPropertyRendered, getUtils, cloneCsnNonDict,
7
+ isBuiltinType, applyTransformations, cloneAnnotationValue
8
8
  } = require('../model/csnUtils');
9
9
  const edmUtils = require('./edmUtils.js');
10
10
  const edmAnnoPreproc = require('./edmAnnoPreprocessor.js');
@@ -14,6 +14,12 @@ const expandCSNToFinalBaseType = require('../transform/odata/toFinalBaseType');
14
14
 
15
15
  const NavResAnno = '@Capabilities.NavigationRestrictions.RestrictedProperties';
16
16
 
17
+ // Capabilities that can be pulled up to NavigationRestrictions
18
+ const capabilities = Object.keys(require('../gen/Dictionary.json').
19
+ types['Capabilities.NavigationPropertyRestriction'].Properties).
20
+ filter(c => !['NavigationProperty', 'Navigability'].includes(c)).
21
+ map(c => `@Capabilities.`+c);
22
+
17
23
  /**
18
24
  * edmPreprocessor warms up the model so that it can be converted into an EDM document and
19
25
  * contains all late & application specific model transformations
@@ -203,7 +209,8 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
203
209
  // TODO: work on service basis (this requires post exposure renaming)
204
210
  let fallBackSchemaName = 'root';
205
211
  let i = 1;
206
- while (Object.keys(defs).some(artName => {
212
+ const defNames = Object.keys(defs);
213
+ while (defNames.some(artName => {
207
214
  const p = artName.split('.');
208
215
  return p.length === 2 && p[0] === fallBackSchemaName;
209
216
  })) {
@@ -417,9 +424,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
417
424
  while(elt && !isBuiltinType(elt.type) && !elt.elements) {
418
425
  elt = csn.definitions[elt.type];
419
426
  }
420
- if(elt && elt.elements) {
427
+ if(elt && elt.elements && !elt.$visited) {
428
+ setProp(elt, '$visited', true);
421
429
  forEachMemberRecursively(elt, initContainments,
422
430
  path, true, { pathWithoutProp: true, elementsOnly: true });
431
+ delete elt.$visited;
423
432
  }
424
433
  }
425
434
  }
@@ -437,9 +446,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
437
446
  while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
438
447
  elt = csn.definitions[elt.type];
439
448
  }
440
- if(elt && elt.elements) {
449
+ if(elt && elt.elements && !elt.$visited) {
450
+ setProp(elt, '$visited', true);
441
451
  forEachMemberRecursively(elt, markToContainer,
442
452
  [], true, { elementsOnly: true });
453
+ delete elt.$visited;
443
454
  }
444
455
  }
445
456
  }
@@ -1492,7 +1503,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1492
1503
  and do not render associations (this will include the foreign keys of
1493
1504
  the _isToContainer association).
1494
1505
  */
1495
- function initEdmKeyRefPaths(def) {
1506
+ function initEdmKeyRefPaths(def, defName) {
1496
1507
  if(def.$keys) {
1497
1508
  setProp(def, '$edmKeyPaths', []);
1498
1509
  // for all key elements that shouldn't be ignored produce the paths
@@ -1503,7 +1514,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1503
1514
  // This is structured OData ONLY
1504
1515
  // if the foreign keys are explicitly requested, ignore associations and use the flat foreign keys instead
1505
1516
  if(!options.renderForeignKeys || (options.renderForeignKeys && !k.target))
1506
- def.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
1517
+ def.$edmKeyPaths.push(...produceKeyRefPaths(k, kn, [ 'definitions', defName, 'elements', kn ]));
1507
1518
  }
1508
1519
  // In v2/v4 flat, associations are never rendered
1509
1520
  else if(!k.target) {
@@ -1525,7 +1536,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1525
1536
  navprops to be key ref.
1526
1537
  If element is of scalar type, return it as an array.
1527
1538
  */
1528
- function produceKeyRefPaths(eltCsn, prefix) {
1539
+ function produceKeyRefPaths(eltCsn, prefix, path) {
1529
1540
  const keyPaths = [];
1530
1541
  // we want to point to the element in the entity which is the first path step
1531
1542
  const location = def.$path.concat(['elements']).concat(prefix.split('/')[0]);
@@ -1537,18 +1548,25 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1537
1548
  }
1538
1549
  // OData requires all elements along the path to be nullable: false (that is either key or notNull)
1539
1550
 
1540
- const finalType = csnUtils.getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
1541
- const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
1542
- (finalType && (finalType.elements || finalType.items && finalType.items.elements));
1551
+ const finalType = csnUtils.getFinalTypeDef(eltCsn.items?.type || eltCsn.type);
1552
+ const elements = eltCsn.elements || eltCsn.items?.elements ||
1553
+ finalType?.elements || finalType?.items?.elements;
1543
1554
  if(elements) {
1544
1555
  Object.entries(elements).forEach(([eltName, elt]) => {
1545
- const newRefs = produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName);
1546
- if(newRefs.length) {
1547
- keyPaths.push(...newRefs);
1556
+ if(!elt.$visited) {
1557
+ setProp(elt, '$visited', true);
1558
+ const newRefs = produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName, path);
1559
+ if(newRefs.length) {
1560
+ keyPaths.push(...newRefs);
1548
1561
  // check path step key for spec violations
1549
- const pathSegment = `${prefix}/${eltName}`;
1550
- checkKeySpecViolations(elt, location, pathSegment);
1562
+ const pathSegment = `${prefix}/${eltName}`;
1563
+ checkKeySpecViolations(elt, location, pathSegment);
1564
+ }
1565
+ delete elt.$visited;
1566
+ } else {
1567
+ error('odata-key-recursive', path, { name: prefix });
1551
1568
  }
1569
+
1552
1570
  });
1553
1571
  }
1554
1572
  /* If element is a managed association (can't be anything else),
@@ -1572,7 +1590,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1572
1590
  art = art._type || csnUtils.getCsnDef(art.type);
1573
1591
  }
1574
1592
  }
1575
- keyPaths.push(...produceKeyRefPaths(art, prefix + options.pathDelimiter + k.ref.join(options.pathDelimiter)));
1593
+ keyPaths.push(...produceKeyRefPaths(art, prefix + options.pathDelimiter + k.ref.join(options.pathDelimiter), path));
1576
1594
  });
1577
1595
  }
1578
1596
  else {
@@ -1677,7 +1695,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1677
1695
  while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
1678
1696
  elt = csn.definitions[elt.type];
1679
1697
  }
1680
- elt && elt.elements && Object.values(elt.elements).forEach(e => produceTargetPath(newPrefix, e, curDef));
1698
+ if(elt && elt.elements && !elt.$visited) {
1699
+ setProp(elt, '$visited', true);
1700
+ Object.values(elt.elements).forEach(e => produceTargetPath(newPrefix, e, curDef));
1701
+ delete elt.$visited;
1702
+ }
1681
1703
  }
1682
1704
  }
1683
1705
  }
@@ -1750,7 +1772,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1750
1772
  while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
1751
1773
  elt = csn.definitions[elt.type];
1752
1774
  }
1753
- elt && elt.elements && Object.values(elt.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, curDef)));
1775
+ if(elt && elt.elements && !elt.$visited) {
1776
+ setProp(elt, '$visited', true);
1777
+ Object.values(elt.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, curDef)));
1778
+ delete elt.$visited;
1779
+ }
1754
1780
  }
1755
1781
  }
1756
1782
  npbs.forEach(p => p.Path = prefix + '/' + p.Path );
@@ -1832,7 +1858,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1832
1858
  if(container.$containeeAssociations) {
1833
1859
  // copy or create container restrictions, don't modify original
1834
1860
  const localRestrictions = container[NavResAnno] ?
1835
- cloneCsnDictionary(container[NavResAnno], options) : []
1861
+ cloneAnnotationValue(container[NavResAnno]) : []
1836
1862
 
1837
1863
  // prefix the existing navigation property restritictions on the container
1838
1864
  if(prefix.length) {
@@ -1867,13 +1893,10 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1867
1893
 
1868
1894
  const props = Object.entries(containee);
1869
1895
  let newEntry = false;
1870
- ['@Capabilities.DeleteRestrictions',
1871
- '@Capabilities.InsertRestrictions',
1872
- '@Capabilities.UpdateRestrictions',
1873
- '@Capabilities.ReadRestrictions'].forEach(c => {
1874
- if(edmUtils.mergeIntoNavPropEntry(c, navPropEntry, prefix.concat(path), props))
1875
- newEntry = true;
1876
- });
1896
+ capabilities.forEach(c => {
1897
+ if(edmUtils.mergeIntoNavPropEntry(c, navPropEntry, prefix.concat(path), props))
1898
+ newEntry = true;
1899
+ });
1877
1900
 
1878
1901
  if(newEntry && !hasEntry) {
1879
1902
  localRestrictions.push(navPropEntry);
@@ -1995,12 +2018,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1995
2018
  if (obj.type && isBuiltinType(obj.type) && !obj.target && !obj.targetAspect) {
1996
2019
  let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
1997
2020
  edmUtils.assignProp(obj, '_edmType', edmType);
1998
- } else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.type)))) {
2021
+ } else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.getFinalBaseTypeWithProps(obj.items.type)?.type))) {
1999
2022
  let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType'], obj.$path);
2000
2023
  edmUtils.assignProp(obj, '_edmType', edmType);
2001
2024
  }
2002
2025
  // This is the special case when we have array of array, but will not be supported in the future
2003
- else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.getFinalTypeDef(obj.items.items.type))) {
2026
+ else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.getFinalBaseTypeWithProps(obj.items.items.type)?.type)) {
2004
2027
  let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
2005
2028
  edmUtils.assignProp(obj, '_edmType', edmType);
2006
2029
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  const { setProp } = require('../base/model');
3
- const { isBuiltinType, isEdmPropertyRendered, applyTransformations } = require('../model/csnUtils');
3
+ const { isBuiltinType, isEdmPropertyRendered, applyTransformations, cloneAnnotationValue } = require('../model/csnUtils');
4
4
  const { escapeString, hasControlCharacters, hasUnpairedUnicodeSurrogate } = require("../render/utils/stringEscapes");
5
5
 
6
6
  /* eslint max-statements-per-line:off */
@@ -508,6 +508,10 @@ function mapCdsToEdmType(csn, messageFunctions, isV2=false, isMediaType=false, l
508
508
  'cds.hana.SMALLDECIMAL': 'Edm.Decimal', // V4: Scale="floating" Precision="16"
509
509
  'cds.Integer64': 'Edm.Int64',
510
510
  'cds.Integer': 'Edm.Int32',
511
+ 'cds.Int64': 'Edm.Int64',
512
+ 'cds.Int32': 'Edm.Int32',
513
+ 'cds.Int16': 'Edm.Int16',
514
+ 'cds.UInt8': 'Edm.Byte',
511
515
  'cds.hana.SMALLINT': 'Edm.Int16',
512
516
  'cds.hana.TINYINT': 'Edm.Byte',
513
517
  'cds.Double': 'Edm.Double',
@@ -758,7 +762,8 @@ function mergeIntoNavPropEntry(annoPrefix, navPropEntry, prefix, props) {
758
762
 
759
763
  // Filter properties with prefix and reduce them into a new dictionary
760
764
  const o = props.filter(p => p[0].startsWith(annoPrefix+'.')).reduce((a,c) => {
761
- a[c[0].replace(annoPrefix+'.', '')] = c[1];
765
+ // clone the annotation value to avoid side effects with rewritten paths
766
+ a[c[0].replace(annoPrefix+'.', '')] = cloneAnnotationValue(c[1]);
762
767
  return a;
763
768
  }, { });
764
769
 
@@ -1808,6 +1808,13 @@
1808
1808
  "Parameter"
1809
1809
  ]
1810
1810
  },
1811
+ "UI.IsCopyAction": {
1812
+ "Type": "Core.Tag",
1813
+ "AppliesTo": [
1814
+ "Record"
1815
+ ],
1816
+ "$experimental": true
1817
+ },
1811
1818
  "UI.CreateHidden": {
1812
1819
  "Type": "Core.Tag",
1813
1820
  "AppliesTo": [
@@ -1897,6 +1904,13 @@
1897
1904
  ],
1898
1905
  "$experimental": true
1899
1906
  },
1907
+ "UI.LeadingEntitySet": {
1908
+ "Type": "Edm.String",
1909
+ "AppliesTo": [
1910
+ "EntityContainer"
1911
+ ],
1912
+ "$experimental": true
1913
+ },
1900
1914
  "Validation.Pattern": {
1901
1915
  "Type": "Edm.String",
1902
1916
  "AppliesTo": [
@@ -1 +1 @@
1
- 2067e213918678e2d85713a46e69d9cc
1
+ d054015b5c1bda3f92422cb3f44733bb