@loomcore/api 0.1.95 → 0.1.98

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 (39) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +77 -77
  3. package/dist/__tests__/common-test.utils.js +3 -5
  4. package/dist/__tests__/postgres-test-migrations/postgres-test-schema.js +266 -266
  5. package/dist/__tests__/postgres.test-database.js +8 -8
  6. package/dist/databases/migrations/migration-runner.js +21 -21
  7. package/dist/databases/mongo-db/utils/convert-operations-to-pipeline.util.js +65 -473
  8. package/dist/databases/operations/index.d.ts +3 -4
  9. package/dist/databases/operations/index.js +3 -4
  10. package/dist/databases/operations/{join.operation.d.ts → inner-join.operation.d.ts} +1 -1
  11. package/dist/databases/operations/{join.operation.js → inner-join.operation.js} +2 -2
  12. package/dist/databases/operations/left-join-many.operation.d.ts +7 -0
  13. package/dist/databases/operations/left-join-many.operation.js +15 -0
  14. package/dist/databases/operations/{join-many.operation.d.ts → left-join.operation.d.ts} +1 -1
  15. package/dist/databases/operations/{join-many.operation.js → left-join.operation.js} +2 -2
  16. package/dist/databases/operations/operation.d.ts +4 -5
  17. package/dist/databases/postgres/commands/postgres-batch-update.command.js +11 -12
  18. package/dist/databases/postgres/commands/postgres-create-many.command.js +4 -4
  19. package/dist/databases/postgres/commands/postgres-create.command.js +4 -4
  20. package/dist/databases/postgres/commands/postgres-full-update-by-id.command.js +14 -14
  21. package/dist/databases/postgres/commands/postgres-partial-update-by-id.command.js +8 -8
  22. package/dist/databases/postgres/commands/postgres-update.command.js +8 -8
  23. package/dist/databases/postgres/migrations/__tests__/test-migration-helper.js +1 -0
  24. package/dist/databases/postgres/migrations/postgres-initial-schema.js +224 -224
  25. package/dist/databases/postgres/postgres.database.js +17 -17
  26. package/dist/databases/postgres/queries/postgres-get-all.query.js +4 -5
  27. package/dist/databases/postgres/queries/postgres-get-by-id.query.js +4 -5
  28. package/dist/databases/postgres/queries/postgres-get.query.js +4 -5
  29. package/dist/databases/postgres/utils/build-join-clauses.d.ts +1 -1
  30. package/dist/databases/postgres/utils/build-join-clauses.js +20 -363
  31. package/dist/databases/postgres/utils/build-select-clause.js +15 -27
  32. package/dist/databases/postgres/utils/does-table-exist.util.js +4 -4
  33. package/dist/databases/postgres/utils/transform-join-results.js +19 -120
  34. package/dist/models/initial-database-config.interface.d.ts +1 -0
  35. package/package.json +92 -92
  36. package/dist/databases/operations/join-through-many.operation.d.ts +0 -10
  37. package/dist/databases/operations/join-through-many.operation.js +0 -21
  38. package/dist/databases/operations/join-through.operation.d.ts +0 -10
  39. package/dist/databases/operations/join-through.operation.js +0 -21
@@ -1,7 +1,6 @@
1
- import { Join } from "../../operations/join.operation.js";
2
- import { JoinMany } from "../../operations/join-many.operation.js";
3
- import { JoinThrough } from "../../operations/join-through.operation.js";
4
- import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
1
+ import { LeftJoin } from "../../operations/left-join.operation.js";
2
+ import { InnerJoin } from "../../operations/inner-join.operation.js";
3
+ import { LeftJoinMany } from "../../operations/left-join-many.operation.js";
5
4
  import { buildJoinClauses } from "../utils/build-join-clauses.js";
6
5
  import { buildSelectClause } from "../utils/build-select-clause.js";
7
6
  import { transformJoinResults } from "../utils/transform-join-results.js";
@@ -11,7 +10,7 @@ export async function getById(client, operations, queryObject, id, pluralResourc
11
10
  if (!id)
12
11
  throw new BadRequestError('id is required');
13
12
  const joinClauses = buildJoinClauses(operations, pluralResourceName);
14
- const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany);
13
+ const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
15
14
  const selectClause = hasJoins
16
15
  ? await buildSelectClause(client, pluralResourceName, pluralResourceName, operations)
17
16
  : '*';
@@ -1,7 +1,6 @@
1
- import { Join } from "../../operations/join.operation.js";
2
- import { JoinMany } from "../../operations/join-many.operation.js";
3
- import { JoinThrough } from "../../operations/join-through.operation.js";
4
- import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
1
+ import { LeftJoin } from "../../operations/left-join.operation.js";
2
+ import { InnerJoin } from "../../operations/inner-join.operation.js";
3
+ import { LeftJoinMany } from "../../operations/left-join-many.operation.js";
5
4
  import { buildWhereClause } from "../utils/build-where-clause.js";
6
5
  import { buildOrderByClause } from "../utils/build-order-by-clause.js";
7
6
  import { buildJoinClauses } from "../utils/build-join-clauses.js";
@@ -14,7 +13,7 @@ export async function get(client, operations, queryOptions, pluralResourceName)
14
13
  const joinClauses = buildJoinClauses(operations, pluralResourceName);
15
14
  const orderByClause = buildOrderByClause(queryOptions);
16
15
  const paginationClause = buildPaginationClause(queryOptions);
17
- const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany);
16
+ const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
18
17
  const selectClause = hasJoins
19
18
  ? await buildSelectClause(client, pluralResourceName, pluralResourceName, operations)
20
19
  : '*';
@@ -1,2 +1,2 @@
1
1
  import { Operation } from "../../operations/operation.js";
2
- export declare function buildJoinClauses(operations: Operation[], mainTableName?: string): string;
2
+ export declare function buildJoinClauses(operations: Operation[], mainTableName: string): string;
@@ -1,374 +1,31 @@
1
- import { Join } from "../../operations/join.operation.js";
2
- import { JoinMany } from "../../operations/join-many.operation.js";
3
- import { JoinThrough } from "../../operations/join-through.operation.js";
4
- import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
1
+ import { LeftJoin } from "../../operations/left-join.operation.js";
2
+ import { InnerJoin } from "../../operations/inner-join.operation.js";
3
+ import { LeftJoinMany } from "../../operations/left-join-many.operation.js";
5
4
  import { toSnakeCase } from "./convert-keys.util.js";
6
5
  function convertFieldToSnakeCase(field) {
7
- if (field.startsWith('_')) {
6
+ if (field.startsWith("_")) {
8
7
  return field;
9
8
  }
10
- else if (field === 'id') {
11
- return '_id';
12
- }
13
- else {
14
- return toSnakeCase(field);
15
- }
9
+ return toSnakeCase(field);
16
10
  }
17
- function resolveLocalField(localField, mainTableName, operations) {
18
- if (!localField.includes('.')) {
19
- const snakeField = convertFieldToSnakeCase(localField);
20
- return mainTableName
21
- ? `"${mainTableName}"."${snakeField}"`
22
- : `"${snakeField}"`;
23
- }
24
- const [alias, field] = localField.split('.');
25
- const objectJoin = operations.find(op => op instanceof JoinThrough && op.as === alias);
26
- if (objectJoin) {
27
- return `(${alias}.aggregated->>'${field}')::integer`;
28
- }
29
- const arrayJoin = operations.find(op => (op instanceof JoinMany || op instanceof JoinThroughMany) && op.as === alias);
30
- if (arrayJoin) {
31
- return `(${alias}.aggregated->>'${field}')::integer`;
32
- }
33
- const snakeField = convertFieldToSnakeCase(field);
34
- return `${alias}."${snakeField}"`;
35
- }
36
- function findEnrichmentTarget(operation, operations) {
37
- if (!operation.localField.includes('.')) {
38
- return null;
39
- }
40
- const [alias, field] = operation.localField.split('.');
41
- const target = operations.find(op => (op instanceof JoinMany || op instanceof JoinThroughMany) && op.as === alias);
42
- if (target && operations.indexOf(target) < operations.indexOf(operation)) {
43
- return { target, field };
44
- }
45
- return null;
11
+ function resolveLocalField(localField, mainTableName) {
12
+ if (!localField.includes(".")) {
13
+ const snake = convertFieldToSnakeCase(localField);
14
+ return `"${mainTableName}"."${snake}"`;
15
+ }
16
+ const [alias, field] = localField.split(".");
17
+ const snake = convertFieldToSnakeCase(field);
18
+ return `${alias}."${snake}"`;
46
19
  }
47
20
  export function buildJoinClauses(operations, mainTableName) {
48
- let joinClauses = '';
49
- const processedAliases = new Set();
50
- const enrichedAliases = new Set();
51
- const joinOperations = operations.filter(op => op instanceof Join);
52
- const targetsToEnrich = new Set();
53
- const enrichmentsByTarget = new Map();
21
+ let joinClause = "";
54
22
  for (const operation of operations) {
55
- if (operation instanceof JoinMany || operation instanceof JoinThroughMany) {
56
- const enrichment = findEnrichmentTarget(operation, operations);
57
- if (enrichment) {
58
- const { target } = enrichment;
59
- targetsToEnrich.add(target.as);
60
- if (!enrichmentsByTarget.has(target.as)) {
61
- enrichmentsByTarget.set(target.as, []);
62
- }
63
- enrichmentsByTarget.get(target.as).push({ operation, field: enrichment.field });
64
- }
65
- }
66
- }
67
- for (const [targetAlias, enrichments] of enrichmentsByTarget.entries()) {
68
- enrichments.sort((a, b) => operations.indexOf(a.operation) - operations.indexOf(b.operation));
69
- }
70
- for (const operation of operations) {
71
- if (processedAliases.has(operation.as)) {
72
- continue;
73
- }
74
- const isEnrichmentOp = (operation instanceof JoinMany || operation instanceof JoinThroughMany) &&
75
- findEnrichmentTarget(operation, operations) !== null;
76
- if (targetsToEnrich.has(operation.as) && !isEnrichmentOp) {
77
- continue;
78
- }
79
- if (operation instanceof Join) {
80
- const localFieldRef = resolveLocalField(operation.localField, mainTableName, operations);
81
- joinClauses += ` LEFT JOIN "${operation.from}" AS ${operation.as} ON ${localFieldRef} = "${operation.as}"."${operation.foreignField}"`;
82
- processedAliases.add(operation.as);
83
- }
84
- else if (operation instanceof JoinMany) {
85
- const enrichment = findEnrichmentTarget(operation, operations);
86
- if (enrichment) {
87
- const enrichments = enrichmentsByTarget.get(enrichment.target.as);
88
- const isFirstEnrichment = enrichments[0].operation === operation;
89
- if (isFirstEnrichment && !enrichedAliases.has(enrichment.target.as)) {
90
- const { target, field } = enrichment;
91
- const targetLocalFieldRef = target.localField.includes('.')
92
- ? resolveLocalField(target.localField, mainTableName, operations)
93
- : (() => {
94
- const snakeField = convertFieldToSnakeCase(target.localField);
95
- return mainTableName ? `"${mainTableName}"."${snakeField}"` : `"${snakeField}"`;
96
- })();
97
- let enrichmentJoins = '';
98
- let jsonBuildObjects = '';
99
- for (const enrich of enrichments) {
100
- const enrichField = enrich.field;
101
- const enrichOp = enrich.operation;
102
- const enrichAlias = `enrich_${enrichOp.as}`;
103
- const nestedJoin = joinOperations.find((j) => {
104
- if (!j.localField.includes('.'))
105
- return false;
106
- const [referencedAlias] = j.localField.split('.');
107
- const referencedJoin = operations.find(op => (op instanceof Join || op instanceof JoinMany || op instanceof JoinThroughMany) &&
108
- op.as === referencedAlias);
109
- if (referencedJoin instanceof Join && referencedJoin.from === enrichOp.from) {
110
- return true;
111
- }
112
- if (enrichOp instanceof JoinThroughMany && referencedJoin instanceof Join && referencedJoin.from === enrichOp.from) {
113
- return true;
114
- }
115
- return false;
116
- });
117
- if (nestedJoin) {
118
- const nestedField = nestedJoin.localField.split('.')[1];
119
- enrichmentJoins += ` LEFT JOIN LATERAL (
120
- SELECT COALESCE(
121
- JSON_AGG(
122
- enrich_elem.value || jsonb_build_object('${nestedJoin.as}', nested_join_data.${nestedJoin.as})
123
- ),
124
- '[]'::json
125
- ) AS enriched
126
- FROM (
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 enrich_data
132
- CROSS JOIN LATERAL jsonb_array_elements(enrich_data.enriched::jsonb) AS enrich_elem
133
- LEFT JOIN LATERAL (
134
- SELECT row_to_json(nested_table) AS ${nestedJoin.as}
135
- FROM "${nestedJoin.from}" AS nested_table
136
- WHERE nested_table."${nestedJoin.foreignField}" = (enrich_elem.value->>'${nestedField}')::integer
137
- AND nested_table."_deleted" IS NULL
138
- LIMIT 1
139
- ) AS nested_join_data ON true
140
- ) AS ${enrichAlias} ON true`;
141
- }
142
- else {
143
- enrichmentJoins += ` LEFT JOIN LATERAL (
144
- SELECT COALESCE(JSON_AGG(row_to_json(enrich_table)), '[]'::json) AS enriched
145
- FROM "${enrichOp.from}" AS enrich_table
146
- WHERE enrich_table."${enrichOp.foreignField}" = (elem.value->>'${enrichField}')::integer
147
- AND enrich_table."_deleted" IS NULL
148
- ) AS ${enrichAlias} ON true`;
149
- }
150
- jsonBuildObjects += ` || jsonb_build_object('${enrichOp.as}', COALESCE(${enrichAlias}.enriched, '[]'::json))`;
151
- }
152
- joinClauses += ` LEFT JOIN LATERAL (
153
- SELECT COALESCE(
154
- JSON_AGG(
155
- elem.value${jsonBuildObjects}
156
- ),
157
- '[]'::json
158
- ) AS aggregated
159
- FROM (
160
- SELECT COALESCE(JSON_AGG(row_to_json(target_table)), '[]'::json) AS aggregated
161
- FROM "${target.from}" AS target_table
162
- WHERE target_table."${target.foreignField}" = ${targetLocalFieldRef}
163
- AND target_table."_deleted" IS NULL
164
- ) AS original_data
165
- CROSS JOIN LATERAL jsonb_array_elements(original_data.aggregated::jsonb) AS elem${enrichmentJoins}
166
- ) AS ${target.as} ON true`;
167
- enrichedAliases.add(target.as);
168
- processedAliases.add(target.as);
169
- for (const enrich of enrichments) {
170
- processedAliases.add(enrich.operation.as);
171
- }
172
- }
173
- }
174
- else {
175
- const localFieldRef = resolveLocalField(operation.localField, mainTableName, operations);
176
- joinClauses += ` LEFT JOIN LATERAL (
177
- SELECT COALESCE(JSON_AGG(row_to_json("${operation.from}")), '[]'::json) AS aggregated
178
- FROM "${operation.from}"
179
- WHERE "${operation.from}"."${operation.foreignField}" = ${localFieldRef}
180
- AND "${operation.from}"."_deleted" IS NULL
181
- ) AS ${operation.as} ON true`;
182
- processedAliases.add(operation.as);
183
- }
184
- }
185
- else if (operation instanceof JoinThrough) {
186
- const localFieldRef = resolveLocalField(operation.localField, mainTableName, operations);
187
- joinClauses += ` LEFT JOIN LATERAL (
188
- SELECT row_to_json(${operation.as}) AS aggregated
189
- FROM "${operation.through}"
190
- INNER JOIN "${operation.from}" AS ${operation.as}
191
- ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
192
- WHERE "${operation.through}"."${operation.throughLocalField}" = ${localFieldRef}
193
- AND "${operation.through}"."_deleted" IS NULL
194
- AND ${operation.as}."_deleted" IS NULL
195
- LIMIT 1
196
- ) AS ${operation.as} ON true`;
197
- processedAliases.add(operation.as);
198
- }
199
- else if (operation instanceof JoinThroughMany) {
200
- const enrichment = findEnrichmentTarget(operation, operations);
201
- if (enrichment) {
202
- const enrichments = enrichmentsByTarget.get(enrichment.target.as);
203
- const isFirstEnrichment = enrichments[0].operation === operation;
204
- if (isFirstEnrichment && !enrichedAliases.has(enrichment.target.as)) {
205
- const { target, field } = enrichment;
206
- const targetLocalFieldRef = target.localField.includes('.')
207
- ? resolveLocalField(target.localField, mainTableName, operations)
208
- : (() => {
209
- const snakeField = convertFieldToSnakeCase(target.localField);
210
- return mainTableName ? `"${mainTableName}"."${snakeField}"` : `"${snakeField}"`;
211
- })();
212
- const isTargetJoinMany = target instanceof JoinMany;
213
- let enrichmentJoins = '';
214
- let jsonBuildObjects = '';
215
- for (const enrich of enrichments) {
216
- const enrichField = enrich.field;
217
- const enrichOp = enrich.operation;
218
- const enrichAlias = `enrich_${enrichOp.as}`;
219
- if (enrichOp instanceof JoinMany) {
220
- const nestedJoin = joinOperations.find(j => j.localField.includes('.') &&
221
- j.localField.startsWith(`${enrichOp.as}.`));
222
- if (nestedJoin) {
223
- const nestedField = nestedJoin.localField.split('.')[1];
224
- enrichmentJoins += ` LEFT JOIN LATERAL (
225
- SELECT COALESCE(
226
- JSON_AGG(
227
- enrich_elem.value || jsonb_build_object('${nestedJoin.as}', nested_join_data.${nestedJoin.as})
228
- ),
229
- '[]'::json
230
- ) AS enriched
231
- FROM (
232
- SELECT COALESCE(JSON_AGG(row_to_json(enrich_table)), '[]'::json) AS enriched
233
- FROM "${enrichOp.from}" AS enrich_table
234
- WHERE enrich_table."${enrichOp.foreignField}" = (elem.value->>'${enrichField}')::integer
235
- AND enrich_table."_deleted" IS NULL
236
- ) AS enrich_data
237
- CROSS JOIN LATERAL jsonb_array_elements(enrich_data.enriched::jsonb) AS enrich_elem
238
- LEFT JOIN LATERAL (
239
- SELECT row_to_json(nested_table) AS ${nestedJoin.as}
240
- FROM "${nestedJoin.from}" AS nested_table
241
- WHERE nested_table."${nestedJoin.foreignField}" = (enrich_elem.value->>'${nestedField}')::integer
242
- AND nested_table."_deleted" IS NULL
243
- LIMIT 1
244
- ) AS nested_join_data ON true
245
- ) AS ${enrichAlias} ON true`;
246
- }
247
- else {
248
- enrichmentJoins += ` LEFT JOIN LATERAL (
249
- SELECT COALESCE(JSON_AGG(row_to_json(enrich_table)), '[]'::json) AS enriched
250
- FROM "${enrichOp.from}" AS enrich_table
251
- WHERE enrich_table."${enrichOp.foreignField}" = (elem.value->>'${enrichField}')::integer
252
- AND enrich_table."_deleted" IS NULL
253
- ) AS ${enrichAlias} ON true`;
254
- }
255
- }
256
- else {
257
- const nestedJoin = joinOperations.find((j) => {
258
- if (!j.localField.includes('.'))
259
- return false;
260
- const [referencedAlias] = j.localField.split('.');
261
- const referencedJoin = operations.find(op => (op instanceof Join || op instanceof JoinMany || op instanceof JoinThroughMany) &&
262
- op.as === referencedAlias);
263
- if (referencedJoin instanceof Join && referencedJoin.from === enrichOp.from) {
264
- return true;
265
- }
266
- if (enrichOp instanceof JoinThroughMany && referencedJoin instanceof Join && referencedJoin.from === enrichOp.from) {
267
- return true;
268
- }
269
- return false;
270
- });
271
- if (nestedJoin) {
272
- const nestedField = nestedJoin.localField.split('.')[1];
273
- enrichmentJoins += ` LEFT JOIN LATERAL (
274
- SELECT COALESCE(
275
- JSON_AGG(
276
- enrich_elem.value || jsonb_build_object('${nestedJoin.as}', nested_join_data.${nestedJoin.as})
277
- ),
278
- '[]'::json
279
- ) AS enriched
280
- FROM (
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 enrich_data
289
- CROSS JOIN LATERAL jsonb_array_elements(enrich_data.enriched::jsonb) AS enrich_elem
290
- LEFT JOIN LATERAL (
291
- SELECT row_to_json(nested_table) AS ${nestedJoin.as}
292
- FROM "${nestedJoin.from}" AS nested_table
293
- WHERE nested_table."${nestedJoin.foreignField}" = (enrich_elem.value->>'${nestedField}')::integer
294
- AND nested_table."_deleted" IS NULL
295
- LIMIT 1
296
- ) AS nested_join_data ON true
297
- ) AS ${enrichAlias} ON true`;
298
- }
299
- else {
300
- enrichmentJoins += ` LEFT JOIN LATERAL (
301
- SELECT COALESCE(JSON_AGG(row_to_json(${enrichAlias}_table)), '[]'::json) AS enriched
302
- FROM "${enrichOp.through}"
303
- INNER JOIN "${enrichOp.from}" AS ${enrichAlias}_table
304
- ON ${enrichAlias}_table."${enrichOp.foreignField}" = "${enrichOp.through}"."${enrichOp.throughForeignField}"
305
- WHERE "${enrichOp.through}"."${enrichOp.throughLocalField}" = (elem.value->>'${enrichField}')::integer
306
- AND "${enrichOp.through}"."_deleted" IS NULL
307
- AND ${enrichAlias}_table."_deleted" IS NULL
308
- ) AS ${enrichAlias} ON true`;
309
- }
310
- }
311
- jsonBuildObjects += ` || jsonb_build_object('${enrichOp.as}', COALESCE(${enrichAlias}.enriched, '[]'::json))`;
312
- }
313
- if (isTargetJoinMany) {
314
- joinClauses += ` LEFT JOIN LATERAL (
315
- SELECT COALESCE(
316
- JSON_AGG(
317
- elem.value${jsonBuildObjects}
318
- ),
319
- '[]'::json
320
- ) AS aggregated
321
- FROM (
322
- SELECT COALESCE(JSON_AGG(row_to_json(target_table)), '[]'::json) AS aggregated
323
- FROM "${target.from}" AS target_table
324
- WHERE target_table."${target.foreignField}" = ${targetLocalFieldRef}
325
- AND target_table."_deleted" IS NULL
326
- ) AS original_data
327
- CROSS JOIN LATERAL jsonb_array_elements(original_data.aggregated::jsonb) AS elem${enrichmentJoins}
328
- ) AS ${target.as} ON true`;
329
- }
330
- else {
331
- const targetThrough = target;
332
- joinClauses += ` LEFT JOIN LATERAL (
333
- SELECT COALESCE(
334
- JSON_AGG(
335
- elem.value${jsonBuildObjects}
336
- ),
337
- '[]'::json
338
- ) AS aggregated
339
- FROM (
340
- SELECT COALESCE(JSON_AGG(row_to_json(target_table)), '[]'::json) AS aggregated
341
- FROM "${targetThrough.through}"
342
- INNER JOIN "${targetThrough.from}" AS target_table
343
- ON target_table."${targetThrough.foreignField}" = "${targetThrough.through}"."${targetThrough.throughForeignField}"
344
- WHERE "${targetThrough.through}"."${targetThrough.throughLocalField}" = ${targetLocalFieldRef}
345
- AND "${targetThrough.through}"."_deleted" IS NULL
346
- AND target_table."_deleted" IS NULL
347
- ) AS original_data
348
- CROSS JOIN LATERAL jsonb_array_elements(original_data.aggregated::jsonb) AS elem${enrichmentJoins}
349
- ) AS ${target.as} ON true`;
350
- }
351
- enrichedAliases.add(target.as);
352
- processedAliases.add(target.as);
353
- for (const enrich of enrichments) {
354
- processedAliases.add(enrich.operation.as);
355
- }
356
- }
357
- }
358
- else {
359
- const localFieldRef = resolveLocalField(operation.localField, mainTableName, operations);
360
- joinClauses += ` LEFT JOIN LATERAL (
361
- SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS aggregated
362
- FROM "${operation.through}"
363
- INNER JOIN "${operation.from}" AS ${operation.as}
364
- ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
365
- WHERE "${operation.through}"."${operation.throughLocalField}" = ${localFieldRef}
366
- AND "${operation.through}"."_deleted" IS NULL
367
- AND ${operation.as}."_deleted" IS NULL
368
- ) AS ${operation.as} ON true`;
369
- processedAliases.add(operation.as);
370
- }
23
+ if (operation instanceof LeftJoin || operation instanceof InnerJoin || operation instanceof LeftJoinMany) {
24
+ const localRef = resolveLocalField(operation.localField, mainTableName);
25
+ const foreignSnake = convertFieldToSnakeCase(operation.foreignField);
26
+ const joinType = operation instanceof InnerJoin ? "INNER JOIN" : "LEFT JOIN";
27
+ joinClause += ` ${joinType} "${operation.from}" AS ${operation.as} ON ${localRef} = ${operation.as}."${foreignSnake}"`;
371
28
  }
372
29
  }
373
- return joinClauses;
30
+ return joinClause;
374
31
  }
@@ -1,14 +1,13 @@
1
- import { Join } from '../../operations/join.operation.js';
2
- import { JoinMany } from '../../operations/join-many.operation.js';
3
- import { JoinThrough } from '../../operations/join-through.operation.js';
4
- import { JoinThroughMany } from '../../operations/join-through-many.operation.js';
1
+ import { LeftJoin } from '../../operations/left-join.operation.js';
2
+ import { InnerJoin } from '../../operations/inner-join.operation.js';
3
+ import { LeftJoinMany } from '../../operations/left-join-many.operation.js';
5
4
  async function getTableColumns(client, tableName) {
6
- const result = await client.query(`
7
- SELECT column_name
8
- FROM information_schema.columns
9
- WHERE table_schema = current_schema()
10
- AND table_name = $1
11
- ORDER BY ordinal_position
5
+ const result = await client.query(`
6
+ SELECT column_name
7
+ FROM information_schema.columns
8
+ WHERE table_schema = current_schema()
9
+ AND table_name = $1
10
+ ORDER BY ordinal_position
12
11
  `, [tableName]);
13
12
  return result.rows.map(row => row.column_name);
14
13
  }
@@ -17,43 +16,32 @@ function findEnrichmentTarget(operation, operations) {
17
16
  return null;
18
17
  }
19
18
  const [alias] = operation.localField.split('.');
20
- const target = operations.find(op => (op instanceof JoinMany || op instanceof JoinThroughMany) && op.as === alias);
19
+ const target = operations.find(op => op instanceof LeftJoinMany && op.as === alias);
21
20
  if (target && operations.indexOf(target) < operations.indexOf(operation)) {
22
21
  return { target, field: operation.localField.split('.')[1] };
23
22
  }
24
23
  return null;
25
24
  }
26
25
  export async function buildSelectClause(client, mainTableName, mainTableAlias, operations) {
27
- const joinOperations = operations.filter(op => op instanceof Join);
28
- const joinManyOperations = operations.filter(op => op instanceof JoinMany);
29
- const joinThroughOperations = operations.filter(op => op instanceof JoinThrough);
30
- const joinThroughManyOperations = operations.filter(op => op instanceof JoinThroughMany);
26
+ const leftJoinOperations = operations.filter(op => op instanceof LeftJoin);
27
+ const innerJoinOperations = operations.filter(op => op instanceof InnerJoin);
28
+ const leftJoinManyOperations = operations.filter(op => op instanceof LeftJoinMany);
31
29
  const mainTableColumns = await getTableColumns(client, mainTableName);
32
30
  const mainSelects = mainTableColumns.map(col => `"${mainTableName}"."${col}" AS "${col}"`);
33
31
  const joinSelects = [];
34
- for (const join of joinOperations) {
32
+ for (const join of [...leftJoinOperations, ...innerJoinOperations]) {
35
33
  const joinColumns = await getTableColumns(client, join.from);
36
34
  for (const col of joinColumns) {
37
35
  joinSelects.push(`${join.as}."${col}" AS "${join.as}__${col}"`);
38
36
  }
39
37
  }
40
- for (const joinThrough of joinThroughOperations) {
41
- joinSelects.push(`${joinThrough.as}.aggregated AS "${joinThrough.as}"`);
42
- }
43
- for (const joinMany of joinManyOperations) {
38
+ for (const joinMany of leftJoinManyOperations) {
44
39
  const enrichment = findEnrichmentTarget(joinMany, operations);
45
40
  if (enrichment) {
46
41
  continue;
47
42
  }
48
43
  joinSelects.push(`${joinMany.as}.aggregated AS "${joinMany.as}"`);
49
44
  }
50
- for (const joinThroughMany of joinThroughManyOperations) {
51
- const enrichment = findEnrichmentTarget(joinThroughMany, operations);
52
- if (enrichment) {
53
- continue;
54
- }
55
- joinSelects.push(`${joinThroughMany.as}.aggregated AS "${joinThroughMany.as}"`);
56
- }
57
45
  const allSelects = [...mainSelects, ...joinSelects];
58
46
  return allSelects.join(', ');
59
47
  }
@@ -1,8 +1,8 @@
1
1
  export async function doesTableExist(client, tableName) {
2
- const result = await client.query(`
3
- SELECT EXISTS (
4
- SELECT 1 FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = $1
5
- )
2
+ const result = await client.query(`
3
+ SELECT EXISTS (
4
+ SELECT 1 FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = $1
5
+ )
6
6
  `, [tableName]);
7
7
  return result.rows[0].exists;
8
8
  }