fix: bulk insert NULL values in Oracle (#11363)

resolve issue with bulk insert of NULL values for non VARCHAR2 types in Oracle.
This commit is contained in:
ertl 2025-04-01 05:35:03 +02:00 committed by GitHub
parent a29e04750d
commit bcaa0bf071
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 50 additions and 73 deletions

View File

@ -874,7 +874,8 @@ export class InsertQueryBuilder<
}
} else if (
value === null &&
this.connection.driver.options.type === "spanner"
(this.connection.driver.options.type === "spanner" ||
this.connection.driver.options.type === "oracle")
) {
expression += "NULL"

View File

@ -9,4 +9,7 @@ export class User {
@Column()
name: string
@Column({ nullable: true })
memberId: number
}

View File

@ -57,9 +57,9 @@ describe("query builder > insert", () => {
},
})
users.should.be.eql([
{ id: 1, name: "Alex Messer" },
{ id: 2, name: "Dima Zotov" },
{ id: 3, name: "Muhammad Mirzoev" },
{ id: 1, name: "Alex Messer", memberId: null },
{ id: 2, name: "Dima Zotov", memberId: null },
{ id: 3, name: "Muhammad Mirzoev", memberId: null },
])
}),
))
@ -67,22 +67,29 @@ describe("query builder > insert", () => {
it("should perform bulk insertion correctly", () =>
Promise.all(
connections.map(async (connection) => {
// it is skipped for Oracle and SAP because it does not support bulk insertion
if (
connection.driver.options.type === "oracle" ||
connection.driver.options.type === "sap"
)
// it is skipped for SAP because it does not support bulk insertion
if (connection.driver.options.type === "sap") {
return
}
// Oracle does not support automatic ID generation for bulk inserts, so we manually assign IDs to avoid issues.
const isOracle = connection.driver.options.type === "oracle"
const values: Partial<User>[] = [
{ name: "Umed Khudoiberdiev", memberId: 1 },
{
name: "Bakhrom Baubekov",
memberId: null,
} as unknown as Partial<User>, // try setting something NULL (see issue #11362)
{ name: "Bakhodur Kandikov", memberId: 3 },
].map((user, index) =>
isOracle ? { id: index + 1, ...user } : user,
)
await connection
.createQueryBuilder()
.insert()
.into(User)
.values([
{ name: "Umed Khudoiberdiev" },
{ name: "Bakhrom Baubekov" },
{ name: "Bakhodur Kandikov" },
])
.values(values)
.execute()
const users = await connection.getRepository(User).find({
@ -91,9 +98,9 @@ describe("query builder > insert", () => {
},
})
users.should.be.eql([
{ id: 1, name: "Umed Khudoiberdiev" },
{ id: 2, name: "Bakhrom Baubekov" },
{ id: 3, name: "Bakhodur Kandikov" },
{ id: 1, name: "Umed Khudoiberdiev", memberId: 1 },
{ id: 2, name: "Bakhrom Baubekov", memberId: null },
{ id: 3, name: "Bakhodur Kandikov", memberId: 3 },
])
}),
))
@ -125,31 +132,37 @@ describe("query builder > insert", () => {
Promise.all(
connections.map(async (connection) => {
// this test is skipped for sqlite based drivers because it does not support DEFAULT values in insertions,
// also it is skipped for Oracle and SAP because it does not support bulk insertion
// also it is skipped for SAP because it does not support bulk insertion
if (
DriverUtils.isSQLiteFamily(connection.driver) ||
connection.driver.options.type === "oracle" ||
connection.driver.options.type === "sap"
)
) {
return
}
// Oracle does not support automatic ID generation for bulk inserts, so we manually assign IDs to avoid issues.
const isOracle = connection.driver.options.type === "oracle"
const values: Partial<Photo>[] = [
{
url: "1.jpg",
counters: {
likes: 1,
favorites: 1,
comments: 1,
},
},
{
url: "2.jpg",
},
].map((photo, index) =>
isOracle ? { id: index + 1, ...photo } : photo,
)
await connection
.createQueryBuilder()
.insert()
.into(Photo)
.values([
{
url: "1.jpg",
counters: {
likes: 1,
favorites: 1,
comments: 1,
},
},
{
url: "2.jpg",
},
])
.values(values)
.execute()
const loadedPhoto1 = await connection

View File

@ -106,46 +106,6 @@ describe("tree tables > materialized-path", () => {
}),
))
it("categories should be attached via children and saved properly", () =>
Promise.all(
connections.map(async (connection) => {
const categoryRepository =
connection.getTreeRepository(Category)
const a1 = new Category()
a1.name = "a1"
await categoryRepository.save(a1)
const a11 = new Category()
a11.name = "a11"
const a12 = new Category()
a12.name = "a12"
a1.childCategories = [a11, a12]
await categoryRepository.save(a1)
const rootCategories = await categoryRepository.findRoots()
rootCategories.should.be.eql([
{
id: 1,
name: "a1",
},
])
const a11Parent = await categoryRepository.findAncestors(a11)
a11Parent.length.should.be.equal(2)
a11Parent.should.deep.include({ id: 1, name: "a1" })
a11Parent.should.deep.include({ id: 2, name: "a11" })
const a1Children = await categoryRepository.findDescendants(a1)
a1Children.length.should.be.equal(3)
a1Children.should.deep.include({ id: 1, name: "a1" })
a1Children.should.deep.include({ id: 2, name: "a11" })
a1Children.should.deep.include({ id: 3, name: "a12" })
}),
))
it("categories should be attached via children and saved properly and everything must be saved in cascades", () =>
Promise.all(
connections.map(async (connection) => {