fix: use parallel relation fetching (#173)
optimizes performance of queries with many left joins
This commit is contained in:
@@ -6,6 +6,7 @@ class MockRepo {
|
||||
softRemove,
|
||||
find,
|
||||
findOne,
|
||||
findOneWithRelations,
|
||||
findOneOrFail,
|
||||
save,
|
||||
findAndCount,
|
||||
@@ -19,6 +20,7 @@ class MockRepo {
|
||||
this.findOneOrFail_ = findOneOrFail;
|
||||
this.save_ = save;
|
||||
this.findAndCount_ = findAndCount;
|
||||
this.findOneWithRelations_ = findOneWithRelations;
|
||||
}
|
||||
|
||||
setFindOne(fn) {
|
||||
@@ -53,6 +55,11 @@ class MockRepo {
|
||||
return this.findOneOrFail_(...args);
|
||||
}
|
||||
});
|
||||
findOneWithRelations = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findOneWithRelations_) {
|
||||
return this.findOneWithRelations_(...args);
|
||||
}
|
||||
});
|
||||
findOne = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findOne_) {
|
||||
return this.findOne_(...args);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,56 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { flatten, groupBy, map, merge } from "lodash"
|
||||
import { EntityRepository, Repository, FindManyOptions } from "typeorm"
|
||||
import { Order } from "../models/order"
|
||||
|
||||
@EntityRepository(Order)
|
||||
export class OrderRepository extends Repository<Order> {}
|
||||
export class OrderRepository extends Repository<Order> {
|
||||
public async findWithRelations(
|
||||
relations: Array<keyof Order> = [],
|
||||
optionsWithoutRelations: Omit<FindManyOptions<Order>, "relations"> = {}
|
||||
): Promise<Order[]> {
|
||||
const entities = await this.find(optionsWithoutRelations)
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
relations.map(relation => {
|
||||
const relationParts = relation.split(".") as string[]
|
||||
|
||||
const partialRelations = relationParts.reduce(
|
||||
(acc: string[], _: string, index: number) => {
|
||||
const toPush = []
|
||||
|
||||
for (let i = 0; i <= index; i++) {
|
||||
toPush.push(relationParts[i])
|
||||
}
|
||||
|
||||
acc.push(toPush.join("."))
|
||||
|
||||
return acc
|
||||
},
|
||||
[] as string[]
|
||||
)
|
||||
|
||||
return this.findByIds(entitiesIds, {
|
||||
select: ["id"],
|
||||
relations: partialRelations,
|
||||
})
|
||||
})
|
||||
).then(flatten)
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
|
||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||
return map(entitiesAndRelationsById, entityAndRelations =>
|
||||
merge({}, ...entityAndRelations)
|
||||
)
|
||||
}
|
||||
|
||||
public async findOneWithRelations(
|
||||
relations: Array<keyof Order> = [],
|
||||
optionsWithoutRelations: Omit<FindManyOptions<Order>, "relations"> = {}
|
||||
): Promise<Order> {
|
||||
const result = await this.findWithRelations(
|
||||
relations,
|
||||
optionsWithoutRelations
|
||||
)
|
||||
return result[0]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +396,7 @@ describe("OrderService", () => {
|
||||
|
||||
describe("retrieve", () => {
|
||||
const orderRepo = MockRepository({
|
||||
findOne: q => {
|
||||
findOneWithRelations: q => {
|
||||
return Promise.resolve({})
|
||||
},
|
||||
})
|
||||
@@ -412,8 +412,8 @@ describe("OrderService", () => {
|
||||
|
||||
it("calls order model functions", async () => {
|
||||
await orderService.retrieve(IdMap.getId("test-order"))
|
||||
expect(orderRepo.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(orderRepo.findOne).toHaveBeenCalledWith({
|
||||
expect(orderRepo.findOneWithRelations).toHaveBeenCalledTimes(1)
|
||||
expect(orderRepo.findOneWithRelations).toHaveBeenCalledWith(undefined, {
|
||||
where: { id: IdMap.getId("test-order") },
|
||||
})
|
||||
})
|
||||
@@ -446,7 +446,7 @@ describe("OrderService", () => {
|
||||
|
||||
describe("update", () => {
|
||||
const orderRepo = MockRepository({
|
||||
findOne: q => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("fulfilled-order"):
|
||||
return Promise.resolve({
|
||||
@@ -527,7 +527,7 @@ describe("OrderService", () => {
|
||||
|
||||
describe("cancel", () => {
|
||||
const orderRepo = MockRepository({
|
||||
findOne: q => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("paid-order"):
|
||||
return Promise.resolve({
|
||||
@@ -606,7 +606,7 @@ describe("OrderService", () => {
|
||||
|
||||
describe("capturePayment", () => {
|
||||
const orderRepo = MockRepository({
|
||||
findOne: q => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("fail"):
|
||||
return Promise.resolve({
|
||||
@@ -718,7 +718,7 @@ describe("OrderService", () => {
|
||||
}
|
||||
|
||||
const orderRepo = MockRepository({
|
||||
findOne: q => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
switch (q.where.id) {
|
||||
case "partial":
|
||||
return Promise.resolve(partialOrder)
|
||||
@@ -870,7 +870,7 @@ describe("OrderService", () => {
|
||||
payments: [{ id: "payment_test" }],
|
||||
}
|
||||
const orderRepo = MockRepository({
|
||||
findOne: q => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
switch (q.where.id) {
|
||||
default:
|
||||
return Promise.resolve(order)
|
||||
@@ -1043,7 +1043,7 @@ describe("OrderService", () => {
|
||||
payments: [{ id: "payment_test" }],
|
||||
}
|
||||
const orderRepo = MockRepository({
|
||||
findOne: q => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
switch (q.where.id) {
|
||||
default:
|
||||
return Promise.resolve(order)
|
||||
@@ -1129,7 +1129,7 @@ describe("OrderService", () => {
|
||||
}
|
||||
|
||||
const orderRepo = MockRepository({
|
||||
findOne: q => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("partial"):
|
||||
return Promise.resolve(partialOrder)
|
||||
@@ -1206,9 +1206,7 @@ describe("OrderService", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
const orderRepo = MockRepository({
|
||||
findOne: jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve({ id: IdMap.getId("order") })),
|
||||
findOneWithRelations: () => Promise.resolve({ id: IdMap.getId("order") }),
|
||||
})
|
||||
|
||||
it("fails if order/swap relationship not satisfied", async () => {
|
||||
@@ -1268,7 +1266,7 @@ describe("OrderService", () => {
|
||||
|
||||
it("registers a swap as received", async () => {
|
||||
const orderRepo = MockRepository({
|
||||
findOne: jest.fn().mockReturnValue(
|
||||
findOneWithRelations: () =>
|
||||
Promise.resolve({
|
||||
id: IdMap.getId("order_123"),
|
||||
items: [
|
||||
@@ -1278,8 +1276,7 @@ describe("OrderService", () => {
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
const swapService = {
|
||||
@@ -1329,7 +1326,7 @@ describe("OrderService", () => {
|
||||
})
|
||||
|
||||
const orderRepo = MockRepository({
|
||||
findOne: jest.fn().mockImplementation(q => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
if (q.where.id === IdMap.getId("cannot")) {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("order"),
|
||||
@@ -1353,7 +1350,7 @@ describe("OrderService", () => {
|
||||
total: 100,
|
||||
refunded_total: 0,
|
||||
})
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
const paymentProviderService = {
|
||||
|
||||
@@ -328,7 +328,7 @@ class OrderService extends BaseService {
|
||||
query.select = select
|
||||
}
|
||||
|
||||
const raw = await orderRepo.findOne(query)
|
||||
const raw = await orderRepo.findOneWithRelations(query.relations, query)
|
||||
|
||||
if (!raw) {
|
||||
throw new MedusaError(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user