@sap/cds-compiler 6.0.14 → 6.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 +61 -0
- package/bin/cdsc.js +6 -2
- package/bin/cdsse.js +1 -1
- package/bin/cdsv2m.js +1 -1
- package/lib/api/main.js +29 -7
- package/lib/api/options.js +2 -0
- package/lib/base/builtins.js +9 -0
- package/lib/base/keywords.js +1 -1
- package/lib/base/message-registry.js +5 -3
- package/lib/base/messages.js +3 -3
- package/lib/base/model.js +1 -0
- package/lib/base/node-helpers.js +10 -2
- package/lib/base/optionProcessorHelper.js +7 -2
- package/lib/checks/assocOutsideService.js +3 -1
- package/lib/checks/featureFlags.js +4 -1
- 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 +38 -21
- 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 +10 -1
- 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 +15 -14
- package/lib/compiler/shared.js +6 -7
- package/lib/compiler/tweak-assocs.js +6 -6
- package/lib/compiler/utils.js +9 -16
- package/lib/compiler/xpr-rewrite.js +2 -2
- package/lib/gen/BaseParser.js +43 -37
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1424 -1433
- package/lib/gen/Dictionary.json +1 -7
- 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 +9 -5
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.d.ts +10 -2
- package/lib/model/cloneCsn.js +1 -0
- package/lib/optionProcessor.js +13 -7
- package/lib/parsers/AstBuildingParser.js +24 -21
- package/lib/parsers/identifiers.js +2 -30
- package/lib/render/toCdl.js +63 -9
- package/lib/render/toSql.js +127 -108
- package/lib/render/utils/sql.js +67 -0
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +20 -2
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/draft/odata.js +14 -4
- package/lib/transform/forOdata.js +91 -2
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/odata/flattening.js +1 -1
- package/lib/transform/transformUtils.js +2 -2
- package/lib/transform/translateAssocsToJoins.js +2 -26
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,52 @@ 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.2.2 - 2025-07-28
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- compiler: `@extension.code` was accidentally restricted to non-expression values.
|
|
16
|
+
|
|
17
|
+
## Version 6.2.0 - 2025-07-25
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- parser: CDL-casts in queries now support all type expressions, e.g. `field : many String not null`.
|
|
22
|
+
- compiler: Association paths in annotation expressions can now end with a filter, e.g. `@anno: (assoc[1=1])`.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- compiler: Annotation `@extension.code` is no longer propagated.
|
|
27
|
+
- Update OData vocabularies: Common
|
|
28
|
+
- The list of CDL keywords was updated for the latest CDL grammar.
|
|
29
|
+
- to.cdl: Foreign keys of managed associations are only rendered explicitly if
|
|
30
|
+
the compiler can't infer them when recompiled.
|
|
31
|
+
- cdsc: The command `parseCdl` was renamed to `parse`, since it also supports CSN input.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- compiler:
|
|
36
|
+
+ Calculated elements can now have a localized type.
|
|
37
|
+
+ Associations in sub-queries of an `order by` of a `UNION` are now redirected.
|
|
38
|
+
|
|
39
|
+
## Version 6.1.0 - 2025-06-27
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- for.odata:
|
|
44
|
+
+ Introduce a new option `addAnnotationAddressViaNavigationPath` to annotate services
|
|
45
|
+
containing draft-enabled entities with `@Common.AddressViaNavigationPath`.
|
|
46
|
+
+ Introduce a new option `draftMessages` that enhances the draft generation logic.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- Update OData vocabularies: Capabilities, Common
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- compiler: The ternary condition operator `…?…:…` is now right-associative as usual
|
|
55
|
+
(in v5, chaining it like in `…?…:…?…:…` was not possible without parentheses).
|
|
56
|
+
|
|
11
57
|
## Version 6.0.14 - 2025-06-18
|
|
12
58
|
|
|
13
59
|
### Fixed
|
|
@@ -117,6 +163,21 @@ The compiler behavior concerning `beta` features can change at any time without
|
|
|
117
163
|
|
|
118
164
|
- to.edm(x): Fixed crash for rare case if annotation expressions were used.
|
|
119
165
|
|
|
166
|
+
## Version 5.9.8 - 2025-07-14
|
|
167
|
+
|
|
168
|
+
### Fixed
|
|
169
|
+
|
|
170
|
+
- compiler: Calculated elements can now have a localized type
|
|
171
|
+
|
|
172
|
+
## Version 5.9.6 - 2025-06-18
|
|
173
|
+
|
|
174
|
+
### Fixed
|
|
175
|
+
|
|
176
|
+
- to.sql: Fix error when calculated element refers to a localized element.
|
|
177
|
+
- to.edm(x):
|
|
178
|
+
+ Fix errors for service entities containing multiple path steps (e.g. `Service.Prefix.MyEntity`).
|
|
179
|
+
+ Support enum references in annotation expressions that were resolved by the compiler.
|
|
180
|
+
|
|
120
181
|
## Version 5.9.4 - 2025-05-22
|
|
121
182
|
|
|
122
183
|
### 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;
|
|
@@ -704,7 +704,11 @@ async function executeCommandLine( command, options, args ) {
|
|
|
704
704
|
return;
|
|
705
705
|
if (!omitHeadline) {
|
|
706
706
|
const sqlTypes = {
|
|
707
|
-
sql: true,
|
|
707
|
+
sql: true,
|
|
708
|
+
hdbconstraint: true,
|
|
709
|
+
hdbtable: true,
|
|
710
|
+
hdbview: true,
|
|
711
|
+
hdbprojectionview: true,
|
|
708
712
|
};
|
|
709
713
|
const commentStarter = fileName.split('.').pop() in sqlTypes ? '--$' : '//';
|
|
710
714
|
process.stdout.write(`${ commentStarter } ------------------- ${ fileName } -------------------\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,6 +36,7 @@ const publicOptionsNewAPI = [
|
|
|
36
36
|
'booleanEquality',
|
|
37
37
|
'dollarNowAsTimestamp',
|
|
38
38
|
// ODATA
|
|
39
|
+
'addAnnotationAddressViaNavigationPath',
|
|
39
40
|
'odataOpenapiHints',
|
|
40
41
|
'edm4OpenAPI',
|
|
41
42
|
'odataVersion',
|
|
@@ -48,6 +49,7 @@ const publicOptionsNewAPI = [
|
|
|
48
49
|
'odataV2PartialConstr',
|
|
49
50
|
'odataVocabularies',
|
|
50
51
|
'odataNoCreator',
|
|
52
|
+
'draftMessages',
|
|
51
53
|
'service',
|
|
52
54
|
'serviceNames',
|
|
53
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
|
|
|
@@ -1219,11 +1219,13 @@ const centralMessageTexts = {
|
|
|
1219
1219
|
'type-invalid-cast': {
|
|
1220
1220
|
std: 'Can\'t cast to $(TYPE)',
|
|
1221
1221
|
'to-structure': 'Can\'t cast to a structured type',
|
|
1222
|
+
'to-inline-structure': 'Can\'t cast to a structure',
|
|
1222
1223
|
'from-structure': 'Structured elements can\'t be cast to a different type',
|
|
1223
1224
|
'expr-to-structure': 'Can\'t cast an expression to a structured type',
|
|
1224
1225
|
'val-to-structure': 'Can\'t cast $(VALUE) to a structured type',
|
|
1225
1226
|
'from-assoc': 'Invalid type cast on an association',
|
|
1226
1227
|
assoc: 'Can\'t cast to an association',
|
|
1228
|
+
expand: 'Expands can\'t have a type cast', // TODO: Improve
|
|
1227
1229
|
},
|
|
1228
1230
|
|
|
1229
1231
|
// -----------------------------------------------------------------------------------
|
package/lib/base/messages.js
CHANGED
|
@@ -193,7 +193,7 @@ class CompileMessage {
|
|
|
193
193
|
this.validNames = null;
|
|
194
194
|
this.home = home; // semantic location, e.g. 'entity:"E"/element:"x"'
|
|
195
195
|
this.severity = severity;
|
|
196
|
-
|
|
196
|
+
this.messageId = id;
|
|
197
197
|
Object.defineProperty( this, '$module', { value: moduleName, configurable: true } );
|
|
198
198
|
// Uncomment when running TypeScript linter
|
|
199
199
|
// this.messageId = id;
|
|
@@ -1329,8 +1329,8 @@ function compareMessage( a, b ) {
|
|
|
1329
1329
|
const aFile = a.$location && a.$location.file;
|
|
1330
1330
|
const bFile = b.$location && b.$location.file;
|
|
1331
1331
|
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/
|
|
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/max-len
|
|
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/max-len
|
|
1334
1334
|
return ( c( aFile, bFile ) ||
|
|
1335
1335
|
c( a.$location.line, b.$location.line ) ||
|
|
1336
1336
|
c( a.$location.col, b.$location.col ) ||
|
package/lib/base/model.js
CHANGED
package/lib/base/node-helpers.js
CHANGED
|
@@ -19,9 +19,17 @@ class PromiseAllError extends Error {
|
|
|
19
19
|
*
|
|
20
20
|
* This function only works as intended if no promise in `promises` fulfill
|
|
21
21
|
* with a value which is an instance of Error.
|
|
22
|
+
*
|
|
23
|
+
* @param {Promise[]} promises
|
|
24
|
+
* @param {(e: Error) => boolean} shouldImmediatelyReject
|
|
25
|
+
* Determine on a per-promise basis whether we should immediately abort all promises.
|
|
22
26
|
*/
|
|
23
|
-
function promiseAllDoNotRejectImmediately( promises ) {
|
|
24
|
-
return Promise.all( promises.map( p => p.catch(e =>
|
|
27
|
+
function promiseAllDoNotRejectImmediately( promises, shouldImmediatelyReject = _error => false ) {
|
|
28
|
+
return Promise.all( promises.map( p => p.catch((e) => {
|
|
29
|
+
if (shouldImmediatelyReject(e))
|
|
30
|
+
throw e;
|
|
31
|
+
return e;
|
|
32
|
+
}) ) )
|
|
25
33
|
.then( values => (values.some(e => e instanceof Error)
|
|
26
34
|
? Promise.reject( new PromiseAllError(
|
|
27
35
|
values, 'At least one promise has been rejected'
|
|
@@ -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
|
/**
|
|
@@ -21,7 +21,9 @@ function assertNoAssocUsageOutsideOfService( parent, prop, ref, path, grandparen
|
|
|
21
21
|
return;
|
|
22
22
|
|
|
23
23
|
const { _links } = parent;
|
|
24
|
-
|
|
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 )
|
|
25
27
|
return;
|
|
26
28
|
|
|
27
29
|
for (let i = 0; i < _links.length - 1; i++) {
|
|
@@ -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
|
};
|
|
@@ -647,6 +647,8 @@ function assertConsistency( model, stage ) {
|
|
|
647
647
|
'where', 'columns', 'mixin', 'quantifier', 'offset',
|
|
648
648
|
'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
|
|
649
649
|
'$limit', 'limit', '_status', '_origin',
|
|
650
|
+
// via casts
|
|
651
|
+
'enum',
|
|
650
652
|
],
|
|
651
653
|
},
|
|
652
654
|
_leadingQuery: { kind: true, test: TODO },
|
|
@@ -1059,7 +1061,7 @@ function assertConsistency( model, stage ) {
|
|
|
1059
1061
|
// TODO
|
|
1060
1062
|
// else if (spec.instanceOf && spec.instanceOf !== 'ignore' &&
|
|
1061
1063
|
// Object.getPrototypeOf( node ) !== spec.instanceOf.prototype)
|
|
1062
|
-
// eslint-disable-next-line @stylistic/
|
|
1064
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1063
1065
|
// throw new InternalConsistencyError( `Expected object of class ${ spec.instanceOf.name } but found ${ found }${ at( [ null, parent ], prop, name ) }` );
|
|
1064
1066
|
}
|
|
1065
1067
|
|
package/lib/compiler/base.js
CHANGED
|
@@ -21,7 +21,7 @@ const kindProperties = {
|
|
|
21
21
|
entity: {
|
|
22
22
|
elements: true, actions: true, params: () => false, include: true,
|
|
23
23
|
},
|
|
24
|
-
select: { normalized: 'select', elements:
|
|
24
|
+
select: { normalized: 'select', elements: propExists, enum: propExists },
|
|
25
25
|
$join: { normalized: 'select' },
|
|
26
26
|
$tableAlias: { normalized: 'alias' }, // table alias in select
|
|
27
27
|
$self: { normalized: 'alias' }, // table alias in select
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -222,7 +222,7 @@ const dateRegEx = /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/;
|
|
|
222
222
|
// YYYY - MM - dd
|
|
223
223
|
const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
|
|
224
224
|
// T HH : mm : ss TZD
|
|
225
|
-
// eslint-disable-next-line @stylistic/
|
|
225
|
+
// eslint-disable-next-line @stylistic/max-len, sonarjs/regex-complexity
|
|
226
226
|
const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
|
|
227
227
|
// YYYY - MM - dd T HH : mm : ss . fraction TZD
|
|
228
228
|
// eslint-disable-next-line sonarjs/regex-complexity
|
package/lib/compiler/checks.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
// * Different ad-hoc value/type checks (associations, enum, ...) -
|
|
6
6
|
// specify a proper one and use consistently
|
|
7
7
|
// * Using name comparisons instead proper object comparisons.
|
|
8
|
-
// * effectiveType issues.
|
|
9
8
|
// * Often forgot to consider CSN input
|
|
10
9
|
|
|
11
10
|
'use strict';
|
|
@@ -18,7 +17,7 @@ const {
|
|
|
18
17
|
isDeprecatedEnabled,
|
|
19
18
|
} = require('../base/model');
|
|
20
19
|
const { typeParameters } = require('./builtins');
|
|
21
|
-
const { propagationRules } = require('../base/builtins');
|
|
20
|
+
const { propagationRules, acceptsExprValues } = require('../base/builtins');
|
|
22
21
|
const { annotationVal } = require('./utils');
|
|
23
22
|
|
|
24
23
|
const $location = Symbol.for( 'cds.$location' );
|
|
@@ -84,7 +83,7 @@ function check( model ) {
|
|
|
84
83
|
checkName( art );
|
|
85
84
|
checkTypeArguments( art );
|
|
86
85
|
|
|
87
|
-
if (art.value && !art.$calcDepElement && art.type)
|
|
86
|
+
if (art.value && !art.$calcDepElement && (art.type || art.elements || art.items))
|
|
88
87
|
checkTypeCast( art.value, art );
|
|
89
88
|
|
|
90
89
|
for (const anno of iterateAnnotations( art ))
|
|
@@ -245,25 +244,43 @@ function check( model ) {
|
|
|
245
244
|
}
|
|
246
245
|
|
|
247
246
|
function checkTypeCast( xpr, user ) {
|
|
248
|
-
const
|
|
249
|
-
const elem =
|
|
247
|
+
const isSqlCast = (xpr.op?.val === 'cast');
|
|
248
|
+
const elem = isSqlCast
|
|
250
249
|
? xpr.args?.[0]?._artifact
|
|
251
250
|
: xpr._artifact;
|
|
252
|
-
|
|
253
|
-
|
|
251
|
+
|
|
252
|
+
const typeArt = isSqlCast ? xpr : user;
|
|
253
|
+
if (!elem || !isSqlCast && typeArt.type?.$inferred)
|
|
254
254
|
return; // e.g. $inferred:'generated'
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
|
|
256
|
+
const { type } = typeArt;
|
|
257
|
+
if (type) { // has explicit type
|
|
258
|
+
if (type._artifact?._effectiveType?.name.id === 'cds.Map') {
|
|
257
259
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'std', type: 'cds.Map' } );
|
|
258
|
-
|
|
260
|
+
}
|
|
261
|
+
else if (type._artifact?.elements) {
|
|
259
262
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'to-structure' } );
|
|
260
|
-
|
|
263
|
+
}
|
|
264
|
+
else if (elem.elements) { // TODO: calc elements
|
|
261
265
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'from-structure' } );
|
|
262
|
-
|
|
266
|
+
}
|
|
267
|
+
else if (elem.target && !type._artifact?.target) {
|
|
263
268
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'from-assoc' } );
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
269
|
+
}
|
|
270
|
+
else if (!elem.target && // referenced element is not association
|
|
271
|
+
!user.type?.$inferred && // $inferred types already reported in resolve.js.
|
|
272
|
+
(
|
|
273
|
+
// assoc used in SQL cast
|
|
274
|
+
type._artifact?.target && isSqlCast ||
|
|
275
|
+
// there is a target and the type is a direct `cds.Association`;
|
|
276
|
+
// other types handled by resolver already.
|
|
277
|
+
typeArt.target && type._artifact?.category === 'relation'
|
|
278
|
+
)
|
|
279
|
+
) {
|
|
280
|
+
// - redirection-check in resolve.js already checks this for CDL-casts
|
|
281
|
+
// - `"cast": { "target": "…", "type": "cds.Association", … }` via CSN input.
|
|
282
|
+
error('type-invalid-cast', [ type.location, user ], { '#': 'assoc' });
|
|
283
|
+
}
|
|
267
284
|
}
|
|
268
285
|
}
|
|
269
286
|
|
|
@@ -935,16 +952,16 @@ function check( model ) {
|
|
|
935
952
|
// One argument must be "$self" and the other an assoc
|
|
936
953
|
if (xpr.op.val === '=' && xpr.args.length === 2) {
|
|
937
954
|
// Tree-ish expression from the compiler (not augmented)
|
|
938
|
-
// eslint-disable-next-line @stylistic/
|
|
955
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
939
956
|
return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[1] ) ||
|
|
940
|
-
// eslint-disable-next-line @stylistic/
|
|
957
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
941
958
|
isAssociationOperand( xpr.args[1] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
|
|
942
959
|
}
|
|
943
960
|
else if (xpr.args.length === 3 && xpr.args[1].val === '=') {
|
|
944
961
|
// Tree-ish expression from the compiler (not augmented)
|
|
945
|
-
// eslint-disable-next-line @stylistic/
|
|
962
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
946
963
|
return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[2] ) ||
|
|
947
|
-
// eslint-disable-next-line @stylistic/
|
|
964
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
948
965
|
isAssociationOperand( xpr.args[2] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
|
|
949
966
|
}
|
|
950
967
|
|
|
@@ -963,7 +980,7 @@ function check( model ) {
|
|
|
963
980
|
const name = anno.name?.id;
|
|
964
981
|
if (!name)
|
|
965
982
|
return true;
|
|
966
|
-
if (!propagationRules[`@${ name }`])
|
|
983
|
+
if (!propagationRules[`@${ name }`] || acceptsExprValues[`@${ name }`])
|
|
967
984
|
return true;
|
|
968
985
|
error( 'anno-unexpected-expr', [ anno.location, art ], { anno: name },
|
|
969
986
|
'Unexpected expression as value for $(ANNO)' );
|
|
@@ -1165,7 +1182,7 @@ function check( model ) {
|
|
|
1165
1182
|
value.literal !== 'timestamp' && value.literal !== 'string') {
|
|
1166
1183
|
// Hm, actually date and time cannot be mixed
|
|
1167
1184
|
warning( null, loc, { type, anno },
|
|
1168
|
-
// eslint-disable-next-line @stylistic/
|
|
1185
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1169
1186
|
'A date/time value or a string is required for type $(TYPE) for annotation $(ANNO)' );
|
|
1170
1187
|
}
|
|
1171
1188
|
}
|
package/lib/compiler/define.js
CHANGED
|
@@ -1010,6 +1010,7 @@ function define( model ) {
|
|
|
1010
1010
|
}
|
|
1011
1011
|
// Either expression (value), expand, new virtual or new association
|
|
1012
1012
|
else if (col.value || col.name) {
|
|
1013
|
+
col.kind = 'element';
|
|
1013
1014
|
if (!col._block)
|
|
1014
1015
|
setLink( col, '_block', parent._block );
|
|
1015
1016
|
if (col.inline) { // `@anno elem.{ * }` does not work
|
|
@@ -1031,8 +1032,7 @@ function define( model ) {
|
|
|
1031
1032
|
initSelectItems( col, null, user ); // TODO: use col as user (i.e. remove param)
|
|
1032
1033
|
}
|
|
1033
1034
|
|
|
1034
|
-
|
|
1035
|
-
initExprAnnoBlock( col, parent._block );
|
|
1035
|
+
initCdlTypeCast( col, parent );
|
|
1036
1036
|
}
|
|
1037
1037
|
|
|
1038
1038
|
if (hasItems && !wildcard && parent.excludingDict && !options.$recompile) {
|
|
@@ -1044,6 +1044,26 @@ function define( model ) {
|
|
|
1044
1044
|
}
|
|
1045
1045
|
}
|
|
1046
1046
|
|
|
1047
|
+
function initCdlTypeCast( col, parent ) {
|
|
1048
|
+
if (col.val)
|
|
1049
|
+
return; // e.g. '*' column
|
|
1050
|
+
|
|
1051
|
+
setMemberParent( col, col.name, parent );
|
|
1052
|
+
initMembers( col, col, col._block );
|
|
1053
|
+
|
|
1054
|
+
// We don't allow CDL-style casts to anonymous structures. We reject it already here
|
|
1055
|
+
// and not in checks.js to ensure that it's rejected in parseCdl.
|
|
1056
|
+
if (col.elements) {
|
|
1057
|
+
error('type-invalid-cast', [ col.elements[$location] ?? col.location, col ],
|
|
1058
|
+
{ '#': 'to-inline-structure' });
|
|
1059
|
+
}
|
|
1060
|
+
else if (col.expand && (col.type || col.elements || col.items)) {
|
|
1061
|
+
const loc = (col.type?.location || col.elements?.[$location] ||
|
|
1062
|
+
col.items?.location || col.location);
|
|
1063
|
+
error('type-invalid-cast', [ loc, col ], { '#': 'expand' });
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1047
1067
|
/**
|
|
1048
1068
|
* If we have a valid top-level exists, exists in filters of sub-expressions can be translated,
|
|
1049
1069
|
* since we will have a top-level subquery after exists-processing in the forRelationalDB.
|
|
@@ -1150,7 +1170,7 @@ function define( model ) {
|
|
|
1150
1170
|
// We do not want to complain separately about all element properties:
|
|
1151
1171
|
error( 'ext-unexpected-element', [ e.location, construct ],
|
|
1152
1172
|
{ name: e.name.id, code: 'extend … with enum' },
|
|
1153
|
-
// eslint-disable-next-line @stylistic/
|
|
1173
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1154
1174
|
'Unexpected elements like $(NAME) in an extension for an enum. Additionally, use $(CODE) when extending enums' );
|
|
1155
1175
|
// Don't emit 'ext-expecting-enum' if this error is emitted.
|
|
1156
1176
|
return;
|
|
@@ -1238,7 +1258,6 @@ function define( model ) {
|
|
|
1238
1258
|
if (elem.$duplicates === true && add)
|
|
1239
1259
|
elem.$duplicates = null;
|
|
1240
1260
|
setMemberParent( elem, name, parent, add && prop );
|
|
1241
|
-
// console.log(message( null, elem.location, elem, {}, 'Info', 'INIT').toString())
|
|
1242
1261
|
checkRedefinition( elem );
|
|
1243
1262
|
initMembers( elem, elem, bl, initExtensions );
|
|
1244
1263
|
if (boundSelfParamType && (elem.kind === 'action' || elem.kind === 'function'))
|
|
@@ -1320,7 +1339,7 @@ function define( model ) {
|
|
|
1320
1339
|
// - artifacts (CDL-only anyway) only inside [extend] context|service
|
|
1321
1340
|
if (!dict)
|
|
1322
1341
|
return false;
|
|
1323
|
-
const feature = kindProperties[parent.kind][prop];
|
|
1342
|
+
const feature = kindProperties[parent.kind ?? 'element'][prop];
|
|
1324
1343
|
if (feature &&
|
|
1325
1344
|
(feature === true || construct.kind !== 'extend' || feature( prop, parent )))
|
|
1326
1345
|
return true;
|
package/lib/compiler/extend.js
CHANGED
|
@@ -561,7 +561,7 @@ function extend( model ) {
|
|
|
561
561
|
{
|
|
562
562
|
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
563
563
|
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
564
|
-
// eslint-disable-next-line @stylistic/
|
|
564
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
565
565
|
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
566
566
|
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
567
567
|
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
@@ -117,7 +117,15 @@ function finalizeParseCdl( model ) {
|
|
|
117
117
|
// containing it. Otherwise some type's aren't properly resolved.
|
|
118
118
|
// TODO: If resolveTypeUnchecked is reworked, we may be able to simplify this coding.
|
|
119
119
|
(artifact.$queries || []).forEach( art => resolveTypesForParseCdl( art, artifact ) );
|
|
120
|
-
(artifact.columns || []).forEach(
|
|
120
|
+
(artifact.columns || []).forEach( (col) => {
|
|
121
|
+
// TODO: Can we use "ensureColumnName" of populate.js? It depends on column indices
|
|
122
|
+
// _after_ wildcards were expanded, though.
|
|
123
|
+
if (!col.name && col.value?.path) {
|
|
124
|
+
const last = col.value.path.at(-1);
|
|
125
|
+
col.name = { id: last?.id || '', location: last?.location, $inferred: 'as' };
|
|
126
|
+
}
|
|
127
|
+
resolveTypesForParseCdl( col, artifact );
|
|
128
|
+
} );
|
|
121
129
|
forEachGeneric( artifact, 'mixin', art => resolveTypesForParseCdl( art, artifact ) );
|
|
122
130
|
|
|
123
131
|
// For better error messages for `type of`s in `returns`, we pass the object as the new main.
|