@sap/cds-compiler 4.1.2 → 4.2.2
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 +101 -1
- package/bin/cdsc.js +6 -3
- package/doc/CHANGELOG_BETA.md +5 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +2 -2
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +24 -24
- package/lib/base/message-registry.js +41 -6
- package/lib/base/messages.js +7 -0
- package/lib/base/model.js +37 -8
- package/lib/checks/elements.js +11 -10
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +2 -3
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/utils.js +3 -2
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +27 -24
- package/lib/compiler/base.js +6 -2
- package/lib/compiler/builtins.js +34 -34
- package/lib/compiler/checks.js +179 -208
- package/lib/compiler/classes.js +2 -2
- package/lib/compiler/cycle-detector.js +6 -6
- package/lib/compiler/define.js +66 -45
- package/lib/compiler/extend.js +81 -72
- package/lib/compiler/finalize-parse-cdl.js +26 -26
- package/lib/compiler/generate.js +61 -45
- package/lib/compiler/index.js +47 -49
- package/lib/compiler/kick-start.js +8 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +42 -35
- package/lib/compiler/propagator.js +6 -6
- package/lib/compiler/resolve.js +170 -126
- package/lib/compiler/shared.js +122 -45
- package/lib/compiler/tweak-assocs.js +93 -40
- package/lib/compiler/utils.js +15 -12
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +678 -772
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +686 -646
- package/lib/edm/edmUtils.js +277 -296
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1253 -1276
- package/lib/json/from-csn.js +34 -4
- package/lib/json/to-csn.js +4 -4
- package/lib/language/language.g4 +2 -5
- package/lib/main.d.ts +61 -1
- package/lib/model/csnUtils.js +31 -2
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/modelCompare/compare.js +37 -2
- package/lib/modelCompare/utils/filter.js +1 -1
- package/lib/optionProcessor.js +15 -3
- package/lib/render/toCdl.js +30 -4
- package/lib/render/toSql.js +5 -9
- package/lib/render/utils/common.js +8 -6
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +121 -47
- package/lib/transform/db/flattening.js +75 -7
- package/lib/transform/forOdata.js +4 -1
- package/lib/transform/forRelationalDB.js +80 -62
- package/lib/transform/localized.js +91 -54
- package/lib/transform/transformUtils.js +9 -10
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -7,11 +7,92 @@
|
|
|
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
|
+
## Version 4.2.2 - 2023-08-31
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- to.sql|hdi.migration: Fix bug that caused a migration to be rendered for the HANA CDS types that were removed from the CSN
|
|
15
|
+
|
|
16
|
+
## Version 4.2.0 - 2023-08-29
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Compiler:
|
|
21
|
+
+ Option `moduleLookupDirectories: [ 'strings' ]` can be used to specify additional module lookup
|
|
22
|
+
directories, similar to `node_modules/`.
|
|
23
|
+
+ LIMIT and OFFSET clauses can now contain expressions, including parameter references.
|
|
24
|
+
- to.edm(x):
|
|
25
|
+
+ Detect spec violation `scale` > `precision`.
|
|
26
|
+
- to.sql/for.odata:
|
|
27
|
+
+ With the new option `fewerLocalizedViews: true|false`, an entity/view will not get a localized convenience
|
|
28
|
+
view, if it only has associations to localized entities/views. Only if there is actually a localized
|
|
29
|
+
element (e.g. new localized element or reference to one), will it get a convenience view.
|
|
30
|
+
- to.sql
|
|
31
|
+
+ In a localized scenario, create foreign key constraints for the generated `.texts` entities.
|
|
32
|
+
+ Casting `null` to a structure such as `null as field : StructT` is now supported. For each leaf element,
|
|
33
|
+
an additional `null as field_name` column is added.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Compiler:
|
|
38
|
+
+ Selecting fields of structures or associations (without filters) are now candidates for ON-condition
|
|
39
|
+
rewrites: It is no longer necessary to select the struct/association directly.
|
|
40
|
+
+ Consistently handle the case when type elements are defined to be a `key`:
|
|
41
|
+
the `key` property is not only preserved with `includes`, but also in other cases.
|
|
42
|
+
Use option `deprecated.noKeyPropagationWithExpansions` to switch to the v3 behavior.
|
|
43
|
+
+ When including aspects or entities into structured type definitions,
|
|
44
|
+
do not add actions to the type.
|
|
45
|
+
+ An `annotate` statement in the `extensions` section of a CSN now consistently uses the
|
|
46
|
+
`elements` property even if the `annotate` is intended to be used for an enum symbol.
|
|
47
|
+
Before v4.2, the compiler has used the `enum` property in a CSN of flavor `xtended`
|
|
48
|
+
(`gensrc`) if it was certain that it was to be applied to an enum symbol.
|
|
49
|
+
- to.cdl: If a definition has an `actions` property, an `actions {…}` block is now always rendered,
|
|
50
|
+
and is not ignored if empty.
|
|
51
|
+
- to.sql:
|
|
52
|
+
+ For SQL dialect "sqlite", `cds.DateTime` is now rendered as `DATETIME_TEXT` instead of `TIMESTAMP_TEXT`.
|
|
53
|
+
+ Casting a literal (except `null`) to a structure now yields a proper error.
|
|
54
|
+
+ `.texts` entities annotated with `@cds.persistence.skip` (without their original entity having that annotation)
|
|
55
|
+
lead to deployment issues later. It is now an error.
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- Compiler:
|
|
60
|
+
+ Reject invalid reference in the `on` of `join`s already while compiling,
|
|
61
|
+
not just when calling the (SQL) backend.
|
|
62
|
+
+ Correct the calculation of annotation assignments on the return structure of actions
|
|
63
|
+
when both `annotate … with {…}` and `annotate … with returns {…}` had been used
|
|
64
|
+
on the same structure element. Ensure that it works when non-applied, too.
|
|
65
|
+
+ Do not remove or invent `actions` properties with zero actions or functions in it.
|
|
66
|
+
+ Correct auto-redirection of direct cycle associations: if the source and target of a
|
|
67
|
+
model association are the same entity, and the main artifact of the service association
|
|
68
|
+
based on the model association is a suitable auto-redirection target, then use it
|
|
69
|
+
as new target, independently from the value of `@cds.redirection.target`.
|
|
70
|
+
- to.cdl: Indirectly structured types (`type T: Struct;`) with `includes` (`extend T with T2;`), are now properly rendered.
|
|
71
|
+
- to.sql/hdi/hdbcds:
|
|
72
|
+
+ Views on views with parameters did not have localized convenience views based on
|
|
73
|
+
other localized views (missing `localized.` prefix in FROM clause)
|
|
74
|
+
+ Run less checks on entities marked with `@cds.persistence.exists`
|
|
75
|
+
+ Correctly render SELECT items where the column name conflicts with a table alias
|
|
76
|
+
- to.sql: Casting expressions to a structured type yields a proper error instead of strange compiler error.
|
|
77
|
+
- to.edm(x):
|
|
78
|
+
+ Don't expand `@mandatory` if element has an annotation with prefix `@Common.FieldControl.`.
|
|
79
|
+
+ Fix a bug when referencing nested many structures, especially referring to a managed association via
|
|
80
|
+
`$self` comparison.
|
|
81
|
+
+ Improve handling of non-collection annotation values for collection-like vocabulary types.
|
|
82
|
+
+ Don't render `Scale: variable` for `cds.Decimal(scale:0)`.
|
|
83
|
+
- to.sql|hdi.migration:
|
|
84
|
+
+ Fixed a bug that caused rendering of `ALTER` statements to abort early and not render some statements.
|
|
85
|
+
+ CSN output now only contains real `cds` builtins, no early remapping to HANA CDS types or similar.
|
|
86
|
+
- to.sql.migration: Don't drop-create views marked with `@cds.persistence.exists`
|
|
87
|
+
|
|
88
|
+
### Removed
|
|
89
|
+
|
|
10
90
|
## Version 4.1.2 - 2023-07-31
|
|
11
91
|
|
|
12
92
|
### Fixed
|
|
13
93
|
|
|
14
|
-
- to.hdi.migration: Changes in constraints are not rendered as part of the .hdbmigrationtable file,
|
|
94
|
+
- to.hdi.migration: Changes in constraints are not rendered as part of the .hdbmigrationtable file,
|
|
95
|
+
as they belong in other HDI artifacts
|
|
15
96
|
|
|
16
97
|
## Version 4.1.0 - 2023-07-28
|
|
17
98
|
|
|
@@ -196,6 +277,25 @@ The compiler behavior concerning `beta` features can change at any time without
|
|
|
196
277
|
are removed, because since - `Entity.myElement` could also be a definition,
|
|
197
278
|
creating ambiguities. This did not work always, anyway.
|
|
198
279
|
|
|
280
|
+
## Version 3.9.10 - 2023-08-25
|
|
281
|
+
|
|
282
|
+
### Fixed
|
|
283
|
+
|
|
284
|
+
- to.edm(x): Error reporting for incorrect handling of "Collection()" has been improved.
|
|
285
|
+
- to.sql/hdi/hdbcds: Views on views with parameters did not have localized convenience views based on
|
|
286
|
+
other localized views (missing `localized.` prefix in FROM clause)
|
|
287
|
+
- to.sql: Casting expressions to a structured type yields a proper error instead of strange compiler error.
|
|
288
|
+
- to.sql.migration: Don't drop-create views marked with `@cds.persistence.exists` or `@cds.persistence.skip`
|
|
289
|
+
|
|
290
|
+
## Version 3.9.8 - 2023-08-03
|
|
291
|
+
|
|
292
|
+
### Fixed
|
|
293
|
+
|
|
294
|
+
- to.edm(x):
|
|
295
|
+
+ Don't expand `@mandatory` if element has an annotation with prefix `@Common.FieldControl.`.
|
|
296
|
+
+ Fix a bug when referencing nested many structures, especially referring to a managed association via
|
|
297
|
+
`$self` comparison.
|
|
298
|
+
- to.sql/hdi/hdbcds: Detect navigation into arrayed structures and raise helpful errors instead of running into internal errors.
|
|
199
299
|
|
|
200
300
|
## Version 3.9.6 - 2023-07-27
|
|
201
301
|
|
package/bin/cdsc.js
CHANGED
|
@@ -162,7 +162,7 @@ function cdsc_main() {
|
|
|
162
162
|
validateDirectBackendOption(cmdLine.command, cmdLine.options, cmdLine.args);
|
|
163
163
|
|
|
164
164
|
// If set through CLI (and not options file), `beta` is a string and needs processing.
|
|
165
|
-
if (
|
|
165
|
+
if (typeof cmdLine.options.beta === 'string') {
|
|
166
166
|
const features = cmdLine.options.beta.split(',');
|
|
167
167
|
cmdLine.options.beta = {};
|
|
168
168
|
features.forEach((val) => {
|
|
@@ -191,14 +191,17 @@ function cdsc_main() {
|
|
|
191
191
|
console.error( `Running ‘${cmdLine.command}’ with test-mode shuffle ${cmdLine.options.testMode} …` );
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
// If set through CLI (and not options file), `deprecated`
|
|
195
|
-
|
|
194
|
+
// If set through CLI (and not options file), `deprecated` and `moduleLookupDirectories`
|
|
195
|
+
// are strings and needs processing.
|
|
196
|
+
if (typeof cmdLine.options.deprecated === 'string') {
|
|
196
197
|
const features = cmdLine.options.deprecated.split(',');
|
|
197
198
|
cmdLine.options.deprecated = {};
|
|
198
199
|
features.forEach((val) => {
|
|
199
200
|
cmdLine.options.deprecated[val] = true;
|
|
200
201
|
});
|
|
201
202
|
}
|
|
203
|
+
if (typeof cmdLine.options.moduleLookupDirectories === 'string')
|
|
204
|
+
cmdLine.options.moduleLookupDirectories = cmdLine.options.moduleLookupDirectories.split(',');
|
|
202
205
|
|
|
203
206
|
parseSeverityOptions(cmdLine);
|
|
204
207
|
|
package/doc/CHANGELOG_BETA.md
CHANGED
|
@@ -73,6 +73,11 @@ Now enabled per default.
|
|
|
73
73
|
|
|
74
74
|
## Version 3.7.0 - 2023-02-22
|
|
75
75
|
|
|
76
|
+
### Added `annotationExpressions`
|
|
77
|
+
|
|
78
|
+
This option allows to use expressions as annotation values, e.g.
|
|
79
|
+
`@anno: (1+2)`.
|
|
80
|
+
|
|
76
81
|
### Added `calculatedElements`
|
|
77
82
|
|
|
78
83
|
Allows to define calculated elements in entities and aspects. When used in views, they
|
|
@@ -11,6 +11,21 @@ Note: `deprecated` features are listed in this ChangeLog just for information.
|
|
|
11
11
|
**When the `deprecated` option is set, the `beta` option is ignored,
|
|
12
12
|
and several new features are not available.**
|
|
13
13
|
|
|
14
|
+
## Version 4.2.0 - 2023-08-29
|
|
15
|
+
|
|
16
|
+
### Added `noKeyPropagationWithExpansions`
|
|
17
|
+
|
|
18
|
+
When this option is set, element `id` in types `Orig` and `I` are keys,
|
|
19
|
+
but `id` in `D` is not.
|
|
20
|
+
|
|
21
|
+
```cds
|
|
22
|
+
type Orig { key id: Integer };
|
|
23
|
+
type I: Orig {};
|
|
24
|
+
type D: Orig;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
When this option is not set, element `id` in all three types are keys.
|
|
28
|
+
|
|
14
29
|
## Version 4.0.0 - 2023-06-06
|
|
15
30
|
|
|
16
31
|
### Added `downgradableErrors`
|
package/lib/api/main.js
CHANGED
|
@@ -400,8 +400,7 @@ function sqlMigration( csn, options, beforeImage ) {
|
|
|
400
400
|
const beforeArtifact = beforeImage.definitions[artifactName];
|
|
401
401
|
const diffArtifact = diff.definitions[artifactName];
|
|
402
402
|
// TODO: exists, abstract? isPersistedOnDb?
|
|
403
|
-
if (diffArtifact && diffArtifact['@cds.persistence.name'] &&
|
|
404
|
-
(diffArtifact.query || diffArtifact.projection) &&
|
|
403
|
+
if (diffArtifact && diffArtifact['@cds.persistence.name'] && csnUtils.isPersistedAsView(diffArtifact) &&
|
|
405
404
|
(diffArtifact[modelCompare.isChanged] === true || // we know it changed because we compared two views
|
|
406
405
|
diffArtifact[modelCompare.isChanged] === undefined)) { // if it was removed in the after, then we don't have the flag
|
|
407
406
|
drops.creates[artifactName] = `DROP VIEW ${ identifierUtils.renderArtifactName(artifactName) };`;
|
|
@@ -409,6 +408,7 @@ function sqlMigration( csn, options, beforeImage ) {
|
|
|
409
408
|
else if (diffArtifact &&
|
|
410
409
|
diffArtifact['@cds.persistence.skip'] !== true &&
|
|
411
410
|
diffArtifact.kind === beforeArtifact.kind && // detect action -> entity
|
|
411
|
+
csnUtils.isPersistedAsTable(diffArtifact) === csnUtils.isPersistedAsTable(beforeArtifact) && // detect removal of @cds.persistence.exists
|
|
412
412
|
csnUtils.isPersistedAsView(diffArtifact) === csnUtils.isPersistedAsView(beforeArtifact) // detect view -> entity
|
|
413
413
|
) { // don't render again, but need info for primary key extension
|
|
414
414
|
diffArtifact['@cds.persistence.skip'] = true;
|
package/lib/api/options.js
CHANGED
|
@@ -30,6 +30,7 @@ const publicOptionsNewAPI = [
|
|
|
30
30
|
'pre2134ReferentialConstraintNames',
|
|
31
31
|
'generatedByComment',
|
|
32
32
|
'betterSqliteSessionVariables',
|
|
33
|
+
'fewerLocalizedViews',
|
|
33
34
|
// ODATA
|
|
34
35
|
'odataOpenapiHints',
|
|
35
36
|
'odataVersion',
|
|
@@ -52,7 +53,6 @@ const privateOptions = [
|
|
|
52
53
|
// Not callable via cdsc, keep private for now until we are sure that we want this
|
|
53
54
|
'filterCsn',
|
|
54
55
|
'lintMode',
|
|
55
|
-
'fuzzyCsnError',
|
|
56
56
|
'traceFs',
|
|
57
57
|
'traceParser',
|
|
58
58
|
'traceParserAmb',
|
|
@@ -66,7 +66,7 @@ const privateOptions = [
|
|
|
66
66
|
'noRecompile',
|
|
67
67
|
'internalMsg',
|
|
68
68
|
'disableHanaComments', // in case of issues with hana comment rendering
|
|
69
|
-
'localizedWithoutCoalesce', // deprecated version of 'localizedLanguageFallback', TODO(
|
|
69
|
+
'localizedWithoutCoalesce', // deprecated version of 'localizedLanguageFallback', TODO(v5): Remove option
|
|
70
70
|
];
|
|
71
71
|
|
|
72
72
|
const overallOptions = publicOptionsNewAPI.concat(privateOptions);
|
package/lib/api/validate.js
CHANGED
|
@@ -130,26 +130,28 @@ const validators = {
|
|
|
130
130
|
assertIntegrityType: generateStringValidator([ 'DB', 'RT' ]),
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
+
// Note: if `validate()` returns true, it means the option is _invalid_!
|
|
133
134
|
const allCombinationValidators = {
|
|
134
|
-
'valid-structured': {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
},
|
|
146
|
-
'beta-no-test': {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
getParameters: () => {},
|
|
150
|
-
getMessage: () => 'Option "beta" was used. This option should not be used in productive scenarios!',
|
|
135
|
+
'valid-structured': (options, message) => {
|
|
136
|
+
if (options.odataVersion === 'v2' && options.odataFormat === 'structured')
|
|
137
|
+
message.error('api-invalid-combination', null, { '#': 'valid-structured' });
|
|
138
|
+
},
|
|
139
|
+
'sql-dialect-and-naming': (options, message) => {
|
|
140
|
+
if (options.sqlDialect && options.sqlMapping && options.sqlDialect !== 'hana' && [ 'quoted', 'hdbcds' ].includes(options.sqlMapping))
|
|
141
|
+
message.error('api-invalid-combination', null, { '#': 'sql-dialect-and-naming', name: options.sqlDialect, prop: options.sqlMapping });
|
|
142
|
+
},
|
|
143
|
+
'sql-dialect-and-localized': (options, message) => {
|
|
144
|
+
if (options.fewerLocalizedViews && options.sqlDialect === 'hana')
|
|
145
|
+
message.error('api-invalid-combination', null, { '#': 'sql-dialect-and-localized', option: 'fewerLocalizedViews', value: 'hana' });
|
|
146
|
+
},
|
|
147
|
+
'beta-no-test': (options, message) => {
|
|
148
|
+
if (options.beta && !options.testMode)
|
|
149
|
+
message.warning('api-unexpected-combination', null, { '#': 'beta-no-test', option: 'beta' });
|
|
151
150
|
},
|
|
152
151
|
};
|
|
152
|
+
|
|
153
|
+
const alwaysRunValidators = [ 'beta-no-test', 'sql-dialect-and-localized' ];
|
|
154
|
+
|
|
153
155
|
/* eslint-disable jsdoc/no-undefined-types */
|
|
154
156
|
/**
|
|
155
157
|
* Run the validations for each option.
|
|
@@ -172,11 +174,12 @@ function validate( options, moduleName, customValidators = {}, combinationValida
|
|
|
172
174
|
const validator = customValidators[optionName] || validators[optionName] || booleanValidator;
|
|
173
175
|
|
|
174
176
|
if (!validator.validate(optionValue)) {
|
|
175
|
-
error('invalid-option', null, {
|
|
177
|
+
error('api-invalid-option', null, {
|
|
178
|
+
'#': 'value',
|
|
176
179
|
prop: optionName,
|
|
177
180
|
value: validator.expected(optionValue),
|
|
178
181
|
othervalue: validator.found(optionValue),
|
|
179
|
-
}
|
|
182
|
+
});
|
|
180
183
|
}
|
|
181
184
|
});
|
|
182
185
|
throwWithAnyError();
|
|
@@ -184,11 +187,8 @@ function validate( options, moduleName, customValidators = {}, combinationValida
|
|
|
184
187
|
|
|
185
188
|
const message = makeMessageFunction(null, options, moduleName);
|
|
186
189
|
|
|
187
|
-
for (const combinationValidatorName of combinationValidators.concat(
|
|
188
|
-
|
|
189
|
-
if (combinationValidator.validate(options))
|
|
190
|
-
message[combinationValidator.severity]('invalid-option-combination', null, combinationValidator.getParameters(options), combinationValidator.getMessage(options));
|
|
191
|
-
}
|
|
190
|
+
for (const combinationValidatorName of combinationValidators.concat(alwaysRunValidators))
|
|
191
|
+
allCombinationValidators[combinationValidatorName](options, message);
|
|
192
192
|
|
|
193
193
|
message.throwWithAnyError();
|
|
194
194
|
}
|
|
@@ -63,6 +63,7 @@ const centralMessages = {
|
|
|
63
63
|
'anno-undefined-element': { severity: 'Warning' },
|
|
64
64
|
'anno-undefined-param': { severity: 'Warning' },
|
|
65
65
|
'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'deprecated' },
|
|
66
|
+
'anno-unexpected-localized-skip': { severity: 'Error', configurableFor: true },
|
|
66
67
|
|
|
67
68
|
'name-invalid-dollar-alias': { severity: 'Error', configurableFor: true },
|
|
68
69
|
|
|
@@ -114,6 +115,7 @@ const centralMessages = {
|
|
|
114
115
|
'type-ambiguous-target': { severity: 'Warning' },
|
|
115
116
|
|
|
116
117
|
'ref-unexpected-autoexposed': { severity: 'Error' },
|
|
118
|
+
'ref-unexpected-many-navigation': { severity: 'Error' },
|
|
117
119
|
// Published! Used in @sap/cds-lsp; if renamed, add to oldMessageIds and contact colleagues
|
|
118
120
|
'ref-undefined-art': { severity: 'Error' },
|
|
119
121
|
'ref-undefined-def': { severity: 'Error' },
|
|
@@ -230,6 +232,8 @@ const centralMessageTexts = {
|
|
|
230
232
|
std: 'Invalid option $(NAME)!',
|
|
231
233
|
deprecated: 'Option $(NAME) is no longer supported! Use latest API options instead',
|
|
232
234
|
magicVars: 'Option $(PROP) is no longer supported! Use $(OTHERPROP) instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
|
|
235
|
+
value: 'Expected option $(PROP) to have $(VALUE). Found: $(OTHERVALUE)',
|
|
236
|
+
type: 'Expected option $(OPTION) to be of type $(VALUE). Found: $(OTHERVALUE)',
|
|
233
237
|
},
|
|
234
238
|
|
|
235
239
|
'api-invalid-variable-replacement': {
|
|
@@ -239,6 +243,22 @@ const centralMessageTexts = {
|
|
|
239
243
|
'noDollar': 'Option $(OPTION) does not know $(NAME). Did you forget a leading $(CODE)?'
|
|
240
244
|
},
|
|
241
245
|
|
|
246
|
+
'api-invalid-combination': {
|
|
247
|
+
std: 'Invalid option combination found: $(OPTION) and $(PROP)', // unused
|
|
248
|
+
'valid-structured': 'Structured OData is only supported with OData version v4',
|
|
249
|
+
'sql-dialect-and-naming': 'sqlDialect $(NAME) can\'t be combined with sqlMapping $(PROP)',
|
|
250
|
+
'sql-dialect-and-localized': 'Option $(OPTION) can\'t be combined with SQL dialect $(VALUE) or the to.hdi()/to.hdbcds() backend',
|
|
251
|
+
},
|
|
252
|
+
'api-unexpected-combination': {
|
|
253
|
+
std: 'Unexpected option combination: $(OPTION) and $(PROP)', // unused
|
|
254
|
+
'beta-no-test':'Option $(OPTION) was used. This option should not be used in productive scenarios!',
|
|
255
|
+
},
|
|
256
|
+
'api-invalid-lookup-dir': {
|
|
257
|
+
std: '',
|
|
258
|
+
slash: 'Expected directory $(VALUE) in option $(OPTION) to end with $(OTHERVALUE)',
|
|
259
|
+
relative: 'Expected directory $(VALUE) in option $(OPTION) to not start with $(OTHERVALUE)',
|
|
260
|
+
},
|
|
261
|
+
|
|
242
262
|
'anno-duplicate': {
|
|
243
263
|
std: 'Duplicate assignment with $(ANNO)',
|
|
244
264
|
doc: 'Duplicate assignment with a doc comment',
|
|
@@ -546,6 +566,9 @@ const centralMessageTexts = {
|
|
|
546
566
|
std: '$(ART) can\'t be extended because it originates from an include',
|
|
547
567
|
elements: '$(ART) can\'t be extended by elements/enums because it originates from an include',
|
|
548
568
|
},
|
|
569
|
+
'ref-unexpected-many-navigation': {
|
|
570
|
+
std: 'Unexpected navigation into arrayed structure',
|
|
571
|
+
},
|
|
549
572
|
'ref-unexpected-scope': {
|
|
550
573
|
std: 'Unexpected parameter reference',
|
|
551
574
|
calc: 'Calculated elements can\'t use parameter references',
|
|
@@ -737,6 +760,12 @@ const centralMessageTexts = {
|
|
|
737
760
|
elements: 'Duplicate element $(NAME) through multiple includes $(SORTED_ARTS)',
|
|
738
761
|
actions: 'Duplicate action or function $(NAME) through multiple includes $(SORTED_ARTS)',
|
|
739
762
|
},
|
|
763
|
+
'ref-invalid-element': {
|
|
764
|
+
std: 'Invalid element reference',
|
|
765
|
+
$tableAlias: 'Can\'t refer to source elements of table alias $(ID)',
|
|
766
|
+
mixin: 'Can\'t refer to the query\'s own mixin $(ID)',
|
|
767
|
+
$self: 'Can\'t refer to the query\'s own elements',
|
|
768
|
+
},
|
|
740
769
|
'ref-invalid-override': {
|
|
741
770
|
std: 'Overridden element of include must not change element structure', // unused
|
|
742
771
|
'new-not-structured': 'Expected element $(NAME) to be structured, because it overrides the included element from $(ART)',
|
|
@@ -848,14 +877,17 @@ const centralMessageTexts = {
|
|
|
848
877
|
changed: 'Changed element $(ID) is a primary key change and will not work with dialect $(NAME)'
|
|
849
878
|
},
|
|
850
879
|
|
|
880
|
+
'type-invalid-cast': {
|
|
881
|
+
std: 'Invalid cast to $(TYPE)', // unused
|
|
882
|
+
'to-structure': 'Can\'t cast to a structured type',
|
|
883
|
+
'from-structure': 'Structured elements can\'t be cast to a different type',
|
|
884
|
+
'expr-to-structure': 'Can\'t cast an expression to a structured type',
|
|
885
|
+
'val-to-structure': 'Can\'t cast $(VALUE) to a structured type'
|
|
886
|
+
},
|
|
887
|
+
|
|
851
888
|
// -----------------------------------------------------------------------------------
|
|
852
889
|
// Expressions
|
|
853
890
|
// -----------------------------------------------------------------------------------
|
|
854
|
-
'expr-invalid-operator': 'Comparing $(ID) is only allowed with $(OP)',
|
|
855
|
-
'expr-missing-comparison': {
|
|
856
|
-
std: 'Expected a comparison with $(OP) when using $(ID) in an ON-condition',
|
|
857
|
-
},
|
|
858
|
-
|
|
859
891
|
'type-invalid-cardinality': {
|
|
860
892
|
std: 'Invalid value $(VALUE) for cardinality', // unused variant
|
|
861
893
|
sourceMax: 'Invalid value $(PROP) for maximum source cardinality, expecting a positive number or $(OTHERPROP)',
|
|
@@ -868,6 +900,8 @@ const centralMessageTexts = {
|
|
|
868
900
|
|
|
869
901
|
'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers',
|
|
870
902
|
|
|
903
|
+
'expr-missing-foreign-key': 'Path step $(ID) of $(ELEMREF) has no valid foreign keys',
|
|
904
|
+
|
|
871
905
|
// OData version dependent messages
|
|
872
906
|
'odata-spec-violation-array': 'Unexpected array type for OData $(VERSION)',
|
|
873
907
|
'odata-spec-violation-param' : 'Expected parameter to be typed with either scalar or structured type for OData $(VERSION)',
|
|
@@ -900,6 +934,7 @@ const centralMessageTexts = {
|
|
|
900
934
|
incompatible: 'Unexpected EDM Type $(TYPE) for OData $(VERSION)',
|
|
901
935
|
facet: 'Unexpected EDM Type facet $(NAME) of type $(TYPE) for OData $(VERSION)',
|
|
902
936
|
external: 'Referenced type $(TYPE) marked as $(ANNO) can\'t be rendered as $(CODE) in service $(NAME) for OData $(VERSION)',
|
|
937
|
+
scale: 'Expected scale $(NUMBER) to be less than or equal to precision $(RAWVALUE)'
|
|
903
938
|
},
|
|
904
939
|
'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(META)',
|
|
905
940
|
'odata-spec-violation-namespace': {
|
|
@@ -965,7 +1000,7 @@ const centralMessageTexts = {
|
|
|
965
1000
|
// -----------------------------------------------------------------------------------
|
|
966
1001
|
'enum': 'Value $(VALUE) is not one out of $(RAWVALUES) for $(ANNO) of type $(TYPE)',
|
|
967
1002
|
'std': 'Unexpected value $(VALUE) for $(ANNO) of type $(TYPE)',
|
|
968
|
-
'
|
|
1003
|
+
'incompval': 'Unexpected $(STR) value for $(ANNO) of type $(TYPE)',
|
|
969
1004
|
'nestedcollection': 'Nested collections are not supported for $(ANNO)',
|
|
970
1005
|
'enumincollection': 'Enum inside collection is not supported for $(ANNO)',
|
|
971
1006
|
'multexpr': 'EDM JSON code contains more than one dynamic expression: $(RAWVALUES) for $(ANNO)',
|
package/lib/base/messages.js
CHANGED
|
@@ -1300,6 +1300,13 @@ function deduplicateMessages( messages ) {
|
|
|
1300
1300
|
|
|
1301
1301
|
function shortArtName( art ) {
|
|
1302
1302
|
const name = getArtifactName( art );
|
|
1303
|
+
if (!name) {
|
|
1304
|
+
const loc = art.location ? ` at ${ locationString( art.location ) }` : '';
|
|
1305
|
+
throw new CompilerAssertion(
|
|
1306
|
+
art.path
|
|
1307
|
+
? `No artifact for ${ art.path.map( i => i.id ).join( '.' )}${ loc }`
|
|
1308
|
+
: `No name found in ${ Object.keys( art ).join( '+' )}${ loc }` );
|
|
1309
|
+
}
|
|
1303
1310
|
if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
|
|
1304
1311
|
!name.absolute.includes(':'))
|
|
1305
1312
|
return quote.double( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );
|
package/lib/base/model.js
CHANGED
|
@@ -34,6 +34,7 @@ const availableBetaFlags = {
|
|
|
34
34
|
odataTerms: true,
|
|
35
35
|
optionalActionFunctionParameters: true, // not supported by runtime, yet.
|
|
36
36
|
associationDefault: true,
|
|
37
|
+
annotateForeignKeys: true,
|
|
37
38
|
// disabled by --beta-mode
|
|
38
39
|
nestedServices: false,
|
|
39
40
|
};
|
|
@@ -44,6 +45,7 @@ const availableDeprecatedFlags = {
|
|
|
44
45
|
downgradableErrors: true,
|
|
45
46
|
includesNonShadowedFirst: true,
|
|
46
47
|
eagerPersistenceForGeneratedEntities: true,
|
|
48
|
+
noKeyPropagationWithExpansions: true,
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
const oldDeprecatedFlags_v2 = [
|
|
@@ -130,18 +132,26 @@ function checkRemovedDeprecatedFlags( options, { error } ) {
|
|
|
130
132
|
});
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Apply function `callback` to all artifacts in dictionary
|
|
137
|
+
* `model.definitions`. See function `forEachGeneric` for details.
|
|
138
|
+
* TODO: should we skip "namespaces" already here?
|
|
139
|
+
*/
|
|
136
140
|
function forEachDefinition( model, callback ) {
|
|
137
141
|
forEachGeneric( model, 'definitions', callback );
|
|
138
142
|
}
|
|
139
143
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Apply function `callback` to all members of object `obj` (main artifact or
|
|
146
|
+
* parent member). Members are considered those in dictionaries `elements`,
|
|
147
|
+
* `enum`, `actions` and `params` of `obj`, `elements` and `enums` are also
|
|
148
|
+
* searched inside property `items` (array of). See function `forEachGeneric`
|
|
149
|
+
* for details.
|
|
150
|
+
*
|
|
151
|
+
* @param {XSN.Artifact} construct
|
|
152
|
+
* @param {(member: XSN.Artifact, memberName: string, prop: string) => void} callback
|
|
153
|
+
* @param {XSN.Artifact} [target]
|
|
154
|
+
*/
|
|
145
155
|
function forEachMember( construct, callback, target ) {
|
|
146
156
|
let obj = construct;
|
|
147
157
|
while (obj.items)
|
|
@@ -155,6 +165,24 @@ function forEachMember( construct, callback, target ) {
|
|
|
155
165
|
callback( construct.returns, '', 'params' );
|
|
156
166
|
}
|
|
157
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Same as forEachMember, but inside each member, calls itself recursively, i.e.
|
|
170
|
+
* sub members are traversed as well.
|
|
171
|
+
*
|
|
172
|
+
* @param {XSN.Artifact} construct
|
|
173
|
+
* @param {(member: XSN.Artifact, memberName: string, prop: string) => void} callback
|
|
174
|
+
* @param {XSN.Artifact} [target]
|
|
175
|
+
*/
|
|
176
|
+
function forEachMemberRecursively( construct, callback, target ) {
|
|
177
|
+
forEachMember( construct, ( member, memberName, prop ) => {
|
|
178
|
+
callback( member, memberName, prop );
|
|
179
|
+
if (Array.isArray(member.$duplicates)) // redefinitions
|
|
180
|
+
member.$duplicates.forEach( o => callback( o, memberName, prop ) );
|
|
181
|
+
// Descend into nested members, too
|
|
182
|
+
forEachMemberRecursively( member, callback );
|
|
183
|
+
}, target);
|
|
184
|
+
}
|
|
185
|
+
|
|
158
186
|
// Apply function `callback` to all objects in dictionary `dict`, including all
|
|
159
187
|
// duplicates (found under the same name). Function `callback` is called with
|
|
160
188
|
// the following arguments: the object, the name, and -if it is a duplicate-
|
|
@@ -197,6 +225,7 @@ module.exports = {
|
|
|
197
225
|
queryOps,
|
|
198
226
|
forEachDefinition,
|
|
199
227
|
forEachMember,
|
|
228
|
+
forEachMemberRecursively,
|
|
200
229
|
forEachGeneric,
|
|
201
230
|
forEachInOrder,
|
|
202
231
|
setProp,
|
package/lib/checks/elements.js
CHANGED
|
@@ -119,18 +119,19 @@ function checkManagedAssoc( art ) {
|
|
|
119
119
|
forEachMemberRecursively(art, (member) => {
|
|
120
120
|
if (this.csnUtils.isAssocOrComposition(member) &&
|
|
121
121
|
!isManagedComposition.bind(this)(member)) {
|
|
122
|
+
// Implementation note: Imported services (i.e. external ones) may contain to-many associations
|
|
123
|
+
// with an empty foreign key list. If the user (in this case importer) explicitly sets an empty
|
|
124
|
+
// foreign key array, we won't emit a warning to avoid spamming the user.
|
|
122
125
|
const max = member.cardinality?.max ? member.cardinality.max : 1;
|
|
123
|
-
if (max !== 1 && !member.on) {
|
|
126
|
+
if (max !== 1 && !member.on && (!member.keys || member.keys.length > 0)) {
|
|
124
127
|
const isNoDb = art['@cds.persistence.skip'] || art['@cds.persistence.exists'];
|
|
125
|
-
this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality ? member.cardinality.$path : member.$path,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
|
|
133
|
-
});
|
|
128
|
+
this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality ? member.cardinality.$path : member.$path, {
|
|
129
|
+
value: cardinality2str(member, false),
|
|
130
|
+
'#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
|
|
131
|
+
}, {
|
|
132
|
+
std: 'Expected association with target cardinality $(VALUE) to have an ON-condition',
|
|
133
|
+
comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
|
|
134
|
+
});
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { applyTransformationsOnNonDictionary } = require('../model/csnUtils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check all refs in the given parent for the traversal of paths
|
|
7
|
+
* into `.items`
|
|
8
|
+
*
|
|
9
|
+
* @param {object} parent Object with the expression as a property
|
|
10
|
+
* @param {string} propOnParent Name of the expression property on parent
|
|
11
|
+
* @param {Array} e Expression to check - see module.exports
|
|
12
|
+
* @param {CSN.Path} path
|
|
13
|
+
*/
|
|
14
|
+
function navigationIntoMany( parent, propOnParent, e, path ) {
|
|
15
|
+
applyTransformationsOnNonDictionary(parent, propOnParent, {
|
|
16
|
+
ref: (_parent, _prop, ref, _path) => {
|
|
17
|
+
const itemNavigationIndex = _parent._links?.findIndex(l => l.art.items);
|
|
18
|
+
if (itemNavigationIndex !== -1 && _parent.ref.length > itemNavigationIndex + 1)
|
|
19
|
+
this.message('ref-unexpected-many-navigation', _path);
|
|
20
|
+
},
|
|
21
|
+
}, { skipStandard: { type: true } }, path);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
columns: navigationIntoMany,
|
|
26
|
+
from: navigationIntoMany,
|
|
27
|
+
on: navigationIntoMany,
|
|
28
|
+
having: navigationIntoMany,
|
|
29
|
+
groupBy: navigationIntoMany,
|
|
30
|
+
orderBy: navigationIntoMany,
|
|
31
|
+
where: navigationIntoMany,
|
|
32
|
+
xpr: navigationIntoMany,
|
|
33
|
+
};
|
|
@@ -182,10 +182,13 @@ function requireForeignKeyAccess( parent, refIndex, noForeignKeyCallback ) {
|
|
|
182
182
|
}
|
|
183
183
|
else {
|
|
184
184
|
// For cases where `Association to T { struct.one, struct.two };` is used instead of `{ struct }`.
|
|
185
|
-
//
|
|
185
|
+
// We know that `{ struct, struct.one }` is not possible, so no prefix check required.
|
|
186
|
+
// If `ref.length` does not cover any full foreign key, then we call noForeignKeyCallback()
|
|
187
|
+
// as well. This could happen for `struct.one` as foreign key, and `assoc.struct = …`
|
|
188
|
+
// in ON-condition.
|
|
186
189
|
let fkIndex = 0;
|
|
187
190
|
let success = false;
|
|
188
|
-
while (!success && possibleKeys.length > 0) {
|
|
191
|
+
while (!success && possibleKeys.length > 0 && refIndex + fkIndex + 1 < ref.length) {
|
|
189
192
|
const pathStep = ref[refIndex + fkIndex + 1].id || ref[refIndex + fkIndex + 1];
|
|
190
193
|
|
|
191
194
|
// Function is immediately executed, before next iteration of loop. Access is fine.
|
|
@@ -76,7 +76,7 @@ function _hasForeignKeyOrElements( def ) {
|
|
|
76
76
|
* @param {boolean} inColumns True if the ref is part of a from
|
|
77
77
|
*/
|
|
78
78
|
function checkQueryRef( obj, inColumns ) {
|
|
79
|
-
if (!obj
|
|
79
|
+
if (!obj)
|
|
80
80
|
return;
|
|
81
81
|
|
|
82
82
|
if (obj.expand || obj.inline)
|
|
@@ -179,8 +179,7 @@ function _checkRef( ref, _links, $path, inColumns ) {
|
|
|
179
179
|
|
|
180
180
|
// check managed association to have foreign keys array filled
|
|
181
181
|
if (art.keys && !hasForeignKeys.call(this, art)) {
|
|
182
|
-
this.error(
|
|
183
|
-
'Path step $(ID) of $(ELEMREF) has no foreign keys');
|
|
182
|
+
this.error('expr-missing-foreign-key', $path, { id: pathStep, elemref: { ref } } );
|
|
184
183
|
break; // only one error per path
|
|
185
184
|
}
|
|
186
185
|
else if (art.on) {
|