@sap/cds-compiler 6.8.0 → 6.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +4 -0
  3. package/bin/cdshi.js +1 -0
  4. package/lib/api/main.js +8 -1
  5. package/lib/api/options.js +3 -1
  6. package/lib/base/builtins.js +13 -9
  7. package/lib/base/csnRefs.js +8 -10
  8. package/lib/base/message-registry.js +61 -3
  9. package/lib/base/messages.js +2 -0
  10. package/lib/base/optionProcessor.js +2 -0
  11. package/lib/base/specialOptions.js +1 -1
  12. package/lib/compiler/assert-consistency.js +11 -9
  13. package/lib/compiler/base.js +5 -1
  14. package/lib/compiler/define.js +1 -1
  15. package/lib/compiler/dictionaries.js +2 -3
  16. package/lib/compiler/extend.js +137 -27
  17. package/lib/compiler/lsp-api.js +3 -3
  18. package/lib/compiler/populate.js +4 -5
  19. package/lib/compiler/resolve.js +50 -35
  20. package/lib/compiler/shared.js +33 -14
  21. package/lib/compiler/tweak-assocs.js +2 -2
  22. package/lib/compiler/utils.js +26 -23
  23. package/lib/compiler/xpr-rewrite.js +2 -2
  24. package/lib/edm/EdmPrimitiveTypeDefinitions.js +4 -1
  25. package/lib/edm/annotations/genericTranslation.js +49 -6
  26. package/lib/edm/csn2edm.js +4 -2
  27. package/lib/edm/edm.js +6 -3
  28. package/lib/gen/BaseParser.js +59 -97
  29. package/lib/gen/CdlGrammar.checksum +1 -1
  30. package/lib/gen/CdlParser.js +2055 -1969
  31. package/lib/gen/Dictionary.json +67 -7
  32. package/lib/json/from-csn.js +7 -12
  33. package/lib/json/to-csn.js +59 -35
  34. package/lib/parsers/AstBuildingParser.js +43 -30
  35. package/lib/render/toCdl.js +46 -27
  36. package/lib/render/toSql.js +9 -0
  37. package/lib/render/utils/common.js +3 -2
  38. package/lib/tool-lib/enrichCsn.js +1 -0
  39. package/lib/transform/effective/flattening.js +6 -5
  40. package/lib/transform/effective/main.js +5 -0
  41. package/lib/transform/forOdata.js +20 -2
  42. package/lib/transform/forRelationalDB.js +8 -4
  43. package/lib/transform/tupleExpansion.js +40 -0
  44. package/package.json +3 -40
package/CHANGELOG.md CHANGED
@@ -13,6 +13,61 @@ we might not list every change in its behavior here.
13
13
  Productive code should never require a `beta` flag to be set, and
14
14
  might use a deprecated flag only for a limited period of time.
15
15
 
16
+ ## Version 6.9.1 - 2026-05-05
17
+
18
+ ### Bug Fixes
19
+
20
+ - **compiler:**
21
+ + make an element added via `extend` correctly shadow an element from an include
22
+ + do not issue a warning for a correct use of `$projection`
23
+ - **odata:**
24
+ + do not generate wrong ReferentialConstraints for unmanaged Composition without a partner
25
+ + render Partner attribute on forward association correctly
26
+
27
+
28
+
29
+ ## Version 6.9.0 - 2026-04-21
30
+
31
+ ### Features
32
+
33
+ - **compiler:**
34
+ + add support for extending views/projections with several SQL clauses:
35
+ `where`, `group by`, `having`, `order by`, `limit`
36
+ + allow extending derived enum types/elements by adding enum symbols
37
+ + inside `extend … with enum {…}`, it is now possible to use `extend existingEnumSymbol with @Anno`,
38
+ like it is already possible inside `extend … with {…}`
39
+ + when the new options `v7KeyPropagation` is set, the propagation of the `key` property in queries is simplified,
40
+ and the `key` property is not propagated when including structures into types
41
+ - **odata:** set `meta.compilerCsnFlavor` in the OData transformed CSN
42
+ - **seal**: set `meta.compilerCsnFlavor` in the SEAL transformed CSN
43
+ - **effective:** set `meta.compilerCsnFlavor` in the effective CSN
44
+ - **sql:**
45
+ + set `meta.compilerCsnFlavor` in SQL transformed CSN
46
+ + allow single-leafed structures within a `list` on the left-hand side of the `IN` operator.
47
+ For example `(author, struct) in (...)` becomes `(author_ID, struct_leaf) in (...)`.
48
+ + support `cds.Vector` on Postgres and H2
49
+
50
+ ### Bug Fixes
51
+
52
+ - **compiler:**
53
+ + don't introduce a strange `cast` property in the CSN for a column with an expand on a to-many association
54
+ + while crawling tokens for LSP, allow extensions without name
55
+ - **effective:** don't absolutify paths in filters in annotations
56
+ - **sql:** properly resolve references in annotation values and `on` conditions for a column inside an expand without base reference
57
+
58
+ ### Improvements
59
+
60
+ - **compiler:**
61
+ + add `compilerVersion` to CSN meta
62
+ + issue a warning if a structure in an annotation value would have the same CSN representation as an expression
63
+ + issue warnings for extends on built-in types
64
+ + warn on duplicate members from includes via extend
65
+ - **odata:**
66
+ + update OData vocabularies: Common, Core, UI
67
+ + using `@readonly` with an expression value on entities or aspects is no longer silently accepted and now produces an error
68
+
69
+
70
+
16
71
  ## Version 6.8.0 - 2026-03-05
17
72
 
18
73
  ### Features
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Getting started
2
2
 
3
3
 
4
+
4
5
  ## Installation and Usage
5
6
 
6
7
  Install with npm:
@@ -20,16 +21,19 @@ Or maintain your package.json dependencies as follows:
20
21
  If your project already has a dependency to `@sap/cds`, nothing has to be done.
21
22
 
22
23
 
24
+
23
25
  ## Documentation
24
26
 
25
27
  Please refer to the [official CDS documentation](https://cap.cloud.sap/docs/cds/).
26
28
 
27
29
 
30
+
28
31
  ## How to Obtain Support
29
32
 
30
33
  In case you find a bug, please report an [incident](https://cap.cloud.sap/docs/resources/#reporting-incidents) on SAP Support Portal.
31
34
 
32
35
 
36
+
33
37
  ## History and License
34
38
 
35
39
  The cds-compiler uses [Semantic Versioning](./doc/Versioning.md) for its version numbers.
package/bin/cdshi.js CHANGED
@@ -32,6 +32,7 @@ const categoryChars = { // default: first char of category name
32
32
  ExtService: 'S', // highlight like service definition
33
33
  ExtContext: 'C', // highlight like context definition
34
34
  // ExtElement: 'E', // using the first letter is the default
35
+ ExtEnum: 'H', // highlight like enum symbol definition
35
36
  ExtBoundAction: 'B', // highlight like bound action definition
36
37
  ExtParam: 'P', // highlight like entity/action parameter definition
37
38
  FromImplicit: 'W',
package/lib/api/main.js CHANGED
@@ -307,7 +307,14 @@ function forEffectiveInternal( csn, options, internalOptions, messageFunctions )
307
307
  function forSeal( csn, options, messageFunctions ) {
308
308
  const internalOptions = prepareOptions.for.seal(options);
309
309
  internalOptions.transformation = 'effective';
310
- return forEffectiveInternal(csn, options, internalOptions, messageFunctions);
310
+ const result = forEffectiveInternal(csn, options, internalOptions, messageFunctions);
311
+
312
+ if (!options.testMode) {
313
+ result.meta ??= {};
314
+ result.meta.compilerCsnFlavor = 'seal';
315
+ }
316
+
317
+ return result;
311
318
  }
312
319
 
313
320
  /**
@@ -2,7 +2,8 @@
2
2
 
3
3
  // The options are specified in ../base/optionProcessor.js (and some other files).
4
4
  // Some backends feel the need to "translate" option, which require to list
5
- // all options also here (as public" or "private" option).
5
+ // all options also here (as "public" or "private" option).
6
+ // Parser and core compiler options might not have been listed here.
6
7
 
7
8
  const { validate, generateStringValidator } = require('./validate');
8
9
  const { makeMessageFunction } = require('../base/messages');
@@ -25,6 +26,7 @@ const publicOptionsNewAPI = [
25
26
  'defaultStringLength',
26
27
  'csnFlavor',
27
28
  'noDollarCalc',
29
+ 'v7KeyPropagation',
28
30
  // DB
29
31
  'sqlDialect',
30
32
  'sqlMapping',
@@ -89,29 +89,33 @@ function isMagicVariable( name ) {
89
89
  }
90
90
 
91
91
  /**
92
- * Properties that are required next to `=` to make an annotation value an actual expression
93
- * and not some foreign structure.
92
+ * Properties (without those from CSN v0.1.0) that make an annotation object value
93
+ * an actual expression and not some foreign structure.
94
94
  *
95
95
  * @type {string[]}
96
96
  */
97
- const xprInAnnoProperties = [
98
- 'ref', 'xpr', 'list', 'literal', 'val',
99
- '#', 'func', 'args', 'SELECT', 'SET',
100
- 'cast',
97
+ const primaryExprProperties = [
98
+ 'ref', 'xpr', 'list', 'val', '#', 'func', 'SELECT', 'SET',
99
+ ];
100
+ // only with 'ref'/'val'/'func'/misc:
101
+ const exprProperties = [
102
+ ...primaryExprProperties,
103
+ 'param', 'literal', 'args', 'cast',
101
104
  ];
102
-
103
105
 
104
106
  /**
105
107
  * Return whether JSON object `val` is a representation for an annotation expression
106
108
  */
107
109
  function isAnnotationExpression( val ) {
108
- return val?.['='] !== undefined && xprInAnnoProperties.some( prop => val[prop] !== undefined );
110
+ return val?.['='] !== undefined && // TODO: truthy
111
+ primaryExprProperties.some( prop => val[prop] !== undefined );
109
112
  }
110
113
 
111
114
  module.exports = {
112
115
  propagationRules,
113
116
  acceptsExprValues,
114
- xprInAnnoProperties,
117
+ primaryExprProperties,
118
+ exprProperties,
115
119
  isInReservedNamespace,
116
120
  isBuiltinType,
117
121
  isMagicVariable,
@@ -784,14 +784,15 @@ function csnRefs( csn, universalReady ) {
784
784
  const target = assocTarget( parent, refCtx );
785
785
  return resolvePath( path, target.elements[head], target, 'target' );
786
786
  }
787
- if (baseEnv) { // ref-target (filter condition), expand, inline
788
- if (semantics.dynamic !== 'query')
789
- return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
790
- // in an ON condition of an association inside inner expand/inline:
787
+ if (semantics.dynamic === 'query') {
788
+ // in an anno, or an ON condition of an association inside inner expand/inline:
791
789
  const elemParent = getCache( parent, '_element' );
792
- if (elemParent) // expand in expand
790
+ if (elemParent?.elements) // expand in expand
793
791
  return resolvePath( path, elemParent.elements[head], null, 'query' );
794
792
  }
793
+ else if (baseEnv) { // ref-target (filter condition), expand, inline
794
+ return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
795
+ }
795
796
  if (!query) { // outside queries - TODO: items?
796
797
  // refs in annos on foreign keys use fk name, not fk ref name:
797
798
  const dict = parent.elements ?? getCache( parent, '_keys' );
@@ -810,11 +811,9 @@ function csnRefs( csn, universalReady ) {
810
811
  if (!qcache)
811
812
  throw new CompilerAssertion( `For semantics '${ refCtx }', query not in cache at: ${ locationString(query.$location) }` );
812
813
 
813
- if (semantics.dynamic === 'query') {
814
- // TODO: for ON condition in expand, would need to use cached _element
815
- // TODO: test and implement - Issue #11792!
814
+ if (semantics.dynamic === 'query')
816
815
  return resolvePath( path, qcache.elements[head], null, 'query' );
817
- }
816
+
818
817
  for (const name in qcache.$aliases) {
819
818
  const alias = qcache.$aliases[name];
820
819
  const found = alias.elements[head];
@@ -1144,7 +1143,6 @@ function analyseCsnPath( csnPath, csn, resolve ) {
1144
1143
  baseEnv = resolve.expandInline( obj, baseCtx, main, query, parent, baseEnv );
1145
1144
  refCtx = prop;
1146
1145
  }
1147
- // TODO: for on condition in expand, also set an environment
1148
1146
  isName = prop;
1149
1147
  }
1150
1148
  else if (prop === 'on') {
@@ -89,6 +89,7 @@ const centralMessages = {
89
89
  'anno-invalid-sql-kind': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
90
90
  'anno-invalid-sql-view': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
91
91
  'anno-invalid-sql-view-element': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
92
+ 'ext-duplicate-include': { severity: 'Warning', errorFor: [ 'v7' ] },
92
93
  'ext-undefined-action': { severity: 'Warning' },
93
94
  'ext-undefined-art': { severity: 'Warning' }, // for annotate statement (for CDL path root)
94
95
  'ext-undefined-def': { severity: 'Warning' }, // for annotate statement (for CSN or CDL path cont)
@@ -100,6 +101,7 @@ const centralMessages = {
100
101
  'ext-undefined-element-sec': { severity: 'Error', configurableFor: true }, // for security-relevant…
101
102
  'ext-undefined-action-sec': { severity: 'Error', configurableFor: true }, // for security-relevant…
102
103
  'ext-undefined-param-sec': { severity: 'Error', configurableFor: true }, // … annotate statement
104
+ 'ext-unexpected-type-property': { severity: 'Warning', errorFor: [ 'v7' ] },
103
105
  'ext-unexpected-returns': { severity: 'Warning' },
104
106
  'ext-unexpected-returns-sec': { severity: 'Error', configurableFor: true }, // … annotate statement
105
107
  'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'deprecated' },
@@ -125,8 +127,11 @@ const centralMessages = {
125
127
  'empty-type': { severity: 'Info' }, // only still an error in old transformers
126
128
 
127
129
  'ref-deprecated-orderby': { severity: 'Error', configurableFor: true },
128
- 'ref-deprecated-self-element': { severity: 'Error', configurableFor: true },
130
+ 'ref-deprecated-self-element': { severity: 'Error', configurableFor: 'v7' },
129
131
  'ref-deprecated-variable': { severity: 'Warning' },
132
+ 'ref-deprecated-in-extend': { severity: 'Warning' },
133
+ 'ref-special-in-extend': { severity: 'Warning' },
134
+ 'ref-unexpected-in-extend': { severity: 'Error' },
130
135
  'ref-invalid-type': { severity: 'Error' },
131
136
  'ref-unexpected-self': { severity: 'Error' },
132
137
  'ref-invalid-include': { severity: 'Error' },
@@ -204,6 +209,8 @@ const centralMessages = {
204
209
  // remark: a hard syntax error in new parser for `null` together with `not null`
205
210
  'syntax-duplicate-equal-clause': { severity: 'Warning' },
206
211
  'syntax-invalid-name': { severity: 'Error' },
212
+ 'syntax-invalid-anno-struct': { severity: 'Warning', errorFor: [ 'v7' ] },
213
+ 'syntax-invalid-nested-proj': { severity: 'Warning', errorFor: [ 'v7' ] },
207
214
  'syntax-missing-as': { severity: 'Error', configurableFor: true },
208
215
  'syntax-missing-proj-semicolon': { severity: 'Warning' },
209
216
  'syntax-unexpected-after': { severity: 'Error' },
@@ -248,6 +255,7 @@ const centralMessages = {
248
255
  'odata-unexpected-nullable-key': { severity: 'Error', configurableFor: true },
249
256
  'odata-invalid-key-type': { severity: 'Warning' },
250
257
  'odata-invalid-property-name': { severity: 'Warning' },
258
+ 'odata-unexpected-xpr-anno': { severity: 'Error', configurableFor: true },
251
259
  'odata-anno-preproc': { severity: 'Warning' },
252
260
  'odata-anno-dict': { severity: 'Warning' },
253
261
  'odata-anno-vocref': { severity: 'Warning' },
@@ -535,6 +543,10 @@ const centralMessageTexts = {
535
543
  dot: 'Use a $(NEWCODE), not a $(CODE) after the arguments or filter on an entity',
536
544
  colon: 'Use a $(NEWCODE), not a $(CODE) between the element names in a reference',
537
545
  },
546
+ 'syntax-invalid-anno-struct': {
547
+ std: 'Most CSN processors interpret a structure with property $(PROP) as expression',
548
+ ref: 'Most CSN processors interpret a structure with property $(PROP) as reference',
549
+ },
538
550
  // 'syntax-ignoring-doc-comment' (Info)
539
551
  'syntax-unexpected-reserved-word': '$(CODE) is a reserved word - write $(DELIMITED) instead if you want to use it as name',
540
552
  'syntax-invalid-text-block': 'Missing newline in text block',
@@ -710,6 +722,14 @@ const centralMessageTexts = {
710
722
  type: 'Illegal recursive type definition to $(TYPE)',
711
723
  },
712
724
  'ref-deprecated-orderby': 'Replace source element reference $(ID) by $(NEWCODE); auto-corrected',
725
+ 'ref-deprecated-in-extend': {
726
+ std: 'In an extension, do not use the table alias $(ID) to refer to source elements',
727
+ columns: 'In an added column, do not use the table alias $(ID) to refer to source elements',
728
+ where: 'In an added WHERE, do not use the table alias $(ID) to refer to source elements',
729
+ groupBy: 'In an added GROUP BY, do not use the table alias $(ID) to refer to source elements',
730
+ having: 'In an added HAVING, do not use the table alias $(ID) to refer to source elements',
731
+ orderBy: 'In an added ORDER BY, do not use the table alias $(ID) to refer to source elements',
732
+ },
713
733
  'ref-missing-self-counterpart' : {
714
734
  std: 'Expected to find a matching element in $self-comparison for foreign key $(PROP) of association $(NAME)',
715
735
  unmanaged: 'Expected to find a matching element in $self-comparison for $(PROP) of association $(NAME)',
@@ -1080,6 +1100,12 @@ const centralMessageTexts = {
1080
1100
 
1081
1101
  'def-expected-structured': 'Events must either be structured or be projections',
1082
1102
 
1103
+ 'ext-duplicate-include': {
1104
+ std: 'Duplicate $(NAME) through multiple includes $(SORTED_ARTS)',
1105
+ elements: 'Duplicate element $(NAME) through multiple includes $(SORTED_ARTS)',
1106
+ actions: 'Duplicate action or function $(NAME) through multiple includes $(SORTED_ARTS)',
1107
+ },
1108
+
1083
1109
  'duplicate-definition': {
1084
1110
  std: 'Duplicate definition of $(NAME)',
1085
1111
  absolute: 'Duplicate definition of artifact $(NAME)',
@@ -1112,7 +1138,6 @@ const centralMessageTexts = {
1112
1138
  'old-not-target': 'Expected element $(NAME) not to be an association, because it overrides the included element from $(ART)',
1113
1139
  },
1114
1140
 
1115
- 'ref-expecting-$self': 'Use $(NEWCODE) instead of $(CODE) here or remove $(CODE) altogether if possible; the compiler has rewritten it to $(NEWCODE) in CSN',
1116
1141
  'ref-expecting-assoc': {
1117
1142
  std: 'Expecting path $(ELEMREF) following “EXISTS” predicate to end with association/composition',
1118
1143
  'with-type': 'Expecting path $(ELEMREF) following “EXISTS” predicate to end with association/composition, found $(TYPE)',
@@ -1157,6 +1182,32 @@ const centralMessageTexts = {
1157
1182
  join: 'Artifact $(ART) can\'t be extended with columns, because it contains a JOIN',
1158
1183
  union: 'Artifact $(ART) can\'t be extended with columns, because it contains a UNION',
1159
1184
  },
1185
+ 'extend-where': {
1186
+ std: 'Artifact $(ART) can\'t be extended with WHERE, only simple views/projections without JOINs and UNIONs can',
1187
+ join: 'Artifact $(ART) can\'t be extended with WHERE, because it contains a JOIN',
1188
+ union: 'Artifact $(ART) can\'t be extended with WHERE, because it contains a UNION',
1189
+ },
1190
+ 'extend-groupby': {
1191
+ std: 'Artifact $(ART) can\'t be extended with GROUP BY, only simple views/projections without JOINs and UNIONs can',
1192
+ join: 'Artifact $(ART) can\'t be extended with GROUP BY, because it contains a JOIN',
1193
+ union: 'Artifact $(ART) can\'t be extended with GROUP BY, because it contains a UNION',
1194
+ },
1195
+ 'extend-having': {
1196
+ std: 'Artifact $(ART) can\'t be extended with HAVING, only simple views/projections without JOINs and UNIONs can',
1197
+ join: 'Artifact $(ART) can\'t be extended with HAVING, because it contains a JOIN',
1198
+ union: 'Artifact $(ART) can\'t be extended with HAVING, because it contains a UNION',
1199
+ },
1200
+ 'extend-orderby': {
1201
+ std: 'Artifact $(ART) can\'t be extended with ORDER BY, only simple views/projections without JOINs and UNIONs can',
1202
+ join: 'Artifact $(ART) can\'t be extended with ORDER BY, because it contains a JOIN',
1203
+ union: 'Artifact $(ART) can\'t be extended with ORDER BY, because it contains a UNION',
1204
+ },
1205
+ 'extend-limit': {
1206
+ std: 'Artifact $(ART) can\'t be extended with LIMIT, only simple views/projections without JOINs and UNIONs can',
1207
+ join: 'Artifact $(ART) can\'t be extended with LIMIT, because it contains a JOIN',
1208
+ union: 'Artifact $(ART) can\'t be extended with LIMIT, because it contains a UNION',
1209
+ },
1210
+ 'ext-unexpected-sql-clause': 'Artifact $(ART) can\'t be extended with a $(KEYWORD) clause, because it already exists',
1160
1211
  'extend-repeated-intralayer': 'Unstable element order due to repeated extensions in same layer',
1161
1212
  'extend-unexpected-include': 'Can\'t extend $(META) with includes',
1162
1213
 
@@ -1257,11 +1308,17 @@ const centralMessageTexts = {
1257
1308
  },
1258
1309
 
1259
1310
  'ref-special-in-extend': {
1260
- std: 'In an added column, $(ID) refers to the element of the projection source $(ART), not the table alias or mixin',
1311
+ std: 'In an extension, $(ID) refers to the element of the projection source $(ART), not the table alias or mixin',
1261
1312
  alias: 'In an added column, $(ID) refers to the element of the projection source $(ART), not the table alias',
1262
1313
  mixin: 'In an added column, $(ID) refers to the element of the projection source $(ART), not the mixin',
1263
1314
  },
1264
1315
 
1316
+ 'ref-unexpected-in-extend': {
1317
+ std: 'Unexpected $(ID) in an extension; it matches both a source element of $(ART) and the table alias or mixin',
1318
+ alias: 'Unexpected $(ID) in an added $(KEYWORD); it matches both a source element of $(ART) and the table alias',
1319
+ mixin: 'Unexpected $(ID) in an added $(KEYWORD); it matches both a source element of $(ART) and the mixin',
1320
+ },
1321
+
1265
1322
  'type-managed-composition': {
1266
1323
  std: 'Managed compositions can\'t be used in types', // yet
1267
1324
  sub: 'Managed compositions can\'t be used in sub elements',
@@ -1401,6 +1458,7 @@ const centralMessageTexts = {
1401
1458
  xpr: 'Ignoring unexpected expression as default value',
1402
1459
  colitem: 'Ignoring unexpected default value for a structured or collection like parameter',
1403
1460
  },
1461
+ 'odata-unexpected-xpr-anno': 'Annotation $(ANNO) with expression value is not allowed for kind $(KIND)',
1404
1462
  // -----------------------------------------------------------------------------------
1405
1463
  // All odata-anno MUST have a '$(ANNO)' parameter to indicate error location
1406
1464
  // -----------------------------------------------------------------------------------
@@ -854,6 +854,8 @@ function value( val ) {
854
854
  const keywordRepresentations = {
855
855
  association: 'Association',
856
856
  composition: 'Composition',
857
+ groupby: 'group by',
858
+ orderby: 'order by',
857
859
  };
858
860
  function keyword( val ) {
859
861
  const v = val.toLowerCase();
@@ -53,6 +53,7 @@ optionProcessor
53
53
  .option(' --test-sort-csn')
54
54
  .option(' --doc-comment')
55
55
  .option(' --propagate-doc-comments')
56
+ .option(' --v7-key-propagation')
56
57
  .option(' --add-texts-language-assoc')
57
58
  .option(' --localized-without-coalesce')
58
59
  .option(' --tenant-discriminator')
@@ -156,6 +157,7 @@ optionProcessor
156
157
  option is implicitly enabled as well.
157
158
  --doc-comment Preserve /** */ comments at annotation positions as doc property in CSN
158
159
  --propagate-doc-comments Propagate doc comments ('--doc-comment')
160
+ --v7-key-propagation Use simplified propagation of 'key' in query entities
159
161
  --add-texts-language-assoc In generated texts entities, add association "language"
160
162
  to "sap.common.Languages" if it exists
161
163
  --localized-without-coalesce Omit coalesce in localized convenience views
@@ -1,6 +1,6 @@
1
1
  // Definitions for beta and deprecated options
2
2
 
3
- // Normal options are in ../model/optionProcessor.js (and some other files),
3
+ // Normal options are in ../base/optionProcessor.js (and some other files),
4
4
  // unfortunately partly non-grep-able (option `fooBar` is defined via `--foo-bar`)
5
5
 
6
6
  'use strict';
@@ -283,7 +283,7 @@ function assertConsistency( model, stage ) {
283
283
  'quantifier', 'orderBy', 'limit', 'name', '$parens', 'kind',
284
284
  '_origin', '$contains', // TODO tmp, see TODO in getOriginRaw()
285
285
  '_parent', '_main', '_leadingQuery', '_effectiveType', '$effectiveSeqNo', // in FROM
286
- '_$next', // parsing error: tableTerm with UNION on rhs.
286
+ '$expand', '_$next', // parsing error: tableTerm with UNION on rhs.
287
287
  ],
288
288
  },
289
289
  select: { // sub query
@@ -309,7 +309,7 @@ function assertConsistency( model, stage ) {
309
309
  'on', '$parens', 'cardinality',
310
310
  'kind', 'name', '_block', '_parent', '_main', '_user',
311
311
  '$tableAliases', '_combined', '_joinParent', '$joinArgsIndex',
312
- '_leadingQuery', '_$next', '_deps',
312
+ '_leadingQuery', '_$next', '_deps', '$expand',
313
313
  ],
314
314
  },
315
315
  ref: {
@@ -329,7 +329,7 @@ function assertConsistency( model, stage ) {
329
329
  '$parens',
330
330
  'kind', 'name', '_block', '_parent', '_main', 'elements',
331
331
  '_effectiveType', '$effectiveSeqNo', '_origin', '_joinParent', '$joinArgsIndex',
332
- '$duplicates', // duplicate query in FROM clause
332
+ '$expand', '$duplicates', // duplicate query in FROM clause
333
333
  ],
334
334
  },
335
335
  none: { optional: () => true }, // parse error
@@ -352,13 +352,13 @@ function assertConsistency( model, stage ) {
352
352
  requires: [ 'location', 'name' ],
353
353
  optional: [ '$duplicates' ],
354
354
  },
355
- orderBy: { inherits: 'value', test: isArray( expression ) },
355
+ orderBy: { kind: [ 'extend' ], inherits: 'value', test: isArray( expression ) },
356
356
  sort: { test: locationVal( isString ), enum: [ 'asc', 'desc' ] },
357
357
  nulls: { test: locationVal( isString ), enum: [ 'first', 'last' ] },
358
358
  $orderBy: { inherits: 'orderBy' },
359
- groupBy: { inherits: 'value', test: isArray( expression ) },
359
+ groupBy: { kind: [ 'extend' ], inherits: 'value', test: isArray( expression ) },
360
360
  $limit: { test: TODO },
361
- limit: { requires: [ 'rows' ], optional: [ 'offset', 'location' ] },
361
+ limit: { kind: [ 'extend' ], requires: [ 'rows' ], optional: [ 'offset', 'location' ] },
362
362
  rows: { inherits: 'value' },
363
363
  offset: { inherits: 'value' },
364
364
  _combined: { test: TODO },
@@ -438,6 +438,8 @@ function assertConsistency( model, stage ) {
438
438
  'location', '$inferred', 'sort', 'nulls',
439
439
  'param', 'scope', // for dynamic parameter '?'
440
440
  'args', 'op', 'func', 'suffix',
441
+ // needed for groupBy extensions - TODO: outside `value`:
442
+ '$extended', '_block', '_outer',
441
443
  // calculated elements on-write - TODO: outside `value`
442
444
  'stored',
443
445
  ],
@@ -512,8 +514,8 @@ function assertConsistency( model, stage ) {
512
514
  test: args,
513
515
  },
514
516
  on: { kind: true, inherits: 'value', test: expression },
515
- where: { inherits: 'value' },
516
- having: { inherits: 'value' },
517
+ where: { kind: [ 'extend' ], inherits: 'value' },
518
+ having: { kind: [ 'extend' ], inherits: 'value' },
517
519
  op: { test: locationVal( isString ) },
518
520
  join: { test: locationVal( isString ) },
519
521
  quantifier: { test: locationVal( isString ) },
@@ -736,7 +738,7 @@ function assertConsistency( model, stage ) {
736
738
  $expand: {
737
739
  kind: true,
738
740
  // See description of `setExpandStatus()` of in `lib/compiler/utils.js`.
739
- test: isOneOf( [ 'origin', 'annotate', 'target' ] ),
741
+ test: isOneOf( [ 'origin', 'target', 'annotate', 'extend' ] ),
740
742
  },
741
743
  $inCycle: { kind: true, test: isBoolean },
742
744
 
@@ -3,6 +3,7 @@
3
3
  'use strict';
4
4
 
5
5
  const { CompilerAssertion } = require( '../base/error' );
6
+ const $inferred = Symbol.for( 'cds.$inferred' );
6
7
 
7
8
  const dictKinds = {
8
9
  definitions: 'absolute',
@@ -67,7 +68,8 @@ const kindProperties = {
67
68
 
68
69
  function propExists( prop, parent ) {
69
70
  const obj = parent.returns || parent;
70
- return (obj.items || obj.targetAspect || obj)[prop];
71
+ const members = (obj.items || obj.targetAspect || obj)[prop];
72
+ return members && !members[$inferred];
71
73
  }
72
74
 
73
75
  /**
@@ -90,6 +92,8 @@ function getArtifactName( art ) {
90
92
  const namePath = [];
91
93
  let parent = art._outer || art;
92
94
  while (parent._main || parent.kind === 'builtin') { // until we hit the main artifact
95
+ if (!parent.name)
96
+ return '<error>';
93
97
  if (parent.name.$inferred !== '$internal' || parent.kind === '$inline')
94
98
  namePath.push( parent );
95
99
  if (parent.kind === 'select')
@@ -1007,7 +1007,7 @@ function define( model ) {
1007
1007
  }
1008
1008
  else if ((col.expand || col.value) &&
1009
1009
  !path && // no parse error (path without last item)
1010
- (insideExpand || query._parent.kind !== 'select')) { // not sub-selects
1010
+ (insideExpand || query._parent?.kind !== 'select')) { // not sub-selects
1011
1011
  error( 'query-req-name',
1012
1012
  // TODO: message function: `query` should work directly
1013
1013
  [ (col.value || col).location, (query.name ? query : query._parent ) ],
@@ -86,9 +86,8 @@ function dictAddArray( dict, name, entry, messageCallback ) {
86
86
  function dictFirst( dict ) {
87
87
  if (!dict)
88
88
  return dict;
89
- for (const name in dict)
90
- return dict[name];
91
- return undefined;
89
+ const names = Object.keys( dict );
90
+ return names.length ? dict[names[0]] : null;
92
91
  }
93
92
 
94
93
  // Push `entry` to the array value with key `name` in the dictionary `dict`.