@sap/cds-compiler 6.2.2 → 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 +35 -0
- package/bin/cdsc.js +11 -4
- package/lib/api/options.js +1 -1
- package/lib/base/message-registry.js +36 -7
- package/lib/base/messages.js +11 -4
- package/lib/base/model.js +0 -1
- package/lib/checks/assocOutsideService.js +17 -30
- package/lib/checks/checkForTypes.js +0 -18
- package/lib/checks/checkPathsInStoredCalcElement.js +2 -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/checks.js +47 -18
- package/lib/compiler/index.js +88 -6
- package/lib/compiler/resolve.js +7 -7
- package/lib/compiler/tweak-assocs.js +47 -25
- package/lib/gen/BaseParser.js +1 -1
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +381 -378
- package/lib/gen/Dictionary.json +0 -2
- package/lib/model/csnRefs.js +9 -4
- package/lib/model/csnUtils.js +67 -2
- package/lib/optionProcessor.js +2 -3
- package/lib/parsers/AstBuildingParser.js +5 -6
- package/lib/render/toCdl.js +10 -4
- package/lib/render/utils/common.js +4 -2
- 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/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 +135 -115
- 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 +510 -571
- package/lib/transform/tupleExpansion.js +495 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/package.json +1 -1
- package/lib/base/cleanSymbols.js +0 -17
- package/lib/checks/nonexpandableStructured.js +0 -39
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
'use strict'
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
setProp, forEachGeneric, forEachDefinition, isBetaEnabled,
|
|
5
|
+
} = require('../base/model');
|
|
4
6
|
const { makeMessageFunction } = require('../base/messages');
|
|
5
7
|
const { recompileX } = require('../compiler/index');
|
|
6
8
|
const { linkToOrigin, pathName } = require('../compiler/utils');
|
|
7
|
-
const
|
|
9
|
+
const { compactModel, compactExpr } = require('../json/to-csn');
|
|
8
10
|
const { deduplicateMessages } = require('../base/messages');
|
|
9
11
|
const { timetrace } = require('../utils/timetrace');
|
|
10
|
-
const {CompilerAssertion} = require('../base/error');
|
|
12
|
+
const { CompilerAssertion } = require('../base/error');
|
|
11
13
|
// Paths that start with an artifact of protected kind are special
|
|
12
14
|
// either ignore them in QAT building or in path rewriting
|
|
13
|
-
const internalArtifactKinds = ['builtin', '$parameters', 'param'];
|
|
15
|
+
const internalArtifactKinds = [ 'builtin', '$parameters', 'param' ];
|
|
14
16
|
|
|
15
|
-
function translateAssocsToJoinsCSN(csn, options){
|
|
17
|
+
function translateAssocsToJoinsCSN(csn, options) {
|
|
16
18
|
timetrace.start('A2J: Recompiling model');
|
|
17
19
|
// Do not re-complain about localized
|
|
18
20
|
const compileOptions = { ...options, $skipNameCheck: true };
|
|
@@ -24,7 +26,7 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
24
26
|
translateAssocsToJoins(model, options);
|
|
25
27
|
timetrace.stop('A2J: Translating associations to joins');
|
|
26
28
|
// Use the effective elements list as columns
|
|
27
|
-
forEachDefinition(model, art => {
|
|
29
|
+
forEachDefinition(model, (art) => {
|
|
28
30
|
if (art.$queries) {
|
|
29
31
|
for (const query of art.$queries) {
|
|
30
32
|
query.columns = Object.values(query.elements);
|
|
@@ -46,8 +48,7 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
46
48
|
return compactModel(model, compileOptions);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
function translateAssocsToJoins(model, inputOptions = {})
|
|
50
|
-
{
|
|
51
|
+
function translateAssocsToJoins(model, inputOptions = {}) {
|
|
51
52
|
const { error, warning, throwWithError } = makeMessageFunction(model, inputOptions, 'a2j');
|
|
52
53
|
|
|
53
54
|
const options = model.options || inputOptions;
|
|
@@ -66,32 +67,27 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
66
67
|
|
|
67
68
|
return model;
|
|
68
69
|
|
|
69
|
-
function prepareAssociations(art)
|
|
70
|
-
|
|
71
|
-
if(art.kind === 'element' && art.target)
|
|
72
|
-
{
|
|
70
|
+
function prepareAssociations(art) {
|
|
71
|
+
if (art.kind === 'element' && art.target) {
|
|
73
72
|
/* Create the prefix string up to the main artifact which is
|
|
74
73
|
prepended to all source side paths of the resulting ON condition
|
|
75
74
|
(cut off name.id from name.element)
|
|
76
75
|
*/
|
|
77
76
|
art.$elementPrefix = '';
|
|
78
|
-
for(let parent = art._parent; parent?.kind === 'element'; parent = parent._parent)
|
|
77
|
+
for (let parent = art._parent; parent?.kind === 'element'; parent = parent._parent)
|
|
79
78
|
art.$elementPrefix = parent.name.id + pathDelimiter + art.$elementPrefix;
|
|
80
79
|
|
|
81
80
|
/*
|
|
82
81
|
Create path prefix tree for Foreign Keys, required to substitute
|
|
83
82
|
aliases in ON cond calculation, also very useful to detect fk overlaps.
|
|
84
83
|
*/
|
|
85
|
-
if(art.foreignKeys && !art.$fkPathPrefixTree)
|
|
86
|
-
{
|
|
84
|
+
if (art.foreignKeys && !art.$fkPathPrefixTree) {
|
|
87
85
|
art.$fkPathPrefixTree = { children: Object.create(null) };
|
|
88
|
-
forEachGeneric(art, 'foreignKeys', fk => {
|
|
86
|
+
forEachGeneric(art, 'foreignKeys', (fk) => {
|
|
89
87
|
let ppt = art.$fkPathPrefixTree;
|
|
90
|
-
fk.targetElement.path.forEach(ps => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
else
|
|
94
|
-
ppt = ppt.children[ps.id];
|
|
88
|
+
fk.targetElement.path.forEach((ps) => {
|
|
89
|
+
ppt.children[ps.id] ??= { children: Object.create(null) };
|
|
90
|
+
ppt = ppt.children[ps.id];
|
|
95
91
|
});
|
|
96
92
|
ppt._fk = fk;
|
|
97
93
|
});
|
|
@@ -101,14 +97,13 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
101
97
|
forEachGeneric(art, 'elements', prepareAssociations);
|
|
102
98
|
}
|
|
103
99
|
|
|
104
|
-
function transformQueries(art)
|
|
105
|
-
|
|
106
|
-
if(art.$queries === undefined)
|
|
100
|
+
function transformQueries(art) {
|
|
101
|
+
if (art.$queries === undefined)
|
|
107
102
|
return;
|
|
108
103
|
|
|
109
104
|
function forEachQuery(callback, env) {
|
|
110
|
-
art.$queries.forEach((q,i) => {
|
|
111
|
-
if(env !== undefined)
|
|
105
|
+
art.$queries.forEach((q, i) => {
|
|
106
|
+
if (env !== undefined)
|
|
112
107
|
env.queryIndex = i;
|
|
113
108
|
callback(q, env);
|
|
114
109
|
});
|
|
@@ -116,7 +111,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
116
111
|
|
|
117
112
|
const env = {
|
|
118
113
|
aliasCount: 0,
|
|
119
|
-
walkover: {
|
|
114
|
+
walkover: {
|
|
115
|
+
from: true, onCondFrom: true, select: true, filter: true,
|
|
116
|
+
},
|
|
120
117
|
};
|
|
121
118
|
/*
|
|
122
119
|
Setup QAs for mixins
|
|
@@ -140,7 +137,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
140
137
|
forEachQuery(createQAForFromClauseSubQuery, env);
|
|
141
138
|
|
|
142
139
|
// 2) Walk over each from table path, transform it into a join tree
|
|
143
|
-
env.walkover = {
|
|
140
|
+
env.walkover = {
|
|
141
|
+
from: true, onCondFrom: false, select: false, filter: false,
|
|
142
|
+
};
|
|
144
143
|
env.callback = createInnerJoins;
|
|
145
144
|
forEachQuery(walkQuery, env);
|
|
146
145
|
|
|
@@ -152,7 +151,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
152
151
|
// 4) Rewrite ON condition paths that are part of the original FROM block
|
|
153
152
|
// (same rewrite as (injected) assoc ON cond paths but with different table alias).
|
|
154
153
|
// 5) Prepend table alias to all remaining paths
|
|
155
|
-
env.walkover = {
|
|
154
|
+
env.walkover = {
|
|
155
|
+
from: false, onCondFrom: true, select: true, filter: false,
|
|
156
|
+
};
|
|
156
157
|
env.callback = substituteDollarSelf;
|
|
157
158
|
forEachQuery(walkQuery, env);
|
|
158
159
|
env.callback = rewriteGenericPaths;
|
|
@@ -163,8 +164,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
// Transform each FROM table path into a join tree and attach the tree to the path object
|
|
166
|
-
function createInnerJoins(fromPathNode, env)
|
|
167
|
-
{
|
|
167
|
+
function createInnerJoins(fromPathNode, env) {
|
|
168
168
|
const fqat = env.lead.$tableAliases[fromPathNode.name.id].$fqat;
|
|
169
169
|
const joinTree = createJoinTree(env, undefined, fqat, 'inner', '$fqat', undefined);
|
|
170
170
|
|
|
@@ -172,16 +172,12 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// Translate all other join relevant query paths into left outer join tree and attach it to the lead query
|
|
175
|
-
function createLeftOuterJoins(query, env)
|
|
176
|
-
|
|
177
|
-
if(query.op.val === 'SELECT')
|
|
178
|
-
{
|
|
175
|
+
function createLeftOuterJoins(query, env) {
|
|
176
|
+
if (query.op.val === 'SELECT') {
|
|
179
177
|
env.lead = query;
|
|
180
178
|
let joinTree = query.from;
|
|
181
|
-
for(const tan in query.$tableAliases)
|
|
182
|
-
|
|
183
|
-
if(query.$tableAliases[tan].kind !== '$self') // don't drive into $projection/$self tableAlias (yet)
|
|
184
|
-
{
|
|
179
|
+
for (const tan in query.$tableAliases) {
|
|
180
|
+
if (query.$tableAliases[tan].kind !== '$self') { // don't drive into $projection/$self tableAlias (yet)
|
|
185
181
|
const ta = query.$tableAliases[tan];
|
|
186
182
|
joinTree = createJoinTree(env, joinTree, ta.$qat, 'left', '$qat', ta.$QA);
|
|
187
183
|
}
|
|
@@ -198,12 +194,11 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
198
194
|
must be created and added separately to the lead query $tableAlias'es.
|
|
199
195
|
Also, the name of the subquery (the alias) needs to be set to the final QA alias name.
|
|
200
196
|
*/
|
|
201
|
-
function createQAForFromClauseSubQuery(query, env)
|
|
202
|
-
{
|
|
197
|
+
function createQAForFromClauseSubQuery(query, env) {
|
|
203
198
|
for (const taName in query.$tableAliases) {
|
|
204
199
|
if (query.$tableAliases[taName].kind !== '$self') {
|
|
205
200
|
const ta = query.$tableAliases[taName];
|
|
206
|
-
if(!ta.$QA) {
|
|
201
|
+
if (!ta.$QA) {
|
|
207
202
|
let alias = taName;
|
|
208
203
|
if (ta.name.$inferred === '$internal') {
|
|
209
204
|
// query has no explicit table alias, i.e. is internal: make it visible and remove `$`
|
|
@@ -213,9 +208,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
213
208
|
}
|
|
214
209
|
ta.$QA = createQA(env, ta._origin, alias, undefined);
|
|
215
210
|
incAliasCount(env, ta.$QA);
|
|
216
|
-
if(ta.name && ta.name.id)
|
|
211
|
+
if (ta.name && ta.name.id)
|
|
217
212
|
ta.name.id = ta.$QA.name.id;
|
|
218
|
-
}
|
|
219
213
|
}
|
|
220
214
|
}
|
|
221
215
|
}
|
|
@@ -238,15 +232,12 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
238
232
|
aliases.
|
|
239
233
|
|
|
240
234
|
*/
|
|
241
|
-
function createQAForMixinAssoc(query, env)
|
|
242
|
-
|
|
243
|
-
if(query.op.val === 'SELECT')
|
|
244
|
-
{
|
|
235
|
+
function createQAForMixinAssoc(query, env) {
|
|
236
|
+
if (query.op.val === 'SELECT') {
|
|
245
237
|
env.lead = query;
|
|
246
238
|
// use view as QA origin
|
|
247
|
-
forEachGeneric(query, 'mixin', art => {
|
|
248
|
-
if(!art.$QA)
|
|
249
|
-
{
|
|
239
|
+
forEachGeneric(query, 'mixin', (art) => {
|
|
240
|
+
if (!art.$QA) {
|
|
250
241
|
art.$QA = createQA(env, art.target._artifact, art.name.id );
|
|
251
242
|
art.$QA.mixin = true;
|
|
252
243
|
}
|
|
@@ -257,29 +248,28 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
257
248
|
/*
|
|
258
249
|
Substitute $self/$projection expression with its value
|
|
259
250
|
*/
|
|
260
|
-
function substituteDollarSelf(pathNode, env)
|
|
261
|
-
{
|
|
251
|
+
function substituteDollarSelf(pathNode, env) {
|
|
262
252
|
// do not substitute $self values for outer order by clauses
|
|
263
253
|
if (env?.location === 'UnionOuterOrderBy')
|
|
264
254
|
return;
|
|
265
255
|
|
|
266
256
|
let pathValue = pathNode;
|
|
267
|
-
let [head, ...tail] = pathValue.path;
|
|
268
|
-
while(tail.length && head._navigation?.kind === '$self') {
|
|
257
|
+
let [ head, ...tail ] = pathValue.path;
|
|
258
|
+
while (tail.length && head._navigation?.kind === '$self') {
|
|
269
259
|
const self = head;
|
|
270
|
-
[head, ...tail] = tail;
|
|
271
|
-
if(head) {
|
|
260
|
+
[ head, ...tail ] = tail;
|
|
261
|
+
if (head) {
|
|
272
262
|
pathValue = self._navigation._origin.elements[head.id].value;
|
|
273
263
|
// core compiler has already caught $self.<assoc>.<postfix> and
|
|
274
264
|
// non-path $self expressions with postfix path
|
|
275
|
-
if(pathValue.path) {
|
|
276
|
-
if(tail.length)
|
|
277
|
-
pathValue = constructPathNode([...pathValue.path, ...tail], pathValue.alias, false);
|
|
278
|
-
[head, ...tail] = pathValue.path;
|
|
265
|
+
if (pathValue.path) {
|
|
266
|
+
if (tail.length)
|
|
267
|
+
pathValue = constructPathNode([ ...pathValue.path, ...tail ], pathValue.alias, false);
|
|
268
|
+
[ head, ...tail ] = pathValue.path;
|
|
279
269
|
}
|
|
280
270
|
}
|
|
281
271
|
}
|
|
282
|
-
if(head)
|
|
272
|
+
if (head)
|
|
283
273
|
replaceNodeContent(pathNode, pathValue);
|
|
284
274
|
}
|
|
285
275
|
|
|
@@ -291,51 +281,50 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
291
281
|
the respective FK aliases.
|
|
292
282
|
No flattening of structured leaf types necessary, this is done in renderer
|
|
293
283
|
*/
|
|
294
|
-
function rewriteGenericPaths(pathNode, env)
|
|
295
|
-
|
|
296
|
-
if(pathNode.$rewritten)
|
|
284
|
+
function rewriteGenericPaths(pathNode, env) {
|
|
285
|
+
if (pathNode.$rewritten)
|
|
297
286
|
return;
|
|
298
287
|
|
|
299
|
-
if(env.location === 'onCondFrom')
|
|
300
|
-
|
|
301
|
-
if(checkPathDictionary(pathNode, env)) {
|
|
288
|
+
if (env.location === 'onCondFrom') {
|
|
289
|
+
if (checkPathDictionary(pathNode, env)) {
|
|
302
290
|
const [ tableAlias, tail ] = constructTableAliasAndTailPath(pathNode.path);
|
|
303
291
|
const pathStr = translateONCondPath(tail).map(ps => ps.id).join(pathDelimiter);
|
|
304
292
|
replaceNodeContent(pathNode,
|
|
305
|
-
|
|
293
|
+
constructPathNode([ tableAlias, { id: pathStr, _artifact: pathNode._artifact } ]));
|
|
306
294
|
}
|
|
307
295
|
}
|
|
308
|
-
else
|
|
309
|
-
{
|
|
296
|
+
else {
|
|
310
297
|
// Paths without _navigation in ORDER BY are select item aliases, they must
|
|
311
298
|
// be rendered verbatim
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
299
|
+
// eslint-disable-next-line prefer-const
|
|
300
|
+
let [ head, ...tail ] = pathNode.path;
|
|
301
|
+
if ((env.location === 'OrderBy' && !head._navigation) ||
|
|
302
|
+
env.location === 'UnionOuterOrderBy' && (!head._navigation || [ '$self', '$projection' ].includes(head.id)))
|
|
315
303
|
return;
|
|
316
304
|
|
|
317
305
|
// path outside ON cond:
|
|
318
306
|
// spin the crystal ball to identify the correct table alias
|
|
319
307
|
|
|
320
308
|
// pop ta ps
|
|
321
|
-
if(head._navigation.kind !== '$tableAlias')
|
|
309
|
+
if (head._navigation.kind !== '$tableAlias')
|
|
322
310
|
tail = pathNode.path;
|
|
311
|
+
const rootQA = head._navigation._parent.$QA || head._navigation.$QA;
|
|
323
312
|
// if tail.length > 1, search bottom up for QA
|
|
324
313
|
// default to rootQA, _parent.$QA has precedence
|
|
325
|
-
const [QA, ps] =
|
|
326
|
-
if(!QA) {
|
|
314
|
+
const [ QA, ps ] = rightMostJoinRelevantQA(tail, rootQA);
|
|
315
|
+
if (!QA) {
|
|
327
316
|
error(null, pathNode.$location,
|
|
328
|
-
|
|
329
|
-
|
|
317
|
+
{ name: pathName(pathNode.path) },
|
|
318
|
+
'Debug me: No QA found for generic path rewriting in $(NAME)');
|
|
330
319
|
return;
|
|
331
320
|
}
|
|
332
321
|
// if the found QA is the mixin QA and if the path length is one,
|
|
333
322
|
// this indicates the publishing of a mixin assoc, don't rewrite the path
|
|
334
|
-
if(QA.mixin && tail.length === 1)
|
|
323
|
+
if (QA.mixin && tail.length === 1)
|
|
335
324
|
return;
|
|
336
325
|
let pos = tail.indexOf(ps);
|
|
337
326
|
// cut off ps if it's a join relevant association with postfix
|
|
338
|
-
if(tail.length-(pos+1) > 0 && ps._artifact.target &&
|
|
327
|
+
if (tail.length - (pos + 1) > 0 && ps._artifact.target && (rootQA.mixin || rootQA !== QA))
|
|
339
328
|
pos++;
|
|
340
329
|
// QA + tail is the rewritten path
|
|
341
330
|
tail = tail.slice(pos);
|
|
@@ -343,32 +332,41 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
343
332
|
// if so, substitute path with pregenerated foreign key, prepend by optional
|
|
344
333
|
// (to be flattened) prefix
|
|
345
334
|
|
|
346
|
-
for(let i = 0; i < tail.length-1; i++) {
|
|
347
|
-
if(tail[i]._navigation
|
|
335
|
+
for (let i = 0; i < tail.length - 1; i++) {
|
|
336
|
+
if (tail[i]._navigation) {
|
|
348
337
|
// the correct flattened foreign key must match the leaf artifact and access path prefix of this path
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (!fk)
|
|
353
|
-
throw new CompilerAssertion('Debug me: No
|
|
354
|
-
|
|
355
|
-
tail
|
|
338
|
+
const fk = findForeignKey(tail[i], tail[i + 1]);
|
|
339
|
+
|
|
340
|
+
// if the assoc is not join relevant, we should have found the foreign key
|
|
341
|
+
if (tail[i]._navigation.$njr && !fk)
|
|
342
|
+
throw new CompilerAssertion('Debug me: No FK found for FK rewriting');
|
|
343
|
+
|
|
344
|
+
if (fk && fk.name.id !== tail[i + 1].id)
|
|
345
|
+
tail[i + 1].id = fk.name.id; // fk renamed
|
|
356
346
|
}
|
|
357
347
|
}
|
|
358
348
|
tail = [
|
|
359
|
-
{
|
|
360
|
-
|
|
361
|
-
|
|
349
|
+
{
|
|
350
|
+
id: tail.map(p => p.id).join(pathDelimiter),
|
|
351
|
+
_artifact: tail[tail.length - 1]._artifact,
|
|
352
|
+
},
|
|
362
353
|
];
|
|
363
354
|
replaceNodeContent(pathNode,
|
|
364
|
-
|
|
355
|
+
constructPathNode([ constructTableAliasPathStep(QA), ...tail ]));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function findForeignKey(assoc, fk) {
|
|
359
|
+
return Object.values(assoc._artifact.foreignKeys).find(k => k.targetElement._artifact === fk._artifact);
|
|
365
360
|
}
|
|
366
361
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Search right-to-left for first QA that matches either
|
|
364
|
+
* - a $tableAlias
|
|
365
|
+
* - or an association (skip this QA if postfix path is foreign key)
|
|
366
|
+
*
|
|
367
|
+
* If no QA found, return rootQA with first path step.
|
|
368
|
+
*/
|
|
369
|
+
function rightMostJoinRelevantQA(path, rootQA) {
|
|
372
370
|
/*
|
|
373
371
|
Search right to left to find first QA in QAT tree
|
|
374
372
|
Start with n-1st path element (to not find QA for exposed
|
|
@@ -376,16 +374,20 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
376
374
|
If no QA could be found, return rootQA with first path
|
|
377
375
|
step.
|
|
378
376
|
*/
|
|
379
|
-
let QA
|
|
380
|
-
let pl = path.length-1;
|
|
377
|
+
let QA;
|
|
378
|
+
let pl = path.length - 1;
|
|
381
379
|
let ps = path[pl]; // return [null, ps] for pl==0
|
|
382
|
-
while(!QA && pl > 0)
|
|
383
|
-
|
|
380
|
+
while (!QA && pl > 0) {
|
|
381
|
+
const next = ps;
|
|
384
382
|
ps = path[--pl];
|
|
385
|
-
if(ps._navigation)
|
|
383
|
+
if (ps._navigation) {
|
|
384
|
+
const tailIsFk = !ps.where && !ps.args && ps._artifact.foreignKeys && findForeignKey(ps, next);
|
|
385
|
+
if (tailIsFk)
|
|
386
|
+
continue;
|
|
386
387
|
QA = ps._navigation.$QA;
|
|
388
|
+
}
|
|
387
389
|
}
|
|
388
|
-
return [(QA
|
|
390
|
+
return [ (QA || rootQA), ps ];
|
|
389
391
|
}
|
|
390
392
|
}
|
|
391
393
|
|
|
@@ -394,21 +396,19 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
394
396
|
If WHERE does not exist, create a new one. This step must be done after rewriteGenericPaths()
|
|
395
397
|
as the filter expressions would be traversed twice.
|
|
396
398
|
*/
|
|
397
|
-
function attachFirstFilterConditions(query)
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if(query.where)
|
|
402
|
-
{
|
|
403
|
-
if(query.where.op.val === 'and')
|
|
399
|
+
function attachFirstFilterConditions(query) {
|
|
400
|
+
if (query.$startFilters) {
|
|
401
|
+
if (query.where) {
|
|
402
|
+
if (query.where.op.val === 'and')
|
|
404
403
|
query.where.args.push(...query.$startFilters.map(parenthesise));
|
|
405
404
|
else
|
|
406
|
-
query.where = { op: {val: 'and' }, args: [ parenthesise(query.where),
|
|
405
|
+
query.where = { op: { val: 'and' }, args: [ parenthesise(query.where), ...query.$startFilters.map(parenthesise) ] };
|
|
407
406
|
}
|
|
408
|
-
else
|
|
407
|
+
else {
|
|
409
408
|
query.where = query.$startFilters.length > 1
|
|
410
|
-
? { op: {val: 'and' }, args: query.$startFilters.map(parenthesise) }
|
|
409
|
+
? { op: { val: 'and' }, args: query.$startFilters.map(parenthesise) }
|
|
411
410
|
: parenthesise(query.$startFilters[0]);
|
|
411
|
+
}
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
414
|
|
|
@@ -418,48 +418,41 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
418
418
|
case QAT.origin is an association, create a new JOIN node using
|
|
419
419
|
the existing joinTree as LHS and the QAT.QA as RHS.
|
|
420
420
|
*/
|
|
421
|
-
function createJoinTree(env, joinTree, parentQat, joinType, qatAttribName, lastAssocQA)
|
|
422
|
-
|
|
423
|
-
for(const childQatId in parentQat)
|
|
424
|
-
{
|
|
421
|
+
function createJoinTree(env, joinTree, parentQat, joinType, qatAttribName, lastAssocQA) {
|
|
422
|
+
for (const childQatId in parentQat) {
|
|
425
423
|
const childQat = parentQat[childQatId];
|
|
426
424
|
|
|
427
425
|
// If this QAT is not join relevant, don't drill down any further but
|
|
428
426
|
// continue with current parentQat
|
|
429
|
-
if(!childQat.$njr) {
|
|
427
|
+
if (!childQat.$njr) {
|
|
430
428
|
let newAssocLHS = lastAssocQA;
|
|
431
429
|
const art = childQat._origin;
|
|
432
|
-
if(art.kind === 'entity')
|
|
433
|
-
|
|
434
|
-
if(!childQat.$QA)
|
|
430
|
+
if (art.kind === 'entity') {
|
|
431
|
+
if (!childQat.$QA)
|
|
435
432
|
childQat.$QA = createQA(env, art, art.name.id.split('.').pop(), childQat._namedArgs);
|
|
436
433
|
incAliasCount(env, childQat.$QA);
|
|
437
434
|
newAssocLHS = childQat.$QA;
|
|
438
435
|
|
|
439
|
-
if(joinTree === undefined) // This is the first artifact in the JOIN tree
|
|
440
|
-
{
|
|
436
|
+
if (joinTree === undefined) { // This is the first artifact in the JOIN tree
|
|
441
437
|
joinTree = childQat.$QA;
|
|
442
438
|
// Collect the toplevel filters and add them to the where condition
|
|
443
|
-
if(childQat._filter)
|
|
444
|
-
{
|
|
439
|
+
if (childQat._filter) {
|
|
445
440
|
// Filter conditions are unique for each JOIN, they don't need to be copied
|
|
446
441
|
const filter = childQat._filter;
|
|
447
|
-
rewritePathsInFilterExpression(filter,
|
|
448
|
-
|
|
449
|
-
/* filterPath=> */ pathNode.path ];
|
|
450
|
-
}, env);
|
|
442
|
+
rewritePathsInFilterExpression(filter, pathNode => [ /* tableAlias=> */ constructTableAliasPathStep(childQat.$QA),
|
|
443
|
+
/* filterPath=> */ pathNode.path ], env);
|
|
451
444
|
|
|
452
|
-
if(!env.lead.$startFilters)
|
|
445
|
+
if (!env.lead.$startFilters)
|
|
453
446
|
env.lead.$startFilters = [];
|
|
454
447
|
env.lead.$startFilters.push( filter );
|
|
455
448
|
}
|
|
456
449
|
}
|
|
457
450
|
}
|
|
458
|
-
else if(art.target) { // it's not an artifact, so it should be an assoc step
|
|
459
|
-
if(joinTree === undefined)
|
|
451
|
+
else if (art.target) { // it's not an artifact, so it should be an assoc step
|
|
452
|
+
if (joinTree === undefined)
|
|
460
453
|
throw new CompilerAssertion('Can\'t follow Associations without starting Entity');
|
|
461
454
|
|
|
462
|
-
if(!childQat.$QA)
|
|
455
|
+
if (!childQat.$QA)
|
|
463
456
|
childQat.$QA = createQA(env, art.target._artifact, art.name.id, childQat._namedArgs);
|
|
464
457
|
|
|
465
458
|
incAliasCount(env, childQat.$QA);
|
|
@@ -473,32 +466,28 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
473
466
|
return joinTree;
|
|
474
467
|
}
|
|
475
468
|
|
|
476
|
-
function createJoinQA(joinType, lhs, rhs, assocQAT, assocSourceQA, env)
|
|
477
|
-
{
|
|
469
|
+
function createJoinQA(joinType, lhs, rhs, assocQAT, assocSourceQA, env) {
|
|
478
470
|
const node = { op: { val: 'join' }, join: { val: joinType }, args: [ lhs, rhs ] };
|
|
479
471
|
const assoc = assocQAT._origin;
|
|
480
|
-
if(isBetaEnabled(options, 'mapAssocToJoinCardinality'))
|
|
472
|
+
if (isBetaEnabled(options, 'mapAssocToJoinCardinality'))
|
|
481
473
|
node.cardinality = mapAssocToJoinCardinality(assoc);
|
|
482
|
-
|
|
474
|
+
|
|
483
475
|
// 'path steps' for the src/tgt table alias
|
|
484
476
|
const srcTableAlias = constructTableAliasPathStep(assocSourceQA);
|
|
485
477
|
const tgtTableAlias = constructTableAliasPathStep(assocQAT.$QA);
|
|
486
478
|
|
|
487
479
|
node.on = createOnCondition(assoc, srcTableAlias, tgtTableAlias, options.tenantDiscriminator);
|
|
488
480
|
|
|
489
|
-
if(assocQAT._filter)
|
|
490
|
-
{
|
|
481
|
+
if (assocQAT._filter) {
|
|
491
482
|
// Filter conditions are unique for each JOIN, they don't need to be copied
|
|
492
483
|
const filter = assocQAT._filter;
|
|
493
|
-
rewritePathsInFilterExpression(filter,
|
|
494
|
-
return [ tgtTableAlias, pathNode.path ];
|
|
495
|
-
}, env);
|
|
484
|
+
rewritePathsInFilterExpression(filter, pathNode => [ tgtTableAlias, pathNode.path ], env);
|
|
496
485
|
|
|
497
486
|
// If toplevel ON cond op is AND add filter condition to the args array,
|
|
498
487
|
// create a new toplevel AND op otherwise
|
|
499
488
|
const onCond = (Array.isArray(node.on) ? node.on[0] : node.on);
|
|
500
489
|
|
|
501
|
-
if(onCond.op.val === 'and')
|
|
490
|
+
if (onCond.op.val === 'and')
|
|
502
491
|
onCond.args.push(parenthesise(filter));
|
|
503
492
|
else
|
|
504
493
|
node.on = parenthesise({ op: { val: 'and' }, args: [ parenthesise(onCond), parenthesise(filter) ] });
|
|
@@ -529,33 +518,33 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
529
518
|
function mapAssocToJoinCardinality(assoc) {
|
|
530
519
|
/** @type {object} */
|
|
531
520
|
const xsnCard = {
|
|
532
|
-
targetMax
|
|
521
|
+
targetMax: { literal: 'number', val: 1 },
|
|
533
522
|
};
|
|
534
|
-
if(assoc.type._artifact._effectiveType.name.id === 'cds.Composition') {
|
|
523
|
+
if (assoc.type._artifact._effectiveType.name.id === 'cds.Composition') {
|
|
535
524
|
xsnCard.sourceMin = { literal: 'number', val: 1 };
|
|
536
525
|
xsnCard.sourceMax = { literal: 'number', val: 1 };
|
|
537
526
|
}
|
|
538
527
|
else {
|
|
539
|
-
xsnCard.sourceMax = { literal: 'string', val: '*' }
|
|
528
|
+
xsnCard.sourceMax = { literal: 'string', val: '*' };
|
|
540
529
|
}
|
|
541
530
|
|
|
542
|
-
if(assoc.cardinality) {
|
|
543
|
-
if(assoc.cardinality.sourceMax && assoc.cardinality.sourceMax.val === 1) {
|
|
531
|
+
if (assoc.cardinality) {
|
|
532
|
+
if (assoc.cardinality.sourceMax && assoc.cardinality.sourceMax.val === 1) {
|
|
544
533
|
xsnCard.sourceMax.literal = 'number';
|
|
545
534
|
xsnCard.sourceMax.val = 1;
|
|
546
535
|
}
|
|
547
|
-
if(assoc.cardinality.targetMax && assoc.cardinality.targetMax.val !== 1) {
|
|
536
|
+
if (assoc.cardinality.targetMax && assoc.cardinality.targetMax.val !== 1) {
|
|
548
537
|
xsnCard.targetMax.literal = 'string';
|
|
549
538
|
xsnCard.targetMax.val = '*';
|
|
550
539
|
}
|
|
551
|
-
else if(assoc.cardinality.targetMin && assoc.cardinality.targetMin.val === 1)
|
|
540
|
+
else if (assoc.cardinality.targetMin && assoc.cardinality.targetMin.val === 1) {
|
|
552
541
|
xsnCard.targetMin = { literal: 'number', val: 1 };
|
|
542
|
+
}
|
|
553
543
|
}
|
|
554
544
|
return xsnCard;
|
|
555
545
|
}
|
|
556
546
|
// produce the ON condition for a given association
|
|
557
|
-
function createOnCondition(assoc, srcAlias, tgtAlias, compareTenants)
|
|
558
|
-
{
|
|
547
|
+
function createOnCondition(assoc, srcAlias, tgtAlias, compareTenants) {
|
|
559
548
|
const prefixes = [ assoc.name.id ];
|
|
560
549
|
/* This is no art and can be removed once ON cond for published
|
|
561
550
|
and renamed backlink assocs are publicly available. Example:
|
|
@@ -565,82 +554,82 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
565
554
|
This requires ON cond rewritten to: $self = foo.toE but instead its still $self = toEb.toE,
|
|
566
555
|
so prefix 'foo' won't match....
|
|
567
556
|
*/
|
|
568
|
-
if(assoc._origin && !prefixes.includes(assoc._origin.name.id))
|
|
557
|
+
if (assoc._origin && !prefixes.includes(assoc._origin.name.id))
|
|
569
558
|
prefixes.push(assoc._origin.name.id);
|
|
570
559
|
|
|
571
560
|
// produce the ON condition of the managed association
|
|
572
|
-
if(assoc.foreignKeys)
|
|
573
|
-
{
|
|
561
|
+
if (assoc.foreignKeys) {
|
|
574
562
|
/*
|
|
575
563
|
Get both the source and the target column names for the EQ term.
|
|
576
564
|
For the src side provide a path prefix for all paths that is the assocElement name itself preceded by
|
|
577
565
|
the path up to the first lead artifact (usually the entity or view) (or in QAT speak: follow the parent
|
|
578
566
|
QATs until a QA has been found).
|
|
579
567
|
*/
|
|
580
|
-
if(!assoc.$flatSrcFKs)
|
|
568
|
+
if (!assoc.$flatSrcFKs)
|
|
581
569
|
setProp(assoc, '$flatSrcFKs', flattenElement(assoc, true, assoc.name.id, assoc.name.id));
|
|
582
|
-
if(!assoc.$flatTgtFKs)
|
|
570
|
+
if (!assoc.$flatTgtFKs)
|
|
583
571
|
setProp(assoc, '$flatTgtFKs', flattenElement(assoc, false));
|
|
584
572
|
|
|
585
|
-
if(assoc.$flatSrcFKs.length
|
|
586
|
-
throw new CompilerAssertion(
|
|
573
|
+
if (assoc.$flatSrcFKs.length !== assoc.$flatTgtFKs.length)
|
|
574
|
+
throw new CompilerAssertion(`srcPaths length [${ assoc.$flatSrcFKs.length }] != tgtPaths length [${ assoc.$flatTgtFKs.length }]`);
|
|
587
575
|
|
|
588
576
|
/*
|
|
589
577
|
Put all src/tgt path siblings into the EQ term and create the proper path objects
|
|
590
578
|
with the src/tgt table alias path steps in front.
|
|
591
579
|
*/
|
|
592
580
|
const args = compareTenants && addTenantComparison(assoc) || [];
|
|
593
|
-
for(let i = 0; i < assoc.$flatSrcFKs.length; i++)
|
|
594
|
-
|
|
595
|
-
|
|
581
|
+
for (let i = 0; i < assoc.$flatSrcFKs.length; i++) {
|
|
582
|
+
args.push({
|
|
583
|
+
op: { val: '=' },
|
|
596
584
|
args: [ constructPathNode( [ srcAlias, prefixFK(assoc.$elementPrefix, assoc.$flatSrcFKs[i]) ] ),
|
|
597
|
-
|
|
585
|
+
constructPathNode( [ tgtAlias, assoc.$flatTgtFKs[i] ] ) ],
|
|
586
|
+
});
|
|
598
587
|
}
|
|
599
588
|
// TODO: why inner "parenthesise" - comparison in `and`?
|
|
600
589
|
return parenthesise((args.length > 1 ? { op: { val: 'and' }, args: [ ...args.map(parenthesise) ] } : args[0] ));
|
|
601
590
|
}
|
|
602
591
|
else if (assoc.on) {
|
|
603
|
-
if(env.assocStack === undefined) {
|
|
592
|
+
if (env.assocStack === undefined) {
|
|
604
593
|
env.assocStack = [];
|
|
605
|
-
env.assocStack.head = function() {
|
|
606
|
-
return this[this.length-1];
|
|
607
|
-
}
|
|
608
|
-
env.assocStack.id = function() {
|
|
594
|
+
env.assocStack.head = function head() {
|
|
595
|
+
return this[this.length - 1];
|
|
596
|
+
};
|
|
597
|
+
env.assocStack.id = function id() {
|
|
609
598
|
return (this.head() && this.head().name.id);
|
|
610
|
-
}
|
|
611
|
-
env.assocStack.element = function() {
|
|
599
|
+
};
|
|
600
|
+
env.assocStack.element = function element() {
|
|
612
601
|
return (this.head() && (this.head().name.element || this.head().name.id));
|
|
613
|
-
}
|
|
614
|
-
env.assocStack.stripAssocPrefix = function(path) {
|
|
602
|
+
};
|
|
603
|
+
env.assocStack.stripAssocPrefix = function stripAssocPrefix(path) {
|
|
615
604
|
return this.stripPrefix(path);
|
|
616
|
-
}
|
|
605
|
+
};
|
|
617
606
|
|
|
618
607
|
// offset must be a negative value to indicate prefix length
|
|
619
608
|
// offset=0 includes the element assoc id itself
|
|
620
|
-
env.assocStack.stripPrefix = function(path, offset=0) {
|
|
609
|
+
env.assocStack.stripPrefix = function stripPrefix(path, offset = 0) {
|
|
621
610
|
const elt = this.element();
|
|
622
611
|
const id = this.id();
|
|
623
|
-
if(elt) {
|
|
612
|
+
if (elt) {
|
|
624
613
|
let found = true;
|
|
625
|
-
const epath = [elt];
|
|
626
|
-
const epl = epath.length+offset;
|
|
627
|
-
if(epl < path.length) {
|
|
628
|
-
for(let i = 0; i < epl && found; i++)
|
|
614
|
+
const epath = [ elt ];
|
|
615
|
+
const epl = epath.length + offset;
|
|
616
|
+
if (epl < path.length) {
|
|
617
|
+
for (let i = 0; i < epl && found; i++)
|
|
629
618
|
found = epath[i] === path[i].id;
|
|
630
|
-
|
|
631
|
-
if(found)
|
|
619
|
+
|
|
620
|
+
if (found)
|
|
632
621
|
return path.slice(epl);
|
|
633
622
|
}
|
|
634
623
|
}
|
|
635
|
-
if(id) {
|
|
624
|
+
if (id) {
|
|
636
625
|
let found = true;
|
|
637
|
-
const epath = [id];
|
|
638
|
-
const epl = epath.length+offset;
|
|
639
|
-
if(epl < path.length) {
|
|
640
|
-
for(let i = 0; i < epl && found; i++)
|
|
626
|
+
const epath = [ id ];
|
|
627
|
+
const epl = epath.length + offset;
|
|
628
|
+
if (epl < path.length) {
|
|
629
|
+
for (let i = 0; i < epl && found; i++)
|
|
641
630
|
found = epath[i] === path[i].id;
|
|
642
|
-
|
|
643
|
-
if(found)
|
|
631
|
+
|
|
632
|
+
if (found)
|
|
644
633
|
return path.slice(epl);
|
|
645
634
|
}
|
|
646
635
|
}
|
|
@@ -651,12 +640,13 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
651
640
|
const onCond = cloneOnCondition(assoc.on);
|
|
652
641
|
env.assocStack.pop();
|
|
653
642
|
return compareTenants ? addTenantComparison(assoc, onCond) : onCond;
|
|
654
|
-
|
|
655
|
-
|
|
643
|
+
}
|
|
644
|
+
else if (!hasPersistenceSkipAnnotation(assoc._main)) {
|
|
656
645
|
// TODO: exclude non-persisted entities from SQL generation; they may have
|
|
657
646
|
// to-many associations without foreign keys nor ON-condition.
|
|
658
|
-
throw new CompilerAssertion(`Association must have either ON-condition or foreign keys: ${assoc.name.id} at ${JSON.stringify(assoc.location)}`);
|
|
659
|
-
}
|
|
647
|
+
throw new CompilerAssertion(`Association must have either ON-condition or foreign keys: ${ assoc.name.id } at ${ JSON.stringify(assoc.location) }`);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
660
650
|
return null;
|
|
661
651
|
}
|
|
662
652
|
|
|
@@ -666,13 +656,13 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
666
656
|
// the current query must also be (check in addTenantFields). If we allow
|
|
667
657
|
// assocs from tenant-independent entities to tenant-dependent ones, we
|
|
668
658
|
// also need to use the current query = `env.lead`.
|
|
669
|
-
if(annotationVal(assoc.target._artifact['@cds.tenant.independent']))
|
|
659
|
+
if (annotationVal(assoc.target._artifact['@cds.tenant.independent']))
|
|
670
660
|
return cond;
|
|
671
661
|
|
|
672
662
|
const args = [ constructPathNode([ srcAlias ]), constructPathNode([ tgtAlias ]) ];
|
|
673
663
|
args[0].path.push({ id: 'tenant' }); // no need for _artifact
|
|
674
664
|
args[1].path.push({ id: 'tenant' }); // no need for _artifact
|
|
675
|
-
const comparison = { op: {val: '=' }, args };
|
|
665
|
+
const comparison = { op: { val: '=' }, args };
|
|
676
666
|
if (!cond) // for managed assoc
|
|
677
667
|
return [ comparison ];
|
|
678
668
|
return { op: { val: 'and' }, args: [ comparison, parenthesise(cond) ] };
|
|
@@ -680,41 +670,36 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
680
670
|
|
|
681
671
|
// make foreign key absolute to its main entity
|
|
682
672
|
function prefixFK(prefix, fk) {
|
|
683
|
-
return prefix ? { id: prefix+fk.id, _artifact: fk._artifact } : fk
|
|
673
|
+
return prefix ? { id: prefix + fk.id, _artifact: fk._artifact } : fk;
|
|
684
674
|
}
|
|
685
675
|
// clone ON condition with rewritten paths and substituted backlink conditions
|
|
686
|
-
function cloneOnCondition(expr)
|
|
687
|
-
{
|
|
676
|
+
function cloneOnCondition(expr) {
|
|
688
677
|
const op = expr.op?.val;
|
|
689
|
-
if(op === 'xpr' || op === 'ixpr' || op === 'nary')
|
|
678
|
+
if (op === 'xpr' || op === 'ixpr' || op === 'nary')
|
|
690
679
|
return cloneOnCondExprStream(expr);
|
|
691
|
-
|
|
692
|
-
return cloneOnCondExprTree(expr);
|
|
680
|
+
return cloneOnCondExprTree(expr);
|
|
693
681
|
}
|
|
694
682
|
|
|
695
683
|
function cloneOnCondExprStream(expr) {
|
|
696
|
-
const args = expr
|
|
684
|
+
const { args } = expr;
|
|
697
685
|
const result = { op: { val: expr.op.val }, args: [ ] };
|
|
698
|
-
for(let i = 0; i < args.length; i++)
|
|
699
|
-
{
|
|
686
|
+
for (let i = 0; i < args.length; i++) {
|
|
700
687
|
const op = args[i].op?.val;
|
|
701
|
-
if(op === 'xpr' || op === 'ixpr' || op === 'nary')
|
|
702
|
-
{
|
|
688
|
+
if (op === 'xpr' || op === 'ixpr' || op === 'nary') {
|
|
703
689
|
result.args.push(cloneOnCondition(args[i]));
|
|
704
690
|
}
|
|
705
691
|
// If this is a backlink condition, produce the
|
|
706
692
|
// ON cond of the forward assoc with swapped src/tgt aliases
|
|
707
|
-
else if(i < args.length-2 && args[i].path &&
|
|
708
|
-
args[i+1]?.literal === 'token' && args[i+1]?.val === '=' && args[i+2].path)
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
if(env.assocStack.length === 2) {
|
|
715
|
-
error('type-invalid-self', [env.assocStack[0].location, env.assocStack[0]], { name: '$self' });
|
|
693
|
+
else if (i < args.length - 2 && args[i].path &&
|
|
694
|
+
args[i + 1]?.literal === 'token' && args[i + 1]?.val === '=' && args[i + 2].path) {
|
|
695
|
+
const fwdAssoc = getForwardAssociation(args[i].path, args[i + 2].path);
|
|
696
|
+
if (fwdAssoc) {
|
|
697
|
+
// env.assocStack.includes(fwdAssoc) => recursion
|
|
698
|
+
if (env.assocStack.length === 2) {
|
|
699
|
+
error('type-invalid-self', [ env.assocStack[0].location, env.assocStack[0] ], { name: '$self' });
|
|
716
700
|
// don't check these paths again
|
|
717
|
-
args[i].$check =
|
|
701
|
+
args[i].$check = false;
|
|
702
|
+
args[i + 2].$check = false;
|
|
718
703
|
}
|
|
719
704
|
else {
|
|
720
705
|
result.args.push(createOnCondition(fwdAssoc, ...swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias)));
|
|
@@ -722,11 +707,13 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
722
707
|
i += 2; // skip next two tokens and continue with loop
|
|
723
708
|
continue;
|
|
724
709
|
}
|
|
725
|
-
else // it's ensured that it's a path
|
|
710
|
+
else { // it's ensured that it's a path
|
|
726
711
|
result.args.push(rewritePathNode(args[i]));
|
|
712
|
+
}
|
|
727
713
|
}
|
|
728
|
-
else // could be `{op:…}`, clone generically
|
|
714
|
+
else { // could be `{op:…}`, clone generically
|
|
729
715
|
result.args.push(cloneOnCondition(args[i]));
|
|
716
|
+
}
|
|
730
717
|
}
|
|
731
718
|
return result;
|
|
732
719
|
}
|
|
@@ -734,30 +721,32 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
734
721
|
function cloneOnCondExprTree(expr) {
|
|
735
722
|
// TODO: This function is not covered by an tests, only cloneOnCondExprStream is.
|
|
736
723
|
// keep parentheses intact
|
|
737
|
-
if(Array.isArray(expr))
|
|
724
|
+
if (Array.isArray(expr))
|
|
738
725
|
return expr.map(cloneOnCondition);
|
|
739
726
|
|
|
740
727
|
// If this is a backlink condition, produce the
|
|
741
728
|
// ON cond of the forward assoc with swapped src/tgt aliases
|
|
742
729
|
const fwdAssoc = getForwardAssociationExpr(expr);
|
|
743
|
-
if(fwdAssoc) {
|
|
744
|
-
if(env.assocStack.length === 2) {
|
|
730
|
+
if (fwdAssoc) {
|
|
731
|
+
if (env.assocStack.length === 2) {
|
|
745
732
|
// reuse (ugly) error message from forHana
|
|
746
|
-
error(null, expr.location,
|
|
733
|
+
error(null, expr.location, { id: '$self' },
|
|
734
|
+
'An association that uses $(ID) in its ON-condition can\'t be compared to $(ID)');
|
|
747
735
|
// don't check these paths again
|
|
748
|
-
expr.args.forEach(x =>
|
|
736
|
+
expr.args.forEach((x) => {
|
|
737
|
+
x.$check = false;
|
|
738
|
+
} );
|
|
749
739
|
return expr;
|
|
750
740
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
}
|
|
741
|
+
|
|
742
|
+
return createOnCondition(fwdAssoc, ...swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias));
|
|
754
743
|
}
|
|
755
744
|
|
|
756
745
|
// If this is an ordinary expression, clone it and mangle its arguments
|
|
757
746
|
// this will substitute multiple backlink conditions ($self = ... AND $self = ...AND ...)
|
|
758
|
-
if(expr.op) {
|
|
747
|
+
if (expr.op) {
|
|
759
748
|
const x = clone(expr);
|
|
760
|
-
if(expr.args)
|
|
749
|
+
if (expr.args)
|
|
761
750
|
x.args = expr.args.map(cloneOnCondition);
|
|
762
751
|
return x;
|
|
763
752
|
}
|
|
@@ -778,7 +767,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
778
767
|
|
|
779
768
|
let i = 0;
|
|
780
769
|
let fwdOrigin = fwdAssoc;
|
|
781
|
-
while(fwdOrigin._origin) {
|
|
770
|
+
while (fwdOrigin._origin) {
|
|
782
771
|
fwdOrigin = fwdOrigin._origin;
|
|
783
772
|
i++;
|
|
784
773
|
}
|
|
@@ -786,52 +775,50 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
786
775
|
// points to the signature of the current view and ensures that the ON cond is
|
|
787
776
|
// resolvable) make sure that the original assoc target is contained in the local
|
|
788
777
|
// query source
|
|
789
|
-
if(assoc.kind === 'mixin' && i > 0 && fwdOrigin.kind !== 'mixin') {
|
|
778
|
+
if (assoc.kind === 'mixin' && i > 0 && fwdOrigin.kind !== 'mixin') {
|
|
790
779
|
const tas = Object.values(env.lead.$tableAliases);
|
|
791
780
|
const i = tas.findIndex(ta => ta._artifact === fwdOrigin.target._artifact);
|
|
792
|
-
if(i >= 0 && tas[i].$QA) {
|
|
781
|
+
if (i >= 0 && tas[i].$QA) {
|
|
793
782
|
newTgtAlias.id = tas[i].$QA.name.id;
|
|
794
783
|
newTgtAlias._artifact = tas[i]._effectiveType;
|
|
795
784
|
newTgtAlias._navigation = tas[i].$QA.path[0]._navigation;
|
|
796
785
|
}
|
|
797
786
|
else {
|
|
798
787
|
error(null, [ assocQAT._origin.location, assocQAT._origin ], { name: fwdOrigin.target._artifact.name.id, art: assoc.name.id },
|
|
799
|
-
|
|
788
|
+
'Expected association target $(NAME) of association $(ART) to be a query source');
|
|
800
789
|
newTgtAlias = Object.assign(newTgtAlias, srcAlias);
|
|
801
790
|
}
|
|
802
791
|
}
|
|
803
792
|
else {
|
|
804
793
|
newTgtAlias = Object.assign(newTgtAlias, srcAlias);
|
|
805
794
|
}
|
|
806
|
-
return [newSrcAlias, newTgtAlias];
|
|
795
|
+
return [ newSrcAlias, newTgtAlias ];
|
|
807
796
|
}
|
|
808
797
|
|
|
809
|
-
function rewritePathNode(pathNode)
|
|
810
|
-
{
|
|
798
|
+
function rewritePathNode(pathNode) {
|
|
811
799
|
let tableAlias;
|
|
812
|
-
let path = pathNode
|
|
813
|
-
if(!path) // it's not a path return it
|
|
800
|
+
let { path } = pathNode;
|
|
801
|
+
if (!path) // it's not a path return it
|
|
814
802
|
return pathNode;
|
|
815
803
|
|
|
816
|
-
let [head, ...tail] = path;
|
|
804
|
+
let [ head, ...tail ] = path;
|
|
817
805
|
|
|
818
806
|
// don't rewrite path
|
|
819
|
-
if(internalArtifactKinds.includes(head._artifact.kind))
|
|
807
|
+
if (internalArtifactKinds.includes(head._artifact.kind))
|
|
820
808
|
return pathNode;
|
|
821
809
|
|
|
822
810
|
// strip the absolute path indicators
|
|
823
811
|
let hasDollarSelfPrefix = false;
|
|
824
|
-
if (['$projection', '$self'].includes(head.id) && tail.length) {
|
|
812
|
+
if ([ '$projection', '$self' ].includes(head.id) && tail.length) {
|
|
825
813
|
hasDollarSelfPrefix = true;
|
|
826
814
|
path = tail;
|
|
827
815
|
}
|
|
828
816
|
|
|
829
|
-
if(!checkPathDictionary(pathNode, env))
|
|
817
|
+
if (!checkPathDictionary(pathNode, env))
|
|
830
818
|
return pathNode;
|
|
831
|
-
}
|
|
832
819
|
|
|
833
|
-
|
|
834
|
-
{
|
|
820
|
+
|
|
821
|
+
if (rhs.mixin) {
|
|
835
822
|
if (hasDollarSelfPrefix) {
|
|
836
823
|
/* Do the $projection resolution ONLY in own query not for referenced forward ON condition
|
|
837
824
|
view YP as select from Y mixin ( toXP: association to XP on $projection.yid = toXP.xid; } into { yid };
|
|
@@ -839,22 +826,21 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
839
826
|
X join Y ON ($self = toYP.toXP) => ($projection.yid = toXP.xid) => (Y.yid = X.xid)
|
|
840
827
|
$projection must be removed from $projection.yid (get's aliased with the mixinAssocQAT.$QA)
|
|
841
828
|
*/
|
|
842
|
-
if(env.assocStack.length < 2) {
|
|
843
|
-
const value = env.lead.elements[path[0].id]
|
|
829
|
+
if (env.assocStack.length < 2) {
|
|
830
|
+
const { value } = env.lead.elements[path[0].id];
|
|
844
831
|
/*
|
|
845
832
|
If the value is an expression in the select block, return the unmodified
|
|
846
833
|
expression. rewriteGenericPaths will check and rewrite these paths later
|
|
847
834
|
to the correct ON condition expression.
|
|
848
835
|
*/
|
|
849
836
|
|
|
850
|
-
if(!value.path)
|
|
837
|
+
if (!value.path)
|
|
851
838
|
return value;
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
}
|
|
839
|
+
|
|
840
|
+
// check for associations, not allowed at this time, trouble in resolving
|
|
841
|
+
// and addressing the correct foreign key (tuple)
|
|
842
|
+
[ head, ...tail ] = path;
|
|
843
|
+
path = value.path.concat(tail);
|
|
858
844
|
}
|
|
859
845
|
}
|
|
860
846
|
else {
|
|
@@ -889,20 +875,21 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
889
875
|
'toTgt.elt' must now be rendered for each JOIN using the correct QA clone.
|
|
890
876
|
*/
|
|
891
877
|
|
|
892
|
-
if(assocQAT.$QA.mixin)
|
|
878
|
+
if (assocQAT.$QA.mixin)
|
|
893
879
|
assocQAT._parent.$QA = assocQAT.$QA;
|
|
894
880
|
|
|
895
881
|
/* if the $projection path has association path steps make sure to address the
|
|
896
882
|
element by its last table alias name. Search from the end upwards
|
|
897
883
|
to the top for the first association path step and cut off path here.
|
|
898
884
|
*/
|
|
899
|
-
let i = path.length-1;
|
|
900
|
-
while(i >= 0 && !path[i--]._artifact.target)
|
|
885
|
+
let i = path.length - 1;
|
|
886
|
+
while (i >= 0 && !path[i--]._artifact.target)
|
|
887
|
+
;
|
|
901
888
|
// if this mixin ON condition path had a $projection/$self prefix, it could be
|
|
902
889
|
// that the path of the select list had many many associations, we're only interested in
|
|
903
890
|
// the last one (see MixinUsage2.cds V.toX as an example)
|
|
904
|
-
if(hasDollarSelfPrefix)
|
|
905
|
-
path.splice(0, i+1);
|
|
891
|
+
if (hasDollarSelfPrefix)
|
|
892
|
+
path.splice(0, i + 1);
|
|
906
893
|
|
|
907
894
|
/*
|
|
908
895
|
If the mixin is a backlink to some forward association, the forward ON condition
|
|
@@ -946,58 +933,60 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
946
933
|
lead to V.query[0].$tableAliases.$A. Instead we need to lookup the element in the
|
|
947
934
|
combined list of elements made available by the from clause.
|
|
948
935
|
*/
|
|
949
|
-
let _navigation
|
|
950
|
-
if(env.assocStack.length === 2) {
|
|
936
|
+
let _navigation; // don't modify original path
|
|
937
|
+
if (env.assocStack.length === 2) {
|
|
951
938
|
// a mixin assoc cannot have a structure prefix, it's sufficient to check head
|
|
952
|
-
if(head.id === env.assocStack.id()) {
|
|
939
|
+
if (head.id === env.assocStack.id()) {
|
|
953
940
|
// source side from view point of view (target side from forward point of view)
|
|
954
941
|
path = tail; // pop assoc step
|
|
955
942
|
const elt = env.lead._combined[path[0].id];
|
|
956
943
|
|
|
957
|
-
if(elt) {
|
|
958
|
-
if(Array.isArray(elt)) {
|
|
944
|
+
if (elt) {
|
|
945
|
+
if (Array.isArray(elt)) {
|
|
959
946
|
const names = elt.map(e => (e._origin._main || e._origin).name.id);
|
|
960
|
-
error(null, [ assocQAT._origin.location, assocQAT._origin ], {
|
|
961
|
-
|
|
947
|
+
error(null, [ assocQAT._origin.location, assocQAT._origin ], {
|
|
948
|
+
elemref: path[0].id, id: assoc.name.id, art: assoc._main, names,
|
|
949
|
+
},
|
|
950
|
+
'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) is available from multiple query sources $(NAMES)');
|
|
962
951
|
return pathNode.path;
|
|
963
|
-
} else {
|
|
964
|
-
// check if element has same origin on both ends
|
|
965
|
-
if(elt._origin._main !== path[0]._artifact._origin._main) {
|
|
966
|
-
warning(null, [ assocQAT._origin.location, assocQAT._origin ], {
|
|
967
|
-
elemref: path[0].id,
|
|
968
|
-
id: assoc.name.id, art: assoc._main.name.id,
|
|
969
|
-
name: path[0]._artifact._origin._main.name.id,
|
|
970
|
-
alias: elt._origin._main.name.id,
|
|
971
|
-
source: elt._main.name.id,
|
|
972
|
-
}, 'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) originates from $(NAME) and from $(ALIAS) in $(SOURCE)');
|
|
973
|
-
}
|
|
974
|
-
_navigation = elt._parent;
|
|
975
952
|
}
|
|
976
|
-
|
|
953
|
+
// check if element has same origin on both ends
|
|
954
|
+
if (elt._origin._main !== path[0]._artifact._origin._main) {
|
|
955
|
+
warning(null, [ assocQAT._origin.location, assocQAT._origin ], {
|
|
956
|
+
elemref: path[0].id,
|
|
957
|
+
id: assoc.name.id,
|
|
958
|
+
art: assoc._main.name.id,
|
|
959
|
+
name: path[0]._artifact._origin._main.name.id,
|
|
960
|
+
alias: elt._origin._main.name.id,
|
|
961
|
+
source: elt._main.name.id,
|
|
962
|
+
}, 'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) originates from $(NAME) and from $(ALIAS) in $(SOURCE)');
|
|
963
|
+
}
|
|
964
|
+
_navigation = elt._parent;
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
977
967
|
error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: (assoc._main || assoc) },
|
|
978
|
-
|
|
968
|
+
'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) has not been found');
|
|
979
969
|
return pathNode.path;
|
|
980
970
|
}
|
|
981
|
-
}
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
982
973
|
// target side from view point of view (source side from forward point of view)
|
|
983
|
-
//if(assocQAT.$QA._artifact === path[0]._artifact._parent)
|
|
974
|
+
// if(assocQAT.$QA._artifact === path[0]._artifact._parent)
|
|
984
975
|
_navigation = assocQAT;
|
|
985
976
|
}
|
|
986
977
|
}
|
|
987
978
|
[ tableAlias, path ] = constructTableAliasAndTailPath(path, _navigation);
|
|
988
979
|
}
|
|
989
|
-
else // ON condition of non-mixin association
|
|
990
|
-
{
|
|
980
|
+
else { // ON condition of non-mixin association
|
|
991
981
|
// strip a structure prefix from this ON cond path (offset -1)
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
{
|
|
982
|
+
path = env.assocStack.stripPrefix(path, -1);
|
|
983
|
+
[ head, ...tail ] = path;
|
|
984
|
+
if (prefixes.includes(head.id)) { // target side
|
|
995
985
|
// no element prefix on target side
|
|
996
986
|
path = translateONCondPath(tail);
|
|
997
987
|
tableAlias = tgtAlias;
|
|
998
988
|
}
|
|
999
|
-
else // source side
|
|
1000
|
-
{
|
|
989
|
+
else { // source side
|
|
1001
990
|
tableAlias = srcAlias;
|
|
1002
991
|
// if path is not an absolute path, prepend element prefix
|
|
1003
992
|
path = translateONCondPath(path, !hasDollarSelfPrefix ? assoc.$elementPrefix : undefined);
|
|
@@ -1009,25 +998,25 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1009
998
|
|
|
1010
999
|
// Return the original association if expr is a backlink term, undefined otherwise
|
|
1011
1000
|
function getForwardAssociationExpr(expr) {
|
|
1012
|
-
if(expr.op && expr.op.val === '=' && expr.args.length === 2)
|
|
1001
|
+
if (expr.op && expr.op.val === '=' && expr.args.length === 2)
|
|
1013
1002
|
return getForwardAssociation(expr.args[0].path, expr.args[1].path);
|
|
1014
|
-
|
|
1003
|
+
|
|
1015
1004
|
return undefined;
|
|
1016
1005
|
}
|
|
1017
1006
|
|
|
1018
1007
|
function getForwardAssociation(lhs, rhs) {
|
|
1019
1008
|
// [alpha.]BACKLINK.[beta.]FORWARD
|
|
1020
|
-
if(lhs && rhs) {
|
|
1021
|
-
if(rhs.length === 1 && rhs[0].id === '$self' &&
|
|
1009
|
+
if (lhs && rhs) {
|
|
1010
|
+
if (rhs.length === 1 && rhs[0].id === '$self' &&
|
|
1022
1011
|
lhs.length > 1 && hasPrefix(lhs))
|
|
1023
|
-
return lhs[lhs.length-1]._artifact;
|
|
1024
|
-
if(lhs.length === 1 && lhs[0].id === '$self' &&
|
|
1012
|
+
return lhs[lhs.length - 1]._artifact;
|
|
1013
|
+
if (lhs.length === 1 && lhs[0].id === '$self' &&
|
|
1025
1014
|
rhs.length > 1 && hasPrefix(rhs))
|
|
1026
|
-
return rhs[rhs.length-1]._artifact;
|
|
1015
|
+
return rhs[rhs.length - 1]._artifact;
|
|
1027
1016
|
}
|
|
1028
1017
|
|
|
1029
1018
|
function hasPrefix(path) {
|
|
1030
|
-
return path.reduce((rc, ps) => !rc ? (ps.id
|
|
1019
|
+
return path.reduce((rc, ps) => (!rc ? (ps.id === env.assocStack.id()) : rc), false);
|
|
1031
1020
|
}
|
|
1032
1021
|
return undefined;
|
|
1033
1022
|
}
|
|
@@ -1038,41 +1027,38 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1038
1027
|
A QA (QueryArtifact) is a representative for a table/view that must appear
|
|
1039
1028
|
in the FROM clause either named directly or indirectly through an association.
|
|
1040
1029
|
*/
|
|
1041
|
-
function createQA(env, artifact, alias, namedArgs=undefined)
|
|
1042
|
-
|
|
1043
|
-
if(alias === undefined) {
|
|
1030
|
+
function createQA(env, artifact, alias, namedArgs = undefined) {
|
|
1031
|
+
if (alias === undefined)
|
|
1044
1032
|
throw new CompilerAssertion('no alias provided');
|
|
1045
|
-
|
|
1033
|
+
|
|
1046
1034
|
|
|
1047
1035
|
const pathStep = {
|
|
1048
1036
|
id: (artifact._main || artifact).name.id,
|
|
1049
1037
|
_artifact: artifact,
|
|
1050
|
-
_navigation
|
|
1038
|
+
_navigation: { name: { select: env.queryIndex + 1 } }, // ???
|
|
1051
1039
|
};
|
|
1052
1040
|
|
|
1053
|
-
if(namedArgs)
|
|
1041
|
+
if (namedArgs)
|
|
1054
1042
|
pathStep.args = namedArgs;
|
|
1055
|
-
if(isBooleanAnnotation(artifact['@cds.persistence.udf'], true))
|
|
1043
|
+
if (isBooleanAnnotation(artifact['@cds.persistence.udf'], true))
|
|
1056
1044
|
pathStep.$syntax = 'udf';
|
|
1057
|
-
if(isBooleanAnnotation(artifact['@cds.persistence.calcview'], true))
|
|
1045
|
+
if (isBooleanAnnotation(artifact['@cds.persistence.calcview'], true))
|
|
1058
1046
|
pathStep.$syntax = 'calcview';
|
|
1059
1047
|
|
|
1060
|
-
const node = constructPathNode( [ pathStep
|
|
1048
|
+
const node = constructPathNode( [ pathStep ], alias );
|
|
1061
1049
|
return node;
|
|
1062
1050
|
}
|
|
1063
1051
|
|
|
1064
1052
|
// Remark CW: why boolean and not just truthy/falsy as usual? See annotationVal() below
|
|
1065
|
-
function isBooleanAnnotation(prop, val=true) {
|
|
1053
|
+
function isBooleanAnnotation(prop, val = true) {
|
|
1066
1054
|
return prop && prop.val !== undefined && prop.val === val && prop.literal === 'boolean';
|
|
1067
1055
|
}
|
|
1068
1056
|
|
|
1069
|
-
function incAliasCount(env, QA)
|
|
1070
|
-
|
|
1071
|
-
if(!QA.numberedAlias)
|
|
1072
|
-
{
|
|
1057
|
+
function incAliasCount(env, QA) {
|
|
1058
|
+
if (!QA.numberedAlias) {
|
|
1073
1059
|
// Debug only:
|
|
1074
1060
|
// QA.name.id += '_' + (QA.path[0]._navigation === undefined ? '***navigation_missing***' : QA.path[0]._navigation.name.select) + '_' + env.aliasCount++;
|
|
1075
|
-
QA.name.id +=
|
|
1061
|
+
QA.name.id += `_${ env.aliasCount++ }`;
|
|
1076
1062
|
QA.numberedAlias = true;
|
|
1077
1063
|
}
|
|
1078
1064
|
}
|
|
@@ -1088,8 +1074,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1088
1074
|
tableAlias = [ aliasName, _artifact, _navigation ]
|
|
1089
1075
|
path = [ { id: ..., _artifact: ... (unused) } ]
|
|
1090
1076
|
*/
|
|
1091
|
-
function rewritePathsInFilterExpression(node, getTableAliasAndPathSteps, env)
|
|
1092
|
-
{
|
|
1077
|
+
function rewritePathsInFilterExpression(node, getTableAliasAndPathSteps, env) {
|
|
1093
1078
|
const innerEnv = {
|
|
1094
1079
|
lead: env.lead,
|
|
1095
1080
|
location: env.location,
|
|
@@ -1097,10 +1082,10 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1097
1082
|
aliasCount: env.aliasCount,
|
|
1098
1083
|
walkover: {},
|
|
1099
1084
|
callback: [
|
|
1100
|
-
function(pathNode) {
|
|
1101
|
-
if(checkPathDictionary(pathNode, env)) {
|
|
1085
|
+
function rewritePathNode(pathNode) {
|
|
1086
|
+
if (checkPathDictionary(pathNode, env)) {
|
|
1102
1087
|
const head = pathNode.path[0];
|
|
1103
|
-
if(head._navigation?.kind === '$self') {
|
|
1088
|
+
if (head._navigation?.kind === '$self') {
|
|
1104
1089
|
substituteDollarSelf(pathNode);
|
|
1105
1090
|
}
|
|
1106
1091
|
else {
|
|
@@ -1109,14 +1094,14 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1109
1094
|
const leafArtifact = path.at(-1)._artifact;
|
|
1110
1095
|
// Walk from left to right and search for first assoc. If assocs in filters become join relevant in the future,
|
|
1111
1096
|
// i.e. not only fk-access, we need to revisit this
|
|
1112
|
-
for(let i = 0; i < path.length; i++) {
|
|
1097
|
+
for (let i = 0; i < path.length; i++) {
|
|
1113
1098
|
const pathStep = path[i];
|
|
1114
|
-
if(pathStep._artifact?.foreignKeys) {
|
|
1099
|
+
if (pathStep._artifact?.foreignKeys) {
|
|
1115
1100
|
const possibleNonAliasedFkName = path.slice(i).map(ps => ps.id).join(pathDelimiter);
|
|
1116
|
-
if(!pathStep._artifact.$flatSrcFKs)
|
|
1117
|
-
|
|
1101
|
+
if (!pathStep._artifact.$flatSrcFKs)
|
|
1102
|
+
setProp(pathStep._artifact, '$flatSrcFKs', flattenElement(pathStep._artifact, true, pathStep._artifact.name.id, pathStep._artifact.name.id));
|
|
1118
1103
|
const fk = pathStep._artifact.$flatSrcFKs.find(f => f._artifact === leafArtifact && f.acc.startsWith(possibleNonAliasedFkName));
|
|
1119
|
-
if(fk) {
|
|
1104
|
+
if (fk) {
|
|
1120
1105
|
rewrittenPath.push(fk);
|
|
1121
1106
|
i = path.length;
|
|
1122
1107
|
continue;
|
|
@@ -1128,7 +1113,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1128
1113
|
replaceNodeContent(pathNode, constructPathNode([ tableAlias, { id: rewrittenPath.map(ps => ps.id).join(pathDelimiter), _artifact: pathNode._artifact } ]));
|
|
1129
1114
|
}
|
|
1130
1115
|
}
|
|
1131
|
-
}
|
|
1116
|
+
},
|
|
1117
|
+
],
|
|
1132
1118
|
};
|
|
1133
1119
|
walk(node, innerEnv);
|
|
1134
1120
|
}
|
|
@@ -1139,11 +1125,11 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1139
1125
|
If newNode is a path => oldNode._artifact === newNode._artifact, no need to
|
|
1140
1126
|
exchange _artifact (as non-iterable property it is not assigned).
|
|
1141
1127
|
*/
|
|
1142
|
-
function replaceNodeContent(oldNode, newNode)
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1128
|
+
function replaceNodeContent(oldNode, newNode) {
|
|
1129
|
+
if (!newNode.path) {
|
|
1130
|
+
Object.keys(oldNode).forEach((k) => {
|
|
1131
|
+
delete oldNode[k];
|
|
1132
|
+
});
|
|
1147
1133
|
delete oldNode._artifact;
|
|
1148
1134
|
}
|
|
1149
1135
|
Object.assign(oldNode, newNode);
|
|
@@ -1157,7 +1143,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1157
1143
|
if (tableAlias._joinParent)
|
|
1158
1144
|
tableAlias._joinParent.args[tableAlias.$joinArgsIndex] = replacementNode;
|
|
1159
1145
|
else
|
|
1160
|
-
|
|
1146
|
+
tableAlias._parent.from = replacementNode;
|
|
1161
1147
|
}
|
|
1162
1148
|
|
|
1163
1149
|
/*
|
|
@@ -1165,72 +1151,63 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1165
1151
|
respecting the src or the target side of the ON condition.
|
|
1166
1152
|
Return an array of column names and it's leaf element.
|
|
1167
1153
|
*/
|
|
1168
|
-
function flattenElement(element, srcSide, prefix, acc)
|
|
1169
|
-
{
|
|
1154
|
+
function flattenElement(element, srcSide, prefix, acc) {
|
|
1170
1155
|
// terminate if element is unstructured
|
|
1171
|
-
if(!element.foreignKeys && !element.elements)
|
|
1156
|
+
if (!element.foreignKeys && !element.elements)
|
|
1172
1157
|
return [ { id: prefix, _artifact: element, acc } ];
|
|
1173
1158
|
|
|
1174
1159
|
let paths = [];
|
|
1175
1160
|
// get paths of managed assocs (unmanaged assocs are not allowed in FK paths)
|
|
1176
|
-
if(element.foreignKeys)
|
|
1177
|
-
|
|
1178
|
-
for(const fkn in element.foreignKeys)
|
|
1179
|
-
{
|
|
1161
|
+
if (element.foreignKeys) {
|
|
1162
|
+
for (const fkn in element.foreignKeys) {
|
|
1180
1163
|
const fk = element.foreignKeys[fkn];
|
|
1181
1164
|
// ignore an unmanaged association
|
|
1182
|
-
if(fk.targetElement._artifact.target &&
|
|
1165
|
+
if (fk.targetElement._artifact.target &&
|
|
1183
1166
|
fk.targetElement._artifact.on &&
|
|
1184
1167
|
!fk.targetElement._artifact.foreignKeys)
|
|
1185
1168
|
continue;
|
|
1186
1169
|
// once a fk is to be followed, treat all sub-paths as srcSide, this will add fk.name.id only
|
|
1187
|
-
if(srcSide)
|
|
1188
|
-
paths = paths.concat(flattenElement(fk.targetElement._artifact, true,
|
|
1189
|
-
|
|
1190
|
-
{
|
|
1170
|
+
if (srcSide) {
|
|
1171
|
+
paths = paths.concat(flattenElement(fk.targetElement._artifact, true, fk.name.id, fk.targetElement.path.map(ps => ps.id).join(pathDelimiter)));
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1191
1174
|
// consume path segments until the next assoc and substitute against fk alias until path is eaten up
|
|
1192
1175
|
let [ assocStep, tail, fkPrefix ] = pathAsStringUpToAssoc(fk.targetElement.path);
|
|
1193
|
-
while(assocStep && tail.length)
|
|
1194
|
-
|
|
1195
|
-
[tail, fkPrefix] =
|
|
1196
|
-
[assocStep, tail, fkPrefix] = pathAsStringUpToAssoc(tail, fkPrefix);
|
|
1176
|
+
while (assocStep && tail.length) {
|
|
1177
|
+
[ tail, fkPrefix ] = substituteFKAliasForPath(assocStep, tail, fkPrefix);
|
|
1178
|
+
[ assocStep, tail, fkPrefix ] = pathAsStringUpToAssoc(tail, fkPrefix);
|
|
1197
1179
|
}
|
|
1198
1180
|
paths = paths.concat(flattenElement(fk.targetElement._artifact, true, fkPrefix, fk.targetElement.path.map(ps => ps.id).join(pathDelimiter)));
|
|
1199
1181
|
}
|
|
1200
1182
|
}
|
|
1201
1183
|
}
|
|
1202
1184
|
// get paths of plain structured elements
|
|
1203
|
-
else if(element.elements)
|
|
1204
|
-
|
|
1205
|
-
for(const n in element.elements)
|
|
1206
|
-
{
|
|
1185
|
+
else if (element.elements) {
|
|
1186
|
+
for (const n in element.elements) {
|
|
1207
1187
|
const elt = element.elements[n];
|
|
1208
1188
|
paths = paths.concat(flattenElement(elt, true, elt.name.id, elt.name.id));
|
|
1209
1189
|
}
|
|
1210
1190
|
}
|
|
1211
|
-
return paths.map(p => {
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
}
|
|
1217
|
-
} );
|
|
1191
|
+
return paths.map(p => ({
|
|
1192
|
+
id: (prefix ? prefix + pathDelimiter : '' ) + p.id,
|
|
1193
|
+
acc: (acc ? acc + pathDelimiter : '') + p.acc,
|
|
1194
|
+
_artifact: p._artifact,
|
|
1195
|
+
}) );
|
|
1218
1196
|
}
|
|
1219
1197
|
|
|
1220
1198
|
|
|
1221
1199
|
/*
|
|
1222
1200
|
Construct both the TA path step and the path tail for a given AST path array
|
|
1223
1201
|
*/
|
|
1224
|
-
function constructTableAliasAndTailPath(path, navigation=undefined)
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
if(navigation === undefined)
|
|
1202
|
+
function constructTableAliasAndTailPath(path, navigation = undefined) {
|
|
1203
|
+
const [ head, ...tail ] = path;
|
|
1204
|
+
if (navigation === undefined)
|
|
1228
1205
|
navigation = head._navigation;
|
|
1229
1206
|
|
|
1230
1207
|
const QA = navigation.$QA || navigation._parent.$QA;
|
|
1231
1208
|
|
|
1232
1209
|
// First path step is table alias, use and pop it off
|
|
1233
|
-
if(navigation.$QA && tail.length > 0)
|
|
1210
|
+
if (navigation.$QA && tail.length > 0)
|
|
1234
1211
|
path = tail;
|
|
1235
1212
|
|
|
1236
1213
|
return [ constructTableAliasPathStep(QA), path ];
|
|
@@ -1242,15 +1219,13 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1242
1219
|
/*
|
|
1243
1220
|
Translate ON cond paths and substitute FK aliases
|
|
1244
1221
|
*/
|
|
1245
|
-
function translateONCondPath(path, prefix)
|
|
1246
|
-
{
|
|
1222
|
+
function translateONCondPath(path, prefix) {
|
|
1247
1223
|
let [ assocStep, tail, fkPrefix ] = pathAsStringUpToAssoc(path);
|
|
1248
|
-
while(assocStep && tail.length)
|
|
1249
|
-
|
|
1250
|
-
[tail, fkPrefix] =
|
|
1251
|
-
[assocStep, tail, fkPrefix] = pathAsStringUpToAssoc(tail, fkPrefix);
|
|
1224
|
+
while (assocStep && tail.length) {
|
|
1225
|
+
[ tail, fkPrefix ] = substituteFKAliasForPath(assocStep, tail, fkPrefix);
|
|
1226
|
+
[ assocStep, tail, fkPrefix ] = pathAsStringUpToAssoc(tail, fkPrefix);
|
|
1252
1227
|
}
|
|
1253
|
-
return [ { id: (prefix ? prefix + fkPrefix : fkPrefix), _artifact: path[path.length-1]._artifact } ];
|
|
1228
|
+
return [ { id: (prefix ? prefix + fkPrefix : fkPrefix), _artifact: path[path.length - 1]._artifact } ];
|
|
1254
1229
|
}
|
|
1255
1230
|
|
|
1256
1231
|
/*
|
|
@@ -1261,17 +1236,16 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1261
1236
|
|
|
1262
1237
|
Return assocPathStep, the remaining tail path and the path string
|
|
1263
1238
|
*/
|
|
1264
|
-
function pathAsStringUpToAssoc(path, pathStr)
|
|
1265
|
-
|
|
1266
|
-
if(!pathStr)
|
|
1239
|
+
function pathAsStringUpToAssoc(path, pathStr) {
|
|
1240
|
+
if (!pathStr)
|
|
1267
1241
|
pathStr = '';
|
|
1268
|
-
const assocStep = path.find(ps => {
|
|
1269
|
-
if(pathStr.length > 0)
|
|
1242
|
+
const assocStep = path.find((ps) => {
|
|
1243
|
+
if (pathStr.length > 0)
|
|
1270
1244
|
pathStr += pathDelimiter;
|
|
1271
1245
|
pathStr += ps.id;
|
|
1272
1246
|
return (ps._artifact.target); // true if it has a target => is assoc => terminate find
|
|
1273
1247
|
});
|
|
1274
|
-
return [ assocStep, path.slice(path.indexOf(assocStep)+1), pathStr ];
|
|
1248
|
+
return [ assocStep, path.slice(path.indexOf(assocStep) + 1), pathStr ];
|
|
1275
1249
|
}
|
|
1276
1250
|
|
|
1277
1251
|
/*
|
|
@@ -1291,18 +1265,15 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1291
1265
|
* @param {any[]} path
|
|
1292
1266
|
* @param {string} [pathStr='']
|
|
1293
1267
|
*/
|
|
1294
|
-
function substituteFKAliasForPath(assocStep, path, pathStr='')
|
|
1295
|
-
|
|
1296
|
-
if(assocStep && assocStep._artifact && assocStep._artifact.$fkPathPrefixTree) {
|
|
1268
|
+
function substituteFKAliasForPath(assocStep, path, pathStr = '') {
|
|
1269
|
+
if (assocStep && assocStep._artifact && assocStep._artifact.$fkPathPrefixTree) {
|
|
1297
1270
|
let ppt = assocStep._artifact.$fkPathPrefixTree.children;
|
|
1298
1271
|
/** @type any */
|
|
1299
|
-
let fk
|
|
1300
|
-
let fkPs
|
|
1301
|
-
path.forEach(ps => {
|
|
1302
|
-
if(ppt[ps.id])
|
|
1303
|
-
|
|
1304
|
-
if(ppt[ps.id]._fk)
|
|
1305
|
-
{
|
|
1272
|
+
let fk; // last found FK
|
|
1273
|
+
let fkPs; // last path step that found FK
|
|
1274
|
+
path.forEach((ps) => {
|
|
1275
|
+
if (ppt[ps.id]) {
|
|
1276
|
+
if (ppt[ps.id]._fk) {
|
|
1306
1277
|
fk = ppt[ps.id]._fk;
|
|
1307
1278
|
fkPs = ps;
|
|
1308
1279
|
}
|
|
@@ -1310,24 +1281,21 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1310
1281
|
}
|
|
1311
1282
|
});
|
|
1312
1283
|
|
|
1313
|
-
if(fk)
|
|
1314
|
-
|
|
1315
|
-
if(pathStr.length)
|
|
1284
|
+
if (fk) {
|
|
1285
|
+
if (pathStr.length)
|
|
1316
1286
|
pathStr += pathDelimiter;
|
|
1317
1287
|
pathStr += fk.name.id;
|
|
1318
1288
|
}
|
|
1319
1289
|
|
|
1320
|
-
const tail = path.slice(path.indexOf(fkPs)+1);
|
|
1290
|
+
const tail = path.slice(path.indexOf(fkPs) + 1);
|
|
1321
1291
|
// If foreign key is an association itself, apply substituteFKAliasForPath on tail
|
|
1322
|
-
if(fk && fk.targetElement._artifact.target && tail.length)
|
|
1292
|
+
if (fk && fk.targetElement._artifact.target && tail.length)
|
|
1323
1293
|
return substituteFKAliasForPath(fk.targetElement, tail, pathStr);
|
|
1324
|
-
|
|
1325
|
-
return [ tail, pathStr ];
|
|
1326
|
-
}
|
|
1327
|
-
else {
|
|
1328
|
-
//error(null, assocStep.location, `No fkPrefixTree for association, please report this error`);
|
|
1329
|
-
return [ path, pathStr ];
|
|
1294
|
+
return [ tail, pathStr ];
|
|
1330
1295
|
}
|
|
1296
|
+
|
|
1297
|
+
// error(null, assocStep.location, `No fkPrefixTree for association, please report this error`);
|
|
1298
|
+
return [ path, pathStr ];
|
|
1331
1299
|
}
|
|
1332
1300
|
|
|
1333
1301
|
/*
|
|
@@ -1340,25 +1308,24 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1340
1308
|
4) $self, $projection without suffix should not appear here anymore
|
|
1341
1309
|
Returns true on success, false otherwise
|
|
1342
1310
|
*/
|
|
1343
|
-
function checkPathDictionary(pathDict, env)
|
|
1344
|
-
|
|
1345
|
-
if(pathDict.$check !== undefined)
|
|
1311
|
+
function checkPathDictionary(pathDict, env) {
|
|
1312
|
+
if (pathDict.$check !== undefined)
|
|
1346
1313
|
return pathDict.$check;
|
|
1347
1314
|
|
|
1348
1315
|
pathDict.$check = true;
|
|
1349
1316
|
|
|
1350
1317
|
// all leaf types must be scalar in a query
|
|
1351
|
-
let path = pathDict
|
|
1352
|
-
let [head, ...tail] = path;
|
|
1318
|
+
let { path } = pathDict;
|
|
1319
|
+
let [ head, ...tail ] = path;
|
|
1353
1320
|
|
|
1354
1321
|
// pop head again if it is a table alias or $projection
|
|
1355
|
-
if(['$projection', '$self'].includes(head.id)) {
|
|
1322
|
+
if ([ '$projection', '$self' ].includes(head.id)) {
|
|
1356
1323
|
path = tail;
|
|
1357
|
-
[head, ...tail] = path;
|
|
1324
|
+
[ head, ...tail ] = path;
|
|
1358
1325
|
}
|
|
1359
|
-
if(head && env.tableAliases && env.tableAliases.includes(head.id))
|
|
1326
|
+
if (head && env.tableAliases && env.tableAliases.includes(head.id))
|
|
1360
1327
|
path = tail;
|
|
1361
|
-
|
|
1328
|
+
|
|
1362
1329
|
// assocStack eventually undefined => head is not Assoc
|
|
1363
1330
|
// assoc prefix can be something structured or just the id, the core
|
|
1364
1331
|
// compiler decides if it wants to add 'element' or only 'id' to the XSN
|
|
@@ -1369,52 +1336,51 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1369
1336
|
const lead = env.art || env.lead;
|
|
1370
1337
|
path.forEach((ps) => {
|
|
1371
1338
|
/* checks for all path steps */
|
|
1372
|
-
if(ps.args) {
|
|
1373
|
-
error(null, [pathDict.location, lead],
|
|
1374
|
-
|
|
1375
|
-
|
|
1339
|
+
if (ps.args) {
|
|
1340
|
+
error(null, [ pathDict.location, lead ],
|
|
1341
|
+
{ art: lead._main || lead, id: ps.id },
|
|
1342
|
+
'$(ART): $(ID) must not have parameters');
|
|
1376
1343
|
pathDict.$check = false;
|
|
1377
1344
|
}
|
|
1378
|
-
if(ps.where) {
|
|
1379
|
-
error(null, [pathDict.location, lead],
|
|
1380
|
-
|
|
1381
|
-
|
|
1345
|
+
if (ps.where) {
|
|
1346
|
+
error(null, [ pathDict.location, lead ],
|
|
1347
|
+
{ art: lead._main || lead, id: ps.id },
|
|
1348
|
+
'$(ART): $(ID) must not have a filter');
|
|
1382
1349
|
pathDict.$check = false;
|
|
1383
1350
|
}
|
|
1384
|
-
if(ps._artifact.virtual) {
|
|
1385
|
-
error(null, [pathDict.location, lead],
|
|
1386
|
-
|
|
1387
|
-
|
|
1351
|
+
if (ps._artifact.virtual) {
|
|
1352
|
+
error(null, [ pathDict.location, lead ],
|
|
1353
|
+
{ art: lead._main || lead, id: ps.id },
|
|
1354
|
+
'$(ART): $(ID) must not be virtual');
|
|
1388
1355
|
pathDict.$check = false;
|
|
1389
1356
|
}
|
|
1390
1357
|
// checks for all path steps except the first one (if it is the name of the defining association)
|
|
1391
|
-
if(ps._artifact && ps._artifact.target)
|
|
1392
|
-
{
|
|
1358
|
+
if (ps._artifact && ps._artifact.target) {
|
|
1393
1359
|
// if this is not the last path step, complain
|
|
1394
|
-
const la1 = pathDict.path[pathDict.path.indexOf(ps)+1];
|
|
1395
|
-
if(la1) {
|
|
1396
|
-
if(ps._artifact.on)
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
'$(ART): $(ID) in path $(ALIAS) must not be an unmanaged association');
|
|
1360
|
+
const la1 = pathDict.path[pathDict.path.indexOf(ps) + 1];
|
|
1361
|
+
if (la1) {
|
|
1362
|
+
if (ps._artifact.on) {
|
|
1363
|
+
error(null, [ pathDict.location, lead ],
|
|
1364
|
+
{ art: lead._main || lead, id: ps.id, alias: pathAsStr(pathDict.path) },
|
|
1365
|
+
'$(ART): $(ID) in path $(ALIAS) must not be an unmanaged association');
|
|
1401
1366
|
pathDict.$check = false;
|
|
1402
1367
|
}
|
|
1403
|
-
else if(ps._artifact.$fkPathPrefixTree)// must be managed
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1368
|
+
else if (ps._artifact.$fkPathPrefixTree) { // must be managed
|
|
1369
|
+
if (!ps._artifact.$fkPathPrefixTree.children[la1.id]) {
|
|
1370
|
+
error(null, [ pathDict.location, lead ],
|
|
1371
|
+
{
|
|
1372
|
+
art: lead._main || lead, id: la1.id, name: ps.id, alias: pathAsStr(pathDict.path),
|
|
1373
|
+
},
|
|
1374
|
+
'$(ART): $(ID) is not foreign key of managed association $(NAME) in path $(ALIAS)' );
|
|
1409
1375
|
pathDict.$check = false;
|
|
1410
1376
|
}
|
|
1411
1377
|
}
|
|
1412
1378
|
}
|
|
1413
1379
|
else {
|
|
1414
1380
|
// it is the last path step => no association
|
|
1415
|
-
error(null, [pathDict.location, lead],
|
|
1416
|
-
|
|
1417
|
-
|
|
1381
|
+
error(null, [ pathDict.location, lead ],
|
|
1382
|
+
{ art: lead._main || lead, id: ps.id, alias: pathAsStr(pathDict.path) },
|
|
1383
|
+
'$(ART): $(ID) in path $(ALIAS) must not be an association');
|
|
1418
1384
|
pathDict.$check = false;
|
|
1419
1385
|
}
|
|
1420
1386
|
}
|
|
@@ -1423,9 +1389,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1423
1389
|
const lastSegment = path[path.length - 1];
|
|
1424
1390
|
const artifact = lastSegment && lastSegment._artifact && lastSegment._artifact.type && lastSegment._artifact.type._artifact && lastSegment._artifact.type._artifact;
|
|
1425
1391
|
if (artifact && artifact.elements) {
|
|
1426
|
-
error(null, [pathDict.location, lead],
|
|
1427
|
-
|
|
1428
|
-
|
|
1392
|
+
error(null, [ pathDict.location, lead ],
|
|
1393
|
+
{ art: lead._main || lead, id: lastSegment.id },
|
|
1394
|
+
'$(ART): $(ID) must have scalar type');
|
|
1429
1395
|
pathDict.$check = false;
|
|
1430
1396
|
}
|
|
1431
1397
|
return pathDict.$check;
|
|
@@ -1450,23 +1416,23 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1450
1416
|
A node in the path prefix tree is abbreviated as QAT (which stands for Query Association Tree,
|
|
1451
1417
|
a term originating from way back in time).
|
|
1452
1418
|
*/
|
|
1453
|
-
function mergePathIntoQAT(pathDict, env)
|
|
1454
|
-
|
|
1455
|
-
const path = pathDict.path;
|
|
1419
|
+
function mergePathIntoQAT(pathDict, env) {
|
|
1420
|
+
const { path } = pathDict;
|
|
1456
1421
|
|
|
1457
|
-
if(path.length === 0)
|
|
1422
|
+
if (path.length === 0)
|
|
1458
1423
|
return;
|
|
1459
1424
|
|
|
1460
1425
|
let qatChildrenName = '$qat';
|
|
1461
|
-
if(env.location === 'from')
|
|
1426
|
+
if (env.location === 'from')
|
|
1462
1427
|
qatChildrenName = '$fqat';
|
|
1463
|
-
if(env.location === 'onCondFrom')
|
|
1428
|
+
if (env.location === 'onCondFrom')
|
|
1464
1429
|
return;
|
|
1465
1430
|
|
|
1466
|
-
|
|
1431
|
+
// eslint-disable-next-line prefer-const
|
|
1432
|
+
let [ head, ...tail ] = path;
|
|
1467
1433
|
|
|
1468
1434
|
// qatParent is the node where the starting qat is attached to
|
|
1469
|
-
let qatParent
|
|
1435
|
+
let qatParent;
|
|
1470
1436
|
|
|
1471
1437
|
// Note: If head is $self, we would need to resolve it if the path follows associations.
|
|
1472
1438
|
// However, that is already rejected by SQL backend checks. For example $self paths
|
|
@@ -1474,17 +1440,16 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1474
1440
|
|
|
1475
1441
|
// FROM and filter paths do not have a _navigation, but for filter paths
|
|
1476
1442
|
// the corresponding path step (to where the filter was attached to) is in env.pathStep
|
|
1477
|
-
if(!head._navigation)
|
|
1478
|
-
{
|
|
1443
|
+
if (!head._navigation) {
|
|
1479
1444
|
// speciality for OrderBy: If path has no _navigation don't merge it.
|
|
1480
1445
|
// Path is alias to select item expression
|
|
1481
|
-
if(['OrderBy', 'UnionOuterOrderBy'].includes(env.location))
|
|
1446
|
+
if ([ 'OrderBy', 'UnionOuterOrderBy' ].includes(env.location))
|
|
1482
1447
|
return;
|
|
1483
1448
|
|
|
1484
1449
|
// env.pathStep is set in walkPath for walk on filter conditions
|
|
1485
|
-
if(env.pathStep)
|
|
1450
|
+
if (env.pathStep)
|
|
1486
1451
|
qatParent = env.pathStep._navigation;
|
|
1487
|
-
else if(pathDict.name) // from table path with its alias
|
|
1452
|
+
else if (pathDict.name) // from table path with its alias
|
|
1488
1453
|
qatParent = env.lead.$tableAliases[pathDict.name.id];
|
|
1489
1454
|
else
|
|
1490
1455
|
// tableAlias not found yet, last resort is head.id => published Assoc
|
|
@@ -1492,32 +1457,28 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1492
1457
|
tail = path; // start with the full path
|
|
1493
1458
|
}
|
|
1494
1459
|
// All other paths have a _navigation attribute
|
|
1495
|
-
else if(head._navigation)
|
|
1496
|
-
{
|
|
1460
|
+
else if (head._navigation) {
|
|
1497
1461
|
/*
|
|
1498
1462
|
Always start with QAT merge at $tableAlias, even if path doesn't start there.
|
|
1499
1463
|
First identify $tableAlias (must be either head or head's parent) The resolver
|
|
1500
1464
|
sets a _navigation at the very first path step that either points to $tableAlias
|
|
1501
1465
|
or to a top level element from _combined which itself parent's to $tableAlias).
|
|
1502
1466
|
*/
|
|
1503
|
-
if(head._navigation.kind === '$navElement')
|
|
1504
|
-
{
|
|
1467
|
+
if (head._navigation.kind === '$navElement') {
|
|
1505
1468
|
qatParent = head._navigation._parent;
|
|
1506
1469
|
tail = path; // Start with the full path (no table alias prefix)
|
|
1507
1470
|
}
|
|
1508
|
-
else if(head._navigation.kind === 'mixin') // This is a mixin assoc
|
|
1509
|
-
{
|
|
1471
|
+
else if (head._navigation.kind === 'mixin') { // This is a mixin assoc
|
|
1510
1472
|
qatParent = head._navigation;
|
|
1511
1473
|
tail = path;
|
|
1512
1474
|
}
|
|
1513
|
-
else // Head is a table alias already
|
|
1514
|
-
{
|
|
1475
|
+
else { // Head is a table alias already
|
|
1515
1476
|
qatParent = head._navigation;
|
|
1516
1477
|
}
|
|
1517
1478
|
}
|
|
1518
1479
|
|
|
1519
|
-
if(qatParent
|
|
1520
|
-
throw new CompilerAssertion(
|
|
1480
|
+
if (qatParent === undefined || qatParent === null)
|
|
1481
|
+
throw new CompilerAssertion(`table alias/qathost not found for path: ${ pathAsStr(path) }`);
|
|
1521
1482
|
|
|
1522
1483
|
const rootQat = qatParent;
|
|
1523
1484
|
|
|
@@ -1535,22 +1496,21 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1535
1496
|
*/
|
|
1536
1497
|
let qatName = pathStep.id;
|
|
1537
1498
|
|
|
1538
|
-
if(pathStep.where)
|
|
1539
|
-
qatName
|
|
1540
|
-
|
|
1541
|
-
if(pathStep.args) {
|
|
1499
|
+
if (pathStep.where)
|
|
1500
|
+
qatName += JSON.stringify(compactExpr(pathStep.where));
|
|
1501
|
+
|
|
1502
|
+
if (pathStep.args) {
|
|
1542
1503
|
// sort named arguments
|
|
1543
1504
|
const sortedNamedArgs = Object.create(null);
|
|
1544
|
-
Object.keys(pathStep.args).sort().forEach(p => {
|
|
1505
|
+
Object.keys(pathStep.args).sort().forEach((p) => {
|
|
1545
1506
|
sortedNamedArgs[p] = compactExpr(pathStep.args[p]);
|
|
1546
|
-
})
|
|
1507
|
+
});
|
|
1547
1508
|
qatName += JSON.stringify(sortedNamedArgs);
|
|
1548
1509
|
}
|
|
1549
1510
|
|
|
1550
1511
|
|
|
1551
1512
|
qat = qatChildren[qatName];
|
|
1552
|
-
if (!qat)
|
|
1553
|
-
{
|
|
1513
|
+
if (!qat) {
|
|
1554
1514
|
qat = linkToOrigin(pathStep._artifact, pathStep.id, qatParent, undefined, pathStep.location);
|
|
1555
1515
|
/*
|
|
1556
1516
|
Query filter have precedence over default filters.
|
|
@@ -1558,10 +1518,10 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1558
1518
|
TODO: If Filter become JOIN relevant, default filters MUST BE cloned before starting the transformation
|
|
1559
1519
|
or the paths won't be added to the QAT and the rewriting would be done on the filter definition.
|
|
1560
1520
|
*/
|
|
1561
|
-
if(pathStep.where
|
|
1562
|
-
qat._filter = pathStep.where
|
|
1563
|
-
if(pathStep.args)
|
|
1564
|
-
qat._namedArgs= pathStep.args;
|
|
1521
|
+
if (pathStep.where /* || pathStep._artifact.where */)
|
|
1522
|
+
qat._filter = pathStep.where; /* || clone(pathStep._artifact.where) */
|
|
1523
|
+
if (pathStep.args)
|
|
1524
|
+
qat._namedArgs = pathStep.args;
|
|
1565
1525
|
|
|
1566
1526
|
/*
|
|
1567
1527
|
If qat._origin has a QA, it must be a mixin association
|
|
@@ -1569,9 +1529,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1569
1529
|
template to have space for table aliases (if mixin assoc is
|
|
1570
1530
|
followed with different filter conditions).
|
|
1571
1531
|
*/
|
|
1572
|
-
if(qat._origin.$QA) {
|
|
1532
|
+
if (qat._origin.$QA) {
|
|
1573
1533
|
qat.$QA = clone(qat._origin.$QA);
|
|
1574
|
-
if(qat._namedArgs)
|
|
1534
|
+
if (qat._namedArgs)
|
|
1575
1535
|
qat.$QA.path[0].args = qat._namedArgs;
|
|
1576
1536
|
}
|
|
1577
1537
|
qat.kind = '$navElement';
|
|
@@ -1597,32 +1557,27 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1597
1557
|
so the flattening can either be done right here or very late when rewriting generic paths.
|
|
1598
1558
|
*/
|
|
1599
1559
|
const art = qat._origin;
|
|
1600
|
-
if(noJoinForFK && qat._origin.target) {
|
|
1601
|
-
if(!pathStep.args && // has no args
|
|
1560
|
+
if (noJoinForFK && qat._origin.target) {
|
|
1561
|
+
if (!pathStep.args && // has no args
|
|
1602
1562
|
(
|
|
1603
1563
|
(
|
|
1604
1564
|
!pathStep.where && // has no filter
|
|
1605
1565
|
// not leaf + next step is foreign key
|
|
1606
|
-
i < tail.length-1 &&
|
|
1607
|
-
// path terminates on a scalar type
|
|
1608
|
-
// _effectiveType.elements can be removed if forRelationalDB can expand fk paths correctly
|
|
1609
|
-
!(tail[tail.length-1]._artifact._effectiveType.elements || tail[tail.length-1]._artifact._effectiveType.target) &&
|
|
1566
|
+
i < tail.length - 1 &&
|
|
1610
1567
|
// association is managed
|
|
1611
1568
|
art.foreignKeys &&
|
|
1612
1569
|
// n+1st path step is foreign key
|
|
1613
|
-
Object.values(art.foreignKeys).some(fk => fk.targetElement.path[0].id === tail[i+1].id)
|
|
1614
|
-
)
|
|
1615
|
-
||
|
|
1570
|
+
Object.values(art.foreignKeys).some(fk => fk.targetElement.path[0].id === tail[i + 1].id)
|
|
1571
|
+
) ||
|
|
1616
1572
|
(
|
|
1617
1573
|
// Non-FROM block path terminates on association, e.g. publishing associations
|
|
1618
1574
|
// with or without filter.
|
|
1619
|
-
i === tail.length-1 &&
|
|
1575
|
+
i === tail.length - 1 &&
|
|
1620
1576
|
env.location !== 'from'
|
|
1621
1577
|
)
|
|
1622
|
-
))
|
|
1623
|
-
{
|
|
1578
|
+
)) {
|
|
1624
1579
|
// If qat has no children yet mark it njr
|
|
1625
|
-
if(!qat[qatChildrenName]) {
|
|
1580
|
+
if (!qat[qatChildrenName]) {
|
|
1626
1581
|
setProp(qat, '$njr', true);
|
|
1627
1582
|
// flatten left hand side ON condition paths ( => foreign keys to the source side)
|
|
1628
1583
|
setProp(art, '$flatSrcFKs', flattenElement(art, true, art.name.id, art.name.id));
|
|
@@ -1637,12 +1592,12 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1637
1592
|
qatChildren = createQATChildren(qat);
|
|
1638
1593
|
qatParent = qat; // Current qat becomes parent to the next level of children
|
|
1639
1594
|
// don't destroy $self navigation for later $self substitution
|
|
1640
|
-
if(!pathStep._navigation || pathStep._navigation.kind !== '$self')
|
|
1595
|
+
if (!pathStep._navigation || pathStep._navigation.kind !== '$self')
|
|
1641
1596
|
setProp( pathStep, '_navigation', qat );
|
|
1642
1597
|
});
|
|
1643
1598
|
|
|
1644
|
-
if(!qat)
|
|
1645
|
-
throw new CompilerAssertion(
|
|
1599
|
+
if (!qat)
|
|
1600
|
+
throw new CompilerAssertion(`No leaf qat for head: ${ head } tail: ${ pathAsStr(tail, '"') } produced`);
|
|
1646
1601
|
|
|
1647
1602
|
/*
|
|
1648
1603
|
If path terminates on an entity or an association (from clause,
|
|
@@ -1653,20 +1608,19 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1653
1608
|
only place where the original FROM alias is available.
|
|
1654
1609
|
*/
|
|
1655
1610
|
let art = qat._origin;
|
|
1656
|
-
if(art._main)
|
|
1657
|
-
if(art.target)
|
|
1658
|
-
art = art.target._artifact;
|
|
1659
|
-
|
|
1660
|
-
art = undefined;
|
|
1611
|
+
if (art._main) { // element (assoc) or sub query
|
|
1612
|
+
if (art.target)
|
|
1613
|
+
art = art.target ? art.target._artifact : undefined;
|
|
1614
|
+
}
|
|
1661
1615
|
|
|
1662
|
-
if(art)
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
if(!rootQat.$QA)
|
|
1666
|
-
{
|
|
1616
|
+
if (art) {
|
|
1617
|
+
// If rootQat ($tableAlias) already has a QA, reuse it, create a new one otherwise.
|
|
1618
|
+
if (!rootQat.$QA) {
|
|
1667
1619
|
// Use the original FROM alias if available!
|
|
1668
|
-
if(pathDict.name && pathDict.name.id)
|
|
1669
|
-
|
|
1620
|
+
if (pathDict.name && pathDict.name.id) {
|
|
1621
|
+
qat.$QA = createQA(env, art, pathDict.name.id, qat._namedArgs);
|
|
1622
|
+
rootQat.$QA = qat.$QA;
|
|
1623
|
+
}
|
|
1670
1624
|
}
|
|
1671
1625
|
}
|
|
1672
1626
|
|
|
@@ -1674,26 +1628,24 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1674
1628
|
// starting with the first $njr=false Qat
|
|
1675
1629
|
let njr = true;
|
|
1676
1630
|
do {
|
|
1677
|
-
if(qat.$njr !== undefined && qat.$njr === false)
|
|
1631
|
+
if (qat.$njr !== undefined && qat.$njr === false)
|
|
1678
1632
|
njr = false;
|
|
1679
|
-
if(qat.$njr !== undefined && !njr)
|
|
1633
|
+
if (qat.$njr !== undefined && !njr)
|
|
1680
1634
|
qat.$njr = njr;
|
|
1681
1635
|
qat = qat._parent;
|
|
1682
|
-
} while(qat._parent && qat._parent[qatChildrenName]);
|
|
1636
|
+
} while (qat._parent && qat._parent[qatChildrenName]);
|
|
1683
1637
|
|
|
1684
1638
|
|
|
1685
1639
|
// Return or create a new children dictionary for a given QAT
|
|
1686
1640
|
// Children are grouped under the filter condition that precedes them.
|
|
1687
|
-
function createQATChildren(parentQat)
|
|
1688
|
-
|
|
1689
|
-
if(!parentQat[qatChildrenName])
|
|
1641
|
+
function createQATChildren(parentQat) {
|
|
1642
|
+
if (!parentQat[qatChildrenName])
|
|
1690
1643
|
parentQat[qatChildrenName] = Object.create(null);
|
|
1691
1644
|
return parentQat[qatChildrenName];
|
|
1692
1645
|
}
|
|
1693
1646
|
}
|
|
1694
1647
|
|
|
1695
|
-
function pathAsStr(p, delim='')
|
|
1696
|
-
{
|
|
1648
|
+
function pathAsStr(p, delim = '') {
|
|
1697
1649
|
return p.map(p => delim + p.id + delim).join('.');
|
|
1698
1650
|
}
|
|
1699
1651
|
|
|
@@ -1704,28 +1656,28 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1704
1656
|
if (Array.isArray(obj))
|
|
1705
1657
|
newObj = [];
|
|
1706
1658
|
else if (obj.constructor) // important for classes, else prototype chain for inheritance will not be correct
|
|
1707
|
-
newObj = new obj.constructor()
|
|
1659
|
+
newObj = new obj.constructor();
|
|
1708
1660
|
else if (!Object.getPrototypeOf(obj))
|
|
1709
1661
|
newObj = Object.create(null); // dictionary
|
|
1710
1662
|
else
|
|
1711
1663
|
newObj = {};
|
|
1712
1664
|
|
|
1713
|
-
const props =
|
|
1665
|
+
const props = Object.getOwnPropertyNames(obj); // clone own properties only, not inherited ones
|
|
1714
1666
|
for (const p of props) {
|
|
1715
1667
|
const pd = Object.getOwnPropertyDescriptor(obj, p);
|
|
1716
|
-
if (pd && pd.enumerable === false)
|
|
1717
|
-
{
|
|
1668
|
+
if (pd && pd.enumerable === false) {
|
|
1718
1669
|
pd.value = obj[p]; // don't copy references
|
|
1719
1670
|
Object.defineProperty(newObj, p, pd);
|
|
1720
1671
|
}
|
|
1721
|
-
else
|
|
1672
|
+
else {
|
|
1722
1673
|
newObj[p] = clone(obj[p]);
|
|
1674
|
+
}
|
|
1723
1675
|
}
|
|
1724
1676
|
return newObj;
|
|
1725
1677
|
}
|
|
1726
1678
|
|
|
1727
1679
|
function parenthesise(expr) {
|
|
1728
|
-
if(typeof expr === 'object' && expr.op && expr.args)
|
|
1680
|
+
if (typeof expr === 'object' && expr.op && expr.args)
|
|
1729
1681
|
setProp(expr, '$parens', [ expr.$location ]);
|
|
1730
1682
|
return expr;
|
|
1731
1683
|
}
|
|
@@ -1740,53 +1692,50 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1740
1692
|
* @param {boolean} [rewritten=true] If true, mark the objects with $rewritten
|
|
1741
1693
|
* @returns {object} CSN path
|
|
1742
1694
|
*/
|
|
1743
|
-
function constructPathNode(pathSteps, alias, rewritten=true)
|
|
1744
|
-
{
|
|
1695
|
+
function constructPathNode(pathSteps, alias, rewritten = true) {
|
|
1745
1696
|
const node = {
|
|
1746
1697
|
$rewritten: rewritten,
|
|
1747
|
-
path
|
|
1698
|
+
path: pathSteps.map((p) => {
|
|
1748
1699
|
const o = {};
|
|
1749
|
-
Object.keys(p).forEach(k => {
|
|
1750
|
-
if(!(rewritten && ['_'].includes(k[0])))
|
|
1700
|
+
Object.keys(p).forEach((k) => {
|
|
1701
|
+
if (!(rewritten && [ '_' ].includes(k[0])))
|
|
1751
1702
|
o[k] = p[k];
|
|
1752
1703
|
});
|
|
1753
1704
|
setProp(o, '_artifact', p._artifact );
|
|
1754
1705
|
setProp(o, '_navigation', p._navigation );
|
|
1755
|
-
return o;
|
|
1706
|
+
return o;
|
|
1707
|
+
}),
|
|
1756
1708
|
};
|
|
1757
1709
|
|
|
1758
|
-
if(alias)
|
|
1759
|
-
node.name = { id: alias }
|
|
1710
|
+
if (alias)
|
|
1711
|
+
node.name = { id: alias };
|
|
1760
1712
|
|
|
1761
1713
|
// set the leaf artifact
|
|
1762
|
-
setProp(node, '_artifact', pathSteps[pathSteps.length-1]._artifact);
|
|
1763
|
-
if(pathSteps[0]._navigation)
|
|
1714
|
+
setProp(node, '_artifact', pathSteps[pathSteps.length - 1]._artifact);
|
|
1715
|
+
if (pathSteps[0]._navigation)
|
|
1764
1716
|
setProp(node.path[0], '_navigation', pathSteps[0]._navigation);
|
|
1765
1717
|
return node;
|
|
1766
1718
|
}
|
|
1767
1719
|
|
|
1768
1720
|
// Crawl all relevant sections of the query AST for paths
|
|
1769
|
-
function walkQuery(query, env)
|
|
1770
|
-
|
|
1771
|
-
if(!query)
|
|
1721
|
+
function walkQuery(query, env) {
|
|
1722
|
+
if (!query)
|
|
1772
1723
|
return;
|
|
1773
1724
|
|
|
1774
|
-
if(!env.walkover)
|
|
1725
|
+
if (!env.walkover)
|
|
1775
1726
|
env.walkover = {};
|
|
1776
1727
|
env.location = query.op;
|
|
1777
1728
|
env.position = query;
|
|
1778
1729
|
|
|
1779
|
-
if(query.op.val === 'SELECT')
|
|
1780
|
-
{
|
|
1730
|
+
if (query.op.val === 'SELECT') {
|
|
1781
1731
|
env.lead = query;
|
|
1782
1732
|
|
|
1783
1733
|
env.location = 'from';
|
|
1784
1734
|
walkFrom(query.from);
|
|
1785
1735
|
|
|
1786
1736
|
env.location = 'select';
|
|
1787
|
-
if(env.walkover[env.location])
|
|
1788
|
-
|
|
1789
|
-
for(const alias in query.elements)
|
|
1737
|
+
if (env.walkover[env.location]) {
|
|
1738
|
+
for (const alias in query.elements)
|
|
1790
1739
|
walk(query.elements[alias].value, env);
|
|
1791
1740
|
|
|
1792
1741
|
env.location = 'Where';
|
|
@@ -1800,7 +1749,7 @@ function walkQuery(query, env)
|
|
|
1800
1749
|
env.location = 'UnionOuterOrderBy';
|
|
1801
1750
|
// outer orderBy's of anonymous union
|
|
1802
1751
|
walk(query.$orderBy, env);
|
|
1803
|
-
if(query.limit) {
|
|
1752
|
+
if (query.limit) {
|
|
1804
1753
|
env.location = 'Limit';
|
|
1805
1754
|
walk(query.limit.rows, env);
|
|
1806
1755
|
env.location = 'Offset';
|
|
@@ -1809,27 +1758,22 @@ function walkQuery(query, env)
|
|
|
1809
1758
|
}
|
|
1810
1759
|
}
|
|
1811
1760
|
|
|
1812
|
-
function walkFrom(fromBlock)
|
|
1813
|
-
{
|
|
1761
|
+
function walkFrom(fromBlock) {
|
|
1814
1762
|
const aliases = [];
|
|
1815
1763
|
env.position = fromBlock;
|
|
1816
|
-
if(fromBlock)
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
{
|
|
1820
|
-
if(fromBlock.name)
|
|
1764
|
+
if (fromBlock) {
|
|
1765
|
+
if (env.walkover[env.location] && walkPath(fromBlock, env)) {
|
|
1766
|
+
if (fromBlock.name)
|
|
1821
1767
|
aliases.push(fromBlock.name.id);
|
|
1822
1768
|
}
|
|
1823
|
-
else
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
fromBlock.args.reduce((a, arg) => a.splice(0,...walkFrom(arg)), aliases);
|
|
1769
|
+
else {
|
|
1770
|
+
if (fromBlock.args)
|
|
1771
|
+
fromBlock.args.reduce((a, arg) => a.splice(0, ...walkFrom(arg)), aliases);
|
|
1827
1772
|
|
|
1828
1773
|
env.location = 'onCondFrom';
|
|
1829
|
-
if(env.walkover[env.location])
|
|
1830
|
-
{
|
|
1774
|
+
if (env.walkover[env.location]) {
|
|
1831
1775
|
env.tableAliases = aliases;
|
|
1832
|
-
walk(fromBlock.on, env)
|
|
1776
|
+
walk(fromBlock.on, env);
|
|
1833
1777
|
delete env.tableAliases;
|
|
1834
1778
|
}
|
|
1835
1779
|
env.location = 'from';
|
|
@@ -1844,50 +1788,45 @@ function walkQuery(query, env)
|
|
|
1844
1788
|
...: any additional payload for the callback
|
|
1845
1789
|
}
|
|
1846
1790
|
*/
|
|
1847
|
-
function walk(node, env)
|
|
1848
|
-
{
|
|
1791
|
+
function walk(node, env) {
|
|
1849
1792
|
env.position = node;
|
|
1850
1793
|
// In some expressions queries can occur, do not follow them as they
|
|
1851
1794
|
// are walked as member of the queries array
|
|
1852
|
-
if(!env || !node || (node && node.query && !node.query._artifact))
|
|
1795
|
+
if (!env || !node || (node && node.query && !node.query._artifact))
|
|
1853
1796
|
return;
|
|
1854
1797
|
|
|
1855
|
-
if(node.path) {
|
|
1798
|
+
if (node.path) {
|
|
1856
1799
|
walkPath(node, env);
|
|
1857
1800
|
return;
|
|
1858
1801
|
}
|
|
1859
1802
|
|
|
1860
1803
|
// Ask for Array before typeof object (which would also be true for Array)
|
|
1861
|
-
if(Array.isArray(node)) {
|
|
1804
|
+
if (Array.isArray(node)) {
|
|
1862
1805
|
node.forEach(n => walk(n, env));
|
|
1863
1806
|
}
|
|
1864
1807
|
// instanceof Object doesn't respect dictionaries...
|
|
1865
|
-
else if(typeof node === 'object') {
|
|
1866
|
-
Object.entries(node).forEach(([n, v]) => {
|
|
1867
|
-
if(n !== 'type')
|
|
1808
|
+
else if (typeof node === 'object') {
|
|
1809
|
+
Object.entries(node).forEach(([ n, v ]) => {
|
|
1810
|
+
if (n !== 'type')
|
|
1868
1811
|
walk(v, env);
|
|
1869
|
-
}
|
|
1870
1812
|
});
|
|
1871
1813
|
}
|
|
1872
1814
|
}
|
|
1873
1815
|
|
|
1874
|
-
function walkPath(node, env)
|
|
1875
|
-
{
|
|
1876
|
-
const path = node.path;
|
|
1816
|
+
function walkPath(node, env) {
|
|
1817
|
+
const { path } = node;
|
|
1877
1818
|
// Ignore paths that have no artifact (function calls etc.) or that are builtins ($now, $user)
|
|
1878
1819
|
// or that are parameters ($parameters or escaped paths (':')
|
|
1879
|
-
//path.length && path[ path.length-1 ]._artifact
|
|
1880
|
-
const art = path?.length && path[path.length-1]._artifact;
|
|
1820
|
+
// path.length && path[ path.length-1 ]._artifact
|
|
1821
|
+
const art = path?.length && path[path.length - 1]._artifact;
|
|
1881
1822
|
|
|
1882
1823
|
// regardless of the position in the query, ignore paths that have virtual path steps
|
|
1883
|
-
if(art && path.some(ps => ps._artifact?.virtual?.val))
|
|
1824
|
+
if (art && path.some(ps => ps._artifact?.virtual?.val))
|
|
1884
1825
|
return path;
|
|
1885
|
-
if(art && !internalArtifactKinds.includes(art.kind) && node.scope !== 'param')
|
|
1886
|
-
|
|
1887
|
-
if(env.callback)
|
|
1888
|
-
{
|
|
1826
|
+
if (art && !internalArtifactKinds.includes(art.kind) && node.scope !== 'param') {
|
|
1827
|
+
if (env.callback) {
|
|
1889
1828
|
// an array of callbacks applied to the node
|
|
1890
|
-
if(Array.isArray(env.callback))
|
|
1829
|
+
if (Array.isArray(env.callback))
|
|
1891
1830
|
env.callback.forEach(cb => cb(node, env));
|
|
1892
1831
|
else
|
|
1893
1832
|
env.callback(node, env);
|