@loomcore/api 0.1.76 → 0.1.78

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.
@@ -2,75 +2,171 @@ import { Join } from "../../operations/join.operation.js";
2
2
  import { JoinMany } from "../../operations/join-many.operation.js";
3
3
  import { JoinThrough } from "../../operations/join-through.operation.js";
4
4
  import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
5
+ function resolveLocalField(localField, mainTableName, operations) {
6
+ if (!localField.includes('.')) {
7
+ return mainTableName
8
+ ? `"${mainTableName}"."${localField}"`
9
+ : `"${localField}"`;
10
+ }
11
+ const [alias, field] = localField.split('.');
12
+ const objectJoin = operations.find(op => op instanceof JoinThrough && op.as === alias);
13
+ if (objectJoin) {
14
+ return `(${alias}.aggregated->>'${field}')::integer`;
15
+ }
16
+ const arrayJoin = operations.find(op => (op instanceof JoinMany || op instanceof JoinThroughMany) && op.as === alias);
17
+ if (arrayJoin) {
18
+ return `(${alias}.aggregated->>'${field}')::integer`;
19
+ }
20
+ return `${alias}."${field}"`;
21
+ }
22
+ function findEnrichmentTarget(operation, operations) {
23
+ if (!operation.localField.includes('.')) {
24
+ return null;
25
+ }
26
+ const [alias, field] = operation.localField.split('.');
27
+ const target = operations.find(op => (op instanceof JoinMany || op instanceof JoinThroughMany) && op.as === alias);
28
+ if (target && operations.indexOf(target) < operations.indexOf(operation)) {
29
+ return { target, field };
30
+ }
31
+ return null;
32
+ }
5
33
  export function buildJoinClauses(operations, mainTableName) {
6
34
  let joinClauses = '';
7
- const joinThroughOperations = operations.filter(op => op instanceof JoinThrough);
8
35
  const processedAliases = new Set();
9
- const aliasesToSkip = new Set();
36
+ const enrichedAliases = new Set();
37
+ const joinOperations = operations.filter(op => op instanceof Join);
38
+ const targetsToEnrich = new Set();
39
+ const enrichmentsByTarget = new Map();
10
40
  for (const operation of operations) {
11
- if (operation instanceof JoinThroughMany && operation.localField.includes('.')) {
12
- const [tableAlias] = operation.localField.split('.');
13
- const referencedJoinThroughMany = operations
14
- .filter(op => op instanceof JoinThroughMany)
15
- .find(jtm => jtm.as === tableAlias);
16
- if (referencedJoinThroughMany) {
17
- const referencedIndex = operations.indexOf(referencedJoinThroughMany);
18
- const currentIndex = operations.indexOf(operation);
19
- if (referencedIndex < currentIndex) {
20
- aliasesToSkip.add(tableAlias);
41
+ if (operation instanceof JoinMany || operation instanceof JoinThroughMany) {
42
+ const enrichment = findEnrichmentTarget(operation, operations);
43
+ if (enrichment) {
44
+ const { target } = enrichment;
45
+ targetsToEnrich.add(target.as);
46
+ if (!enrichmentsByTarget.has(target.as)) {
47
+ enrichmentsByTarget.set(target.as, []);
21
48
  }
49
+ enrichmentsByTarget.get(target.as).push({ operation, field: enrichment.field });
22
50
  }
23
51
  }
24
52
  }
53
+ for (const [targetAlias, enrichments] of enrichmentsByTarget.entries()) {
54
+ enrichments.sort((a, b) => operations.indexOf(a.operation) - operations.indexOf(b.operation));
55
+ }
25
56
  for (const operation of operations) {
57
+ if (processedAliases.has(operation.as)) {
58
+ continue;
59
+ }
60
+ const isEnrichmentOp = (operation instanceof JoinMany || operation instanceof JoinThroughMany) &&
61
+ findEnrichmentTarget(operation, operations) !== null;
62
+ if (targetsToEnrich.has(operation.as) && !isEnrichmentOp) {
63
+ continue;
64
+ }
26
65
  if (operation instanceof Join) {
27
- let localFieldRef;
28
- if (operation.localField.includes('.')) {
29
- const [tableAlias, columnName] = operation.localField.split('.');
30
- const referencedJoinThrough = joinThroughOperations.find(jt => jt.as === tableAlias);
31
- if (referencedJoinThrough) {
32
- localFieldRef = `(${tableAlias}.aggregated->>'${columnName}')::integer`;
33
- }
34
- else {
35
- localFieldRef = `${tableAlias}."${columnName}"`;
36
- }
37
- }
38
- else {
39
- localFieldRef = mainTableName
40
- ? `"${mainTableName}"."${operation.localField}"`
41
- : `"${operation.localField}"`;
42
- }
66
+ const localFieldRef = resolveLocalField(operation.localField, mainTableName, operations);
43
67
  joinClauses += ` LEFT JOIN "${operation.from}" AS ${operation.as} ON ${localFieldRef} = "${operation.as}"."${operation.foreignField}"`;
68
+ processedAliases.add(operation.as);
44
69
  }
45
70
  else if (operation instanceof JoinMany) {
46
- let localFieldRef;
47
- if (operation.localField.includes('.')) {
48
- const [tableAlias, columnName] = operation.localField.split('.');
49
- localFieldRef = `${tableAlias}."${columnName}"`;
71
+ const enrichment = findEnrichmentTarget(operation, operations);
72
+ if (enrichment) {
73
+ const enrichments = enrichmentsByTarget.get(enrichment.target.as);
74
+ const isFirstEnrichment = enrichments[0].operation === operation;
75
+ if (isFirstEnrichment && !enrichedAliases.has(enrichment.target.as)) {
76
+ const { target, field } = enrichment;
77
+ const targetLocalFieldRef = target.localField.includes('.')
78
+ ? resolveLocalField(target.localField, mainTableName, operations)
79
+ : (mainTableName ? `"${mainTableName}"."${target.localField}"` : `"${target.localField}"`);
80
+ let enrichmentJoins = '';
81
+ let jsonBuildObjects = '';
82
+ for (const enrich of enrichments) {
83
+ const enrichField = enrich.field;
84
+ const enrichOp = enrich.operation;
85
+ const enrichAlias = `enrich_${enrichOp.as}`;
86
+ const nestedJoin = joinOperations.find((j) => {
87
+ if (!j.localField.includes('.'))
88
+ return false;
89
+ const [referencedAlias] = j.localField.split('.');
90
+ const referencedJoin = operations.find(op => (op instanceof Join || op instanceof JoinMany || op instanceof JoinThroughMany) &&
91
+ op.as === referencedAlias);
92
+ if (referencedJoin instanceof Join && referencedJoin.from === enrichOp.from) {
93
+ return true;
94
+ }
95
+ if (enrichOp instanceof JoinThroughMany && referencedJoin instanceof Join && referencedJoin.from === enrichOp.from) {
96
+ return true;
97
+ }
98
+ return false;
99
+ });
100
+ if (nestedJoin) {
101
+ const nestedField = nestedJoin.localField.split('.')[1];
102
+ enrichmentJoins += ` LEFT JOIN LATERAL (
103
+ SELECT COALESCE(
104
+ JSON_AGG(
105
+ enrich_elem.value || jsonb_build_object('${nestedJoin.as}', nested_join_data.${nestedJoin.as})
106
+ ),
107
+ '[]'::json
108
+ ) AS enriched
109
+ FROM (
110
+ SELECT COALESCE(JSON_AGG(row_to_json(enrich_table)), '[]'::json) AS enriched
111
+ FROM "${enrichOp.from}" AS enrich_table
112
+ WHERE enrich_table."${enrichOp.foreignField}" = (elem.value->>'${enrichField}')::integer
113
+ AND enrich_table."_deleted" IS NULL
114
+ ) AS enrich_data
115
+ CROSS JOIN LATERAL jsonb_array_elements(enrich_data.enriched::jsonb) AS enrich_elem
116
+ LEFT JOIN LATERAL (
117
+ SELECT row_to_json(nested_table) AS ${nestedJoin.as}
118
+ FROM "${nestedJoin.from}" AS nested_table
119
+ WHERE nested_table."${nestedJoin.foreignField}" = (enrich_elem.value->>'${nestedField}')::integer
120
+ AND nested_table."_deleted" IS NULL
121
+ LIMIT 1
122
+ ) AS nested_join_data ON true
123
+ ) AS ${enrichAlias} ON true`;
124
+ }
125
+ else {
126
+ enrichmentJoins += ` LEFT JOIN LATERAL (
127
+ SELECT COALESCE(JSON_AGG(row_to_json(enrich_table)), '[]'::json) AS enriched
128
+ FROM "${enrichOp.from}" AS enrich_table
129
+ WHERE enrich_table."${enrichOp.foreignField}" = (elem.value->>'${enrichField}')::integer
130
+ AND enrich_table."_deleted" IS NULL
131
+ ) AS ${enrichAlias} ON true`;
132
+ }
133
+ jsonBuildObjects += ` || jsonb_build_object('${enrichOp.as}', COALESCE(${enrichAlias}.enriched, '[]'::json))`;
134
+ }
135
+ joinClauses += ` LEFT JOIN LATERAL (
136
+ SELECT COALESCE(
137
+ JSON_AGG(
138
+ elem.value${jsonBuildObjects}
139
+ ),
140
+ '[]'::json
141
+ ) AS aggregated
142
+ FROM (
143
+ SELECT COALESCE(JSON_AGG(row_to_json(target_table)), '[]'::json) AS aggregated
144
+ FROM "${target.from}" AS target_table
145
+ WHERE target_table."${target.foreignField}" = ${targetLocalFieldRef}
146
+ AND target_table."_deleted" IS NULL
147
+ ) AS original_data
148
+ CROSS JOIN LATERAL jsonb_array_elements(original_data.aggregated::jsonb) AS elem${enrichmentJoins}
149
+ ) AS ${target.as} ON true`;
150
+ enrichedAliases.add(target.as);
151
+ processedAliases.add(target.as);
152
+ for (const enrich of enrichments) {
153
+ processedAliases.add(enrich.operation.as);
154
+ }
155
+ }
50
156
  }
51
157
  else {
52
- localFieldRef = mainTableName
53
- ? `"${mainTableName}"."${operation.localField}"`
54
- : `"${operation.localField}"`;
158
+ const localFieldRef = resolveLocalField(operation.localField, mainTableName, operations);
159
+ joinClauses += ` LEFT JOIN LATERAL (
160
+ SELECT COALESCE(JSON_AGG(row_to_json("${operation.from}")), '[]'::json) AS aggregated
161
+ FROM "${operation.from}"
162
+ WHERE "${operation.from}"."${operation.foreignField}" = ${localFieldRef}
163
+ AND "${operation.from}"."_deleted" IS NULL
164
+ ) AS ${operation.as} ON true`;
165
+ processedAliases.add(operation.as);
55
166
  }
56
- joinClauses += ` LEFT JOIN LATERAL (
57
- SELECT COALESCE(JSON_AGG(row_to_json("${operation.from}")), '[]'::json) AS aggregated
58
- FROM "${operation.from}"
59
- WHERE "${operation.from}"."${operation.foreignField}" = ${localFieldRef}
60
- AND "${operation.from}"."_deleted" IS NULL
61
- ) AS ${operation.as} ON true`;
62
167
  }
63
168
  else if (operation instanceof JoinThrough) {
64
- let localFieldRef;
65
- if (operation.localField.includes('.')) {
66
- const [tableAlias, columnName] = operation.localField.split('.');
67
- localFieldRef = `${tableAlias}."${columnName}"`;
68
- }
69
- else {
70
- localFieldRef = mainTableName
71
- ? `"${mainTableName}"."${operation.localField}"`
72
- : `"${operation.localField}"`;
73
- }
169
+ const localFieldRef = resolveLocalField(operation.localField, mainTableName, operations);
74
170
  joinClauses += ` LEFT JOIN LATERAL (
75
171
  SELECT row_to_json(${operation.as}) AS aggregated
76
172
  FROM "${operation.through}"
@@ -81,171 +177,166 @@ export function buildJoinClauses(operations, mainTableName) {
81
177
  AND ${operation.as}."_deleted" IS NULL
82
178
  LIMIT 1
83
179
  ) AS ${operation.as} ON true`;
180
+ processedAliases.add(operation.as);
84
181
  }
85
182
  else if (operation instanceof JoinThroughMany) {
86
- let localFieldRef;
87
- let shouldSkipOriginalJoin = false;
88
- if (operation.localField.includes('.')) {
89
- const [tableAlias, columnName] = operation.localField.split('.');
90
- const referencedJoinThroughMany = operations
91
- .filter(op => op instanceof JoinThroughMany)
92
- .find(jtm => jtm.as === tableAlias);
93
- if (referencedJoinThroughMany) {
94
- const referencedIndex = operations.indexOf(referencedJoinThroughMany);
95
- const currentIndex = operations.indexOf(operation);
96
- if (referencedIndex < currentIndex) {
97
- shouldSkipOriginalJoin = true;
98
- const originalJoinWasSkipped = aliasesToSkip.has(tableAlias);
99
- const originalJoin = referencedJoinThroughMany;
100
- const mainTableRef = mainTableName ? `"${mainTableName}"."_id"` : '"_id"';
101
- const isAgentsJoin = operation.from === 'agents';
102
- if (isAgentsJoin) {
103
- if (originalJoinWasSkipped) {
104
- joinClauses += ` LEFT JOIN LATERAL (
183
+ const enrichment = findEnrichmentTarget(operation, operations);
184
+ if (enrichment) {
185
+ const enrichments = enrichmentsByTarget.get(enrichment.target.as);
186
+ const isFirstEnrichment = enrichments[0].operation === operation;
187
+ if (isFirstEnrichment && !enrichedAliases.has(enrichment.target.as)) {
188
+ const { target, field } = enrichment;
189
+ const targetLocalFieldRef = target.localField.includes('.')
190
+ ? resolveLocalField(target.localField, mainTableName, operations)
191
+ : (mainTableName ? `"${mainTableName}"."${target.localField}"` : `"${target.localField}"`);
192
+ const isTargetJoinMany = target instanceof JoinMany;
193
+ let enrichmentJoins = '';
194
+ let jsonBuildObjects = '';
195
+ for (const enrich of enrichments) {
196
+ const enrichField = enrich.field;
197
+ const enrichOp = enrich.operation;
198
+ const enrichAlias = `enrich_${enrichOp.as}`;
199
+ if (enrichOp instanceof JoinMany) {
200
+ const nestedJoin = joinOperations.find(j => j.localField.includes('.') &&
201
+ j.localField.startsWith(`${enrichOp.as}.`));
202
+ if (nestedJoin) {
203
+ const nestedField = nestedJoin.localField.split('.')[1];
204
+ enrichmentJoins += ` LEFT JOIN LATERAL (
105
205
  SELECT COALESCE(
106
206
  JSON_AGG(
107
- policy_elem.value || jsonb_build_object(
108
- 'agents',
109
- COALESCE(
110
- (SELECT JSON_AGG(agent_elem.value || jsonb_build_object('agent_person', person_data.value))
111
- FROM jsonb_array_elements(COALESCE(agents_agg.agents, '[]'::json)::jsonb) AS agent_elem
112
- LEFT JOIN LATERAL (
113
- SELECT row_to_json(p) AS value
114
- FROM "persons" AS p
115
- WHERE p."_id" = (agent_elem.value->>'person_id')::integer
116
- AND p."_deleted" IS NULL
117
- LIMIT 1
118
- ) AS person_data ON true),
119
- '[]'::json
120
- )
121
- )
207
+ enrich_elem.value || jsonb_build_object('${nestedJoin.as}', nested_join_data.${nestedJoin.as})
122
208
  ),
123
209
  '[]'::json
124
- ) AS aggregated
210
+ ) AS enriched
125
211
  FROM (
126
- SELECT COALESCE(JSON_AGG(row_to_json(${tableAlias})), '[]'::json) AS aggregated
127
- FROM "${originalJoin.through}"
128
- INNER JOIN "${originalJoin.from}" AS ${tableAlias}
129
- ON ${tableAlias}."${originalJoin.foreignField}" = "${originalJoin.through}"."${originalJoin.throughForeignField}"
130
- WHERE "${originalJoin.through}"."${originalJoin.throughLocalField}" = ${mainTableRef}
131
- AND "${originalJoin.through}"."_deleted" IS NULL
132
- AND ${tableAlias}."_deleted" IS NULL
133
- ) AS policies_subquery
134
- CROSS JOIN LATERAL jsonb_array_elements(policies_subquery.aggregated::jsonb) AS policy_elem
212
+ SELECT COALESCE(JSON_AGG(row_to_json(enrich_table)), '[]'::json) AS enriched
213
+ FROM "${enrichOp.from}" AS enrich_table
214
+ WHERE enrich_table."${enrichOp.foreignField}" = (elem.value->>'${enrichField}')::integer
215
+ AND enrich_table."_deleted" IS NULL
216
+ ) AS enrich_data
217
+ CROSS JOIN LATERAL jsonb_array_elements(enrich_data.enriched::jsonb) AS enrich_elem
135
218
  LEFT JOIN LATERAL (
136
- SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS agents
137
- FROM "${operation.through}"
138
- INNER JOIN "${operation.from}" AS ${operation.as}
139
- ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
140
- WHERE "${operation.through}"."${operation.throughLocalField}" = (policy_elem.value->>'${columnName}')::integer
141
- AND "${operation.through}"."_deleted" IS NULL
142
- AND ${operation.as}."_deleted" IS NULL
143
- ) AS agents_agg ON true
144
- ) AS ${operation.as} ON true`;
219
+ SELECT row_to_json(nested_table) AS ${nestedJoin.as}
220
+ FROM "${nestedJoin.from}" AS nested_table
221
+ WHERE nested_table."${nestedJoin.foreignField}" = (enrich_elem.value->>'${nestedField}')::integer
222
+ AND nested_table."_deleted" IS NULL
223
+ LIMIT 1
224
+ ) AS nested_join_data ON true
225
+ ) AS ${enrichAlias} ON true`;
145
226
  }
146
227
  else {
147
- joinClauses += ` LEFT JOIN LATERAL (
148
- SELECT COALESCE(
149
- JSON_AGG(
150
- policy_elem.value || jsonb_build_object(
151
- 'agents',
152
- COALESCE(
153
- (SELECT JSON_AGG(agent_elem.value || jsonb_build_object('agent_person', person_data.value))
154
- FROM jsonb_array_elements(COALESCE(agents_agg.agents, '[]'::json)::jsonb) AS agent_elem
155
- LEFT JOIN LATERAL (
156
- SELECT row_to_json(p) AS value
157
- FROM "persons" AS p
158
- WHERE p."_id" = (agent_elem.value->>'person_id')::integer
159
- AND p."_deleted" IS NULL
160
- LIMIT 1
161
- ) AS person_data ON true),
162
- '[]'::json
163
- )
164
- )
165
- ),
166
- '[]'::json
167
- ) AS aggregated
168
- FROM jsonb_array_elements(COALESCE(${tableAlias}.aggregated, '[]'::json)::jsonb) AS policy_elem
169
- LEFT JOIN LATERAL (
170
- SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS agents
171
- FROM "${operation.through}"
172
- INNER JOIN "${operation.from}" AS ${operation.as}
173
- ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
174
- WHERE "${operation.through}"."${operation.throughLocalField}" = (policy_elem.value->>'${columnName}')::integer
175
- AND "${operation.through}"."_deleted" IS NULL
176
- AND ${operation.as}."_deleted" IS NULL
177
- ) AS agents_agg ON true
178
- ) AS ${operation.as} ON true`;
228
+ enrichmentJoins += ` LEFT JOIN LATERAL (
229
+ SELECT COALESCE(JSON_AGG(row_to_json(enrich_table)), '[]'::json) AS enriched
230
+ FROM "${enrichOp.from}" AS enrich_table
231
+ WHERE enrich_table."${enrichOp.foreignField}" = (elem.value->>'${enrichField}')::integer
232
+ AND enrich_table."_deleted" IS NULL
233
+ ) AS ${enrichAlias} ON true`;
179
234
  }
180
235
  }
181
236
  else {
182
- if (originalJoinWasSkipped) {
183
- joinClauses += ` LEFT JOIN LATERAL (
237
+ const nestedJoin = joinOperations.find((j) => {
238
+ if (!j.localField.includes('.'))
239
+ return false;
240
+ const [referencedAlias] = j.localField.split('.');
241
+ const referencedJoin = operations.find(op => (op instanceof Join || op instanceof JoinMany || op instanceof JoinThroughMany) &&
242
+ op.as === referencedAlias);
243
+ if (referencedJoin instanceof Join && referencedJoin.from === enrichOp.from) {
244
+ return true;
245
+ }
246
+ if (enrichOp instanceof JoinThroughMany && referencedJoin instanceof Join && referencedJoin.from === enrichOp.from) {
247
+ return true;
248
+ }
249
+ return false;
250
+ });
251
+ if (nestedJoin) {
252
+ const nestedField = nestedJoin.localField.split('.')[1];
253
+ enrichmentJoins += ` LEFT JOIN LATERAL (
184
254
  SELECT COALESCE(
185
255
  JSON_AGG(
186
- policy_elem.value || jsonb_build_object('agents', COALESCE(agents_agg.agents, '[]'::json))
256
+ enrich_elem.value || jsonb_build_object('${nestedJoin.as}', nested_join_data.${nestedJoin.as})
187
257
  ),
188
258
  '[]'::json
189
- ) AS aggregated
259
+ ) AS enriched
190
260
  FROM (
191
- SELECT COALESCE(JSON_AGG(row_to_json(${tableAlias})), '[]'::json) AS aggregated
192
- FROM "${originalJoin.through}"
193
- INNER JOIN "${originalJoin.from}" AS ${tableAlias}
194
- ON ${tableAlias}."${originalJoin.foreignField}" = "${originalJoin.through}"."${originalJoin.throughForeignField}"
195
- WHERE "${originalJoin.through}"."${originalJoin.throughLocalField}" = ${mainTableRef}
196
- AND "${originalJoin.through}"."_deleted" IS NULL
197
- AND ${tableAlias}."_deleted" IS NULL
198
- ) AS policies_subquery
199
- CROSS JOIN LATERAL jsonb_array_elements(policies_subquery.aggregated::jsonb) AS policy_elem
261
+ SELECT COALESCE(JSON_AGG(row_to_json(${enrichAlias}_table)), '[]'::json) AS enriched
262
+ FROM "${enrichOp.through}"
263
+ INNER JOIN "${enrichOp.from}" AS ${enrichAlias}_table
264
+ ON ${enrichAlias}_table."${enrichOp.foreignField}" = "${enrichOp.through}"."${enrichOp.throughForeignField}"
265
+ WHERE "${enrichOp.through}"."${enrichOp.throughLocalField}" = (elem.value->>'${enrichField}')::integer
266
+ AND "${enrichOp.through}"."_deleted" IS NULL
267
+ AND ${enrichAlias}_table."_deleted" IS NULL
268
+ ) AS enrich_data
269
+ CROSS JOIN LATERAL jsonb_array_elements(enrich_data.enriched::jsonb) AS enrich_elem
200
270
  LEFT JOIN LATERAL (
201
- SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS agents
202
- FROM "${operation.through}"
203
- INNER JOIN "${operation.from}" AS ${operation.as}
204
- ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
205
- WHERE "${operation.through}"."${operation.throughLocalField}" = (policy_elem.value->>'${columnName}')::integer
206
- AND "${operation.through}"."_deleted" IS NULL
207
- AND ${operation.as}."_deleted" IS NULL
208
- ) AS agents_agg ON true
209
- ) AS ${operation.as} ON true`;
271
+ SELECT row_to_json(nested_table) AS ${nestedJoin.as}
272
+ FROM "${nestedJoin.from}" AS nested_table
273
+ WHERE nested_table."${nestedJoin.foreignField}" = (enrich_elem.value->>'${nestedField}')::integer
274
+ AND nested_table."_deleted" IS NULL
275
+ LIMIT 1
276
+ ) AS nested_join_data ON true
277
+ ) AS ${enrichAlias} ON true`;
210
278
  }
211
279
  else {
212
- joinClauses += ` LEFT JOIN LATERAL (
213
- SELECT COALESCE(
214
- JSON_AGG(
215
- policy_elem.value || jsonb_build_object('agents', COALESCE(agents_agg.agents, '[]'::json))
216
- ),
217
- '[]'::json
218
- ) AS aggregated
219
- FROM jsonb_array_elements(COALESCE(${tableAlias}.aggregated, '[]'::json)::jsonb) AS policy_elem
220
- LEFT JOIN LATERAL (
221
- SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS agents
222
- FROM "${operation.through}"
223
- INNER JOIN "${operation.from}" AS ${operation.as}
224
- ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
225
- WHERE "${operation.through}"."${operation.throughLocalField}" = (policy_elem.value->>'${columnName}')::integer
226
- AND "${operation.through}"."_deleted" IS NULL
227
- AND ${operation.as}."_deleted" IS NULL
228
- ) AS agents_agg ON true
229
- ) AS ${operation.as} ON true`;
280
+ enrichmentJoins += ` LEFT JOIN LATERAL (
281
+ SELECT COALESCE(JSON_AGG(row_to_json(${enrichAlias}_table)), '[]'::json) AS enriched
282
+ FROM "${enrichOp.through}"
283
+ INNER JOIN "${enrichOp.from}" AS ${enrichAlias}_table
284
+ ON ${enrichAlias}_table."${enrichOp.foreignField}" = "${enrichOp.through}"."${enrichOp.throughForeignField}"
285
+ WHERE "${enrichOp.through}"."${enrichOp.throughLocalField}" = (elem.value->>'${enrichField}')::integer
286
+ AND "${enrichOp.through}"."_deleted" IS NULL
287
+ AND ${enrichAlias}_table."_deleted" IS NULL
288
+ ) AS ${enrichAlias} ON true`;
230
289
  }
231
290
  }
232
- processedAliases.add(operation.as);
233
- continue;
291
+ jsonBuildObjects += ` || jsonb_build_object('${enrichOp.as}', COALESCE(${enrichAlias}.enriched, '[]'::json))`;
292
+ }
293
+ if (isTargetJoinMany) {
294
+ joinClauses += ` LEFT JOIN LATERAL (
295
+ SELECT COALESCE(
296
+ JSON_AGG(
297
+ elem.value${jsonBuildObjects}
298
+ ),
299
+ '[]'::json
300
+ ) AS aggregated
301
+ FROM (
302
+ SELECT COALESCE(JSON_AGG(row_to_json(target_table)), '[]'::json) AS aggregated
303
+ FROM "${target.from}" AS target_table
304
+ WHERE target_table."${target.foreignField}" = ${targetLocalFieldRef}
305
+ AND target_table."_deleted" IS NULL
306
+ ) AS original_data
307
+ CROSS JOIN LATERAL jsonb_array_elements(original_data.aggregated::jsonb) AS elem${enrichmentJoins}
308
+ ) AS ${target.as} ON true`;
234
309
  }
235
310
  else {
236
- localFieldRef = `${tableAlias}."${columnName}"`;
311
+ const targetThrough = target;
312
+ joinClauses += ` LEFT JOIN LATERAL (
313
+ SELECT COALESCE(
314
+ JSON_AGG(
315
+ elem.value${jsonBuildObjects}
316
+ ),
317
+ '[]'::json
318
+ ) AS aggregated
319
+ FROM (
320
+ SELECT COALESCE(JSON_AGG(row_to_json(target_table)), '[]'::json) AS aggregated
321
+ FROM "${targetThrough.through}"
322
+ INNER JOIN "${targetThrough.from}" AS target_table
323
+ ON target_table."${targetThrough.foreignField}" = "${targetThrough.through}"."${targetThrough.throughForeignField}"
324
+ WHERE "${targetThrough.through}"."${targetThrough.throughLocalField}" = ${targetLocalFieldRef}
325
+ AND "${targetThrough.through}"."_deleted" IS NULL
326
+ AND target_table."_deleted" IS NULL
327
+ ) AS original_data
328
+ CROSS JOIN LATERAL jsonb_array_elements(original_data.aggregated::jsonb) AS elem${enrichmentJoins}
329
+ ) AS ${target.as} ON true`;
330
+ }
331
+ enrichedAliases.add(target.as);
332
+ processedAliases.add(target.as);
333
+ for (const enrich of enrichments) {
334
+ processedAliases.add(enrich.operation.as);
237
335
  }
238
- }
239
- else {
240
- localFieldRef = `${tableAlias}."${columnName}"`;
241
336
  }
242
337
  }
243
338
  else {
244
- localFieldRef = mainTableName
245
- ? `"${mainTableName}"."${operation.localField}"`
246
- : `"${operation.localField}"`;
247
- }
248
- if (!shouldSkipOriginalJoin && !aliasesToSkip.has(operation.as)) {
339
+ const localFieldRef = resolveLocalField(operation.localField, mainTableName, operations);
249
340
  joinClauses += ` LEFT JOIN LATERAL (
250
341
  SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS aggregated
251
342
  FROM "${operation.through}"
@@ -257,9 +348,6 @@ export function buildJoinClauses(operations, mainTableName) {
257
348
  ) AS ${operation.as} ON true`;
258
349
  processedAliases.add(operation.as);
259
350
  }
260
- else if (aliasesToSkip.has(operation.as)) {
261
- processedAliases.add(operation.as);
262
- }
263
351
  }
264
352
  }
265
353
  return joinClauses;
@@ -12,6 +12,17 @@ async function getTableColumns(client, tableName) {
12
12
  `, [tableName]);
13
13
  return result.rows.map(row => row.column_name);
14
14
  }
15
+ function findEnrichmentTarget(operation, operations) {
16
+ if (!operation.localField.includes('.')) {
17
+ return null;
18
+ }
19
+ const [alias] = operation.localField.split('.');
20
+ const target = operations.find(op => (op instanceof JoinMany || op instanceof JoinThroughMany) && op.as === alias);
21
+ if (target && operations.indexOf(target) < operations.indexOf(operation)) {
22
+ return { target, field: operation.localField.split('.')[1] };
23
+ }
24
+ return null;
25
+ }
15
26
  export async function buildSelectClause(client, mainTableName, mainTableAlias, operations) {
16
27
  const joinOperations = operations.filter(op => op instanceof Join);
17
28
  const joinManyOperations = operations.filter(op => op instanceof JoinMany);
@@ -22,41 +33,26 @@ export async function buildSelectClause(client, mainTableName, mainTableAlias, o
22
33
  const joinSelects = [];
23
34
  for (const join of joinOperations) {
24
35
  const joinColumns = await getTableColumns(client, join.from);
25
- if (joinColumns.length === 0) {
26
- continue;
27
- }
28
36
  for (const col of joinColumns) {
29
37
  joinSelects.push(`${join.as}."${col}" AS "${join.as}__${col}"`);
30
38
  }
31
39
  }
32
- for (const joinMany of joinManyOperations) {
33
- joinSelects.push(`${joinMany.as}.aggregated AS "${joinMany.as}"`);
34
- }
35
40
  for (const joinThrough of joinThroughOperations) {
36
41
  joinSelects.push(`${joinThrough.as}.aggregated AS "${joinThrough.as}"`);
37
42
  }
38
- const replacedJoins = new Map();
39
- for (const joinThroughMany of joinThroughManyOperations) {
40
- if (joinThroughMany.localField.includes('.')) {
41
- const [tableAlias] = joinThroughMany.localField.split('.');
42
- const referencedJoin = joinThroughManyOperations.find(j => j.as === tableAlias);
43
- if (referencedJoin) {
44
- const referencedIndex = operations.indexOf(referencedJoin);
45
- const currentIndex = operations.indexOf(joinThroughMany);
46
- if (referencedIndex < currentIndex) {
47
- replacedJoins.set(tableAlias, joinThroughMany.as);
48
- }
49
- }
43
+ for (const joinMany of joinManyOperations) {
44
+ const enrichment = findEnrichmentTarget(joinMany, operations);
45
+ if (enrichment) {
46
+ continue;
50
47
  }
48
+ joinSelects.push(`${joinMany.as}.aggregated AS "${joinMany.as}"`);
51
49
  }
52
50
  for (const joinThroughMany of joinThroughManyOperations) {
53
- if (replacedJoins.has(joinThroughMany.as)) {
54
- const replacingAlias = replacedJoins.get(joinThroughMany.as);
55
- joinSelects.push(`${replacingAlias}.aggregated AS "${joinThroughMany.as}"`);
56
- }
57
- else {
58
- joinSelects.push(`${joinThroughMany.as}.aggregated AS "${joinThroughMany.as}"`);
51
+ const enrichment = findEnrichmentTarget(joinThroughMany, operations);
52
+ if (enrichment) {
53
+ continue;
59
54
  }
55
+ joinSelects.push(`${joinThroughMany.as}.aggregated AS "${joinThroughMany.as}"`);
60
56
  }
61
57
  const allSelects = [...mainSelects, ...joinSelects];
62
58
  return allSelects.join(', ');