diff --git a/src/query-builder/QueryBuilder.ts b/src/query-builder/QueryBuilder.ts index d55a32a5d9..3ad96a1274 100644 --- a/src/query-builder/QueryBuilder.ts +++ b/src/query-builder/QueryBuilder.ts @@ -819,7 +819,7 @@ export abstract class QueryBuilder { /** * Computes given where argument - transforms to a where string all forms it can take. */ - protected createWhereConditionExpression(condition: WhereClauseCondition): string { + protected createWhereConditionExpression(condition: WhereClauseCondition, alwaysWrap: boolean = false): string { if (typeof condition === "string") return condition; @@ -828,7 +828,9 @@ export abstract class QueryBuilder { return "1=1"; } - if (condition.length === 1) { + // In the future we should probably remove this entire condition + // but for now to prevent any breaking changes it exists. + if (condition.length === 1 && !alwaysWrap) { return this.createWhereClausesExpression(condition); } @@ -872,6 +874,8 @@ export abstract class QueryBuilder { case "not": return `NOT(${this.createWhereConditionExpression(condition.condition)})`; + case "brackets": + return `${this.createWhereConditionExpression(condition.condition, true)}`; } throw new TypeError(`Unsupported FindOperator ${FindOperator.constructor.name}`); @@ -1184,7 +1188,10 @@ export abstract class QueryBuilder { where.whereFactory(whereQueryBuilder as any); - return whereQueryBuilder.expressionMap.wheres; + return { + operator: "brackets", + condition: whereQueryBuilder.expressionMap.wheres + }; } if (where instanceof Function) { diff --git a/src/query-builder/WhereClause.ts b/src/query-builder/WhereClause.ts index 1a239b16b8..47b6b0fa28 100644 --- a/src/query-builder/WhereClause.ts +++ b/src/query-builder/WhereClause.ts @@ -1,4 +1,4 @@ -type WrappingOperator = "not"; +type WrappingOperator = "not" | "brackets"; type PredicateOperator = "lessThan" | "lessThanOrEqual" | "moreThan" | "moreThanOrEqual" | "equal" | "notEqual" | "ilike" | "like" | "between" | "in" | "any" | "isNull"; diff --git a/test/functional/query-builder/brackets/query-builder-brackets.ts b/test/functional/query-builder/brackets/query-builder-brackets.ts index 5d653c6179..c249d3af91 100644 --- a/test/functional/query-builder/brackets/query-builder-brackets.ts +++ b/test/functional/query-builder/brackets/query-builder-brackets.ts @@ -6,14 +6,44 @@ import {User} from "./entity/User"; import {Brackets} from "../../../../src/query-builder/Brackets"; describe("query builder > brackets", () => { - + let connections: Connection[]; before(async () => connections = await createTestingConnections({ entities: [__dirname + "/entity/*{.js,.ts}"], + enabledDrivers: [ "sqlite" ], })); beforeEach(() => reloadTestingDatabases(connections)); after(() => closeTestingConnections(connections)); + it("should put parentheses in the SQL", () => Promise.all(connections.map(async connection => { + const sql = await connection.createQueryBuilder(User, "user") + .where("user.isAdmin = :isAdmin", { isAdmin: true }) + .orWhere(new Brackets(qb => { + qb.where("user.firstName = :firstName1", { firstName1: "Hello" }) + .andWhere("user.lastName = :lastName1", { lastName1: "Mars" }); + })) + .orWhere(new Brackets(qb => { + qb.where("user.firstName = :firstName2", { firstName2: "Hello" }) + .andWhere("user.lastName = :lastName2", { lastName2: "Earth" }); + })) + .andWhere(new Brackets(qb => { + qb.where("user.firstName = :firstName3 AND foo = bar", { firstName3: "Hello" }) + + })) + .disableEscaping() + .getSql() + + expect(sql).to.be.equal( + "SELECT user.id AS user_id, user.firstName AS user_firstName, " + + "user.lastName AS user_lastName, user.isAdmin AS user_isAdmin " + + "FROM user user " + + "WHERE user.isAdmin = ? " + + "OR (user.firstName = ? AND user.lastName = ?) " + + "OR (user.firstName = ? AND user.lastName = ?) " + + "AND (user.firstName = ? AND foo = bar)" + ) + }))); + it("should put brackets correctly into WHERE expression", () => Promise.all(connections.map(async connection => { const user1 = new User(); @@ -50,4 +80,4 @@ describe("query builder > brackets", () => { }))); -}); \ No newline at end of file +}); diff --git a/test/functional/query-builder/select/query-builder-select.ts b/test/functional/query-builder/select/query-builder-select.ts index 6fe1f385cd..9c16adfa16 100644 --- a/test/functional/query-builder/select/query-builder-select.ts +++ b/test/functional/query-builder/select/query-builder-select.ts @@ -412,9 +412,9 @@ describe("query builder > select", () => { expect(sql).to.equal( 'SELECT post.id AS post_id FROM external_post post WHERE ' + - '((post.outlet = ? AND post.id = ?) OR ' + - '(post.outlet = ? AND post.id = ?) OR ' + - '(post.outlet = ? AND post.id = ?))' + '(((post.outlet = ? AND post.id = ?)) OR ' + + '((post.outlet = ? AND post.id = ?)) OR ' + + '((post.outlet = ? AND post.id = ?)))' ) expect(params).to.eql([ "foo", 1, "bar", 2, "baz", 5 ]) }))) @@ -428,9 +428,9 @@ describe("query builder > select", () => { expect(sql).to.equal( 'SELECT post.id AS post_id FROM external_post post WHERE ' + - '((post.outlet = ? AND post.id = ?) OR ' + - '(post.outlet = ? AND post.id = ?) OR ' + - 'post.id = ?)' + '(((post.outlet = ? AND post.id = ?)) OR ' + + '((post.outlet = ? AND post.id = ?)) OR ' + + '(post.id = ?))' ) expect(params).to.eql([ "foo", 1, "bar", 2, 5 ]) })))