@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
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { setProp } = require('../base/model');
|
|
4
|
+
const { applyTransformations, isDollarSelfOrProjectionOperand, implicitAs } = require('../model/csnUtils');
|
|
5
|
+
const { isBuiltinType } = require('../base/builtins');
|
|
6
|
+
const { condAsTree } = require('../model/xprAsTree');
|
|
7
|
+
const { pathToMessageString } = require('../base/messages');
|
|
8
|
+
const { pathId } = require('../model/csnRefs');
|
|
9
|
+
const { cloneCsnNonDict } = require('../model/cloneCsn');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* All relational operators supported by tuple expansion.
|
|
13
|
+
* Also includes `is not`, which is actually two tokens.
|
|
14
|
+
*
|
|
15
|
+
* @type {string[]}
|
|
16
|
+
*/
|
|
17
|
+
const RelationalOperators = [ '=', '<>', '==', '!=', 'is', 'is not' /* , 'like' */ ];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Operators that to be used to combine expanded expressions by keeping logical relations.
|
|
21
|
+
*/
|
|
22
|
+
const OperatorCombinator = {
|
|
23
|
+
__proto__: null,
|
|
24
|
+
'=': 'and',
|
|
25
|
+
'==': 'and',
|
|
26
|
+
'<>': 'or',
|
|
27
|
+
'!=': 'or',
|
|
28
|
+
is: 'and',
|
|
29
|
+
'is not': 'or',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get transformation functions for "tuple expansion", i.e. for expanding
|
|
35
|
+
* structure references in expressions.
|
|
36
|
+
*
|
|
37
|
+
* @param {CSN.Model} csn
|
|
38
|
+
* @param {object} csnUtils
|
|
39
|
+
* @param {object} msgFunctions
|
|
40
|
+
*
|
|
41
|
+
* @returns {object} Object containing functions `expandStructsInExpression` and `flattenPath`
|
|
42
|
+
*/
|
|
43
|
+
function tupleExpansion(csn, csnUtils, msgFunctions) {
|
|
44
|
+
const { message, error, info } = msgFunctions;
|
|
45
|
+
const { inspectRef, effectiveType, resolvePath } = csnUtils;
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
expandStructsInExpression,
|
|
49
|
+
flattenPath,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Expand structured expression arguments to flat reference paths.
|
|
54
|
+
* Structured elements are real sub element lists and managed associations.
|
|
55
|
+
* All unmanaged association definitions are rewritten if applicable (elements/mixins).
|
|
56
|
+
* Also, HAVING and WHERE clauses are rewritten.
|
|
57
|
+
* We also check for infix filters and `.xpr` in columns.
|
|
58
|
+
*
|
|
59
|
+
* @param {object} [traversalOptions={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts
|
|
60
|
+
*/
|
|
61
|
+
function expandStructsInExpression( traversalOptions = {} ) {
|
|
62
|
+
applyTransformations(csn, {
|
|
63
|
+
on: expandExpr,
|
|
64
|
+
having: expandExpr,
|
|
65
|
+
where: expandExpr,
|
|
66
|
+
xpr: expandExpr,
|
|
67
|
+
args: (parent, name, args, path) => {
|
|
68
|
+
if (!parent.id && !parent.func)
|
|
69
|
+
return; // ensure we're not in JOIN
|
|
70
|
+
|
|
71
|
+
if (Array.isArray(parent.args)) {
|
|
72
|
+
expandExpr(parent, name, args, path);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (const argName in parent.args) // named arguments
|
|
77
|
+
rejectAnyDirectStructureReference([ parent.args[argName] ], path.concat(name, argName));
|
|
78
|
+
},
|
|
79
|
+
}, [], traversalOptions);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function expandExpr(parent, name, _xpr, path) {
|
|
83
|
+
// We need to structurize the _model_, as error locations may otherwise point to non-existent paths.
|
|
84
|
+
// Note: structurizer does not go into nested `xpr`.
|
|
85
|
+
parent[name] = condAsTree(parent[name]);
|
|
86
|
+
parent[name] = expandStructurizedExpr(parent[name], path.concat(name));
|
|
87
|
+
parent[name] = parent[name].flat(Infinity); // tokenize again
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function expandStructurizedExpr(expr, location) {
|
|
91
|
+
if (!Array.isArray(expr))
|
|
92
|
+
return expr; // don't traverse strings, etc.
|
|
93
|
+
|
|
94
|
+
expr = expr.map((e, i) => expandStructurizedExpr(e, location.concat(i)));
|
|
95
|
+
|
|
96
|
+
if (expr.length === 3 && typeof expr[1] === 'string') // also includes `<lhs> is null`
|
|
97
|
+
return expandBinaryOp(expr[0], [ expr[1] ], expr[2], location) ?? expr;
|
|
98
|
+
|
|
99
|
+
if (expr.length === 4 && expr[1] === 'is' && expr[2] === 'not' && expr[3] === 'null')
|
|
100
|
+
return expandBinaryOp(expr[0], [ expr[1], expr[2] ], expr[3], location) ?? expr;
|
|
101
|
+
|
|
102
|
+
// expr.length is either <3 or >=4, in which case we reject all structure references
|
|
103
|
+
// in the array, but still traverse sub-arrays (the expression is structurized, after all).
|
|
104
|
+
rejectAnyDirectStructureReference(expr, location);
|
|
105
|
+
|
|
106
|
+
return expr;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Expands the binary operation `lhs <op> rhs`, where `op` may be one or more tokens,
|
|
111
|
+
* e.g. `is not` for `is not null`.
|
|
112
|
+
*
|
|
113
|
+
* @param {object} lhs
|
|
114
|
+
* @param {string[]} operators
|
|
115
|
+
* @param {object} rhs
|
|
116
|
+
* @param location
|
|
117
|
+
* @returns {object[]|null} In case of errors, returns `null`.
|
|
118
|
+
*/
|
|
119
|
+
function expandBinaryOp(lhs, operators, rhs, location) {
|
|
120
|
+
const lhsArt = lhs._art || lhs.ref && enrichRef(lhs, location.concat(0));
|
|
121
|
+
const rhsArt = rhs._art || rhs.ref && enrichRef(rhs, location.concat(2));
|
|
122
|
+
|
|
123
|
+
if (!lhsArt && !rhsArt || !isExpandable(lhsArt) && !isExpandable(rhsArt))
|
|
124
|
+
return null; // no structure to expand
|
|
125
|
+
|
|
126
|
+
if (isDollarSelfOrProjectionOperand(lhs) || isDollarSelfOrProjectionOperand(rhs)) {
|
|
127
|
+
// if either side is bare `$self`, the compiler has handled it already
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// At least one side is expandable. That means, if we can't expand a structural reference
|
|
132
|
+
// starting here, we _must_ emit an error.
|
|
133
|
+
|
|
134
|
+
const opStr = operators.join(' ');
|
|
135
|
+
if (!RelationalOperators.includes(opStr)) {
|
|
136
|
+
message('expr-unexpected-operator', location, { op: opStr, elemref: (lhsArt && lhs) || rhs },
|
|
137
|
+
'Unexpected operator $(OP) in structural comparison with $(ELEMREF)');
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const lhsIsVal = isVal(lhs);
|
|
142
|
+
const rhsIsVal = isVal(rhs);
|
|
143
|
+
|
|
144
|
+
const lhsPaths = lhsIsVal ? [] : flattenPath(lhs, false, true );
|
|
145
|
+
const rhsPaths = rhsIsVal ? [] : flattenPath(rhs, false, true );
|
|
146
|
+
|
|
147
|
+
const msgCode = () => `${ xprForMessage( lhs ) } ${ opStr } ${ xprForMessage( rhs ) }`;
|
|
148
|
+
|
|
149
|
+
if (xprOperationRejected())
|
|
150
|
+
return null;
|
|
151
|
+
if (scalarOperationRejected())
|
|
152
|
+
return null;
|
|
153
|
+
|
|
154
|
+
if (lhsPaths.length === 0 && rhsPaths.length === 0) {
|
|
155
|
+
error('expr-invalid-expansion-empty', location, {
|
|
156
|
+
code: msgCode(),
|
|
157
|
+
}, 'Neither side of the expression $(CODE) expands to anything');
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const xref = createCrossReferences(lhsPaths, rhsPaths, lhs, rhs);
|
|
162
|
+
|
|
163
|
+
for (const xn in xref) {
|
|
164
|
+
const x = xref[xn];
|
|
165
|
+
|
|
166
|
+
// Each entry of the left side must have a matching entry on the right.
|
|
167
|
+
if (!x.lhs || !x.rhs) {
|
|
168
|
+
error('expr-invalid-expansion', location, {
|
|
169
|
+
'#': 'path-mismatch',
|
|
170
|
+
value: msgCode(),
|
|
171
|
+
name: xn,
|
|
172
|
+
alias: pathToMessageString(x.rhs ? lhs : rhs),
|
|
173
|
+
} );
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (rejectNonScalarRef(lhs, x.lhs, location) || rejectNonScalarRef(rhs, x.rhs, location)) {
|
|
178
|
+
// one side was expanded to a non-scalar reference
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// info about type incompatibility if no other errors occurred
|
|
183
|
+
if (!lhsIsVal && !rhsIsVal && x.lhs && x.rhs) {
|
|
184
|
+
if (getType(x.lhs._art) !== getType(x.rhs._art)) {
|
|
185
|
+
info('expr-ignoring-type-mismatch', location, {
|
|
186
|
+
code: msgCode(),
|
|
187
|
+
name: xn,
|
|
188
|
+
}, 'Types of sub path $(NAME) differ when expanding structure in $(CODE)');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// We're not adding another `xpr`, because `op` binds stronger than the combinators
|
|
194
|
+
// in all cases we care about.
|
|
195
|
+
const xpr = Object.values(xref).map((x) => {
|
|
196
|
+
delete x.lhs.comparisonRef;
|
|
197
|
+
delete x.rhs.comparisonRef;
|
|
198
|
+
return [ x.lhs, ...operators, x.rhs ];
|
|
199
|
+
});
|
|
200
|
+
// insert the combinator between each value
|
|
201
|
+
for (let i = 1; i < xpr.length; i += 2)
|
|
202
|
+
xpr.splice(i, 0, OperatorCombinator[opStr]);
|
|
203
|
+
|
|
204
|
+
return xpr.length > 1 ? { xpr } : xpr;
|
|
205
|
+
|
|
206
|
+
function rejectNonScalarRef(e, x, location) {
|
|
207
|
+
if ( !x || isVal(e) || isScalarOrNoType(x))
|
|
208
|
+
return false;
|
|
209
|
+
// error: one side was expanded to a non-scalar, e.g. unmanaged association
|
|
210
|
+
error('expr-invalid-expansion', location, {
|
|
211
|
+
'#': 'non-scalar',
|
|
212
|
+
value: msgCode(),
|
|
213
|
+
name: pathToMessageString( e ),
|
|
214
|
+
});
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Check for and reject invalid expressions with complex operands.
|
|
220
|
+
*
|
|
221
|
+
* @returns {boolean} If true, expression is invalid.
|
|
222
|
+
*/
|
|
223
|
+
function xprOperationRejected() {
|
|
224
|
+
if (Array.isArray(lhs) || Array.isArray(rhs)) {
|
|
225
|
+
error('ref-unexpected-structured', location, { '#': 'complexExpr', elemref: lhs.ref ? lhs : rhs, value: msgCode() });
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Check for and reject invalid expressions with scalar values/references.
|
|
234
|
+
*
|
|
235
|
+
* @returns {boolean} If true, expression is invalid.
|
|
236
|
+
*/
|
|
237
|
+
function scalarOperationRejected() {
|
|
238
|
+
if ((opStr === 'is' || opStr === 'is not') && (rhs === 'null' || lhs === 'null'))
|
|
239
|
+
return false; // `is [not] null` works even with multiple elements
|
|
240
|
+
|
|
241
|
+
if ((lhsIsVal || isScalarOrNoType(lhs)) && rhsPaths.length !== 1) {
|
|
242
|
+
const variant = rhs._art?.target && 'assoc-expr' || 'struct-expr';
|
|
243
|
+
error('ref-unexpected-structured', location, { '#': variant, elemref: rhs });
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
if ((rhsIsVal || isScalarOrNoType(rhs)) && lhsPaths.length !== 1) {
|
|
247
|
+
const variant = lhs._art?.target && 'assoc-expr' || 'struct-expr';
|
|
248
|
+
error('ref-unexpected-structured', location, { '#': variant, elemref: lhs });
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (lhs.ref && isScalarOrNoType(lhs)) {
|
|
253
|
+
error('expr-unsupported-expansion', location, { '#': 'scalarRef', elemref: lhs, value: msgCode() });
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
if (rhs.ref && isScalarOrNoType(rhs)) {
|
|
257
|
+
error('expr-unsupported-expansion', location, { '#': 'scalarRef', elemref: rhs, value: msgCode() });
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function rejectAnyDirectStructureReference(expr, location) {
|
|
266
|
+
if (expr[0] === 'exists') {
|
|
267
|
+
// we ignore WHERE EXISTS clauses; they are not relevant for OData,
|
|
268
|
+
// and in SQL it was handled before
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
for (const x of expr) {
|
|
273
|
+
const art = x?._art || x?.ref && !x.$scope && inspectRef(location.concat(0)).art;
|
|
274
|
+
if (art && isExpandable(art)) {
|
|
275
|
+
const variant = art.target && 'assoc' || 'std';
|
|
276
|
+
error('ref-unexpected-structured', location, { '#': variant, elemref: x });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Pairs left and right sub-paths into a common structure.
|
|
283
|
+
*
|
|
284
|
+
* @param {Array} lhsPaths
|
|
285
|
+
* @param {Array} rhsPaths
|
|
286
|
+
* @param {object} lhs
|
|
287
|
+
* @param {object} rhs
|
|
288
|
+
* @returns {Record<string, object>}
|
|
289
|
+
*/
|
|
290
|
+
function createCrossReferences(lhsPaths, rhsPaths, lhs, rhs) {
|
|
291
|
+
const xRef = Object.create(null);
|
|
292
|
+
|
|
293
|
+
const rhsIsVal = isVal(rhs);
|
|
294
|
+
for (const p of lhsPaths) {
|
|
295
|
+
const name = p.comparisonRef.slice(lhs.ref.length).map(pathId).join('.');
|
|
296
|
+
xRef[name] ??= { name };
|
|
297
|
+
xRef[name].lhs = p;
|
|
298
|
+
if (rhsIsVal)
|
|
299
|
+
xRef[name].rhs = rhs;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const lhsIsVal = isVal(lhs);
|
|
303
|
+
for (const p of rhsPaths) {
|
|
304
|
+
const name = p.comparisonRef.slice(rhs.ref.length).map(pathId).join('.');
|
|
305
|
+
xRef[name] ??= { name };
|
|
306
|
+
xRef[name].rhs = p;
|
|
307
|
+
if (lhsIsVal)
|
|
308
|
+
xRef[name].lhs = lhs;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return xRef;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Flatten structured leaf types and return an array of paths.
|
|
316
|
+
*
|
|
317
|
+
* Argument 'path' must be an object of the form
|
|
318
|
+
* `{ _art: <leaf_artifact>, ref: [...] }`
|
|
319
|
+
* with `_art` identifying `ref[ref.length-1]`
|
|
320
|
+
*
|
|
321
|
+
* A produced path has the form `{ _art: <ref>, ref: [ <id> (, <id>)* ] }`
|
|
322
|
+
*
|
|
323
|
+
* Flattening stops on all non-structured elements, if followMgdAssoc=false.
|
|
324
|
+
*
|
|
325
|
+
* If fullRef is true, a path step is produced as `{ id: <id>, _art: <link> }`
|
|
326
|
+
*/
|
|
327
|
+
function flattenPath(path, fullRef = false, followMgdAssoc = false) {
|
|
328
|
+
let art = path._art;
|
|
329
|
+
if (!art)
|
|
330
|
+
return [ path ];
|
|
331
|
+
|
|
332
|
+
if (!art.elements) {
|
|
333
|
+
if (followMgdAssoc && art.target && art.keys) {
|
|
334
|
+
const rc = [];
|
|
335
|
+
for (const k of art.keys) {
|
|
336
|
+
const nps = {
|
|
337
|
+
ref: k.ref.map(p => (fullRef ? { id: p } : p) ),
|
|
338
|
+
comparisonRef: [ k.as || implicitAs(k.ref) ],
|
|
339
|
+
};
|
|
340
|
+
setProp(nps, '_art', k._art);
|
|
341
|
+
const paths = flattenPath( nps, fullRef, followMgdAssoc );
|
|
342
|
+
// prepend prefix path
|
|
343
|
+
paths.forEach((p) => {
|
|
344
|
+
p.comparisonRef.unshift(...clonePath(path.comparisonRef || path.ref));
|
|
345
|
+
p.ref.unshift(...clonePath(path.ref));
|
|
346
|
+
if (path.$scope !== undefined)
|
|
347
|
+
setProp(p, '$scope', path.$scope);
|
|
348
|
+
});
|
|
349
|
+
rc.push(...paths);
|
|
350
|
+
}
|
|
351
|
+
return rc;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (art.type?.ref)
|
|
355
|
+
art = resolvePath(art.type);
|
|
356
|
+
else if (art.type && !isBuiltinType(art.type))
|
|
357
|
+
art = csn.definitions[art.type];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const { elements } = art;
|
|
361
|
+
if (!elements) {
|
|
362
|
+
setProp(path, '_art', art);
|
|
363
|
+
return [ path ];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const rc = [];
|
|
367
|
+
Object.entries(elements).forEach(([ en, elt ]) => {
|
|
368
|
+
const step = (fullRef ? { id: en, _art: elt } : en );
|
|
369
|
+
const nps = {
|
|
370
|
+
ref: [ step ],
|
|
371
|
+
comparisonRef: [ step ],
|
|
372
|
+
};
|
|
373
|
+
setProp(nps, '_art', elt);
|
|
374
|
+
const paths = flattenPath( nps, fullRef, followMgdAssoc );
|
|
375
|
+
// prepend prefix path
|
|
376
|
+
paths.forEach((p) => {
|
|
377
|
+
p.comparisonRef.unshift(...clonePath(path.comparisonRef || path.ref));
|
|
378
|
+
p.ref.splice(0, 0, ...clonePath(path.ref));
|
|
379
|
+
if (path.$scope !== undefined)
|
|
380
|
+
setProp(p, '$scope', path.$scope);
|
|
381
|
+
});
|
|
382
|
+
rc.push(...paths);
|
|
383
|
+
});
|
|
384
|
+
return rc;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function getType(art) {
|
|
388
|
+
const effArt = art && effectiveType(art);
|
|
389
|
+
return Object.keys(effArt).length ? effArt : art.type;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function isExpandable(art) {
|
|
393
|
+
art = art && effectiveType(art);
|
|
394
|
+
if (!art)
|
|
395
|
+
return false;
|
|
396
|
+
|
|
397
|
+
// items in ON conditions are illegal but this should be checked elsewhere
|
|
398
|
+
const elements = art.elements || art.items?.elements;
|
|
399
|
+
return !!(elements || art.target && art.keys);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Returns true if the given artifact or artifact reference has a scalar type
|
|
404
|
+
* or no type at all, e.g. for references to elements of type `cds.String`.
|
|
405
|
+
*
|
|
406
|
+
* @param art
|
|
407
|
+
* @returns {boolean}
|
|
408
|
+
*/
|
|
409
|
+
function isScalarOrNoType(art) {
|
|
410
|
+
art = art?._art ?? art;
|
|
411
|
+
if (isTypeScalarBuiltin(art.type))
|
|
412
|
+
return true;
|
|
413
|
+
|
|
414
|
+
art = art && effectiveType(art); // builtins are resolved to `{}`
|
|
415
|
+
if (!art)
|
|
416
|
+
return false;
|
|
417
|
+
|
|
418
|
+
const type = art.type || art.items?.type;
|
|
419
|
+
const elements = art.elements || art.items?.elements;
|
|
420
|
+
const target = art.target || art.items?.target;
|
|
421
|
+
if (!elements && !type && !target)
|
|
422
|
+
return true;
|
|
423
|
+
|
|
424
|
+
return isTypeScalarBuiltin(type);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function isTypeScalarBuiltin(type) {
|
|
428
|
+
// "cds.Map" can't be used for tuple expansion, even though it is a structured type.
|
|
429
|
+
return (type && isBuiltinType(type) &&
|
|
430
|
+
type !== 'cds.Association' && type !== 'cds.Composition' && type !== 'cds.Map');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Enrich a reference node with properties that are also set by our enricher.
|
|
435
|
+
* There are paths (generated by "where-exists") that are missing these properties, hence
|
|
436
|
+
* we add them here.
|
|
437
|
+
* Since the result of tuple expansion is not exposed in any CSN, we don't need to care about
|
|
438
|
+
* cleanup afterwards.
|
|
439
|
+
*
|
|
440
|
+
* TODO: Can we move this to the enricher?
|
|
441
|
+
*
|
|
442
|
+
* @param node Object with `ref`
|
|
443
|
+
* @param {CSN.Path} loc Path to the node.
|
|
444
|
+
* @returns {*}
|
|
445
|
+
*/
|
|
446
|
+
function enrichRef(node, loc) {
|
|
447
|
+
if (!node.ref)
|
|
448
|
+
return node;
|
|
449
|
+
const inspected = inspectRef(loc);
|
|
450
|
+
|
|
451
|
+
if (inspected.links)
|
|
452
|
+
setProp(node, '_links', inspected.links);
|
|
453
|
+
if (inspected.art)
|
|
454
|
+
setProp(node, '_art', inspected.art );
|
|
455
|
+
if (inspected.$env)
|
|
456
|
+
setProp(node, '$env', inspected.$env );
|
|
457
|
+
|
|
458
|
+
setProp(node, '$scope', inspected.scope);
|
|
459
|
+
setProp(node, '$path', [ ...loc ]);
|
|
460
|
+
|
|
461
|
+
return node._art;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function isVal(valOrRef) {
|
|
466
|
+
return (valOrRef === 'null' || valOrRef.val !== undefined);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function xprForMessage(xpr) {
|
|
470
|
+
if (isVal(xpr))
|
|
471
|
+
return xpr.val ?? xpr;
|
|
472
|
+
if (xpr.ref)
|
|
473
|
+
return pathToMessageString( xpr );
|
|
474
|
+
return '…';
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Clone a ref. Otherwise, it may happen that two paths with filters are transformed twice
|
|
479
|
+
* during flattening, due to object equality.
|
|
480
|
+
*
|
|
481
|
+
* @param {any[]} ref
|
|
482
|
+
* @returns {any[]}
|
|
483
|
+
*/
|
|
484
|
+
function clonePath(ref) {
|
|
485
|
+
return ref.map((step) => {
|
|
486
|
+
if (typeof step === 'string')
|
|
487
|
+
return step;
|
|
488
|
+
return cloneCsnNonDict(step);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
module.exports = {
|
|
493
|
+
tupleExpansion,
|
|
494
|
+
RelationalOperators,
|
|
495
|
+
};
|
|
@@ -580,7 +580,7 @@ function checkFileCase( dep, realpath, nativeRealpath, messageFunctions ) {
|
|
|
580
580
|
for (const using of dep.usingFroms) {
|
|
581
581
|
const { warning } = messageFunctions;
|
|
582
582
|
warning('file-unexpected-case-mismatch', [ using.location, using ], {},
|
|
583
|
-
// eslint-disable-next-line @stylistic/
|
|
583
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
584
584
|
'The imported filename differs on the filesystem; ensure that capitalization matches the actual file\'s name');
|
|
585
585
|
}
|
|
586
586
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-compiler",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "CDS (Core Data Services) compiler and backends",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"author": "SAP SE (https://www.sap.com)",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"download": "exit 0",
|
|
18
18
|
"gen": "npm run rdpg",
|
|
19
|
-
"rdpg": "node ./redepage/bin/redepage --compile lib/gen/CdlParser.js --copy-base-parser lib/parsers/CdlGrammar.g4 && node scripts/genGrammarChecksum.js",
|
|
19
|
+
"rdpg": "node ./redepage/bin/redepage --compile lib/gen/CdlParser.js --copy-base-parser lib/parsers/CdlGrammar.g4 && node ./scripts/createCdlKeywordList.js && node scripts/genGrammarChecksum.js",
|
|
20
20
|
"xmakeAfterInstall": "npm run gen",
|
|
21
21
|
"xmakePrepareRelease": "echo \"$(node scripts/stripReadme.js README.md)\" > README.md && node scripts/assertSnapshotVersioning.js && node scripts/assertChangelog.js && node scripts/cleanup.js --remove-dev",
|
|
22
22
|
"test": "node scripts/xmakeTestDispatcher.js",
|
package/lib/base/cleanSymbols.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Remove the given symbols from the object.
|
|
5
|
-
* Does NOT do so recursively, only directly on the object.
|
|
6
|
-
*
|
|
7
|
-
* @param {object} obj
|
|
8
|
-
* @param {...any} symbols
|
|
9
|
-
*/
|
|
10
|
-
function cleanSymbols( obj, ...symbols ) {
|
|
11
|
-
for (const symbol of symbols)
|
|
12
|
-
delete obj[symbol];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
module.exports = {
|
|
16
|
-
cleanSymbols,
|
|
17
|
-
};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { otherSideIsExpandableStructure, resolveArtifactType } = require('./utils');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Check the given expression for non-expandable structure usage
|
|
7
|
-
*
|
|
8
|
-
* @param {object} parent Object with the expression as a property
|
|
9
|
-
* @param {string} name Name of the expression property on parent
|
|
10
|
-
* @param {Array} expression Expression to check - .on .xpr .having and .where
|
|
11
|
-
*/
|
|
12
|
-
function nonexpandableStructuredInExpression( parent, name, expression ) {
|
|
13
|
-
for (let i = 0; i < expression.length; i++) {
|
|
14
|
-
if (expression[i].ref) {
|
|
15
|
-
const { ref } = expression[i];
|
|
16
|
-
// eslint-disable-next-line prefer-const
|
|
17
|
-
let { _art, $scope } = expression[i];
|
|
18
|
-
if (!_art)
|
|
19
|
-
continue;
|
|
20
|
-
const validStructuredElement = otherSideIsExpandableStructure.call(this, expression, i);
|
|
21
|
-
if (_art) {
|
|
22
|
-
_art = resolveArtifactType.call(this, _art);
|
|
23
|
-
// Paths of an expression may end on a structured element only if both operands in the expression end on a structured element
|
|
24
|
-
if ((_art?.elements || _art?.keys && (i === 0 || expression[i - 1] !== 'exists')) && !validStructuredElement && ($scope !== '$self' || $scope === '$self' && ref.length > 1)) { // TODO: Use $self to navigate to struct
|
|
25
|
-
this.error('ref-unexpected-structured',
|
|
26
|
-
name === 'on' ? [ ...parent.$path, name, i ] : expression[i].$path,
|
|
27
|
-
{ '#': 'std', elemref: { ref } } );
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
module.exports = {
|
|
35
|
-
on: nonexpandableStructuredInExpression,
|
|
36
|
-
having: nonexpandableStructuredInExpression,
|
|
37
|
-
where: nonexpandableStructuredInExpression,
|
|
38
|
-
xpr: nonexpandableStructuredInExpression,
|
|
39
|
-
};
|