Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: move query generation code in orUpdate into getQuery #7880

Merged
merged 1 commit into from
Jul 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
91 changes: 63 additions & 28 deletions src/query-builder/InsertQueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {

/**
* Adds additional ON CONFLICT statement supported in postgres and cockroach.
*
* @deprecated Use `orIgnore` or `orUpdate`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did this note is telling us to use orUpdate instead if orUpdate is marked as deprecated as well? I didn't get it

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like only one of 3 overloads of orUpdate is deprecated.
Although I think this deprecated overload could just be removed instead - new overload will not break old code because it adds just one union type to first parameter.

*/
onConflict(statement: string): this {
this.expressionMap.onConflict = statement;
Expand All @@ -272,31 +274,36 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
* Adds additional ignore statement supported in databases.
*/
orIgnore(statement: string | boolean = true): this {
this.expressionMap.onIgnore = statement;
this.expressionMap.onIgnore = !!statement;
return this;
}

/**
* @deprecated
*/
orUpdate(statement?: { columns?: string[], overwrite?: string[], conflict_target?: string | string[] }): this;

orUpdate(overwrite: string[], conflictTarget?: string | string[]): this;

/**
* Adds additional update statement supported in databases.
*/
orUpdate(statement?: { columns?: string[], overwrite?: string[], conflict_target?: string | string[] }): this {
this.expressionMap.onUpdate = {};
if (statement && Array.isArray(statement.conflict_target))
this.expressionMap.onUpdate.conflict = ` ( ${statement.conflict_target.map((columnName) => this.escape(columnName)).join(", ")} ) `;
if (statement && typeof statement.conflict_target === "string")
this.expressionMap.onUpdate.conflict = ` ON CONSTRAINT ${this.escape(statement.conflict_target)} `;
if (statement && Array.isArray(statement.columns))
this.expressionMap.onUpdate.columns = statement.columns.map(column => `${this.escape(column)} = :${column}`).join(", ");
if (statement && Array.isArray(statement.overwrite)) {
if (this.connection.driver instanceof MysqlDriver || this.connection.driver instanceof AuroraDataApiDriver) {
this.expressionMap.onUpdate.overwrite = statement.overwrite.map(column => `${column} = VALUES(${column})`).join(", ");
} 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 => `${this.escape(column)} = EXCLUDED.${this.escape(column)}`).join(", ");
orUpdate(statementOrOverwrite?: { columns?: string[], overwrite?: string[], conflict_target?: string | string[] } | string[], conflictTarget?: string | string[]): this {
if (!Array.isArray(statementOrOverwrite)) {
this.expressionMap.onUpdate = {
conflict: statementOrOverwrite?.conflict_target,
columns: statementOrOverwrite?.columns,
overwrite: statementOrOverwrite?.overwrite,
};
return this;
}
}
return this;
}

this.expressionMap.onUpdate = {
overwrite: statementOrOverwrite,
conflict: conflictTarget,
};
return this;
}

// -------------------------------------------------------------------------
// Protected Methods
Expand Down Expand Up @@ -346,18 +353,46 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
}
}
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) {
const { overwrite, columns, conflict } = this.expressionMap.onUpdate;
query += `${columns ? " ON CONFLICT " + conflict + " DO UPDATE SET " + columns : ""}`;
query += `${overwrite ? " ON CONFLICT " + conflict + " DO UPDATE SET " + overwrite : ""}`;
}
} else if (this.connection.driver instanceof MysqlDriver || this.connection.driver instanceof AuroraDataApiDriver) {
if (this.expressionMap.onIgnore) {
query += " ON CONFLICT DO NOTHING ";
} else if (this.expressionMap.onConflict) {
query += ` ON CONFLICT ${this.expressionMap.onConflict} `;
} else if (this.expressionMap.onUpdate) {
const { overwrite, columns, conflict } = this.expressionMap.onUpdate;

let conflictTarget = "ON CONFLICT";

if (Array.isArray(conflict)) {
conflictTarget += ` ( ${conflict.map((column) => this.escape(column)).join(", ")} )`;
} else if (conflict) {
conflictTarget += ` ON CONSTRAINT ${this.escape(conflict)}`;
}

if (Array.isArray(overwrite)) {
query += ` ${conflictTarget} DO UPDATE SET `;
query += overwrite?.map(column => `${this.escape(column)} = EXCLUDED.${this.escape(column)}`).join(", ");
query += " ";
} else if (columns) {
query += ` ${conflictTarget} DO UPDATE SET `;
query += columns.map(column => `${this.escape(column)} = :${column}`).join(", ");
query += " ";
}
}
}

if (this.connection.driver instanceof MysqlDriver || this.connection.driver instanceof AuroraDataApiDriver) {
if (this.expressionMap.onUpdate) {
const { overwrite, columns } = this.expressionMap.onUpdate;
query += `${columns ? " ON DUPLICATE KEY UPDATE " + columns : ""}`;
query += `${overwrite ? " ON DUPLICATE KEY UPDATE " + overwrite : ""}`;
const { overwrite, columns } = this.expressionMap.onUpdate;

if (Array.isArray(overwrite)) {
query += " ON DUPLICATE KEY UPDATE ";
query += overwrite.map(column => `${this.escape(column)} = VALUES(${this.escape(column)})`).join(", ");
query += " ";
} else if (Array.isArray(columns)) {
query += " ON DUPLICATE KEY UPDATE ";
query += columns.map(column => `${this.escape(column)} = :${column}`).join(", ");
query += " ";
}
}
}

Expand Down
8 changes: 6 additions & 2 deletions src/query-builder/QueryExpressionMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,16 @@ export class QueryExpressionMap {
/**
* Optional on ignore statement used in insertion query in databases.
*/
onIgnore: string|boolean = false;
onIgnore: boolean = false;

/**
* Optional on update statement used in insertion query in databases.
*/
onUpdate: { columns?: string, conflict?: string, overwrite?: string };
onUpdate: {
conflict?: string | string[],
columns?: string[],
overwrite?: string[],
};

/**
* JOIN queries.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,7 @@ describe("query builder > insertion > on conflict", () => {
.insert()
.into(Post)
.values(post2)
.orUpdate({
conflict_target: ['date'],
overwrite: ['title']
})
.orUpdate(['title'], ['date'])
.setParameter("title", post2.title)
.execute();

Expand Down
17 changes: 6 additions & 11 deletions test/github-issues/1780/issue-1780.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,14 @@ describe("github issues > #1780 Support for insertion ignore on duplicate error"
await UserRepository
.createQueryBuilder()
.insert()
.orUpdate({ columns: [ "is_updated" ] })
.setParameter("is_updated", user1.is_updated)
.orUpdate([ "is_updated" ])
.into(User)
.values(user1)
.execute();
await UserRepository
.createQueryBuilder()
.insert()
.orUpdate({ columns: [ "is_updated" ] })
.setParameter("is_updated", user2.is_updated)
.orUpdate([ "is_updated" ])
.into(User)
.values(user2)
.execute();
Expand Down Expand Up @@ -108,16 +106,14 @@ describe("github issues > #1780 Support for insertion ignore on duplicate error"
await UserRepository
.createQueryBuilder()
.insert()
.orUpdate({ columns: [ "is_updated" ], conflict_target: [ "first_name", "last_name" ] })
.setParameter("is_updated", user1.is_updated)
.orUpdate([ "is_updated" ], [ "first_name", "last_name" ])
.into(User)
.values(user1)
.execute();
await UserRepository
.createQueryBuilder()
.insert()
.orUpdate({ columns: [ "is_updated" ], conflict_target: [ "first_name", "last_name" ] })
.setParameter("is_updated", user2.is_updated)
.orUpdate([ "is_updated" ], [ "first_name", "last_name" ])
.into(User)
.values(user2)
.execute();
Expand All @@ -134,15 +130,14 @@ describe("github issues > #1780 Support for insertion ignore on duplicate error"
await UserRepository
.createQueryBuilder()
.insert()
.orUpdate({ columns: [ "is_updated" ], conflict_target: "constraint_unique_idx" })
.setParameter("is_updated", user1.is_updated)
.orUpdate([ "is_updated" ], "constraint_unique_idx")
.into(User)
.values(user1)
.execute();
await UserRepository
.createQueryBuilder()
.insert()
.orUpdate({ columns: [ "is_updated" ], conflict_target: "constraint_unique_idx" })
.orUpdate([ "is_updated" ], "constraint_unique_idx")
.setParameter("is_updated", user2.is_updated)
.into(User)
.values(user2)
Expand Down
2 changes: 1 addition & 1 deletion test/github-issues/182/entity/Post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ export class Post {
@Column({ nullable: false })
status: PostStatus;

}
}
7 changes: 2 additions & 5 deletions test/github-issues/3047/issue-3047.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe("github issues > #3047 Mysqsl on duplicate key update use current value
.insert()
.into(User)
.values(user2)
.orUpdate({ overwrite: ["is_updated"]})
.orUpdate(["is_updated"])
.execute();

let loadedUser = await UserRepository.find();
Expand Down Expand Up @@ -76,10 +76,7 @@ describe("github issues > #3047 Mysqsl on duplicate key update use current value
.insert()
.into(User)
.values(user2)
.orUpdate({
conflict_target: [ "first_name", "last_name" ],
overwrite: ["is_updated"],
})
.orUpdate([ "is_updated" ], [ "first_name", "last_name" ])
.execute();

let loadedUser = await UserRepository.find();
Expand Down
5 changes: 1 addition & 4 deletions test/github-issues/4096/issue-4096.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@ describe("github issues > #4096 SQLite support for orUpdate", () => {
.insert()
.into(User)
.values(user2)
.orUpdate({
conflict_target: [ "email", "username" ],
overwrite: ["bio"],
})
.orUpdate(["bio"], [ "email", "username" ])
.execute();

const users = await UserRepository.find();
Expand Down
5 changes: 1 addition & 4 deletions test/github-issues/4513/issue-4513.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,7 @@ describe("github issues > #4513 CockroachDB support for onConflict", () => {
.insert()
.into(User)
.values(user2)
.orUpdate({
conflict_target: ["name", "email"],
overwrite: ["age"],
})
.orUpdate(["age"], ["name", "email"])
.execute();

await connection.manager.findOne(User, { name: "example", email: "example@example.com" }).should.eventually.be.eql({
Expand Down