@sap/cds-compiler 5.1.2 → 5.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 +58 -0
- package/bin/cdsc.js +7 -2
- package/bin/cdshi.js +24 -17
- package/bin/cdsse.js +17 -18
- package/doc/CHANGELOG_BETA.md +9 -4
- package/lib/api/main.js +19 -2
- package/lib/api/options.js +4 -1
- package/lib/api/validate.js +5 -0
- package/lib/base/builtins.js +1 -0
- package/lib/base/message-registry.js +40 -3
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +0 -11
- package/lib/checks/actionsFunctions.js +0 -12
- package/lib/checks/structuredAnnoExpressions.js +10 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +2 -2
- package/lib/compiler/checks.js +25 -6
- package/lib/compiler/define.js +27 -31
- package/lib/compiler/extend.js +16 -18
- package/lib/compiler/generate.js +3 -3
- package/lib/compiler/populate.js +22 -16
- package/lib/compiler/propagator.js +3 -2
- package/lib/compiler/resolve.js +87 -94
- package/lib/compiler/shared.js +12 -13
- package/lib/compiler/tweak-assocs.js +390 -86
- package/lib/compiler/utils.js +41 -33
- package/lib/compiler/xpr-rewrite.js +45 -58
- package/lib/edm/annotations/genericTranslation.js +17 -13
- package/lib/edm/csn2edm.js +28 -4
- package/lib/edm/edm.js +68 -28
- package/lib/edm/edmInboundChecks.js +5 -8
- package/lib/edm/edmPreprocessor.js +66 -40
- package/lib/edm/edmUtils.js +1 -1
- package/lib/gen/BaseParser.js +778 -0
- package/lib/gen/CdlParser.js +4477 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +4072 -4024
- package/lib/inspect/inspectPropagation.js +1 -1
- package/lib/json/from-csn.js +5 -3
- package/lib/json/to-csn.js +7 -10
- package/lib/language/antlrParser.js +96 -0
- package/lib/language/errorStrategy.js +1 -1
- package/lib/language/genericAntlrParser.js +32 -4
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.d.ts +23 -0
- package/lib/model/cloneCsn.js +22 -13
- package/lib/model/csnUtils.js +2 -0
- package/lib/model/revealInternalProperties.js +2 -0
- package/lib/modelCompare/utils/filter.js +70 -42
- package/lib/optionProcessor.js +16 -10
- package/lib/parsers/AstBuildingParser.js +1290 -0
- package/lib/parsers/CdlGrammar.g4 +2013 -0
- package/lib/parsers/Lexer.js +249 -0
- package/lib/render/toCdl.js +46 -45
- package/lib/render/toSql.js +5 -5
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/db/applyTransformations.js +54 -16
- package/lib/transform/draft/odata.js +10 -11
- package/lib/transform/effective/flattening.js +10 -14
- package/lib/transform/forRelationalDB.js +7 -6
- package/lib/transform/odata/flattening.js +42 -31
- package/lib/transform/odata/toFinalBaseType.js +7 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
- package/share/messages/redirected-to-ambiguous.md +5 -4
- package/share/messages/redirected-to-complex.md +6 -3
package/lib/compiler/resolve.js
CHANGED
|
@@ -99,6 +99,7 @@ function resolve( model ) {
|
|
|
99
99
|
Object.assign( model.$functions, {
|
|
100
100
|
resolveExpr,
|
|
101
101
|
addForeignKeyNavigations,
|
|
102
|
+
redirectionChain,
|
|
102
103
|
} );
|
|
103
104
|
|
|
104
105
|
const ignoreSpecifiedElements
|
|
@@ -174,9 +175,10 @@ function resolve( model ) {
|
|
|
174
175
|
// TODO: or should we push elems with `expand` sibling to extra list for
|
|
175
176
|
// better messages? (Whatever that means exactly.)
|
|
176
177
|
|
|
177
|
-
if (elem.
|
|
178
|
-
if (elem.
|
|
179
|
-
// we're traversing top-level elements of the query;
|
|
178
|
+
if (elem._columnParent) {
|
|
179
|
+
if (elem._columnParent?.kind !== '$inline')
|
|
180
|
+
// we're traversing top-level elements of the query;
|
|
181
|
+
// other _columnParent kinds can't happen
|
|
180
182
|
throw new CompilerAssertion('found unexpected "expand", but expected "inline"');
|
|
181
183
|
|
|
182
184
|
if (!isPathBreakout( elem.value )) {
|
|
@@ -205,13 +207,17 @@ function resolve( model ) {
|
|
|
205
207
|
if (!nav.item._navigation) // first non-table-alias
|
|
206
208
|
setLink( nav.item, '_navigation', navItem );
|
|
207
209
|
|
|
208
|
-
if
|
|
209
|
-
|
|
210
|
+
// We consider an element only projected if the path doesn't have
|
|
211
|
+
// either arguments or filters; but we build up the navigation env
|
|
212
|
+
// nonetheless, as it makes rewriting paths later on easier.
|
|
213
|
+
let isComplexPath = !!(path[index].where || path[index].args);
|
|
214
|
+
|
|
210
215
|
++index;
|
|
211
216
|
while (navItem && index < path.length) {
|
|
212
217
|
const step = path[index];
|
|
213
|
-
if (!step?.id
|
|
218
|
+
if (!step?.id)
|
|
214
219
|
break;
|
|
220
|
+
isComplexPath ||= !!(step.where || step.args);
|
|
215
221
|
if (!navItem.elements?.[step.id]) {
|
|
216
222
|
const elements = navItem._origin?.elements ||
|
|
217
223
|
navItem._origin?.target?._artifact?.elements;
|
|
@@ -228,26 +234,26 @@ function resolve( model ) {
|
|
|
228
234
|
setLink( step, '_navigation', navItem );
|
|
229
235
|
++index;
|
|
230
236
|
}
|
|
231
|
-
// Last path step, if found, is a simple
|
|
237
|
+
// Last path step, if found, is a projected, either complex or simple.
|
|
232
238
|
if (index === path.length && navItem)
|
|
233
|
-
pushLink( navItem, '_projections', elem );
|
|
239
|
+
pushLink( navItem, isComplexPath ? '_complexProjections' : '_projections', elem );
|
|
234
240
|
}
|
|
235
241
|
}
|
|
236
242
|
|
|
237
243
|
function columnParentPath( elem ) {
|
|
238
|
-
if (!elem.
|
|
244
|
+
if (!elem._columnParent || !elem.value?.path || isPathBreakout( elem.value ))
|
|
239
245
|
return elem.value?.path;
|
|
240
246
|
|
|
241
247
|
const fullPath = [ ...elem.value.path ];
|
|
242
|
-
let
|
|
243
|
-
while (
|
|
244
|
-
if (
|
|
245
|
-
isPathBreakout(
|
|
248
|
+
let columnParent = elem._columnParent;
|
|
249
|
+
while (columnParent) {
|
|
250
|
+
if (columnParent.kind !== '$inline' || !columnParent.value?.path ||
|
|
251
|
+
isPathBreakout( columnParent.value )) {
|
|
246
252
|
// path breakout for e.g. `$self.{ foo }`, `1 as a .{ foo }`
|
|
247
253
|
return null;
|
|
248
254
|
}
|
|
249
|
-
fullPath.unshift(...
|
|
250
|
-
|
|
255
|
+
fullPath.unshift(...columnParent.value.path);
|
|
256
|
+
columnParent = columnParent._columnParent;
|
|
251
257
|
}
|
|
252
258
|
return fullPath;
|
|
253
259
|
}
|
|
@@ -285,16 +291,16 @@ function resolve( model ) {
|
|
|
285
291
|
return false;
|
|
286
292
|
}
|
|
287
293
|
|
|
288
|
-
function inheritedSourceKeyProp( { value,
|
|
294
|
+
function inheritedSourceKeyProp( { value, _columnParent } ) {
|
|
289
295
|
if (!value || !value.path)
|
|
290
296
|
return null;
|
|
291
|
-
const nav = !
|
|
297
|
+
const nav = !_columnParent && pathNavigation( value );
|
|
292
298
|
const item = value.path[value.path.length - 1];
|
|
293
299
|
if (nav?.navigation && nav.item === item)
|
|
294
300
|
return item._artifact?.key;
|
|
295
|
-
if (value.path.length !== 1 ||
|
|
301
|
+
if (value.path.length !== 1 || _columnParent?.kind !== '$inline')
|
|
296
302
|
return null;
|
|
297
|
-
const hpath =
|
|
303
|
+
const hpath = _columnParent.value?.path;
|
|
298
304
|
const head = hpath?.length === 1 && hpath[0]._navigation;
|
|
299
305
|
return head?.kind === '$tableAlias' && item._artifact?.key;
|
|
300
306
|
}
|
|
@@ -316,7 +322,7 @@ function resolve( model ) {
|
|
|
316
322
|
propagateKeys = false;
|
|
317
323
|
info( 'query-from-many', [ toMany.location, query ], { art: toMany }, {
|
|
318
324
|
std: 'Key properties are not propagated because a to-many association $(ART) is selected',
|
|
319
|
-
// eslint-disable-next-line max-len
|
|
325
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
320
326
|
element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
|
|
321
327
|
} );
|
|
322
328
|
}
|
|
@@ -344,7 +350,7 @@ function resolve( model ) {
|
|
|
344
350
|
const elem = query.elements[name];
|
|
345
351
|
|
|
346
352
|
if (!elem.$inferred && elem.value?.path) {
|
|
347
|
-
const path = elem.
|
|
353
|
+
const path = elem._columnParent ? columnParentPath( elem ) : elem.value.path;
|
|
348
354
|
if (testExpr({ path }, selectTest, () => false, elem))
|
|
349
355
|
propagateKeys = false;
|
|
350
356
|
}
|
|
@@ -357,9 +363,9 @@ function resolve( model ) {
|
|
|
357
363
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
358
364
|
info( 'query-navigate-many', [ art.location, user || query ], { art }, {
|
|
359
365
|
std: 'Navigating along to-many association $(ART) - key properties are not propagated',
|
|
360
|
-
// eslint-disable-next-line max-len
|
|
366
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
361
367
|
element: 'Navigating along to-many association $(MEMBER) of $(ART) - key properties are not propagated',
|
|
362
|
-
// eslint-disable-next-line max-len
|
|
368
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
363
369
|
alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
|
|
364
370
|
} );
|
|
365
371
|
}
|
|
@@ -395,9 +401,6 @@ function resolve( model ) {
|
|
|
395
401
|
function resolveRefs( art ) {
|
|
396
402
|
if (art.builtin)
|
|
397
403
|
return;
|
|
398
|
-
// console.log(info( null, [ art.location, art ], {}, 'REFS').toString());
|
|
399
|
-
// console.log(info( null, [ art.location, art ], { art: art.target || 'none' },
|
|
400
|
-
// 'RR: $(ART)').toString());
|
|
401
404
|
const parent = art._parent;
|
|
402
405
|
const allowedInMain = [ 'entity', 'aspect', 'event' ].includes( adHocOrMainKind( art ) );
|
|
403
406
|
const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
|
|
@@ -513,8 +516,6 @@ function resolve( model ) {
|
|
|
513
516
|
}
|
|
514
517
|
}
|
|
515
518
|
if (obj.target) {
|
|
516
|
-
// console.log(error( 'test-target', [ obj.location, obj ],
|
|
517
|
-
// { target: obj.target, kind: obj.kind }, 'Target: $(TARGET), Kind $(KIND)'));
|
|
518
519
|
if (!obj.target.$inferred || obj.target.$inferred === 'aspect-composition')
|
|
519
520
|
resolveTarget( art, obj );
|
|
520
521
|
else
|
|
@@ -530,7 +531,6 @@ function resolve( model ) {
|
|
|
530
531
|
resolvePath( art.targetElement, 'targetElement', art );
|
|
531
532
|
|
|
532
533
|
// Resolve projections/views
|
|
533
|
-
// if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
|
|
534
534
|
|
|
535
535
|
if (art.$queries)
|
|
536
536
|
art.$queries.forEach( resolveQuery );
|
|
@@ -923,14 +923,12 @@ function resolve( model ) {
|
|
|
923
923
|
// TODO: or set silent dependencies in init?
|
|
924
924
|
forEachGeneric( query, 'elements', elem => dependsOnSilent( query, elem ) );
|
|
925
925
|
forEachGeneric( query, '$tableAliases', ( alias ) => {
|
|
926
|
-
// console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
|
|
927
926
|
if (alias.kind === 'mixin')
|
|
928
927
|
resolveRefs( alias ); // mixin element
|
|
929
928
|
else if (alias.kind !== '$self')
|
|
930
929
|
// pure path has been resolved, resolve args and filter now:
|
|
931
930
|
resolveExpr( alias, 'from', query._parent );
|
|
932
931
|
} );
|
|
933
|
-
// if (!query.$inlines) console.log('RQ:',query)
|
|
934
932
|
for (const col of query.$inlines)
|
|
935
933
|
resolveExpr( col.value, 'column', col );
|
|
936
934
|
// for (const col of query.$inlines)
|
|
@@ -1008,7 +1006,7 @@ function resolve( model ) {
|
|
|
1008
1006
|
}
|
|
1009
1007
|
const target = resolvePath( obj.target, 'target', art );
|
|
1010
1008
|
|
|
1011
|
-
if (obj.
|
|
1009
|
+
if (obj._columnParent && obj.type && !obj.type.$inferred && art._main && art._main.query) {
|
|
1012
1010
|
// New association inside expand/inline: The on-condition can't be properly checked,
|
|
1013
1011
|
// so abort early. See #8797
|
|
1014
1012
|
error( 'query-unexpected-assoc', [ obj.name.location, art ], {},
|
|
@@ -1090,17 +1088,8 @@ function resolve( model ) {
|
|
|
1090
1088
|
issue['#'] = 'order';
|
|
1091
1089
|
}
|
|
1092
1090
|
}
|
|
1093
|
-
if (issue['#'])
|
|
1094
|
-
|
|
1095
|
-
message( 'type-expecting-service-target', [ art.target.location, art ], issue, {
|
|
1096
|
-
std: 'Expecting service entity $(TARGET)',
|
|
1097
|
-
ref: 'Expecting service entity $(TARGET); its element $(ID) referred to at line $(LINE), column $(COL) is not from an element with the same name in the provided model target',
|
|
1098
|
-
key: 'Expecting service entity $(TARGET); its key element $(ID) is not from a key element with the same name in the provided model target',
|
|
1099
|
-
missing: 'Expecting service entity $(TARGET); it does not have the key element $(ID) of the provided model target',
|
|
1100
|
-
order: 'Expecting service entity $(TARGET); its key elements are in a different order than those of the provided model target',
|
|
1101
|
-
} );
|
|
1102
|
-
/* eslint-enable max-len */
|
|
1103
|
-
}
|
|
1091
|
+
if (issue['#'])
|
|
1092
|
+
message( 'type-expecting-service-target', [ art.target.location, art ], issue );
|
|
1104
1093
|
}
|
|
1105
1094
|
|
|
1106
1095
|
function keyElementNames( elements ) {
|
|
@@ -1138,7 +1127,7 @@ function resolve( model ) {
|
|
|
1138
1127
|
function addImplicitForeignKeys( art, obj, target ) {
|
|
1139
1128
|
obj.foreignKeys = Object.create( null );
|
|
1140
1129
|
forEachInOrder( target, 'elements', ( elem, name ) => {
|
|
1141
|
-
if (elem.key
|
|
1130
|
+
if (elem.key?.val) {
|
|
1142
1131
|
const location = weakLocation( obj.target.location );
|
|
1143
1132
|
const key = {
|
|
1144
1133
|
name: { location, id: elem.name.id, $inferred: 'keys' },
|
|
@@ -1223,7 +1212,7 @@ function resolve( model ) {
|
|
|
1223
1212
|
const text = (item !== last) ? 'sub' : 'std';
|
|
1224
1213
|
error( 'duplicate-key-ref', [ item.location, key ], { '#': text, name }, {
|
|
1225
1214
|
std: 'Foreign key $(NAME) already refers to the same target element',
|
|
1226
|
-
// eslint-disable-next-line max-len
|
|
1215
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1227
1216
|
sub: 'Foreign key $(NAME) already refers to the target element whose sub element is again referred to here',
|
|
1228
1217
|
// TODO: please add ideas for a better text, e.g. to (closed) PR #11325
|
|
1229
1218
|
} );
|
|
@@ -1241,9 +1230,8 @@ function resolve( model ) {
|
|
|
1241
1230
|
const origType = assoc && effectiveType( assoc );
|
|
1242
1231
|
if (origType === 0)
|
|
1243
1232
|
return;
|
|
1244
|
-
if (!origType
|
|
1245
|
-
const
|
|
1246
|
-
const loc = (path && path[path.length - 1] || elem.value || elem).location;
|
|
1233
|
+
if (!origType?.target) {
|
|
1234
|
+
const loc = (elem.value?.path?.at(-1) || elem.value || elem).location;
|
|
1247
1235
|
error( 'redirected-no-assoc', [ loc, elem ], {},
|
|
1248
1236
|
'Only an association can be redirected' );
|
|
1249
1237
|
return;
|
|
@@ -1253,63 +1241,68 @@ function resolve( model ) {
|
|
|
1253
1241
|
error( 'type-invalid-cast', [ elem.type.location, elem ], { '#': 'assoc' } );
|
|
1254
1242
|
return;
|
|
1255
1243
|
}
|
|
1256
|
-
|
|
1257
|
-
// .toString(), elem.value)
|
|
1258
|
-
const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
|
|
1259
|
-
if (nav && nav.item !== elem.value.path[elem.value.path.length - 1]) {
|
|
1260
|
-
if (!elem.on && origType.on) {
|
|
1261
|
-
error( 'rewrite-not-supported', [ elem.target.location, elem ] );
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1244
|
+
|
|
1265
1245
|
const origTarget = origType.target._artifact;
|
|
1266
|
-
// console.log(require('../model/revealInternalProperties').ref(elem),
|
|
1267
|
-
// !!origTarget,!!origType._effectiveType,!!origType.target)
|
|
1268
|
-
if (!origTarget || !target)
|
|
1269
|
-
return;
|
|
1270
1246
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
|
|
1278
|
-
'The redirected target is the original $(ART)' );
|
|
1279
|
-
}
|
|
1280
|
-
setLink( elem, '_redirected', chain ); // store the chain
|
|
1281
|
-
return;
|
|
1247
|
+
if (target === origTarget && !elem.target.$inferred && !elem.on && !elem.foreignKeys) {
|
|
1248
|
+
// Only a managed redirection gets this info message. Because otherwise
|
|
1249
|
+
// we'd have to check whether on-condition/foreignKeys are the same.
|
|
1250
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
1251
|
+
info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
|
|
1252
|
+
'The redirected target is the original $(ART)' );
|
|
1282
1253
|
}
|
|
1254
|
+
|
|
1283
1255
|
if (elem.foreignKeys || elem.on)
|
|
1284
1256
|
return; // TODO: or should we still bring an msg if nothing in common?
|
|
1257
|
+
|
|
1258
|
+
const chain = redirectionChain( elem, target, origTarget );
|
|
1259
|
+
setLink( elem, '_redirected', chain );
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Get the redirection chain between a target and the original target.
|
|
1264
|
+
*
|
|
1265
|
+
* @param {XSN.Artifact} elem
|
|
1266
|
+
* @param {XSN.Artifact} target
|
|
1267
|
+
* @param {XSN.Artifact} origTarget
|
|
1268
|
+
* @param {boolean} [silent] Whether to report error and other messages.
|
|
1269
|
+
* @returns {XSN.Artifact[]|null}
|
|
1270
|
+
*/
|
|
1271
|
+
function redirectionChain( elem, target, origTarget, silent = false ) {
|
|
1272
|
+
if (!origTarget || !target)
|
|
1273
|
+
return null;
|
|
1274
|
+
if (target === origTarget)
|
|
1275
|
+
return []; // e.g. explicit ON-condition/foreign keys or original target
|
|
1276
|
+
|
|
1277
|
+
const chain = [];
|
|
1285
1278
|
// now check whether target and origTarget are "related"
|
|
1279
|
+
// first: check via simple projections
|
|
1286
1280
|
while (target.query) {
|
|
1287
1281
|
const from = target.query.args ? {} : target.query.from;
|
|
1288
1282
|
if (!from)
|
|
1289
|
-
return;
|
|
1283
|
+
return null; // parse error - TODO: or UNION?
|
|
1290
1284
|
if (!from.path) {
|
|
1285
|
+
if (silent)
|
|
1286
|
+
break;
|
|
1291
1287
|
const isTarget = target === elem.target._artifact;
|
|
1292
1288
|
const op = from.op?.val || target.query.op?.val;
|
|
1293
1289
|
const variant = (!isTarget && 'std') || (op && 'targetOp') || 'target';
|
|
1294
|
-
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
1295
1290
|
info( 'redirected-to-complex', [ elem.target.location, elem ],
|
|
1296
|
-
{ art: target, '#': variant, keyword: op || '' }
|
|
1297
|
-
std: 'Redirection involves the complex view $(ART)',
|
|
1298
|
-
target: 'The redirected target $(ART) is a complex view',
|
|
1299
|
-
targetOp: 'The redirected target $(ART) is a complex view with $(KEYWORD)',
|
|
1300
|
-
} );
|
|
1291
|
+
{ art: target, '#': variant, keyword: op || '' } );
|
|
1301
1292
|
break;
|
|
1302
1293
|
}
|
|
1303
1294
|
target = from._artifact;
|
|
1304
1295
|
if (!target)
|
|
1305
|
-
return;
|
|
1296
|
+
return null;
|
|
1306
1297
|
chain.push( from );
|
|
1307
1298
|
if (target === origTarget) {
|
|
1299
|
+
// found in simple projection chain
|
|
1308
1300
|
chain.reverse();
|
|
1309
|
-
|
|
1310
|
-
return;
|
|
1301
|
+
return chain;
|
|
1311
1302
|
}
|
|
1312
1303
|
}
|
|
1304
|
+
|
|
1305
|
+
// there is a complex view in-between; search through table aliases
|
|
1313
1306
|
let redirected = null;
|
|
1314
1307
|
chain.reverse();
|
|
1315
1308
|
let news = [ { chain, sources: [ target ] } ];
|
|
@@ -1327,25 +1320,25 @@ function resolve( model ) {
|
|
|
1327
1320
|
else if (!redirected) {
|
|
1328
1321
|
redirected = (s.kind === '$tableAlias') ? [ s, ...o.chain ] : o.chain;
|
|
1329
1322
|
}
|
|
1330
|
-
else {
|
|
1323
|
+
else if (!silent) {
|
|
1331
1324
|
error( 'redirected-to-ambiguous', [ elem.target.location, elem ], { art: origTarget },
|
|
1332
1325
|
'The redirected target originates more than once from $(ART)' );
|
|
1333
|
-
return;
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
else {
|
|
1329
|
+
return null;
|
|
1334
1330
|
}
|
|
1335
1331
|
}
|
|
1336
1332
|
}
|
|
1337
1333
|
}
|
|
1338
|
-
if (redirected) {
|
|
1339
|
-
setLink( elem, '_redirected', redirected );
|
|
1340
|
-
}
|
|
1341
|
-
else if (redirected == null) {
|
|
1334
|
+
if (!silent && redirected == null) {
|
|
1342
1335
|
error( 'redirected-to-unrelated', [ elem.target.location, elem ], { art: origTarget },
|
|
1343
1336
|
'The redirected target does not originate from $(ART)' );
|
|
1344
1337
|
}
|
|
1345
|
-
return;
|
|
1338
|
+
return redirected;
|
|
1346
1339
|
|
|
1347
1340
|
// B = proj on A, C = A x B, X = { a: assoc to A on a.Q1 = ...}, Y = X.{ a: redirected to C }
|
|
1348
|
-
// what does a: redirected to C
|
|
1341
|
+
// what does 'a: redirected to C' mean?
|
|
1349
1342
|
// -> collect all elements Qi used in ON (corr: foreign keys)
|
|
1350
1343
|
// -> only use an tableAlias which has propagation for all elements
|
|
1351
1344
|
// no - error if the original target can be reached twice
|
|
@@ -1377,7 +1370,7 @@ function resolve( model ) {
|
|
|
1377
1370
|
if (a.path && a.kind !== '$self' && a.kind !== 'mixin')
|
|
1378
1371
|
sources.push( a );
|
|
1379
1372
|
}
|
|
1380
|
-
if (alias.kind === '$
|
|
1373
|
+
if (alias.kind === '$tableAlias')
|
|
1381
1374
|
news.push( { chain: [ alias, ...chain ], sources } );
|
|
1382
1375
|
else
|
|
1383
1376
|
news.push( { chain, sources } );
|
|
@@ -1436,7 +1429,6 @@ function resolve( model ) {
|
|
|
1436
1429
|
}
|
|
1437
1430
|
|
|
1438
1431
|
function resolveExpr( expr, exprCtx, user ) {
|
|
1439
|
-
// console.log(expr?.location.line,exprCtx)
|
|
1440
1432
|
traverseExpr( expr, exprCtx, user, resolveExprItem );
|
|
1441
1433
|
}
|
|
1442
1434
|
|
|
@@ -1513,7 +1505,7 @@ function resolve( model ) {
|
|
|
1513
1505
|
message( 'expr-unexpected-filter', [ location, user ], { '#': variant }, {
|
|
1514
1506
|
std: 'A filter can only be provided when navigating along associations',
|
|
1515
1507
|
// to help users for `… from E:toF { toF[…].x }`
|
|
1516
|
-
// eslint-disable-next-line max-len
|
|
1508
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1517
1509
|
tableAlias: 'A filter can only be provided when navigating along associations, but found table alias',
|
|
1518
1510
|
from: 'A filter can only be provided for the source entity or associations',
|
|
1519
1511
|
} );
|
|
@@ -1593,7 +1585,8 @@ function pathNavigation( ref ) {
|
|
|
1593
1585
|
if (root.kind === '$self')
|
|
1594
1586
|
return { item, tableAlias: root };
|
|
1595
1587
|
if (root.kind !== '$tableAlias' || ref.path.length < 2)
|
|
1596
|
-
return {};
|
|
1588
|
+
return {}; // should not happen
|
|
1589
|
+
// table alias
|
|
1597
1590
|
return { navigation: root.elements?.[item.id], item, tableAlias: root };
|
|
1598
1591
|
}
|
|
1599
1592
|
|
package/lib/compiler/shared.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Compiler functions and utilities shared across all phases
|
|
2
|
-
// TODO: rename to paths.js and move non resolve-paths functions to somewhere else
|
|
3
2
|
|
|
4
3
|
'use strict';
|
|
5
4
|
|
|
@@ -546,7 +545,7 @@ function fns( model ) {
|
|
|
546
545
|
ruser = ruser._outer;
|
|
547
546
|
|
|
548
547
|
// Handle expand/inline, `type of`, :param, global (internally for CDL):
|
|
549
|
-
if (user.
|
|
548
|
+
if (user._columnParent && !semantics.isMainRef) { // in expand/inline
|
|
550
549
|
const { name } = semantics;
|
|
551
550
|
semantics = semantics.nestedColumn();
|
|
552
551
|
semantics.name = name;
|
|
@@ -563,7 +562,7 @@ function fns( model ) {
|
|
|
563
562
|
|
|
564
563
|
// Search in lexical environments, including $self/$projection:
|
|
565
564
|
const { isMainRef } = semantics;
|
|
566
|
-
const lexical = semantics.lexical?.( ruser ); // TODO:
|
|
565
|
+
const lexical = semantics.lexical?.( ruser ); // TODO: _columnParent?
|
|
567
566
|
if (lexical) {
|
|
568
567
|
const [ nextProp, dictProp ] = (isMainRef)
|
|
569
568
|
? [ '_block', 'artifacts' ]
|
|
@@ -608,7 +607,7 @@ function fns( model ) {
|
|
|
608
607
|
// element item in the path)
|
|
609
608
|
// TODO - think about setting _navigation for all $navElement – the
|
|
610
609
|
// "ref: ['tabAlias']: inline: […]" handling might be easier
|
|
611
|
-
// (no
|
|
610
|
+
// (no _columnParent consultation for key prop and renaming support)
|
|
612
611
|
function getPathItem( ref, semantics, user ) {
|
|
613
612
|
// let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
|
|
614
613
|
const { path } = ref;
|
|
@@ -918,7 +917,7 @@ function fns( model ) {
|
|
|
918
917
|
}
|
|
919
918
|
|
|
920
919
|
function nestedElements( user ) {
|
|
921
|
-
const colParent = user.
|
|
920
|
+
const colParent = user._columnParent;
|
|
922
921
|
Functions.effectiveType( colParent ); // set _origin
|
|
923
922
|
const path = colParent?.value?.path;
|
|
924
923
|
if (!path?.length)
|
|
@@ -1129,7 +1128,7 @@ function fns( model ) {
|
|
|
1129
1128
|
}
|
|
1130
1129
|
|
|
1131
1130
|
function undefinedNestedElement( user, head, valid, _dict, _art, path, semantics ) {
|
|
1132
|
-
const art = user.
|
|
1131
|
+
const art = user._columnParent._origin;
|
|
1133
1132
|
if (!art)
|
|
1134
1133
|
return null; // no consequential error
|
|
1135
1134
|
return undefinedItemElement( user, head, valid, null, art, path, semantics );
|
|
@@ -1226,7 +1225,7 @@ function fns( model ) {
|
|
|
1226
1225
|
// of invisible table aliases; at least one stakeholder uses this,
|
|
1227
1226
|
// so it can't be an error (yet).
|
|
1228
1227
|
message( 'ref-deprecated-self-element', [ ref.path[0].location, user._user ], {},
|
|
1229
|
-
// eslint-disable-next-line max-len
|
|
1228
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1230
1229
|
'Referring to the query\'s own elements here might lead to invalid SQL references; use source elements only' );
|
|
1231
1230
|
return false;
|
|
1232
1231
|
default:
|
|
@@ -1551,7 +1550,7 @@ function fns( model ) {
|
|
|
1551
1550
|
const location = (user.expand || user.inline)[$location];
|
|
1552
1551
|
// mention `table alias` in text only with initial single path item ref,
|
|
1553
1552
|
// but do not mention that $self { … } is allowed, shouldn't be advertised:
|
|
1554
|
-
const txt = (path.length > 1 || user.
|
|
1553
|
+
const txt = (path.length > 1 || user._columnParent) ? 'struct' : 'init';
|
|
1555
1554
|
const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
|
|
1556
1555
|
message( 'def-unexpected-nested-proj', [ location, user ], { '#': txt, code } );
|
|
1557
1556
|
}
|
|
@@ -1618,7 +1617,7 @@ function fns( model ) {
|
|
|
1618
1617
|
else if (target._artifact && target._artifact !== user._main && user._main.kind === 'entity') {
|
|
1619
1618
|
const last = path[path.length - 1];
|
|
1620
1619
|
warning( 'ref-invalid-backlink', [ last.location, user ], { art: target, id: '$self' },
|
|
1621
|
-
// eslint-disable-next-line max-len
|
|
1620
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1622
1621
|
'The target $(ART) of the association is not the current entity represented by $(ID)' );
|
|
1623
1622
|
}
|
|
1624
1623
|
}
|
|
@@ -1655,7 +1654,7 @@ function fns( model ) {
|
|
|
1655
1654
|
|
|
1656
1655
|
function checkOnlyForeignKeyNavigation( user, path, startIndex = 0, msgPrefix = '' ) {
|
|
1657
1656
|
// has to be run after foreign-key rewrite
|
|
1658
|
-
const outer = user.
|
|
1657
|
+
const outer = user._columnParent?._origin;
|
|
1659
1658
|
let assoc = outer?.foreignKeys &&
|
|
1660
1659
|
pathStartsWithSelf( { path } ) == null && // not $self or CDS var like $now
|
|
1661
1660
|
outer;
|
|
@@ -1708,11 +1707,11 @@ function fns( model ) {
|
|
|
1708
1707
|
std: 'Can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1709
1708
|
keys: 'Can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1710
1709
|
complete: 'The reference must cover a full foreign key reference of association $(ART)',
|
|
1711
|
-
// eslint-disable-next-line max-len
|
|
1710
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1712
1711
|
'self-std': 'In column ref starting with $(ALIAS), we can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1713
|
-
// eslint-disable-next-line max-len
|
|
1712
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1714
1713
|
'self-keys': 'In column ref starting with $(ALIAS), we can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1715
|
-
// eslint-disable-next-line max-len
|
|
1714
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1716
1715
|
'self-complete': 'The column reference starting with $(ALIAS) must cover a full foreign key reference of association $(ART)',
|
|
1717
1716
|
} );
|
|
1718
1717
|
// TODO later: mention allowed ones
|