mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
better implementation of different types of query builders
This commit is contained in:
parent
b5140d245c
commit
4acda40a64
@ -75,7 +75,7 @@ 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 -
|
||||
* `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.
|
||||
|
||||
### BUG FIXES
|
||||
|
||||
@ -10,6 +10,14 @@ export class DeleteQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Specifies FROM which entity's table select/update/delete will be executed.
|
||||
* Also sets a main string alias of the selection data.
|
||||
*/
|
||||
from(entityTarget: Function|string, aliasName: string): this {
|
||||
return this.setMainAlias(entityTarget, aliasName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets WHERE condition in the query builder.
|
||||
* If you had previously WHERE expression defined,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {QueryBuilder} from "./QueryBuilder";
|
||||
import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
|
||||
/**
|
||||
* Allows to build complex sql queries in a fashion way and execute those queries.
|
||||
@ -9,6 +10,32 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Specifies FROM which entity's table select/update/delete will be executed.
|
||||
* Also sets a main string alias of the selection data.
|
||||
*/
|
||||
into(entityTarget: Function|string): this {
|
||||
return this.setMainAlias(entityTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Values needs to be inserted into table.
|
||||
*/
|
||||
values(values: Partial<Entity>): this;
|
||||
|
||||
/**
|
||||
* Values needs to be inserted into table.
|
||||
*/
|
||||
values(values: Partial<Entity>[]): this;
|
||||
|
||||
/**
|
||||
* Values needs to be inserted into table.
|
||||
*/
|
||||
values(values: ObjectLiteral|ObjectLiteral[]): this {
|
||||
this.expressionMap.valuesSet = values;
|
||||
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,
|
||||
|
||||
@ -232,19 +232,11 @@ export abstract class QueryBuilder<Entity> {
|
||||
update(entityOrTableNameUpdateSet?: string|Function|ObjectLiteral, maybeUpdateSet?: ObjectLiteral): UpdateQueryBuilder<Entity> {
|
||||
const updateSet = maybeUpdateSet ? maybeUpdateSet : entityOrTableNameUpdateSet as ObjectLiteral|undefined;
|
||||
|
||||
if (entityOrTableNameUpdateSet instanceof Function) { // entityOrTableNameUpdateSet is entity class
|
||||
this.expressionMap.createMainAlias({
|
||||
target: entityOrTableNameUpdateSet
|
||||
});
|
||||
|
||||
} else if (typeof entityOrTableNameUpdateSet === "string") { // todo: check if entityOrTableNameUpdateSet is entity target string
|
||||
this.expressionMap.createMainAlias({
|
||||
tableName: entityOrTableNameUpdateSet
|
||||
});
|
||||
}
|
||||
if (entityOrTableNameUpdateSet instanceof Function || typeof entityOrTableNameUpdateSet === "string")
|
||||
this.setMainAlias(entityOrTableNameUpdateSet);
|
||||
|
||||
this.expressionMap.queryType = "update";
|
||||
this.expressionMap.updateSet = updateSet;
|
||||
this.expressionMap.valuesSet = updateSet;
|
||||
|
||||
// loading it dynamically because of circular issue
|
||||
const UpdateQueryBuilderCls = require("./UpdateQueryBuilder").UpdateQueryBuilder;
|
||||
@ -268,29 +260,6 @@ export abstract class QueryBuilder<Entity> {
|
||||
return new DeleteQueryBuilderCls(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies FROM which entity's table select/update/delete will be executed.
|
||||
* Also sets a main string alias of the selection data.
|
||||
*/
|
||||
from(entityTarget: Function|string, aliasName: string): this {
|
||||
|
||||
// if table has a metadata then find it to properly escape its properties
|
||||
// const metadata = this.connection.entityMetadatas.find(metadata => metadata.tableName === tableName);
|
||||
if (entityTarget instanceof Function || this.connection.hasMetadata(entityTarget)) {
|
||||
this.expressionMap.createMainAlias({
|
||||
name: aliasName,
|
||||
metadata: this.connection.getMetadata(entityTarget),
|
||||
});
|
||||
|
||||
} else {
|
||||
this.expressionMap.createMainAlias({
|
||||
name: aliasName,
|
||||
tableName: entityTarget,
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets given parameter's value.
|
||||
*/
|
||||
@ -434,6 +403,29 @@ export abstract class QueryBuilder<Entity> {
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Specifies FROM which entity's table select/update/delete will be executed.
|
||||
* Also sets a main string alias of the selection data.
|
||||
*/
|
||||
protected setMainAlias(entityTarget: Function|string, aliasName?: string): this {
|
||||
|
||||
// if table has a metadata then find it to properly escape its properties
|
||||
// const metadata = this.connection.entityMetadatas.find(metadata => metadata.tableName === tableName);
|
||||
if (entityTarget instanceof Function || this.connection.hasMetadata(entityTarget)) {
|
||||
this.expressionMap.createMainAlias({
|
||||
name: aliasName,
|
||||
metadata: this.connection.getMetadata(entityTarget),
|
||||
});
|
||||
|
||||
} else {
|
||||
this.expressionMap.createMainAlias({
|
||||
name: aliasName,
|
||||
tableName: entityTarget,
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
protected buildEscapedEntityColumnSelects(aliasName: string, metadata: EntityMetadata): SelectQuery[] {
|
||||
const hasMainAlias = this.expressionMap.selects.some(select => select.selection === aliasName);
|
||||
|
||||
@ -575,14 +567,39 @@ export abstract class QueryBuilder<Entity> {
|
||||
return "DELETE FROM " + et(tableName);
|
||||
// return "DELETE " + (alias ? ea(alias) : "") + " FROM " + this.escapeTable(tableName) + " " + (alias ? ea(alias) : ""); // TODO: only mysql supports aliasing, so what to do with aliases in DELETE queries? right now aliases are used however we are relaying that they will always match a table names
|
||||
case "update":
|
||||
const updateSet = Object.keys(this.expressionMap.updateSet).map(key => ea(key) + "=:updateSet__" + key);
|
||||
const params = Object.keys(this.expressionMap.updateSet).reduce((object, key) => {
|
||||
let valuesSet = this.expressionMap.valuesSet as ObjectLiteral;
|
||||
if (!valuesSet)
|
||||
throw new Error(`Cannot perform update query because updation values are not defined.`);
|
||||
|
||||
const updateSet = Object.keys(valuesSet).map(key => ea(key) + "=:updateSet__" + key);
|
||||
const params = Object.keys(valuesSet).reduce((object, key) => {
|
||||
// todo: map propertyNames to names ?
|
||||
object["updateSet__" + key] = this.expressionMap.updateSet![key];
|
||||
object["updateSet__" + key] = valuesSet[key];
|
||||
return object;
|
||||
}, {} as ObjectLiteral);
|
||||
this.setParameters(params);
|
||||
return "UPDATE " + this.escapeTable(tableName) + " " + (aliasName ? ea(aliasName) : "") + " SET " + updateSet;
|
||||
case "insert":
|
||||
let valuesSets: ObjectLiteral[] = []; // todo: check if valuesSet is defined and has items if its an array
|
||||
if (this.expressionMap.valuesSet instanceof Array && this.expressionMap.valuesSet.length > 0) {
|
||||
valuesSets = this.expressionMap.valuesSet;
|
||||
} else if (this.expressionMap.valuesSet instanceof Object) {
|
||||
valuesSets = [this.expressionMap.valuesSet];
|
||||
} else {
|
||||
throw new Error(`Cannot perform insert query because values are not defined.`);
|
||||
}
|
||||
|
||||
const parameters: ObjectLiteral = {};
|
||||
const columns = Object.keys(valuesSets[0]).map(columnName => ea(columnName));
|
||||
const values = valuesSets.map((valueSet, key) => {
|
||||
return "(" + Object.keys(valueSet).map(columnName => {
|
||||
const paramName = ":inserted_" + key + "_" + columnName;
|
||||
parameters[paramName] = valueSet[columnName];
|
||||
return paramName;
|
||||
}).join(",") + ")";
|
||||
}).join(", ");
|
||||
|
||||
return `INSERT INTO ${this.escapeTable(tableName)}(${columns}) VALUES ${values}`;
|
||||
}
|
||||
|
||||
throw new Error("No query builder type is specified.");
|
||||
|
||||
@ -39,8 +39,9 @@ export class QueryExpressionMap {
|
||||
|
||||
/**
|
||||
* If update query was used, it needs "update set" - properties which will be updated by this query.
|
||||
* If insert query was used, it needs "insert set" - values that needs to be inserted.
|
||||
*/
|
||||
updateSet?: ObjectLiteral;
|
||||
valuesSet?: ObjectLiteral|ObjectLiteral[];
|
||||
|
||||
/**
|
||||
* JOIN queries.
|
||||
@ -148,26 +149,6 @@ export class QueryExpressionMap {
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a main alias and adds it to the current expression map.
|
||||
*/
|
||||
createMainAlias(options: { name: string }): Alias;
|
||||
|
||||
/**
|
||||
* Creates a main alias and adds it to the current expression map.
|
||||
*/
|
||||
createMainAlias(options: { name: string, metadata: EntityMetadata }): Alias;
|
||||
|
||||
/**
|
||||
* Creates a main alias and adds it to the current expression map.
|
||||
*/
|
||||
createMainAlias(options: { name?: string, target: Function|string }): Alias;
|
||||
|
||||
/**
|
||||
* Creates a main alias and adds it to the current expression map.
|
||||
*/
|
||||
createMainAlias(options: { name?: string, tableName: string }): Alias;
|
||||
|
||||
/**
|
||||
* Creates a main alias and adds it to the current expression map.
|
||||
*/
|
||||
@ -253,7 +234,7 @@ export class QueryExpressionMap {
|
||||
map.selects = this.selects.map(select => select);
|
||||
this.aliases.forEach(alias => map.aliases.push(new Alias(alias)));
|
||||
map.mainAlias = this.mainAlias;
|
||||
map.updateSet = this.updateSet;
|
||||
map.valuesSet = this.valuesSet;
|
||||
map.joinAttributes = this.joinAttributes.map(join => new JoinAttribute(this.connection, this, join));
|
||||
map.relationIdAttributes = this.relationIdAttributes.map(relationId => new RelationIdAttribute(this, relationId));
|
||||
map.relationCountAttributes = this.relationCountAttributes.map(relationCount => new RelationCountAttribute(this, relationCount));
|
||||
|
||||
@ -40,6 +40,14 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Specifies FROM which entity's table select/update/delete will be executed.
|
||||
* Also sets a main string alias of the selection data.
|
||||
*/
|
||||
from(entityTarget: Function|string, aliasName: string): this {
|
||||
return this.setMainAlias(entityTarget, aliasName);
|
||||
}
|
||||
|
||||
/**
|
||||
* INNER JOINs (without selection) entity's property.
|
||||
* Given entity property should be a relation.
|
||||
@ -602,12 +610,232 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
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[] }> {
|
||||
return this.executeEntitiesAndRawResults({ release: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
return this.executeCountQuery({ release: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
try {
|
||||
const result = await Promise.all([
|
||||
this.executeEntitiesAndRawResults({ release: false }),
|
||||
this.executeCountQuery({ release: false })
|
||||
]);
|
||||
return [result[0].entities, result[1]];
|
||||
|
||||
} finally {
|
||||
if (this.ownQueryRunner) // means we created our own query runner
|
||||
await this.queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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!);
|
||||
}
|
||||
|
||||
protected async executeCountQuery(options: { release: boolean }): Promise<number> {
|
||||
|
||||
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 (options.release && this.ownQueryRunner) // means we created our own query runner
|
||||
await this.queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes sql generated by query builder and returns object with raw results and entities created from them.
|
||||
*/
|
||||
protected async executeEntitiesAndRawResults(options: { release: boolean }): 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);
|
||||
@ -734,209 +962,9 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (this.ownQueryRunner) // means we created our own query runner
|
||||
if (options.release && 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!);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -10,6 +10,13 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Values needs to be updated.
|
||||
*/
|
||||
set(values: Partial<Entity>) {
|
||||
this.expressionMap.valuesSet = values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets WHERE condition in the query builder.
|
||||
* If you had previously WHERE expression defined,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user