@sap/cds-compiler 6.3.6 → 6.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/LICENSE +32 -0
- package/README.md +14 -2
- package/bin/cdsse.js +0 -3
- package/doc/CHANGELOG_BETA.md +1 -1
- package/doc/CHANGELOG_DEPRECATED.md +1 -1
- package/lib/base/message-registry.js +7 -0
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +2 -0
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/checks.js +37 -26
- package/lib/compiler/define.js +1 -1
- package/lib/compiler/extend.js +39 -50
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/lsp-api.js +1 -1
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +29 -6
- package/lib/compiler/resolve.js +13 -3
- package/lib/compiler/shared.js +31 -25
- package/lib/compiler/tweak-assocs.js +86 -28
- package/lib/compiler/xpr-rewrite.js +70 -38
- package/lib/edm/annotations/edmJson.js +206 -37
- package/lib/edm/csn2edm.js +13 -0
- package/lib/edm/edmUtils.js +2 -2
- package/lib/gen/BaseParser.js +106 -72
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1500 -1509
- package/lib/json/to-csn.js +8 -5
- package/lib/language/genericAntlrParser.js +0 -0
- package/lib/main.js +19 -16
- package/lib/model/csnRefs.js +589 -521
- package/lib/model/csnUtils.js +8 -5
- package/lib/model/enrichCsn.js +1 -0
- package/lib/parsers/AstBuildingParser.js +72 -27
- package/lib/render/toCdl.js +2 -1
- package/lib/render/toHdbcds.js +6 -3
- package/lib/render/toSql.js +5 -0
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +4 -1
- package/lib/transform/db/cdsPersistence.js +17 -18
- package/lib/transform/db/expansion.js +179 -3
- package/lib/transform/db/flattening.js +16 -5
- package/lib/transform/db/rewriteCalculatedElements.js +79 -283
- package/lib/transform/effective/main.js +8 -1
- package/lib/transform/forOdata.js +1 -1
- package/lib/transform/forRelationalDB.js +21 -80
- package/lib/transform/localized.js +65 -110
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
- package/lib/transform/transformUtils.js +23 -21
- package/lib/transform/translateAssocsToJoins.js +7 -5
- package/lib/transform/tupleExpansion.js +16 -3
- package/package.json +1 -1
- package/doc/DeprecatedOptions_v2.md +0 -150
- package/doc/NameResolution.md +0 -837
- package/lib/transform/parseExpr.js +0 -415
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
forEachDefinition,
|
|
13
13
|
forEachMember,
|
|
14
14
|
forEachGeneric,
|
|
15
|
+
isBetaEnabled,
|
|
15
16
|
} = require( '../base/model');
|
|
16
17
|
const {
|
|
17
18
|
setLink,
|
|
@@ -34,7 +35,11 @@ function propagate( model ) {
|
|
|
34
35
|
virtual,
|
|
35
36
|
notNull,
|
|
36
37
|
targetElement: onlyViaParent, // in foreign keys
|
|
37
|
-
value:
|
|
38
|
+
value: enumOrCalcValue, // enum symbol value, calculated element
|
|
39
|
+
// `value` is also used for column expression
|
|
40
|
+
// TODO(!): think of having an extra XSN property for calculated elements,
|
|
41
|
+
// replacing `value:…`+`$syntax:'calc'` and `$calc:…
|
|
42
|
+
$calc: enumOrCalcValue,
|
|
38
43
|
// masked: special = done in definer
|
|
39
44
|
// key: special = done in resolver
|
|
40
45
|
// actions: struct includes & primary source = in definer/resolver
|
|
@@ -59,7 +64,7 @@ function propagate( model ) {
|
|
|
59
64
|
// enum: expensive,
|
|
60
65
|
// params: expensive, // actually only with parent action
|
|
61
66
|
// returns,
|
|
62
|
-
$enclosed: annotation,
|
|
67
|
+
$enclosed: annotation, // TODO: hm
|
|
63
68
|
};
|
|
64
69
|
const ruleToFunction = {
|
|
65
70
|
__proto__: null,
|
|
@@ -71,7 +76,7 @@ function propagate( model ) {
|
|
|
71
76
|
for (const rule in propagationRules)
|
|
72
77
|
props[rule] = ruleToFunction[propagationRules[rule]];
|
|
73
78
|
|
|
74
|
-
const { rewriteAnnotationsRefs } = xprRewriteFns( model );
|
|
79
|
+
const { rewriteAnnotationsRefs, rewriteRefsInExpression } = xprRewriteFns( model );
|
|
75
80
|
|
|
76
81
|
const { message, throwWithError } = model.$messageFunctions;
|
|
77
82
|
|
|
@@ -165,7 +170,9 @@ function propagate( model ) {
|
|
|
165
170
|
// console.log('PROPS:',ref(source),'->',ref(target),keys.join('+'))
|
|
166
171
|
for (const prop of keys) {
|
|
167
172
|
// TODO: warning with competing props from multi-includes, but not in propagator.js
|
|
168
|
-
if (target[prop] !== undefined
|
|
173
|
+
if (target[prop] !== undefined &&
|
|
174
|
+
(prop !== 'value' || !source.$calcDepElement || !target._main?.query) ||
|
|
175
|
+
source[prop] === undefined)
|
|
169
176
|
continue;
|
|
170
177
|
const transformer = props[prop] || props[prop.charAt(0)];
|
|
171
178
|
if (transformer)
|
|
@@ -282,6 +289,22 @@ function propagate( model ) {
|
|
|
282
289
|
}
|
|
283
290
|
}
|
|
284
291
|
|
|
292
|
+
function enumOrCalcValue( prop, destination, origin ) {
|
|
293
|
+
// Remark: with include, the calc expression has been copied early
|
|
294
|
+
if (prop === 'value' && !origin.$calcDepElement) {
|
|
295
|
+
onlyViaParent( prop, destination, origin ); // enum value
|
|
296
|
+
}
|
|
297
|
+
else if (destination.kind === 'element' &&
|
|
298
|
+
destination._main?.query && // query element
|
|
299
|
+
!destination.$calc && origin.$calc !== true &&
|
|
300
|
+
isBetaEnabled( model.options, '$calcForDraft' )) {
|
|
301
|
+
destination.$calc
|
|
302
|
+
= Object.assign( copyExpr( origin[prop] ), { $inferred: 'prop' } );
|
|
303
|
+
if (rewriteRefsInExpression( destination, origin, '$calc' ))
|
|
304
|
+
destination.$calc = true; // TODO: or { val: true }?
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
285
308
|
function notWithExpand( prop, target, source ) {
|
|
286
309
|
if (!target.expand || prop === 'type' && source.elements)
|
|
287
310
|
always( prop, target, source );
|
|
@@ -296,13 +319,13 @@ function propagate( model ) {
|
|
|
296
319
|
function annotation( prop, target, source ) {
|
|
297
320
|
const anno = source[prop];
|
|
298
321
|
if (anno.val !== null)
|
|
299
|
-
withKind( prop, target, source );
|
|
322
|
+
withKind( prop, target, source ); // TODO: unfold
|
|
300
323
|
}
|
|
301
324
|
|
|
302
325
|
function docComment( prop, target, source ) {
|
|
303
326
|
if (model.options.propagateDocComments)
|
|
304
327
|
annotation( prop, target, source );
|
|
305
|
-
else // TODO:
|
|
328
|
+
else // TODO: or just "never"
|
|
306
329
|
onlyViaParent( prop, target, source );
|
|
307
330
|
}
|
|
308
331
|
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -1583,12 +1583,19 @@ function resolve( model ) {
|
|
|
1583
1583
|
// (for code completion)
|
|
1584
1584
|
const last = expr.path[expr.path.length - 1];
|
|
1585
1585
|
if (!last || !(last.args || last.where || last.cardinality) ||
|
|
1586
|
-
expr.$expected === 'approved-exists' ||
|
|
1587
1586
|
user.expand || user.inline ||
|
|
1588
1587
|
expWithFilter.includes( expected ) || // `from`, …
|
|
1589
1588
|
last._navigation?.kind === '$tableAlias') // error already reported
|
|
1590
1589
|
return ref;
|
|
1591
1590
|
|
|
1591
|
+
if (expr.$expected === 'approved-exists') {
|
|
1592
|
+
if (last.where?.args?.length === 0) {
|
|
1593
|
+
// at the moment, empty filter is not allowed on last path step
|
|
1594
|
+
reportUnexpectedArgsAndFilter( last, expected, user, null, 'last-empty-filter' );
|
|
1595
|
+
}
|
|
1596
|
+
return ref;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1592
1599
|
const type = effectiveType( last._artifact );
|
|
1593
1600
|
const art = type && (type.kind === 'entity' ? type : type.target?._artifact);
|
|
1594
1601
|
if (!art)
|
|
@@ -1596,7 +1603,8 @@ function resolve( model ) {
|
|
|
1596
1603
|
if (last.args || last.where || last.cardinality) {
|
|
1597
1604
|
const unexpectedFilter = (expected !== 'annotation' && expected !== 'column' &&
|
|
1598
1605
|
expected !== 'calc' && 'std') ||
|
|
1599
|
-
isQuasiVirtualAssociation( type ) && 'model-only'
|
|
1606
|
+
isQuasiVirtualAssociation( type ) && 'model-only' ||
|
|
1607
|
+
last.where?.args?.length === 0 && 'last-empty-filter';
|
|
1600
1608
|
reportUnexpectedArgsAndFilter( last, expected, user, art, unexpectedFilter );
|
|
1601
1609
|
}
|
|
1602
1610
|
// TODO: we should have different message-ids for the "last" stuff: adding
|
|
@@ -1696,7 +1704,9 @@ function resolve( model ) {
|
|
|
1696
1704
|
error( 'expr-unexpected-argument', loc, { '#': variant } );
|
|
1697
1705
|
}
|
|
1698
1706
|
if ((step.where || step.cardinality) && variant) {
|
|
1699
|
-
const location =
|
|
1707
|
+
const location = step.where?.location || step.cardinality?.location
|
|
1708
|
+
? combinedLocation( step.where, step.cardinality )
|
|
1709
|
+
: step.location;
|
|
1700
1710
|
// XSN TODO: filter$location including […]
|
|
1701
1711
|
error( 'expr-unexpected-filter', [ location, user ], { '#': variant } );
|
|
1702
1712
|
}
|
package/lib/compiler/shared.js
CHANGED
|
@@ -40,6 +40,7 @@ function fns( model ) {
|
|
|
40
40
|
} = model.$messageFunctions;
|
|
41
41
|
const Functions = model.$functions;
|
|
42
42
|
|
|
43
|
+
// Map `exprCtx` (is a param of traversal functions) to reference semantics
|
|
43
44
|
const referenceSemantics = {
|
|
44
45
|
// global: ------------------------------------------------------------------
|
|
45
46
|
using: { // only used to produce error message
|
|
@@ -60,10 +61,10 @@ function fns( model ) {
|
|
|
60
61
|
isMainRef: 'no-generated',
|
|
61
62
|
lexical: userBlock,
|
|
62
63
|
dynamic: modelDefinitions,
|
|
63
|
-
notFound:
|
|
64
|
+
notFound: undefinedForExtend,
|
|
64
65
|
accept: extendableArtifact,
|
|
65
66
|
},
|
|
66
|
-
|
|
67
|
+
_uncheckedExtension: { // to be used only with resolveUncheckedPath()
|
|
67
68
|
isMainRef: 'all',
|
|
68
69
|
lexical: userBlock,
|
|
69
70
|
dynamic: modelDefinitions,
|
|
@@ -76,12 +77,6 @@ function fns( model ) {
|
|
|
76
77
|
notFound: undefinedDefinition,
|
|
77
78
|
accept: acceptStructOrBare,
|
|
78
79
|
},
|
|
79
|
-
_include: { // cyclic include: no accept
|
|
80
|
-
isMainRef: 'no-generated',
|
|
81
|
-
lexical: userBlock,
|
|
82
|
-
dynamic: modelBuiltinsOrDefinitions,
|
|
83
|
-
notFound: undefinedDefinition,
|
|
84
|
-
},
|
|
85
80
|
target: {
|
|
86
81
|
isMainRef: 'no-autoexposed',
|
|
87
82
|
lexical: userBlock,
|
|
@@ -315,6 +310,7 @@ function fns( model ) {
|
|
|
315
310
|
rewriteProjectionToSelf: true,
|
|
316
311
|
}),
|
|
317
312
|
},
|
|
313
|
+
//
|
|
318
314
|
};
|
|
319
315
|
|
|
320
316
|
Object.assign( model.$functions, {
|
|
@@ -853,7 +849,7 @@ function fns( model ) {
|
|
|
853
849
|
const { isMainRef } = semantics;
|
|
854
850
|
if (isMainRef) {
|
|
855
851
|
artItemsCount = (typeof ref.scope === 'number' && ref.scope) ||
|
|
856
|
-
|
|
852
|
+
(ref.scope ? 1 : path.length);
|
|
857
853
|
}
|
|
858
854
|
let art = null;
|
|
859
855
|
const elementsEnv = semantics.navigation || environment;
|
|
@@ -1287,26 +1283,36 @@ function fns( model ) {
|
|
|
1287
1283
|
|
|
1288
1284
|
function undefinedForAnnotate( user, item, valid, _dict, prev, path ) {
|
|
1289
1285
|
// in a CSN source, only one env was tested (valid.length 1):
|
|
1290
|
-
const
|
|
1286
|
+
const name = (prev) ? `${ prev.name.id }.${ item.id }` : item.id;
|
|
1291
1287
|
if (!user.elements && !user.actions && !user.enum && !user.params &&
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1288
|
+
endsWithSuffix( name, '.drafts', art => art?._service && art.kind === 'entity' ) ||
|
|
1289
|
+
endsWithSuffix( name, '.DraftAdministrativeData',
|
|
1290
|
+
( art, prefix ) => (art?.kind === 'service' || prefix === 'DRAFT') ) ||
|
|
1291
|
+
name === 'DRAFT' && path?.length === 2 && path[1].id === 'DraftAdministrativeData' ||
|
|
1292
|
+
name.startsWith( 'localized.' )) // TODO: only if suffix is defined
|
|
1295
1293
|
return;
|
|
1296
1294
|
signalNotFound( (valid.length > 1 ? 'ext-undefined-art' : 'ext-undefined-def'),
|
|
1297
1295
|
// TODO: ext-undefined-xyz
|
|
1298
|
-
[ item.location, user ], valid, { art } );
|
|
1296
|
+
[ item.location, user ], valid, { art: name } );
|
|
1299
1297
|
}
|
|
1300
1298
|
|
|
1301
|
-
function
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1299
|
+
function undefinedForExtend( user, item, valid, _dict, prev ) {
|
|
1300
|
+
// in a CSN source, only one env was tested (valid.length 1):
|
|
1301
|
+
const name = (prev) ? `${ prev.name.id }.${ item.id }` : item.id;
|
|
1302
|
+
if (name.startsWith( 'localized.' )) {
|
|
1303
|
+
error( 'ref-undefined-art', [ user.name.location || user.location, user ],
|
|
1304
|
+
{ '#': 'localized', keyword: 'annotate' } );
|
|
1305
|
+
}
|
|
1306
|
+
else {
|
|
1307
|
+
undefinedDefinition( user, item, valid, _dict, prev );
|
|
1308
|
+
}
|
|
1306
1309
|
}
|
|
1307
1310
|
|
|
1308
|
-
function
|
|
1309
|
-
|
|
1311
|
+
function endsWithSuffix( name, suffix, cond ) {
|
|
1312
|
+
if (!name.endsWith( suffix ))
|
|
1313
|
+
return false;
|
|
1314
|
+
const prefix = name.slice( 0, -suffix.length );
|
|
1315
|
+
return cond( model.definitions[prefix], prefix, name );
|
|
1310
1316
|
}
|
|
1311
1317
|
|
|
1312
1318
|
function undefinedParam( user, head, valid, _dict, _art, _path, semantics ) {
|
|
@@ -1537,7 +1543,7 @@ function fns( model ) {
|
|
|
1537
1543
|
}
|
|
1538
1544
|
// TODO: combine $requireElementAccess/$autoElement to $bareRoot ?
|
|
1539
1545
|
else if (!user.expand && !user.inline && // $self._artifact to main artifact
|
|
1540
|
-
|
|
1546
|
+
!(art._main && art.kind !== 'select') && ref.path[0]._navigation?.kind === '$self') {
|
|
1541
1547
|
// TODO: better ref-invalid-self
|
|
1542
1548
|
const { location, id } = path[0];
|
|
1543
1549
|
error( 'ref-unexpected-self', [ location, user ], { id } );
|
|
@@ -1655,7 +1661,7 @@ function fns( model ) {
|
|
|
1655
1661
|
}
|
|
1656
1662
|
}
|
|
1657
1663
|
else if (source.kind !== 'entity' &&
|
|
1658
|
-
|
|
1664
|
+
!acceptEventProjectionSource( source, user )) {
|
|
1659
1665
|
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null,
|
|
1660
1666
|
{ '#': user._main.kind } );
|
|
1661
1667
|
return (source === art) ? art : false; // art to show cyclic issues
|
|
@@ -1937,8 +1943,8 @@ function fns( model ) {
|
|
|
1937
1943
|
// has to be run after foreign-key rewrite
|
|
1938
1944
|
const outer = user._columnParent?._origin;
|
|
1939
1945
|
let assoc = outer?.foreignKeys &&
|
|
1940
|
-
|
|
1941
|
-
|
|
1946
|
+
pathStartsWithSelf( { path } ) == null && // not $self or CDS var like $now
|
|
1947
|
+
outer;
|
|
1942
1948
|
for (let index = startIndex; index < path.length; ++index) {
|
|
1943
1949
|
if (assoc?.target) {
|
|
1944
1950
|
if (!assoc.foreignKeys) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const {
|
|
6
6
|
forEachGeneric,
|
|
7
7
|
forEachInOrder,
|
|
8
|
+
isBetaEnabled,
|
|
8
9
|
} = require('../base/model');
|
|
9
10
|
const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
|
|
10
11
|
|
|
@@ -90,7 +91,7 @@ function tweakAssocs( model ) {
|
|
|
90
91
|
|
|
91
92
|
if (art.query) {
|
|
92
93
|
traverseQueryPost(art.query, false, (query) => {
|
|
93
|
-
forEachGeneric( query, 'elements',
|
|
94
|
+
forEachGeneric( query, 'elements', handleQueryElements );
|
|
94
95
|
});
|
|
95
96
|
}
|
|
96
97
|
}
|
|
@@ -116,10 +117,10 @@ function tweakAssocs( model ) {
|
|
|
116
117
|
return;
|
|
117
118
|
const loc = [ elem.target.location, elem ];
|
|
118
119
|
const main = elem._main || elem;
|
|
119
|
-
if (!elem.$inferred && !main.$inferred) {
|
|
120
|
+
if (!elem.$inferred && !main.$inferred && !model.options.$recompile) {
|
|
120
121
|
info( 'assoc-target-not-in-service', loc,
|
|
121
122
|
{ target, '#': (elem._main.query ? 'select' : 'define') }, {
|
|
122
|
-
std: '
|
|
123
|
+
std: 'Association target $(TARGET) is outside any service', // not used
|
|
123
124
|
define: 'Target $(TARGET) of explicitly defined association is outside any service',
|
|
124
125
|
select: 'Target $(TARGET) of explicitly selected association is outside any service',
|
|
125
126
|
} );
|
|
@@ -135,6 +136,26 @@ function tweakAssocs( model ) {
|
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
function handleQueryElements( column ) {
|
|
140
|
+
rewriteAssociationCheck( column );
|
|
141
|
+
if (!isBetaEnabled( model.options, '$calcForDraft' ))
|
|
142
|
+
return;
|
|
143
|
+
const { value } = column; // `value` = column expression
|
|
144
|
+
if (!value || !value.args && !value.suffix)
|
|
145
|
+
return;
|
|
146
|
+
// TODO: what about non-simple refs (assocs, even with filter/args)?
|
|
147
|
+
|
|
148
|
+
// with “real” expressions, set $calc according to these
|
|
149
|
+
// (with references, $calc might be inherited from the source element)
|
|
150
|
+
column.$calc = copyExpr( column.value, null ); // copy while keeping location
|
|
151
|
+
|
|
152
|
+
if (traverseExpr.STOP ===
|
|
153
|
+
traverseExpr( column.$calc, 'rewrite-on', column,
|
|
154
|
+
ref => rewriteColumnPath( ref, column ) ))
|
|
155
|
+
column.$calc = true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check explicit ON / keys with REDIRECTED TO
|
|
138
159
|
function rewriteAssociationCheck( element ) {
|
|
139
160
|
const elem = element.items || element; // TODO v6: nested items
|
|
140
161
|
if (elem.elements)
|
|
@@ -732,6 +753,7 @@ function tweakAssocs( model ) {
|
|
|
732
753
|
rewritePathForEnv( expr, navEnv, assoc );
|
|
733
754
|
}
|
|
734
755
|
else if (assoc._main.query) { // from ON cond of mixin element in query
|
|
756
|
+
// here also $calc
|
|
735
757
|
const root = expr.path[0]._navigation || expr.path[0]._artifact;
|
|
736
758
|
if (expr.scope === 'param' || root?.kind === '$parameters') {
|
|
737
759
|
if (assoc.$errorReported !== 'assoc-unexpected-scope') {
|
|
@@ -743,7 +765,8 @@ function tweakAssocs( model ) {
|
|
|
743
765
|
}
|
|
744
766
|
return;
|
|
745
767
|
}
|
|
746
|
-
|
|
768
|
+
|
|
769
|
+
if (expr.path[0]._navigation) { // rewrite: src elem, mixin, $self[.elem]
|
|
747
770
|
const nav = pathNavigation( expr );
|
|
748
771
|
const elem = (assoc._origin === root) ? assoc : navProjection( nav.navigation, assoc );
|
|
749
772
|
// TODO: Use rewritePathForEnv(); make it handle mixins
|
|
@@ -823,8 +846,11 @@ function tweakAssocs( model ) {
|
|
|
823
846
|
|
|
824
847
|
for (let i = startIndex; i < ref.path.length; ++i) {
|
|
825
848
|
if (i > startIndex && art.target) {
|
|
826
|
-
//
|
|
827
|
-
//
|
|
849
|
+
// TODO: Can we combine this with the code from xpr-rewrite.js
|
|
850
|
+
// If the current artifact is an association, we need to respect the redirection
|
|
851
|
+
// chain from original target to new one. We need to use '_originalArtifact' due
|
|
852
|
+
// to secondary associations and their redirection chains. See comment in
|
|
853
|
+
// test3/Redirections/SecondaryAssocs/RedirectedPathRewriteOne.cds
|
|
828
854
|
// FIXME: Won't work with associations in projected structures.
|
|
829
855
|
const origTarget = ref.path[i - 1]?._originalArtifact?.target?._artifact;
|
|
830
856
|
const chain = cachedRedirectionChain( art, origTarget );
|
|
@@ -914,6 +940,33 @@ function tweakAssocs( model ) {
|
|
|
914
940
|
}
|
|
915
941
|
}
|
|
916
942
|
|
|
943
|
+
/**
|
|
944
|
+
* Rewrite the reference `ref` with first elem/mixin ref item `item` for user
|
|
945
|
+
* `assoc` (the query element), `elem` is the first (or preferred) query element
|
|
946
|
+
* for item.
|
|
947
|
+
*/
|
|
948
|
+
function rewriteColumnPath( ref, column ) {
|
|
949
|
+
if (!ref._artifact)
|
|
950
|
+
return null;
|
|
951
|
+
const root = ref.path?.[0];
|
|
952
|
+
const nav = pathNavigation( ref );
|
|
953
|
+
if (nav.navigation) { // TabAlias.elem, elem, mixin
|
|
954
|
+
const elem = navProjection( nav.navigation, null );
|
|
955
|
+
// TODO?: Use rewritePathForEnv(); make it handle mixins?
|
|
956
|
+
if (rewritePath( ref, nav.item, column, elem, null ))
|
|
957
|
+
return traverseExpr.STOP;
|
|
958
|
+
}
|
|
959
|
+
else if (ref.scope === 'param' || root?.kind === '$parameters') {
|
|
960
|
+
return traverseExpr.STOP;
|
|
961
|
+
}
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Rewrite the reference `ref` with first elem/mixin ref item `item` for user
|
|
967
|
+
* `assoc` (the query element), `elem` is the first (or preferred) query element
|
|
968
|
+
* for item.
|
|
969
|
+
*/
|
|
917
970
|
function rewritePath( ref, item, assoc, elem, location ) {
|
|
918
971
|
const { path } = ref;
|
|
919
972
|
const root = path[0];
|
|
@@ -928,9 +981,9 @@ function tweakAssocs( model ) {
|
|
|
928
981
|
delete root._navigation;
|
|
929
982
|
setArtifactLink( root, elem );
|
|
930
983
|
setArtifactLink( ref, elem );
|
|
931
|
-
return;
|
|
984
|
+
return true; // ERROR
|
|
932
985
|
}
|
|
933
|
-
if (item !== root) {
|
|
986
|
+
if (item !== root) { // TableAlias.item, $self.item
|
|
934
987
|
// e.g. mixin ON-condition: Base.foo -> $self.foo or multi-path projection,
|
|
935
988
|
// $projection -> $self
|
|
936
989
|
root.id = '$self';
|
|
@@ -948,7 +1001,7 @@ function tweakAssocs( model ) {
|
|
|
948
1001
|
setLink( root, '_navigation', elem );
|
|
949
1002
|
}
|
|
950
1003
|
if (!elem.name) // nothing to do for own $projection, $projection.elem
|
|
951
|
-
return;
|
|
1004
|
+
return false; // (except having it renamed to $self)
|
|
952
1005
|
item.id = elem.name.id;
|
|
953
1006
|
let state = null;
|
|
954
1007
|
for (const i of path) {
|
|
@@ -964,6 +1017,7 @@ function tweakAssocs( model ) {
|
|
|
964
1017
|
}
|
|
965
1018
|
if (state !== true)
|
|
966
1019
|
setArtifactLink( ref, state );
|
|
1020
|
+
return false;
|
|
967
1021
|
}
|
|
968
1022
|
|
|
969
1023
|
function prependSelfToPath( path, elem ) {
|
|
@@ -1049,24 +1103,6 @@ function tweakAssocs( model ) {
|
|
|
1049
1103
|
}
|
|
1050
1104
|
}
|
|
1051
1105
|
|
|
1052
|
-
function navProjection( navigation, preferred ) {
|
|
1053
|
-
// TODO: Info if more than one possibility?
|
|
1054
|
-
if (!navigation)
|
|
1055
|
-
return {};
|
|
1056
|
-
|
|
1057
|
-
if (!navigation._projections && !navigation._complexProjections)
|
|
1058
|
-
return null;
|
|
1059
|
-
|
|
1060
|
-
// _complexProjections contains projections that are not "simple",
|
|
1061
|
-
// i.e. contain a filter or arguments. Only used if it contains our
|
|
1062
|
-
// preferred association.
|
|
1063
|
-
if (preferred && ( navigation._complexProjections?.includes( preferred ) ||
|
|
1064
|
-
navigation._projections?.includes( preferred )))
|
|
1065
|
-
return preferred;
|
|
1066
|
-
|
|
1067
|
-
return navigation._projections?.[0] || null;
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
1106
|
function findRewriteTarget( expr, index, env, user ) {
|
|
1071
1107
|
if (env.kind === '$navElement' || env.kind === '$tableAlias') {
|
|
1072
1108
|
const r = firstProjectionForPath( expr.path, index, env, user );
|
|
@@ -1216,7 +1252,7 @@ function followNavigationPath( path, nav ) {
|
|
|
1216
1252
|
* - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
|
|
1217
1253
|
* - $self -> { item: undefined, tableAlias: $self }
|
|
1218
1254
|
* - $parameters.P, :P -> {}
|
|
1219
|
-
* - $now
|
|
1255
|
+
* - $now -> {}
|
|
1220
1256
|
* - undef, redef -> {}
|
|
1221
1257
|
* With 'navigation': store that navigation._artifact is projected
|
|
1222
1258
|
* With 'navigation': rewrite its ON condition
|
|
@@ -1245,4 +1281,26 @@ function pathNavigation( ref ) {
|
|
|
1245
1281
|
return { navigation: root.elements[item.id], item, tableAlias: root };
|
|
1246
1282
|
}
|
|
1247
1283
|
|
|
1284
|
+
/**
|
|
1285
|
+
* Return the first (or preferred) query elements which projections the navigation
|
|
1286
|
+
* element `navigation` (i.e. source element belonging to a specific table alias).
|
|
1287
|
+
*/
|
|
1288
|
+
function navProjection( navigation, preferred ) {
|
|
1289
|
+
// TODO: Info if more than one possibility?
|
|
1290
|
+
if (!navigation)
|
|
1291
|
+
return {};
|
|
1292
|
+
|
|
1293
|
+
if (!navigation._projections && !navigation._complexProjections)
|
|
1294
|
+
return null;
|
|
1295
|
+
|
|
1296
|
+
// _complexProjections contains projections that are not "simple",
|
|
1297
|
+
// i.e. contain a filter or arguments. Only used if it contains our
|
|
1298
|
+
// preferred association.
|
|
1299
|
+
if (preferred && ( navigation._complexProjections?.includes( preferred ) ||
|
|
1300
|
+
navigation._projections?.includes( preferred )))
|
|
1301
|
+
return preferred;
|
|
1302
|
+
|
|
1303
|
+
return navigation._projections?.[0] || null;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1248
1306
|
module.exports = tweakAssocs;
|