diff --git a/src/query-builder/InsertQueryBuilder.ts b/src/query-builder/InsertQueryBuilder.ts index a720c14621..f947830457 100644 --- a/src/query-builder/InsertQueryBuilder.ts +++ b/src/query-builder/InsertQueryBuilder.ts @@ -220,7 +220,7 @@ export class InsertQueryBuilder extends QueryBuilder { } /** - * Adds additional ON CONFLICT statement supported in postgres. + * Adds additional ON CONFLICT statement supported in postgres and cockroach. */ onConflict(statement: string): this { this.expressionMap.onConflict = statement; @@ -249,7 +249,7 @@ export class InsertQueryBuilder extends QueryBuilder { if (statement && statement.overwrite instanceof Array) { if (this.connection.driver instanceof MysqlDriver) { this.expressionMap.onUpdate.overwrite = statement.overwrite.map(column => `${column} = VALUES(${column})`).join(", "); - } else if (this.connection.driver instanceof PostgresDriver || this.connection.driver instanceof AbstractSqliteDriver) { + } else if (this.connection.driver instanceof PostgresDriver || this.connection.driver instanceof AbstractSqliteDriver || this.connection.driver instanceof CockroachDriver) { this.expressionMap.onUpdate.overwrite = statement.overwrite.map(column => `${column} = EXCLUDED.${column}`).join(", "); } } @@ -300,7 +300,7 @@ export class InsertQueryBuilder extends QueryBuilder { query += ` DEFAULT VALUES`; } } - if (this.connection.driver instanceof PostgresDriver || this.connection.driver instanceof AbstractSqliteDriver) { + if (this.connection.driver instanceof PostgresDriver || this.connection.driver instanceof AbstractSqliteDriver || this.connection.driver instanceof CockroachDriver) { query += `${this.expressionMap.onIgnore ? " ON CONFLICT DO NOTHING " : ""}`; query += `${this.expressionMap.onConflict ? " ON CONFLICT " + this.expressionMap.onConflict : ""}`; if (this.expressionMap.onUpdate) { diff --git a/test/github-issues/4513/entity/User.ts b/test/github-issues/4513/entity/User.ts new file mode 100644 index 0000000000..6df8684959 --- /dev/null +++ b/test/github-issues/4513/entity/User.ts @@ -0,0 +1,13 @@ +import { Entity, PrimaryColumn, Column } from "../../../../src"; + +@Entity() +export class User { + @PrimaryColumn() + name: string; + + @PrimaryColumn() + email: string; + + @Column() + age: number; +} \ No newline at end of file diff --git a/test/github-issues/4513/issue-4513.ts b/test/github-issues/4513/issue-4513.ts new file mode 100644 index 0000000000..f5d15cd96d --- /dev/null +++ b/test/github-issues/4513/issue-4513.ts @@ -0,0 +1,140 @@ +import "reflect-metadata"; +import { createTestingConnections, closeTestingConnections, reloadTestingDatabases } from "../../utils/test-utils"; +import { Connection } from "../../../src/connection/Connection"; +import { User } from "./entity/User"; + +describe("github issues > #4513 CockroachDB support for onConflict", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchema: true, + enabledDrivers: ["cockroachdb"] + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("should insert if no conflict", () => Promise.all(connections.map(async connection => { + const user1 = new User(); + user1.name = "example"; + user1.email = "example@example.com"; + user1.age = 30; + + await connection.createQueryBuilder() + .insert() + .into(User) + .values(user1) + .execute(); + + const user2 = new User(); + user2.name = "example2"; + user2.email = "example2@example.com"; + user2.age = 42; + + await connection.createQueryBuilder() + .insert() + .into(User) + .values(user2) + .onConflict(`("name", "email") DO NOTHING`) + .execute(); + + await connection.manager.find(User).should.eventually.have.lengthOf(2); + }))); + + it("should update on conflict with do update", () => Promise.all(connections.map(async connection => { + const user1 = new User(); + user1.name = "example"; + user1.email = "example@example.com"; + user1.age = 30; + + await connection.createQueryBuilder() + .insert() + .into(User) + .values(user1) + .execute(); + + const user2 = new User(); + user2.name = "example"; + user2.email = "example@example.com"; + user2.age = 42; + + await connection.createQueryBuilder() + .insert() + .into(User) + .values(user2) + .onConflict(`("name", "email") DO UPDATE SET age = EXCLUDED.age`) + .execute(); + + await connection.manager.findOne(User, { name: "example", email: "example@example.com" }).should.eventually.be.eql({ + name: "example", + email: "example@example.com", + age: 42, + }); + }))); + + it("should not update on conflict with do nothing", () => Promise.all(connections.map(async connection => { + const user1 = new User(); + user1.name = "example"; + user1.email = "example@example.com"; + user1.age = 30; + + await connection.createQueryBuilder() + .insert() + .into(User) + .values(user1) + .execute(); + + const user2 = new User(); + user2.name = "example"; + user2.email = "example@example.com"; + user2.age = 42; + + await connection.createQueryBuilder() + .insert() + .into(User) + .values(user2) + .onConflict(`("name", "email") DO NOTHING`) + .execute(); + + await connection.manager.findOne(User, { name: "example", email: "example@example.com" }).should.eventually.be.eql({ + name: "example", + email: "example@example.com", + age: 30, + }); + }))); + + it("should update with orUpdate", () => Promise.all(connections.map(async connection => { + const user1 = new User(); + user1.name = "example"; + user1.email = "example@example.com"; + user1.age = 30; + + await connection.createQueryBuilder() + .insert() + .into(User) + .values(user1) + .execute(); + + const user2 = new User(); + user2.name = "example"; + user2.email = "example@example.com"; + user2.age = 42; + + await connection.createQueryBuilder() + .insert() + .into(User) + .values(user2) + .orUpdate({ + conflict_target: ["name", "email"], + overwrite: ["age"], + }) + .execute(); + + await connection.manager.findOne(User, { name: "example", email: "example@example.com" }).should.eventually.be.eql({ + name: "example", + email: "example@example.com", + age: 42, + }); + }))); +}); \ No newline at end of file