@sap/cds-compiler 6.1.0 → 6.3.0
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 +78 -0
- package/bin/cdsc.js +17 -6
- package/bin/cdsse.js +1 -1
- package/bin/cdsv2m.js +1 -1
- package/lib/api/main.js +29 -7
- package/lib/api/options.js +1 -1
- package/lib/base/builtins.js +9 -0
- package/lib/base/keywords.js +1 -1
- package/lib/base/message-registry.js +41 -10
- package/lib/base/messages.js +13 -6
- package/lib/base/model.js +1 -1
- package/lib/base/optionProcessorHelper.js +7 -2
- package/lib/checks/assocOutsideService.js +17 -30
- package/lib/checks/checkForTypes.js +0 -18
- package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
- package/lib/checks/featureFlags.js +4 -1
- package/lib/checks/onConditions.js +2 -2
- package/lib/checks/queryNoDbArtifacts.js +16 -15
- package/lib/checks/types.js +1 -1
- package/lib/checks/utils.js +30 -6
- package/lib/checks/validator.js +4 -5
- package/lib/compiler/assert-consistency.js +3 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +85 -39
- package/lib/compiler/define.js +24 -5
- package/lib/compiler/extend.js +1 -1
- package/lib/compiler/finalize-parse-cdl.js +9 -1
- package/lib/compiler/generate.js +4 -4
- package/lib/compiler/index.js +88 -6
- package/lib/compiler/lsp-api.js +2 -0
- package/lib/compiler/populate.js +8 -8
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +22 -21
- package/lib/compiler/shared.js +6 -6
- package/lib/compiler/tweak-assocs.js +53 -31
- package/lib/compiler/utils.js +9 -16
- package/lib/compiler/xpr-rewrite.js +2 -2
- package/lib/gen/BaseParser.js +35 -29
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1424 -1430
- package/lib/gen/Dictionary.json +1 -2
- package/lib/gen/cdlKeywords.json +26 -0
- package/lib/inspect/inspectPropagation.js +1 -1
- package/lib/json/from-csn.js +2 -2
- package/lib/json/to-csn.js +1 -1
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +9 -4
- package/lib/model/csnUtils.js +67 -2
- package/lib/optionProcessor.js +9 -9
- package/lib/parsers/AstBuildingParser.js +28 -26
- package/lib/parsers/identifiers.js +2 -30
- package/lib/render/toCdl.js +73 -13
- package/lib/render/toSql.js +127 -108
- package/lib/render/utils/common.js +4 -2
- package/lib/render/utils/sql.js +67 -0
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/associations.js +37 -1
- package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
- package/lib/transform/db/assocsToQueries/utils.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +37 -36
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +20 -2
- package/lib/transform/draft/db.js +20 -20
- package/lib/transform/draft/odata.js +38 -40
- package/lib/transform/effective/associations.js +1 -1
- package/lib/transform/effective/flattening.js +40 -47
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/forOdata.js +201 -92
- package/lib/transform/forRelationalDB.js +151 -142
- package/lib/transform/localized.js +116 -109
- package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
- package/lib/transform/odata/createForeignKeys.js +73 -70
- package/lib/transform/odata/flattening.js +216 -200
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
- package/lib/transform/odata/toFinalBaseType.js +40 -39
- package/lib/transform/odata/typesExposure.js +151 -133
- package/lib/transform/odata/utils.js +7 -6
- package/lib/transform/parseExpr.js +165 -162
- package/lib/transform/transformUtils.js +184 -551
- package/lib/transform/translateAssocsToJoins.js +511 -596
- package/lib/transform/tupleExpansion.js +495 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
- package/lib/base/cleanSymbols.js +0 -17
- package/lib/checks/nonexpandableStructured.js +0 -39
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,69 @@ Note: `beta` fixes, changes and features are usually not listed in this ChangeLo
|
|
|
8
8
|
but in [doc/CHANGELOG_BETA.md](doc/CHANGELOG_BETA.md).
|
|
9
9
|
The compiler behavior concerning `beta` features can change at any time without notice.
|
|
10
10
|
|
|
11
|
+
## Version 6.3.0 - 2025-08-28
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- compiler: Column casts can now use more modifiers such as `default` directly.
|
|
16
|
+
- for.odata/to.edm(x):
|
|
17
|
+
+ New option `draftUserDescription` is now available. It adds the fields `CreatedByUserDescription`,
|
|
18
|
+
`LastChangedByUserDescription`, `InProcessByUserDescription` to the `DraftAdministrativeData` entity.
|
|
19
|
+
- to.sql:
|
|
20
|
+
+ Structures with only one element can now be compared to scalar values.
|
|
21
|
+
This also applies to associations with only one foreign key.
|
|
22
|
+
+ `cds.UInt8` can now be used in SQL dialects "h2" and "postgres".
|
|
23
|
+
+ Managed associations can now be used in comparisons, e.g. `assoc = struct`.
|
|
24
|
+
+ Structures and managed associations with only one element can be compared with scalars, e.g. `struct = 1`.
|
|
25
|
+
+ In the draft use case, the `DRAFT.DraftAdministrativeData` entity now includes the following fields by default:
|
|
26
|
+
`CreatedByUserDescription`, `LastChangedByUserDescription`, `InProcessByUserDescription`, and `DraftMessages`.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Update OData vocabularies: Common
|
|
31
|
+
- cdsc: EDMX output uses XML comments as service separators instead of `//`.
|
|
32
|
+
If there is only one service, no header is printed, allowing piping the output to a file.
|
|
33
|
+
- to.sql: path expressions which end in a foreign key are now always optimized to use the element of the source side.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- compiler: Redirecting associations to non-query entities was fixed.
|
|
38
|
+
- to.sql/to.edm(x): References to associations can now be compared to other associations and structures.
|
|
39
|
+
- to.sql: Referencing a foreign key of an `@cds.persistence.skip` entity previously caused an
|
|
40
|
+
error in queries. Now the foreign key in the source entity is resolved and rendered.
|
|
41
|
+
|
|
42
|
+
### Removed
|
|
43
|
+
|
|
44
|
+
- for.odata/to.edm(x): The `addAnnotationAddressViaNavigationPath` option has been removed. Its functionality is included in the `draftMessages` option.
|
|
45
|
+
|
|
46
|
+
## Version 6.2.2 - 2025-07-28
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- compiler: `@extension.code` was accidentally restricted to non-expression values.
|
|
51
|
+
|
|
52
|
+
## Version 6.2.0 - 2025-07-25
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- parser: CDL-casts in queries now support all type expressions, e.g. `field : many String not null`.
|
|
57
|
+
- compiler: Association paths in annotation expressions can now end with a filter, e.g. `@anno: (assoc[1=1])`.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- compiler: Annotation `@extension.code` is no longer propagated.
|
|
62
|
+
- Update OData vocabularies: Common
|
|
63
|
+
- The list of CDL keywords was updated for the latest CDL grammar.
|
|
64
|
+
- to.cdl: Foreign keys of managed associations are only rendered explicitly if
|
|
65
|
+
the compiler can't infer them when recompiled.
|
|
66
|
+
- cdsc: The command `parseCdl` was renamed to `parse`, since it also supports CSN input.
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- compiler:
|
|
71
|
+
+ Calculated elements can now have a localized type.
|
|
72
|
+
+ Associations in sub-queries of an `order by` of a `UNION` are now redirected.
|
|
73
|
+
|
|
11
74
|
## Version 6.1.0 - 2025-06-27
|
|
12
75
|
|
|
13
76
|
### Added
|
|
@@ -135,6 +198,21 @@ The compiler behavior concerning `beta` features can change at any time without
|
|
|
135
198
|
|
|
136
199
|
- to.edm(x): Fixed crash for rare case if annotation expressions were used.
|
|
137
200
|
|
|
201
|
+
## Version 5.9.8 - 2025-07-14
|
|
202
|
+
|
|
203
|
+
### Fixed
|
|
204
|
+
|
|
205
|
+
- compiler: Calculated elements can now have a localized type
|
|
206
|
+
|
|
207
|
+
## Version 5.9.6 - 2025-06-18
|
|
208
|
+
|
|
209
|
+
### Fixed
|
|
210
|
+
|
|
211
|
+
- to.sql: Fix error when calculated element refers to a localized element.
|
|
212
|
+
- to.edm(x):
|
|
213
|
+
+ Fix errors for service entities containing multiple path steps (e.g. `Service.Prefix.MyEntity`).
|
|
214
|
+
+ Support enum references in annotation expressions that were resolved by the compiler.
|
|
215
|
+
|
|
138
216
|
## Version 5.9.4 - 2025-05-22
|
|
139
217
|
|
|
140
218
|
### Fixed
|
package/bin/cdsc.js
CHANGED
|
@@ -151,7 +151,7 @@ function cdscMain() {
|
|
|
151
151
|
cmdLine.options.attachValidNames = true;
|
|
152
152
|
|
|
153
153
|
// Internally, parseCdl/parseOnly are options, so we map the command to it.
|
|
154
|
-
if (cmdLine.command === '
|
|
154
|
+
if (cmdLine.command === 'parse') {
|
|
155
155
|
cmdLine.command = 'toCsn';
|
|
156
156
|
cmdLine.options.toCsn = cmdLine.options.parseCdl;
|
|
157
157
|
cmdLine.options.parseCdl = true;
|
|
@@ -462,13 +462,15 @@ async function executeCommandLine( command, options, args ) {
|
|
|
462
462
|
}
|
|
463
463
|
else if (options.json) {
|
|
464
464
|
const result = main.to.edm.all(csn, options);
|
|
465
|
+
const omitHeadline = Object.keys(result).length === 1;
|
|
465
466
|
for (const serviceName in result)
|
|
466
|
-
writeToFileOrDisplay(options.out, `${ serviceName }.json`, result[serviceName]);
|
|
467
|
+
writeToFileOrDisplay(options.out, `${ serviceName }.json`, result[serviceName], omitHeadline);
|
|
467
468
|
}
|
|
468
469
|
else {
|
|
469
470
|
const result = main.to.edmx.all(csn, options);
|
|
471
|
+
const omitHeadline = Object.keys(result).length === 1;
|
|
470
472
|
for (const serviceName in result)
|
|
471
|
-
writeToFileOrDisplay(options.out, `${ serviceName }.xml`, result[serviceName]);
|
|
473
|
+
writeToFileOrDisplay(options.out, `${ serviceName }.xml`, result[serviceName], omitHeadline);
|
|
472
474
|
}
|
|
473
475
|
return model;
|
|
474
476
|
}
|
|
@@ -704,10 +706,19 @@ async function executeCommandLine( command, options, args ) {
|
|
|
704
706
|
return;
|
|
705
707
|
if (!omitHeadline) {
|
|
706
708
|
const sqlTypes = {
|
|
707
|
-
sql: true,
|
|
709
|
+
sql: true,
|
|
710
|
+
hdbconstraint: true,
|
|
711
|
+
hdbtable: true,
|
|
712
|
+
hdbview: true,
|
|
713
|
+
hdbprojectionview: true,
|
|
708
714
|
};
|
|
709
|
-
|
|
710
|
-
|
|
715
|
+
if (fileName.endsWith('.xml')) {
|
|
716
|
+
process.stdout.write(`<!-- ------------------- ${ fileName.replaceAll('-->', '-- >') } ------------------- -->\n`);
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
const commentStarter = fileName.split('.').pop() in sqlTypes ? '--$' : '//';
|
|
720
|
+
process.stdout.write(`${ commentStarter } ------------------- ${ fileName } -------------------\n`);
|
|
721
|
+
}
|
|
711
722
|
}
|
|
712
723
|
|
|
713
724
|
process.stdout.write(`${ content }\n`);
|
package/bin/cdsse.js
CHANGED
package/bin/cdsv2m.js
CHANGED
package/lib/api/main.js
CHANGED
|
@@ -15,6 +15,7 @@ const generateDrafts = lazyload('../transform/draft/odata');
|
|
|
15
15
|
const tenant = lazyload('../transform/addTenantFields');
|
|
16
16
|
const toSql = lazyload('../render/toSql');
|
|
17
17
|
const toCdl = require('../render/toCdl');
|
|
18
|
+
const sqlRenderUtils = lazyload('../render/utils/sql');
|
|
18
19
|
const modelCompare = lazyload('../modelCompare/compare');
|
|
19
20
|
const diffFilter = lazyload('../modelCompare/utils/filter');
|
|
20
21
|
const sortViews = lazyload('../model/sortViews');
|
|
@@ -386,11 +387,13 @@ function hdi( csn, options, messageFunctions ) {
|
|
|
386
387
|
|
|
387
388
|
objectUtils.forEach(flat, (key) => {
|
|
388
389
|
const artifactNameLikeInCsn = key.replace(/\.[^/.]+$/, '');
|
|
389
|
-
|
|
390
|
-
|
|
390
|
+
if (key.endsWith('.hdbtable') || key.endsWith('.hdbview') || key.endsWith('.hdbprojectionview')) {
|
|
391
|
+
nameMapping[artifactNameLikeInCsn] = key;
|
|
391
392
|
sqlArtifactsWithCSNNamesToSort[artifactNameLikeInCsn] = flat[key];
|
|
392
|
-
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
393
395
|
sqlArtifactsNotToSort[key] = flat[key];
|
|
396
|
+
}
|
|
394
397
|
});
|
|
395
398
|
|
|
396
399
|
const sorted = sortViews({ sql: sqlArtifactsWithCSNNamesToSort, csn: sqlCSN })
|
|
@@ -687,7 +690,7 @@ function hdiMigration( csn, options, messageFunctions, beforeImage ) {
|
|
|
687
690
|
return {
|
|
688
691
|
afterImage,
|
|
689
692
|
definitions: createSqlDefinitions(hdbkinds, afterImage),
|
|
690
|
-
deletions: createSqlDeletions(deletions, beforeImage),
|
|
693
|
+
deletions: createSqlDeletions(deletions, beforeImage, options),
|
|
691
694
|
migrations: createSqlMigrations(migrations, afterImage),
|
|
692
695
|
};
|
|
693
696
|
}
|
|
@@ -719,11 +722,30 @@ function createSqlDefinitions( hdbkinds, afterImage ) {
|
|
|
719
722
|
* @param {CSN.Model} beforeImage CSN used to create correct file names in result structure.
|
|
720
723
|
* @returns {object[]} Array of objects, each having: name and suffix - only .hdbtable as suffix for now
|
|
721
724
|
*/
|
|
722
|
-
function createSqlDeletions( deletions, beforeImage ) {
|
|
725
|
+
function createSqlDeletions( deletions, beforeImage, options ) {
|
|
723
726
|
const result = [];
|
|
724
|
-
objectUtils.forEach(deletions, name => result.push({
|
|
727
|
+
objectUtils.forEach(deletions, name => result.push({
|
|
728
|
+
name: getFileName(name, beforeImage), suffix: getSuffix(beforeImage, beforeImage.definitions[name], options),
|
|
729
|
+
}));
|
|
725
730
|
return result;
|
|
726
731
|
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Determines the appropriate file suffix based on the type of the provided artifact.
|
|
735
|
+
*
|
|
736
|
+
* @param {CSN.Artifact} artifact - The artifact object to evaluate.
|
|
737
|
+
* @returns {string} The file suffix corresponding to the artifact type:
|
|
738
|
+
* - '.hdbview' for query artifacts
|
|
739
|
+
* - '.hdbprojectionview' for projection artifacts
|
|
740
|
+
* - '.hdbtable' for other artifacts
|
|
741
|
+
*/
|
|
742
|
+
function getSuffix( csn, artifact, options ) {
|
|
743
|
+
if (artifact.query || artifact.projection)
|
|
744
|
+
return sqlRenderUtils.isProjectionView(csn, artifact, options) ? '.hdbprojectionview' : '.hdbview';
|
|
745
|
+
|
|
746
|
+
return '.hdbtable';
|
|
747
|
+
}
|
|
748
|
+
|
|
727
749
|
/**
|
|
728
750
|
* From the given migrations, create the correct result structure.
|
|
729
751
|
*
|
|
@@ -1382,7 +1404,7 @@ function handleTenantDiscriminator( options, internalOptions, messageFunctions )
|
|
|
1382
1404
|
*/
|
|
1383
1405
|
|
|
1384
1406
|
/**
|
|
1385
|
-
* A map of { <file.hdbtable|hdbview|hdbconstraint...>:<content> }.
|
|
1407
|
+
* A map of { <file.hdbtable|hdbview|hdbprojectionview|hdbconstraint...>:<content> }.
|
|
1386
1408
|
*
|
|
1387
1409
|
* @typedef {object} HDIArtifacts
|
|
1388
1410
|
*/
|
package/lib/api/options.js
CHANGED
|
@@ -36,7 +36,6 @@ const publicOptionsNewAPI = [
|
|
|
36
36
|
'booleanEquality',
|
|
37
37
|
'dollarNowAsTimestamp',
|
|
38
38
|
// ODATA
|
|
39
|
-
'addAnnotationAddressViaNavigationPath',
|
|
40
39
|
'odataOpenapiHints',
|
|
41
40
|
'edm4OpenAPI',
|
|
42
41
|
'odataVersion',
|
|
@@ -50,6 +49,7 @@ const publicOptionsNewAPI = [
|
|
|
50
49
|
'odataVocabularies',
|
|
51
50
|
'odataNoCreator',
|
|
52
51
|
'draftMessages',
|
|
52
|
+
'draftUserDescription',
|
|
53
53
|
'service',
|
|
54
54
|
'serviceNames',
|
|
55
55
|
// to.cdl
|
package/lib/base/builtins.js
CHANGED
|
@@ -29,6 +29,14 @@ const propagationRules = {
|
|
|
29
29
|
'@sql.append': 'never',
|
|
30
30
|
'@sql.prepend': 'never',
|
|
31
31
|
'@sql.replace': 'never',
|
|
32
|
+
'@extension.code': 'never', // for cds-oyster, not security relevant, but convenience
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Of the annotations above, only these accept expressions as annotation values.
|
|
36
|
+
*/
|
|
37
|
+
const acceptsExprValues = {
|
|
38
|
+
__proto__: null,
|
|
39
|
+
'@extension.code': true,
|
|
32
40
|
};
|
|
33
41
|
|
|
34
42
|
/**
|
|
@@ -101,6 +109,7 @@ function isAnnotationExpression( val ) {
|
|
|
101
109
|
|
|
102
110
|
module.exports = {
|
|
103
111
|
propagationRules,
|
|
112
|
+
acceptsExprValues,
|
|
104
113
|
xprInAnnoProperties,
|
|
105
114
|
isInReservedNamespace,
|
|
106
115
|
isBuiltinType,
|
package/lib/base/keywords.js
CHANGED
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
// "sloppy": with an upcoming _minor_ version of the compiler, the compilation
|
|
30
30
|
// might lead to an error anyway or the compiled CSN might look different.
|
|
31
31
|
|
|
32
|
-
/* eslint @stylistic/
|
|
33
|
-
/* eslint @stylistic/
|
|
34
|
-
/* eslint @stylistic/
|
|
32
|
+
/* eslint @stylistic/no-multi-spaces: 0 */
|
|
33
|
+
/* eslint @stylistic/max-len: 0 */
|
|
34
|
+
/* eslint @stylistic/key-spacing: 0 */
|
|
35
35
|
|
|
36
36
|
'use strict';
|
|
37
37
|
|
|
@@ -178,7 +178,7 @@ const centralMessages = {
|
|
|
178
178
|
'service-nested-context': { severity: 'Error', configurableFor: true }, // does not hurt compile, TODO
|
|
179
179
|
'service-nested-service': { severity: 'Error' }, // not supported yet; TODO: configurableFor:'test'?
|
|
180
180
|
|
|
181
|
-
'expr-unexpected-operator': { severity: 'Error'
|
|
181
|
+
'expr-unexpected-operator': { severity: 'Error' },
|
|
182
182
|
|
|
183
183
|
// Published! Used in @sap/cds-lsp; if renamed, add to oldMessageIds and contact colleagues
|
|
184
184
|
// Also used by other projects that rely on double-quotes for delimited identifiers.
|
|
@@ -196,7 +196,7 @@ const centralMessages = {
|
|
|
196
196
|
'syntax-missing-as': { severity: 'Error', configurableFor: true },
|
|
197
197
|
'syntax-missing-proj-semicolon': { severity: 'Warning' },
|
|
198
198
|
'syntax-unexpected-after': { severity: 'Error' },
|
|
199
|
-
'syntax-unexpected-filter': { severity: 'Error', configurableFor:
|
|
199
|
+
'syntax-unexpected-filter': { severity: 'Error', configurableFor: 'v7' },
|
|
200
200
|
'syntax-unexpected-many-one': { severity: 'Error' },
|
|
201
201
|
'syntax-deprecated-ref-virtual': { severity: 'Error' },
|
|
202
202
|
'syntax-unexpected-reserved-word': { severity: 'Error', configurableFor: true },
|
|
@@ -786,8 +786,11 @@ const centralMessageTexts = {
|
|
|
786
786
|
calc: 'Calculated elements can\'t use parameter references',
|
|
787
787
|
},
|
|
788
788
|
'ref-unexpected-structured': {
|
|
789
|
-
std: 'Unexpected usage of structured
|
|
790
|
-
|
|
789
|
+
std: 'Unexpected usage of structured element $(ELEMREF)',
|
|
790
|
+
assoc: 'Unexpected usage of managed association $(ELEMREF)',
|
|
791
|
+
'struct-expr': 'Structured element $(ELEMREF) can\'t be used in expressions with scalars; only possible for structures with one leaf-element',
|
|
792
|
+
'assoc-expr': 'Associations $(ELEMREF) can\'t be used in expressions with scalars; only possible for association with one foreign key',
|
|
793
|
+
complexExpr: 'Unexpected reference to a structured element $(ELEMREF) in expression $(VALUE)',
|
|
791
794
|
},
|
|
792
795
|
'ref-unexpected-virtual': {
|
|
793
796
|
std: 'Unexpected reference to virtual element $(NAME)', // "std" currently unused
|
|
@@ -801,8 +804,14 @@ const centralMessageTexts = {
|
|
|
801
804
|
'with-filter': 'Unexpected reference to an association with filter',
|
|
802
805
|
'self-with-filter': 'Unexpected column reference starting with $(ALIAS) to an association with filter',
|
|
803
806
|
self: 'A reference to an unmanaged association is only valid when compared via $(CODE)',
|
|
807
|
+
|
|
804
808
|
expr: 'Associations can\'t be used as values in expressions',
|
|
805
809
|
'expr-comp': 'Compositions can\'t be used as values in expressions',
|
|
810
|
+
'anno-expr': 'Associations can\'t be used in expressions for annotation values',
|
|
811
|
+
'anno-expr-comp': 'Compositions can\'t be used in expressions for annotation values',
|
|
812
|
+
'query-expr': 'Unmanaged associations can\'t be used in expressions in queries',
|
|
813
|
+
'query-expr-comp': 'Unmanaged compositions can\'t be used in expressions in queries',
|
|
814
|
+
|
|
806
815
|
'assoc-stored': 'Associations and compositions can\'t be used as values in stored calculated elements',
|
|
807
816
|
|
|
808
817
|
'managed-filter': 'Unexpected managed association $(NAME) in filter expression of $(ID)',
|
|
@@ -831,6 +840,7 @@ const centralMessageTexts = {
|
|
|
831
840
|
unmanagedleaf: 'Unexpected unmanaged association as final path step of $(ELEMREF) in an ON-condition',
|
|
832
841
|
'calc-non-fk': 'Can\'t follow association $(ID) in path $(ELEMREF) in a stored calculated element; only foreign keys can be referred to, but not $(NAME)',
|
|
833
842
|
'calc-unmanaged': 'Can\'t follow association $(ID) in path $(ELEMREF) in a stored calculated element',
|
|
843
|
+
'calc-missing': 'Missing foreign key access for association $(ID) in path $(ELEMREF) in a stored calculated element',
|
|
834
844
|
},
|
|
835
845
|
'ref-unexpected-filter': {
|
|
836
846
|
std: 'Unexpected filter in path $(ELEMREF)', // unused
|
|
@@ -983,10 +993,16 @@ const centralMessageTexts = {
|
|
|
983
993
|
},
|
|
984
994
|
'def-unexpected-key': {
|
|
985
995
|
std: '$(ART) can\'t have additional keys',
|
|
986
|
-
virtual: 'Unexpected $(
|
|
996
|
+
virtual: 'Unexpected $(KEYWORD) for virtual element',
|
|
987
997
|
// TODO: Better message?
|
|
988
998
|
include: '$(ART) can\'t have additional keys (through include)',
|
|
989
|
-
invalidType: 'Unexpected $(
|
|
999
|
+
invalidType: 'Unexpected $(KEYWORD) for element of type $(TYPE)',
|
|
1000
|
+
},
|
|
1001
|
+
'def-unsupported-key': {
|
|
1002
|
+
std: '$(KEYWORD) is not supported here', // unused variant
|
|
1003
|
+
kind: '$(KEYWORD) is only supported for elements in an entity or an aspect',
|
|
1004
|
+
sub: '$(KEYWORD) is only supported for top-level elements',
|
|
1005
|
+
type: '$(KEYWORD) is not supported for elements of type $(TYPE)',
|
|
990
1006
|
},
|
|
991
1007
|
'def-unexpected-localized': {
|
|
992
1008
|
std: 'Unexpected $(KEYWORD)',
|
|
@@ -1046,6 +1062,9 @@ const centralMessageTexts = {
|
|
|
1046
1062
|
'include-elements': 'Duplicate element $(NAME) through multiple includes $(SORTED_ARTS)',
|
|
1047
1063
|
'include-actions': 'Duplicate action or function $(NAME) through multiple includes $(SORTED_ARTS)',
|
|
1048
1064
|
},
|
|
1065
|
+
'ref-invalid-assoc-navigation': {
|
|
1066
|
+
std: 'Invalid navigation along association $(ID) in path $(ELEMREF) to target $(NAME) having annotation $(ANNO)',
|
|
1067
|
+
},
|
|
1049
1068
|
'ref-invalid-element': {
|
|
1050
1069
|
std: 'Invalid element reference',
|
|
1051
1070
|
$tableAlias: 'Can\'t refer to source elements of table alias $(ID)',
|
|
@@ -1219,11 +1238,13 @@ const centralMessageTexts = {
|
|
|
1219
1238
|
'type-invalid-cast': {
|
|
1220
1239
|
std: 'Can\'t cast to $(TYPE)',
|
|
1221
1240
|
'to-structure': 'Can\'t cast to a structured type',
|
|
1241
|
+
'to-inline-structure': 'Can\'t cast to a structure',
|
|
1222
1242
|
'from-structure': 'Structured elements can\'t be cast to a different type',
|
|
1223
1243
|
'expr-to-structure': 'Can\'t cast an expression to a structured type',
|
|
1224
1244
|
'val-to-structure': 'Can\'t cast $(VALUE) to a structured type',
|
|
1225
1245
|
'from-assoc': 'Invalid type cast on an association',
|
|
1226
1246
|
assoc: 'Can\'t cast to an association',
|
|
1247
|
+
expand: 'Expands can\'t have a type cast', // TODO: Improve
|
|
1227
1248
|
},
|
|
1228
1249
|
|
|
1229
1250
|
// -----------------------------------------------------------------------------------
|
|
@@ -1252,7 +1273,17 @@ const centralMessageTexts = {
|
|
|
1252
1273
|
publishingFilter: 'Can\'t publish managed association $(ID) with filter, as it must have at least one foreign key',
|
|
1253
1274
|
},
|
|
1254
1275
|
|
|
1255
|
-
|
|
1276
|
+
'expr-invalid-expansion': {
|
|
1277
|
+
std: 'Path $(NAME) in expression $(VALUE) can\'t be expanded',
|
|
1278
|
+
'path-mismatch': 'Missing sub path $(NAME) in $(ALIAS) for tuple expansion of $(VALUE); both sides must expand to the same sub paths',
|
|
1279
|
+
'non-scalar': 'Path $(NAME) in expression $(VALUE) can\'t be expanded as it does not contain any scalar element',
|
|
1280
|
+
},
|
|
1281
|
+
'expr-unsupported-expansion': {
|
|
1282
|
+
std: 'Unsupported $(ELEMREF) in structural expression $(VALUE)',
|
|
1283
|
+
scalarRef: 'Unsupported scalar reference $(ELEMREF) in structural expression $(VALUE)',
|
|
1284
|
+
},
|
|
1285
|
+
|
|
1286
|
+
// tenant isolation via discriminator column:
|
|
1256
1287
|
'tenant-invalid-alias-name': {
|
|
1257
1288
|
std: 'Can\'t have a table alias named $(NAME) in a tenant-dependent entity',
|
|
1258
1289
|
implicit: 'Provide an explicit table alias name; do not use $(NAME)',
|
package/lib/base/messages.js
CHANGED
|
@@ -911,14 +911,20 @@ function transformElementRef( arg ) {
|
|
|
911
911
|
if (!ref)
|
|
912
912
|
return quoted( arg );
|
|
913
913
|
// Can be used by CSN backends or compiler to create a simple path such as E:elem
|
|
914
|
-
return quoted(
|
|
915
|
-
|
|
914
|
+
return quoted( pathToMessageString( arg ) );
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function pathToMessageString( arg ) {
|
|
918
|
+
const ref = arg?.ref || arg?.path || arg; // support CSN and XSN
|
|
919
|
+
if (!ref)
|
|
920
|
+
return null;
|
|
921
|
+
|
|
922
|
+
return ((arg.scope === 'param' || arg.param) ? ':' : '') +
|
|
916
923
|
ref.map(
|
|
917
924
|
item => (typeof item !== 'string'
|
|
918
925
|
? `${ item.id }${ item.args ? '(…)' : '' }${ item.where ? '[…]' : '' }`
|
|
919
926
|
: item)
|
|
920
|
-
).join('.')
|
|
921
|
-
);
|
|
927
|
+
).join('.');
|
|
922
928
|
}
|
|
923
929
|
|
|
924
930
|
function transformArg( arg, r, args, texts ) {
|
|
@@ -1329,8 +1335,8 @@ function compareMessage( a, b ) {
|
|
|
1329
1335
|
const aFile = a.$location && a.$location.file;
|
|
1330
1336
|
const bFile = b.$location && b.$location.file;
|
|
1331
1337
|
if (aFile && bFile) {
|
|
1332
|
-
const aEnd = a.$location.endLine && a.$location.endCol && a.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/
|
|
1333
|
-
const bEnd = b.$location.endLine && b.$location.endCol && b.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/
|
|
1338
|
+
const aEnd = a.$location.endLine && a.$location.endCol && a.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/max-len
|
|
1339
|
+
const bEnd = b.$location.endLine && b.$location.endCol && b.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/max-len
|
|
1334
1340
|
return ( c( aFile, bFile ) ||
|
|
1335
1341
|
c( a.$location.line, b.$location.line ) ||
|
|
1336
1342
|
c( a.$location.col, b.$location.col ) ||
|
|
@@ -1958,4 +1964,5 @@ module.exports = {
|
|
|
1958
1964
|
// for tests only
|
|
1959
1965
|
constructSemanticLocationFromCsnPath,
|
|
1960
1966
|
homeName,
|
|
1967
|
+
pathToMessageString,
|
|
1961
1968
|
};
|
package/lib/base/model.js
CHANGED
|
@@ -26,9 +26,9 @@ const availableBetaFlags = {
|
|
|
26
26
|
calcAssoc: true,
|
|
27
27
|
temporalRawProjection: true,
|
|
28
28
|
v7preview: true,
|
|
29
|
-
draftMessages: true,
|
|
30
29
|
rewriteAnnotationExpressionsViaType: true,
|
|
31
30
|
sqlServiceDummies: true,
|
|
31
|
+
projectionViews: true,
|
|
32
32
|
// disabled by --beta-mode
|
|
33
33
|
nestedServices: false,
|
|
34
34
|
};
|
|
@@ -71,8 +71,9 @@ function createOptionProcessor() {
|
|
|
71
71
|
/**
|
|
72
72
|
* API: Define a command
|
|
73
73
|
* @param {string} cmdString Command name, short and long form, e.g. 'S, toSql'
|
|
74
|
+
* @param {object} [cmdOptions] Optional options, e.g. `aliases`.
|
|
74
75
|
*/
|
|
75
|
-
function command( cmdString ) {
|
|
76
|
+
function command( cmdString, cmdOptions = null ) {
|
|
76
77
|
/** @type {object} */
|
|
77
78
|
const cmd = {
|
|
78
79
|
options: {},
|
|
@@ -83,6 +84,7 @@ function createOptionProcessor() {
|
|
|
83
84
|
return cmd;
|
|
84
85
|
},
|
|
85
86
|
help: commandHelp,
|
|
87
|
+
aliases: cmdOptions?.aliases,
|
|
86
88
|
..._parseCommandString(cmdString),
|
|
87
89
|
};
|
|
88
90
|
if (optionProcessor.commands[cmd.longName])
|
|
@@ -96,6 +98,10 @@ function createOptionProcessor() {
|
|
|
96
98
|
|
|
97
99
|
optionProcessor.commands[cmd.shortName] = cmd;
|
|
98
100
|
}
|
|
101
|
+
|
|
102
|
+
for (const alias of cmdOptions?.aliases ?? [])
|
|
103
|
+
optionProcessor.commands[alias] = cmd;
|
|
104
|
+
|
|
99
105
|
return cmd;
|
|
100
106
|
|
|
101
107
|
// Command API: Define a command option
|
|
@@ -400,7 +406,6 @@ function createOptionProcessor() {
|
|
|
400
406
|
else
|
|
401
407
|
result.errors.push(errorMsg);
|
|
402
408
|
}
|
|
403
|
-
|
|
404
409
|
return result;
|
|
405
410
|
|
|
406
411
|
/**
|
|
@@ -1,42 +1,29 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
4
|
+
const { forEachMemberRecursively } = require('../model/csnUtils');
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* Asserts that there is no association usage outside of the specified service.
|
|
5
8
|
* We do not check in type-ofs - we resolve them, so they are not a problem.
|
|
6
9
|
*
|
|
7
|
-
* @param {
|
|
8
|
-
* @param {string}
|
|
9
|
-
* @param {object} ref - The reference object.
|
|
10
|
-
* @param {Array} path - The path array indicating the location in the CSN.
|
|
11
|
-
* @param {object} grandparent - The grandparent object in the CSN.
|
|
12
|
-
* @param {string} parentProp - The property name of the grandparent object.
|
|
10
|
+
* @param {CSN.Artifact} artifact Artifact to validate
|
|
11
|
+
* @param {string} artifactName Name of the artifact
|
|
13
12
|
*/
|
|
14
|
-
function assertNoAssocUsageOutsideOfService(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return;
|
|
18
|
-
|
|
19
|
-
if (this.csn.definitions[this.options.effectiveServiceName]?.kind !== 'service' ||
|
|
20
|
-
!artifactName.startsWith(`${ this.options.effectiveServiceName }.`))
|
|
21
|
-
return;
|
|
22
|
-
|
|
23
|
-
const { _links } = parent;
|
|
24
|
-
// session variables can't have assoc steps, _links of 1 can't have assoc steps
|
|
25
|
-
// TODO: (typeof parentProp === 'number' && path[path.length - 2] === 'on') - ignore on-conditions, as they are cut off anyway
|
|
26
|
-
if (parent.$scope === '$magic' || _links?.length <= 1 )
|
|
13
|
+
function assertNoAssocUsageOutsideOfService( artifact, artifactName ) {
|
|
14
|
+
if (artifact.kind !== 'entity' || this.csn.definitions[this.options.effectiveServiceName]?.kind !== 'service' ||
|
|
15
|
+
!artifactName.startsWith(`${ this.options.effectiveServiceName }.`))
|
|
27
16
|
return;
|
|
28
17
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
18
|
+
if (artifact.kind === 'entity' && (artifact.query || artifact.projection)) {
|
|
19
|
+
forEachMemberRecursively(artifact, (element, elementName, prop, path) => {
|
|
20
|
+
if (element && element.target && !element.target.startsWith(`${ this.options.effectiveServiceName }.`)) {
|
|
21
|
+
this.error('assoc-invalid-outside-service', path,
|
|
22
|
+
{ name: this.options.effectiveServiceName, id: elementName },
|
|
23
|
+
'Association $(ID) pointing outside of service $(NAME) must not be published');
|
|
24
|
+
}
|
|
25
|
+
}, [ 'definitions', artifactName ], true, { elementsOnly: true });
|
|
37
26
|
}
|
|
38
27
|
}
|
|
39
28
|
|
|
40
|
-
module.exports =
|
|
41
|
-
ref: assertNoAssocUsageOutsideOfService,
|
|
42
|
-
};
|
|
29
|
+
module.exports = assertNoAssocUsageOutsideOfService;
|
|
@@ -18,22 +18,6 @@ function checkForHanaTypes( parent, name, type, path ) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* Check that `cds.UInt8` is not used - we don't have a clear idea how to represent it on postgres and h2
|
|
23
|
-
*
|
|
24
|
-
* @param {object} parent Object with a type
|
|
25
|
-
* @param {string} name Name of the type property on parent
|
|
26
|
-
* @param {Array} type type to check
|
|
27
|
-
* @param {CSN.Path} path
|
|
28
|
-
*/
|
|
29
|
-
function CheckForUInt8( parent, name, type, path ) {
|
|
30
|
-
const artifact = this.csn.definitions[path[1]];
|
|
31
|
-
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && parent.type === 'cds.UInt8') {
|
|
32
|
-
this.error('ref-unexpected-type', [ ...path, 'type' ], { type: 'cds.UInt8', value: this.options.sqlDialect },
|
|
33
|
-
'Type $(TYPE) can\'t be used with sqlDialect $(VALUE)');
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
21
|
/**
|
|
38
22
|
* Check types - specifically for postgres and h2
|
|
39
23
|
*
|
|
@@ -44,8 +28,6 @@ function CheckForUInt8( parent, name, type, path ) {
|
|
|
44
28
|
*/
|
|
45
29
|
function checkTypes( parent, name, type, path ) {
|
|
46
30
|
checkForHanaTypes.bind(this)(parent, name, type, path);
|
|
47
|
-
if (this.options.sqlDialect === 'postgres' || this.options.sqlDialect === 'h2')
|
|
48
|
-
CheckForUInt8.bind(this)(parent, name, type, path);
|
|
49
31
|
}
|
|
50
32
|
|
|
51
33
|
module.exports = {
|
|
@@ -56,8 +56,9 @@ function _checkPathsInStoredCalcElement( parent, value, csnPath ) {
|
|
|
56
56
|
else {
|
|
57
57
|
// It's a managed association - access of the foreign keys is allowed
|
|
58
58
|
requireForeignKeyAccess(parent, i, (errorIndex) => {
|
|
59
|
+
const variant = errorIndex >= value.length ? 'calc-missing' : 'calc-non-fk';
|
|
59
60
|
this.error('ref-unexpected-navigation', csnPath, {
|
|
60
|
-
'#':
|
|
61
|
+
'#': variant, id, elemref: parent, name: value[errorIndex]?.id || value[errorIndex],
|
|
61
62
|
});
|
|
62
63
|
hasPathError = true;
|
|
63
64
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { setProp } = require('../base/model');
|
|
4
4
|
const { featureFlags } = require('../transform/featureFlags');
|
|
5
|
-
const { isSqlService, isDummyService } = require('../transform/db/processSqlServices');
|
|
5
|
+
const { isSqlService, isDummyService, isDataProductService } = require('../transform/db/processSqlServices');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
*
|
|
@@ -32,5 +32,8 @@ module.exports = {
|
|
|
32
32
|
|
|
33
33
|
if (isDummyService(artifact, this.options))
|
|
34
34
|
setFeatureFlag( '$dummyService' ).call(this);
|
|
35
|
+
|
|
36
|
+
if (isDataProductService(artifact, this.options))
|
|
37
|
+
setFeatureFlag( '$dataProductService' ).call(this);
|
|
35
38
|
},
|
|
36
39
|
};
|
|
@@ -135,7 +135,7 @@ function validateOnCondition( member, memberName, property, path ) {
|
|
|
135
135
|
((type.target && type.keys || type.elements) && validStructuredElement ||
|
|
136
136
|
(type.target && validDollarSelf)) && !type.virtual
|
|
137
137
|
) {
|
|
138
|
-
// Do nothing - handled by
|
|
138
|
+
// Do nothing - handled by tuple expansion
|
|
139
139
|
}
|
|
140
140
|
else if (type.items && !type.virtual) {
|
|
141
141
|
this.error(null, onPath, { elemref: { ref } },
|
|
@@ -183,7 +183,7 @@ function requireForeignKeyAccess( parent, refIndex, noForeignKeyCallback ) {
|
|
|
183
183
|
ref.splice(refIndex + 1, 1, ...resolved);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
const next = pathId(ref[refIndex + 1]);
|
|
186
|
+
const next = ref[refIndex + 1] && pathId(ref[refIndex + 1]);
|
|
187
187
|
let possibleKeys = next && assoc.keys.filter(r => r.ref[0] === next);
|
|
188
188
|
if (!possibleKeys || possibleKeys.length === 0) {
|
|
189
189
|
noForeignKeyCallback(refIndex + 1);
|