QueryBuilder is abstract now and all different kinds of query builders were created for different query types -

`SelectQueryBuilder`, `UpdateQueryBuilder`, `InsertQueryBuilder` and `DeleteQueryBuilder` with individual method available.
This commit is contained in:
Umed Khudoiberdiev 2017-06-20 17:05:50 +05:00
parent a019f03488
commit b5140d245c
23 changed files with 1231 additions and 968 deletions

View File

@ -75,6 +75,8 @@ of `QueryRunner`, e.g. `queryRunner.connection` and `queryRunner.manager`
* refactored how query runner works, removed query runner provider
* fixed some issues with sqlite, sqlite now strongly works on a single connection
* `Connection` how has `createQueryRunner` that can be used to control database connection and its transaction state
* `QueryBuilder` is abtract now and all different kinds of query builders were created for different query types -
`SelectQueryBuilder`, `UpdateQueryBuilder`, `InsertQueryBuilder` and `DeleteQueryBuilder` with individual method available.
### BUG FIXES

View File

@ -30,6 +30,7 @@ import {RepositoryFactory} from "../repository/RepositoryFactory";
import {DriverFactory} from "../driver/DriverFactory";
import {ConnectionMetadataBuilder} from "./ConnectionMetadataBuilder";
import {QueryRunner} from "../query-runner/QueryRunner";
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
/**
* Connection is a single database ORM connection to a specific DBMS database.
@ -368,28 +369,28 @@ export class Connection {
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>|Function|string, alias: string, queryRunner?: QueryRunner): QueryBuilder<Entity>;
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>|Function|string, alias: string, queryRunner?: QueryRunner): SelectQueryBuilder<Entity>;
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder(queryRunner?: QueryRunner): QueryBuilder<any>;
createQueryBuilder(queryRunner?: QueryRunner): SelectQueryBuilder<any>;
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass?: ObjectType<Entity>|Function|string|QueryRunner, alias?: string, queryRunner?: QueryRunner): QueryBuilder<Entity> {
createQueryBuilder<Entity>(entityOrRunner?: ObjectType<Entity>|Function|string|QueryRunner, alias?: string, queryRunner?: QueryRunner): SelectQueryBuilder<Entity> {
if (this instanceof MongoEntityManager)
throw new Error(`Query Builder is not supported by MongoDB.`);
if (alias) {
const metadata = this.getMetadata(entityClass as Function|string);
return new QueryBuilder(this, queryRunner)
const metadata = this.getMetadata(entityOrRunner as Function|string);
return new SelectQueryBuilder(this, queryRunner)
.select(alias)
.from(metadata.target, alias);
} else {
return new QueryBuilder(this, entityClass as QueryRunner|undefined);
return new SelectQueryBuilder(this, entityOrRunner as QueryRunner|undefined);
}
}

View File

@ -1,11 +1,12 @@
import {getMetadataArgsStorage} from "../../index";
import {RelationCountMetadataArgs} from "../../metadata-args/RelationCountMetadataArgs";
import {QueryBuilder} from "../../query-builder/QueryBuilder";
import {SelectQueryBuilder} from "../../query-builder/SelectQueryBuilder";
/**
* Holds a number of children in the closure table of the column.
*/
export function RelationCount<T>(relation: string|((object: T) => any), alias?: string, queryBuilderFactory?: (qb: QueryBuilder<any>) => QueryBuilder<any>): Function {
export function RelationCount<T>(relation: string|((object: T) => any), alias?: string, queryBuilderFactory?: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>): Function {
return function (object: Object, propertyName: string) {
const args: RelationCountMetadataArgs = {
target: object.constructor,

View File

@ -1,11 +1,12 @@
import {getMetadataArgsStorage} from "../../index";
import {RelationIdMetadataArgs} from "../../metadata-args/RelationIdMetadataArgs";
import {QueryBuilder} from "../../query-builder/QueryBuilder";
import {SelectQueryBuilder} from "../../query-builder/SelectQueryBuilder";
/**
* Special decorator used to extract relation id into separate entity property.
*/
export function RelationId<T>(relation: string|((object: T) => any), alias?: string, queryBuilderFactory?: (qb: QueryBuilder<any>) => QueryBuilder<any>): Function {
export function RelationId<T>(relation: string|((object: T) => any), alias?: string, queryBuilderFactory?: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>): Function {
return function (object: Object, propertyName: string) {
const args: RelationIdMetadataArgs = {
target: object.constructor,

View File

@ -23,6 +23,7 @@ import {getMetadataArgsStorage} from "../index";
import {AbstractRepository} from "../repository/AbstractRepository";
import {CustomRepositoryCannotInheritRepositoryError} from "../repository/error/CustomRepositoryCannotInheritRepositoryError";
import {QueryRunner} from "../query-runner/QueryRunner";
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
/**
* Entity manager supposed to work with any entity, automatically find its repository and call its methods,
@ -86,17 +87,17 @@ export class EntityManager {
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>|Function|string, alias: string, queryRunner?: QueryRunner): QueryBuilder<Entity>;
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>|Function|string, alias: string, queryRunner?: QueryRunner): SelectQueryBuilder<Entity>;
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder(queryRunner?: QueryRunner): QueryBuilder<any>;
createQueryBuilder(queryRunner?: QueryRunner): SelectQueryBuilder<any>;
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass?: ObjectType<Entity>|Function|string|QueryRunner, alias?: string, queryRunner?: QueryRunner): QueryBuilder<Entity> {
createQueryBuilder<Entity>(entityClass?: ObjectType<Entity>|Function|string|QueryRunner, alias?: string, queryRunner?: QueryRunner): SelectQueryBuilder<Entity> {
if (alias) {
return this.connection.createQueryBuilder(entityClass as Function|string, alias, queryRunner || this.queryRunner);

View File

@ -1,7 +1,7 @@
import {FindManyOptions} from "./FindManyOptions";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {FindOneOptions} from "./FindOneOptions";
import {ObjectLiteral} from "../common/ObjectLiteral";
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
/**
* Utilities to work with FindOptions.
@ -59,7 +59,7 @@ export class FindOptionsUtils {
/**
* Applies give find one options to the given query builder.
*/
static applyFindOneOptionsOrConditionsToQueryBuilder<T>(qb: QueryBuilder<T>, options: FindOneOptions<T>|Partial<T>|undefined): QueryBuilder<T> {
static applyFindOneOptionsOrConditionsToQueryBuilder<T>(qb: SelectQueryBuilder<T>, options: FindOneOptions<T>|Partial<T>|undefined): SelectQueryBuilder<T> {
if (this.isFindOneOptions(options))
return this.applyOptionsToQueryBuilder(qb, options);
@ -72,7 +72,7 @@ export class FindOptionsUtils {
/**
* Applies give find many options to the given query builder.
*/
static applyFindManyOptionsOrConditionsToQueryBuilder<T>(qb: QueryBuilder<T>, options: FindManyOptions<T>|Partial<T>|undefined): QueryBuilder<T> {
static applyFindManyOptionsOrConditionsToQueryBuilder<T>(qb: SelectQueryBuilder<T>, options: FindManyOptions<T>|Partial<T>|undefined): SelectQueryBuilder<T> {
if (this.isFindManyOptions(options))
return this.applyOptionsToQueryBuilder(qb, options);
@ -85,7 +85,7 @@ export class FindOptionsUtils {
/**
* Applies give find options to the given query builder.
*/
static applyOptionsToQueryBuilder<T>(qb: QueryBuilder<T>, options: FindOneOptions<T>|FindManyOptions<T>|undefined): QueryBuilder<T> {
static applyOptionsToQueryBuilder<T>(qb: SelectQueryBuilder<T>, options: FindOneOptions<T>|FindManyOptions<T>|undefined): SelectQueryBuilder<T> {
// if options are not set then simply return query builder. This is made for simplicity of usage.
if (!options || !this.isFindOneOptions(options))
@ -134,7 +134,7 @@ export class FindOptionsUtils {
/**
* Applies given simple conditions set to a given query builder.
*/
static applyConditions<T>(qb: QueryBuilder<T>, conditions: ObjectLiteral): QueryBuilder<T> {
static applyConditions<T>(qb: SelectQueryBuilder<T>, conditions: ObjectLiteral): SelectQueryBuilder<T> {
Object.keys(conditions).forEach((key, index) => {
if (conditions![key] === null) {
qb.andWhere(`${qb.alias}.${key} IS NULL`);

View File

@ -1,4 +1,5 @@
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
/**
* Arguments for RelationCountMetadata class.
*/
@ -27,6 +28,6 @@ export interface RelationCountMetadataArgs {
/**
* Extra condition applied to "ON" section of join.
*/
readonly queryBuilderFactory?: (qb: QueryBuilder<any>) => QueryBuilder<any>;
readonly queryBuilderFactory?: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>;
}

View File

@ -1,4 +1,5 @@
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
/**
* Arguments for RelationIdMetadataArgs class.
*/
@ -27,6 +28,6 @@ export interface RelationIdMetadataArgs {
/**
* Extra condition applied to "ON" section of join.
*/
readonly queryBuilderFactory?: (qb: QueryBuilder<any>) => QueryBuilder<any>;
readonly queryBuilderFactory?: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>;
}

View File

@ -2,6 +2,7 @@ import {RelationCountMetadataArgs} from "../metadata-args/RelationCountMetadataA
import {EntityMetadata} from "./EntityMetadata";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {RelationMetadata} from "./RelationMetadata";
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
/**
* Contains all information about entity's relation count.
@ -45,7 +46,7 @@ export class RelationCountMetadata {
/**
* Extra condition applied to "ON" section of join.
*/
queryBuilderFactory?: (qb: QueryBuilder<any>) => QueryBuilder<any>;
queryBuilderFactory?: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>;
// ---------------------------------------------------------------------
// Constructor

View File

@ -2,6 +2,7 @@ import {RelationIdMetadataArgs} from "../metadata-args/RelationIdMetadataArgs";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {EntityMetadata} from "./EntityMetadata";
import {RelationMetadata} from "./RelationMetadata";
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
/**
* Contains all information about entity's relation count.
@ -45,7 +46,7 @@ export class RelationIdMetadata {
/**
* Extra condition applied to "ON" section of join.
*/
queryBuilderFactory?: (qb: QueryBuilder<any>) => QueryBuilder<any>;
queryBuilderFactory?: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>;
// ---------------------------------------------------------------------
// Constructor

View File

@ -0,0 +1,57 @@
import {QueryBuilder} from "./QueryBuilder";
import {ObjectLiteral} from "../common/ObjectLiteral";
/**
* Allows to build complex sql queries in a fashion way and execute those queries.
*/
export class DeleteQueryBuilder<Entity> extends QueryBuilder<Entity> {
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Sets WHERE condition in the query builder.
* If you had previously WHERE expression defined,
* calling this function will override previously set WHERE conditions.
* Additionally you can add parameters used in where expression.
*/
where(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "simple", condition: where });
if (parameters) this.setParameters(parameters);
return this;
}
/**
* Adds new AND WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "and", condition: where });
if (parameters) this.setParameters(parameters);
return this;
}
/**
* Adds new OR WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "or", condition: where });
if (parameters) this.setParameters(parameters);
return this;
}
/**
* Clones query builder as it is.
* Note: it uses new query runner, if you want query builder that uses exactly same query runner,
* you can create query builder this way: new DeleteQueryBuilder(queryBuilder) where queryBuilder
* is cloned QueryBuilder.
*/
clone(): DeleteQueryBuilder<Entity> {
const qb = new DeleteQueryBuilder<Entity>(this.connection);
qb.expressionMap = this.expressionMap.clone();
return qb;
}
}

View File

@ -0,0 +1,24 @@
import {QueryBuilder} from "./QueryBuilder";
/**
* Allows to build complex sql queries in a fashion way and execute those queries.
*/
export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Clones query builder as it is.
* Note: it uses new query runner, if you want query builder that uses exactly same query runner,
* you can create query builder this way: new InsertQueryBuilder(queryBuilder) where queryBuilder
* is cloned QueryBuilder.
*/
clone(): InsertQueryBuilder<Entity> {
const qb = new InsertQueryBuilder<Entity>(this.connection);
qb.expressionMap = this.expressionMap.clone();
return qb;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@ export class QueryExpressionMap {
/**
* Represents query type. QueryBuilder is able to build SELECT, UPDATE and DELETE queries.
*/
queryType: "select"|"update"|"delete" = "select";
queryType: "select"|"update"|"delete"|"insert" = "select";
/**
* Data needs to be SELECT-ed.

View File

@ -0,0 +1,942 @@
import {RawSqlResultsToEntityTransformer} from "./transformer/RawSqlResultsToEntityTransformer";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {ObjectLiteral} from "../common/ObjectLiteral";
import {QueryRunner} from "../query-runner/QueryRunner";
import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
import {Connection} from "../connection/Connection";
import {JoinOptions} from "./JoinOptions";
import {PessimisticLockTransactionRequiredError} from "./error/PessimisticLockTransactionRequiredError";
import {NoVersionOrUpdateDateColumnError} from "./error/NoVersionOrUpdateDateColumnError";
import {OptimisticLockVersionMismatchError} from "./error/OptimisticLockVersionMismatchError";
import {OptimisticLockCanNotBeUsedError} from "./error/OptimisticLockCanNotBeUsedError";
import {PostgresDriver} from "../driver/postgres/PostgresDriver";
import {MysqlDriver} from "../driver/mysql/MysqlDriver";
import {LockNotSupportedOnGivenDriverError} from "./error/LockNotSupportedOnGivenDriverError";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {JoinAttribute} from "./JoinAttribute";
import {RelationIdAttribute} from "./relation-id/RelationIdAttribute";
import {RelationCountAttribute} from "./relation-count/RelationCountAttribute";
import {QueryExpressionMap} from "./QueryExpressionMap";
import {SelectQuery} from "./SelectQuery";
import {RelationIdLoader} from "./relation-id/RelationIdLoader";
import {RelationIdLoadResult} from "./relation-id/RelationIdLoadResult";
import {RelationIdMetadataToAttributeTransformer} from "./relation-id/RelationIdMetadataToAttributeTransformer";
import {RelationCountLoadResult} from "./relation-count/RelationCountLoadResult";
import {RelationCountLoader} from "./relation-count/RelationCountLoader";
import {RelationCountMetadataToAttributeTransformer} from "./relation-count/RelationCountMetadataToAttributeTransformer";
import {OracleDriver} from "../driver/oracle/OracleDriver";
import {Broadcaster} from "../subscriber/Broadcaster";
import {UpdateQueryBuilder} from "./UpdateQueryBuilder";
import {DeleteQueryBuilder} from "./DeleteQueryBuilder";
import {InsertQueryBuilder} from "./InsertQueryBuilder";
import {QueryBuilder} from "./QueryBuilder";
/**
* Allows to build complex sql queries in a fashion way and execute those queries.
*/
export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* INNER JOINs (without selection) entity's property.
* Given entity property should be a relation.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoin(property: string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* INNER JOINs (without selection) given entity's table.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoin(entity: Function|string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* INNER JOINs (without selection) given table.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoin(tableName: string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* INNER JOINs (without selection).
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoin(entityOrProperty: Function|string, aliasName: string, condition: string = "", options?: JoinOptions): this {
this.join("INNER", entityOrProperty, aliasName, condition, options);
return this;
}
/**
* LEFT JOINs (without selection) entity's property.
* Given entity property should be a relation.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoin(property: string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* LEFT JOINs (without selection) entity's table.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoin(entity: Function|string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* LEFT JOINs (without selection) given table.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoin(tableName: string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* LEFT JOINs (without selection).
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoin(entityOrProperty: Function|string, aliasName: string, condition: string = "", options?: JoinOptions): this {
this.join("LEFT", entityOrProperty, aliasName, condition, options);
return this;
}
/**
* INNER JOINs entity's property and adds all selection properties to SELECT.
* Given entity property should be a relation.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoinAndSelect(property: string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* INNER JOINs entity and adds all selection properties to SELECT.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoinAndSelect(entity: Function|string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* INNER JOINs table and adds all selection properties to SELECT.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoinAndSelect(tableName: string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* INNER JOINs and adds all selection properties to SELECT.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoinAndSelect(entityOrProperty: Function|string, aliasName: string, condition: string = "", options?: JoinOptions): this {
this.addSelect(aliasName);
this.innerJoin(entityOrProperty, aliasName, condition, options);
return this;
}
/**
* LEFT JOINs entity's property and adds all selection properties to SELECT.
* Given entity property should be a relation.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoinAndSelect(property: string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* LEFT JOINs entity and adds all selection properties to SELECT.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoinAndSelect(entity: Function|string, aliasName: string, condition: string, options?: JoinOptions): this;
/**
* LEFT JOINs table and adds all selection properties to SELECT.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoinAndSelect(tableName: string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* LEFT JOINs and adds all selection properties to SELECT.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoinAndSelect(entityOrProperty: Function|string, aliasName: string, condition: string = "", options?: JoinOptions): this {
this.addSelect(aliasName);
this.leftJoin(entityOrProperty, aliasName, condition, options);
return this;
}
/**
* INNER JOINs entity's property, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there are multiple rows of selecting data, and mapped result will be an array.
* Given entity property should be a relation.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoinAndMapMany(mapToProperty: string, property: string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* INNER JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there are multiple rows of selecting data, and mapped result will be an array.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoinAndMapMany(mapToProperty: string, entity: Function|string, aliasName: string, condition: string, options?: JoinOptions): this;
/**
* INNER JOINs table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there are multiple rows of selecting data, and mapped result will be an array.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoinAndMapMany(mapToProperty: string, tableName: string, aliasName: string, condition: string, options?: JoinOptions): this;
/**
* INNER JOINs, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there are multiple rows of selecting data, and mapped result will be an array.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoinAndMapMany(mapToProperty: string, entityOrProperty: Function|string, aliasName: string, condition: string = "", options?: JoinOptions): this {
this.addSelect(aliasName);
this.join("INNER", entityOrProperty, aliasName, condition, options, mapToProperty, true);
return this;
}
/**
* INNER JOINs entity's property, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
* Given entity property should be a relation.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoinAndMapOne(mapToProperty: string, property: string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* INNER JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoinAndMapOne(mapToProperty: string, entity: Function|string, aliasName: string, condition: string, options?: JoinOptions): this;
/**
* INNER JOINs table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoinAndMapOne(mapToProperty: string, tableName: string, aliasName: string, condition: string, options?: JoinOptions): this;
/**
* INNER JOINs, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
innerJoinAndMapOne(mapToProperty: string, entityOrProperty: Function|string, aliasName: string, condition: string = "", options?: JoinOptions): this {
this.addSelect(aliasName);
this.join("INNER", entityOrProperty, aliasName, condition, options, mapToProperty, false);
return this;
}
/**
* LEFT JOINs entity's property, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there are multiple rows of selecting data, and mapped result will be an array.
* Given entity property should be a relation.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoinAndMapMany(mapToProperty: string, property: string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* LEFT JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there are multiple rows of selecting data, and mapped result will be an array.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoinAndMapMany(mapToProperty: string, entity: Function|string, aliasName: string, condition: string, options?: JoinOptions): this;
/**
* LEFT JOINs table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there are multiple rows of selecting data, and mapped result will be an array.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoinAndMapMany(mapToProperty: string, tableName: string, aliasName: string, condition: string, options?: JoinOptions): this;
/**
* LEFT JOINs, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there are multiple rows of selecting data, and mapped result will be an array.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoinAndMapMany(mapToProperty: string, entityOrProperty: Function|string, aliasName: string, condition: string = "", options?: JoinOptions): this {
this.addSelect(aliasName);
this.join("LEFT", entityOrProperty, aliasName, condition, options, mapToProperty, true);
return this;
}
/**
* LEFT JOINs entity's property, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
* Given entity property should be a relation.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoinAndMapOne(mapToProperty: string, property: string, aliasName: string, condition?: string, options?: JoinOptions): this;
/**
* LEFT JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoinAndMapOne(mapToProperty: string, entity: Function|string, aliasName: string, condition: string, options?: JoinOptions): this;
/**
* LEFT JOINs table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoinAndMapOne(mapToProperty: string, tableName: string, aliasName: string, condition: string, options?: JoinOptions): this;
/**
* LEFT JOINs, SELECTs the data returned by a join and MAPs all that data to some entity's property.
* This is extremely useful when you want to select some data and map it to some virtual property.
* It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
* You also need to specify an alias of the joined data.
* Optionally, you can add condition and parameters used in condition.
*/
leftJoinAndMapOne(mapToProperty: string, entityOrProperty: Function|string, aliasName: string, condition: string = "", options?: JoinOptions): this {
this.addSelect(aliasName);
this.join("LEFT", entityOrProperty, aliasName, condition, options, mapToProperty, false);
return this;
}
/**
* LEFT JOINs relation id and maps it into some entity's property.
* Optionally, you can add condition and parameters used in condition.
*/
loadRelationIdAndMap(mapToProperty: string, relationName: string): this;
/**
* LEFT JOINs relation id and maps it into some entity's property.
* Optionally, you can add condition and parameters used in condition.
*/
loadRelationIdAndMap(mapToProperty: string, relationName: string, options: { disableMixedMap: boolean }): this;
/**
* LEFT JOINs relation id and maps it into some entity's property.
* Optionally, you can add condition and parameters used in condition.
*/
loadRelationIdAndMap(mapToProperty: string, relationName: string, aliasName: string, queryBuilderFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>): this;
/**
* LEFT JOINs relation id and maps it into some entity's property.
* Optionally, you can add condition and parameters used in condition.
*/
loadRelationIdAndMap(mapToProperty: string,
relationName: string,
aliasNameOrOptions?: string|{ disableMixedMap?: boolean },
queryBuilderFactory?: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>): this {
const relationIdAttribute = new RelationIdAttribute(this.expressionMap);
relationIdAttribute.mapToProperty = mapToProperty;
relationIdAttribute.relationName = relationName;
if (typeof aliasNameOrOptions === "string")
relationIdAttribute.alias = aliasNameOrOptions;
if (aliasNameOrOptions instanceof Object && (aliasNameOrOptions as any).disableMixedMap)
relationIdAttribute.disableMixedMap = true;
relationIdAttribute.queryBuilderFactory = queryBuilderFactory;
this.expressionMap.relationIdAttributes.push(relationIdAttribute);
if (relationIdAttribute.relation.junctionEntityMetadata) {
this.expressionMap.createAlias({
name: relationIdAttribute.junctionAlias,
metadata: relationIdAttribute.relation.junctionEntityMetadata
});
}
return this;
}
/**
* Counts number of entities of entity's relation and maps the value into some entity's property.
* Optionally, you can add condition and parameters used in condition.
*/
loadRelationCountAndMap(mapToProperty: string, relationName: string, aliasName?: string, queryBuilderFactory?: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>): this {
const relationCountAttribute = new RelationCountAttribute(this.expressionMap);
relationCountAttribute.mapToProperty = mapToProperty;
relationCountAttribute.relationName = relationName;
relationCountAttribute.alias = aliasName;
relationCountAttribute.queryBuilderFactory = queryBuilderFactory;
this.expressionMap.relationCountAttributes.push(relationCountAttribute);
this.expressionMap.createAlias({
name: relationCountAttribute.junctionAlias
});
if (relationCountAttribute.relation.junctionEntityMetadata) {
this.expressionMap.createAlias({
name: relationCountAttribute.junctionAlias,
metadata: relationCountAttribute.relation.junctionEntityMetadata
});
}
return this;
}
/**
* Sets WHERE condition in the query builder.
* If you had previously WHERE expression defined,
* calling this function will override previously set WHERE conditions.
* Additionally you can add parameters used in where expression.
*/
where(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "simple", condition: where });
if (parameters) this.setParameters(parameters);
return this;
}
/**
* Adds new AND WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "and", condition: where });
if (parameters) this.setParameters(parameters);
return this;
}
/**
* Adds new OR WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "or", condition: where });
if (parameters) this.setParameters(parameters);
return this;
}
/**
* Sets HAVING condition in the query builder.
* If you had previously HAVING expression defined,
* calling this function will override previously set HAVING conditions.
* Additionally you can add parameters used in where expression.
*/
having(having: string, parameters?: ObjectLiteral): this {
this.expressionMap.havings.push({ type: "simple", condition: having });
if (parameters) this.setParameters(parameters);
return this;
}
/**
* Adds new AND HAVING condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andHaving(having: string, parameters?: ObjectLiteral): this {
this.expressionMap.havings.push({ type: "and", condition: having });
if (parameters) this.setParameters(parameters);
return this;
}
/**
* Adds new OR HAVING condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orHaving(having: string, parameters?: ObjectLiteral): this {
this.expressionMap.havings.push({ type: "or", condition: having });
if (parameters) this.setParameters(parameters);
return this;
}
/**
* Sets GROUP BY condition in the query builder.
* If you had previously GROUP BY expression defined,
* calling this function will override previously set GROUP BY conditions.
*/
groupBy(groupBy: string): this {
this.expressionMap.groupBys = [groupBy];
return this;
}
/**
* Adds GROUP BY condition in the query builder.
*/
addGroupBy(groupBy: string): this {
this.expressionMap.groupBys.push(groupBy);
return this;
}
/**
* Sets ORDER BY condition in the query builder.
* If you had previously ORDER BY expression defined,
* calling this function will override previously set ORDER BY conditions.
*/
orderBy(sort: string, order?: "ASC"|"DESC"): this;
/**
* Sets ORDER BY condition in the query builder.
* If you had previously ORDER BY expression defined,
* calling this function will override previously set ORDER BY conditions.
*/
orderBy(sort: undefined): this;
/**
* Sets ORDER BY condition in the query builder.
* If you had previously ORDER BY expression defined,
* calling this function will override previously set ORDER BY conditions.
*/
orderBy(sort?: string, order: "ASC"|"DESC" = "ASC"): this {
if (sort) {
this.expressionMap.orderBys = { [sort]: order };
} else {
this.expressionMap.orderBys = {};
}
return this;
}
/**
* Adds ORDER BY condition in the query builder.
*/
addOrderBy(sort: string, order: "ASC"|"DESC" = "ASC"): this {
this.expressionMap.orderBys[sort] = order;
return this;
}
/**
* Set's LIMIT - maximum number of rows to be selected.
* NOTE that it may not work as you expect if you are using joins.
* If you want to implement pagination, and you are having join in your query,
* then use instead take method instead.
*/
limit(limit?: number): this {
this.expressionMap.limit = limit;
return this;
}
/**
* Set's OFFSET - selection offset.
* NOTE that it may not work as you expect if you are using joins.
* If you want to implement pagination, and you are having join in your query,
* then use instead skip method instead.
*/
offset(offset?: number): this {
this.expressionMap.offset = offset;
return this;
}
/**
* Sets maximal number of entities to take.
*/
take(take?: number): this {
this.expressionMap.take = take;
return this;
}
/**
* Sets number of entities to skip.
*/
skip(skip?: number): this {
this.expressionMap.skip = skip;
return this;
}
/**
* Sets maximal number of entities to take.
*
* @deprecated use take method instead
*/
setMaxResults(take?: number): this {
this.expressionMap.take = take;
return this;
}
/**
* Sets number of entities to skip.
*
* @deprecated use skip method instead
*/
setFirstResult(skip?: number): this {
this.expressionMap.skip = skip;
return this;
}
/**
* Sets locking mode.
*/
setLock(lockMode: "optimistic", lockVersion: number): this;
/**
* Sets locking mode.
*/
setLock(lockMode: "optimistic", lockVersion: Date): this;
/**
* Sets locking mode.
*/
setLock(lockMode: "pessimistic_read"|"pessimistic_write"): this;
/**
* Sets locking mode.
*/
setLock(lockMode: "optimistic"|"pessimistic_read"|"pessimistic_write", lockVersion?: number|Date): this {
this.expressionMap.lockMode = lockMode;
this.expressionMap.lockVersion = lockVersion;
return this;
}
/**
* Executes sql generated by query builder and returns object with raw results and entities created from them.
*/
async getEntitiesAndRawResults(): Promise<{ entities: Entity[], rawResults: any[] }> {
const broadcaster = new Broadcaster(this.connection);
const relationIdLoader = new RelationIdLoader(this.connection, this.queryRunner, this.expressionMap.relationIdAttributes);
const relationCountLoader = new RelationCountLoader(this.connection, this.queryRunner, this.expressionMap.relationCountAttributes);
const relationIdMetadataTransformer = new RelationIdMetadataToAttributeTransformer(this.expressionMap);
relationIdMetadataTransformer.transform();
const relationCountMetadataTransformer = new RelationCountMetadataToAttributeTransformer(this.expressionMap);
relationCountMetadataTransformer.transform();
try {
if (!this.expressionMap.mainAlias)
throw new Error(`Alias is not set. Looks like nothing is selected. Use select*, delete, update method to set an alias.`);
if ((this.expressionMap.lockMode === "pessimistic_read" || this.expressionMap.lockMode === "pessimistic_write") && !this.queryRunner.isTransactionActive)
throw new PessimisticLockTransactionRequiredError();
if (this.expressionMap.lockMode === "optimistic") {
const metadata = this.expressionMap.mainAlias!.metadata;
if (!metadata.versionColumn && !metadata.updateDateColumn)
throw new NoVersionOrUpdateDateColumnError(metadata.name);
}
const mainAliasName = this.expressionMap.mainAlias.name;
if (this.expressionMap.skip || this.expressionMap.take) {
// we are skipping order by here because its not working in subqueries anyway
// to make order by working we need to apply it on a distinct query
const [sql, parameters] = this.getSqlWithParameters({ skipOrderBy: true });
const [selects, orderBys] = this.createOrderByCombinedWithSelectExpression("distinctAlias");
const distinctAlias = this.escapeTable("distinctAlias");
const metadata = this.expressionMap.mainAlias!.metadata;
let idsQuery = `SELECT `;
idsQuery += metadata.primaryColumns.map((primaryColumn, index) => {
const propertyName = this.escapeAlias(mainAliasName + "_" + primaryColumn.databaseName);
if (index === 0) {
return `DISTINCT(${distinctAlias}.${propertyName}) as "ids_${primaryColumn.databaseName}"`;
} else {
return `${distinctAlias}.${propertyName}) as "ids_${primaryColumn.databaseName}"`;
}
}).join(", ");
if (selects.length > 0)
idsQuery += ", " + selects;
idsQuery += ` FROM (${sql}) ${distinctAlias}`; // TODO: WHAT TO DO WITH PARAMETERS HERE? DO THEY WORK?
if (orderBys.length > 0) {
idsQuery += " ORDER BY " + orderBys;
} else {
idsQuery += ` ORDER BY "ids_${metadata.primaryColumns[0].databaseName}"`; // this is required for mssql driver if firstResult is used. Other drivers don't care about it
}
if (this.connection.driver instanceof SqlServerDriver) { // todo: temporary. need to refactor and make a proper abstraction
if (this.expressionMap.skip || this.expressionMap.take) {
idsQuery += ` OFFSET ${this.expressionMap.skip || 0} ROWS`;
if (this.expressionMap.take)
idsQuery += " FETCH NEXT " + this.expressionMap.take + " ROWS ONLY";
}
} else {
if (this.expressionMap.take)
idsQuery += " LIMIT " + this.expressionMap.take;
if (this.expressionMap.skip)
idsQuery += " OFFSET " + this.expressionMap.skip;
}
let entities: any[] = [];
let rawResults: any[] = await this.queryRunner.query(idsQuery, parameters);
if (rawResults.length > 0) {
let condition = "";
const parameters: ObjectLiteral = {};
if (metadata.hasMultiplePrimaryKeys) {
condition = rawResults.map(result => {
return metadata.primaryColumns.map(primaryColumn => {
parameters["ids_" + primaryColumn.propertyName] = result["ids_" + primaryColumn.propertyName];
return mainAliasName + "." + primaryColumn.propertyName + "=:ids_" + primaryColumn.propertyName;
}).join(" AND ");
}).join(" OR ");
} else {
const ids = rawResults.map(result => result["ids_" + metadata.primaryColumns[0].propertyName]);
const areAllNumbers = ids.every((id: any) => typeof id === "number");
if (areAllNumbers) {
// fixes #190. if all numbers then its safe to perform query without parameter
condition = `${mainAliasName}.${metadata.primaryColumns[0].propertyName} IN (${ids.join(", ")})`;
} else {
parameters["ids"] = ids;
condition = mainAliasName + "." + metadata.primaryColumns[0].propertyName + " IN (:ids)";
}
}
const clonnedQb = new SelectQueryBuilder(this);
clonnedQb.expressionMap.extraAppendedAndWhereCondition = condition;
const [queryWithIdsSql, queryWithIdsParameters] = clonnedQb
.setParameters(parameters)
.getSqlWithParameters();
rawResults = await this.queryRunner.query(queryWithIdsSql, queryWithIdsParameters);
const rawRelationIdResults = await relationIdLoader.load(rawResults);
const rawRelationCountResults = await relationCountLoader.load(rawResults);
entities = this.rawResultsToEntities(rawResults, rawRelationIdResults, rawRelationCountResults);
if (this.expressionMap.mainAlias.hasMetadata) {
await broadcaster.broadcastLoadEventsForAll(this.expressionMap.mainAlias.target, rawResults);
}
}
return {
entities: entities,
rawResults: rawResults
};
} else {
const [sql, parameters] = this.getSqlWithParameters();
const rawResults = await this.queryRunner.query(sql, parameters);
const rawRelationIdResults = await relationIdLoader.load(rawResults);
const rawRelationCountResults = await relationCountLoader.load(rawResults);
const entities = this.rawResultsToEntities(rawResults, rawRelationIdResults, rawRelationCountResults);
if (this.expressionMap.mainAlias.hasMetadata)
await broadcaster.broadcastLoadEventsForAll(this.expressionMap.mainAlias.target, rawResults);
return {
entities: entities,
rawResults: rawResults
};
}
} finally {
if (this.ownQueryRunner) // means we created our own query runner
await this.queryRunner.release();
}
}
/**
* Gets count - number of entities selected by sql generated by this query builder.
* Count excludes all limitations set by setFirstResult and setMaxResults methods call.
*/
async getCount(): Promise<number> {
if (this.expressionMap.lockMode === "optimistic")
throw new OptimisticLockCanNotBeUsedError();
const mainAlias = this.expressionMap.mainAlias!.name; // todo: will this work with "fromTableName"?
const metadata = this.expressionMap.mainAlias!.metadata;
const distinctAlias = this.escapeAlias(mainAlias);
let countSql = `COUNT(` + metadata.primaryColumns.map((primaryColumn, index) => {
const propertyName = this.escapeColumn(primaryColumn.databaseName);
if (index === 0) {
return `DISTINCT(${distinctAlias}.${propertyName})`;
} else {
return `${distinctAlias}.${propertyName})`;
}
}).join(", ") + ") as \"cnt\"";
const countQueryBuilder = new SelectQueryBuilder(this)
.orderBy(undefined)
.offset(undefined)
.limit(undefined)
.select(countSql);
countQueryBuilder.expressionMap.ignoreParentTablesJoins = true;
const [countQuerySql, countQueryParameters] = countQueryBuilder.getSqlWithParameters();
try {
const results = await this.queryRunner.query(countQuerySql, countQueryParameters);
if (!results || !results[0] || !results[0]["cnt"])
return 0;
return parseInt(results[0]["cnt"]);
} finally {
if (this.ownQueryRunner) // means we created our own query runner
await this.queryRunner.release();
}
}
/**
* Gets all raw results returned by execution of generated query builder sql.
*/
async getRawMany(): Promise<any[]> {
if (this.expressionMap.lockMode === "optimistic")
throw new OptimisticLockCanNotBeUsedError();
return this.execute();
}
/**
* Gets first raw result returned by execution of generated query builder sql.
*/
async getRawOne(): Promise<any> {
if (this.expressionMap.lockMode === "optimistic")
throw new OptimisticLockCanNotBeUsedError();
const results = await this.execute();
return results[0];
}
/**
* Gets entities and count returned by execution of generated query builder sql.
*/
async getManyAndCount(): Promise<[Entity[], number]> {
if (this.expressionMap.lockMode === "optimistic")
throw new OptimisticLockCanNotBeUsedError();
// todo: share database connection and counter
return Promise.all([
this.getMany(),
this.getCount()
]);
}
/**
* Gets entities returned by execution of generated query builder sql.
*/
async getMany(): Promise<Entity[]> {
if (this.expressionMap.lockMode === "optimistic")
throw new OptimisticLockCanNotBeUsedError();
const results = await this.getEntitiesAndRawResults();
return results.entities;
}
/**
* Gets single entity returned by execution of generated query builder sql.
*/
async getOne(): Promise<Entity|undefined> {
const results = await this.getEntitiesAndRawResults();
const result = results.entities[0] as any;
if (result && this.expressionMap.lockMode === "optimistic" && this.expressionMap.lockVersion) {
const metadata = this.expressionMap.mainAlias!.metadata;
if (this.expressionMap.lockVersion instanceof Date) {
const actualVersion = result[metadata.updateDateColumn!.propertyName]; // what if columns arent set?
this.expressionMap.lockVersion.setMilliseconds(0);
if (actualVersion.getTime() !== this.expressionMap.lockVersion.getTime())
throw new OptimisticLockVersionMismatchError(metadata.name, this.expressionMap.lockVersion, actualVersion);
} else {
const actualVersion = result[metadata.versionColumn!.propertyName]; // what if columns arent set?
if (actualVersion !== this.expressionMap.lockVersion)
throw new OptimisticLockVersionMismatchError(metadata.name, this.expressionMap.lockVersion, actualVersion);
}
}
return result;
}
/**
* Clones query builder as it is.
* Note: it uses new query runner, if you want query builder that uses exactly same query runner,
* you can create query builder this way: new SelectQueryBuilder(queryBuilder) where queryBuilder
* is cloned QueryBuilder.
*/
clone(): SelectQueryBuilder<Entity> {
const qb = new SelectQueryBuilder<Entity>(this.connection);
qb.expressionMap = this.expressionMap.clone();
return qb;
}
/**
* Enables special query builder options.
*
* @deprecated looks like enableRelationIdValues is not used anymore. What to do? Remove this method? What about persistence?
*/
enableAutoRelationIdsLoad(): this {
this.expressionMap.mainAlias!.metadata.relations.forEach(relation => {
this.loadRelationIdAndMap(this.expressionMap.mainAlias!.name + "." + relation.propertyPath,
this.expressionMap.mainAlias!.name + "." + relation.propertyPath,
{ disableMixedMap: true });
});
return this;
}
/**
* Adds new AND WHERE with conditions for the given ids.
*
* @experimental Maybe this method should be moved to repository?
* @deprecated
*/
andWhereInIds(ids: any[]): this {
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
this.andWhere(whereExpression, parameters);
return this;
}
/**
* Adds new OR WHERE with conditions for the given ids.
*
* @experimental Maybe this method should be moved to repository?
* @deprecated
*/
orWhereInIds(ids: any[]): this {
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
this.orWhere(whereExpression, parameters);
return this;
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
protected join(direction: "INNER"|"LEFT", entityOrProperty: Function|string, aliasName: string, condition?: string, options?: JoinOptions, mapToProperty?: string, isMappingMany?: boolean): void {
const joinAttribute = new JoinAttribute(this.connection, this.expressionMap);
joinAttribute.direction = direction;
joinAttribute.mapToProperty = mapToProperty;
joinAttribute.options = options;
joinAttribute.isMappingMany = isMappingMany;
joinAttribute.entityOrProperty = entityOrProperty; // relationName
joinAttribute.condition = condition; // joinInverseSideCondition
// joinAttribute.junctionAlias = joinAttribute.relation.isOwning ? parentAlias + "_" + destinationTableAlias : destinationTableAlias + "_" + parentAlias;
this.expressionMap.joinAttributes.push(joinAttribute);
// todo: find and set metadata right there?
joinAttribute.alias = this.expressionMap.createAlias({
name: aliasName,
metadata: joinAttribute.metadata!
});
if (joinAttribute.relation && joinAttribute.relation.junctionEntityMetadata) {
this.expressionMap.createAlias({
name: joinAttribute.junctionAlias,
metadata: joinAttribute.relation.junctionEntityMetadata
});
}
}
protected rawResultsToEntities(results: any[], rawRelationIdResults: RelationIdLoadResult[], rawRelationCountResults: RelationCountLoadResult[]) {
return new RawSqlResultsToEntityTransformer(this.connection.driver, this.expressionMap.joinAttributes, rawRelationIdResults, rawRelationCountResults)
.transform(results, this.expressionMap.mainAlias!);
}
}

View File

@ -0,0 +1,57 @@
import {QueryBuilder} from "./QueryBuilder";
import {ObjectLiteral} from "../common/ObjectLiteral";
/**
* Allows to build complex sql queries in a fashion way and execute those queries.
*/
export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> {
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Sets WHERE condition in the query builder.
* If you had previously WHERE expression defined,
* calling this function will override previously set WHERE conditions.
* Additionally you can add parameters used in where expression.
*/
where(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "simple", condition: where });
if (parameters) this.setParameters(parameters);
return this;
}
/**
* Adds new AND WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
andWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "and", condition: where });
if (parameters) this.setParameters(parameters);
return this;
}
/**
* Adds new OR WHERE condition in the query builder.
* Additionally you can add parameters used in where expression.
*/
orWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: "or", condition: where });
if (parameters) this.setParameters(parameters);
return this;
}
/**
* Clones query builder as it is.
* Note: it uses new query runner, if you want query builder that uses exactly same query runner,
* you can create query builder this way: new UpdateQueryBuilder(queryBuilder) where queryBuilder
* is cloned QueryBuilder.
*/
clone(): UpdateQueryBuilder<Entity> {
const qb = new UpdateQueryBuilder<Entity>(this.connection);
qb.expressionMap = this.expressionMap.clone();
return qb;
}
}

View File

@ -3,6 +3,7 @@ import {QueryBuilderUtils} from "../QueryBuilderUtils";
import {RelationMetadata} from "../../metadata/RelationMetadata";
import {QueryExpressionMap} from "../QueryExpressionMap";
import {QueryBuilder} from "../QueryBuilder";
import {SelectQueryBuilder} from "../SelectQueryBuilder";
export class RelationCountAttribute {
@ -24,7 +25,7 @@ export class RelationCountAttribute {
/**
* Extra condition applied to "ON" section of join.
*/
queryBuilderFactory?: (qb: QueryBuilder<any>) => QueryBuilder<any>;
queryBuilderFactory?: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>;
// -------------------------------------------------------------------------
// Constructor

View File

@ -3,6 +3,7 @@ import {QueryBuilderUtils} from "../QueryBuilderUtils";
import {EntityMetadata} from "../../metadata/EntityMetadata";
import {QueryBuilder} from "../QueryBuilder";
import {QueryExpressionMap} from "../QueryExpressionMap";
import {SelectQueryBuilder} from "../SelectQueryBuilder";
/**
* Stores all join relation id attributes which will be used to build a JOIN query.
@ -31,7 +32,7 @@ export class RelationIdAttribute {
/**
* Extra condition applied to "ON" section of join.
*/
queryBuilderFactory?: (qb: QueryBuilder<any>) => QueryBuilder<any>;
queryBuilderFactory?: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>;
/**
* Indicates if relation id should NOT be loaded as id map.

View File

@ -7,6 +7,7 @@ import {ObjectType} from "../common/ObjectType";
import {CustomRepositoryDoesNotHaveEntityError} from "./error/CustomRepositoryDoesNotHaveEntityError";
import {getMetadataArgsStorage} from "../index";
import {CustomRepositoryNotFoundError} from "./error/CustomRepositoryNotFoundError";
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
/**
* Provides abstract class for custom repositories that do not inherit from original orm Repository.
@ -62,7 +63,7 @@ export class AbstractRepository<Entity extends ObjectLiteral> {
* Creates a new query builder for the repository's entity that can be used to build a sql query.
* If current repository does not manage any entity, then exception will be thrown.
*/
protected createQueryBuilder(alias: string): QueryBuilder<Entity> {
protected createQueryBuilder(alias: string): SelectQueryBuilder<Entity> {
const target = this.getCustomRepositoryTarget(this.constructor);
if (!target)
throw new CustomRepositoryDoesNotHaveEntityError(this.constructor);
@ -73,7 +74,7 @@ export class AbstractRepository<Entity extends ObjectLiteral> {
/**
* Creates a new query builder for the given entity that can be used to build a sql query.
*/
protected createQueryBuilderFor<T>(entity: ObjectType<T>, alias: string): QueryBuilder<T> {
protected createQueryBuilderFor<T>(entity: ObjectType<T>, alias: string): SelectQueryBuilder<T> {
return this.getRepositoryFor(entity).createQueryBuilder(alias);
}

View File

@ -8,6 +8,7 @@ import {RemoveOptions} from "./RemoveOptions";
import {FindManyOptions} from "../find-options/FindManyOptions";
import {Connection} from "../connection/Connection";
import {ObjectType} from "../common/ObjectType";
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
/**
* Base abstract entity for all entities, used in ActiveRecord patterns.
@ -96,7 +97,7 @@ export class EntityModel {
/**
* Creates a new query builder that can be used to build a sql query.
*/
static createQueryBuilder<T extends EntityModel>(this: ObjectType<T>, alias: string): QueryBuilder<T> {
static createQueryBuilder<T extends EntityModel>(this: ObjectType<T>, alias: string): SelectQueryBuilder<T> {
return (this as any).getRepository().createQueryBuilder(alias);
}

View File

@ -35,6 +35,7 @@ import {
} from "../driver/mongodb/typings";
import {MongoEntityManager} from "../entity-manager/MongoEntityManager";
import {QueryRunner} from "../query-runner/QueryRunner";
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
/**
* Repository used to manage mongodb documents of a single entity type.
@ -66,7 +67,7 @@ export class MongoRepository<Entity extends ObjectLiteral> extends Repository<En
* Using Query Builder with MongoDB is not supported yet.
* Calling this method will return an error.
*/
createQueryBuilder(alias: string, queryRunner?: QueryRunner): QueryBuilder<Entity> {
createQueryBuilder(alias: string, queryRunner?: QueryRunner): SelectQueryBuilder<Entity> {
throw new Error(`Query Builder is not supported by MongoDB.`);
}

View File

@ -8,6 +8,7 @@ import {SaveOptions} from "./SaveOptions";
import {RemoveOptions} from "./RemoveOptions";
import {EntityManager} from "../entity-manager/EntityManager";
import {QueryRunner} from "../query-runner/QueryRunner";
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
/**
* Repository is supposed to work with your entity objects. Find entities, insert, update, delete, etc.
@ -64,7 +65,7 @@ export class Repository<Entity extends ObjectLiteral> {
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder(alias: string, queryRunner?: QueryRunner): QueryBuilder<Entity> {
createQueryBuilder(alias: string, queryRunner?: QueryRunner): SelectQueryBuilder<Entity> {
return this.manager.createQueryBuilder(this.metadata.target, alias, queryRunner || this.queryRunner);
}

View File

@ -1,5 +1,6 @@
import {Repository} from "./Repository";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
/**
* Repository with additional functions to work with trees.
@ -41,7 +42,7 @@ export class TreeRepository<Entity> extends Repository<Entity> {
/**
* Creates a query builder used to get descendants of the entities in a tree.
*/
createDescendantsQueryBuilder(alias: string, closureTableAlias: string, entity: Entity): QueryBuilder<Entity> {
createDescendantsQueryBuilder(alias: string, closureTableAlias: string, entity: Entity): SelectQueryBuilder<Entity> {
// create shortcuts for better readability
const escapeAlias = (alias: string) => this.manager.connection.driver.escapeAlias(alias);
@ -89,7 +90,7 @@ export class TreeRepository<Entity> extends Repository<Entity> {
/**
* Creates a query builder used to get ancestors of the entities in the tree.
*/
createAncestorsQueryBuilder(alias: string, closureTableAlias: string, entity: Entity): QueryBuilder<Entity> {
createAncestorsQueryBuilder(alias: string, closureTableAlias: string, entity: Entity): SelectQueryBuilder<Entity> {
// create shortcuts for better readability
const escapeAlias = (alias: string) => this.manager.connection.driver.escapeAlias(alias);