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