@sap/cds-compiler 3.9.10 → 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,21 @@
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
+
10
25
  ## Version 3.9.10 - 2023-08-25
11
26
 
12
27
  ### 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 = {} ) {
@@ -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
@@ -792,6 +792,7 @@ const centralMessageTexts = {
792
792
  incompatible: 'Unexpected EDM Type $(TYPE) for OData $(VERSION)',
793
793
  facet: 'Unexpected EDM Type facet $(NAME) of type $(TYPE) for OData $(VERSION)',
794
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)'
795
796
  },
796
797
  'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(META)',
797
798
  'odata-spec-violation-namespace': {
@@ -819,7 +820,7 @@ const centralMessageTexts = {
819
820
  'odata-anno-preproc': {
820
821
  'std': 'unused message text',
821
822
  'nokey': 'Expected target $(NAME) to have a key element for $(ANNO)',
822
- 'multkeys': 'Expected target $(NAME) to have only one key element',
823
+ 'multkeys': 'Expected target $(NAME) to have only one key element for $(ANNO)',
823
824
  'vhlnokey': 'Expected value help list entity $(NAME) to have a key element for $(ANNO)',
824
825
  'vhlmultkeys': 'Expected value help list entity $(NAME) to have only one key element for $(ANNO)',
825
826
  'notforentity': 'Unexpected usage of $(ANNO) for an entity',
@@ -827,8 +828,8 @@ const centralMessageTexts = {
827
828
  'noassoc': 'Expected association $(ID) to exist for $(ANNO)',
828
829
  'vallistignored': '$(NAME) is ignored for $(ANNO) as $(CODE) is present',
829
830
  'notastring': 'Expected value to be a string for $(ANNO)',
830
- 'notexist': 'Expect entity $(ID) to exist for $(ANNO)',
831
- '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'
832
833
  },
833
834
  // -----------------------------------------------------------------------------------
834
835
  // GenericTranslation:
@@ -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
@@ -1009,8 +1009,9 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1009
1009
  else {
1010
1010
  // regular record
1011
1011
  if (dTypeIsACollection) {
1012
- message('odata-anno-value', msg.location,
1013
- { anno: msg.anno(), str: 'structured', '#': 'incompval' });
1012
+ message('odata-anno-value', msg.location, {
1013
+ anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'incompval',
1014
+ });
1014
1015
  }
1015
1016
  oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName, dTypeIsACollection, msg));
1016
1017
  }
@@ -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
  */
@@ -516,7 +516,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
516
516
  *
517
517
  * @param {Array} thing
518
518
  * @param {CSN.Path} path
519
- * @param {boolean} [withAlias=false] Whether to "expand" the (implicit) alias aswell.
519
+ * @param {boolean} [withAlias] Whether to "expand" the (implicit) alias aswell.
520
520
  * @returns {Array} New array - with all structured things expanded
521
521
  */
522
522
  function expand( thing, path, withAlias = false ) {
@@ -617,7 +617,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
617
617
  *
618
618
  * @param {object} base The raw set of things a * can expand to
619
619
  * @param {Array} subs Things - the .expand/.inline or .columns
620
- * @param {string[]} [excluding=[]]
620
+ * @param {string[]} [excluding]
621
621
  * @returns {Array} If there was a star, expand it and handle shadowing/excluding, else just return subs
622
622
  */
623
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 }
@@ -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.10",
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)",