@sap/cds-compiler 6.6.2 → 6.7.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 +28 -1
  2. package/bin/cdsc.js +2 -0
  3. package/bin/cdsse.js +1 -1
  4. package/lib/base/message-registry.js +6 -7
  5. package/lib/base/model.js +0 -72
  6. package/lib/checks/elements.js +1 -1
  7. package/lib/checks/featureFlags.js +2 -2
  8. package/lib/compiler/assert-consistency.js +3 -4
  9. package/lib/compiler/base.js +8 -0
  10. package/lib/compiler/builtins.js +8 -9
  11. package/lib/compiler/checks.js +27 -6
  12. package/lib/compiler/cycle-detector.js +4 -4
  13. package/lib/compiler/define.js +65 -83
  14. package/lib/compiler/extend.js +357 -325
  15. package/lib/compiler/finalize-parse-cdl.js +3 -4
  16. package/lib/compiler/generate.js +205 -203
  17. package/lib/compiler/kick-start.js +34 -49
  18. package/lib/compiler/populate.js +95 -28
  19. package/lib/compiler/propagator.js +3 -5
  20. package/lib/compiler/resolve.js +17 -13
  21. package/lib/compiler/shared.js +47 -19
  22. package/lib/compiler/tweak-assocs.js +2 -4
  23. package/lib/compiler/utils.js +84 -31
  24. package/lib/gen/BaseParser.js +924 -1055
  25. package/lib/gen/CdlGrammar.checksum +1 -1
  26. package/lib/gen/CdlParser.js +5 -2
  27. package/lib/json/from-csn.js +25 -16
  28. package/lib/main.d.ts +13 -0
  29. package/lib/model/revealInternalProperties.js +18 -0
  30. package/lib/parsers/AstBuildingParser.js +22 -5
  31. package/lib/render/toHdbcds.js +2 -2
  32. package/lib/render/utils/sql.js +2 -2
  33. package/lib/render/utils/standardDatabaseFunctions.js +2 -2
  34. package/lib/transform/db/constraints.js +3 -4
  35. package/lib/transform/db/killAnnotations.js +1 -1
  36. package/lib/transform/db/processSqlServices.js +10 -11
  37. package/lib/transform/forOdata.js +7 -124
  38. package/lib/transform/odata/fioriTreeViews.js +173 -0
  39. package/lib/transform/odata/flattening.js +2 -2
  40. package/lib/transform/translateAssocsToJoins.js +7 -4
  41. package/package.json +1 -1
  42. package/share/messages/message-explanations.json +0 -2
  43. package/share/messages/type-unexpected-foreign-keys.md +0 -52
  44. package/share/messages/type-unexpected-on-condition.md +0 -52
@@ -0,0 +1,173 @@
1
+ 'use strict';
2
+
3
+ const { isBuiltinType } = require('../../base/builtins');
4
+ /**
5
+ * Processing the `@hierarchy` annotation on projections/views and generates:
6
+ * - the OData annotations: `@Aggregation.RecursiveHierarchy`, `@Hierarchy.RecursiveHierarchy`
7
+ * `@Capabilities.FilterRestrictions.NonFilterableProperties` and
8
+ * `@Capabilities.SortRestrictions.NonSortableProperties`
9
+ * - the computed elements: LimitedDescendantCount, DistanceFromRoot, DrillState, LimitedRank
10
+ * Logic is executed only when certain conditions are met:
11
+ * - annotated definition is a projection or view
12
+ * - if the `@hierarchy` annotation has a true value - exactly one managed association to self must exist
13
+ * - if the `@hierarchy` annotation has a reference as value - the reference must point to an existing managed association
14
+ * to self
15
+ * - the managed association has exactly one foreign key
16
+ * - the foreign key is of scalar type
17
+ * Otherwise, appropriate warnings/errors are raised.
18
+ */
19
+ function generateFioriTreeViewAnnotationsAndFields(def, defName, messageFunctions, csnUtils, transformers) {
20
+ const { error, warning } = messageFunctions;
21
+ const { isManagedAssociation, isAssociation } = csnUtils;
22
+ const { setAnnotation, addElement, createScalarElement } = transformers;
23
+
24
+ if (Object.keys(def).some(key => key.startsWith('@hierarchy#')))
25
+ error(null, [ 'definitions', defName ], {}, 'Assigning qualifier with the @hierarchy annotation is not supported');
26
+
27
+ // supported only on projections or views
28
+ if (!(def.projection || def.query)) {
29
+ warning(null, [ 'definitions', defName ], {}, 'Annotation @hierarchy is only supported on projections and views');
30
+ return;
31
+ }
32
+
33
+ // collect all managed and unmanaged associations pointing to self
34
+ const mngAssocsToSelf = [];
35
+ const unmgAssocsToSelf = [];
36
+ Object.entries(def.elements).forEach(([ name, elem ]) => {
37
+ if (isManagedAssociation(elem) && elem.target === defName && ![ 'DraftAdministrativeData' ].includes(name))
38
+ mngAssocsToSelf.push([ name, elem ]);
39
+ else if (isAssociation(elem) && elem.on &&
40
+ elem.target === defName && ![ 'SiblingEntity' ].includes(name))
41
+ unmgAssocsToSelf.push([ name, elem ]);
42
+ });
43
+
44
+ // if the @hierarchy annotation has a true value, we need to make sure the definition has exacly one
45
+ // association pointing to self and has a single foreign key that is of scalar type
46
+ if (typeof def['@hierarchy'] === 'boolean' && def['@hierarchy'] === true) {
47
+ if (mngAssocsToSelf.length > 1) {
48
+ warning(null, [ 'definitions', defName ], {},
49
+ 'Annotation @hierarchy with value true is ignored as multiple managed associations to self exist');
50
+ return;
51
+ }
52
+ else if (mngAssocsToSelf.length === 0) {
53
+ if (unmgAssocsToSelf.length === 1) {
54
+ error(null, unmgAssocsToSelf[0][1].$path || [ 'definitions', defName, 'elements', unmgAssocsToSelf[0][0] ],
55
+ { name: unmgAssocsToSelf[0][0] }, 'Annotation @hierarchy is not supported for unmanaged association $(NAME)');
56
+ }
57
+ else {
58
+ warning(null, [ 'definitions', defName ], {},
59
+ 'Annotation @hierarchy with value true is ignored as no managed association to self exists');
60
+ }
61
+ return;
62
+ }
63
+
64
+ const assoc = mngAssocsToSelf[0][1];
65
+ const assocName = mngAssocsToSelf[0][0];
66
+
67
+ if (isValidHierarchyAssociation(assoc, assocName)) {
68
+ addHierarchyAnnotations(def, defName, assoc.keys[0].ref.join(), assocName);
69
+ addHierarchyFields(def, defName);
70
+ }
71
+ }
72
+ else if (typeof def['@hierarchy'] === 'object' && def['@hierarchy']['=']) {
73
+ const assocName = def['@hierarchy']['='];
74
+ const unmngAssoc = unmgAssocsToSelf.find(a => a[0] === assocName);
75
+ if (unmngAssoc) {
76
+ error(null, unmngAssoc[1].$path || [ 'definitions', defName, 'elements', unmngAssoc[0] ],
77
+ { name: unmngAssoc[0] }, 'Annotation @hierarchy is not supported for unmanaged association $(NAME)');
78
+ return;
79
+ }
80
+
81
+ const assoc = mngAssocsToSelf.find(a => a[0] === assocName);
82
+ if (!assoc) {
83
+ warning(null, [ 'definitions', defName ], { name: assocName },
84
+ 'Annotation @hierarchy refers to a non-existing managed association $(NAME)');
85
+ return;
86
+ }
87
+ if (isValidHierarchyAssociation(assoc[1], assocName)) {
88
+ addHierarchyAnnotations(def, defName, assoc[1].keys[0].ref.join(), assocName);
89
+ addHierarchyFields(def, defName);
90
+ }
91
+ }
92
+
93
+ // returns true if assoc has one key and that one key is scalar, otherwise report a warning and return false
94
+ function isValidHierarchyAssociation(assoc, assocName) {
95
+ // the association must have exactly one scalar foreign key
96
+ if (assoc.keys.length > 1) {
97
+ warning(null, assoc.$path || [ 'definitions', defName, 'elements', assocName ], { name: assocName },
98
+ 'Annotation @hierarchy is ignored as the managed association $(NAME) has multiple foreign keys');
99
+ return false;
100
+ }
101
+
102
+ const fkName = assoc.keys[0].ref.join();
103
+ const defKey = Object.entries(def.elements).find(([ name, elem ]) => elem.key && name === fkName);
104
+ if (defKey && !isBuiltinType(defKey[1].type)) {
105
+ warning(null, assoc.$path || [ 'definitions', defName, 'elements', assocName ], { name: assocName },
106
+ 'Annotation @hierarchy is ignored as the foreign key of the managed association $(NAME) is not of a scalar type');
107
+ return false;
108
+ }
109
+ return true;
110
+ }
111
+
112
+ function addHierarchyAnnotations(def, defName, defKeyName, assocName) {
113
+ const qualifier = `${ defName.split('.').pop() }Hierarchy`;
114
+ const hierarchyProps = [ 'LimitedDescendantCount', 'DistanceFromRoot', 'DrillState', 'LimitedRank' ];
115
+
116
+ [ `@Aggregation.RecursiveHierarchy#${ qualifier }`,
117
+ `@Aggregation.RecursiveHierarchy#${ qualifier }.NodeProperty`,
118
+ `@Aggregation.RecursiveHierarchy#${ qualifier }.ParentNavigationProperty`,
119
+ `@Hierarchy.RecursiveHierarchy#${ qualifier }`,
120
+ `@Hierarchy.RecursiveHierarchy#${ qualifier }.LimitedDescendantCount`,
121
+ `@Hierarchy.RecursiveHierarchy#${ qualifier }.DistanceFromRoot`,
122
+ `@Hierarchy.RecursiveHierarchy#${ qualifier }.DrillState`,
123
+ `@Hierarchy.RecursiveHierarchy#${ qualifier }.LimitedRank` ].forEach(anno => checkAndReportErrorWhenAnnotationExists(def, defName, anno));
124
+
125
+ setAnnotation(def, `@Aggregation.RecursiveHierarchy#${ qualifier }.NodeProperty`, { '=': defKeyName });
126
+ setAnnotation(def, `@Aggregation.RecursiveHierarchy#${ qualifier }.ParentNavigationProperty`, { '=': assocName });
127
+ setAnnotation(def, `@Hierarchy.RecursiveHierarchy#${ qualifier }.LimitedDescendantCount`, { '=': 'LimitedDescendantCount' });
128
+ setAnnotation(def, `@Hierarchy.RecursiveHierarchy#${ qualifier }.DistanceFromRoot`, { '=': 'DistanceFromRoot' });
129
+ setAnnotation(def, `@Hierarchy.RecursiveHierarchy#${ qualifier }.DrillState`, { '=': 'DrillState' });
130
+ setAnnotation(def, `@Hierarchy.RecursiveHierarchy#${ qualifier }.LimitedRank`, { '=': 'LimitedRank' });
131
+
132
+ // if @Capabilities.FilterRestrictions.NonFilterableProperties or @Capabilities.SortRestrictions.NonSortableProperties
133
+ // are already defined, we append to the existing value the created elements
134
+ if (def['@Capabilities.FilterRestrictions.NonFilterableProperties'] && Array.isArray(def['@Capabilities.FilterRestrictions.NonFilterableProperties']))
135
+ def['@Capabilities.FilterRestrictions.NonFilterableProperties'].push(...hierarchyProps);
136
+ else
137
+ setAnnotation(def, '@Capabilities.FilterRestrictions.NonFilterableProperties', [ ...hierarchyProps ]);
138
+
139
+ if (def['@Capabilities.SortRestrictions.NonSortableProperties'] && Array.isArray(def['@Capabilities.SortRestrictions.NonSortableProperties']))
140
+ def['@Capabilities.SortRestrictions.NonSortableProperties'].push(...hierarchyProps);
141
+ else
142
+ setAnnotation(def, '@Capabilities.SortRestrictions.NonSortableProperties', [ ...hierarchyProps ]);
143
+ }
144
+
145
+ function addHierarchyFields(def, defName) {
146
+ const limitedDescendantCount = createScalarElement('LimitedDescendantCount', 'cds.Integer');
147
+ limitedDescendantCount.LimitedDescendantCount['@Core.Computed'] = true;
148
+ limitedDescendantCount.LimitedDescendantCount.$calc = { val: null };
149
+ addElement(limitedDescendantCount, def, defName);
150
+
151
+ const distanceFromRoot = createScalarElement('DistanceFromRoot', 'cds.Integer');
152
+ distanceFromRoot.DistanceFromRoot['@Core.Computed'] = true;
153
+ distanceFromRoot.DistanceFromRoot.$calc = { val: null };
154
+ addElement(distanceFromRoot, def, defName);
155
+
156
+ const drillState = createScalarElement('DrillState', 'cds.String');
157
+ drillState.DrillState['@Core.Computed'] = true;
158
+ drillState.DrillState.$calc = { val: null };
159
+ addElement(drillState, def, defName);
160
+
161
+ const limitedRank = createScalarElement('LimitedRank', 'cds.Integer');
162
+ limitedRank.LimitedRank['@Core.Computed'] = true;
163
+ limitedRank.LimitedRank.$calc = { val: null };
164
+ addElement(limitedRank, def, defName);
165
+ }
166
+
167
+ function checkAndReportErrorWhenAnnotationExists(def, defName, anno) {
168
+ if (def[anno])
169
+ error(null, [ 'definitions', defName ], { anno, name: defName }, 'Annotation $(ANNO) is already defined on the hierarchy entity $(NAME)');
170
+ }
171
+ }
172
+
173
+ module.exports = generateFioriTreeViewAnnotationsAndFields;
@@ -2,12 +2,12 @@
2
2
 
3
3
  const {
4
4
  forEachDefinition,
5
- copyAnnotations, forEachMemberRecursively,
5
+ copyAnnotations, forEachMemberRecursively, forEachGeneric,
6
6
  transformExpression, transformAnnotationExpression,
7
7
  } = require('../../model/csnUtils');
8
8
  const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
9
9
  const transformUtils = require('../transformUtils');
10
- const { setProp, forEachGeneric } = require('../../base/model');
10
+ const { setProp } = require('../../base/model');
11
11
  const {
12
12
  applyTransformationsOnDictionary,
13
13
  applyTransformationsOnNonDictionary,
@@ -1,11 +1,14 @@
1
1
  'use strict';
2
2
 
3
- const {
4
- setProp, forEachGeneric, forEachDefinition, isBetaEnabled,
5
- } = require('../base/model');
3
+ const { setProp, isBetaEnabled } = require('../base/model');
6
4
  const { makeMessageFunction } = require('../base/messages');
7
5
  const { recompileX } = require('../compiler/index');
8
- const { linkToOrigin, pathName } = require('../compiler/utils');
6
+ const {
7
+ linkToOrigin,
8
+ pathName,
9
+ forEachGeneric,
10
+ forEachDefinition,
11
+ } = require('../compiler/utils');
9
12
  const { compactModel, compactExpr } = require('../json/to-csn');
10
13
  const { deduplicateMessages } = require('../base/messages');
11
14
  const { timetrace } = require('../utils/timetrace');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "6.6.2",
3
+ "version": "6.7.1",
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)",
@@ -17,8 +17,6 @@
17
17
  "rewrite-undefined-key",
18
18
  "syntax-expecting-unsigned-int",
19
19
  "type-missing-enum-value",
20
- "type-unexpected-foreign-keys",
21
- "type-unexpected-on-condition",
22
20
  "wildcard-excluding-one"
23
21
  ]
24
22
  }
@@ -1,52 +0,0 @@
1
- # type-unexpected-foreign-keys
2
-
3
- Foreign keys were specified in a composition-of-aspect.
4
-
5
- Compositions of aspects are managed by the compiler.
6
- Specifying a foreign key list is not supported.
7
- If you need to specify foreign keys, use a composition
8
- of an entity instead.
9
-
10
- The message's severity is `Error`.
11
-
12
- ## Example
13
-
14
- Erroneous code example:
15
-
16
- ```cds
17
- aspect Item {
18
- key ID : UUID;
19
- field : String;
20
- };
21
- entity Model {
22
- key ID : UUID;
23
- Item : Composition of Item { ID }; // ❌
24
- };
25
- ```
26
-
27
- `Item` is an aspect. Because an explicit list of foreign keys is specified,
28
- the compiler rejects this CDS snippet. With an explicit foreign key list,
29
- only entities can be used, but not aspects.
30
-
31
- ## How to Fix
32
-
33
- Either remove the explicit list of foreign keys and let the compiler handle
34
- the composition, or use a composition of entity instead.
35
-
36
- ```cds
37
- aspect Item {
38
- key ID : UUID;
39
- field : String;
40
- };
41
- entity Model {
42
- key ID : UUID;
43
- Item : Composition of Model.Item { ID }; // ok
44
- };
45
- entity Model.Item : Item { };
46
- ```
47
-
48
- The snippet uses a user-defined entity, that includes the aspects.
49
-
50
- ## Related Messages
51
-
52
- - `type-unexpected-on-condition`
@@ -1,52 +0,0 @@
1
- # type-unexpected-on-condition
2
-
3
- An ON-condition was specified in a composition-of-aspect.
4
-
5
- Compositions of aspects are managed by the compiler.
6
- Specifying an ON-condition is not supported.
7
- If you need to specify an ON-condition, use a composition
8
- of an entity instead.
9
-
10
- The message's severity is `Error`.
11
-
12
- ## Example
13
-
14
- Erroneous code example:
15
-
16
- ```cds
17
- aspect Item {
18
- key ID : UUID;
19
- field : String;
20
- };
21
- entity Model {
22
- key ID : UUID;
23
- Item : Composition of Item on Item.ID = ID; // ❌
24
- };
25
- ```
26
-
27
- `Item` is an aspect. Because an ON-condition is specified, the compiler
28
- rejects this CDS snippet. With an ON-condition, only entities can be used,
29
- but not aspects.
30
-
31
- ## How to Fix
32
-
33
- Either remove the ON-condition and let the compiler handle
34
- the composition, or use a composition of entity instead.
35
-
36
- ```cds
37
- aspect Item {
38
- key ID : UUID;
39
- field : String;
40
- };
41
- entity Model {
42
- key ID : UUID;
43
- Item : Composition of Model.Item on Item.ID = ID; // ok
44
- };
45
- entity Model.Item : Item { };
46
- ```
47
-
48
- The snippet uses a user-defined entity, that includes the aspects.
49
-
50
- ## Related Messages
51
-
52
- - `type-unexpected-foreign-keys`