@sap/cds-compiler 6.7.1 → 6.7.3
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 +16 -1
- package/lib/base/message-registry.js +7 -0
- package/lib/checks/manyExpand.js +28 -0
- package/lib/checks/validator.js +2 -0
- package/lib/compiler/shared.js +5 -1
- package/lib/edm/edmAnnoPreprocessor.js +2 -2
- package/lib/render/toSql.js +1 -1
- package/lib/transform/db/expansion.js +2 -44
- package/lib/transform/effective/main.js +2 -1
- package/lib/transform/featureFlags.js +6 -1
- package/lib/transform/forRelationalDB.js +2 -1
- package/lib/transform/translateAssocsToJoins.js +28 -28
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,22 @@ we might not list every change in its behavior here.
|
|
|
13
13
|
Productive code should never require a `beta` flag to be set, and
|
|
14
14
|
might use a deprecated flag only for a limited period of time.
|
|
15
15
|
|
|
16
|
+
## Version 6.7.3 - 2026-02-11
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **sql:** do not resolve path navigations to virtual elements which resulted in an internal error.
|
|
21
|
+
|
|
22
|
+
## Version 6.7.2 - 2026-02-04
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **compiler:** Just issue warning for `using` declaration referring to nothing
|
|
27
|
+
(fixes regression introduced with v6.7.0 if there is a file containing
|
|
28
|
+
a `namespace` declaration, but no definitions)
|
|
29
|
+
- **effective:** clean up internal Symbols from meta section
|
|
30
|
+
- **sql:** clean up internal Symbols from meta section
|
|
31
|
+
|
|
16
32
|
## Version 6.7.1 - 2026-01-28
|
|
17
33
|
|
|
18
34
|
### Fixed
|
|
@@ -20,7 +36,6 @@ might use a deprecated flag only for a limited period of time.
|
|
|
20
36
|
- compiler: Properly accept aspects as composition targets in an `extend`
|
|
21
37
|
(fixes regression introduced with v6.7.0)
|
|
22
38
|
|
|
23
|
-
|
|
24
39
|
## Version 6.7.0 - 2026-01-23
|
|
25
40
|
|
|
26
41
|
### Added
|
|
@@ -160,10 +160,12 @@ const centralMessages = {
|
|
|
160
160
|
'type-ambiguous-target': { severity: 'Warning' },
|
|
161
161
|
|
|
162
162
|
'ref-unexpected-autoexposed': { severity: 'Error' },
|
|
163
|
+
'ref-unexpected-many-expand': { severity: 'Error' },
|
|
163
164
|
'ref-unexpected-many-navigation': { severity: 'Error' },
|
|
164
165
|
// Published! Used in @sap/cds-lsp; if renamed, add to oldMessageIds and contact colleagues
|
|
165
166
|
'ref-undefined-art': { severity: 'Error' },
|
|
166
167
|
'ref-undefined-def': { severity: 'Error' },
|
|
168
|
+
'ref-undefined-using': { severity: 'Warning' },
|
|
167
169
|
'ref-undefined-var': { severity: 'Error' },
|
|
168
170
|
'ref-undefined-element': { severity: 'Error' },
|
|
169
171
|
'anno-undefined-element': { severity: 'Error' },
|
|
@@ -738,6 +740,7 @@ const centralMessageTexts = {
|
|
|
738
740
|
std: 'No artifact has been found with name $(ART)',
|
|
739
741
|
localized: 'Can\'t extend localized definitions, only annotate them using an $(KEYWORD) statement',
|
|
740
742
|
},
|
|
743
|
+
'ref-undefined-using': 'There is no definition in the model whose name starts with $(ART)',
|
|
741
744
|
// TODO: proposal 'No definition found for $(NAME)',
|
|
742
745
|
'ref-undefined-element': {
|
|
743
746
|
std: 'Element $(ART) has not been found',
|
|
@@ -786,6 +789,10 @@ const centralMessageTexts = {
|
|
|
786
789
|
std: '$(ART) can\'t be extended because it originates from an include',
|
|
787
790
|
elements: '$(ART) can\'t be extended by elements/enums because it originates from an include',
|
|
788
791
|
},
|
|
792
|
+
'ref-unexpected-many-expand': {
|
|
793
|
+
std: 'Unexpected expand with to-many association',
|
|
794
|
+
composition: 'Unexpected expand with to-many composition',
|
|
795
|
+
},
|
|
789
796
|
'ref-unexpected-many-navigation': {
|
|
790
797
|
std: 'Unexpected navigation into arrayed structure',
|
|
791
798
|
},
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { applyTransformationsOnNonDictionary } = require('../model/csnUtils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check for nested projections (expand) on to-many associations or compositions.
|
|
7
|
+
* Nested projections are only valid for to-one relationships.
|
|
8
|
+
*
|
|
9
|
+
* @param {object} parent Object with the expression as a property
|
|
10
|
+
* @param {string} propOnParent Name of the expression property on parent
|
|
11
|
+
* @param {Array} _e Expression to check (unused)
|
|
12
|
+
* @param {CSN.Path} path
|
|
13
|
+
*/
|
|
14
|
+
function expandToMany( parent, propOnParent, _e, path ) {
|
|
15
|
+
applyTransformationsOnNonDictionary(parent, propOnParent, {
|
|
16
|
+
expand: (_parent, _prop, _expand, _path) => {
|
|
17
|
+
const last = _parent._links?.at(-1).art;
|
|
18
|
+
if (last?.cardinality && last.cardinality.max !== 1) {
|
|
19
|
+
this.message('ref-unexpected-many-expand', _path,
|
|
20
|
+
{ '#': last.type === 'cds.Composition' ? 'composition' : 'std' });
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
}, null, path);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
columns: expandToMany,
|
|
28
|
+
};
|
package/lib/checks/validator.js
CHANGED
|
@@ -12,6 +12,7 @@ const { validateSelectItems } = require('./selectItems');
|
|
|
12
12
|
const { rejectParamDefaultsInHanaCds, warnAboutDefaultOnAssociationForHanaCds } = require('./defaultValues');
|
|
13
13
|
const validateCdsPersistenceAnnotation = require('./cdsPersistence');
|
|
14
14
|
const navigationIntoMany = require('./manyNavigations');
|
|
15
|
+
const expandToMany = require('./manyExpand');
|
|
15
16
|
const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
|
|
16
17
|
const validateHasPersistedElements = require('./hasPersistedElements');
|
|
17
18
|
const checkForHanaTypes = require('./checkForTypes');
|
|
@@ -91,6 +92,7 @@ const forRelationalDBCsnValidators = [
|
|
|
91
92
|
existsMustNotStartWithDollarSelf,
|
|
92
93
|
assertFilterOfExists,
|
|
93
94
|
navigationIntoMany,
|
|
95
|
+
expandToMany,
|
|
94
96
|
checkPathsInStoredCalcElement,
|
|
95
97
|
featureFlags,
|
|
96
98
|
];
|
package/lib/compiler/shared.js
CHANGED
|
@@ -43,11 +43,15 @@ function fns( model ) {
|
|
|
43
43
|
// Map `exprCtx` (is a param of traversal functions) to reference semantics
|
|
44
44
|
const referenceSemantics = {
|
|
45
45
|
// global: ------------------------------------------------------------------
|
|
46
|
-
using: { // only used to produce
|
|
46
|
+
using: { // only used to produce warnings
|
|
47
47
|
isMainRef: 'no-leaf-gap',
|
|
48
48
|
lexical: null,
|
|
49
49
|
dynamic: modelDefinitions,
|
|
50
50
|
notFound: undefinedDefinition,
|
|
51
|
+
messageMap: {
|
|
52
|
+
'ref-undefined-art': 'ref-undefined-using',
|
|
53
|
+
'ref-undefined-def': 'ref-undefined-using',
|
|
54
|
+
},
|
|
51
55
|
},
|
|
52
56
|
// scope:'global': for cds.Association and auto-redirected targets
|
|
53
57
|
$global: {
|
|
@@ -49,7 +49,7 @@ function addToSetAttr( carrier, propName, propValue, removeFromType = true ) {
|
|
|
49
49
|
|
|
50
50
|
function applyAppSpecificLateCsnTransformationOnElement( options, element, struct, error ) {
|
|
51
51
|
if (options.isV2() && struct['@Aggregation.ApplySupported.PropertyRestrictions'])
|
|
52
|
-
mapAnnotationAssignment(element, struct,
|
|
52
|
+
mapAnnotationAssignment(element, struct, GenerateAnalyticalAnnotations());
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
// etag requires Core.OptimisticConcurrency to be set in V4 (cap/issues#2641)
|
|
@@ -66,7 +66,7 @@ function applyAppSpecificLateCsnTransformationOnElement( options, element, struc
|
|
|
66
66
|
(struct['@Core.OptimisticConcurrency'] || [])/* .push(element.name) */);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function
|
|
69
|
+
function GenerateAnalyticalAnnotations() {
|
|
70
70
|
function mapCommonAttributes( elt, structure, prop ) {
|
|
71
71
|
const CommonAttributes = elt[prop];
|
|
72
72
|
if (!Array.isArray(CommonAttributes)) {
|
package/lib/render/toSql.js
CHANGED
|
@@ -104,7 +104,7 @@ class SqlRenderEnvironment {
|
|
|
104
104
|
* }
|
|
105
105
|
* }
|
|
106
106
|
*
|
|
107
|
-
* @param {CSN.Model} csn
|
|
107
|
+
* @param {CSN.Model} csn for SQL transformed CSN
|
|
108
108
|
* @param {CSN.Options} options Transformation options
|
|
109
109
|
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
110
110
|
* @returns {object} Dictionary of artifact-type:artifacts, where artifacts is a dictionary of name:content
|
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
applyTransformations,
|
|
5
|
-
setDependencies,
|
|
6
5
|
walkCsnPath,
|
|
7
6
|
getUtils,
|
|
8
7
|
forEachDefinition,
|
|
9
8
|
} = require('../../model/csnUtils');
|
|
10
9
|
const { implicitAs, columnAlias, pathId } = require('../../model/csnRefs');
|
|
11
10
|
const { setProp } = require('../../base/model');
|
|
12
|
-
const { forEach } = require('../../utils/objectUtils');
|
|
13
11
|
const { killNonrequiredAnno } = require('./killAnnotations');
|
|
14
12
|
const { featureFlags } = require('../featureFlags');
|
|
15
13
|
const { applyTransformationsOnNonDictionary } = require('./applyTransformations');
|
|
@@ -128,15 +126,11 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
128
126
|
* For such skipped things, error for usage of assoc pointing to them and ignore publishing of assoc pointing to them.
|
|
129
127
|
*/
|
|
130
128
|
function rewriteExpandInline() {
|
|
131
|
-
let cleanup = [];
|
|
132
|
-
let _dependents;
|
|
133
|
-
|
|
134
129
|
const entity = findAnEntity();
|
|
135
130
|
const toDummify = [];
|
|
136
131
|
|
|
137
132
|
applyTransformations(csn, {
|
|
138
|
-
columns: (parent
|
|
139
|
-
const artifact = csn.definitions[path[1]];
|
|
133
|
+
columns: (parent) => {
|
|
140
134
|
// get$combined expects a SET/SELECT - so we wrap the parent
|
|
141
135
|
// (which is the thing inside SET/SELECT)
|
|
142
136
|
// We can directly use SELECT here, as only projections and SELECT can have .columns
|
|
@@ -146,18 +140,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
146
140
|
root[key] = root[key][0].element;
|
|
147
141
|
});
|
|
148
142
|
const rewritten = rewrite(root, parent.columns, parent.excluding);
|
|
149
|
-
|
|
150
|
-
* Do not remove unexpandable many columns in OData
|
|
151
|
-
*/
|
|
152
|
-
if (rewritten.toMany.length > 0 && !options.toOdata) {
|
|
153
|
-
markAsToDummify(artifact, path[1]);
|
|
154
|
-
rewritten.toMany.forEach(({ art }) => {
|
|
155
|
-
error( null, art.$path || [ 'definitions', path[1] ], { name: `${ art.$env || path[1] }:${ art.ref.map(r => r.id || r) }` }, 'Unexpected .expand with to-many association $(NAME)');
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
parent.columns = rewritten.columns;
|
|
160
|
-
}
|
|
143
|
+
parent.columns = rewritten.columns;
|
|
161
144
|
},
|
|
162
145
|
});
|
|
163
146
|
|
|
@@ -166,8 +149,6 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
166
149
|
if (!options.toOdata)
|
|
167
150
|
dummyfy();
|
|
168
151
|
|
|
169
|
-
cleanup.forEach(fn => fn());
|
|
170
|
-
|
|
171
152
|
csnUtils = getUtils(csn);
|
|
172
153
|
|
|
173
154
|
const publishing = [];
|
|
@@ -267,29 +248,6 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
267
248
|
info(null, path, { name: last, target }, 'Ignoring association $(NAME) with target $(TARGET), because it was skipped because of .expand in conjunction with to-many');
|
|
268
249
|
}
|
|
269
250
|
|
|
270
|
-
/**
|
|
271
|
-
* Mark the given artifact and all (transitively) dependent artifacts as `toDummify`.
|
|
272
|
-
* This means that they will be replaced with simple dummy views in `@dummify`
|
|
273
|
-
*
|
|
274
|
-
* @param {CSN.Artifact} artifact
|
|
275
|
-
* @param {string} name
|
|
276
|
-
*/
|
|
277
|
-
function markAsToDummify( artifact, name ) {
|
|
278
|
-
if (!_dependents && cleanup.length === 0)
|
|
279
|
-
({ cleanup, _dependents } = setDependencies(csn, csnUtils));
|
|
280
|
-
|
|
281
|
-
const stack = [ [ artifact, name ] ];
|
|
282
|
-
while (stack.length > 0) {
|
|
283
|
-
const [ a, n ] = stack.pop();
|
|
284
|
-
if (a[_dependents]) {
|
|
285
|
-
forEach(a[_dependents], (dependentName, dependent) => {
|
|
286
|
-
stack.push([ dependent, dependentName ]);
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
toDummify.push(n);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
251
|
/**
|
|
294
252
|
* Replace the artifacts in `toDummify` with simple dummy views as produced by createDummyView.
|
|
295
253
|
*/
|
|
@@ -17,7 +17,7 @@ const misc = require('./misc');
|
|
|
17
17
|
const annotations = require('./annotations');
|
|
18
18
|
const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities } = require('../db/rewriteCalculatedElements');
|
|
19
19
|
const { cloneFullCsn } = require('../../model/cloneCsn');
|
|
20
|
-
const { featureFlags } = require('../featureFlags');
|
|
20
|
+
const { featureFlags, removeFeatureFlags } = require('../featureFlags');
|
|
21
21
|
const getServiceFilterFunction = require('./service');
|
|
22
22
|
const { traverseQuery } = require('../../model/csnRefs');
|
|
23
23
|
const { expandWildcard } = require('../db/expansion');
|
|
@@ -136,6 +136,7 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
messageFunctions.throwWithError();
|
|
139
|
+
removeFeatureFlags(csn);
|
|
139
140
|
|
|
140
141
|
return csn;
|
|
141
142
|
}
|
|
@@ -34,7 +34,7 @@ const temporal = require('./db/temporal');
|
|
|
34
34
|
const associations = require('./db/associations');
|
|
35
35
|
const backlinks = require('./db/backlinks');
|
|
36
36
|
const { getDefaultTypeLengths } = require('../render/utils/common');
|
|
37
|
-
const { featureFlags } = require('./featureFlags');
|
|
37
|
+
const { featureFlags, removeFeatureFlags } = require('./featureFlags');
|
|
38
38
|
const { cloneCsnNonDict, cloneFullCsn } = require('../model/cloneCsn');
|
|
39
39
|
const { processSqlServices, createServiceDummy } = require('./db/processSqlServices');
|
|
40
40
|
const { expandWildcard } = require('./db/expansion');
|
|
@@ -465,6 +465,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
465
465
|
|
|
466
466
|
killTypes.forEach(fn => fn());
|
|
467
467
|
redoProjections.forEach(fn => fn());
|
|
468
|
+
removeFeatureFlags(csn);
|
|
468
469
|
|
|
469
470
|
timetrace.stop('Transform CSN');
|
|
470
471
|
timetrace.stop('HANA transformation');
|
|
@@ -331,10 +331,16 @@ function translateAssocsToJoins(model, inputOptions = {}) {
|
|
|
331
331
|
pos++;
|
|
332
332
|
// QA + tail is the rewritten path
|
|
333
333
|
tail = tail.slice(pos);
|
|
334
|
+
|
|
335
|
+
// leave virtual paths as is
|
|
336
|
+
if ( tail.at(-1)._artifact.virtual?.val ) {
|
|
337
|
+
replaceNodeContent(pathNode, constructPathNode([ constructTableAliasPathStep(QA), ...tail ]));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
334
341
|
// check from left to right (longest match) if a subsequent QAT is $njr
|
|
335
342
|
// if so, substitute path with pregenerated foreign key, prepend by optional
|
|
336
343
|
// (to be flattened) prefix
|
|
337
|
-
|
|
338
344
|
for (let i = 0; i < tail.length - 1; i++) {
|
|
339
345
|
if (tail[i]._navigation) {
|
|
340
346
|
// the correct flattened foreign key must match the leaf artifact and access path prefix of this path
|
|
@@ -345,36 +351,30 @@ function translateAssocsToJoins(model, inputOptions = {}) {
|
|
|
345
351
|
throw new CompilerAssertion('Debug me: No FK found for FK rewriting');
|
|
346
352
|
}
|
|
347
353
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
newTail.push({
|
|
369
|
-
id: tail.slice(i).map(ps => ps.id).join(pathDelimiter),
|
|
370
|
-
_artifact: tail.at(-1)._artifact,
|
|
371
|
-
});
|
|
372
|
-
}
|
|
354
|
+
const newTail = [];
|
|
355
|
+
// construct the tail (segment after last join relevant navigation) and preserve association steps
|
|
356
|
+
// each association may have a structured prefix
|
|
357
|
+
// `struct.foo.bar.assoc. otherStruct.baz.otherAssoc.fk`
|
|
358
|
+
// --> `[otherStruct_baz_otherAssoc, fk]
|
|
359
|
+
for (let i = 0; i < tail.length; i++) {
|
|
360
|
+
if (tail[i]._navigation) {
|
|
361
|
+
const nextNavigation = tail.slice(i + 1).findIndex(ps => ps._navigation);
|
|
362
|
+
if (nextNavigation >= 0) {
|
|
363
|
+
newTail.push({
|
|
364
|
+
id: tail.slice(i, i + 1 + nextNavigation).map(ps => ps.id).join(pathDelimiter),
|
|
365
|
+
_artifact: tail[i + nextNavigation]._artifact,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
newTail.push({
|
|
370
|
+
id: tail.slice(i).map(ps => ps.id).join(pathDelimiter),
|
|
371
|
+
_artifact: tail.at(-1)._artifact,
|
|
372
|
+
});
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
|
-
replaceNodeContent(pathNode,
|
|
376
|
-
constructPathNode([ constructTableAliasPathStep(QA), ...newTail ]));
|
|
377
375
|
}
|
|
376
|
+
replaceNodeContent(pathNode,
|
|
377
|
+
constructPathNode([ constructTableAliasPathStep(QA), ...newTail ]));
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
function findForeignKey(assoc, fk) {
|