mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
refactoring query builder
This commit is contained in:
parent
3c4a80fb49
commit
77534b4dcc
@ -6,6 +6,19 @@ import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
*/
|
||||
export class DeleteQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Implemented Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets generated sql query without parameters being replaced.
|
||||
*/
|
||||
getQuery(): string {
|
||||
let sql = this.createDeleteExpression();
|
||||
sql += this.createWhereExpression();
|
||||
return sql.trim();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
@ -50,4 +63,16 @@ export class DeleteQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
return this;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates DELETE express used to perform insert query.
|
||||
*/
|
||||
protected createDeleteExpression() {
|
||||
const tableName = this.escapeTable(this.getTableName());
|
||||
return `DELETE FROM ${tableName}`; // todo: how do we replace aliases in where to nothing?
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,11 +1,24 @@
|
||||
import {QueryBuilder} from "./QueryBuilder";
|
||||
import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
|
||||
/**
|
||||
* Allows to build complex sql queries in a fashion way and execute those queries.
|
||||
*/
|
||||
export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Implemented Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets generated sql query without parameters being replaced.
|
||||
*/
|
||||
getQuery(): string {
|
||||
let sql = this.createInsertExpression();
|
||||
return sql.trim();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
@ -35,4 +48,52 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
return this;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates INSERT express used to perform insert query.
|
||||
*/
|
||||
protected createInsertExpression() {
|
||||
const valueSets = this.getValueSets();
|
||||
|
||||
// get columns that participate in insertion query
|
||||
const insertColumns: ColumnMetadata[] = [];
|
||||
Object.keys(valueSets[0]).forEach(columnProperty => {
|
||||
const column = this.expressionMap.mainAlias!.metadata.findColumnWithPropertyName(columnProperty);
|
||||
if (column) insertColumns.push(column);
|
||||
});
|
||||
|
||||
// get values needs to be inserted
|
||||
const values = valueSets.map((valueSet, key) => {
|
||||
const columnNames = insertColumns.map(column => {
|
||||
const paramName = ":_inserted_" + key + "_" + column.databaseName;
|
||||
this.setParameter(paramName, valueSet[column.propertyName]);
|
||||
return paramName;
|
||||
});
|
||||
return "(" + columnNames.join(",") + ")";
|
||||
}).join(", ");
|
||||
|
||||
// get a table name and all column database names
|
||||
const tableName = this.escapeTable(this.getTableName());
|
||||
const columnNames = insertColumns.map(column => this.escapeColumn(column.databaseName)).join(", ");
|
||||
|
||||
// generate sql query
|
||||
return `INSERT INTO ${tableName}(${columnNames}) VALUES ${values}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets array of values need to be inserted into the target table.
|
||||
*/
|
||||
protected getValueSets(): ObjectLiteral[] {
|
||||
if (this.expressionMap.valuesSet instanceof Array && this.expressionMap.valuesSet.length > 0)
|
||||
return this.expressionMap.valuesSet;
|
||||
|
||||
if (this.expressionMap.valuesSet instanceof Object)
|
||||
return [this.expressionMap.valuesSet];
|
||||
|
||||
throw new Error(`Cannot perform insert query because values are not defined. Call "qb.values(...)" method to specify inserted values.`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -100,6 +100,15 @@ export abstract class QueryBuilder<Entity> {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Abstract Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets generated sql query without parameters being replaced.
|
||||
*/
|
||||
abstract getQuery(): string;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Accessors
|
||||
// -------------------------------------------------------------------------
|
||||
@ -274,22 +283,6 @@ export abstract class QueryBuilder<Entity> {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets generated sql query without parameters being replaced.
|
||||
*/
|
||||
getQuery(): string {
|
||||
let sql = this.createSelectExpression();
|
||||
sql += this.createJoinExpression();
|
||||
sql += this.createWhereExpression();
|
||||
sql += this.createGroupByExpression();
|
||||
sql += this.createHavingExpression();
|
||||
sql += this.createOrderByExpression();
|
||||
sql += this.createLimitOffsetExpression();
|
||||
sql += this.createLockExpression();
|
||||
sql = this.createLimitOffsetOracleSpecificExpression(sql);
|
||||
return sql.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints sql to stdout using console.log.
|
||||
*/
|
||||
@ -309,18 +302,8 @@ export abstract class QueryBuilder<Entity> {
|
||||
/**
|
||||
* Gets sql to be executed with all parameters used in it.
|
||||
*/
|
||||
getSqlAndParameters(options?: { skipOrderBy?: boolean }): [string, any[]] {
|
||||
let sql = this.createSelectExpression();
|
||||
sql += this.createJoinExpression();
|
||||
sql += this.createWhereExpression();
|
||||
sql += this.createGroupByExpression();
|
||||
sql += this.createHavingExpression();
|
||||
if (!options || !options.skipOrderBy)
|
||||
sql += this.createOrderByExpression();
|
||||
sql += this.createLimitOffsetExpression();
|
||||
sql += this.createLockExpression();
|
||||
sql = this.createLimitOffsetOracleSpecificExpression(sql);
|
||||
return this.connection.driver.escapeQueryWithParameters(sql, this.getParameters());
|
||||
getSqlAndParameters(): [string, any[]] {
|
||||
return this.connection.driver.escapeQueryWithParameters(this.getQuery(), this.getParameters());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -548,56 +531,11 @@ export abstract class QueryBuilder<Entity> {
|
||||
}
|
||||
|
||||
// create a selection query
|
||||
switch (this.expressionMap.queryType) {
|
||||
case "select":
|
||||
const selection = allSelects.map(select => select.selection + (select.aliasName ? " AS " + ea(select.aliasName) : "")).join(", ");
|
||||
if ((this.expressionMap.limit || this.expressionMap.offset) && this.connection.driver instanceof OracleDriver) {
|
||||
return "SELECT ROWNUM " + this.escapeAlias("RN") + "," + selection + " FROM " + this.escapeTable(tableName) + " " + ea(aliasName) + lock;
|
||||
}
|
||||
return "SELECT " + selection + " FROM " + this.escapeTable(tableName) + " " + ea(aliasName) + lock;
|
||||
case "delete":
|
||||
return "DELETE FROM " + et(tableName); // 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 // todo: replace aliases in where to nothing?
|
||||
case "update":
|
||||
let valuesSet = this.expressionMap.valuesSet as ObjectLiteral;
|
||||
if (!valuesSet)
|
||||
throw new Error(`Cannot perform update query because updation values are not defined.`);
|
||||
|
||||
const updateSet: string[] = [];
|
||||
Object.keys(valuesSet).forEach(columnProperty => {
|
||||
const column = this.expressionMap.mainAlias!.metadata.findColumnWithPropertyName(columnProperty);
|
||||
if (column) {
|
||||
const paramName = "_updated_" + column.databaseName;
|
||||
this.setParameter(paramName, valuesSet[column.propertyName]);
|
||||
updateSet.push(ea(column.databaseName) + "=:" + paramName);
|
||||
}
|
||||
});
|
||||
return `UPDATE ${this.escapeTable(tableName)} ${aliasName ? ea(aliasName) : ""} SET ${updateSet.join(", ")}`; // todo: replace aliases in where to nothing?
|
||||
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 columns: ColumnMetadata[] = [];
|
||||
Object.keys(valuesSets[0]).forEach(columnProperty => {
|
||||
const column = this.expressionMap.mainAlias!.metadata.findColumnWithPropertyName(columnProperty);
|
||||
if (column) columns.push(column);
|
||||
});
|
||||
const values = valuesSets.map((valueSet, key) => {
|
||||
return "(" + columns.map(column => {
|
||||
const paramName = ":_inserted_" + key + "_" + column.databaseName;
|
||||
this.setParameter(paramName, valueSet[column.propertyName]);
|
||||
return paramName;
|
||||
}).join(",") + ")";
|
||||
}).join(", ");
|
||||
return `INSERT INTO ${this.escapeTable(tableName)}(${columns.map(column => column.databaseName)}) VALUES ${values}`;
|
||||
const selection = allSelects.map(select => select.selection + (select.aliasName ? " AS " + ea(select.aliasName) : "")).join(", ");
|
||||
if ((this.expressionMap.limit || this.expressionMap.offset) && this.connection.driver instanceof OracleDriver) {
|
||||
return "SELECT ROWNUM " + this.escapeAlias("RN") + "," + selection + " FROM " + this.escapeTable(tableName) + " " + ea(aliasName) + lock;
|
||||
}
|
||||
|
||||
throw new Error("No query builder type is specified.");
|
||||
return "SELECT " + selection + " FROM " + this.escapeTable(tableName) + " " + ea(aliasName) + lock;
|
||||
}
|
||||
|
||||
protected createHavingExpression() {
|
||||
@ -862,6 +800,9 @@ export abstract class QueryBuilder<Entity> {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds lock expression to a query.
|
||||
*/
|
||||
protected createLockExpression(): string {
|
||||
switch (this.expressionMap.lockMode) {
|
||||
case "pessimistic_read":
|
||||
@ -932,4 +873,17 @@ export abstract class QueryBuilder<Entity> {
|
||||
return [whereString, parameters];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets name of the table where insert should be performed.
|
||||
*/
|
||||
protected getTableName(): string {
|
||||
if (!this.expressionMap.mainAlias)
|
||||
throw new Error(`Entity where values should be inserted is not specified. Call "qb.into(entity)" method to specify it.`);
|
||||
|
||||
if (this.expressionMap.mainAlias.hasMetadata)
|
||||
return this.expressionMap.mainAlias.metadata.tableName;
|
||||
|
||||
return this.expressionMap.mainAlias.tableName!;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -7,6 +7,17 @@ import {QueryBuilder} from "./QueryBuilder";
|
||||
*/
|
||||
export class RelationQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Implemented Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets generated sql query without parameters being replaced.
|
||||
*/
|
||||
getQuery(): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -24,6 +24,26 @@ import {ReadStream} from "fs";
|
||||
*/
|
||||
export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Implemented Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets generated sql query without parameters being replaced.
|
||||
*/
|
||||
getQuery(): string {
|
||||
let sql = this.createSelectExpression();
|
||||
sql += this.createJoinExpression();
|
||||
sql += this.createWhereExpression();
|
||||
sql += this.createGroupByExpression();
|
||||
sql += this.createHavingExpression();
|
||||
sql += this.createOrderByExpression();
|
||||
sql += this.createLimitOffsetExpression();
|
||||
sql += this.createLockExpression();
|
||||
sql = this.createLimitOffsetOracleSpecificExpression(sql);
|
||||
return sql.trim();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
@ -509,15 +529,17 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
* 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.
|
||||
*
|
||||
* Calling order by without order set will remove all previously set order bys.
|
||||
*/
|
||||
orderBy(sort: string, order?: "ASC"|"DESC"): this;
|
||||
orderBy(): 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;
|
||||
orderBy(sort: string, order?: "ASC"|"DESC"): this;
|
||||
|
||||
/**
|
||||
* Sets ORDER BY condition in the query builder.
|
||||
@ -829,7 +851,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
}).join(", ") + ") as \"cnt\"";
|
||||
|
||||
const countQueryBuilder = new SelectQueryBuilder(this)
|
||||
.orderBy(undefined)
|
||||
.orderBy()
|
||||
.offset(undefined)
|
||||
.limit(undefined)
|
||||
.select(countSql);
|
||||
@ -880,7 +902,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
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.getSqlAndParameters({ skipOrderBy: true });
|
||||
const [sql, parameters] = this/*.clone().orderBy()*/.getSqlAndParameters();
|
||||
const [selects, orderBys] = this.createOrderByCombinedWithSelectExpression("distinctAlias");
|
||||
|
||||
const distinctAlias = this.escapeTable("distinctAlias");
|
||||
|
||||
@ -6,6 +6,19 @@ import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
*/
|
||||
export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Implemented Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets generated sql query without parameters being replaced.
|
||||
*/
|
||||
getQuery(): string {
|
||||
let sql = this.createUpdateExpression();
|
||||
sql += this.createWhereExpression();
|
||||
return sql.trim();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
@ -49,4 +62,41 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
return this;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates UPDATE express used to perform insert query.
|
||||
*/
|
||||
protected createUpdateExpression() {
|
||||
const valuesSet = this.getValueSets();
|
||||
|
||||
const updateColumnAndValues: string[] = [];
|
||||
Object.keys(valuesSet).forEach(columnProperty => {
|
||||
const column = this.expressionMap.mainAlias!.metadata.findColumnWithPropertyName(columnProperty);
|
||||
if (column) {
|
||||
const paramName = "_updated_" + column.databaseName;
|
||||
this.setParameter(paramName, valuesSet[column.propertyName]);
|
||||
updateColumnAndValues.push(this.escapeAlias(column.databaseName) + "=:" + paramName);
|
||||
}
|
||||
});
|
||||
|
||||
// get a table name and all column database names
|
||||
const tableName = this.escapeTable(this.getTableName());
|
||||
|
||||
// generate and return sql update query
|
||||
return `UPDATE ${tableName} SET ${updateColumnAndValues.join(", ")}`; // todo: how do we replace aliases in where to nothing?
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets array of values need to be inserted into the target table.
|
||||
*/
|
||||
protected getValueSets(): ObjectLiteral {
|
||||
if (this.expressionMap.valuesSet instanceof Object)
|
||||
return this.expressionMap.valuesSet;
|
||||
|
||||
throw new Error(`Cannot perform update query because update values are not defined. Call "qb.set(...)" method to specify inserted values.`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user