@sap/cds-compiler 3.9.8 → 3.9.10

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,16 @@
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 3.9.10 - 2023-08-25
11
+
12
+ ### Fixed
13
+
14
+ - to.edm(x): Error reporting for incorrect handling of "Collection()" has been improved.
15
+ - to.sql/hdi/hdbcds: Views on views with parameters did not have localized convenience views based on
16
+ other localized views (missing `localized.` prefix in FROM clause)
17
+ - to.sql: Casting expressions to a structured type yields a proper error instead of strange compiler error.
18
+ - to.sql.migration: Don't drop-create views marked with `@cds.persistence.exists` or `@cds.persistence.skip`
19
+
10
20
  ## Version 3.9.8 - 2023-08-03
11
21
 
12
22
  ### Fixed
package/lib/api/main.js CHANGED
@@ -375,8 +375,7 @@ function sqlMigration( csn, options, beforeImage ) {
375
375
  const beforeArtifact = beforeImage.definitions[artifactName];
376
376
  const diffArtifact = diff.definitions[artifactName];
377
377
  // TODO: exists, abstract? isPersistedOnDb?
378
- if (diffArtifact && diffArtifact['@cds.persistence.name'] && !diffArtifact['@cds.persistence.skip'] &&
379
- (diffArtifact.query || diffArtifact.projection) &&
378
+ if (diffArtifact && diffArtifact['@cds.persistence.name'] && csnUtils.isPersistedAsView(diffArtifact) &&
380
379
  (diffArtifact[modelCompare.isChanged] === true || // we know it changed because we compared two views
381
380
  diffArtifact[modelCompare.isChanged] === undefined)) { // if it was removed in the after, then we don't have the flag
382
381
  drops.creates[artifactName] = `DROP VIEW ${ identifierUtils.renderArtifactName(artifactName) };`;
@@ -384,6 +383,7 @@ function sqlMigration( csn, options, beforeImage ) {
384
383
  else if (diffArtifact &&
385
384
  diffArtifact['@cds.persistence.skip'] !== true &&
386
385
  diffArtifact.kind === beforeArtifact.kind && // detect action -> entity
386
+ csnUtils.isPersistedAsTable(diffArtifact) === csnUtils.isPersistedAsTable(beforeArtifact) && // detect removal of @cds.persistence.exists
387
387
  csnUtils.isPersistedAsView(diffArtifact) === csnUtils.isPersistedAsView(beforeArtifact) // detect view -> entity
388
388
  ) { // don't render again, but need info for primary key extension
389
389
  diffArtifact['@cds.persistence.skip'] = true;
@@ -742,6 +742,14 @@ const centralMessageTexts = {
742
742
  entity: 'Entity $(ART) with managed compositions can\'t be used in types', // yet
743
743
  },
744
744
 
745
+ 'type-invalid-cast': {
746
+ std: 'Invalid cast to $(TYPE)', // unused
747
+ 'to-structure': 'Can\'t cast to a structured type',
748
+ 'from-structure': 'Structured elements can\'t be cast to a different type',
749
+ 'expr-to-structure': 'Can\'t cast an expression to a structured type',
750
+ 'val-to-structure': 'Can\'t cast $(VALUE) to a structured type'
751
+ },
752
+
745
753
  // -----------------------------------------------------------------------------------
746
754
  // Expressions
747
755
  // -----------------------------------------------------------------------------------
@@ -849,7 +857,7 @@ const centralMessageTexts = {
849
857
  // -----------------------------------------------------------------------------------
850
858
  'enum': 'Value $(VALUE) is not one out of $(RAWVALUES) for $(ANNO) of type $(TYPE)',
851
859
  'std': 'Unexpected value $(VALUE) for $(ANNO) of type $(TYPE)',
852
- 'struct': 'Unexpected $(STR) value for $(ANNO) of type $(TYPE)',
860
+ 'incompval': 'Unexpected $(STR) value for $(ANNO) of type $(TYPE)',
853
861
  'nestedcollection': 'Nested collections are not supported for $(ANNO)',
854
862
  'enumincollection': 'Enum inside collection is not supported for $(ANNO)',
855
863
  'multexpr': 'EDM JSON code contains more than one dynamic expression: $(RAWVALUES) for $(ANNO)',
@@ -516,14 +516,10 @@ function resolve( model ) {
516
516
  ? art.value.args[0]?._artifact
517
517
  : art.value._artifact;
518
518
  if (elem && art.type) { // has explicit type
519
- if (art.type._artifact?.elements) {
520
- error('type-cast-to-structured', [ art.type.location, art ], {},
521
- 'Can\'t cast to structured element');
522
- }
523
- else if (elem.elements) { // TODO: calc elements
524
- error('type-cast-structured', [ art.type.location, art ], {},
525
- 'Structured elements can\'t be cast to a different type');
526
- }
519
+ if (art.type._artifact?.elements)
520
+ error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'to-structure' } );
521
+ else if (elem.elements) // TODO: calc elements
522
+ error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-structure' } );
527
523
  }
528
524
  }
529
525
 
@@ -921,30 +921,33 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
921
921
  // cAnnoValue: the annotation value (c : csn)
922
922
  // oTarget: the result object (o: odata)
923
923
  // oTermName: current term
924
- // dTypeName: expected type of cAnnoValue according to dictionary, may be null (d: dictionary)
925
- function handleValue(cAnnoValue, oTarget, oTermName, dTypeName, msg) {
924
+ // dTypeNameArg: expected type of cAnnoValue according to dictionary, may be null (d: dictionary)
925
+ function handleValue( cAnnoValue, oTarget, oTermName, dTypeNameArg, msg ) {
926
926
  // this function basically only figures out what kind of annotation value we have
927
927
  // (can be: array, expression, enum, pseudo-record, record, simple value),
928
928
  // then calls a more specific function to deal with it and puts
929
929
  // the result into the oTarget object
930
930
 
931
- if (Array.isArray(cAnnoValue))
932
- {
933
- if (isEnumType(dTypeName))
934
- {
931
+ const [ dTypeName, dTypeIsACollection ] = stripCollection(dTypeNameArg);
932
+
933
+ if (Array.isArray(cAnnoValue)) {
934
+ if (isEnumType(dTypeName)) {
935
935
  // if we find an array although we expect an enum, this may be a "flag enum"
936
- checkMultiEnumValue(cAnnoValue);
936
+ checkMultiEnumValue();
937
937
  oTarget.setJSON({ 'EnumMember': generateMultiEnumValue(cAnnoValue, false), 'EnumMember@odata.type' : '#'+dTypeName });
938
938
  oTarget.setXml( { 'EnumMember': generateMultiEnumValue(cAnnoValue, true) });
939
939
  }
940
- else
941
- {
942
- oTarget.append(generateCollection(cAnnoValue, oTermName, dTypeName, msg));
940
+ else {
941
+ oTarget.append(generateCollection(cAnnoValue, oTermName, dTypeName, dTypeIsACollection, msg));
943
942
  }
944
943
  }
945
944
  else if (cAnnoValue && typeof cAnnoValue === 'object') {
946
945
  // an empty record is rendered as <Record/>
947
946
  if ('=' in cAnnoValue) {
947
+ if (dTypeIsACollection) {
948
+ message('odata-anno-value', msg.location,
949
+ { anno: msg.anno(), str: 'path', '#': 'incompval' });
950
+ }
948
951
  // expression
949
952
  const res = handleExpression(cAnnoValue['='], dTypeName);
950
953
  oTarget.setXml( { [res.name] : res.value });
@@ -954,8 +957,8 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
954
957
  const enumSymbol = cAnnoValue['#'];
955
958
  // enum
956
959
  if (dTypeName) {
957
- const typeDef = getDictType(stripCollection(dTypeName));
958
- if(typeDef && typeDef.$Allowed && !typeDef.Members) {
960
+ const typeDef = getDictType(dTypeName);
961
+ if (typeDef && typeDef.$Allowed && !typeDef.Members) {
959
962
  const allowedValue = typeDef.$Allowed.Symbols[enumSymbol];
960
963
  if(!allowedValue) {
961
964
  message('odata-anno-value', msg.location,
@@ -982,7 +985,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
982
985
  }
983
986
  else if (cAnnoValue['$value'] !== undefined) {
984
987
  // "pseudo-structure" used for annotating scalar annotations
985
- handleValue(cAnnoValue['$value'], oTarget, oTermName, dTypeName, msg);
988
+ handleValue(cAnnoValue.$value, oTarget, oTermName, dTypeNameArg, msg);
986
989
 
987
990
  const k = Object.keys(cAnnoValue).filter( x => x[0] === '@');
988
991
  if (!k || k.length === 0) {
@@ -1005,7 +1008,11 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1005
1008
  }
1006
1009
  else {
1007
1010
  // regular record
1008
- oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName, msg));
1011
+ if (dTypeIsACollection) {
1012
+ message('odata-anno-value', msg.location,
1013
+ { anno: msg.anno(), str: 'structured', '#': 'incompval' });
1014
+ }
1015
+ oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName, dTypeIsACollection, msg));
1009
1016
  }
1010
1017
  }
1011
1018
  else {
@@ -1021,7 +1028,6 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1021
1028
  }
1022
1029
  oTarget.setJSON( { [res.jsonName] : res.value });
1023
1030
  }
1024
-
1025
1031
  // found an enum value ("#"), check whether this fits
1026
1032
  // the expected type "dTypeName"
1027
1033
  function checkEnumValue(value) {
@@ -1053,15 +1059,17 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1053
1059
  // cAnnoValue: array
1054
1060
  // dTypeName: expected type, already identified as enum type
1055
1061
  // array is expected to contain enum values
1056
- function checkMultiEnumValue(cAnnoValue) {
1057
- // we know that dTypeName is not null
1062
+ function checkMultiEnumValue( ) {
1063
+ // we know that dTypeName is not null
1058
1064
  const type = getDictType(dTypeName);
1059
- if (!type || type['IsFlags'] !== 'true') {
1065
+ if (!type || type.IsFlags !== 'true') {
1060
1066
  message('odata-anno-value', msg.location,
1061
- { anno: msg.anno(),
1067
+ {
1068
+ anno: msg.anno(),
1062
1069
  str: 'collection',
1063
1070
  type: dTypeName,
1064
- '#': 'struct' });
1071
+ '#': 'incompval',
1072
+ });
1065
1073
  }
1066
1074
 
1067
1075
  let index = 0;
@@ -1092,8 +1100,6 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1092
1100
  }
1093
1101
  }
1094
1102
 
1095
-
1096
-
1097
1103
  // found an expression value ("=") "expr"
1098
1104
  // expected type is dTypeName
1099
1105
  // note: expr can also be provided if an enum/complex type/collection is expected
@@ -1264,18 +1270,20 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1264
1270
  // dTypeName : name of the expected record type according to vocabulary, may be null
1265
1271
  //
1266
1272
  // can be called for a record directly below a term, or at a deeper level
1267
- function generateRecord(obj, termName, dTypeName, msg) {
1273
+ function generateRecord( obj, termName, dTypeName, dTypeIsACollection, msg ) {
1268
1274
  /** @type {object} */
1269
1275
  const newRecord = new Edm.Record(v);
1270
1276
 
1271
1277
  // first determine what is the actual type to be used for the record
1272
1278
  if (dTypeName && !isComplexType(dTypeName)) {
1273
- if (!getDictType(dTypeName) && !isPrimitiveType(dTypeName) && !isCollection(dTypeName))
1274
- message('odata-anno-dict', msg.location,
1275
- { anno: msg.anno(), type: dTypeName });
1276
- else
1279
+ if (!getDictType(dTypeName) && !isPrimitiveType(dTypeName) && !dTypeIsACollection) {
1280
+ message('odata-anno-dict', msg.location, { anno: msg.anno(), type: dTypeName });
1281
+ } else {
1277
1282
  message('odata-anno-value', msg.location,
1278
- { anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'struct' });
1283
+ {
1284
+ anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'incompval',
1285
+ });
1286
+ }
1279
1287
  return newRecord;
1280
1288
  }
1281
1289
 
@@ -1383,19 +1391,14 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1383
1391
 
1384
1392
  // annoValue is an array
1385
1393
  // dTypeName : Collection(...) according to dictionary
1386
- function generateCollection(annoValue, termName, dTypeName, msg) {
1394
+ function generateCollection( annoValue, termName, dTypeName, dTypeIsACollection, msg ) {
1387
1395
  const newCollection = new Edm.Collection(v);
1388
1396
 
1389
- let innerTypeName = null;
1390
- if (dTypeName) {
1391
- const match = dTypeName.match(/^Collection\((.+)\)/);
1392
- if (match) {
1393
- innerTypeName = match[1];
1394
- }
1395
- else {
1396
- message('odata-anno-value', msg.location,
1397
- { anno: msg.anno(), str: 'collection', type: dTypeName, '#': 'struct' });
1398
- }
1397
+ if (dTypeName && !dTypeIsACollection) {
1398
+ message('odata-anno-value', msg.location,
1399
+ {
1400
+ anno: msg.anno(), str: 'collection', type: dTypeName, '#': 'incompval',
1401
+ });
1399
1402
  }
1400
1403
 
1401
1404
  let index = 0;
@@ -1412,7 +1415,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1412
1415
  }
1413
1416
  else if (value && typeof value === 'object') {
1414
1417
  if (value['=']) {
1415
- const res = handleExpression(value['='], innerTypeName);
1418
+ const res = handleExpression(value['='], dTypeName);
1416
1419
  const newPropertyPath = new Edm.ValueThing(v, res.name, res.value );
1417
1420
  newPropertyPath.setJSON( { [res.name] : res.value } );
1418
1421
  newCollection.append(newPropertyPath);
@@ -1425,13 +1428,13 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1425
1428
  newCollection.append(handleEdmJson(value['$edmJson'], msg));
1426
1429
  }
1427
1430
  else {
1428
- newCollection.append(generateRecord(value, termName, innerTypeName, msg));
1431
+ newCollection.append(generateRecord(value, termName, dTypeName, dTypeIsACollection, msg));
1429
1432
  }
1430
1433
  }
1431
1434
  else {
1432
- const res = handleSimpleValue(value, innerTypeName, msg);
1433
- const newThing = (value === null) ?new Edm.ValueThing(v, 'Null') : new Edm.ValueThing(v, res.name, value );
1434
- newThing.setJSON( { [res.jsonName] : res.value });
1435
+ const res = handleSimpleValue(value, dTypeName, msg);
1436
+ const newThing = (value === null) ? new Edm.ValueThing(v, 'Null') : new Edm.ValueThing(v, res.name, value );
1437
+ newThing.setJSON( { [res.jsonName]: res.value });
1435
1438
  newCollection.append(newThing);
1436
1439
  }
1437
1440
 
@@ -1783,23 +1786,21 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1783
1786
  return dTypeName;
1784
1787
  }
1785
1788
 
1786
- function stripCollection(typeName) {
1787
- const match = typeName.match(/^Collection\((.+)\)/);
1788
- if (match) {
1789
- typeName = match[1];
1789
+ function stripCollection( typeName ) {
1790
+ if (typeName) {
1791
+ const match = typeName.match(/^Collection\((.+)\)/);
1792
+ if (match)
1793
+ return [ match[1], true ];
1790
1794
  }
1791
- return typeName;
1795
+
1796
+ return [ typeName, false ];
1792
1797
  }
1793
1798
 
1794
1799
  function isPrimitiveType(typeName) {
1795
1800
  return typeName.split('.')[0] === 'Edm';
1796
1801
  }
1797
1802
 
1798
- function isCollection(typeName) {
1799
- return typeName.match(/^Collection\((.+)\)/) !== null;
1800
- }
1801
-
1802
- function isEnumType(dTypeName) {
1803
+ function isEnumType( dTypeName ) {
1803
1804
  const type = getDictType(dTypeName);
1804
1805
  return type && type['$kind'] === 'EnumType';
1805
1806
  }
@@ -18,13 +18,11 @@ const { forEach } = require('../../utils/objectUtils');
18
18
  * @param {CSN.Options} options
19
19
  * @param {string} pathDelimiter
20
20
  * @param {object} messageFunctions
21
- * @param {Function} messageFunctions.error
22
- * @param {Function} messageFunctions.info
23
- * @param {Function} messageFunctions.throwWithAnyError
24
21
  * @param {object} csnUtils
25
22
  * @param {object} [iterateOptions]
26
23
  */
27
- function expandStructureReferences( csn, options, pathDelimiter, { error, info, throwWithAnyError }, csnUtils, iterateOptions = {} ) {
24
+ function expandStructureReferences( csn, options, pathDelimiter, messageFunctions, csnUtils, iterateOptions = {} ) {
25
+ const { error, info, throwWithAnyError } = messageFunctions;
28
26
  const {
29
27
  isStructured, get$combined, getFinalTypeInfo,
30
28
  } = csnUtils;
@@ -32,7 +30,6 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
32
30
 
33
31
  rewriteExpandInline();
34
32
 
35
-
36
33
  applyTransformations(csn, {
37
34
  keys: (parent, name, keys, path) => {
38
35
  parent.keys = expand(keys, path.concat('keys'), true);
@@ -538,6 +535,18 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
538
535
  col.as = implicitAs(col.ref);
539
536
  newThing.push(col);
540
537
  }
538
+ else if (col.cast?.type) {
539
+ // TODO: Support expanding `null as field : StructuredT`
540
+ const _art = col.cast._type || inspectRef(path.concat(i, 'cast', 'type')).art;
541
+ if (_art && isStructured(_art)) {
542
+ error('type-invalid-cast', path.concat(i, 'cast', 'type'), {
543
+ '#': col.val !== undefined ? 'val-to-structure' : 'expr-to-structure', value: col.val,
544
+ });
545
+ }
546
+ else {
547
+ newThing.push(col);
548
+ }
549
+ }
541
550
  else {
542
551
  newThing.push(col);
543
552
  }
@@ -13,6 +13,7 @@ const {
13
13
  forAllQueries,
14
14
  sortCsnDefinitionsForTests,
15
15
  } = require('../model/csnUtils');
16
+ const {CompilerAssertion} = require('../base/error');
16
17
 
17
18
  /**
18
19
  * Indicator that a definition is localized and has a convenience view.
@@ -577,14 +578,22 @@ function _addLocalizationViews(csn, options, useJoins, config) {
577
578
  if (!obj || !obj.ref)
578
579
  return;
579
580
  const ref = Array.isArray(obj.ref) ? obj.ref[0] : obj.ref;
580
- if (typeof ref !== 'string')
581
- return;
582
- const def = csn.definitions[ref];
583
- if (def && def[_hasLocalizedView]) {
584
- if (Array.isArray(obj.ref))
585
- obj.ref[0] = def[_hasLocalizedView];
586
- else
581
+ if (typeof ref === 'string') {
582
+ const def = csn.definitions[ref];
583
+ if (def && def[_hasLocalizedView]) {
584
+ if (Array.isArray(obj.ref))
585
+ obj.ref[0] = def[_hasLocalizedView];
586
+ else
587
587
  obj.ref = def[_hasLocalizedView];
588
+ }
589
+
590
+ } else if (ref.id) {
591
+ const def = csn.definitions[ref.id];
592
+ if (def && def[_hasLocalizedView])
593
+ obj.ref[0].id = def[_hasLocalizedView];
594
+
595
+ } else if (options.testMode) {
596
+ throw new CompilerAssertion('Debug me: Unhandled reference during localized-rewrite!');
588
597
  }
589
598
  }
590
599
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "3.9.8",
3
+ "version": "3.9.10",
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)",