fixes in query builder - insert and update methods

This commit is contained in:
Umed Khudoiberdiev 2017-06-20 22:44:34 +05:00
parent 22c9a2a8d6
commit e952161f41
6 changed files with 45 additions and 44 deletions

View File

@ -51,6 +51,8 @@ More env variable names you can find in `ConnectionOptionsEnvReader` class.
* fixed how orm creates default values for SqlServer - now it creates constraints for it as well
* migrations interface has changed - now `up` and `down` accept only `QueryRunner`. To use `Connection` and `EntityManager` use properties
of `QueryRunner`, e.g. `queryRunner.connection` and `queryRunner.manager`
* now `update` method in `QueryBuilder` accepts `Partial<Entity>` and property names used in update map are
column property names and they are automatically mapped to column names
### DEPRECATIONS
@ -67,6 +69,8 @@ of `QueryRunner`, e.g. `queryRunner.connection` and `queryRunner.manager`
* now ormconfig is read from `.env`, `.js`, `.json`, `.yml`, `.xml` formats
* all database-specific types are supported now
* now migrations generation is supported. Use `typeorm migrations:generate` command
* `getGeneratedQuery` was renamed to `getQuery` in `QueryBuilder`
* `getSqlWithParameters` was renamed to `getSqlAndParameters` in `QueryBuilder`
### OTHER API CHANGES

View File

@ -443,6 +443,13 @@ export class EntityMetadata {
return this.compareIds(firstEntityIds, secondEntityIds);
}
/**
* Finds column with a given property name.
*/
findColumnWithPropertyName(propertyName: string) {
return this.columns.find(column => column.propertyName === propertyName);
}
/**
* Finds relation with the given name.
*/
@ -603,5 +610,4 @@ export class EntityMetadata {
this.relations.forEach(relation => OrmUtils.mergeDeep(map, relation.createValueMap(relation.propertyPath)));
return map;
}
}

View File

@ -11,8 +11,7 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
// -------------------------------------------------------------------------
/**
* Specifies FROM which entity's table select/update/delete will be executed.
* Also sets a main string alias of the selection data.
* Specifies INTO which entity's table insertion will be executed.
*/
into(entityTarget: Function|string): this {
return this.setMainAlias(entityTarget);

View File

@ -261,7 +261,7 @@ export abstract class QueryBuilder<Entity> {
}
/**
* Sets given parameter's value.
* Sets parameter name and its value.
*/
setParameter(key: string, value: any): this {
this.expressionMap.parameters[key] = value;
@ -304,23 +304,13 @@ export abstract class QueryBuilder<Entity> {
* Parameters in the query are escaped for the currently used driver.
*/
getSql(): 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);
[sql] = this.connection.driver.escapeQueryWithParameters(sql, this.expressionMap.parameters);
return sql.trim();
return this.connection.driver.escapeQueryWithParameters(this.getQuery(), this.expressionMap.parameters)[0];
}
/**
* Gets generated sql without parameters being replaced.
* Gets generated sql query without parameters being replaced.
*/
getGeneratedQuery(): string {
getQuery(): string {
let sql = this.createSelectExpression();
sql += this.createJoinExpression();
sql += this.createWhereExpression();
@ -336,7 +326,7 @@ export abstract class QueryBuilder<Entity> {
/**
* Gets sql to be executed with all parameters used in it.
*/
getSqlWithParameters(options?: { skipOrderBy?: boolean }): [string, any[]] {
getSqlAndParameters(options?: { skipOrderBy?: boolean }): [string, any[]] {
let sql = this.createSelectExpression();
sql += this.createJoinExpression();
sql += this.createWhereExpression();
@ -354,7 +344,7 @@ export abstract class QueryBuilder<Entity> {
* Executes sql generated by query builder and returns raw database results.
*/
async execute(): Promise<any> {
const [sql, parameters] = this.getSqlWithParameters();
const [sql, parameters] = this.getSqlAndParameters();
try {
return await this.queryRunner.query(sql, parameters); // await is needed here because we are using finally
@ -564,21 +554,22 @@ export abstract class QueryBuilder<Entity> {
}
return "SELECT " + selection + " FROM " + this.escapeTable(tableName) + " " + ea(aliasName) + lock;
case "delete":
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
return "DELETE FROM " + et(tableName) + this.createWhereExpression(); // 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 = 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] = valuesSet[key];
return object;
}, {} as ObjectLiteral);
this.setParameters(params);
return "UPDATE " + this.escapeTable(tableName) + " " + (aliasName ? ea(aliasName) : "") + " SET " + updateSet;
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(", ")}` + this.createWhereExpression(); // 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) {
@ -589,18 +580,19 @@ export abstract class QueryBuilder<Entity> {
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 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 "(" + Object.keys(valueSet).map(columnName => {
const paramName = ":inserted_" + key + "_" + columnName;
parameters[paramName] = valueSet[columnName];
return paramName;
}).join(",") + ")";
return "(" + columns.map(column => {
const paramName = ":inserted_" + key + "_" + column.databaseName;
this.setParameter(paramName, valueSet[column.propertyName]);
return paramName;
}).join(",") + ")";
}).join(", ");
this.setParameters(parameters);
return `INSERT INTO ${this.escapeTable(tableName)}(${columns}) VALUES ${values}`;
return `INSERT INTO ${this.escapeTable(tableName)}(${columns.map(column => column.databaseName)}) VALUES ${values}`;
}
throw new Error("No query builder type is specified.");

View File

@ -816,7 +816,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
.select(countSql);
countQueryBuilder.expressionMap.ignoreParentTablesJoins = true;
const [countQuerySql, countQueryParameters] = countQueryBuilder.getSqlWithParameters();
const [countQuerySql, countQueryParameters] = countQueryBuilder.getSqlAndParameters();
try {
const results = await this.queryRunner.query(countQuerySql, countQueryParameters);
@ -861,7 +861,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.getSqlWithParameters({ skipOrderBy: true });
const [sql, parameters] = this.getSqlAndParameters({ skipOrderBy: true });
const [selects, orderBys] = this.createOrderByCombinedWithSelectExpression("distinctAlias");
const distinctAlias = this.escapeTable("distinctAlias");
@ -928,7 +928,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
clonnedQb.expressionMap.extraAppendedAndWhereCondition = condition;
const [queryWithIdsSql, queryWithIdsParameters] = clonnedQb
.setParameters(parameters)
.getSqlWithParameters();
.getSqlAndParameters();
rawResults = await this.queryRunner.query(queryWithIdsSql, queryWithIdsParameters);
const rawRelationIdResults = await relationIdLoader.load(rawResults);
const rawRelationCountResults = await relationCountLoader.load(rawResults);
@ -945,7 +945,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
} else {
const [sql, parameters] = this.getSqlWithParameters();
const [sql, parameters] = this.getSqlAndParameters();
const rawResults = await this.queryRunner.query(sql, parameters);

View File

@ -25,7 +25,7 @@ describe("github issues > #521 Attributes in UPDATE in QB arent getting replaced
.where("name = :name", {
name: "Toyota",
})
.getSqlWithParameters();
.getSqlAndParameters();
return parameters.length.should.eql(2);
})));