mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
moved update/delete/insert operations from persistoperationexecuter into driver specifics; made query builder to support delete and update operations
This commit is contained in:
parent
e530d719b0
commit
a5956a5f62
@ -32,6 +32,7 @@ export class Connection {
|
||||
constructor(name: string, driver: Driver) {
|
||||
this._name = name;
|
||||
this._driver = driver;
|
||||
this._driver.connection = this;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -17,11 +17,16 @@ export interface Driver {
|
||||
* Gets database name to which this connection is made.
|
||||
*/
|
||||
db: string;
|
||||
|
||||
/**
|
||||
* Connection used in this driver.
|
||||
*/
|
||||
connection: Connection;
|
||||
|
||||
/**
|
||||
* Creates a query builder which can be used to build an sql queries.
|
||||
*/
|
||||
createQueryBuilder<Entity>(connection: Connection): QueryBuilder<Entity>;
|
||||
createQueryBuilder<Entity>(): QueryBuilder<Entity>;
|
||||
|
||||
/**
|
||||
* Creates a schema builder which can be used to build database/table schemas.
|
||||
@ -48,4 +53,19 @@ export interface Driver {
|
||||
*/
|
||||
clearDatabase(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Updates rows that match given conditions in the given table.
|
||||
*/
|
||||
update(tableName: string, valuesMap: Object, conditions: Object): Promise<void>;
|
||||
|
||||
/**
|
||||
* Insert a new row into given table.
|
||||
*/
|
||||
insert(tableName: string, valuesMap: Object): Promise<any>;
|
||||
|
||||
/**
|
||||
* Insert a new row into given table.
|
||||
*/
|
||||
delete(tableName: string, conditions: Object): Promise<void>;
|
||||
|
||||
}
|
||||
@ -3,7 +3,6 @@ import {ConnectionOptions} from "../connection/ConnectionOptions";
|
||||
import {SchemaBuilder} from "../schema-builder/SchemaBuilder";
|
||||
import {QueryBuilder} from "../query-builder/QueryBuilder";
|
||||
import {MysqlSchemaBuilder} from "../schema-builder/MysqlSchemaBuilder";
|
||||
import {EntityMetadata} from "../metadata-builder/metadata/EntityMetadata";
|
||||
import {Connection} from "../connection/Connection";
|
||||
|
||||
/**
|
||||
@ -15,8 +14,10 @@ export class MysqlDriver implements Driver {
|
||||
// Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
connection: Connection;
|
||||
|
||||
private mysql: any;
|
||||
private connection: any;
|
||||
private mysqlConnection: any;
|
||||
private connectionOptions: ConnectionOptions;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -46,8 +47,8 @@ export class MysqlDriver implements Driver {
|
||||
/**
|
||||
* Creates a query builder which can be used to build an sql queries.
|
||||
*/
|
||||
createQueryBuilder<Entity>(connection: Connection): QueryBuilder<Entity> {
|
||||
return new QueryBuilder<Entity>(connection);
|
||||
createQueryBuilder<Entity>(): QueryBuilder<Entity> {
|
||||
return new QueryBuilder<Entity>(this.connection);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,24 +63,24 @@ export class MysqlDriver implements Driver {
|
||||
*/
|
||||
connect(options: ConnectionOptions): Promise<void> {
|
||||
this.connectionOptions = options;
|
||||
this.connection = this.mysql.createConnection({
|
||||
this.mysqlConnection = this.mysql.createConnection({
|
||||
host: options.host,
|
||||
user: options.username,
|
||||
password: options.password,
|
||||
database: options.database
|
||||
});
|
||||
return new Promise<void>((ok, fail) => this.connection.connect((err: any) => err ? fail(err) : ok()));
|
||||
return new Promise<void>((ok, fail) => this.mysqlConnection.connect((err: any) => err ? fail(err) : ok()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes connection with database.
|
||||
*/
|
||||
disconnect(): Promise<void> {
|
||||
if (!this.connection)
|
||||
if (!this.mysqlConnection)
|
||||
throw new Error("Connection is not established, cannot disconnect.");
|
||||
|
||||
return new Promise<void>((ok, fail) => {
|
||||
this.connection.end((err: any) => err ? fail(err) : ok());
|
||||
this.mysqlConnection.end((err: any) => err ? fail(err) : ok());
|
||||
});
|
||||
}
|
||||
|
||||
@ -87,9 +88,9 @@ export class MysqlDriver implements Driver {
|
||||
* Executes a given SQL query.
|
||||
*/
|
||||
query<T>(query: string): Promise<T> {
|
||||
if (!this.connection) throw new Error("Connection is not established, cannot execute a query.");
|
||||
console.info("executing:", query);
|
||||
return new Promise<any>((ok, fail) => this.connection.query(query, (err: any, result: any) => {
|
||||
if (!this.mysqlConnection) throw new Error("Connection is not established, cannot execute a query.");
|
||||
// console.info("executing:", query);
|
||||
return new Promise<any>((ok, fail) => this.mysqlConnection.query(query, (err: any, result: any) => {
|
||||
if (err) {
|
||||
console.error("query failed: ", query);
|
||||
fail(err);
|
||||
@ -103,9 +104,9 @@ export class MysqlDriver implements Driver {
|
||||
* Clears all tables in the currently connected database.
|
||||
*/
|
||||
clearDatabase(): Promise<void> {
|
||||
if (!this.connection) throw new Error("Connection is not established, cannot execute a query.");
|
||||
if (!this.mysqlConnection) throw new Error("Connection is not established, cannot execute a query.");
|
||||
|
||||
// todo: omprize and make coder better
|
||||
// todo: optrize and make coder better
|
||||
|
||||
const query1 = `SET FOREIGN_KEY_CHECKS = 0;`;
|
||||
const query2 = `SELECT concat('DROP TABLE IF EXISTS ', table_name, ';') AS q FROM information_schema.tables WHERE table_schema = '${this.connectionOptions.database}';`;
|
||||
@ -121,4 +122,32 @@ export class MysqlDriver implements Driver {
|
||||
.then(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates rows that match given conditions in the given table.
|
||||
*/
|
||||
update(tableName: string, valuesMap: Object, conditions: Object): Promise<void> {
|
||||
const qb = this.createQueryBuilder().update(tableName, valuesMap).from(tableName, "t");
|
||||
Object.keys(conditions).forEach(key => qb.andWhere(key + "=:" + key, { [key]: (<any> conditions)[key] }));
|
||||
return qb.execute().then(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new row into given table.
|
||||
*/
|
||||
insert(tableName: string, keyValues: Object): Promise<any> {
|
||||
const columns = Object.keys(keyValues).join(",");
|
||||
const values = Object.keys(keyValues).map(key => (<any> keyValues)[key]).join(",");
|
||||
const query = `INSERT INTO ${tableName}(${columns}) VALUES (${values})`;
|
||||
return this.query(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
delete(tableName: string, conditions: Object): Promise<void> {
|
||||
const qb = this.createQueryBuilder().delete(tableName);
|
||||
Object.keys(conditions).forEach(key => qb.andWhere(key + "=:" + key, { [key]: (<any> conditions)[key] }));
|
||||
return qb.execute().then(() => {});
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,6 +7,9 @@ import {InsertOperation} from "./operation/InsertOperation";
|
||||
import {JunctionRemoveOperation} from "./operation/JunctionRemoveOperation";
|
||||
import {UpdateByRelationOperation} from "./operation/UpdateByRelationOperation";
|
||||
|
||||
/**
|
||||
* Executes PersistOperation in the given connection.
|
||||
*/
|
||||
export class PersistOperationExecutor {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -20,6 +23,9 @@ export class PersistOperationExecutor {
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Executes given persist operation.
|
||||
*/
|
||||
executePersistOperation(persistOperation: PersistOperation) {
|
||||
return Promise.resolve()
|
||||
.then(() => this.executeInsertOperations(persistOperation))
|
||||
@ -130,32 +136,32 @@ export class PersistOperationExecutor {
|
||||
idColumn = metadata.primaryColumn.name;
|
||||
id = operation.targetEntity[metadata.primaryColumn.propertyName] || idInInserts;
|
||||
}
|
||||
const query = `UPDATE ${tableName} SET ${relationName}='${relationId}' WHERE ${idColumn}='${id}'`;
|
||||
return this.connection.driver.query(query);
|
||||
return this.connection.driver.update(tableName, { [relationName]: relationId }, { [idColumn]: id });
|
||||
}
|
||||
|
||||
private update(updateOperation: UpdateOperation) {
|
||||
const entity = updateOperation.entity;
|
||||
const metadata = this.connection.getMetadata(entity.constructor);
|
||||
const values = updateOperation.columns.map(column => {
|
||||
return column.name + "='" + entity[column.propertyName] + "'";
|
||||
});
|
||||
|
||||
const query = `UPDATE ${metadata.table.name} SET ${values} WHERE ${metadata.primaryColumn.name}='${metadata.getEntityId(entity)}'` ;
|
||||
return this.connection.driver.query(query);
|
||||
const values = updateOperation.columns.reduce((object, column) => {
|
||||
(<any> object)[column.name] = entity[column.propertyName];
|
||||
return object;
|
||||
}, {});
|
||||
return this.connection.driver.update(metadata.table.name, values, { [metadata.primaryColumn.name]: metadata.getEntityId(entity) });
|
||||
}
|
||||
|
||||
private updateDeletedRelations(removeOperation: RemoveOperation) { // todo: check if both many-to-one deletions work too
|
||||
if (removeOperation.relation.isManyToMany || removeOperation.relation.isOneToMany) return;
|
||||
const value = removeOperation.relation.name + "=NULL";
|
||||
const query = `UPDATE ${removeOperation.metadata.table.name} SET ${value} WHERE ${removeOperation.metadata.primaryColumn.name}='${removeOperation.fromEntityId}'` ;
|
||||
return this.connection.driver.query(query);
|
||||
|
||||
return this.connection.driver.update(
|
||||
removeOperation.metadata.table.name,
|
||||
{ [removeOperation.relation.name]: null },
|
||||
{ [removeOperation.metadata.primaryColumn.name]: removeOperation.fromEntityId }
|
||||
);
|
||||
}
|
||||
|
||||
private delete(entity: any) {
|
||||
const metadata = this.connection.getMetadata(entity.constructor);
|
||||
const query = `DELETE FROM ${metadata.table.name} WHERE ${metadata.primaryColumn.name}='${entity[metadata.primaryColumn.propertyName]}'`;
|
||||
return this.connection.driver.query(query);
|
||||
return this.connection.driver.delete(metadata.table.name, { [metadata.primaryColumn.name]: entity[metadata.primaryColumn.propertyName] });
|
||||
}
|
||||
|
||||
private insert(entity: any) {
|
||||
@ -180,8 +186,10 @@ export class PersistOperationExecutor {
|
||||
.filter(relation => entity[relation.propertyName].hasOwnProperty(relation.relatedEntityMetadata.primaryColumn.name))
|
||||
.map(relation => "'" + entity[relation.propertyName][relation.relatedEntityMetadata.primaryColumn.name] + "'");
|
||||
|
||||
const query = `INSERT INTO ${metadata.table.name}(${columns.concat(relationColumns).join(",")}) VALUES (${values.concat(relationValues).join(",")})`;
|
||||
return this.connection.driver.query(query);
|
||||
const allColumns = columns.concat(relationColumns);
|
||||
const allValues = values.concat(relationValues);
|
||||
|
||||
return this.connection.driver.insert(metadata.table.name, this.zipObject(allColumns, allValues));
|
||||
}
|
||||
|
||||
private insertJunctions(junctionOperation: JunctionInsertOperation, insertOperations: InsertOperation[]) {
|
||||
@ -192,9 +200,7 @@ export class PersistOperationExecutor {
|
||||
const id1 = junctionOperation.entity1[metadata1.primaryColumn.name] || insertOperations.find(o => o.entity === junctionOperation.entity1).entityId;
|
||||
const id2 = junctionOperation.entity2[metadata2.primaryColumn.name] || insertOperations.find(o => o.entity === junctionOperation.entity2).entityId;
|
||||
const values = [id1, id2]; // todo: order may differ, find solution (column.table to compare with entity metadata table?)
|
||||
|
||||
const query = `INSERT INTO ${junctionMetadata.table.name}(${columns.join(",")}) VALUES (${values.join(",")})`;
|
||||
return this.connection.driver.query(query);
|
||||
return this.connection.driver.insert(junctionMetadata.table.name, this.zipObject(columns, values));
|
||||
}
|
||||
|
||||
private removeJunctions(junctionOperation: JunctionRemoveOperation) {
|
||||
@ -204,8 +210,14 @@ export class PersistOperationExecutor {
|
||||
const columns = junctionMetadata.columns.map(column => column.name);
|
||||
const id1 = junctionOperation.entity1[metadata1.primaryColumn.name];
|
||||
const id2 = junctionOperation.entity2[metadata2.primaryColumn.name];
|
||||
const query = `DELETE FROM ${junctionMetadata.table.name} WHERE ${columns[0]}='${id1}' AND ${columns[1]}='${id2}'`;
|
||||
return this.connection.driver.query(query);
|
||||
return this.connection.driver.delete(junctionMetadata.table.name, { [columns[0]]: id1, [columns[1]]: id2 });
|
||||
}
|
||||
|
||||
private zipObject(keys: any[], values: any[]): Object {
|
||||
return keys.reduce((object, column, index) => {
|
||||
(<any> object)[column] = values[index];
|
||||
return object;
|
||||
}, {});
|
||||
}
|
||||
|
||||
}
|
||||
@ -19,7 +19,9 @@ export class QueryBuilder<Entity> {
|
||||
private _aliasMap: AliasMap;
|
||||
private type: "select"|"update"|"delete";
|
||||
private selects: string[] = [];
|
||||
private froms: { alias: Alias };
|
||||
private fromEntity: { alias: Alias };
|
||||
private fromTableName: string;
|
||||
private updateQuerySet: Object;
|
||||
private joins: Join[] = [];
|
||||
private groupBys: string[] = [];
|
||||
private wheres: { type: "simple"|"and"|"or", condition: string }[] = [];
|
||||
@ -49,13 +51,43 @@ export class QueryBuilder<Entity> {
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
delete(): this {
|
||||
delete(entity?: Function): this;
|
||||
delete(tableName?: string): this;
|
||||
delete(tableNameOrEntity?: string|Function): this {
|
||||
if (tableNameOrEntity instanceof Function) {
|
||||
const aliasName = (<any> tableNameOrEntity).name;
|
||||
const aliasObj = new Alias(aliasName);
|
||||
aliasObj.target = <Function> tableNameOrEntity;
|
||||
this._aliasMap.addMainAlias(aliasObj);
|
||||
this.fromEntity = { alias: aliasObj };
|
||||
} else if (typeof tableNameOrEntity === "string") {
|
||||
this.fromTableName = <string> tableNameOrEntity;
|
||||
}
|
||||
|
||||
this.type = "delete";
|
||||
return this;
|
||||
}
|
||||
|
||||
update(): this {
|
||||
update(updateSet: Object): this;
|
||||
update(entity: Function, updateSet: Object): this;
|
||||
update(tableName: string, updateSet: Object): this;
|
||||
update(tableNameOrEntityOrUpdateSet?: string|Function, updateSet?: Object): this {
|
||||
if (tableNameOrEntityOrUpdateSet instanceof Function) {
|
||||
const aliasName = (<any> tableNameOrEntityOrUpdateSet).name;
|
||||
const aliasObj = new Alias(aliasName);
|
||||
aliasObj.target = <Function> tableNameOrEntityOrUpdateSet;
|
||||
this._aliasMap.addMainAlias(aliasObj);
|
||||
this.fromEntity = { alias: aliasObj };
|
||||
|
||||
} else if (typeof tableNameOrEntityOrUpdateSet === "object") {
|
||||
updateSet = <Object> tableNameOrEntityOrUpdateSet;
|
||||
|
||||
} else if (typeof tableNameOrEntityOrUpdateSet === "string") {
|
||||
this.fromTableName = <string> tableNameOrEntityOrUpdateSet;
|
||||
}
|
||||
|
||||
this.type = "update";
|
||||
this.updateQuerySet = updateSet;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -86,13 +118,17 @@ export class QueryBuilder<Entity> {
|
||||
return this;
|
||||
}
|
||||
|
||||
//from(tableName: string, alias: string): this;
|
||||
from(entity: Function, alias?: string): this {
|
||||
//from(entityOrTableName: Function|string, alias: string): this {
|
||||
const aliasObj = new Alias(alias);
|
||||
aliasObj.target = entity;
|
||||
this._aliasMap.addMainAlias(aliasObj);
|
||||
this.froms = { alias: aliasObj };
|
||||
from(tableName: string, alias: string): this;
|
||||
from(entity: Function, alias?: string): this;
|
||||
from(entityOrTableName: Function|string, alias: string): this {
|
||||
if (entityOrTableName instanceof Function) {
|
||||
const aliasObj = new Alias(alias);
|
||||
aliasObj.target = <Function> entityOrTableName;
|
||||
this._aliasMap.addMainAlias(aliasObj);
|
||||
this.fromEntity = { alias: aliasObj };
|
||||
} else {
|
||||
this.fromTableName = <string> entityOrTableName;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -233,8 +269,8 @@ export class QueryBuilder<Entity> {
|
||||
return sql;
|
||||
}
|
||||
|
||||
execute(): Promise<void> {
|
||||
return this.connection.driver.query(this.getSql()).then(() => {});
|
||||
execute(): Promise<any> {
|
||||
return this.connection.driver.query(this.getSql());
|
||||
}
|
||||
|
||||
getScalarResults<T>(): Promise<T[]> {
|
||||
@ -266,16 +302,28 @@ export class QueryBuilder<Entity> {
|
||||
|
||||
protected createSelectExpression() {
|
||||
// todo throw exception if selects or from is missing
|
||||
const metadata = this._aliasMap.getEntityMetadataByAlias(this.froms.alias);
|
||||
const tableName = metadata.table.name;
|
||||
const alias = this.froms.alias.name;
|
||||
const allSelects: string[] = [];
|
||||
|
||||
// add select from the main table
|
||||
if (this.selects.indexOf(alias) !== -1)
|
||||
metadata.columns.forEach(column => {
|
||||
allSelects.push(alias + "." + column.name + " AS " + alias + "_" + column.name);
|
||||
});
|
||||
let alias: string, tableName: string;
|
||||
const allSelects: string[] = [];
|
||||
|
||||
if (this.fromEntity) {
|
||||
const metadata = this._aliasMap.getEntityMetadataByAlias(this.fromEntity.alias);
|
||||
tableName = metadata.table.name;
|
||||
alias = this.fromEntity.alias.name;
|
||||
|
||||
// add select from the main table
|
||||
if (this.selects.indexOf(alias) !== -1) {
|
||||
metadata.columns.forEach(column => {
|
||||
allSelects.push(alias + "." + column.name + " AS " + alias + "_" + column.name);
|
||||
});
|
||||
}
|
||||
|
||||
} else if (this.fromTableName) {
|
||||
tableName = this.fromTableName;
|
||||
|
||||
} else {
|
||||
throw new Error("No from given");
|
||||
}
|
||||
|
||||
// add selects from joins
|
||||
this.joins
|
||||
@ -292,13 +340,24 @@ export class QueryBuilder<Entity> {
|
||||
return select !== alias && !this.joins.find(join => join.alias.name === select);
|
||||
}).forEach(select => allSelects.push(select));
|
||||
|
||||
// if still selection is empty, then simply set it to all (*)
|
||||
if (allSelects.length === 0)
|
||||
allSelects.push("*");
|
||||
|
||||
// create a selection query
|
||||
switch (this.type) {
|
||||
case "select":
|
||||
return "SELECT " + allSelects.join(", ") + " FROM " + tableName + " " + alias;
|
||||
case "update":
|
||||
return "UPDATE " + tableName + " " + alias;
|
||||
case "delete":
|
||||
return "DELETE " + tableName + " " + alias;
|
||||
return "DELETE FROM " + tableName + " " + (alias ? alias : "");
|
||||
case "update":
|
||||
const updateSet = Object.keys(this.updateQuerySet).map(key => key + "=:updateQuerySet_" + key);
|
||||
const params = Object.keys(this.updateQuerySet).reduce((object, key) => {
|
||||
(<any> object)["updateQuerySet_" + key] = (<any> this.updateQuerySet)[key];
|
||||
return object;
|
||||
}, {});
|
||||
this.addParameters(params);
|
||||
return "UPDATE " + tableName + " " + (alias ? alias : "") + " SET " + updateSet;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@ -306,12 +365,12 @@ export class QueryBuilder<Entity> {
|
||||
protected createWhereExpression() {
|
||||
if (!this.wheres || !this.wheres.length) return "";
|
||||
|
||||
return " WHERE " + this.wheres.map(where => {
|
||||
return " WHERE " + this.wheres.map((where, index) => {
|
||||
switch (where.type) {
|
||||
case "and":
|
||||
return "AND " + where.condition;
|
||||
return (index > 0 ? "AND " : "") + where.condition;
|
||||
case "or":
|
||||
return "OR " + where.condition;
|
||||
return (index > 0 ? "OR " : "") + where.condition;
|
||||
default:
|
||||
return where.condition;
|
||||
}
|
||||
@ -392,7 +451,8 @@ export class QueryBuilder<Entity> {
|
||||
|
||||
protected replaceParameters(sql: string) {
|
||||
Object.keys(this.parameters).forEach(key => {
|
||||
sql = sql.replace(":" + key, '"' + this.parameters[key] + '"'); // .replace('"', '')
|
||||
const value = this.parameters[key] !== null && this.parameters[key] !== undefined ? '"' + this.parameters[key] + '"' : "NULL";
|
||||
sql = sql.replace(":" + key, value); // .replace('"', '')
|
||||
});
|
||||
return sql;
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ export class Repository<Entity> {
|
||||
*/
|
||||
createQueryBuilder(alias: string): QueryBuilder<Entity> {
|
||||
return this.connection.driver
|
||||
.createQueryBuilder<Entity>(this.connection)
|
||||
.createQueryBuilder<Entity>()
|
||||
.select(alias)
|
||||
.from(this.metadata.target, alias);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user