@sap/cds-compiler 6.2.2 → 6.3.0

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