better implementation of different types of query builders

This commit is contained in:
Umed Khudoiberdiev 2017-06-20 18:21:06 +05:00
parent b5140d245c
commit 4acda40a64
7 changed files with 329 additions and 261 deletions

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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.");

View File

@ -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));

View File

@ -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!);
}
}

View File

@ -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,