@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { isPersistedOnDatabase,
|
|
3
|
+
const { isPersistedOnDatabase, applyTransformationsOnNonDictionary } = require('../model/csnUtils');
|
|
4
4
|
const { isBuiltinType } = require('../base/builtins');
|
|
5
5
|
const { requireForeignKeyAccess } = require('./onConditions');
|
|
6
6
|
const { pathId } = require('../model/csnRefs');
|
|
@@ -26,8 +26,13 @@ function checkQueryForNoDBArtifacts( query ) {
|
|
|
26
26
|
for (const prop of generalQueryProperties) {
|
|
27
27
|
const queryPart = (query.SELECT || query.SET)[prop];
|
|
28
28
|
if (Array.isArray(queryPart)) {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const that = this;
|
|
30
|
+
applyTransformationsOnNonDictionary((query.SELECT || query.SET), prop, {
|
|
31
|
+
ref: (parent, _name, val, csnPath, tokenStream, refIndex) => {
|
|
32
|
+
const danglingAssocAllowed = tokenStream[refIndex - 1] !== 'exists';
|
|
33
|
+
checkQueryRef.call(that, parent, danglingAssocAllowed);
|
|
34
|
+
},
|
|
35
|
+
}, { skipStandard: { on: true } });
|
|
31
36
|
}
|
|
32
37
|
else if (typeof queryPart === 'object') {
|
|
33
38
|
checkQueryRef.call(this, queryPart, prop === 'columns');
|
|
@@ -119,9 +124,10 @@ function _checkExpandInline( obj, previousRefs = [], previousLinks = [] ) {
|
|
|
119
124
|
* @param {CSN.Path} ref
|
|
120
125
|
* @param {object[]} _links
|
|
121
126
|
* @param {CSN.Path} $path
|
|
122
|
-
* @param {boolean}
|
|
127
|
+
* @param {boolean} danglingAssocAllowed usually optimised to foreign key hence allowed even if target is skipped,
|
|
128
|
+
* except in from or after exists
|
|
123
129
|
*/
|
|
124
|
-
function _checkRef( ref, _links, $path,
|
|
130
|
+
function _checkRef( ref, _links, $path, danglingAssocAllowed ) {
|
|
125
131
|
if (!ref || !_links )
|
|
126
132
|
return;
|
|
127
133
|
|
|
@@ -129,7 +135,7 @@ function _checkRef( ref, _links, $path, inColumns ) {
|
|
|
129
135
|
const isPublishedAssoc = this.csnUtils.isAssocOrComposition(_links[_links.length - 1].art);
|
|
130
136
|
|
|
131
137
|
// Don't check the last element - to allow association publishing in columns
|
|
132
|
-
for (let i = 0; i < (
|
|
138
|
+
for (let i = 0; i < (danglingAssocAllowed ? _links.length - 1 : _links.length); i++) {
|
|
133
139
|
const link = _links[i];
|
|
134
140
|
if (!link)
|
|
135
141
|
continue;
|
|
@@ -153,8 +159,8 @@ function _checkRef( ref, _links, $path, inColumns ) {
|
|
|
153
159
|
if (nonPersistedTarget) {
|
|
154
160
|
let isJoinRelevant = isPublishedAssoc || // publishing associations is always join relevant
|
|
155
161
|
isLast || // e.g. FROM targets are always join relevant.
|
|
156
|
-
isUnmanagedOrNoKeys
|
|
157
|
-
|
|
162
|
+
isUnmanagedOrNoKeys || // unmanaged associations are always join relevant -> no FKs
|
|
163
|
+
ref.slice(i).some(s => s.where || s.args); // function calls or filters are always join relevant
|
|
158
164
|
if (!isJoinRelevant) {
|
|
159
165
|
// for managed, published associations with more than one $path-step, only FK
|
|
160
166
|
// access is allowed.
|
|
@@ -164,17 +170,12 @@ function _checkRef( ref, _links, $path, inColumns ) {
|
|
|
164
170
|
}
|
|
165
171
|
|
|
166
172
|
if (isJoinRelevant) {
|
|
167
|
-
|
|
168
|
-
this.error( null, $path, {
|
|
169
|
-
'#': cdsPersistenceSkipped ? 'std' : 'abstract',
|
|
173
|
+
this.error('ref-invalid-assoc-navigation', $path, {
|
|
170
174
|
anno: '@cds.persistence.skip',
|
|
171
175
|
id: nonPersistedTarget.pathStep,
|
|
172
176
|
elemref: { ref },
|
|
173
177
|
name: nonPersistedTarget.name,
|
|
174
|
-
}
|
|
175
|
-
std: 'Unexpected $(ANNO) annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
|
|
176
|
-
abstract: 'Unexpected abstract association target $(NAME) of $(ID) in path $(ELEMREF)',
|
|
177
|
-
} );
|
|
178
|
+
});
|
|
178
179
|
break; // only one error per path
|
|
179
180
|
}
|
|
180
181
|
}
|
package/lib/checks/types.js
CHANGED
|
@@ -152,7 +152,7 @@ function checkTypeOfHasProperType( artOrElement, name, model, error, path, deriv
|
|
|
152
152
|
|
|
153
153
|
|
|
154
154
|
/**
|
|
155
|
-
* Can happen in CSN, e.g. `{ a: { kind: "type" } }`
|
|
155
|
+
* Can happen in CSN, e.g. `{ a: { kind: "type" } }` or via `elem;` in CDL.
|
|
156
156
|
*
|
|
157
157
|
* @param {Function} error the error function
|
|
158
158
|
* @param {CSN.Path} path the path to the element or the artifact
|
package/lib/checks/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { isBuiltinType } = require('../base/builtins');
|
|
4
|
-
const { RelationalOperators } = require('../transform/
|
|
4
|
+
const { RelationalOperators } = require('../transform/tupleExpansion');
|
|
5
5
|
/**
|
|
6
6
|
* Prepare the ref steps so that they are loggable
|
|
7
7
|
*
|
|
@@ -17,7 +17,9 @@ function logReady( refStep ) {
|
|
|
17
17
|
* structured that can be used for tuple expansion. This can either be a
|
|
18
18
|
* real 'elements' thing or a managed association/composition with foreign keys.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
20
|
+
* @TODO: This function also allows `is null` on the right-hand-side.
|
|
21
|
+
* We should move these checks to the actual tuple expansion, because
|
|
22
|
+
* if we're missing cases here, it currently results in incorrect expansion.
|
|
21
23
|
*
|
|
22
24
|
* @param {Array} on the on condition which to check
|
|
23
25
|
* @param {number} startIndex the index of the relational term in the on condition array
|
|
@@ -26,8 +28,7 @@ function logReady( refStep ) {
|
|
|
26
28
|
function otherSideIsExpandableStructure( on, startIndex ) {
|
|
27
29
|
if (on[startIndex - 1] && RelationalOperators.includes(on[startIndex - 1])) {
|
|
28
30
|
const lhs = on[startIndex - 2];
|
|
29
|
-
|
|
30
|
-
return /* lhs?.val !== undefined || */ isOk(resolveArtifactType.call(this, lhs?._art));
|
|
31
|
+
return isOk(resolveArtifactType.call(this, lhs?._art));
|
|
31
32
|
}
|
|
32
33
|
else if (on[startIndex + 1] && RelationalOperators.includes(on[startIndex + 1])) {
|
|
33
34
|
const op = on[startIndex + 1];
|
|
@@ -35,8 +36,7 @@ function otherSideIsExpandableStructure( on, startIndex ) {
|
|
|
35
36
|
if (op === 'is')
|
|
36
37
|
// check for unary operator 'is [not] null' as token stream
|
|
37
38
|
return rhs === 'null' || (rhs === 'not' && on[startIndex + 3] === 'null');
|
|
38
|
-
|
|
39
|
-
return /* rhs?.val !== undefined || */ isOk(resolveArtifactType.call(this, rhs?._art));
|
|
39
|
+
return isOk(resolveArtifactType.call(this, rhs?._art));
|
|
40
40
|
}
|
|
41
41
|
return false;
|
|
42
42
|
|
|
@@ -51,6 +51,29 @@ function otherSideIsExpandableStructure( on, startIndex ) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Check that the opposite operand to a relational term is s value or "is null".
|
|
56
|
+
*
|
|
57
|
+
* @param {Array} expr the expression which to check
|
|
58
|
+
* @param {number} startIndex the index of the relational term in the expression array
|
|
59
|
+
* @returns {boolean} indicates whether the other side of a relational term is scalar
|
|
60
|
+
*/
|
|
61
|
+
function otherSideIsValue( expr, startIndex ) {
|
|
62
|
+
if (expr[startIndex - 1] && RelationalOperators.includes(expr[startIndex - 1]))
|
|
63
|
+
return expr[startIndex - 2]?.val !== undefined;
|
|
64
|
+
|
|
65
|
+
if (expr[startIndex + 1] && RelationalOperators.includes(expr[startIndex + 1])) {
|
|
66
|
+
const op = expr[startIndex + 1];
|
|
67
|
+
const rhs = expr[startIndex + 2];
|
|
68
|
+
if (op === 'is')
|
|
69
|
+
// check for unary operator 'is [not] null' as token stream
|
|
70
|
+
return rhs === 'null' || (rhs === 'not' && expr[startIndex + 3] === 'null');
|
|
71
|
+
return rhs?.val !== undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
/**
|
|
55
78
|
* Get the real type of an artifact
|
|
56
79
|
*
|
|
@@ -68,5 +91,6 @@ function resolveArtifactType( art ) {
|
|
|
68
91
|
module.exports = {
|
|
69
92
|
logReady,
|
|
70
93
|
otherSideIsExpandableStructure,
|
|
94
|
+
otherSideIsValue,
|
|
71
95
|
resolveArtifactType,
|
|
72
96
|
};
|
package/lib/checks/validator.js
CHANGED
|
@@ -42,7 +42,6 @@ const checkForInvalidTarget = require('./invalidTarget');
|
|
|
42
42
|
const { validateAssociationsInItems } = require('./arrayOfs');
|
|
43
43
|
const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
|
|
44
44
|
const checkExplicitlyNullableKeys = require('./nullableKeys');
|
|
45
|
-
const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
|
|
46
45
|
const existsMustEndInAssoc = require('./existsMustEndInAssoc');
|
|
47
46
|
const forbidAssocInExists = require('./existsExpressionsOnlyForeignKeys');
|
|
48
47
|
const checkPathsInStoredCalcElement = require('./checkPathsInStoredCalcElement');
|
|
@@ -87,7 +86,6 @@ const forRelationalDBCsnValidators = [
|
|
|
87
86
|
checkCdsMap,
|
|
88
87
|
existsMustEndInAssoc,
|
|
89
88
|
forbidAssocInExists,
|
|
90
|
-
nonexpandableStructuredInExpression,
|
|
91
89
|
navigationIntoMany,
|
|
92
90
|
checkPathsInStoredCalcElement,
|
|
93
91
|
featureFlags,
|
|
@@ -124,7 +122,7 @@ const forOdataArtifactValidators
|
|
|
124
122
|
checkReadOnlyAndInsertOnly,
|
|
125
123
|
];
|
|
126
124
|
|
|
127
|
-
const forOdataCsnValidators = [ checkCdsMap
|
|
125
|
+
const forOdataCsnValidators = [ checkCdsMap ];
|
|
128
126
|
|
|
129
127
|
const forOdataQueryValidators = [];
|
|
130
128
|
|
|
@@ -202,8 +200,6 @@ function getDBCsnValidators( options ) {
|
|
|
202
200
|
validations.push(checkForParams.csnValidator);
|
|
203
201
|
if (options.sqlDialect === 'h2' || options.sqlDialect === 'postgres')
|
|
204
202
|
validations.push(checkForHanaTypes);
|
|
205
|
-
if (options.transformation === 'effective' && options.effectiveServiceName)
|
|
206
|
-
validations.push(assertNoAssocUsageOutsideOfService);
|
|
207
203
|
|
|
208
204
|
return validations;
|
|
209
205
|
}
|
|
@@ -232,6 +228,9 @@ function forRelationalDB( csn, that ) {
|
|
|
232
228
|
},
|
|
233
229
|
(artifact, artifactName) => {
|
|
234
230
|
if (that.options.transformation === 'effective') {
|
|
231
|
+
if (that.options.effectiveServiceName)
|
|
232
|
+
assertNoAssocUsageOutsideOfService.bind(that)(artifact, artifactName);
|
|
233
|
+
|
|
235
234
|
forEachMemberRecursively(artifact, checkAnnotationExpression.bind(that), [ 'definitions', artifactName ], false, {
|
|
236
235
|
skipArtifact: a => a.returns || (a.params && !a.query),
|
|
237
236
|
});
|
|
@@ -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 ))
|
|
@@ -117,13 +116,21 @@ function check( model ) {
|
|
|
117
116
|
return;
|
|
118
117
|
|
|
119
118
|
const isVirtual = parentProps.virtual?.val || elem.virtual?.val;
|
|
119
|
+
const typeName = elem._effectiveType?.name?.id;
|
|
120
120
|
if (isVirtual) {
|
|
121
121
|
error( 'def-unexpected-key', [ (parentProps.key || elem.key).location, elem ],
|
|
122
|
-
{ '#': 'virtual',
|
|
122
|
+
{ '#': 'virtual', keyword: 'key' } );
|
|
123
123
|
}
|
|
124
|
-
else if (
|
|
124
|
+
else if (typeName === 'cds.Map') {
|
|
125
125
|
error( 'def-unexpected-key', [ elem.type?.location || elem.location, elem ],
|
|
126
|
-
{ '#': 'invalidType',
|
|
126
|
+
{ '#': 'invalidType', keyword: 'key', type: typeName } );
|
|
127
|
+
}
|
|
128
|
+
else if (typeName === 'cds.LargeString' ||
|
|
129
|
+
typeName === 'cds.Vector' ||
|
|
130
|
+
typeName === 'cds.hana.CLOB' ||
|
|
131
|
+
typeName === 'cds.LargeBinary') {
|
|
132
|
+
warning( 'def-unsupported-key', [ elem.type?.location || elem.location, elem ],
|
|
133
|
+
{ '#': 'type', keyword: 'key', type: typeName } );
|
|
127
134
|
}
|
|
128
135
|
}
|
|
129
136
|
|
|
@@ -245,25 +252,43 @@ function check( model ) {
|
|
|
245
252
|
}
|
|
246
253
|
|
|
247
254
|
function checkTypeCast( xpr, user ) {
|
|
248
|
-
const
|
|
249
|
-
const elem =
|
|
255
|
+
const isSqlCast = (xpr.op?.val === 'cast');
|
|
256
|
+
const elem = isSqlCast
|
|
250
257
|
? xpr.args?.[0]?._artifact
|
|
251
258
|
: xpr._artifact;
|
|
252
|
-
|
|
253
|
-
|
|
259
|
+
|
|
260
|
+
const typeArt = isSqlCast ? xpr : user;
|
|
261
|
+
if (!elem || !isSqlCast && typeArt.type?.$inferred)
|
|
254
262
|
return; // e.g. $inferred:'generated'
|
|
255
|
-
|
|
256
|
-
|
|
263
|
+
|
|
264
|
+
const { type } = typeArt;
|
|
265
|
+
if (type) { // has explicit type
|
|
266
|
+
if (type._artifact?._effectiveType?.name.id === 'cds.Map') {
|
|
257
267
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'std', type: 'cds.Map' } );
|
|
258
|
-
|
|
268
|
+
}
|
|
269
|
+
else if (type._artifact?.elements) {
|
|
259
270
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'to-structure' } );
|
|
260
|
-
|
|
271
|
+
}
|
|
272
|
+
else if (elem.elements) { // TODO: calc elements
|
|
261
273
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'from-structure' } );
|
|
262
|
-
|
|
274
|
+
}
|
|
275
|
+
else if (elem.target && !type._artifact?.target) {
|
|
263
276
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'from-assoc' } );
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
277
|
+
}
|
|
278
|
+
else if (!elem.target && // referenced element is not association
|
|
279
|
+
!user.type?.$inferred && // $inferred types already reported in resolve.js.
|
|
280
|
+
(
|
|
281
|
+
// assoc used in SQL cast
|
|
282
|
+
type._artifact?.target && isSqlCast ||
|
|
283
|
+
// there is a target and the type is a direct `cds.Association`;
|
|
284
|
+
// other types handled by resolver already.
|
|
285
|
+
typeArt.target && type._artifact?.category === 'relation'
|
|
286
|
+
)
|
|
287
|
+
) {
|
|
288
|
+
// - redirection-check in resolve.js already checks this for CDL-casts
|
|
289
|
+
// - `"cast": { "target": "…", "type": "cds.Association", … }` via CSN input.
|
|
290
|
+
error('type-invalid-cast', [ type.location, user ], { '#': 'assoc' });
|
|
291
|
+
}
|
|
267
292
|
}
|
|
268
293
|
}
|
|
269
294
|
|
|
@@ -687,12 +712,15 @@ function check( model ) {
|
|
|
687
712
|
*
|
|
688
713
|
* @param {any} xpr The expression to check
|
|
689
714
|
* @param {XSN.Artifact} user User for semantic location
|
|
715
|
+
* @param {any} _parentExpr
|
|
690
716
|
* @param {string} [context] where the expression is used, e.g. 'anno'
|
|
691
717
|
*/
|
|
692
|
-
function checkGenericExpression( xpr, user, context ) {
|
|
718
|
+
function checkGenericExpression( xpr, user, _parentExpr, context ) {
|
|
693
719
|
if (context !== 'anno')
|
|
694
720
|
checkExpressionNotVirtual( xpr, user );
|
|
695
|
-
checkExpressionAssociationUsage( xpr, user,
|
|
721
|
+
checkExpressionAssociationUsage( xpr, user, {
|
|
722
|
+
context, rejectManaged: context === 'anno', rejectUnmanaged: true,
|
|
723
|
+
} );
|
|
696
724
|
if (xpr.op?.val === 'cast') {
|
|
697
725
|
requireExplicitTypeInSqlCast( xpr, user );
|
|
698
726
|
checkTypeCast( xpr, user );
|
|
@@ -713,7 +741,7 @@ function check( model ) {
|
|
|
713
741
|
|
|
714
742
|
visitExpression( elem.on, elem, (xpr, user) => {
|
|
715
743
|
checkExpressionNotVirtual( xpr, user );
|
|
716
|
-
checkExpressionAssociationUsage( xpr, user,
|
|
744
|
+
checkExpressionAssociationUsage( xpr, user, null );
|
|
717
745
|
|
|
718
746
|
if (xpr._artifact?._effectiveType?.name.id === 'cds.Map') {
|
|
719
747
|
error( 'ref-unexpected-map', [ xpr.location, user ], { '#': 'onCond', type: 'cds.Map' } );
|
|
@@ -727,7 +755,9 @@ function check( model ) {
|
|
|
727
755
|
}
|
|
728
756
|
|
|
729
757
|
function checkSelectItemValue( elem ) {
|
|
730
|
-
checkExpressionAssociationUsage( elem.value, elem,
|
|
758
|
+
checkExpressionAssociationUsage( elem.value, elem, {
|
|
759
|
+
context: 'query', rejectManaged: false, rejectUnmanaged: true,
|
|
760
|
+
} );
|
|
731
761
|
checkVirtualSelectItemChangeForV6( elem );
|
|
732
762
|
// To avoid duplicate messages, only run this check if the type wasn't inferred from
|
|
733
763
|
// the cast, as otherwise we will check it twice (once here, once via element).
|
|
@@ -736,8 +766,8 @@ function check( model ) {
|
|
|
736
766
|
checkTypeCast( elem.value, elem );
|
|
737
767
|
checkTypeArguments( elem.value, elem );
|
|
738
768
|
}
|
|
739
|
-
visitSubExpression( elem.value, elem, (xpr) => {
|
|
740
|
-
checkGenericExpression( xpr, elem );
|
|
769
|
+
visitSubExpression( elem.value, elem, (xpr, user, parentExpr) => {
|
|
770
|
+
checkGenericExpression( xpr, elem, parentExpr, 'query' );
|
|
741
771
|
} );
|
|
742
772
|
}
|
|
743
773
|
|
|
@@ -794,7 +824,8 @@ function check( model ) {
|
|
|
794
824
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
795
825
|
// And users can't change structured to non-structured elements.
|
|
796
826
|
if (!elem.$inferred && xpr._artifact._effectiveType?.elements) {
|
|
797
|
-
error( 'ref-unexpected-structured', [ sourceLoc, elem ],
|
|
827
|
+
error( 'ref-unexpected-structured', [ sourceLoc, elem ],
|
|
828
|
+
{ '#': 'struct-expr', elemref: xpr } );
|
|
798
829
|
}
|
|
799
830
|
else if (xpr._artifact.target !== undefined && (!lastStep.where || isStored)) {
|
|
800
831
|
// Allow using an association _with filter_, but only for on-read calculated elements.
|
|
@@ -867,10 +898,11 @@ function check( model ) {
|
|
|
867
898
|
*
|
|
868
899
|
* @param {any} xpr The expression to check
|
|
869
900
|
* @param {XSN.Artifact} user
|
|
870
|
-
* @param {
|
|
901
|
+
* @param {{context: string, rejectUnmanaged, rejectManaged}|null} [rejectAssocTail]
|
|
902
|
+
* Context where association tails are not allowed.
|
|
871
903
|
* @returns {void}
|
|
872
904
|
*/
|
|
873
|
-
function checkExpressionAssociationUsage( xpr, user,
|
|
905
|
+
function checkExpressionAssociationUsage( xpr, user, rejectAssocTail = null ) {
|
|
874
906
|
if (!xpr.args)
|
|
875
907
|
return;
|
|
876
908
|
|
|
@@ -885,12 +917,18 @@ function check( model ) {
|
|
|
885
917
|
const op = getBinaryOp( xpr );
|
|
886
918
|
for (const arg of args) {
|
|
887
919
|
if (arg && !(op?.val !== '=' && isDollarSelfOrProjectionOperand( arg )))
|
|
888
|
-
checkExpressionIsNotAssocOrSelf( arg, user,
|
|
920
|
+
checkExpressionIsNotAssocOrSelf( arg, user, rejectAssocTail );
|
|
889
921
|
}
|
|
890
922
|
}
|
|
891
923
|
}
|
|
892
924
|
|
|
893
|
-
|
|
925
|
+
/**
|
|
926
|
+
* @param arg
|
|
927
|
+
* @param {XSN.Artifact} user
|
|
928
|
+
* @param {{context: string, rejectUnmanaged, rejectManaged}|null} [rejectAssocTail]
|
|
929
|
+
* Context where association tails are not allowed.
|
|
930
|
+
*/
|
|
931
|
+
function checkExpressionIsNotAssocOrSelf( arg, user, rejectAssocTail ) {
|
|
894
932
|
// Arg must not be an association and not $self
|
|
895
933
|
// Only if path is not approved exists path (that is non-query position)
|
|
896
934
|
if (arg.path && arg.$expected !== undefined) { // not 'approved-exists'
|
|
@@ -899,9 +937,17 @@ function check( model ) {
|
|
|
899
937
|
error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': variant } );
|
|
900
938
|
}
|
|
901
939
|
}
|
|
902
|
-
else if (
|
|
903
|
-
|
|
904
|
-
|
|
940
|
+
else if (rejectAssocTail && isAssociationOperand( arg )) {
|
|
941
|
+
if (rejectAssocTail.rejectManaged && rejectAssocTail.rejectUnmanaged ||
|
|
942
|
+
rejectAssocTail.rejectManaged && arg._artifact.keys ||
|
|
943
|
+
rejectAssocTail.rejectUnmanaged && arg._artifact.on) {
|
|
944
|
+
// only a few contexts have special message
|
|
945
|
+
const context = rejectAssocTail.context === 'query' && 'query-' ||
|
|
946
|
+
rejectAssocTail.context === 'anno' && 'anno-' ||
|
|
947
|
+
'';
|
|
948
|
+
const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
|
|
949
|
+
error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': `${ context }${ variant }` } );
|
|
950
|
+
}
|
|
905
951
|
}
|
|
906
952
|
}
|
|
907
953
|
|
|
@@ -935,16 +981,16 @@ function check( model ) {
|
|
|
935
981
|
// One argument must be "$self" and the other an assoc
|
|
936
982
|
if (xpr.op.val === '=' && xpr.args.length === 2) {
|
|
937
983
|
// Tree-ish expression from the compiler (not augmented)
|
|
938
|
-
// eslint-disable-next-line @stylistic/
|
|
984
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
939
985
|
return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[1] ) ||
|
|
940
|
-
// eslint-disable-next-line @stylistic/
|
|
986
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
941
987
|
isAssociationOperand( xpr.args[1] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
|
|
942
988
|
}
|
|
943
989
|
else if (xpr.args.length === 3 && xpr.args[1].val === '=') {
|
|
944
990
|
// Tree-ish expression from the compiler (not augmented)
|
|
945
|
-
// eslint-disable-next-line @stylistic/
|
|
991
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
946
992
|
return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[2] ) ||
|
|
947
|
-
// eslint-disable-next-line @stylistic/
|
|
993
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
948
994
|
isAssociationOperand( xpr.args[2] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
|
|
949
995
|
}
|
|
950
996
|
|
|
@@ -963,7 +1009,7 @@ function check( model ) {
|
|
|
963
1009
|
const name = anno.name?.id;
|
|
964
1010
|
if (!name)
|
|
965
1011
|
return true;
|
|
966
|
-
if (!propagationRules[`@${ name }`])
|
|
1012
|
+
if (!propagationRules[`@${ name }`] || acceptsExprValues[`@${ name }`])
|
|
967
1013
|
return true;
|
|
968
1014
|
error( 'anno-unexpected-expr', [ anno.location, art ], { anno: name },
|
|
969
1015
|
'Unexpected expression as value for $(ANNO)' );
|
|
@@ -1088,7 +1134,7 @@ function check( model ) {
|
|
|
1088
1134
|
*/
|
|
1089
1135
|
function checkAnnotationExpressions( anno, art ) {
|
|
1090
1136
|
if (anno.$tokenTexts) {
|
|
1091
|
-
checkGenericExpression( anno, art, 'anno' );
|
|
1137
|
+
checkGenericExpression( anno, art, null, 'anno' );
|
|
1092
1138
|
}
|
|
1093
1139
|
else if (anno.literal === 'array') {
|
|
1094
1140
|
anno.val.forEach( val => checkAnnotationExpressions( val, art ) );
|
|
@@ -1165,7 +1211,7 @@ function check( model ) {
|
|
|
1165
1211
|
value.literal !== 'timestamp' && value.literal !== 'string') {
|
|
1166
1212
|
// Hm, actually date and time cannot be mixed
|
|
1167
1213
|
warning( null, loc, { type, anno },
|
|
1168
|
-
// eslint-disable-next-line @stylistic/
|
|
1214
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1169
1215
|
'A date/time value or a string is required for type $(TYPE) for annotation $(ANNO)' );
|
|
1170
1216
|
}
|
|
1171
1217
|
}
|
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.
|
package/lib/compiler/generate.js
CHANGED
|
@@ -104,7 +104,7 @@ function generate( model ) {
|
|
|
104
104
|
const lang = textsAspect.elements.language;
|
|
105
105
|
error( 'def-unexpected-element', [ lang.name.location, lang ],
|
|
106
106
|
{ option: 'addTextsLanguageAssoc', art: textsAspect, name: 'language' },
|
|
107
|
-
// eslint-disable-next-line @stylistic/
|
|
107
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
108
108
|
'$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
|
|
109
109
|
hasError = true;
|
|
110
110
|
}
|
|
@@ -209,7 +209,7 @@ function generate( model ) {
|
|
|
209
209
|
conflictingElements.push( elem );
|
|
210
210
|
|
|
211
211
|
const isKey = elem.key && elem.key.val;
|
|
212
|
-
const isLocalized = hasTruthyProp( elem, 'localized' );
|
|
212
|
+
const isLocalized = elem.$syntax !== 'calc' && hasTruthyProp( elem, 'localized' );
|
|
213
213
|
|
|
214
214
|
if (isKey) {
|
|
215
215
|
keys += 1;
|
|
@@ -245,7 +245,7 @@ function generate( model ) {
|
|
|
245
245
|
(fioriEnabled && art.elements.ID_texts)) {
|
|
246
246
|
// TODO if we have too much time: check all elements of texts entity for safety
|
|
247
247
|
warning( null, [ art.name.location, art ], { art: textsEntity },
|
|
248
|
-
// eslint-disable-next-line @stylistic/
|
|
248
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
249
249
|
'Texts entity $(ART) can\'t be created as there is another definition with that name' );
|
|
250
250
|
info( null, [ textsEntity.name.location, textsEntity ], { art },
|
|
251
251
|
'Texts entity for $(ART) can\'t be created with this definition' );
|
|
@@ -640,7 +640,7 @@ function generate( model ) {
|
|
|
640
640
|
}
|
|
641
641
|
if (model.definitions[entityName]) {
|
|
642
642
|
error( null, [ location, elem ], { art: entityName },
|
|
643
|
-
// eslint-disable-next-line @stylistic/
|
|
643
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
644
644
|
'Target entity $(ART) can\'t be created as there is another definition with this name' );
|
|
645
645
|
return false;
|
|
646
646
|
}
|