Skip to content

Commit

Permalink
fix: RelationIdLoader has access to queryPlanner when wrapped in tran…
Browse files Browse the repository at this point in the history
…saction (#9990)

fix issue - createQueryBuilder in repository loses queryplanner when wrapped in txn upsteam until RelationIdLoader

Closes: #9988
  • Loading branch information
riqwan committed May 9, 2023
1 parent 5be20e2 commit 21a9d67
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 6 deletions.
14 changes: 9 additions & 5 deletions src/query-builder/RelationIdLoader.ts
Expand Up @@ -4,6 +4,7 @@ import { DataSource } from "../data-source/DataSource"
import { ObjectLiteral } from "../common/ObjectLiteral"
import { SelectQueryBuilder } from "./SelectQueryBuilder"
import { DriverUtils } from "../driver/DriverUtils"
import { QueryRunner } from "../query-runner/QueryRunner"

/**
* Loads relation ids for the given entities.
Expand All @@ -13,7 +14,10 @@ export class RelationIdLoader {
// Constructor
// -------------------------------------------------------------------------

constructor(private connection: DataSource) {}
constructor(
private connection: DataSource,
protected queryRunner?: QueryRunner | undefined,
) {}

// -------------------------------------------------------------------------
// Public Methods
Expand Down Expand Up @@ -80,7 +84,7 @@ export class RelationIdLoader {
relatedEntityOrEntities = await this.connection.relationLoader.load(
relation,
entitiesOrEntities,
undefined,
this.queryRunner,
queryBuilder,
)
if (!relatedEntityOrEntities.length)
Expand Down Expand Up @@ -242,7 +246,7 @@ export class RelationIdLoader {
const inverseColumns = relation.isOwning
? junctionMetadata.inverseColumns
: junctionMetadata.ownerColumns
const qb = this.connection.createQueryBuilder()
const qb = this.connection.createQueryBuilder(this.queryRunner)

// select all columns from junction table
columns.forEach((column) => {
Expand Down Expand Up @@ -486,7 +490,7 @@ export class RelationIdLoader {
}

// select all columns we need
const qb = this.connection.createQueryBuilder()
const qb = this.connection.createQueryBuilder(this.queryRunner)
relation.entityMetadata.primaryColumns.forEach((primaryColumn) => {
const columnName = DriverUtils.buildAlias(
this.connection.driver,
Expand Down Expand Up @@ -625,7 +629,7 @@ export class RelationIdLoader {
const mainAlias = relation.entityMetadata.targetName

// select all columns we need
const qb = this.connection.createQueryBuilder()
const qb = this.connection.createQueryBuilder(this.queryRunner)
relation.entityMetadata.primaryColumns.forEach((primaryColumn) => {
const columnName = DriverUtils.buildAlias(
this.connection.driver,
Expand Down
6 changes: 5 additions & 1 deletion src/query-builder/SelectQueryBuilder.ts
Expand Up @@ -8,6 +8,7 @@ import { JoinAttribute } from "./JoinAttribute"
import { RelationIdAttribute } from "./relation-id/RelationIdAttribute"
import { RelationCountAttribute } from "./relation-count/RelationCountAttribute"
import { RelationIdLoader } from "./relation-id/RelationIdLoader"
import { RelationIdLoader as QueryStrategyRelationIdLoader } from "./RelationIdLoader"
import { RelationIdMetadataToAttributeTransformer } from "./relation-id/RelationIdMetadataToAttributeTransformer"
import { RelationCountLoader } from "./relation-count/RelationCountLoader"
import { RelationCountMetadataToAttributeTransformer } from "./relation-count/RelationCountMetadataToAttributeTransformer"
Expand Down Expand Up @@ -3572,6 +3573,9 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
}

if (this.expressionMap.relationLoadStrategy === "query") {
const queryStrategyRelationIdLoader =
new QueryStrategyRelationIdLoader(this.connection, queryRunner)

await Promise.all(
this.relationMetadatas.map(async (relation) => {
const relationTarget = relation.inverseEntityMetadata.target
Expand Down Expand Up @@ -3617,7 +3621,7 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
})
if (entities.length > 0) {
const relatedEntityGroups: any[] =
await this.connection.relationIdLoader.loadManyToManyRelationIdsAndGroup(
await queryStrategyRelationIdLoader.loadManyToManyRelationIdsAndGroup(
relation,
entities,
undefined,
Expand Down
11 changes: 11 additions & 0 deletions test/github-issues/9988/entity/category.ts
@@ -0,0 +1,11 @@
import { Entity, ManyToMany, PrimaryGeneratedColumn } from "../../../../src"
import { Product } from "./product"

@Entity({ name: "category" })
export class Category {
@PrimaryGeneratedColumn()
id: number

@ManyToMany(() => Product, (product) => product.categories)
products: Product[]
}
17 changes: 17 additions & 0 deletions test/github-issues/9988/entity/product.ts
@@ -0,0 +1,17 @@
import {
Entity,
JoinTable,
ManyToMany,
PrimaryGeneratedColumn,
} from "../../../../src"
import { Category } from "./category"

@Entity({ name: "product" })
export class Product {
@PrimaryGeneratedColumn()
id: number

@ManyToMany(() => Category, (category) => category.products)
@JoinTable({ name: "product_category" })
categories: Category[]
}
98 changes: 98 additions & 0 deletions test/github-issues/9988/issue-9988.ts
@@ -0,0 +1,98 @@
import "reflect-metadata"
import {
createTestingConnections as createTestingDatasources,
closeTestingConnections,
reloadTestingDatabases,
} from "../../utils/test-utils"
import { DataSource } from "../../../src/data-source/DataSource"
import { expect } from "chai"
import { Product } from "./entity/product"
import { Category } from "./entity/category"

describe("github issues > #9988 RelationIdLoader reuses the same queryplanner within a transaction", () => {
let dataSources: DataSource[]

before(
async () =>
(dataSources = await createTestingDatasources({
entities: [Product, Category],
enabledDrivers: ["postgres"],
schemaCreate: true,
dropSchema: true,
})),
)

beforeEach(() => reloadTestingDatabases(dataSources))
after(() => closeTestingConnections(dataSources))

it("custom repository querybuilders within transactions returns relations for getOne() and getMany", async () => {
await Promise.all(
dataSources.map(async (dataSource) => {
const manager = dataSource.manager

// Setup seed
const categoryRepo = manager.getRepository(Category)
const categoryOne = categoryRepo.create({ id: 1 })
const categoryTwo = categoryRepo.create({ id: 2 })
const productOneId = 1
const productTwoId = 2
await categoryRepo.save(categoryOne)
await categoryRepo.save(categoryTwo)
const options = (id: number) => ({
relationLoadStrategy: "query",
where: { id: productOneId },
relations: { categories: true },
})

// Create a custom repository that uses a query builder without query planner
// For both methods, relationLoadStrategy is set to "query", where the bug lies.
const productRepo = dataSource.getRepository(Product).extend({
async getOne(): Promise<Product> {
return this.createQueryBuilder("product")
.setFindOptions(options(productOneId))
.getOne()
},

async getMany(): Promise<Product[]> {
return this.createQueryBuilder("product")
.setFindOptions(options(productTwoId))
.getMany()
},
})

// Creates a transaction that is shared across all the queries
const getOneProduct = await manager.transaction(
async (txnManager) => {
const customProductRepo =
txnManager.withRepository(productRepo)
const product = customProductRepo.create({
id: productOneId,
categories: [{ id: categoryOne.id }],
})

await customProductRepo.save(product)
return await customProductRepo.getOne()
},
)

expect(getOneProduct.categories.length).to.be.eql(1)

const getManyProduct = await manager.transaction(
async (txnManager) => {
const customProductRepo =
txnManager.withRepository(productRepo)
const product = customProductRepo.create({
id: productTwoId,
categories: [{ id: categoryOne.id }],
})

await customProductRepo.save(product)
return await customProductRepo.getMany()
},
)

expect(getManyProduct[0].categories.length).to.be.eql(1)
}),
)
})
})

0 comments on commit 21a9d67

Please sign in to comment.