@loomcore/api 0.1.71 → 0.1.73
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 +96 -0
- package/dist/databases/mongo-db/utils/convert-operations-to-pipeline.util.js +190 -2
- package/dist/databases/operations/__tests__/models/district.model.d.ts +12 -0
- package/dist/databases/operations/__tests__/models/district.model.js +7 -0
- package/dist/databases/operations/__tests__/models/person.model.d.ts +2 -0
- package/dist/databases/operations/__tests__/models/school.model.d.ts +12 -0
- package/dist/databases/operations/__tests__/models/school.model.js +7 -0
- package/dist/databases/operations/__tests__/models/state.model.d.ts +8 -0
- package/dist/databases/operations/__tests__/models/state.model.js +6 -0
- package/dist/databases/operations/index.d.ts +1 -0
- package/dist/databases/operations/index.js +1 -0
- package/dist/databases/operations/join-through-many.operation.d.ts +10 -0
- package/dist/databases/operations/join-through-many.operation.js +18 -0
- package/dist/databases/operations/operation.d.ts +2 -1
- package/dist/databases/postgres/commands/postgres-batch-update.command.js +2 -1
- package/dist/databases/postgres/queries/postgres-get-all.query.js +2 -1
- package/dist/databases/postgres/queries/postgres-get-by-id.query.js +2 -1
- package/dist/databases/postgres/queries/postgres-get.query.js +2 -1
- package/dist/databases/postgres/utils/build-join-clauses.js +78 -49
- package/dist/databases/postgres/utils/build-select-clause.js +5 -0
- package/dist/databases/postgres/utils/transform-join-results.js +99 -32
- package/package.json +1 -1
|
@@ -304,5 +304,101 @@ export const getPostgresTestSchema = (config) => {
|
|
|
304
304
|
await pool.query('DROP TABLE IF EXISTS "persons_phone_numbers"');
|
|
305
305
|
}
|
|
306
306
|
});
|
|
307
|
+
migrations.push({
|
|
308
|
+
name: '00000000000111_schema-states',
|
|
309
|
+
up: async ({ context: pool }) => {
|
|
310
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
311
|
+
await pool.query(`
|
|
312
|
+
CREATE TABLE IF NOT EXISTS states (
|
|
313
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
314
|
+
${orgColumnDef}
|
|
315
|
+
"name" VARCHAR NOT NULL,
|
|
316
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
317
|
+
"_createdBy" INTEGER NOT NULL,
|
|
318
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
319
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
320
|
+
"_deleted" TIMESTAMPTZ,
|
|
321
|
+
"_deletedBy" INTEGER
|
|
322
|
+
)
|
|
323
|
+
`);
|
|
324
|
+
},
|
|
325
|
+
down: async ({ context: pool }) => {
|
|
326
|
+
await pool.query('DROP TABLE IF EXISTS "states"');
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
migrations.push({
|
|
330
|
+
name: '00000000000112_schema-districts',
|
|
331
|
+
up: async ({ context: pool }) => {
|
|
332
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
333
|
+
await pool.query(`
|
|
334
|
+
CREATE TABLE IF NOT EXISTS districts (
|
|
335
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
336
|
+
${orgColumnDef}
|
|
337
|
+
"name" VARCHAR NOT NULL,
|
|
338
|
+
"state_id" INTEGER NOT NULL,
|
|
339
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
340
|
+
"_createdBy" INTEGER NOT NULL,
|
|
341
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
342
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
343
|
+
"_deleted" TIMESTAMPTZ,
|
|
344
|
+
"_deletedBy" INTEGER,
|
|
345
|
+
CONSTRAINT fk_districts_state_id FOREIGN KEY ("state_id") REFERENCES states("_id") ON DELETE CASCADE
|
|
346
|
+
)
|
|
347
|
+
`);
|
|
348
|
+
},
|
|
349
|
+
down: async ({ context: pool }) => {
|
|
350
|
+
await pool.query('DROP TABLE IF EXISTS "districts"');
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
migrations.push({
|
|
354
|
+
name: '00000000000113_schema-schools',
|
|
355
|
+
up: async ({ context: pool }) => {
|
|
356
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
357
|
+
await pool.query(`
|
|
358
|
+
CREATE TABLE IF NOT EXISTS schools (
|
|
359
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
360
|
+
${orgColumnDef}
|
|
361
|
+
"name" VARCHAR NOT NULL,
|
|
362
|
+
"district_id" INTEGER NOT NULL,
|
|
363
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
364
|
+
"_createdBy" INTEGER NOT NULL,
|
|
365
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
366
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
367
|
+
"_deleted" TIMESTAMPTZ,
|
|
368
|
+
"_deletedBy" INTEGER,
|
|
369
|
+
CONSTRAINT fk_schools_district_id FOREIGN KEY ("district_id") REFERENCES districts("_id") ON DELETE CASCADE
|
|
370
|
+
)
|
|
371
|
+
`);
|
|
372
|
+
},
|
|
373
|
+
down: async ({ context: pool }) => {
|
|
374
|
+
await pool.query('DROP TABLE IF EXISTS "schools"');
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
migrations.push({
|
|
378
|
+
name: '00000000000114_schema-person-schools',
|
|
379
|
+
up: async ({ context: pool }) => {
|
|
380
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
381
|
+
await pool.query(`
|
|
382
|
+
CREATE TABLE IF NOT EXISTS persons_schools (
|
|
383
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
384
|
+
${orgColumnDef}
|
|
385
|
+
"person_id" INTEGER NOT NULL,
|
|
386
|
+
"school_id" INTEGER NOT NULL,
|
|
387
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
388
|
+
"_createdBy" INTEGER NOT NULL,
|
|
389
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
390
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
391
|
+
"_deleted" TIMESTAMPTZ,
|
|
392
|
+
"_deletedBy" INTEGER,
|
|
393
|
+
CONSTRAINT fk_persons_schools_person_id FOREIGN KEY ("person_id") REFERENCES persons("_id") ON DELETE CASCADE,
|
|
394
|
+
CONSTRAINT fk_persons_schools_school_id FOREIGN KEY ("school_id") REFERENCES schools("_id") ON DELETE CASCADE,
|
|
395
|
+
CONSTRAINT uk_persons_schools_person_school UNIQUE ("person_id", "school_id")
|
|
396
|
+
)
|
|
397
|
+
`);
|
|
398
|
+
},
|
|
399
|
+
down: async ({ context: pool }) => {
|
|
400
|
+
await pool.query('DROP TABLE IF EXISTS "persons_schools"');
|
|
401
|
+
}
|
|
402
|
+
});
|
|
307
403
|
return migrations;
|
|
308
404
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { JoinThroughMany } from '../../operations/join-through-many.operation.js';
|
|
4
5
|
export function convertOperationsToPipeline(operations) {
|
|
5
6
|
let pipeline = [];
|
|
6
7
|
const processedOperations = [];
|
|
@@ -124,6 +125,193 @@ export function convertOperationsToPipeline(operations) {
|
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
else if (operation instanceof JoinThrough) {
|
|
128
|
+
const needsObjectIdConversion = operation.foreignField === '_id';
|
|
129
|
+
const isNestedField = operation.localField.includes('.');
|
|
130
|
+
if (needsObjectIdConversion || isNestedField) {
|
|
131
|
+
pipeline.push({
|
|
132
|
+
$lookup: {
|
|
133
|
+
from: operation.through,
|
|
134
|
+
let: { localId: { $cond: [
|
|
135
|
+
{ $eq: [{ $type: `$${operation.localField}` }, 'string'] },
|
|
136
|
+
{ $toObjectId: `$${operation.localField}` },
|
|
137
|
+
`$${operation.localField}`
|
|
138
|
+
] } },
|
|
139
|
+
pipeline: [
|
|
140
|
+
{
|
|
141
|
+
$match: {
|
|
142
|
+
$expr: {
|
|
143
|
+
$eq: [
|
|
144
|
+
{
|
|
145
|
+
$cond: [
|
|
146
|
+
{ $eq: [{ $type: `$${operation.throughLocalField}` }, 'string'] },
|
|
147
|
+
{ $toObjectId: `$${operation.throughLocalField}` },
|
|
148
|
+
`$${operation.throughLocalField}`
|
|
149
|
+
]
|
|
150
|
+
},
|
|
151
|
+
'$$localId'
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
{ $limit: 1 }
|
|
157
|
+
],
|
|
158
|
+
as: `${operation.as}_through`
|
|
159
|
+
}
|
|
160
|
+
}, {
|
|
161
|
+
$unwind: {
|
|
162
|
+
path: `$${operation.as}_through`,
|
|
163
|
+
preserveNullAndEmptyArrays: true
|
|
164
|
+
}
|
|
165
|
+
}, {
|
|
166
|
+
$lookup: {
|
|
167
|
+
from: operation.from,
|
|
168
|
+
let: { foreignId: { $cond: [
|
|
169
|
+
{ $eq: [{ $type: `$${operation.as}_through.${operation.throughForeignField}` }, 'string'] },
|
|
170
|
+
{ $toObjectId: `$${operation.as}_through.${operation.throughForeignField}` },
|
|
171
|
+
`$${operation.as}_through.${operation.throughForeignField}`
|
|
172
|
+
] } },
|
|
173
|
+
pipeline: [
|
|
174
|
+
{
|
|
175
|
+
$match: {
|
|
176
|
+
$expr: {
|
|
177
|
+
$eq: [
|
|
178
|
+
{
|
|
179
|
+
$cond: [
|
|
180
|
+
{ $eq: [{ $type: `$${operation.foreignField}` }, 'string'] },
|
|
181
|
+
{ $toObjectId: `$${operation.foreignField}` },
|
|
182
|
+
`$${operation.foreignField}`
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
'$$foreignId'
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
as: `${operation.as}_temp`
|
|
192
|
+
}
|
|
193
|
+
}, {
|
|
194
|
+
$unwind: {
|
|
195
|
+
path: `$${operation.as}_temp`,
|
|
196
|
+
preserveNullAndEmptyArrays: true
|
|
197
|
+
}
|
|
198
|
+
}, {
|
|
199
|
+
$addFields: {
|
|
200
|
+
[operation.as]: `$${operation.as}_temp`
|
|
201
|
+
}
|
|
202
|
+
}, {
|
|
203
|
+
$project: {
|
|
204
|
+
[`${operation.as}_through`]: 0,
|
|
205
|
+
[`${operation.as}_temp`]: 0
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
if (isNestedField) {
|
|
209
|
+
const [parentAlias] = operation.localField.split('.');
|
|
210
|
+
const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinThrough) && op.as === parentAlias);
|
|
211
|
+
if (parentJoin) {
|
|
212
|
+
pipeline.push({
|
|
213
|
+
$set: {
|
|
214
|
+
[parentAlias]: {
|
|
215
|
+
$mergeObjects: [
|
|
216
|
+
{ $ifNull: [`$${parentAlias}`, {}] },
|
|
217
|
+
{ [operation.as]: `$${operation.as}` }
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
pipeline.push({
|
|
223
|
+
$unset: operation.as
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
const isNestedFieldElse = operation.localField.includes('.');
|
|
230
|
+
if (isNestedFieldElse) {
|
|
231
|
+
pipeline.push({
|
|
232
|
+
$lookup: {
|
|
233
|
+
from: operation.through,
|
|
234
|
+
let: { localId: `$${operation.localField}` },
|
|
235
|
+
pipeline: [
|
|
236
|
+
{
|
|
237
|
+
$match: {
|
|
238
|
+
$expr: {
|
|
239
|
+
$eq: [`$${operation.throughLocalField}`, '$$localId']
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{ $limit: 1 }
|
|
244
|
+
],
|
|
245
|
+
as: `${operation.as}_through`
|
|
246
|
+
}
|
|
247
|
+
}, {
|
|
248
|
+
$unwind: {
|
|
249
|
+
path: `$${operation.as}_through`,
|
|
250
|
+
preserveNullAndEmptyArrays: true
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
pipeline.push({
|
|
256
|
+
$lookup: {
|
|
257
|
+
from: operation.through,
|
|
258
|
+
localField: operation.localField,
|
|
259
|
+
foreignField: operation.throughLocalField,
|
|
260
|
+
as: `${operation.as}_through`
|
|
261
|
+
}
|
|
262
|
+
}, {
|
|
263
|
+
$unwind: {
|
|
264
|
+
path: `$${operation.as}_through`,
|
|
265
|
+
preserveNullAndEmptyArrays: true
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
pipeline.push({
|
|
270
|
+
$lookup: {
|
|
271
|
+
from: operation.from,
|
|
272
|
+
localField: `${operation.as}_through.${operation.throughForeignField}`,
|
|
273
|
+
foreignField: operation.foreignField,
|
|
274
|
+
as: `${operation.as}_temp`
|
|
275
|
+
}
|
|
276
|
+
}, {
|
|
277
|
+
$unwind: {
|
|
278
|
+
path: `$${operation.as}_temp`,
|
|
279
|
+
preserveNullAndEmptyArrays: true
|
|
280
|
+
}
|
|
281
|
+
}, {
|
|
282
|
+
$addFields: {
|
|
283
|
+
[operation.as]: `$${operation.as}_temp`
|
|
284
|
+
}
|
|
285
|
+
}, {
|
|
286
|
+
$project: {
|
|
287
|
+
[`${operation.as}_through`]: 0,
|
|
288
|
+
[`${operation.as}_temp`]: 0
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
if (isNestedFieldElse) {
|
|
292
|
+
const [parentAlias] = operation.localField.split('.');
|
|
293
|
+
const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinThrough) && op.as === parentAlias);
|
|
294
|
+
if (parentJoin) {
|
|
295
|
+
pipeline.push({
|
|
296
|
+
$addFields: {
|
|
297
|
+
[parentAlias]: {
|
|
298
|
+
$mergeObjects: [
|
|
299
|
+
`$${parentAlias}`,
|
|
300
|
+
{ [operation.as]: `$${operation.as}` }
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
pipeline.push({
|
|
306
|
+
$project: {
|
|
307
|
+
[operation.as]: 0
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else if (operation instanceof JoinThroughMany) {
|
|
127
315
|
const needsObjectIdConversion = operation.foreignField === '_id';
|
|
128
316
|
const isNestedField = operation.localField.includes('.');
|
|
129
317
|
if (needsObjectIdConversion || isNestedField) {
|
|
@@ -208,7 +396,7 @@ export function convertOperationsToPipeline(operations) {
|
|
|
208
396
|
});
|
|
209
397
|
if (isNestedField) {
|
|
210
398
|
const [parentAlias] = operation.localField.split('.');
|
|
211
|
-
const parentJoin = processedOperations.find(op => op instanceof Join && op.as === parentAlias);
|
|
399
|
+
const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinThrough) && op.as === parentAlias);
|
|
212
400
|
if (parentJoin) {
|
|
213
401
|
pipeline.push({
|
|
214
402
|
$set: {
|
|
@@ -293,7 +481,7 @@ export function convertOperationsToPipeline(operations) {
|
|
|
293
481
|
});
|
|
294
482
|
if (isNestedFieldElse) {
|
|
295
483
|
const [parentAlias] = operation.localField.split('.');
|
|
296
|
-
const parentJoin = processedOperations.find(op => op instanceof Join && op.as === parentAlias);
|
|
484
|
+
const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinThrough) && op.as === parentAlias);
|
|
297
485
|
if (parentJoin) {
|
|
298
486
|
pipeline.push({
|
|
299
487
|
$addFields: {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IAuditable, IEntity } from "@loomcore/common/models";
|
|
2
|
+
import { IStateModel } from "./state.model.js";
|
|
3
|
+
export interface IDistrictModel extends IEntity, IAuditable {
|
|
4
|
+
name: string;
|
|
5
|
+
state_id: number;
|
|
6
|
+
state?: IStateModel;
|
|
7
|
+
}
|
|
8
|
+
export declare const districtSchema: import("@sinclair/typebox").TObject<{
|
|
9
|
+
name: import("@sinclair/typebox").TString;
|
|
10
|
+
state_id: import("@sinclair/typebox").TNumber;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const districtModelSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { entityUtils } from "@loomcore/common/utils";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
export const districtSchema = Type.Object({
|
|
4
|
+
name: Type.String(),
|
|
5
|
+
state_id: Type.Number(),
|
|
6
|
+
});
|
|
7
|
+
export const districtModelSpec = entityUtils.getModelSpec(districtSchema, { isAuditable: true });
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { IAuditable, IEntity } from "@loomcore/common/models";
|
|
2
2
|
import { IEmailAddressModel } from "./email-address.model.js";
|
|
3
3
|
import { IPhoneNumberModel } from "./phone-number.model.js";
|
|
4
|
+
import { ISchoolModel } from "./school.model.js";
|
|
4
5
|
export interface IPersonModel extends IEntity, IAuditable {
|
|
5
6
|
first_name: string;
|
|
6
7
|
middle_name: string | null;
|
|
7
8
|
last_name: string;
|
|
8
9
|
email_addresses: IEmailAddressModel[];
|
|
9
10
|
phone_numbers: IPhoneNumberModel[];
|
|
11
|
+
school?: ISchoolModel;
|
|
10
12
|
}
|
|
11
13
|
export declare const personSchema: import("@sinclair/typebox").TObject<{
|
|
12
14
|
first_name: import("@sinclair/typebox").TString;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IAuditable, IEntity } from "@loomcore/common/models";
|
|
2
|
+
import { IDistrictModel } from "./district.model.js";
|
|
3
|
+
export interface ISchoolModel extends IEntity, IAuditable {
|
|
4
|
+
name: string;
|
|
5
|
+
district_id: number;
|
|
6
|
+
district?: IDistrictModel;
|
|
7
|
+
}
|
|
8
|
+
export declare const schoolSchema: import("@sinclair/typebox").TObject<{
|
|
9
|
+
name: import("@sinclair/typebox").TString;
|
|
10
|
+
district_id: import("@sinclair/typebox").TNumber;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const schoolModelSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { entityUtils } from "@loomcore/common/utils";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
export const schoolSchema = Type.Object({
|
|
4
|
+
name: Type.String(),
|
|
5
|
+
district_id: Type.Number(),
|
|
6
|
+
});
|
|
7
|
+
export const schoolModelSpec = entityUtils.getModelSpec(schoolSchema, { isAuditable: true });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IAuditable, IEntity } from "@loomcore/common/models";
|
|
2
|
+
export interface IStateModel extends IEntity, IAuditable {
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const stateSchema: import("@sinclair/typebox").TObject<{
|
|
6
|
+
name: import("@sinclair/typebox").TString;
|
|
7
|
+
}>;
|
|
8
|
+
export declare const stateModelSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class JoinThroughMany {
|
|
2
|
+
from: string;
|
|
3
|
+
through: string;
|
|
4
|
+
localField: string;
|
|
5
|
+
throughLocalField: string;
|
|
6
|
+
throughForeignField: string;
|
|
7
|
+
foreignField: string;
|
|
8
|
+
as: string;
|
|
9
|
+
constructor(from: string, through: string, localField: string, throughLocalField: string, throughForeignField: string, foreignField: string, as: string);
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class JoinThroughMany {
|
|
2
|
+
from;
|
|
3
|
+
through;
|
|
4
|
+
localField;
|
|
5
|
+
throughLocalField;
|
|
6
|
+
throughForeignField;
|
|
7
|
+
foreignField;
|
|
8
|
+
as;
|
|
9
|
+
constructor(from, through, localField, throughLocalField, throughForeignField, foreignField, as) {
|
|
10
|
+
this.from = from;
|
|
11
|
+
this.through = through;
|
|
12
|
+
this.localField = localField;
|
|
13
|
+
this.throughLocalField = throughLocalField;
|
|
14
|
+
this.throughForeignField = throughForeignField;
|
|
15
|
+
this.foreignField = foreignField;
|
|
16
|
+
this.as = as;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Join } from "./join.operation.js";
|
|
2
2
|
import { JoinMany } from "./join-many.operation.js";
|
|
3
3
|
import { JoinThrough } from "./join-through.operation.js";
|
|
4
|
-
|
|
4
|
+
import { JoinThroughMany } from "./join-through-many.operation.js";
|
|
5
|
+
export type Operation = Join | JoinMany | JoinThrough | JoinThroughMany;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
|
|
4
5
|
import { BadRequestError } from "../../../errors/index.js";
|
|
5
6
|
import { buildJoinClauses } from '../utils/build-join-clauses.js';
|
|
6
7
|
import { buildSelectClause } from '../utils/build-select-clause.js';
|
|
@@ -39,7 +40,7 @@ export async function batchUpdate(client, entities, operations, queryObject, plu
|
|
|
39
40
|
}
|
|
40
41
|
await client.query('COMMIT');
|
|
41
42
|
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
42
|
-
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough);
|
|
43
|
+
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany);
|
|
43
44
|
const tablePrefix = hasJoins ? pluralResourceName : undefined;
|
|
44
45
|
queryObject.filters._id = { in: entityIds };
|
|
45
46
|
const { whereClause, values } = buildWhereClause(queryObject, [], tablePrefix);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
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
|
+
import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
|
|
4
5
|
import { buildJoinClauses } from '../utils/build-join-clauses.js';
|
|
5
6
|
import { buildSelectClause } from '../utils/build-select-clause.js';
|
|
6
7
|
import { transformJoinResults } from '../utils/transform-join-results.js';
|
|
7
8
|
export async function getAll(client, operations, pluralResourceName) {
|
|
8
9
|
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
9
|
-
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough);
|
|
10
|
+
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany);
|
|
10
11
|
const selectClause = hasJoins
|
|
11
12
|
? await buildSelectClause(client, pluralResourceName, pluralResourceName, operations)
|
|
12
13
|
: '*';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
|
|
4
5
|
import { buildJoinClauses } from "../utils/build-join-clauses.js";
|
|
5
6
|
import { buildSelectClause } from "../utils/build-select-clause.js";
|
|
6
7
|
import { transformJoinResults } from "../utils/transform-join-results.js";
|
|
@@ -10,7 +11,7 @@ export async function getById(client, operations, queryObject, id, pluralResourc
|
|
|
10
11
|
if (!id)
|
|
11
12
|
throw new BadRequestError('id is required');
|
|
12
13
|
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
13
|
-
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough);
|
|
14
|
+
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany);
|
|
14
15
|
const selectClause = hasJoins
|
|
15
16
|
? await buildSelectClause(client, pluralResourceName, pluralResourceName, operations)
|
|
16
17
|
: '*';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
|
|
4
5
|
import { buildWhereClause } from "../utils/build-where-clause.js";
|
|
5
6
|
import { buildOrderByClause } from "../utils/build-order-by-clause.js";
|
|
6
7
|
import { buildJoinClauses } from "../utils/build-join-clauses.js";
|
|
@@ -13,7 +14,7 @@ export async function get(client, operations, queryOptions, pluralResourceName)
|
|
|
13
14
|
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
14
15
|
const orderByClause = buildOrderByClause(queryOptions);
|
|
15
16
|
const paginationClause = buildPaginationClause(queryOptions);
|
|
16
|
-
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough);
|
|
17
|
+
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany);
|
|
17
18
|
const selectClause = hasJoins
|
|
18
19
|
? await buildSelectClause(client, pluralResourceName, pluralResourceName, operations)
|
|
19
20
|
: '*';
|
|
@@ -1,62 +1,91 @@
|
|
|
1
1
|
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
|
+
import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
|
|
4
5
|
export function buildJoinClauses(operations, mainTableName) {
|
|
5
6
|
let joinClauses = '';
|
|
6
|
-
const joinOperations = operations.filter(op => op instanceof Join);
|
|
7
|
-
const joinManyOperations = operations.filter(op => op instanceof JoinMany);
|
|
8
7
|
const joinThroughOperations = operations.filter(op => op instanceof JoinThrough);
|
|
9
|
-
for (const operation of
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
for (const operation of operations) {
|
|
9
|
+
if (operation instanceof Join) {
|
|
10
|
+
let localFieldRef;
|
|
11
|
+
if (operation.localField.includes('.')) {
|
|
12
|
+
const [tableAlias, columnName] = operation.localField.split('.');
|
|
13
|
+
const referencedJoinThrough = joinThroughOperations.find(jt => jt.as === tableAlias);
|
|
14
|
+
if (referencedJoinThrough) {
|
|
15
|
+
localFieldRef = `(${tableAlias}.aggregated->>'${columnName}')::integer`;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
localFieldRef = `${tableAlias}."${columnName}"`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
localFieldRef = mainTableName
|
|
23
|
+
? `"${mainTableName}"."${operation.localField}"`
|
|
24
|
+
: `"${operation.localField}"`;
|
|
25
|
+
}
|
|
26
|
+
joinClauses += ` LEFT JOIN "${operation.from}" AS ${operation.as} ON ${localFieldRef} = "${operation.as}"."${operation.foreignField}"`;
|
|
14
27
|
}
|
|
15
|
-
else {
|
|
16
|
-
localFieldRef
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
else if (operation instanceof JoinMany) {
|
|
29
|
+
let localFieldRef;
|
|
30
|
+
if (operation.localField.includes('.')) {
|
|
31
|
+
const [tableAlias, columnName] = operation.localField.split('.');
|
|
32
|
+
localFieldRef = `${tableAlias}."${columnName}"`;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
localFieldRef = mainTableName
|
|
36
|
+
? `"${mainTableName}"."${operation.localField}"`
|
|
37
|
+
: `"${operation.localField}"`;
|
|
38
|
+
}
|
|
39
|
+
joinClauses += ` LEFT JOIN LATERAL (
|
|
40
|
+
SELECT COALESCE(JSON_AGG(row_to_json("${operation.from}")), '[]'::json) AS aggregated
|
|
41
|
+
FROM "${operation.from}"
|
|
42
|
+
WHERE "${operation.from}"."${operation.foreignField}" = ${localFieldRef}
|
|
43
|
+
AND "${operation.from}"."_deleted" IS NULL
|
|
44
|
+
) AS ${operation.as} ON true`;
|
|
19
45
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
let localFieldRef;
|
|
42
|
-
if (joinThrough.localField.includes('.')) {
|
|
43
|
-
const [tableAlias, columnName] = joinThrough.localField.split('.');
|
|
44
|
-
localFieldRef = `${tableAlias}."${columnName}"`;
|
|
46
|
+
else if (operation instanceof JoinThrough) {
|
|
47
|
+
let localFieldRef;
|
|
48
|
+
if (operation.localField.includes('.')) {
|
|
49
|
+
const [tableAlias, columnName] = operation.localField.split('.');
|
|
50
|
+
localFieldRef = `${tableAlias}."${columnName}"`;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
localFieldRef = mainTableName
|
|
54
|
+
? `"${mainTableName}"."${operation.localField}"`
|
|
55
|
+
: `"${operation.localField}"`;
|
|
56
|
+
}
|
|
57
|
+
joinClauses += ` LEFT JOIN LATERAL (
|
|
58
|
+
SELECT row_to_json(${operation.as}) AS aggregated
|
|
59
|
+
FROM "${operation.through}"
|
|
60
|
+
INNER JOIN "${operation.from}" AS ${operation.as}
|
|
61
|
+
ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
|
|
62
|
+
WHERE "${operation.through}"."${operation.throughLocalField}" = ${localFieldRef}
|
|
63
|
+
AND "${operation.through}"."_deleted" IS NULL
|
|
64
|
+
AND ${operation.as}."_deleted" IS NULL
|
|
65
|
+
LIMIT 1
|
|
66
|
+
) AS ${operation.as} ON true`;
|
|
45
67
|
}
|
|
46
|
-
else {
|
|
47
|
-
localFieldRef
|
|
48
|
-
|
|
49
|
-
|
|
68
|
+
else if (operation instanceof JoinThroughMany) {
|
|
69
|
+
let localFieldRef;
|
|
70
|
+
if (operation.localField.includes('.')) {
|
|
71
|
+
const [tableAlias, columnName] = operation.localField.split('.');
|
|
72
|
+
localFieldRef = `${tableAlias}."${columnName}"`;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
localFieldRef = mainTableName
|
|
76
|
+
? `"${mainTableName}"."${operation.localField}"`
|
|
77
|
+
: `"${operation.localField}"`;
|
|
78
|
+
}
|
|
79
|
+
joinClauses += ` LEFT JOIN LATERAL (
|
|
80
|
+
SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS aggregated
|
|
81
|
+
FROM "${operation.through}"
|
|
82
|
+
INNER JOIN "${operation.from}" AS ${operation.as}
|
|
83
|
+
ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
|
|
84
|
+
WHERE "${operation.through}"."${operation.throughLocalField}" = ${localFieldRef}
|
|
85
|
+
AND "${operation.through}"."_deleted" IS NULL
|
|
86
|
+
AND ${operation.as}."_deleted" IS NULL
|
|
87
|
+
) AS ${operation.as} ON true`;
|
|
50
88
|
}
|
|
51
|
-
joinClauses += ` LEFT JOIN LATERAL (
|
|
52
|
-
SELECT COALESCE(JSON_AGG(row_to_json(${joinThrough.as})), '[]'::json) AS aggregated
|
|
53
|
-
FROM "${joinThrough.through}"
|
|
54
|
-
INNER JOIN "${joinThrough.from}" AS ${joinThrough.as}
|
|
55
|
-
ON ${joinThrough.as}."${joinThrough.foreignField}" = "${joinThrough.through}"."${joinThrough.throughForeignField}"
|
|
56
|
-
WHERE "${joinThrough.through}"."${joinThrough.throughLocalField}" = ${localFieldRef}
|
|
57
|
-
AND "${joinThrough.through}"."_deleted" IS NULL
|
|
58
|
-
AND ${joinThrough.as}."_deleted" IS NULL
|
|
59
|
-
) AS ${joinThrough.as} ON true`;
|
|
60
89
|
}
|
|
61
90
|
return joinClauses;
|
|
62
91
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { JoinThroughMany } from '../../operations/join-through-many.operation.js';
|
|
4
5
|
async function getTableColumns(client, tableName) {
|
|
5
6
|
const result = await client.query(`
|
|
6
7
|
SELECT column_name
|
|
@@ -15,6 +16,7 @@ export async function buildSelectClause(client, mainTableName, mainTableAlias, o
|
|
|
15
16
|
const joinOperations = operations.filter(op => op instanceof Join);
|
|
16
17
|
const joinManyOperations = operations.filter(op => op instanceof JoinMany);
|
|
17
18
|
const joinThroughOperations = operations.filter(op => op instanceof JoinThrough);
|
|
19
|
+
const joinThroughManyOperations = operations.filter(op => op instanceof JoinThroughMany);
|
|
18
20
|
const mainTableColumns = await getTableColumns(client, mainTableName);
|
|
19
21
|
const mainSelects = mainTableColumns.map(col => `"${mainTableName}"."${col}" AS "${col}"`);
|
|
20
22
|
const joinSelects = [];
|
|
@@ -33,6 +35,9 @@ export async function buildSelectClause(client, mainTableName, mainTableAlias, o
|
|
|
33
35
|
for (const joinThrough of joinThroughOperations) {
|
|
34
36
|
joinSelects.push(`${joinThrough.as}.aggregated AS "${joinThrough.as}"`);
|
|
35
37
|
}
|
|
38
|
+
for (const joinThroughMany of joinThroughManyOperations) {
|
|
39
|
+
joinSelects.push(`${joinThroughMany.as}.aggregated AS "${joinThroughMany.as}"`);
|
|
40
|
+
}
|
|
36
41
|
const allSelects = [...mainSelects, ...joinSelects];
|
|
37
42
|
return allSelects.join(', ');
|
|
38
43
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
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
|
+
import { JoinThroughMany } from '../../operations/join-through-many.operation.js';
|
|
4
5
|
export function transformJoinResults(rows, operations) {
|
|
5
6
|
const joinOperations = operations.filter(op => op instanceof Join);
|
|
6
7
|
const joinManyOperations = operations.filter(op => op instanceof JoinMany);
|
|
7
8
|
const joinThroughOperations = operations.filter(op => op instanceof JoinThrough);
|
|
8
|
-
|
|
9
|
+
const joinThroughManyOperations = operations.filter(op => op instanceof JoinThroughMany);
|
|
10
|
+
if (joinOperations.length === 0 && joinManyOperations.length === 0 && joinThroughOperations.length === 0 && joinThroughManyOperations.length === 0) {
|
|
9
11
|
return rows;
|
|
10
12
|
}
|
|
11
13
|
return rows.map(row => {
|
|
@@ -13,7 +15,8 @@ export function transformJoinResults(rows, operations) {
|
|
|
13
15
|
const allJoinAliases = [
|
|
14
16
|
...joinOperations.map(j => j.as),
|
|
15
17
|
...joinManyOperations.map(j => j.as),
|
|
16
|
-
...joinThroughOperations.map(j => j.as)
|
|
18
|
+
...joinThroughOperations.map(j => j.as),
|
|
19
|
+
...joinThroughManyOperations.map(j => j.as)
|
|
17
20
|
];
|
|
18
21
|
for (const key of Object.keys(row)) {
|
|
19
22
|
const hasJoinPrefix = joinOperations.some(join => key.startsWith(`${join.as}__`));
|
|
@@ -22,38 +25,100 @@ export function transformJoinResults(rows, operations) {
|
|
|
22
25
|
transformed[key] = row[key];
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
|
-
for (const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
for (const operation of operations) {
|
|
29
|
+
if (operation instanceof JoinThrough) {
|
|
30
|
+
const jsonValue = row[operation.as];
|
|
31
|
+
let parsedValue;
|
|
32
|
+
if (jsonValue !== null && jsonValue !== undefined) {
|
|
33
|
+
parsedValue = typeof jsonValue === 'string'
|
|
34
|
+
? JSON.parse(jsonValue)
|
|
35
|
+
: jsonValue;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
parsedValue = null;
|
|
39
|
+
}
|
|
40
|
+
if (operation.localField.includes('.')) {
|
|
41
|
+
const [tableAlias] = operation.localField.split('.');
|
|
42
|
+
const relatedJoin = joinOperations.find(j => j.as === tableAlias);
|
|
43
|
+
const relatedJoinThrough = joinThroughOperations.find(j => j.as === tableAlias);
|
|
44
|
+
if ((relatedJoin && transformed[relatedJoin.as]) || (relatedJoinThrough && transformed[relatedJoinThrough.as])) {
|
|
45
|
+
const targetAlias = relatedJoin ? relatedJoin.as : relatedJoinThrough.as;
|
|
46
|
+
transformed[targetAlias][operation.as] = parsedValue;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
transformed[operation.as] = parsedValue;
|
|
36
50
|
}
|
|
37
51
|
}
|
|
52
|
+
else {
|
|
53
|
+
transformed[operation.as] = parsedValue;
|
|
54
|
+
}
|
|
38
55
|
}
|
|
39
|
-
if (
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
else if (operation instanceof Join) {
|
|
57
|
+
const prefix = `${operation.as}__`;
|
|
58
|
+
const joinedData = {};
|
|
59
|
+
let hasAnyData = false;
|
|
60
|
+
for (const key of Object.keys(row)) {
|
|
61
|
+
if (key.startsWith(prefix)) {
|
|
62
|
+
const columnName = key.substring(prefix.length);
|
|
63
|
+
const value = row[key];
|
|
64
|
+
joinedData[columnName] = value;
|
|
65
|
+
if (value !== null && value !== undefined) {
|
|
66
|
+
hasAnyData = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (operation.localField.includes('.')) {
|
|
71
|
+
const [tableAlias] = operation.localField.split('.');
|
|
72
|
+
const relatedJoin = joinOperations.find(j => j.as === tableAlias);
|
|
73
|
+
const relatedJoinThrough = joinThroughOperations.find(j => j.as === tableAlias);
|
|
74
|
+
const findNestedObject = (obj, alias, path = []) => {
|
|
75
|
+
if (obj[alias] !== undefined && obj[alias] !== null) {
|
|
76
|
+
return { obj: obj[alias], path: [...path, alias] };
|
|
77
|
+
}
|
|
78
|
+
for (const key in obj) {
|
|
79
|
+
if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
|
80
|
+
const found = findNestedObject(obj[key], alias, [...path, key]);
|
|
81
|
+
if (found !== null) {
|
|
82
|
+
return found;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
};
|
|
88
|
+
let targetObject = null;
|
|
89
|
+
if (relatedJoin) {
|
|
90
|
+
if (transformed[relatedJoin.as] !== undefined && transformed[relatedJoin.as] !== null) {
|
|
91
|
+
targetObject = transformed[relatedJoin.as];
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const found = findNestedObject(transformed, relatedJoin.as);
|
|
95
|
+
if (found !== null && found.obj !== undefined && found.obj !== null) {
|
|
96
|
+
targetObject = found.obj;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (relatedJoinThrough) {
|
|
101
|
+
const found = findNestedObject(transformed, relatedJoinThrough.as);
|
|
102
|
+
if (found !== null && found.obj !== undefined && found.obj !== null) {
|
|
103
|
+
targetObject = found.obj;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (targetObject) {
|
|
107
|
+
if (hasAnyData) {
|
|
108
|
+
targetObject[operation.as] = joinedData;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
targetObject[operation.as] = null;
|
|
112
|
+
}
|
|
45
113
|
}
|
|
46
114
|
else {
|
|
47
|
-
transformed[
|
|
115
|
+
transformed[operation.as] = hasAnyData ? joinedData : null;
|
|
48
116
|
}
|
|
49
117
|
}
|
|
50
118
|
else {
|
|
51
|
-
transformed[
|
|
119
|
+
transformed[operation.as] = hasAnyData ? joinedData : null;
|
|
52
120
|
}
|
|
53
121
|
}
|
|
54
|
-
else {
|
|
55
|
-
transformed[join.as] = hasAnyData ? joinedData : null;
|
|
56
|
-
}
|
|
57
122
|
}
|
|
58
123
|
for (const joinMany of joinManyOperations) {
|
|
59
124
|
const jsonValue = row[joinMany.as];
|
|
@@ -80,8 +145,8 @@ export function transformJoinResults(rows, operations) {
|
|
|
80
145
|
transformed[joinMany.as] = parsedValue;
|
|
81
146
|
}
|
|
82
147
|
}
|
|
83
|
-
for (const
|
|
84
|
-
const jsonValue = row[
|
|
148
|
+
for (const joinThroughMany of joinThroughManyOperations) {
|
|
149
|
+
const jsonValue = row[joinThroughMany.as];
|
|
85
150
|
let parsedValue;
|
|
86
151
|
if (jsonValue !== null && jsonValue !== undefined) {
|
|
87
152
|
parsedValue = typeof jsonValue === 'string'
|
|
@@ -91,18 +156,20 @@ export function transformJoinResults(rows, operations) {
|
|
|
91
156
|
else {
|
|
92
157
|
parsedValue = [];
|
|
93
158
|
}
|
|
94
|
-
if (
|
|
95
|
-
const [tableAlias] =
|
|
159
|
+
if (joinThroughMany.localField.includes('.')) {
|
|
160
|
+
const [tableAlias] = joinThroughMany.localField.split('.');
|
|
96
161
|
const relatedJoin = joinOperations.find(j => j.as === tableAlias);
|
|
97
|
-
|
|
98
|
-
|
|
162
|
+
const relatedJoinThrough = joinThroughOperations.find(j => j.as === tableAlias);
|
|
163
|
+
if ((relatedJoin && transformed[relatedJoin.as]) || (relatedJoinThrough && transformed[relatedJoinThrough.as])) {
|
|
164
|
+
const targetAlias = relatedJoin ? relatedJoin.as : relatedJoinThrough.as;
|
|
165
|
+
transformed[targetAlias][joinThroughMany.as] = parsedValue;
|
|
99
166
|
}
|
|
100
167
|
else {
|
|
101
|
-
transformed[
|
|
168
|
+
transformed[joinThroughMany.as] = parsedValue;
|
|
102
169
|
}
|
|
103
170
|
}
|
|
104
171
|
else {
|
|
105
|
-
transformed[
|
|
172
|
+
transformed[joinThroughMany.as] = parsedValue;
|
|
106
173
|
}
|
|
107
174
|
}
|
|
108
175
|
return transformed;
|