From e922f353d34c604238c19d3fa4abc1fdad549e11 Mon Sep 17 00:00:00 2001 From: James Ward Date: Wed, 7 Jul 2021 04:13:50 -0400 Subject: [PATCH] refactor: represent wheres internally as conditions (#7854) this updates the way `QueryBuilder` works to only build the expression strings when we are actually building queries. this means we can more easily separate the concerns between compiling the query and building queries. it also allows us to support database specific `where` features more readily --- src/find-options/FindOperator.ts | 2 +- src/index.ts | 2 +- src/query-builder/Brackets.ts | 8 +- src/query-builder/DeleteQueryBuilder.ts | 10 +- src/query-builder/QueryBuilder.ts | 347 +++++++++++------- src/query-builder/QueryExpressionMap.ts | 3 +- src/query-builder/SelectQueryBuilder.ts | 10 +- src/query-builder/SoftDeleteQueryBuilder.ts | 10 +- src/query-builder/UpdateQueryBuilder.ts | 10 +- src/query-builder/WhereClause.ts | 23 ++ ...xpression.ts => WhereExpressionBuilder.ts} | 4 +- .../select/query-builder-select.ts | 2 +- 12 files changed, 259 insertions(+), 172 deletions(-) create mode 100644 src/query-builder/WhereClause.ts rename src/query-builder/{WhereExpression.ts => WhereExpressionBuilder.ts} (99%) diff --git a/src/find-options/FindOperator.ts b/src/find-options/FindOperator.ts index 6189e31fe1..c12a37c99d 100644 --- a/src/find-options/FindOperator.ts +++ b/src/find-options/FindOperator.ts @@ -91,7 +91,7 @@ export class FindOperator { /** * Gets the Type of this FindOperator */ - get type(): string { + get type(): FindOperatorType { return this._type; } diff --git a/src/index.ts b/src/index.ts index ac8120ef14..345d143614 100644 --- a/src/index.ts +++ b/src/index.ts @@ -121,7 +121,7 @@ export {InsertQueryBuilder} from "./query-builder/InsertQueryBuilder"; export {UpdateQueryBuilder} from "./query-builder/UpdateQueryBuilder"; export {RelationQueryBuilder} from "./query-builder/RelationQueryBuilder"; export {Brackets} from "./query-builder/Brackets"; -export {WhereExpression} from "./query-builder/WhereExpression"; +export {WhereExpressionBuilder} from "./query-builder/WhereExpressionBuilder"; export {InsertResult} from "./query-builder/result/InsertResult"; export {UpdateResult} from "./query-builder/result/UpdateResult"; export {DeleteResult} from "./query-builder/result/DeleteResult"; diff --git a/src/query-builder/Brackets.ts b/src/query-builder/Brackets.ts index fdaf0035f0..f01dab1021 100644 --- a/src/query-builder/Brackets.ts +++ b/src/query-builder/Brackets.ts @@ -1,4 +1,4 @@ -import {WhereExpression} from "./WhereExpression"; +import {WhereExpressionBuilder} from "./WhereExpressionBuilder"; /** * Syntax sugar. @@ -9,13 +9,13 @@ export class Brackets { /** * WHERE expression that will be taken into brackets. */ - whereFactory: (qb: WhereExpression) => any; + whereFactory: (qb: WhereExpressionBuilder) => any; /** * Given WHERE query builder that will build a WHERE expression that will be taken into brackets. */ - constructor(whereFactory: (qb: WhereExpression) => any) { + constructor(whereFactory: (qb: WhereExpressionBuilder) => any) { this.whereFactory = whereFactory; } -} \ No newline at end of file +} diff --git a/src/query-builder/DeleteQueryBuilder.ts b/src/query-builder/DeleteQueryBuilder.ts index 5acc1a71c2..67e85bcab6 100644 --- a/src/query-builder/DeleteQueryBuilder.ts +++ b/src/query-builder/DeleteQueryBuilder.ts @@ -7,7 +7,7 @@ import {Connection} from "../connection/Connection"; import {QueryRunner} from "../query-runner/QueryRunner"; import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver"; import {PostgresDriver} from "../driver/postgres/PostgresDriver"; -import {WhereExpression} from "./WhereExpression"; +import {WhereExpressionBuilder} from "./WhereExpressionBuilder"; import {Brackets} from "./Brackets"; import {DeleteResult} from "./result/DeleteResult"; import {ReturningStatementNotSupportedError} from "../error/ReturningStatementNotSupportedError"; @@ -20,7 +20,7 @@ import {BetterSqlite3Driver} from "../driver/better-sqlite3/BetterSqlite3Driver" /** * Allows to build complex sql queries in a fashion way and execute those queries. */ -export class DeleteQueryBuilder extends QueryBuilder implements WhereExpression { +export class DeleteQueryBuilder extends QueryBuilder implements WhereExpressionBuilder { // ------------------------------------------------------------------------- // Constructor @@ -149,7 +149,7 @@ export class DeleteQueryBuilder extends QueryBuilder implements */ where(where: Brackets|string|((qb: this) => string)|ObjectLiteral|ObjectLiteral[], parameters?: ObjectLiteral): this { this.expressionMap.wheres = []; // don't move this block below since computeWhereParameter can add where expressions - const condition = this.computeWhereParameter(where); + const condition = this.getWhereCondition(where); if (condition) this.expressionMap.wheres = [{ type: "simple", condition: condition }]; if (parameters) @@ -162,7 +162,7 @@ export class DeleteQueryBuilder extends QueryBuilder implements * Additionally you can add parameters used in where expression. */ andWhere(where: Brackets|string|((qb: this) => string)|ObjectLiteral|ObjectLiteral[], parameters?: ObjectLiteral): this { - this.expressionMap.wheres.push({ type: "and", condition: this.computeWhereParameter(where) }); + this.expressionMap.wheres.push({ type: "and", condition: this.getWhereCondition(where) }); if (parameters) this.setParameters(parameters); return this; } @@ -172,7 +172,7 @@ export class DeleteQueryBuilder extends QueryBuilder implements * Additionally you can add parameters used in where expression. */ orWhere(where: Brackets|string|((qb: this) => string)|ObjectLiteral|ObjectLiteral[], parameters?: ObjectLiteral): this { - this.expressionMap.wheres.push({ type: "or", condition: this.computeWhereParameter(where) }); + this.expressionMap.wheres.push({ type: "or", condition: this.getWhereCondition(where) }); if (parameters) this.setParameters(parameters); return this; } diff --git a/src/query-builder/QueryBuilder.ts b/src/query-builder/QueryBuilder.ts index ac97c2cb8d..1a30473e95 100644 --- a/src/query-builder/QueryBuilder.ts +++ b/src/query-builder/QueryBuilder.ts @@ -23,6 +23,7 @@ import {FindOperator} from "../find-options/FindOperator"; import {In} from "../find-options/operator/In"; import {EntityColumnNotFound} from "../error/EntityColumnNotFound"; import { TypeORMError } from "../error"; +import { WhereClause, WhereClauseCondition } from "./WhereClause"; // todo: completely cover query builder with tests // todo: entityOrProperty can be target name. implement proper behaviour if it is. @@ -685,8 +686,11 @@ export abstract class QueryBuilder { protected createWhereExpression() { const conditionsArray = []; - const whereExpression = this.createWhereExpressionString(); - whereExpression.trim() && conditionsArray.push(this.createWhereExpressionString()); + const whereExpression = this.createWhereClausesExpression(this.expressionMap.wheres); + + if (whereExpression.length > 0 && whereExpression !== "1=1") { + conditionsArray.push(this.replacePropertyNames(whereExpression)); + } if (this.expressionMap.mainAlias!.hasMetadata) { const metadata = this.expressionMap.mainAlias!.metadata; @@ -792,20 +796,80 @@ export abstract class QueryBuilder { return columns; } - /** - * Concatenates all added where expressions into one string. - */ - protected createWhereExpressionString(): string { - return this.expressionMap.wheres.map((where, index) => { - switch (where.type) { + protected createWhereClausesExpression(clauses: WhereClause[]): string { + return clauses.map((clause, index) => { + const expression = this.createWhereConditionExpression(clause.condition); + + switch (clause.type) { case "and": - return (index > 0 ? "AND " : "") + this.replacePropertyNames(where.condition); + return (index > 0 ? "AND " : "") + expression; case "or": - return (index > 0 ? "OR " : "") + this.replacePropertyNames(where.condition); - default: - return this.replacePropertyNames(where.condition); + return (index > 0 ? "OR " : "") + expression; + } + + return expression; + }).join(" ").trim(); + } + + /** + * Computes given where argument - transforms to a where string all forms it can take. + */ + protected createWhereConditionExpression(condition: WhereClauseCondition): string { + if (typeof condition === "string") + return condition; + + if (Array.isArray(condition)) { + if (condition.length === 0) { + return "1=1"; + } + + if (condition.length === 1) { + return this.createWhereClausesExpression(condition); } - }).join(" "); + + return "(" + this.createWhereClausesExpression(condition) + ")"; + } + + const { driver } = this.connection; + + switch (condition.operator) { + case "lessThan": + return `${condition.parameters[0]} < ${condition.parameters[1]}`; + case "lessThanOrEqual": + return `${condition.parameters[0]} <= ${condition.parameters[1]}`; + case "moreThan": + return `${condition.parameters[0]} > ${condition.parameters[1]}`; + case "moreThanOrEqual": + return `${condition.parameters[0]} >= ${condition.parameters[1]}`; + case "notEqual": + return `${condition.parameters[0]} != ${condition.parameters[1]}`; + case "equal": + return `${condition.parameters[0]} = ${condition.parameters[1]}`; + case "ilike": + if (driver instanceof PostgresDriver || driver instanceof CockroachDriver) { + return `${condition.parameters[0]} ILIKE ${condition.parameters[1]}`; + } + + return `UPPER(${condition.parameters[0]}) LIKE UPPER(${condition.parameters[1]})`; + case "like": + return `${condition.parameters[0]} LIKE ${condition.parameters[1]}`; + case "between": + return `${condition.parameters[0]} BETWEEN ${condition.parameters[1]} AND ${condition.parameters[2]}`; + case "in": + if (condition.parameters.length <= 1) { + return "0=1"; + } + return `${condition.parameters[0]} IN (${condition.parameters.slice(1).join(", ")})`; + case "any": + return `${condition.parameters[0]} = ANY(${condition.parameters[1]})`; + case "isNull": + return `${condition.parameters[0]} IS NULL`; + + case "not": + return `NOT(${this.createWhereConditionExpression(condition.condition)})`; + } + + throw new TypeError(`Unsupported FindOperator ${FindOperator.constructor.name}`); } /** @@ -988,164 +1052,163 @@ export abstract class QueryBuilder { return paths; } - /** - * Computes given where argument - transforms to a where string all forms it can take. - */ - protected computeWhereParameter(where: string|((qb: this) => string)|Brackets|ObjectLiteral|ObjectLiteral[]) { - if (typeof where === "string") - return where; + protected *getPredicates(where: ObjectLiteral) { + if (this.expressionMap.mainAlias!.hasMetadata) { + const propertyPaths = this.createPropertyPath(this.expressionMap.mainAlias!.metadata, where); - if (where instanceof Brackets) { - const whereQueryBuilder = this.createQueryBuilder(); + for (const propertyPath of propertyPaths) { + const [ alias, aliasPropertyPath, columns ] = this.findColumnsForPropertyPath(propertyPath); - whereQueryBuilder.parentQueryBuilder = this; + for (const column of columns) { + let containedWhere = where; - whereQueryBuilder.expressionMap.mainAlias = this.expressionMap.mainAlias; - whereQueryBuilder.expressionMap.aliasNamePrefixingEnabled = this.expressionMap.aliasNamePrefixingEnabled; - whereQueryBuilder.expressionMap.parameters = this.expressionMap.parameters; - whereQueryBuilder.expressionMap.nativeParameters = this.expressionMap.nativeParameters; + for (const part of aliasPropertyPath) { + if (!containedWhere || !(part in containedWhere)) { + containedWhere = {}; + break; + } - where.whereFactory(whereQueryBuilder as any); + containedWhere = containedWhere[part]; + } - const whereString = whereQueryBuilder.createWhereExpressionString(); - return whereString ? "(" + whereString + ")" : ""; + // Use the correct alias & the property path from the column + const aliasPath = this.expressionMap.aliasNamePrefixingEnabled ? + `${alias.name}.${column.propertyPath}` : + column.propertyPath; - } else if (where instanceof Function) { - return where(this); + const parameterValue = column.getEntityValue(containedWhere, true); - } else if (where instanceof Object) { - const wheres: ObjectLiteral[] = Array.isArray(where) ? where : [where]; - let andConditions: string[]; + yield [aliasPath, parameterValue]; + } + } + } else { + for (const key of Object.keys(where)) { + const parameterValue = where[key]; + const aliasPath = this.expressionMap.aliasNamePrefixingEnabled ? `${this.alias}.${key}` : key; - if (this.expressionMap.mainAlias!.hasMetadata) { - andConditions = wheres.map((where, whereIndex) => { - const propertyPaths = this.createPropertyPath(this.expressionMap.mainAlias!.metadata, where); + yield [aliasPath, parameterValue]; + } + } + } - return propertyPaths.map((propertyPath, propertyIndex) => { - const [ alias, aliasPropertyPath, columns ] = this.findColumnsForPropertyPath(propertyPath); + protected getWherePredicateCondition(aliasPath: string, parameterValue: any): WhereClauseCondition { + if (parameterValue instanceof FindOperator) { + let parameters: any[] = []; + if (parameterValue.useParameter) { + if (parameterValue.objectLiteralParameters) { + this.setParameters(parameterValue.objectLiteralParameters); + } else if (parameterValue.multipleParameters) { + for (const v of parameterValue.value) { + parameters.push(this.createParameter(v)); + } + } else { + parameters.push(this.createParameter(parameterValue.value)); + } + } - return columns.map((column, columnIndex) => { + if (parameterValue.type === "raw") { + if (parameterValue.getSql) { + return parameterValue.getSql(aliasPath); + } else { + return { + operator: "equal", + parameters: [ + aliasPath, + parameterValue.value, + ] + }; + } + } else if (parameterValue.type === "not") { + if (parameterValue.child) { + return { + operator: parameterValue.type, + condition: this.getWherePredicateCondition(aliasPath, parameterValue.child), + }; + } else { + return { + operator: "notEqual", + parameters: [ + aliasPath, + ...parameters, + ] + }; + } + } else { + return { + operator: parameterValue.type, + parameters: [ + aliasPath, + ...parameters, + ] + }; + } + } else if (parameterValue === null) { + return { + operator: "isNull", + parameters: [ + aliasPath, + ] + }; + } else { + return { + operator: "equal", + parameters: [ + aliasPath, + this.createParameter(parameterValue), + ] + }; + } + } - // Use the correct alias & the property path from the column - const aliasPath = this.expressionMap.aliasNamePrefixingEnabled ? `${alias.name}.${column.propertyPath}` : column.propertyPath; + protected getWhereCondition(where: string|((qb: this) => string)|Brackets|ObjectLiteral|ObjectLiteral[]): WhereClauseCondition { + if (typeof where === "string") { + return where; + } - let containedWhere = where; + if (where instanceof Brackets) { + const whereQueryBuilder = this.createQueryBuilder(); - for (const part of aliasPropertyPath) { - if (!containedWhere || !(part in containedWhere)) { - containedWhere = {}; - break; - } + whereQueryBuilder.parentQueryBuilder = this; - containedWhere = containedWhere[part]; - } + whereQueryBuilder.expressionMap.mainAlias = this.expressionMap.mainAlias; + whereQueryBuilder.expressionMap.aliasNamePrefixingEnabled = this.expressionMap.aliasNamePrefixingEnabled; + whereQueryBuilder.expressionMap.parameters = this.expressionMap.parameters; + whereQueryBuilder.expressionMap.nativeParameters = this.expressionMap.nativeParameters; - let parameterValue = column.getEntityValue(containedWhere, true); + whereQueryBuilder.expressionMap.wheres = []; - if (parameterValue === null) { - return `${aliasPath} IS NULL`; + where.whereFactory(whereQueryBuilder as any); - } else if (parameterValue instanceof FindOperator) { - let parameters: any[] = []; - if (parameterValue.useParameter) { - if (parameterValue.objectLiteralParameters) { - this.setParameters(parameterValue.objectLiteralParameters); - } else { - const realParameterValues: any[] = parameterValue.multipleParameters ? parameterValue.value : [parameterValue.value]; - realParameterValues.forEach((realParameterValue, realParameterValueIndex) => { + return whereQueryBuilder.expressionMap.wheres; + } - const parameterName = this.createParameter(realParameterValue); - parameters.push(parameterName); - }); - } - } + if (where instanceof Function) { + return where(this); + } - return this.computeFindOperatorExpression(parameterValue, aliasPath, parameters); - } else { - const parameterName = this.createParameter(parameterValue); - return `${aliasPath} = ${parameterName}`; - } + const wheres: ObjectLiteral[] = Array.isArray(where) ? where : [where]; + const clauses: WhereClause[] = []; - }).filter(expression => !!expression).join(" AND "); - }).filter(expression => !!expression).join(" AND "); - }); + for (const where of wheres) { + const conditions: WhereClauseCondition = []; - } else { - andConditions = wheres.map((where, whereIndex) => { - return Object.keys(where).map((key, parameterIndex) => { - const parameterValue = where[key]; - const aliasPath = this.expressionMap.aliasNamePrefixingEnabled ? `${this.alias}.${key}` : key; - if (parameterValue === null) { - return `${aliasPath} IS NULL`; - - } else { - const parameterName = this.createParameter(parameterValue); - return `${aliasPath} = ${parameterName}`; - } - }).join(" AND "); + // Filter the conditions and set up the parameter values + for (const [aliasPath, parameterValue] of this.getPredicates(where)) { + conditions.push({ + type: "and", + condition: this.getWherePredicateCondition(aliasPath, parameterValue), }); } - if (andConditions.length > 1) - return andConditions.map(where => "(" + where + ")").join(" OR "); + clauses.push({ type: "or", condition: conditions }); - return andConditions.join(""); } - return ""; - } - - /** - * Gets SQL needs to be inserted into final query. - */ - protected computeFindOperatorExpression(operator: FindOperator, aliasPath: string, parameters: any[]): string { - const { driver } = this.connection; - - switch (operator.type) { - case "not": - if (operator.child) { - return `NOT(${this.computeFindOperatorExpression(operator.child, aliasPath, parameters)})`; - } else { - return `${aliasPath} != ${parameters[0]}`; - } - case "lessThan": - return `${aliasPath} < ${parameters[0]}`; - case "lessThanOrEqual": - return `${aliasPath} <= ${parameters[0]}`; - case "moreThan": - return `${aliasPath} > ${parameters[0]}`; - case "moreThanOrEqual": - return `${aliasPath} >= ${parameters[0]}`; - case "equal": - return `${aliasPath} = ${parameters[0]}`; - case "ilike": - if (driver instanceof PostgresDriver || driver instanceof CockroachDriver) { - return `${aliasPath} ILIKE ${parameters[0]}`; - } - - return `UPPER(${aliasPath}) LIKE UPPER(${parameters[0]})`; - case "like": - return `${aliasPath} LIKE ${parameters[0]}`; - case "between": - return `${aliasPath} BETWEEN ${parameters[0]} AND ${parameters[1]}`; - case "in": - if (parameters.length === 0) { - return "0=1"; - } - return `${aliasPath} IN (${parameters.join(", ")})`; - case "any": - return `${aliasPath} = ANY(${parameters[0]})`; - case "isNull": - return `${aliasPath} IS NULL`; - case "raw": - if (operator.getSql) { - return operator.getSql(aliasPath); - } else { - return `${aliasPath} = ${operator.value}`; - } + if (clauses.length === 1) { + return clauses[0].condition; } - throw new TypeError(`Unsupported FindOperator ${FindOperator.constructor.name}`); + return clauses; } /** diff --git a/src/query-builder/QueryExpressionMap.ts b/src/query-builder/QueryExpressionMap.ts index 3ed50677e9..2f49c9e453 100644 --- a/src/query-builder/QueryExpressionMap.ts +++ b/src/query-builder/QueryExpressionMap.ts @@ -11,6 +11,7 @@ import {ColumnMetadata} from "../metadata/ColumnMetadata"; import {RelationMetadata} from "../metadata/RelationMetadata"; import {SelectQueryBuilderOption} from "./SelectQueryBuilderOption"; import { TypeORMError } from "../error"; +import { WhereClause } from "./WhereClause"; /** * Contains all properties of the QueryBuilder that needs to be build a final query. @@ -115,7 +116,7 @@ export class QueryExpressionMap { /** * WHERE queries. */ - wheres: { type: "simple"|"and"|"or", condition: string }[] = []; + wheres: WhereClause[] = []; /** * HAVING queries. diff --git a/src/query-builder/SelectQueryBuilder.ts b/src/query-builder/SelectQueryBuilder.ts index 6bc6bbc878..68a63f51f6 100644 --- a/src/query-builder/SelectQueryBuilder.ts +++ b/src/query-builder/SelectQueryBuilder.ts @@ -26,7 +26,7 @@ import {OrderByCondition} from "../find-options/OrderByCondition"; import {QueryExpressionMap} from "./QueryExpressionMap"; import {EntityTarget} from "../common/EntityTarget"; import {QueryRunner} from "../query-runner/QueryRunner"; -import {WhereExpression} from "./WhereExpression"; +import {WhereExpressionBuilder} from "./WhereExpressionBuilder"; import {Brackets} from "./Brackets"; import {AbstractSqliteDriver} from "../driver/sqlite-abstract/AbstractSqliteDriver"; import {QueryResultCacheOptions} from "../cache/QueryResultCacheOptions"; @@ -43,7 +43,7 @@ import { TypeORMError } from "../error"; /** * Allows to build complex sql queries in a fashion way and execute those queries. */ -export class SelectQueryBuilder extends QueryBuilder implements WhereExpression { +export class SelectQueryBuilder extends QueryBuilder implements WhereExpressionBuilder { // ------------------------------------------------------------------------- // Public Implemented Methods @@ -718,7 +718,7 @@ export class SelectQueryBuilder extends QueryBuilder implements */ where(where: Brackets|string|((qb: this) => string)|ObjectLiteral|ObjectLiteral[], parameters?: ObjectLiteral): this { this.expressionMap.wheres = []; // don't move this block below since computeWhereParameter can add where expressions - const condition = this.computeWhereParameter(where); + const condition = this.getWhereCondition(where); if (condition) this.expressionMap.wheres = [{ type: "simple", condition: condition }]; if (parameters) @@ -731,7 +731,7 @@ export class SelectQueryBuilder extends QueryBuilder implements * Additionally you can add parameters used in where expression. */ andWhere(where: string|Brackets|((qb: this) => string)|ObjectLiteral|ObjectLiteral[], parameters?: ObjectLiteral): this { - this.expressionMap.wheres.push({ type: "and", condition: this.computeWhereParameter(where) }); + this.expressionMap.wheres.push({ type: "and", condition: this.getWhereCondition(where) }); if (parameters) this.setParameters(parameters); return this; } @@ -741,7 +741,7 @@ export class SelectQueryBuilder extends QueryBuilder implements * Additionally you can add parameters used in where expression. */ orWhere(where: Brackets|string|((qb: this) => string)|ObjectLiteral|ObjectLiteral[], parameters?: ObjectLiteral): this { - this.expressionMap.wheres.push({ type: "or", condition: this.computeWhereParameter(where) }); + this.expressionMap.wheres.push({ type: "or", condition: this.getWhereCondition(where) }); if (parameters) this.setParameters(parameters); return this; } diff --git a/src/query-builder/SoftDeleteQueryBuilder.ts b/src/query-builder/SoftDeleteQueryBuilder.ts index c032d7a37d..3bb822635d 100644 --- a/src/query-builder/SoftDeleteQueryBuilder.ts +++ b/src/query-builder/SoftDeleteQueryBuilder.ts @@ -6,7 +6,7 @@ import {Connection} from "../connection/Connection"; import {QueryRunner} from "../query-runner/QueryRunner"; import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver"; import {PostgresDriver} from "../driver/postgres/PostgresDriver"; -import {WhereExpression} from "./WhereExpression"; +import {WhereExpressionBuilder} from "./WhereExpressionBuilder"; import {Brackets} from "./Brackets"; import {UpdateResult} from "./result/UpdateResult"; import {ReturningStatementNotSupportedError} from "../error/ReturningStatementNotSupportedError"; @@ -24,7 +24,7 @@ import { TypeORMError } from "../error"; /** * Allows to build complex sql queries in a fashion way and execute those queries. */ -export class SoftDeleteQueryBuilder extends QueryBuilder implements WhereExpression { +export class SoftDeleteQueryBuilder extends QueryBuilder implements WhereExpressionBuilder { // ------------------------------------------------------------------------- // Constructor @@ -153,7 +153,7 @@ export class SoftDeleteQueryBuilder extends QueryBuilder impleme */ where(where: string|((qb: this) => string)|Brackets|ObjectLiteral|ObjectLiteral[], parameters?: ObjectLiteral): this { this.expressionMap.wheres = []; // don't move this block below since computeWhereParameter can add where expressions - const condition = this.computeWhereParameter(where); + const condition = this.getWhereCondition(where); if (condition) this.expressionMap.wheres = [{ type: "simple", condition: condition }]; if (parameters) @@ -166,7 +166,7 @@ export class SoftDeleteQueryBuilder extends QueryBuilder impleme * Additionally you can add parameters used in where expression. */ andWhere(where: string|((qb: this) => string)|Brackets|ObjectLiteral|ObjectLiteral[], parameters?: ObjectLiteral): this { - this.expressionMap.wheres.push({ type: "and", condition: this.computeWhereParameter(where) }); + this.expressionMap.wheres.push({ type: "and", condition: this.getWhereCondition(where) }); if (parameters) this.setParameters(parameters); return this; } @@ -176,7 +176,7 @@ export class SoftDeleteQueryBuilder extends QueryBuilder impleme * Additionally you can add parameters used in where expression. */ orWhere(where: string|((qb: this) => string)|Brackets|ObjectLiteral|ObjectLiteral[], parameters?: ObjectLiteral): this { - this.expressionMap.wheres.push({ type: "or", condition: this.computeWhereParameter(where) }); + this.expressionMap.wheres.push({ type: "or", condition: this.getWhereCondition(where) }); if (parameters) this.setParameters(parameters); return this; } diff --git a/src/query-builder/UpdateQueryBuilder.ts b/src/query-builder/UpdateQueryBuilder.ts index 61a66cfd00..3976d9ca8b 100644 --- a/src/query-builder/UpdateQueryBuilder.ts +++ b/src/query-builder/UpdateQueryBuilder.ts @@ -7,7 +7,7 @@ import {Connection} from "../connection/Connection"; import {QueryRunner} from "../query-runner/QueryRunner"; import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver"; import {PostgresDriver} from "../driver/postgres/PostgresDriver"; -import {WhereExpression} from "./WhereExpression"; +import {WhereExpressionBuilder} from "./WhereExpressionBuilder"; import {Brackets} from "./Brackets"; import {UpdateResult} from "./result/UpdateResult"; import {ReturningStatementNotSupportedError} from "../error/ReturningStatementNotSupportedError"; @@ -27,7 +27,7 @@ import { TypeORMError } from "../error"; /** * Allows to build complex sql queries in a fashion way and execute those queries. */ -export class UpdateQueryBuilder extends QueryBuilder implements WhereExpression { +export class UpdateQueryBuilder extends QueryBuilder implements WhereExpressionBuilder { // ------------------------------------------------------------------------- // Constructor @@ -177,7 +177,7 @@ export class UpdateQueryBuilder extends QueryBuilder implements */ where(where: string|((qb: this) => string)|Brackets|ObjectLiteral|ObjectLiteral[], parameters?: ObjectLiteral): this { this.expressionMap.wheres = []; // don't move this block below since computeWhereParameter can add where expressions - const condition = this.computeWhereParameter(where); + const condition = this.getWhereCondition(where); if (condition) this.expressionMap.wheres = [{ type: "simple", condition: condition }]; if (parameters) @@ -190,7 +190,7 @@ export class UpdateQueryBuilder extends QueryBuilder implements * Additionally you can add parameters used in where expression. */ andWhere(where: string|((qb: this) => string)|Brackets|ObjectLiteral|ObjectLiteral[], parameters?: ObjectLiteral): this { - this.expressionMap.wheres.push({ type: "and", condition: this.computeWhereParameter(where) }); + this.expressionMap.wheres.push({ type: "and", condition: this.getWhereCondition(where) }); if (parameters) this.setParameters(parameters); return this; } @@ -200,7 +200,7 @@ export class UpdateQueryBuilder extends QueryBuilder implements * Additionally you can add parameters used in where expression. */ orWhere(where: string|((qb: this) => string)|Brackets|ObjectLiteral|ObjectLiteral[], parameters?: ObjectLiteral): this { - this.expressionMap.wheres.push({ type: "or", condition: this.computeWhereParameter(where) }); + this.expressionMap.wheres.push({ type: "or", condition: this.getWhereCondition(where) }); if (parameters) this.setParameters(parameters); return this; } diff --git a/src/query-builder/WhereClause.ts b/src/query-builder/WhereClause.ts new file mode 100644 index 0000000000..1a239b16b8 --- /dev/null +++ b/src/query-builder/WhereClause.ts @@ -0,0 +1,23 @@ +type WrappingOperator = "not"; + +type PredicateOperator = "lessThan" | "lessThanOrEqual" | "moreThan" | "moreThanOrEqual" | "equal" | "notEqual" | "ilike" | "like" | "between" | "in" | "any" | "isNull"; + +export interface WherePredicateOperator { + operator: PredicateOperator; + + parameters: string[]; +} + +export interface WhereWrappingOperator { + operator: WrappingOperator; + + condition: WhereClauseCondition; +} + +export interface WhereClause { + type: "simple" | "and" | "or"; + + condition: WhereClauseCondition; +} + +export type WhereClauseCondition = string | WherePredicateOperator | WhereWrappingOperator | WhereClause[]; diff --git a/src/query-builder/WhereExpression.ts b/src/query-builder/WhereExpressionBuilder.ts similarity index 99% rename from src/query-builder/WhereExpression.ts rename to src/query-builder/WhereExpressionBuilder.ts index 268ac6efe7..0350d1a9c0 100644 --- a/src/query-builder/WhereExpression.ts +++ b/src/query-builder/WhereExpressionBuilder.ts @@ -4,7 +4,7 @@ import {Brackets} from "./Brackets"; /** * Query Builders can implement this interface to support where expression */ -export interface WhereExpression { +export interface WhereExpressionBuilder { /** * Sets WHERE condition in the query builder. @@ -137,4 +137,4 @@ export interface WhereExpression { orWhereInIds(ids: any|any[]): this; -} \ 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 56b3c32cf6..6fe1f385cd 100644 --- a/test/functional/query-builder/select/query-builder-select.ts +++ b/test/functional/query-builder/select/query-builder-select.ts @@ -430,7 +430,7 @@ describe("query builder > select", () => { '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.id = ?)' ) expect(params).to.eql([ "foo", 1, "bar", 2, 5 ]) })))