@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.
- package/dist/__tests__/postgres-test-migrations/postgres-test-schema.js +29 -28
- package/dist/databases/operations/__tests__/models/agent.model.d.ts +2 -2
- package/dist/databases/operations/__tests__/models/client-report.model.d.ts +14 -8
- package/dist/databases/operations/__tests__/models/client-report.model.js +1 -1
- package/dist/databases/operations/__tests__/models/person.model.d.ts +4 -4
- package/dist/databases/operations/__tests__/models/person.model.js +2 -2
- package/dist/databases/operations/__tests__/models/policy.model.d.ts +11 -2
- package/dist/databases/operations/__tests__/models/policy.model.js +4 -1
- package/dist/databases/operations/__tests__/models/premium.model.d.ts +12 -0
- package/dist/databases/operations/__tests__/models/premium.model.js +8 -0
- package/dist/databases/operations/join-many.operation.js +3 -0
- package/dist/databases/operations/join-through-many.operation.js +3 -0
- package/dist/databases/operations/join-through.operation.js +3 -0
- package/dist/databases/operations/join.operation.js +3 -0
- package/dist/databases/postgres/utils/build-join-clauses.js +278 -190
- package/dist/databases/postgres/utils/build-select-clause.js +20 -24
- package/dist/databases/postgres/utils/transform-join-results.js +205 -137
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
if (
|
|
48
|
-
const
|
|
49
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const [
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
|
210
|
+
) AS enriched
|
|
125
211
|
FROM (
|
|
126
|
-
SELECT COALESCE(JSON_AGG(row_to_json(
|
|
127
|
-
FROM "${
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
137
|
-
FROM "${
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
148
|
-
SELECT COALESCE(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
183
|
-
|
|
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
|
-
|
|
256
|
+
enrich_elem.value || jsonb_build_object('${nestedJoin.as}', nested_join_data.${nestedJoin.as})
|
|
187
257
|
),
|
|
188
258
|
'[]'::json
|
|
189
|
-
) AS
|
|
259
|
+
) AS enriched
|
|
190
260
|
FROM (
|
|
191
|
-
SELECT COALESCE(JSON_AGG(row_to_json(${
|
|
192
|
-
FROM "${
|
|
193
|
-
INNER JOIN "${
|
|
194
|
-
ON ${
|
|
195
|
-
WHERE "${
|
|
196
|
-
AND "${
|
|
197
|
-
AND ${
|
|
198
|
-
) AS
|
|
199
|
-
CROSS JOIN LATERAL jsonb_array_elements(
|
|
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
|
|
202
|
-
FROM "${
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
213
|
-
SELECT COALESCE(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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(', ');
|