@sap/cds-compiler 3.9.8 → 3.9.12

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,31 @@
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
+
11
+ ## Version 3.9.12 - 2023-12-06
12
+
13
+ ### Fixed
14
+
15
+ - compiler:
16
+ + SQL function `STDDEV(*)` was not parsable.
17
+ + Numbers in scientific notation `-1e1` were sometimes not recognized via CSN input.
18
+ - for.odata: Fix crash when using a projection with associations as action parameter type.
19
+ - for.hana: Fix a bug in association to join translation, expect ON condition operand to be a function without arguments.
20
+ - to.edm(x):
21
+ + Omit `EntitySet` attribute on `Edm.FunctionImport` and `Edm.ActionImport` that return a singleton.
22
+ + Don't render `Scale: variable` for `cds.Decimal(scale:0)`.
23
+ - to.sql/hdi/hdbcds: consider `having` predicate for `exists` expansion
24
+
25
+ ## Version 3.9.10 - 2023-08-25
26
+
27
+ ### Fixed
28
+
29
+ - to.edm(x): Error reporting for incorrect handling of "Collection()" has been improved.
30
+ - to.sql/hdi/hdbcds: Views on views with parameters did not have localized convenience views based on
31
+ other localized views (missing `localized.` prefix in FROM clause)
32
+ - to.sql: Casting expressions to a structured type yields a proper error instead of strange compiler error.
33
+ - to.sql.migration: Don't drop-create views marked with `@cds.persistence.exists` or `@cds.persistence.skip`
34
+
10
35
  ## Version 3.9.8 - 2023-08-03
11
36
 
12
37
  ### Fixed
package/lib/api/main.js CHANGED
@@ -48,7 +48,7 @@ const warnAboutMismatchOdata = [ 'odataVersion' ];
48
48
  * @param {string} transformation Name of the transformation - odata or hana
49
49
  * @param {NestedOptions} options Options used for the transformation
50
50
  * @param {string[]} relevantOptionNames Option names that are defining characteristics
51
- * @param {string[]} [optionalOptionNames=[]] Option names that should be attached as a fyi
51
+ * @param {string[]} [optionalOptionNames] Option names that should be attached as a fyi
52
52
  */
53
53
  function attachTransformerCharacteristics( csn, transformation, options,
54
54
  relevantOptionNames, optionalOptionNames = [] ) {
@@ -141,7 +141,7 @@ function odataInternal( csn, internalOptions ) {
141
141
  * Return a odata-transformed CSN
142
142
  *
143
143
  * @param {CSN.Model} csn Clean input CSN
144
- * @param {ODataOptions} [options={}] Options
144
+ * @param {ODataOptions} [options] Options
145
145
  * @returns {oDataCSN} Return an oData-pre-processed CSN
146
146
  */
147
147
  function odata( csn, options = {} ) {
@@ -154,7 +154,7 @@ function odata( csn, options = {} ) {
154
154
  * Process the given csn back to cdl.
155
155
  *
156
156
  * @param {object} csn CSN to process
157
- * @param {object} [options={}] Options
157
+ * @param {object} [options] Options
158
158
  * @returns {object} { model: string, namespace: string }
159
159
  */
160
160
  function cdl( csn, options = {} ) {
@@ -167,7 +167,7 @@ function cdl( csn, options = {} ) {
167
167
  * Transform a CSN like to.sql
168
168
  *
169
169
  * @param {CSN.Model} csn Plain input CSN
170
- * @param {SqlOptions} [options={}] Options
170
+ * @param {SqlOptions} [options] Options
171
171
  * @returns {CSN.Model} CSN transformed like to.sql
172
172
  * @private
173
173
  */
@@ -182,7 +182,7 @@ function forSql( csn, options = {} ) {
182
182
  * Transform a CSN like to.hdi
183
183
  *
184
184
  * @param {CSN.Model} csn Plain input CSN
185
- * @param {HdiOptions} [options={}] Options
185
+ * @param {HdiOptions} [options] Options
186
186
  * @returns {CSN.Model} CSN transformed like to.hdi
187
187
  * @private
188
188
  */
@@ -197,7 +197,7 @@ function forHdi( csn, options = {} ) {
197
197
  * Transform a CSN like to.hdbcds
198
198
  *
199
199
  * @param {CSN.Model} csn Plain input CSN
200
- * @param {HdbcdsOptions} [options={}] Options
200
+ * @param {HdbcdsOptions} [options] Options
201
201
  * @returns {CSN.Model} CSN transformed like to.hdbcds
202
202
  * @private
203
203
  */
@@ -214,7 +214,7 @@ function forHdbcds( csn, options = {} ) {
214
214
  * Process the given CSN into SQL.
215
215
  *
216
216
  * @param {CSN.Model} csn A clean input CSN
217
- * @param {SqlOptions} [options={}] Options
217
+ * @param {SqlOptions} [options] Options
218
218
  * @returns {SQL[]} Array of SQL statements, tables first, views second
219
219
  */
220
220
  function sql( csn, options = {} ) {
@@ -235,7 +235,7 @@ function sql( csn, options = {} ) {
235
235
  * Process the given CSN into HDI artifacts.
236
236
  *
237
237
  * @param {CSN.Model} csn A clean input CSN
238
- * @param {HdiOptions} [options={}] Options
238
+ * @param {HdiOptions} [options] Options
239
239
  * @returns {HDIArtifacts} { <filename>:<content>, ...}
240
240
  */
241
241
  function hdi( csn, options = {} ) {
@@ -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;
@@ -529,7 +529,7 @@ sql.migration = sqlMigration;
529
529
  * Process the given CSN into HDBCDS artifacts.
530
530
  *
531
531
  * @param {any} csn A clean input CSN
532
- * @param {HdbcdsOptions} [options={}] Options
532
+ * @param {HdbcdsOptions} [options] Options
533
533
  * @returns {HDBCDS} { <filename>:<content>, ...}
534
534
  */
535
535
  function hdbcds( csn, options = {} ) {
@@ -546,7 +546,7 @@ function hdbcds( csn, options = {} ) {
546
546
  * Generate a edm document for the given service
547
547
  *
548
548
  * @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
549
- * @param {ODataOptions} [options={}] Options
549
+ * @param {ODataOptions} [options] Options
550
550
  * @returns {edm} The JSON representation of the service
551
551
  */
552
552
  function edm( csn, options = {} ) {
@@ -577,7 +577,7 @@ edm.all = edmall;
577
577
  * Generate edm documents for all services
578
578
  *
579
579
  * @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
580
- * @param {ODataOptions} [options={}] Options
580
+ * @param {ODataOptions} [options] Options
581
581
  * @returns {edms} { <service>:<JSON representation>, ...}
582
582
  */
583
583
  function edmall( csn, options = {} ) {
@@ -608,7 +608,7 @@ function edmall( csn, options = {} ) {
608
608
  * Generate a edmx document for the given service
609
609
  *
610
610
  * @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
611
- * @param {ODataOptions} [options={}] Options
611
+ * @param {ODataOptions} [options] Options
612
612
  * @returns {edmx} The XML representation of the service
613
613
  */
614
614
  function edmx( csn, options = {} ) {
@@ -640,7 +640,7 @@ edmx.all = edmxall;
640
640
  * Generate edmx documents for all services
641
641
  *
642
642
  * @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
643
- * @param {ODataOptions} [options={}] Options
643
+ * @param {ODataOptions} [options] Options
644
644
  * @returns {edmxs} { <service>:<XML representation>, ...}
645
645
  */
646
646
  function edmxall( csn, options = {} ) {
@@ -74,9 +74,9 @@ const overallOptions = publicOptionsNewAPI.concat(privateOptions);
74
74
  * Apply defaults and make sure that the "hard requirements" are met,
75
75
  * i.e. src: sql if to.sql() was called.
76
76
  *
77
- * @param {FlatOptions} [input={}] Input options
78
- * @param {FlatOptions} [defaults={}] Default options to apply
79
- * @param {FlatOptions} [hardRequire={}] Hard requirements to enforce
77
+ * @param {FlatOptions} [input] Input options
78
+ * @param {FlatOptions} [defaults] Default options to apply
79
+ * @param {FlatOptions} [hardRequire] Hard requirements to enforce
80
80
  * @param {object} [customValidators] Custom validations to run instead of defaults
81
81
  * @param {string[]} [combinationValidators] Option combinations to validate
82
82
  * @param {string} moduleName The called module, e.g. 'for.odata', 'to.hdi'. Needed to initialize the message functions
@@ -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
  // -----------------------------------------------------------------------------------
@@ -784,6 +792,7 @@ const centralMessageTexts = {
784
792
  incompatible: 'Unexpected EDM Type $(TYPE) for OData $(VERSION)',
785
793
  facet: 'Unexpected EDM Type facet $(NAME) of type $(TYPE) for OData $(VERSION)',
786
794
  external: 'Referenced type $(TYPE) marked as $(ANNO) can\'t be rendered as $(CODE) in service $(NAME) for OData $(VERSION)',
795
+ scale: 'Expected scale $(NUMBER) to be less than or equal to precision $(RAWVALUE)'
787
796
  },
788
797
  'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(META)',
789
798
  'odata-spec-violation-namespace': {
@@ -811,7 +820,7 @@ const centralMessageTexts = {
811
820
  'odata-anno-preproc': {
812
821
  'std': 'unused message text',
813
822
  'nokey': 'Expected target $(NAME) to have a key element for $(ANNO)',
814
- 'multkeys': 'Expected target $(NAME) to have only one key element',
823
+ 'multkeys': 'Expected target $(NAME) to have only one key element for $(ANNO)',
815
824
  'vhlnokey': 'Expected value help list entity $(NAME) to have a key element for $(ANNO)',
816
825
  'vhlmultkeys': 'Expected value help list entity $(NAME) to have only one key element for $(ANNO)',
817
826
  'notforentity': 'Unexpected usage of $(ANNO) for an entity',
@@ -819,8 +828,8 @@ const centralMessageTexts = {
819
828
  'noassoc': 'Expected association $(ID) to exist for $(ANNO)',
820
829
  'vallistignored': '$(NAME) is ignored for $(ANNO) as $(CODE) is present',
821
830
  'notastring': 'Expected value to be a string for $(ANNO)',
822
- 'notexist': 'Expect entity $(ID) to exist for $(ANNO)',
823
- 'txtarr': 'Expect $(ANNO) shortcut to have a $(NAME) annotation'
831
+ 'notexist': 'Expected entity $(ID) to exist for $(ANNO)',
832
+ 'txtarr': 'Expected $(ANNO) shortcut to have a $(NAME) annotation'
824
833
  },
825
834
  // -----------------------------------------------------------------------------------
826
835
  // GenericTranslation:
@@ -849,7 +858,7 @@ const centralMessageTexts = {
849
858
  // -----------------------------------------------------------------------------------
850
859
  'enum': 'Value $(VALUE) is not one out of $(RAWVALUES) for $(ANNO) of type $(TYPE)',
851
860
  'std': 'Unexpected value $(VALUE) for $(ANNO) of type $(TYPE)',
852
- 'struct': 'Unexpected $(STR) value for $(ANNO) of type $(TYPE)',
861
+ 'incompval': 'Unexpected $(STR) value for $(ANNO) of type $(TYPE)',
853
862
  'nestedcollection': 'Nested collections are not supported for $(ANNO)',
854
863
  'enumincollection': 'Enum inside collection is not supported for $(ANNO)',
855
864
  'multexpr': 'EDM JSON code contains more than one dynamic expression: $(RAWVALUES) for $(ANNO)',
@@ -127,10 +127,10 @@ const commonQueryValidators = [ validateMixinOnCondition ];
127
127
  *
128
128
  * @param {CSN.Model} csn CSN to check
129
129
  * @param {object} that Will be provided to the validators via "this"
130
- * @param {object[]} [csnValidators=[]] Validations on whole CSN using applyTransformations
131
- * @param {Function[]} [memberValidators=[]] Validations on member-level
132
- * @param {Function[]} [artifactValidators=[]] Validations on artifact-level
133
- * @param {Function[]} [queryValidators=[]] Validations on query-level
130
+ * @param {object[]} [csnValidators] Validations on whole CSN using applyTransformations
131
+ * @param {Function[]} [memberValidators] Validations on member-level
132
+ * @param {Function[]} [artifactValidators] Validations on artifact-level
133
+ * @param {Function[]} [queryValidators] Validations on query-level
134
134
  * @param {object} iterateOptions can be used to skip certain kinds from being iterated e.g. 'action' and 'function' for hana
135
135
  * @returns {Function} Function taking no parameters, that cleans up the attached helpers
136
136
  */
@@ -119,7 +119,7 @@ const specialFunctions = compileFunctions( {
119
119
  MAX: 'COUNT',
120
120
  SUM: 'COUNT',
121
121
  AVG: 'COUNT',
122
- STDDDEV: 'COUNT',
122
+ STDDEV: 'COUNT',
123
123
  VAR: 'COUNT',
124
124
  LOCATE_REGEXPR: [
125
125
  {
@@ -216,7 +216,7 @@ const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})
216
216
  // eslint-disable-next-line max-len
217
217
  const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
218
218
  // YYYY - MM - dd T HH : mm : ss . fraction TZD
219
- const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]\d+)?[ \t]*$/i;
219
+ const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]?\d+)?[ \t]*$/i;
220
220
 
221
221
  /**
222
222
  * Patterns for literal token tests and creation. The value is a map from the
@@ -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,12 @@ 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', type: dTypeName, '#': 'incompval',
1014
+ });
1015
+ }
1016
+ oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName, dTypeIsACollection, msg));
1009
1017
  }
1010
1018
  }
1011
1019
  else {
@@ -1021,7 +1029,6 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1021
1029
  }
1022
1030
  oTarget.setJSON( { [res.jsonName] : res.value });
1023
1031
  }
1024
-
1025
1032
  // found an enum value ("#"), check whether this fits
1026
1033
  // the expected type "dTypeName"
1027
1034
  function checkEnumValue(value) {
@@ -1053,15 +1060,17 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1053
1060
  // cAnnoValue: array
1054
1061
  // dTypeName: expected type, already identified as enum type
1055
1062
  // array is expected to contain enum values
1056
- function checkMultiEnumValue(cAnnoValue) {
1057
- // we know that dTypeName is not null
1063
+ function checkMultiEnumValue( ) {
1064
+ // we know that dTypeName is not null
1058
1065
  const type = getDictType(dTypeName);
1059
- if (!type || type['IsFlags'] !== 'true') {
1066
+ if (!type || type.IsFlags !== 'true') {
1060
1067
  message('odata-anno-value', msg.location,
1061
- { anno: msg.anno(),
1068
+ {
1069
+ anno: msg.anno(),
1062
1070
  str: 'collection',
1063
1071
  type: dTypeName,
1064
- '#': 'struct' });
1072
+ '#': 'incompval',
1073
+ });
1065
1074
  }
1066
1075
 
1067
1076
  let index = 0;
@@ -1092,8 +1101,6 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1092
1101
  }
1093
1102
  }
1094
1103
 
1095
-
1096
-
1097
1104
  // found an expression value ("=") "expr"
1098
1105
  // expected type is dTypeName
1099
1106
  // note: expr can also be provided if an enum/complex type/collection is expected
@@ -1264,18 +1271,20 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1264
1271
  // dTypeName : name of the expected record type according to vocabulary, may be null
1265
1272
  //
1266
1273
  // can be called for a record directly below a term, or at a deeper level
1267
- function generateRecord(obj, termName, dTypeName, msg) {
1274
+ function generateRecord( obj, termName, dTypeName, dTypeIsACollection, msg ) {
1268
1275
  /** @type {object} */
1269
1276
  const newRecord = new Edm.Record(v);
1270
1277
 
1271
1278
  // first determine what is the actual type to be used for the record
1272
1279
  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
1280
+ if (!getDictType(dTypeName) && !isPrimitiveType(dTypeName) && !dTypeIsACollection) {
1281
+ message('odata-anno-dict', msg.location, { anno: msg.anno(), type: dTypeName });
1282
+ } else {
1277
1283
  message('odata-anno-value', msg.location,
1278
- { anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'struct' });
1284
+ {
1285
+ anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'incompval',
1286
+ });
1287
+ }
1279
1288
  return newRecord;
1280
1289
  }
1281
1290
 
@@ -1383,19 +1392,14 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1383
1392
 
1384
1393
  // annoValue is an array
1385
1394
  // dTypeName : Collection(...) according to dictionary
1386
- function generateCollection(annoValue, termName, dTypeName, msg) {
1395
+ function generateCollection( annoValue, termName, dTypeName, dTypeIsACollection, msg ) {
1387
1396
  const newCollection = new Edm.Collection(v);
1388
1397
 
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
- }
1398
+ if (dTypeName && !dTypeIsACollection) {
1399
+ message('odata-anno-value', msg.location,
1400
+ {
1401
+ anno: msg.anno(), str: 'collection', type: dTypeName, '#': 'incompval',
1402
+ });
1399
1403
  }
1400
1404
 
1401
1405
  let index = 0;
@@ -1412,7 +1416,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1412
1416
  }
1413
1417
  else if (value && typeof value === 'object') {
1414
1418
  if (value['=']) {
1415
- const res = handleExpression(value['='], innerTypeName);
1419
+ const res = handleExpression(value['='], dTypeName);
1416
1420
  const newPropertyPath = new Edm.ValueThing(v, res.name, res.value );
1417
1421
  newPropertyPath.setJSON( { [res.name] : res.value } );
1418
1422
  newCollection.append(newPropertyPath);
@@ -1425,13 +1429,13 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1425
1429
  newCollection.append(handleEdmJson(value['$edmJson'], msg));
1426
1430
  }
1427
1431
  else {
1428
- newCollection.append(generateRecord(value, termName, innerTypeName, msg));
1432
+ newCollection.append(generateRecord(value, termName, dTypeName, dTypeIsACollection, msg));
1429
1433
  }
1430
1434
  }
1431
1435
  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 });
1436
+ const res = handleSimpleValue(value, dTypeName, msg);
1437
+ const newThing = (value === null) ? new Edm.ValueThing(v, 'Null') : new Edm.ValueThing(v, res.name, value );
1438
+ newThing.setJSON( { [res.jsonName]: res.value });
1435
1439
  newCollection.append(newThing);
1436
1440
  }
1437
1441
 
@@ -1783,23 +1787,21 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1783
1787
  return dTypeName;
1784
1788
  }
1785
1789
 
1786
- function stripCollection(typeName) {
1787
- const match = typeName.match(/^Collection\((.+)\)/);
1788
- if (match) {
1789
- typeName = match[1];
1790
+ function stripCollection( typeName ) {
1791
+ if (typeName) {
1792
+ const match = typeName.match(/^Collection\((.+)\)/);
1793
+ if (match)
1794
+ return [ match[1], true ];
1790
1795
  }
1791
- return typeName;
1796
+
1797
+ return [ typeName, false ];
1792
1798
  }
1793
1799
 
1794
1800
  function isPrimitiveType(typeName) {
1795
1801
  return typeName.split('.')[0] === 'Edm';
1796
1802
  }
1797
1803
 
1798
- function isCollection(typeName) {
1799
- return typeName.match(/^Collection\((.+)\)/) !== null;
1800
- }
1801
-
1802
- function isEnumType(dTypeName) {
1804
+ function isEnumType( dTypeName ) {
1803
1805
  const type = getDictType(dTypeName);
1804
1806
  return type && type['$kind'] === 'EnumType';
1805
1807
  }
@@ -40,11 +40,11 @@ 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' } );
44
+ }
45
+ else if (keyNames.length > 1) {
46
+ message('odata-anno-preproc', assoc.$path, { anno, name: targetName, '#': 'multkeys' });
45
47
  }
46
- else if (keyNames.length > 1)
47
- message('odata-anno-preproc', null, { anno, name: targetName, '#': 'multkeys' });
48
48
 
49
49
  return keyNames[0];
50
50
  }
@@ -745,7 +745,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
745
745
  if(rt) // add EntitySet attribute only if return type is a non abstract entity
746
746
  {
747
747
  const definition = schemaCsn.definitions[rt];
748
- if(definition && definition.kind === 'entity' && !definition.abstract)
748
+ if (definition && definition.kind === 'entity' && !definition.abstract && !edmUtils.isSingleton(definition))
749
749
  {
750
750
  actionImport.setEdmAttribute('EntitySet', edmUtils.getBaseName(rt));
751
751
  }
@@ -1104,6 +1104,16 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
1104
1104
  { type:edmType, name, version: (p.v4 ? '4.0' : '2.0'), '#': 'facet' });
1105
1105
  }
1106
1106
  });
1107
+ if (edmType === 'Edm.Decimal') {
1108
+ const precision = Number.parseInt(p._edmAttributes.Precision, 10);
1109
+ const scale = Number.parseInt(p._edmAttributes.Scale, 10);
1110
+ if (!Number.isNaN(precision) && !Number.isNaN(scale) && scale > precision) {
1111
+ message('odata-spec-violation-type', pLoc,
1112
+ {
1113
+ type: edmType, number: scale, rawvalue: precision, ersion: (p.v4 ? '4.0' : '2.0'), '#': 'scale',
1114
+ });
1115
+ }
1116
+ }
1107
1117
  }
1108
1118
  else {
1109
1119
  message('odata-spec-violation-type-unknown', pLoc,
@@ -610,7 +610,7 @@ function addTypeFacets(node, csn)
610
610
  // map both floating and variable to => variable
611
611
  if(node._edmAttributes.Scale === 'floating')
612
612
  node.setEdmAttribute('Scale', 'variable');
613
- if(!csn.precision && !csn.scale)
613
+ if (csn.precision == null && csn.scale == null)
614
614
  // if Decimal has no p, s set scale 'variable'
615
615
  node.setXml( { Scale: 'variable' } ); // floating is V4.01
616
616
  }
@@ -17,8 +17,8 @@ const { setProp } = require('../../base/model');
17
17
  * @param {object} parent The "parent" of which we transform a property of
18
18
  * @param {string} prop The property of parent to start at
19
19
  * @param {object} customTransformers Map of prop to transform and function to apply
20
- * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
21
- * @param {applyTransformationsOptions} [options={}]
20
+ * @param {Function[]} [artifactTransformers] Transformations to run on the artifacts, like forEachDefinition
21
+ * @param {applyTransformationsOptions} [options]
22
22
  * @param {CSN.Path} path Path to parent
23
23
  * @returns {object} parent with transformations applied
24
24
  */
@@ -165,8 +165,8 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
165
165
  *
166
166
  * @param {object} csn CSN to enrich in-place
167
167
  * @param {object} customTransformers Map of _prop to transform and function to apply
168
- * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
169
- * @param {applyTransformationsOptions} [options={}]
168
+ * @param {Function[]} [artifactTransformers] Transformations to run on the artifacts, like forEachDefinition
169
+ * @param {applyTransformationsOptions} [options]
170
170
  * @returns {object} CSN with transformations applied
171
171
  */
172
172
  function applyTransformations( csn, customTransformers = {}, artifactTransformers = [], options = { } ) {
@@ -192,7 +192,7 @@ function applyTransformations( csn, customTransformers = {}, artifactTransformer
192
192
  * @param {object} parent The "parent" of which we transform a property of
193
193
  * @param {string} prop The property of parent to start at
194
194
  * @param {object} customTransformers Map of prop to transform and function to apply
195
- * @param {applyTransformationsOptions} [options={}]
195
+ * @param {applyTransformationsOptions} [options]
196
196
  * @param {CSN.Path} path Path pointing to parent
197
197
  * @returns {object} parent[prop] with transformations applied
198
198
  */
@@ -213,7 +213,7 @@ function applyTransformationsOnNonDictionary( parent, prop, customTransformers =
213
213
  *
214
214
  * @param {object} dictionary Dictionary to enrich in-place
215
215
  * @param {object} customTransformers Map of prop to transform and function to apply
216
- * @param {applyTransformationsOptions} [options={}]
216
+ * @param {applyTransformationsOptions} [options]
217
217
  * @param {CSN.Path} path Path pointing to parent
218
218
  * @returns {object} dictionary with transformations applied
219
219
  */
@@ -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);
@@ -519,7 +516,7 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
519
516
  *
520
517
  * @param {Array} thing
521
518
  * @param {CSN.Path} path
522
- * @param {boolean} [withAlias=false] Whether to "expand" the (implicit) alias aswell.
519
+ * @param {boolean} [withAlias] Whether to "expand" the (implicit) alias aswell.
523
520
  * @returns {Array} New array - with all structured things expanded
524
521
  */
525
522
  function expand( thing, path, withAlias = false ) {
@@ -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
  }
@@ -608,7 +617,7 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
608
617
  *
609
618
  * @param {object} base The raw set of things a * can expand to
610
619
  * @param {Array} subs Things - the .expand/.inline or .columns
611
- * @param {string[]} [excluding=[]]
620
+ * @param {string[]} [excluding]
612
621
  * @returns {Array} If there was a star, expand it and handle shadowing/excluding, else just return subs
613
622
  */
614
623
  function replaceStar( base, subs, excluding = [] ) {
@@ -192,7 +192,7 @@ function flattenAllStructStepsInRefs( csn, options, resolved, pathDelimiter, ite
192
192
  * For each step of the links, check if there is a type reference.
193
193
  * If there is, resolve it and store the result in a WeakMap.
194
194
  *
195
- * @param {Array} [links=[]]
195
+ * @param {Array} [links]
196
196
  * @todo seems too hacky
197
197
  * @returns {WeakMap} A WeakMap where a link is the key and the type is the value
198
198
  */
@@ -63,6 +63,8 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
63
63
  if (query.SELECT && query.SELECT.where && query.SELECT.where.length > 1)
64
64
  toProcess.push([ path.slice(0, -1), path.concat('where') ]);
65
65
 
66
+ if (query.SELECT && query.SELECT.having?.length > 1)
67
+ toProcess.push([ path.slice(0, -1), path.concat('having') ]);
66
68
 
67
69
  if (query.SELECT && query.SELECT.columns)
68
70
  toProcess.push([ path.slice(0, -1), path.concat('columns') ]);
@@ -98,7 +98,9 @@ function transform4odataWithCsn(inputModel, options) {
98
98
  inspectRef,
99
99
  artifactRef,
100
100
  effectiveType,
101
- getFinalTypeInfo
101
+ getFinalTypeInfo,
102
+ dropDefinitionCache,
103
+ initDefinition,
102
104
  } = csnUtils;
103
105
 
104
106
  // are we working with structured OData or not
@@ -147,6 +149,8 @@ function transform4odataWithCsn(inputModel, options) {
147
149
  // TODO: handle artifact.projection instead of artifact.query correctly in future V2
148
150
  if (def.kind === 'entity' && def.projection) {
149
151
  def.query = { SELECT: def.projection };
152
+ dropDefinitionCache(def);
153
+ initDefinition(def);
150
154
  }
151
155
  }],
152
156
  { skipArtifact: isExternalServiceMember }
@@ -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
 
@@ -730,7 +730,8 @@ function translateAssocsToJoins(model, inputOptions = {})
730
730
  // this will substitute multiple backlink conditions ($self = ... AND $self = ...AND ...)
731
731
  if(expr.op) {
732
732
  let x = clone(expr);
733
- x.args = expr.args.map(cloneOnCondition);
733
+ if(expr.args)
734
+ x.args = expr.args.map(cloneOnCondition);
734
735
  return x;
735
736
  }
736
737
 
@@ -328,9 +328,9 @@ module.exports = (csn, options) => {
328
328
  * passed to this function.
329
329
  *
330
330
  * @param {CSN.Element} member
331
- * @param {object} [except=null] List of properties which should not be propagated along the origin chain
331
+ * @param {object} [except] List of properties which should not be propagated along the origin chain
332
332
  * of the `member`
333
- * @param {object} [force=null] Overwrite any member propagation rules or any except and always propagate the corresponding keys
333
+ * @param {object} [force] Overwrite any member propagation rules or any except and always propagate the corresponding keys
334
334
  */
335
335
  function propagateMemberPropsFromOrigin( member, except = null, force = null ) {
336
336
  const memberChain = getOriginChain(member);
@@ -613,8 +613,8 @@ module.exports = (csn, options) => {
613
613
  * @param {object} to
614
614
  * @param {Function} getCustomRule getter for the `memberProps` or `defProps`
615
615
  * which shall be used for retrieving custom rules
616
- * @param {object} [except=null] array of properties which should not be propagated
617
- * @param {object} [force=null] Force propagation of the contained keys via a custom rule.
616
+ * @param {object} [except] array of properties which should not be propagated
617
+ * @param {object} [force] Force propagation of the contained keys via a custom rule.
618
618
  */
619
619
  function copyProperties( from, to, getCustomRule, except = null, force = null ) {
620
620
  const keys = Object.keys(from);
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.12",
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)",