From e952161f41d4768d207026002311d009c4a5ca7a Mon Sep 17 00:00:00 2001 From: Umed Khudoiberdiev Date: Tue, 20 Jun 2017 22:44:34 +0500 Subject: [PATCH] fixes in query builder - insert and update methods --- CHANGELOG.md | 4 ++ src/metadata/EntityMetadata.ts | 8 +++- src/query-builder/InsertQueryBuilder.ts | 3 +- src/query-builder/QueryBuilder.ts | 64 +++++++++++-------------- src/query-builder/SelectQueryBuilder.ts | 8 ++-- test/github-issues/521/issue-521.ts | 2 +- 6 files changed, 45 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68a27f297..5bdc98e69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` 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 diff --git a/src/metadata/EntityMetadata.ts b/src/metadata/EntityMetadata.ts index df4c32bc5..62d5a8847 100644 --- a/src/metadata/EntityMetadata.ts +++ b/src/metadata/EntityMetadata.ts @@ -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; } - } \ No newline at end of file diff --git a/src/query-builder/InsertQueryBuilder.ts b/src/query-builder/InsertQueryBuilder.ts index 9660a379f..f8025a58c 100644 --- a/src/query-builder/InsertQueryBuilder.ts +++ b/src/query-builder/InsertQueryBuilder.ts @@ -11,8 +11,7 @@ export class InsertQueryBuilder extends QueryBuilder { // ------------------------------------------------------------------------- /** - * 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); diff --git a/src/query-builder/QueryBuilder.ts b/src/query-builder/QueryBuilder.ts index 3be4439e8..ece2cef79 100644 --- a/src/query-builder/QueryBuilder.ts +++ b/src/query-builder/QueryBuilder.ts @@ -261,7 +261,7 @@ export abstract class QueryBuilder { } /** - * 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 { * 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 { /** * 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 { * Executes sql generated by query builder and returns raw database results. */ async execute(): Promise { - 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 { } 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 { 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."); diff --git a/src/query-builder/SelectQueryBuilder.ts b/src/query-builder/SelectQueryBuilder.ts index 16a0da7d3..2c8cfd97e 100644 --- a/src/query-builder/SelectQueryBuilder.ts +++ b/src/query-builder/SelectQueryBuilder.ts @@ -816,7 +816,7 @@ export class SelectQueryBuilder extends QueryBuilder { .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 extends QueryBuilder { 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 extends QueryBuilder { 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 extends QueryBuilder { } else { - const [sql, parameters] = this.getSqlWithParameters(); + const [sql, parameters] = this.getSqlAndParameters(); const rawResults = await this.queryRunner.query(sql, parameters); diff --git a/test/github-issues/521/issue-521.ts b/test/github-issues/521/issue-521.ts index 40a748370..2b521072e 100644 --- a/test/github-issues/521/issue-521.ts +++ b/test/github-issues/521/issue-521.ts @@ -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); })));