@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.
Files changed (90) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/bin/cdsc.js +17 -6
  3. package/bin/cdsse.js +1 -1
  4. package/bin/cdsv2m.js +1 -1
  5. package/lib/api/main.js +29 -7
  6. package/lib/api/options.js +1 -1
  7. package/lib/base/builtins.js +9 -0
  8. package/lib/base/keywords.js +1 -1
  9. package/lib/base/message-registry.js +41 -10
  10. package/lib/base/messages.js +13 -6
  11. package/lib/base/model.js +1 -1
  12. package/lib/base/optionProcessorHelper.js +7 -2
  13. package/lib/checks/assocOutsideService.js +17 -30
  14. package/lib/checks/checkForTypes.js +0 -18
  15. package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
  16. package/lib/checks/featureFlags.js +4 -1
  17. package/lib/checks/onConditions.js +2 -2
  18. package/lib/checks/queryNoDbArtifacts.js +16 -15
  19. package/lib/checks/types.js +1 -1
  20. package/lib/checks/utils.js +30 -6
  21. package/lib/checks/validator.js +4 -5
  22. package/lib/compiler/assert-consistency.js +3 -1
  23. package/lib/compiler/base.js +1 -1
  24. package/lib/compiler/builtins.js +1 -1
  25. package/lib/compiler/checks.js +85 -39
  26. package/lib/compiler/define.js +24 -5
  27. package/lib/compiler/extend.js +1 -1
  28. package/lib/compiler/finalize-parse-cdl.js +9 -1
  29. package/lib/compiler/generate.js +4 -4
  30. package/lib/compiler/index.js +88 -6
  31. package/lib/compiler/lsp-api.js +2 -0
  32. package/lib/compiler/populate.js +8 -8
  33. package/lib/compiler/propagator.js +1 -1
  34. package/lib/compiler/resolve.js +22 -21
  35. package/lib/compiler/shared.js +6 -6
  36. package/lib/compiler/tweak-assocs.js +53 -31
  37. package/lib/compiler/utils.js +9 -16
  38. package/lib/compiler/xpr-rewrite.js +2 -2
  39. package/lib/gen/BaseParser.js +35 -29
  40. package/lib/gen/CdlGrammar.checksum +1 -1
  41. package/lib/gen/CdlParser.js +1424 -1430
  42. package/lib/gen/Dictionary.json +1 -2
  43. package/lib/gen/cdlKeywords.json +26 -0
  44. package/lib/inspect/inspectPropagation.js +1 -1
  45. package/lib/json/from-csn.js +2 -2
  46. package/lib/json/to-csn.js +1 -1
  47. package/lib/language/multiLineStringParser.js +1 -1
  48. package/lib/model/cloneCsn.js +1 -0
  49. package/lib/model/csnRefs.js +9 -4
  50. package/lib/model/csnUtils.js +67 -2
  51. package/lib/optionProcessor.js +9 -9
  52. package/lib/parsers/AstBuildingParser.js +28 -26
  53. package/lib/parsers/identifiers.js +2 -30
  54. package/lib/render/toCdl.js +73 -13
  55. package/lib/render/toSql.js +127 -108
  56. package/lib/render/utils/common.js +4 -2
  57. package/lib/render/utils/sql.js +67 -0
  58. package/lib/transform/addTenantFields.js +4 -4
  59. package/lib/transform/db/assertUnique.js +2 -1
  60. package/lib/transform/db/associations.js +37 -1
  61. package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
  62. package/lib/transform/db/assocsToQueries/utils.js +1 -1
  63. package/lib/transform/db/cdsPersistence.js +1 -1
  64. package/lib/transform/db/expansion.js +37 -36
  65. package/lib/transform/db/killAnnotations.js +1 -0
  66. package/lib/transform/db/processSqlServices.js +20 -2
  67. package/lib/transform/draft/db.js +20 -20
  68. package/lib/transform/draft/odata.js +38 -40
  69. package/lib/transform/effective/associations.js +1 -1
  70. package/lib/transform/effective/flattening.js +40 -47
  71. package/lib/transform/effective/main.js +6 -4
  72. package/lib/transform/forOdata.js +201 -92
  73. package/lib/transform/forRelationalDB.js +151 -142
  74. package/lib/transform/localized.js +116 -109
  75. package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
  76. package/lib/transform/odata/createForeignKeys.js +73 -70
  77. package/lib/transform/odata/flattening.js +216 -200
  78. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
  79. package/lib/transform/odata/toFinalBaseType.js +40 -39
  80. package/lib/transform/odata/typesExposure.js +151 -133
  81. package/lib/transform/odata/utils.js +7 -6
  82. package/lib/transform/parseExpr.js +165 -162
  83. package/lib/transform/transformUtils.js +184 -551
  84. package/lib/transform/translateAssocsToJoins.js +511 -596
  85. package/lib/transform/tupleExpansion.js +495 -0
  86. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  87. package/lib/utils/moduleResolve.js +1 -1
  88. package/package.json +2 -2
  89. package/lib/base/cleanSymbols.js +0 -17
  90. package/lib/checks/nonexpandableStructured.js +0 -39
@@ -1,32 +1,32 @@
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 };
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
- // FIXME: Move this somewhere more appropriate
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
- if(!ppt.children[ps.id])
98
- ppt = ppt.children[ps.id] = { children: Object.create(null) };
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: { from: true, onCondFrom:true, select:true, filter: true },
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 = { from:true, onCondFrom:false, select: false, filter: false };
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 = { from:false, onCondFrom:true, select: true, filter: false };
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
- constructPathNode([ tableAlias, { id: pathStr, _artifact: pathNode._artifact } ]));
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
- let [head, ...tail] = pathNode.path;
319
- if((env.location === 'OrderBy' && !head._navigation)||
320
- 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)))
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] = rightMostQA(tail, head._navigation._parent.$QA || head._navigation.$QA);
332
- if(!QA) {
314
+ const [ QA, ps ] = rightMostJoinRelevantQA(tail, rootQA);
315
+ if (!QA) {
333
316
  error(null, pathNode.$location,
334
- { name: pathName(pathNode.path) },
335
- '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)');
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 && !ps._navigation.$njr)
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 && tail[i]._navigation.$njr) {
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 leafArt = tail[tail.length-1]._artifact;
356
- const tailPath = tail.map(p=>p.id).join(pathDelimiter);
357
- const fk = tail[i]._artifact.$flatSrcFKs.find(f => f._artifact === leafArt && f.acc.startsWith(tailPath));
358
- if(!fk) {
359
- // const revealInternalProperties = require('../model/revealInternalProperties.js');
360
- // console.log('++++++++ Path tail: ', revealInternalProperties(tail[tail.length-1]._artifact));
361
- // console.log('******** Flat FKs\n', tail[i]._artifact.$flatSrcFKs.map(f => revealInternalProperties(f._artifact)));
362
- throw new CompilerAssertion('Debug me: No flat FK found for FK rewriting');
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
- { id: tail.map(p => p.id).join(pathDelimiter),
370
- _artifact: tail[tail.length-1]._artifact
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
- 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);
375
360
  }
376
361
 
377
- function rightMostQA(path, rootQA) {
378
- // iterate over the path and search for first QA that
379
- // either matches a $tableAlias or an association,
380
- // if no QA could be found, return rootQA
381
- // 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) {
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 = undefined;
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 ? QA : rootQA), ps];
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
- if(query.$startFilters)
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), ...query.$startFilters.map(parenthesise) ] };
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, function(pathNode) {
458
- return [ /* tableAlias=> */ constructTableAliasPathStep(childQat.$QA),
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, function(pathNode) {
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 : { literal: 'number', val: 1 }
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 != assoc.$flatTgtFKs.length)
596
- 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 }]`);
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
- args.push({op: {val: '=' },
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
- constructPathNode( [ tgtAlias, assoc.$flatTgtFKs[i] ] ) ] });
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
- } else if (!hasPersistenceSkipAnnotation(assoc._main)) {
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
- } 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 {
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
- else
702
- return cloneOnCondExprTree(expr);
680
+ return cloneOnCondExprTree(expr);
703
681
  }
704
682
 
705
683
  function cloneOnCondExprStream(expr) {
706
- const args = expr.args;
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
- const fwdAssoc = getForwardAssociation(args[i].path, args[i+2].path);
721
- if(fwdAssoc)
722
- {
723
- //env.assocStack.includes(fwdAssoc) => recursion
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 = args[i+2].$check = false;
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, '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)');
757
735
  // don't check these paths again
758
- expr.args.forEach(x => x.$check = false );
736
+ expr.args.forEach((x) => {
737
+ x.$check = false;
738
+ } );
759
739
  return expr;
760
740
  }
761
- else {
762
- return createOnCondition(fwdAssoc, ...swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias));
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
- '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');
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.path;
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
- if(rhs.mixin)
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].value;
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
- else {
863
- // check for associations, not allowed at this time, trouble in resolving
864
- // and addressing the correct foreign key (tuple)
865
- [ head, ...tail ] = path;
866
- path = value.path.concat(tail);
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 = undefined; // don't modify original path
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 ], { elemref: path[0].id, id: assoc.name.id, art: assoc._main, names },
971
- '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)');
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
- } 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 {
987
967
  error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: (assoc._main || assoc) },
988
- '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');
989
969
  return pathNode.path;
990
970
  }
991
- } else {
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
- [ head, ...tail ] = path = env.assocStack.stripPrefix(path, -1);
1003
- if(prefixes.includes(head.id)) // target side
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 == env.assocStack.id()) : rc, false);
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 : { name: { select: env.queryIndex + 1 } } // ???
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 ], alias );
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 += '_' + env.aliasCount++;
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
- 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));
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
- if(!newNode.path) {
1155
- Object.keys(oldNode).forEach(k => {
1156
- delete oldNode[k] });
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
- tableAlias._parent.from = replacementNode;
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, fk.name.id, fk.targetElement.path.map(ps => ps.id).join(pathDelimiter)));
1199
- else
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] = substituteFKAliasForPath(assocStep, 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
- return {
1223
- id: (prefix ? prefix + pathDelimiter : '' ) + p.id,
1224
- acc: (acc ? acc + pathDelimiter : '') + p.acc,
1225
- _artifact: p._artifact
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
- const [head, ...tail] = path;
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] = substituteFKAliasForPath(assocStep, 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 = undefined; // last found FK
1310
- let fkPs = undefined; // last path step that found FK
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
- else
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.path;
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
- { art: lead._main || lead, id: ps.id },
1385
- '$(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');
1386
1343
  pathDict.$check = false;
1387
1344
  }
1388
- if(ps.where) {
1389
- error(null, [pathDict.location, lead],
1390
- { art: lead._main || lead, id: ps.id },
1391
- '$(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');
1392
1349
  pathDict.$check = false;
1393
1350
  }
1394
- if(ps._artifact.virtual) {
1395
- error(null, [pathDict.location, lead],
1396
- { art: lead._main || lead, id: ps.id },
1397
- '$(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');
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
- error(null, [pathDict.location, lead],
1409
- { art: lead._main || lead, id: ps.id, alias: pathAsStr(pathDict.path) },
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
- if(!ps._artifact.$fkPathPrefixTree.children[la1.id]) {
1416
- error(null, [pathDict.location, lead],
1417
- { art: lead._main || lead, id: la1.id, name: ps.id, alias: pathAsStr(pathDict.path) },
1418
- '$(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)' );
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
- { art: lead._main || lead, id: ps.id, alias: pathAsStr(pathDict.path) },
1427
- '$(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');
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
- { art: lead._main || lead, id: lastSegment.id },
1438
- '$(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');
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
- let [head, ...tail] = path;
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 = undefined;
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 == undefined)
1530
- 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) }`);
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 += JSON.stringify(compactExpr(pathStep.where));
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 /*|| pathStep._artifact.where*/)
1572
- qat._filter = pathStep.where /*|| clone(pathStep._artifact.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('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`);
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) // element (assoc) or sub query
1667
- if(art.target)
1668
- art = art.target._artifact;
1669
- else
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
- // If rootQat ($tableAlias) already has a QA, reuse it, create a new one otherwise.
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
- 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
+ }
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 = Object.getOwnPropertyNames(obj); // clone own properties only, not inherited ones
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 : pathSteps.map(p => {
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
- if(env.walkover[env.location] && walkPath(fromBlock, env))
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
- if(fromBlock.args)
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
  }