@sap/cds-compiler 2.15.2 → 2.15.8

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,35 @@
7
7
  Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
 
10
+ ## Version 2.15.8 - 2022-08-02
11
+
12
+ ### Fixed
13
+
14
+ - to.edm(x): Nested `@UI.TextArrangement` has precedence over `@TextArrangement` shortcut annotation for `@Common.Text`.
15
+ - to.hdi.migration:
16
+ + Respect option `disableHanaComments` when rendering the `ALTER` statements
17
+ + Doc comments rendered the _full doc comment_ instead of only the first paragraph, as `to.hdi` does.
18
+ - compiler: An association's cardinality was lost for associations published in projections.
19
+
20
+ ## Version 2.15.6 - 2022-07-26
21
+
22
+ ### Fixed
23
+
24
+ - Annotations on sub-elements were lost during re-compilation.
25
+
26
+ ## Version 2.15.4 - 2022-06-09
27
+
28
+ ### Fixed
29
+
30
+ - for.odata:
31
+ + Fix derived type to scalar type resolution with intermediate `many`.
32
+ - to.edm(x):
33
+ + (V4 structured) Fix key paths in combination with `--odata-foreign-keys`.
34
+ + Add `Edm.PrimitiveType` to `@odata.Type`.
35
+ + (V4 JSON) Render constant expressions for `Edm.Stream` and `Edm.Untyped`.
36
+ + Fix a bug in target path calculation for `NavigationPropertyBinding`s to external references.
37
+ + Render inner annotations even if `$value` is missing.
38
+ - Update OData vocabularies 'Common', 'UI'.
10
39
 
11
40
  ## Version 2.15.2 - 2022-05-12
12
41
 
package/lib/api/main.js CHANGED
@@ -36,7 +36,7 @@ const propertyToCheck = {
36
36
  const { cloneCsnNonDict } = require('../model/csnUtils');
37
37
  const { toHdbcdsSource } = require('../render/toHdbcds');
38
38
  const { ModelError } = require('../base/error');
39
- const { forEach } = require('../utils/objectUtils');
39
+ const { forEach, forEachKey } = require('../utils/objectUtils');
40
40
 
41
41
  const relevantGeneralOptions = [ /* for future generic options */ ];
42
42
  const relevantOdataOptions = [ 'sqlMapping', 'odataFormat' ];
@@ -639,6 +639,7 @@ function publishCsnProcessor( processor, _name ) {
639
639
  */
640
640
  function api( csn, options = {}, ...args ) {
641
641
  try {
642
+ checkOutdatedOptions( options );
642
643
  return processor( csn, options, ...args );
643
644
  }
644
645
  catch (err) {
@@ -662,6 +663,48 @@ function publishCsnProcessor( processor, _name ) {
662
663
  }
663
664
  }
664
665
 
666
+ // Note: No toCsn, because @sap/cds may still use it (2022-06-15)
667
+ const oldBackendOptionNames = [ 'toSql', 'toOdata', 'toHana', 'forHana' ];
668
+ /**
669
+ * Checks if outdated options are used and if so, throw a compiler error.
670
+ * These include:
671
+ * - magicVars (now variableReplacements)
672
+ * - toOdata/toSql/toHana/forHana -> now flat options
673
+ *
674
+ * @param {CSN.Options} options Backend options
675
+ */
676
+ function checkOutdatedOptions(options) {
677
+ if (!options)
678
+ return;
679
+ const { warning } = makeMessageFunction(null, options, 'api');
680
+
681
+ // This warning has been emitted once, we don't need to emit it again.
682
+ if (options.messages && options.messages.some(m => m.messageId === 'api-invalid-option'))
683
+ return;
684
+
685
+ for (const name of oldBackendOptionNames) {
686
+ if (typeof options[name] === 'object') // may be a boolean due to internal options
687
+ warning('api-invalid-option', null, { '#': 'std', name });
688
+ }
689
+
690
+ if (options.magicVars)
691
+ warning('api-invalid-option', null, { '#': 'magicVars' });
692
+
693
+ // Don't check `options.magicVars`. It's likely that the user renamed `magicVars` but
694
+ // forgot about user -> $user and locale -> $user.locale
695
+ if (options.variableReplacements) {
696
+ if (options.variableReplacements.user)
697
+ warning('api-invalid-option', null, { '#': 'user' });
698
+ if (options.variableReplacements.locale)
699
+ warning('api-invalid-option', null, { '#': 'locale' });
700
+ }
701
+
702
+ forEachKey(options.variableReplacements || {}, (name) => {
703
+ if (!name.startsWith('$') && name !== 'user' && name !== 'locale')
704
+ warning('api-invalid-option', null, { '#': 'noDollar', name });
705
+ });
706
+ }
707
+
665
708
 
666
709
  /**
667
710
  * Option format used by the old API, where they are grouped thematically.
@@ -158,12 +158,12 @@ const centralMessages = {
158
158
  'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
159
159
  'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
160
160
  'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
161
- 'odata-spec-violation-id': { severity: 'Error' },
162
- 'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
161
+ 'odata-spec-violation-id': { severity: 'Error', configurableFor: true },
162
+ 'odata-spec-violation-type': { severity: 'Error', configurableFor: true },
163
163
  'odata-spec-violation-type-unknown': { severity: 'Warning' },
164
164
  'odata-spec-violation-no-key': { severity: 'Warning' },
165
- 'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
166
- 'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
165
+ 'odata-spec-violation-key-array': { severity: 'Error', configurableFor: true }, // more than 30 chars
166
+ 'odata-spec-violation-key-null': { severity: 'Error', configurableFor: true }, // more than 30 chars
167
167
  'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
168
168
  'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
169
169
  };
@@ -193,6 +193,14 @@ for (const oldName in oldMessageIds) {
193
193
 
194
194
  // For messageIds, where no text has been provided via code (central def)
195
195
  const centralMessageTexts = {
196
+ 'api-invalid-option': {
197
+ std: 'Option $(NAME) is deprecated! Use SNAPI options instead',
198
+ magicVars: 'Option “magicVars” is deprecated! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
199
+ user: 'Option “variableReplacements” expects “$user” instead of “user”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
200
+ locale: 'Option “variableReplacements” expects “$user.locale” instead of “locale”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
201
+ 'noDollar': 'Option “variableReplacements” does not know $(NAME). Did you forget a leading “$”?'
202
+ },
203
+
196
204
  'anno-duplicate': 'Duplicate assignment with $(ANNO)',
197
205
  'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
198
206
  'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
@@ -109,6 +109,8 @@ function populate( model ) {
109
109
  function traverseElementEnvironments( art ) {
110
110
  populateView( art );
111
111
  environment( art );
112
+ if (art.elements$)
113
+ mergeSpecifiedElements(art);
112
114
  forEachMember( art, traverseElementEnvironments );
113
115
  }
114
116
 
@@ -425,8 +427,6 @@ function populate( model ) {
425
427
  setLink( view, '_status', '_query' );
426
428
  // must be run in order “sub query in FROM first”:
427
429
  traverseQueryPost( view.query, null, populateQuery );
428
- if (view.elements$) // specified elements
429
- mergeSpecifiedElements( view );
430
430
  if (!view.$entity) {
431
431
  model._entities.push( view );
432
432
  view.$entity = ++model.$entity;
@@ -435,14 +435,25 @@ function populate( model ) {
435
435
  }
436
436
  }
437
437
 
438
- function mergeSpecifiedElements( view ) {
438
+ /**
439
+ * Merge _specified_ elements with _inferred_ elements in the given view/element,
440
+ * where specified elements can appear through CSN.
441
+ *
442
+ * We only copy annotations, since they are not part of `columns`,
443
+ * but only appear in `elements` in CSN.
444
+ *
445
+ * This is important to ensure re-compilability.
446
+ *
447
+ * @param art
448
+ */
449
+ function mergeSpecifiedElements( art ) {
439
450
  // Later we use specified elements as proxies to inferred of leading query
440
451
  // (No, we probably do not.)
441
- for (const id in view.elements) {
442
- const ielem = view.elements[id]; // inferred element
443
- const selem = view.elements$[id]; // specified element
452
+ for (const id in art.elements) {
453
+ const ielem = art.elements[id]; // inferred element
454
+ const selem = art.elements$[id]; // specified element
444
455
  if (!selem) {
445
- info( 'query-missing-element', [ ielem.name.location, view ], { id },
456
+ info( 'query-missing-element', [ ielem.name.location, art ], { id },
446
457
  'Element $(ID) is missing in specified elements' );
447
458
  }
448
459
  else {
@@ -452,13 +463,20 @@ function populate( model ) {
452
463
  ielem[prop] = selem[prop];
453
464
  }
454
465
  selem.$replacement = true;
466
+ if (selem.elements) {
467
+ setLink(ielem, 'elements$', selem.elements);
468
+ delete selem.elements;
469
+ }
455
470
  }
456
471
  }
457
- for (const id in view.elements$) {
458
- const selem = view.elements$[id]; // specified element
459
- if (!selem.$replacement) {
460
- error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
461
- 'Element $(ID) does not result from the query' );
472
+ // Without element expansion, we can't merge nested elements.
473
+ if (art.kind === 'entity' || enableExpandElements) {
474
+ for (const id in art.elements$) {
475
+ const selem = art.elements$[id]; // specified element
476
+ if (!selem.$replacement) {
477
+ error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
478
+ 'Element $(ID) does not result from the query' );
479
+ }
462
480
  }
463
481
  }
464
482
  }
@@ -333,7 +333,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
333
333
  function handleNestedElements(objname, baseElemName, elementsObj) {
334
334
  if(!elementsObj) return;
335
335
  Object.entries(elementsObj).forEach(([elemName, element]) => {
336
- if (Object.keys(element).filter( x => x.substr(0,1) === '@' ).filter(filterKnownVocabularies).length > 0) {
336
+ if (Object.keys(element).filter( x => x[0] === '@' ).filter(filterKnownVocabularies).length > 0) {
337
337
  message(warning, null, `annotations at nested elements are not yet supported, object ${objname}, element ${baseElemName}.${elemName}`);
338
338
  }
339
339
 
@@ -437,11 +437,17 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
437
437
 
438
438
  // Filter unknown toplevel annotations
439
439
  // Final filtering of all annotations is done in handleTerm
440
- const annoNames = Object.keys(carrier).filter( x => x.substr(0,1) === '@' );
440
+
441
+ let annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
441
442
  const nullWhitelist = [ '@Core.OperationAvailable' ];
442
- const knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
443
+ let knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
443
444
  if (knownAnnos.length === 0) return;
444
445
 
446
+ if(rewriteInnerAnnotations()) {
447
+ annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
448
+ knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
449
+ if (knownAnnos.length === 0) return;
450
+ }
445
451
  const prefixTree = createPrefixTree();
446
452
 
447
453
  // usually, for a given carrier there is one target
@@ -610,6 +616,51 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
610
616
  */
611
617
  }
612
618
 
619
+ function rewriteInnerAnnotations() {
620
+ let rc = false;
621
+ for (let a of knownAnnos) {
622
+ const [ prefix, innerAnnotation ] = a.split('.@');
623
+ /*
624
+ New inner annotation (de-)structuring of the core compiler to make
625
+ $value arrays extendable via ellipsis
626
+ @anno: { $value: [ ... ], @innerAnno: ... } is now cracked up by
627
+ the core compiler into:
628
+ @anno: [ ...]
629
+ @anno.@innerAnno: ...
630
+
631
+ Conflict handling if $value is present:
632
+ @anno
633
+ @anno.$value
634
+ @anno.@innerAnno
635
+
636
+ @anno has precedence (as it was before this change) but now
637
+ @anno.$value is overwritten with @anno and the inner annotations
638
+ are applied.
639
+
640
+ Trigger is always the inner annotation, if no inner annotation
641
+ is available, @anno has precedence.
642
+
643
+ Insert $value into $edmJson with inner annotation as well.
644
+ */
645
+ if(innerAnnotation) {
646
+ if(carrier[prefix]) {
647
+ const valPrefix = prefix + '.$value';
648
+ carrier[valPrefix] = carrier[prefix];
649
+ delete carrier[prefix];
650
+ rc = true;
651
+ }
652
+ const edmJsonPrefix = prefix + '.$edmJson';
653
+ if(carrier[edmJsonPrefix]) {
654
+ const valPrefix = prefix + '.$value.$edmJson';
655
+ carrier[valPrefix] = carrier[edmJsonPrefix];
656
+ delete carrier[edmJsonPrefix];
657
+ rc = true;
658
+ }
659
+ }
660
+ }
661
+ return rc;
662
+ }
663
+
613
664
  function createPrefixTree() {
614
665
  // in csn, all annotations are flattened
615
666
  // => values can be - primitive values (string, number)
@@ -619,6 +670,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
619
670
  // by building a "prefix tree" for the annotations attached to the carrier
620
671
  // see example at definition of function mergePathStepsIntoPrefixTree
621
672
  const prefixTree = {};
673
+
622
674
  for (let a of knownAnnos) {
623
675
  // remove leading @ and split at "."
624
676
  // stop splitting at ".@" (used for nested annotations)
@@ -845,7 +897,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
845
897
  // "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
846
898
  oTarget.append(handleEdmJson(cAnnoValue['$edmJson'], context));
847
899
  }
848
- else if ( Object.keys(cAnnoValue).filter( x => x.substr(0,1) !== '@' ).length === 0) {
900
+ else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
849
901
  // object consists only of properties starting with "@"
850
902
  message(warning, context, 'nested annotations without corresponding base annotation');
851
903
  }
@@ -952,6 +1004,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
952
1004
  // Edm.Decimal -> Decimal
953
1005
  // integer tpye -> Int
954
1006
  function handleSimpleValue(val, dTypeName, context) {
1007
+ // these types must be represented as "String" values in XML:
1008
+ const castToXmlString = [ 'Edm.PrimitiveType', 'Edm.Stream', 'Edm.Untyped' ];
955
1009
  // caller already made sure that val is neither object nor array
956
1010
  dTypeName = resolveType(dTypeName);
957
1011
 
@@ -989,12 +1043,12 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
989
1043
  message(warning, context, `found String, but expected enum type ${ dTypeName }`);
990
1044
  typeName = 'EnumMember';
991
1045
  }
992
- else if (dTypeName && dTypeName.startsWith('Edm.') && dTypeName !== 'Edm.PrimitiveType') {
1046
+ else if (dTypeName && dTypeName.startsWith('Edm.') && !castToXmlString.includes(dTypeName)) {
993
1047
  // this covers also all paths
994
1048
  typeName = dTypeName.substring(4);
995
1049
  }
996
1050
  else {
997
- if(dTypeName == undefined || dTypeName === 'Edm.PrimitiveType')
1051
+ if(dTypeName == undefined || castToXmlString.some(t => t === dTypeName))
998
1052
  dTypeName = 'Edm.String';
999
1053
  // TODO
1000
1054
  //message(warning, context, "type is not yet handled: found String, expected type: " + dTypeName);
@@ -297,8 +297,12 @@ function preprocessAnnotations(csn, serviceName, options) {
297
297
 
298
298
  //change the scalar anno into a "pseudo-structured" one
299
299
  // TODO should be flattened, but then alphabetical order is destroyed
300
- let newTextAnno = { '$value': textAnno, '@UI.TextArrangement': value };
301
- carrier['@Common.Text'] = newTextAnno;
300
+
301
+ // Do not overwrite existing nested annotation values, instead give existing
302
+ // nested annotation precedence and remove outer annotation (always)
303
+ if(!carrier['@Common.Text.@UI.TextArrangement'] && textAnno) {
304
+ carrier['@Common.Text'] = { '$value': textAnno, '@UI.TextArrangement': value };
305
+ }
302
306
  delete carrier[aName];
303
307
  }
304
308
  }
@@ -14,7 +14,7 @@ const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../mo
14
14
  const { checkCSNVersion } = require('../json/csnVersion');
15
15
  const { makeMessageFunction } = require('../base/messages');
16
16
  const { EdmTypeFacetMap, EdmTypeFacetNames, EdmPrimitiveTypeMap, getEdm } = require('./edm.js');
17
-
17
+
18
18
  /*
19
19
  OData V2 spec 06/01/2017 PDF version is available from here:
20
20
  https://msdn.microsoft.com/en-us/library/dd541474.aspx
@@ -421,7 +421,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
421
421
  message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
422
422
  else if (options.isV2() && /^(_|[0-9])/.test(p._edmAttributes.Name)) {
423
423
  // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
424
- error('odata-spec-violation-id', pLoc,
424
+ message('odata-spec-violation-id', pLoc,
425
425
  { prop: p._edmAttributes.Name[0], id: p._edmAttributes.Name, version: '2.0', '#': 'v2firstchar' });
426
426
  }
427
427
  });
@@ -621,9 +621,9 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
621
621
  message('odata-spec-violation-id', pLoc, { id: parameterName });
622
622
 
623
623
  // only scalar or structured type in V2 (not entity)
624
- if(param._type &&
625
- !param._type.startsWith('Edm.') &&
626
- csn.definitions[param._type] &&
624
+ if(param._type &&
625
+ !param._type.startsWith('Edm.') &&
626
+ csn.definitions[param._type] &&
627
627
  !edmUtils.isStructuredType(csn.definitions[param._type]))
628
628
  warning('odata-spec-violation-param', pLoc, { version: '2.0' });
629
629
 
@@ -933,7 +933,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
933
933
 
934
934
  // generate the Edm.Annotations tree and append it to the corresponding schema
935
935
  function addAnnotations() {
936
- let { annos, usedVocabularies } = translate.csn2annotationEdm(csn, serviceCsn.name, Edm, options, messageFunctions);
936
+ let { annos, usedVocabularies } = translate.csn2annotationEdm(reqDefs, serviceCsn.name, Edm, options, messageFunctions);
937
937
  // distribute edm:Annotations into the schemas
938
938
  // Distribute each anno into Schema
939
939
  annos.forEach(anno => {
@@ -990,7 +990,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
990
990
  });
991
991
  }
992
992
  else {
993
- message('odata-spec-violation-type-unknown', pLoc,
993
+ message('odata-spec-violation-type-unknown', pLoc,
994
994
  { type:edmType });
995
995
  }
996
996
  }
package/lib/edm/edm.js CHANGED
@@ -54,7 +54,7 @@ const EdmPrimitiveTypeMap = {
54
54
  'Edm.GeometryMultiLineString': { v4: true, SRID: true, desc: 'Collection of line strings in a flat-earth coordinate system' },
55
55
  'Edm.GeometryMultiPolygon': { v4: true, SRID: true, desc: 'Collection of polygons in a flat-earth coordinate system' },
56
56
  'Edm.GeometryCollection': { v4: true, SRID: true, desc: 'Collection of arbitrary Geometry values' },
57
- //'Edm.PrimitiveType': { v4: true, desc: 'Abstract meta type' },
57
+ 'Edm.PrimitiveType': { v4: true, desc: 'Abstract meta type' },
58
58
  //'Edm.Untyped': { v4: true, desc: 'Abstract void type' },
59
59
  };
60
60
 
@@ -1223,9 +1223,15 @@ function getEdm(options, messageFunctions) {
1223
1223
  // short form: key: value
1224
1224
  const inlineConstExpr =
1225
1225
  [ 'Edm.Binary', 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Double', 'Edm.Duration', 'Edm.Guid',
1226
- 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte','Edm.Single', /*'Edm.Stream',*/ 'Edm.String', 'Edm.TimeOfDay',
1226
+ 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte','Edm.Single', 'Edm.Stream', 'Edm.String', 'Edm.TimeOfDay',
1227
+ // Edm.Geo* according to https://issues.oasis-open.org/browse/ODATA-1323
1228
+ /* 'Edm.Geography', 'Edm.GeographyPoint', 'Edm.GeographyLineString', 'Edm.GeographyPolygon', 'Edm.GeographyMultiPoint',
1229
+ 'Edm.GeographyMultiLineString', 'Edm.GeographyMultiPolygon', 'Edm.GeographyCollection', 'Edm.Geometry', 'Edm.GeometryPoint',
1230
+ 'Edm.GeometryLineString', 'Edm.GeometryPolygon', 'Edm.GeometryMultiPoint', 'Edm.GeometryMultiLineString', 'Edm.GeometryMultiPolygon',
1231
+ 'Edm.GeometryCollection',
1232
+ */
1227
1233
  /* UI.xml: defines Annotations with generic type 'Edm.PrimitiveType' */
1228
- 'Edm.PrimitiveType', 'Bool',
1234
+ 'Edm.PrimitiveType', 'Edm.Untyped', 'Bool',
1229
1235
  // Official JSON V4.01 Spec defines these paths as constant inline expression:
1230
1236
  'AnnotationPath', 'ModelElementPath', 'NavigationPropertyPath', 'PropertyPath',
1231
1237
  ];
@@ -1455,12 +1455,9 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1455
1455
  if(isEdmPropertyRendered(k, options) &&
1456
1456
  !(options.isV2() && k['@Core.MediaType'])) {
1457
1457
  if(options.isV4() && options.isStructFormat) {
1458
- // This is structured OData ONLY
1459
- // if the foreign keys are explicitly requested, ignore associations and use the flat foreign keys instead
1460
- if(options.renderForeignKeys && !k.target)
1461
- def.$edmKeyPaths.push([kn]);
1462
- // else produce paths (isEdmPropertyRendered() has filtered @odata.foreignKey4 already)
1463
- else if(!options.renderForeignKeys)
1458
+ // This is structured OData ONLY
1459
+ // if the foreign keys are explicitly requested, ignore associations and use the flat foreign keys instead
1460
+ if(!options.renderForeignKeys || (options.renderForeignKeys && !k.target))
1464
1461
  def.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
1465
1462
  }
1466
1463
  // In v2/v4 flat, associations are never rendered
@@ -1543,13 +1540,13 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1543
1540
  // Nullability
1544
1541
  if((!elt.key && (elt.notNull === undefined || elt.notNull === false)) ||
1545
1542
  elt.key && (elt.notNull !== undefined && elt.notNull === false)) {
1546
- error('odata-spec-violation-key-null', location,
1543
+ message('odata-spec-violation-key-null', location,
1547
1544
  {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1548
1545
  }
1549
1546
  // many
1550
1547
  let type = elt.items || elt.type && !isBuiltinType(elt.type) && csnUtils.getFinalTypeDef(elt.type).items;
1551
1548
  if(type) {
1552
- error('odata-spec-violation-key-array', location,
1549
+ message('odata-spec-violation-key-array', location,
1553
1550
  {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1554
1551
  }
1555
1552
  // type
@@ -1615,7 +1612,14 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1615
1612
  if(isAssociationOrComposition(elt) && !elt.$touched) {
1616
1613
  if(!elt._target.$edmTgtPaths)
1617
1614
  setProp(elt._target, '$edmTgtPaths', []);
1618
- if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
1615
+ // drill into target only if
1616
+ // 1) target has no entity set and this assoc is not going to the container
1617
+ // 2) current definition and target are the same (cycle)
1618
+ // 3) it's no external reference
1619
+ if(!elt.$externalRef &&
1620
+ !elt._target.$hasEntitySet &&
1621
+ !elt._isToContainer &&
1622
+ curDef !== elt._target) {
1619
1623
  // follow elements in the target but avoid cycles
1620
1624
  setProp(elt, '$touched', true);
1621
1625
  elt._target.$edmTgtPaths.push(newPrefix);
@@ -1654,6 +1658,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1654
1658
  // drill into target only if
1655
1659
  // 1) target has no entity set and this assoc is not going to the container
1656
1660
  // 2) current definition and target are the same (cycle)
1661
+ // 3) it's no external reference
1657
1662
  if(!elt.$externalRef &&
1658
1663
  !elt._target.$hasEntitySet &&
1659
1664
  !elt._isToContainer &&
@@ -658,6 +658,7 @@
658
658
  "Type": "Common.FieldControlType",
659
659
  "AppliesTo": [
660
660
  "Property",
661
+ "Parameter",
661
662
  "Record",
662
663
  "EntityType"
663
664
  ]
@@ -3246,6 +3247,7 @@
3246
3247
  "TypeNamePlural": "Edm.String",
3247
3248
  "Title": "UI.DataFieldAbstract",
3248
3249
  "Description": "UI.DataFieldAbstract",
3250
+ "Image": "Edm.Stream",
3249
3251
  "ImageUrl": "Edm.String",
3250
3252
  "TypeImageUrl": "Edm.String",
3251
3253
  "Initials": "Edm.String"
@@ -3693,7 +3695,7 @@
3693
3695
  "$kind": "ComplexType",
3694
3696
  "BaseType": "UI.DataFieldAbstract",
3695
3697
  "Properties": {
3696
- "Value": "Edm.PrimitiveType",
3698
+ "Value": "Edm.Untyped",
3697
3699
  "Label": "Edm.String",
3698
3700
  "Criticality": "UI.CriticalityType",
3699
3701
  "CriticalityRepresentation": "UI.CriticalityRepresentationType",
@@ -3704,8 +3706,8 @@
3704
3706
  "$kind": "ComplexType",
3705
3707
  "BaseType": "UI.DataField",
3706
3708
  "Properties": {
3707
- "Action": "Common.QualifiedName",
3708
3709
  "Value": "Edm.PrimitiveType",
3710
+ "Action": "Common.QualifiedName",
3709
3711
  "Label": "Edm.String",
3710
3712
  "Criticality": "UI.CriticalityType",
3711
3713
  "CriticalityRepresentation": "UI.CriticalityRepresentationType",
@@ -3716,10 +3718,10 @@
3716
3718
  "$kind": "ComplexType",
3717
3719
  "BaseType": "UI.DataField",
3718
3720
  "Properties": {
3721
+ "Value": "Edm.PrimitiveType",
3719
3722
  "SemanticObject": "Edm.String",
3720
3723
  "Action": "Edm.String",
3721
3724
  "Mapping": "Collection(Common.SemanticObjectMappingType)",
3722
- "Value": "Edm.PrimitiveType",
3723
3725
  "Label": "Edm.String",
3724
3726
  "Criticality": "UI.CriticalityType",
3725
3727
  "CriticalityRepresentation": "UI.CriticalityRepresentationType",
@@ -3730,8 +3732,8 @@
3730
3732
  "$kind": "ComplexType",
3731
3733
  "BaseType": "UI.DataField",
3732
3734
  "Properties": {
3733
- "Target": "Edm.NavigationPropertyPath",
3734
3735
  "Value": "Edm.PrimitiveType",
3736
+ "Target": "Edm.NavigationPropertyPath",
3735
3737
  "Label": "Edm.String",
3736
3738
  "Criticality": "UI.CriticalityType",
3737
3739
  "CriticalityRepresentation": "UI.CriticalityRepresentationType",
@@ -3742,9 +3744,9 @@
3742
3744
  "$kind": "ComplexType",
3743
3745
  "BaseType": "UI.DataField",
3744
3746
  "Properties": {
3747
+ "Value": "Edm.PrimitiveType",
3745
3748
  "Url": "Edm.String",
3746
3749
  "UrlContentType": "Edm.String",
3747
- "Value": "Edm.PrimitiveType",
3748
3750
  "Label": "Edm.String",
3749
3751
  "Criticality": "UI.CriticalityType",
3750
3752
  "CriticalityRepresentation": "UI.CriticalityRepresentationType",
@@ -3755,8 +3757,8 @@
3755
3757
  "$kind": "ComplexType",
3756
3758
  "BaseType": "UI.DataField",
3757
3759
  "Properties": {
3758
- "Actions": "Collection(UI.DataField)",
3759
3760
  "Value": "Edm.PrimitiveType",
3761
+ "Actions": "Collection(UI.DataField)",
3760
3762
  "Label": "Edm.String",
3761
3763
  "Criticality": "UI.CriticalityType",
3762
3764
  "CriticalityRepresentation": "UI.CriticalityRepresentationType",
@@ -102,6 +102,7 @@ const ourpropsRegex = /^[_$]?[a-zA-Z]+[0-9]*$/;
102
102
  const typeProperties = [
103
103
  // do not include CSN v0.1.0 properties here:
104
104
  'target', 'elements', 'enum', 'items',
105
+ 'cardinality', // for association publishing in views
105
106
  'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull',
106
107
  'keys', 'on', // only with 'target'
107
108
  ];
@@ -184,9 +184,11 @@ const propertyOrder = (function orderPositions() {
184
184
  }());
185
185
 
186
186
  // sync with definition in from-csn.js:
187
+ // Note: Order here is also the property order in CSN.
187
188
  const typeProperties = [
188
- 'target', 'elements', 'enum', 'items', // TODO: notNull?
189
- 'type', 'length', 'precision', 'scale', 'srid', 'localized',
189
+ 'target', 'elements', 'enum', 'items',
190
+ 'cardinality', // for association publishing in views
191
+ 'type', 'length', 'precision', 'scale', 'srid', 'localized', // TODO: notNull?
190
192
  'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED
191
193
  ];
192
194
 
@@ -200,7 +200,7 @@ function toSqlDdl(csn, options) {
200
200
  Render comment string.
201
201
  */
202
202
  comment(comment) {
203
- return comment && renderStringForSql(comment, options.sqlDialect) || 'NULL';
203
+ return comment && renderStringForSql(getHanaComment({ doc: comment }), options.sqlDialect) || 'NULL';
204
204
  },
205
205
  /*
206
206
  Alter SQL snippet for entity.
@@ -461,7 +461,7 @@ function toSqlDdl(csn, options) {
461
461
  // Change entity properties
462
462
  if (migration.properties) {
463
463
  for (const [ prop, def ] of Object.entries(migration.properties)) {
464
- if (prop === 'doc') {
464
+ if (prop === 'doc' && !options.disableHanaComments) { // def.new may be `null`
465
465
  const alterComment = render.alterEntityComment(tableName, def.new);
466
466
  addMigration(resultObj, artifactName, false, alterComment);
467
467
  }
@@ -527,7 +527,7 @@ function toSqlDdl(csn, options) {
527
527
  }
528
528
  }
529
529
 
530
- if (def.old.doc !== def.new.doc) {
530
+ if (!options.disableHanaComments && def.old.doc !== def.new.doc) {
531
531
  const eltStrOldNoDoc = getEltStrNoProps(def.old, eltName, 'doc');
532
532
  const eltStrNewNoDoc = getEltStrNoProps(def.new, eltName, 'doc');
533
533
  if (eltStrOldNoDoc === eltStrNewNoDoc) { // only `doc` changed
@@ -148,7 +148,9 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
148
148
  // example in actions: 'action act() return Primitive; type Primitive: array of String;'
149
149
  const currService = csnUtils.getServiceName(defName);
150
150
  const finalType = csnUtils.getFinalTypeDef(node.type);
151
- if (finalType.items && isBuiltinType(finalType.items.type)) {
151
+ if (finalType.items &&
152
+ (isBuiltinType(finalType.items.type) || isBuiltinType(csnUtils.getFinalBaseType(finalType.items.type))))
153
+ {
152
154
  if (!isArtifactInService(node.type, currService) || !isV4) {
153
155
  node.items = finalType.items;
154
156
  delete node.type;
@@ -1818,6 +1818,10 @@ function walkPath(node, env)
1818
1818
  // or that are parameters ($parameters or escaped paths (':')
1819
1819
  //path.length && path[ path.length-1 ]._artifact
1820
1820
  const art = path && path.length && path[path.length-1]._artifact;
1821
+
1822
+ // regardless of the position in the query, ignore paths that have virtual path steps
1823
+ if(art && path.some(ps => ps._artifact && ps._artifact.virtual && ps._artifact.virtual.val))
1824
+ return path;
1821
1825
  if(art && !internalArtifactKinds.includes(art.kind))
1822
1826
  {
1823
1827
  if(env.callback)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "2.15.2",
3
+ "version": "2.15.8",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",