@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.
- package/CHANGELOG.md +28 -1
- package/bin/cdsc.js +2 -0
- package/bin/cdsse.js +1 -1
- package/lib/base/message-registry.js +6 -7
- package/lib/base/model.js +0 -72
- package/lib/checks/elements.js +1 -1
- package/lib/checks/featureFlags.js +2 -2
- package/lib/compiler/assert-consistency.js +3 -4
- package/lib/compiler/base.js +8 -0
- package/lib/compiler/builtins.js +8 -9
- package/lib/compiler/checks.js +27 -6
- package/lib/compiler/cycle-detector.js +4 -4
- package/lib/compiler/define.js +65 -83
- package/lib/compiler/extend.js +357 -325
- package/lib/compiler/finalize-parse-cdl.js +3 -4
- package/lib/compiler/generate.js +205 -203
- package/lib/compiler/kick-start.js +34 -49
- package/lib/compiler/populate.js +95 -28
- package/lib/compiler/propagator.js +3 -5
- package/lib/compiler/resolve.js +17 -13
- package/lib/compiler/shared.js +47 -19
- package/lib/compiler/tweak-assocs.js +2 -4
- package/lib/compiler/utils.js +84 -31
- package/lib/gen/BaseParser.js +924 -1055
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +5 -2
- package/lib/json/from-csn.js +25 -16
- package/lib/main.d.ts +13 -0
- package/lib/model/revealInternalProperties.js +18 -0
- package/lib/parsers/AstBuildingParser.js +22 -5
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/utils/sql.js +2 -2
- package/lib/render/utils/standardDatabaseFunctions.js +2 -2
- package/lib/transform/db/constraints.js +3 -4
- package/lib/transform/db/killAnnotations.js +1 -1
- package/lib/transform/db/processSqlServices.js +10 -11
- package/lib/transform/forOdata.js +7 -124
- package/lib/transform/odata/fioriTreeViews.js +173 -0
- package/lib/transform/odata/flattening.js +2 -2
- package/lib/transform/translateAssocsToJoins.js +7 -4
- package/package.json +1 -1
- package/share/messages/message-explanations.json +0 -2
- package/share/messages/type-unexpected-foreign-keys.md +0 -52
- 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
|
|
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 {
|
|
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,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`
|