From 62345012f82f61b2c831667ff09a69011d368f63 Mon Sep 17 00:00:00 2001 From: Umed Khudoiberdiev Date: Mon, 16 Oct 2017 15:04:41 +0500 Subject: [PATCH] re-implemented update, updateById, removeById methods; added delete and insert methods --- .github/ISSUE_TEMPLATE.md | 2 - CHANGELOG.md | 7 +- package.json | 4 +- src/entity-manager/EntityManager.ts | 120 +++++++++++++----- src/find-options/FindOptionsUtils.ts | 3 +- src/query-builder/InsertQueryBuilder.ts | 14 +- src/repository/BaseEntity.ts | 2 +- src/repository/Repository.ts | 56 +++++--- .../repository-remove-by-id-and-ids.ts | 2 +- 9 files changed, 138 insertions(+), 72 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 7345e962a..000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,2 +0,0 @@ - \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fddfaf28..1227dfed5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,13 @@ feel free to ask us and community. * added support for `pg-native` for postgres (#975). To use it you just need to install `npm i pg-native` and it will be picked up automatically. * now Find Options support `-1` and `1` for `DESC` and `ASC` values. This is better user experience for MongoDB users. -* now inheritances in embeddeds are supported (#966) +* now inheritances in embeddeds are supported (#966). * `isArray: boolean` in `ColumnOptions` is deprecated. Use `array: boolean` instead. +* deprecated `removeById` method, now use `deleteById` method instead. +* added `insert` and `delete` methods into repository and entity manager. +* fixed multiple issues with `update`, `updateById` and `removeById` methods in repository and entity manager. Now they do not use `save` and `remove` methods anymore - instead they are using QueryBuilder to build and execute their queries. +* removed opencollective dependency +* multiple small bugfixes ## 0.1.0 diff --git a/package.json b/package.json index 0102529c8..b437b655b 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,6 @@ "glob": "^7.1.2", "js-yaml": "^3.8.4", "mkdirp": "^0.5.1", - "opencollective": "^1.0.3", "reflect-metadata": "^0.1.10", "xml2js": "^0.4.17", "yargonaut": "^1.1.2", @@ -92,8 +91,7 @@ "test": "gulp ci-tests", "compile": "tsc", "setup:config": "gulp createTravisOrmConfig", - "package": "gulp package", - "postinstall": "opencollective postinstall" + "package": "gulp package" }, "bin": { "typeorm": "./cli.js" diff --git a/src/entity-manager/EntityManager.ts b/src/entity-manager/EntityManager.ts index d150a5bd5..f707eb326 100644 --- a/src/entity-manager/EntityManager.ts +++ b/src/entity-manager/EntityManager.ts @@ -28,6 +28,7 @@ import {RepositoryFactory} from "../repository/RepositoryFactory"; import {EntityManagerFactory} from "./EntityManagerFactory"; import {TreeRepositoryNotSupportedError} from "../error/TreeRepositoryNotSupportedError"; import {EntityMetadata} from "../metadata/EntityMetadata"; +import {QueryPartialEntity} from "../query-builder/QueryPartialEntity"; /** * Entity manager supposed to work with any entity, automatically find its repository and call its methods, @@ -358,37 +359,59 @@ export class EntityManager { } /** - * Updates entity partially. Entity can be found by a given conditions. + * Inserts a given entity into the database. + * Unlike save method executes a primitive operation without cascades, relations and other operations included. + * Does not modify source entity and does not execute listeners and subscribers. + * Executes fast and efficient INSERT query. + * Does not check if entity exist in the database, so query will fail if duplicate entity is being inserted. + * You can execute bulk inserts using this method. */ - async update(target: ObjectType|string, conditions: Partial, partialEntity: DeepPartial, options?: SaveOptions): Promise; + async insert(target: ObjectType|string, entity: QueryPartialEntity|QueryPartialEntity[], options?: SaveOptions): Promise { + // todo: in the future create InsertResult with query result information + // todo: think if subscribers and listeners can be executed here as well - /** - * Updates entity partially. Entity can be found by a given find options. - */ - async update(target: ObjectType|string, findOptions: FindOneOptions, partialEntity: DeepPartial, options?: SaveOptions): Promise; + await this.createQueryBuilder() + .insert() + .into(target) + .values(entity) + .execute(); + } /** * Updates entity partially. Entity can be found by a given conditions. + * Unlike save method executes a primitive operation without cascades, relations and other operations included. + * Does not modify source entity and does not execute listeners and subscribers. + * Executes fast and efficient UPDATE query. + * Does not check if entity exist in the database. */ - async update(target: ObjectType|string, conditionsOrFindOptions: Partial|FindOneOptions, partialEntity: DeepPartial, options?: SaveOptions): Promise { - const entity = await this.findOne(target, conditionsOrFindOptions as any); // this is temporary, in the future can be refactored to perform better - if (!entity) - throw new Error(`Cannot find entity to update by a given criteria`); + async update(target: ObjectType|string, conditions: Partial, partialEntity: DeepPartial, options?: SaveOptions): Promise { + // todo: in the future create UpdateResult with query result information + // todo: think if subscribers and listeners can be executed here as well - Object.assign(entity, partialEntity); - await this.save(entity, options); + const queryBuilder = this.createQueryBuilder() + .update(target) + .set(partialEntity); + + FindOptionsUtils.applyConditions(queryBuilder, conditions); // todo: move condition-like syntax into .where method of query builder? + await queryBuilder.execute(); } /** * Updates entity partially. Entity will be found by a given id. + * Unlike save method executes a primitive operation without cascades, relations and other operations included. + * Does not modify source entity and does not execute listeners and subscribers. + * Executes fast and efficient UPDATE query. + * Does not check if entity exist in the database. */ - async updateById(target: ObjectType|string, id: any, partialEntity: DeepPartial, options?: SaveOptions): Promise { - const entity = await this.findOneById(target, id as any); // this is temporary, in the future can be refactored to perform better - if (!entity) - throw new Error(`Cannot find entity to update by a id`); + async updateById(target: ObjectType|string, id: any|any[], partialEntity: DeepPartial, options?: SaveOptions): Promise { + // todo: in the future create UpdateResult with query result information + // todo: think if subscribers and listeners can be executed here as well - Object.assign(entity, partialEntity); - await this.save(entity, options); + await this.createQueryBuilder() + .update(target) + .set(partialEntity) + .whereInIds(id) + .execute(); } /** @@ -498,29 +521,58 @@ export class EntityManager { } /** - * Removes entity by a given entity id. + * Deletes entities by a given conditions. + * Unlike save method executes a primitive operation without cascades, relations and other operations included. + * Does not modify source entity and does not execute listeners and subscribers. + * Executes fast and efficient DELETE query. + * Does not check if entity exist in the database. */ - async removeById(targetOrEntity: ObjectType|string, id: any, options?: RemoveOptions): Promise { - const entity = await this.findOneById(targetOrEntity, id); // this is temporary, in the future can be refactored to perform better - if (!entity) - throw new Error(`Cannot find entity to remove by a given id`); + async delete(targetOrEntity: ObjectType|string, conditions: Partial, options?: RemoveOptions): Promise { + // todo: in the future create DeleteResult with query result information + // todo: think if subscribers and listeners can be executed here as well - await this.remove(entity, options); + const queryBuilder = this.createQueryBuilder() + .delete() + .from(targetOrEntity); + + FindOptionsUtils.applyConditions(queryBuilder, conditions); // todo: move condition-like syntax into .where method of query builder? + await queryBuilder.execute(); } /** - * Removes entity by a given entity ids. + * Deletes entities by a given entity id or ids. + * Unlike save method executes a primitive operation without cascades, relations and other operations included. + * Does not modify source entity and does not execute listeners and subscribers. + * Executes fast and efficient DELETE query. + * Does not check if entity exist in the database. + */ + async deleteById(targetOrEntity: ObjectType|string, id: any|any[], options?: RemoveOptions): Promise { + // todo: in the future create DeleteResult with query result information + // todo: think if subscribers and listeners can be executed here as well + + await this.createQueryBuilder() + .delete() + .from(targetOrEntity) + .whereInIds(id) + .execute(); + } + + /** + * Deletes entity by a given entity id. + * + * @deprecated use deleteById method instead. + */ + async removeById(targetOrEntity: ObjectType|string, id: any, options?: RemoveOptions): Promise { + return this.deleteById(targetOrEntity, id, options); + } + + /** + * Deletes entity by a given entity ids. + * + * @deprecated use deleteById method instead. */ async removeByIds(targetOrEntity: ObjectType|string, ids: any[], options?: RemoveOptions): Promise { - const promises = ids.map(async id => { - const entity = await this.findOneById(targetOrEntity, id); // this is temporary, in the future can be refactored to perform better - if (!entity) - throw new Error(`Cannot find entity to remove by a given id`); - - await this.remove(entity, options); - }); - - await Promise.all(promises); + return this.deleteById(targetOrEntity, ids, options); } /** diff --git a/src/find-options/FindOptionsUtils.ts b/src/find-options/FindOptionsUtils.ts index 73e5592e6..c8ef5f80d 100644 --- a/src/find-options/FindOptionsUtils.ts +++ b/src/find-options/FindOptionsUtils.ts @@ -2,6 +2,7 @@ import {FindManyOptions} from "./FindManyOptions"; import {FindOneOptions} from "./FindOneOptions"; import {ObjectLiteral} from "../common/ObjectLiteral"; import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder"; +import {WhereExpression} from "../query-builder/WhereExpression"; /** * Utilities to work with FindOptions. @@ -161,7 +162,7 @@ export class FindOptionsUtils { /** * Applies given simple conditions set to a given query builder. */ - static applyConditions(qb: SelectQueryBuilder, conditions: ObjectLiteral): SelectQueryBuilder { + static applyConditions(qb: QB, conditions: ObjectLiteral): QB { Object.keys(conditions).forEach((key, index) => { if (conditions![key] === null) { qb.andWhere(`${qb.alias}.${key} IS NULL`); diff --git a/src/query-builder/InsertQueryBuilder.ts b/src/query-builder/InsertQueryBuilder.ts index 0438821e2..0e60287ba 100644 --- a/src/query-builder/InsertQueryBuilder.ts +++ b/src/query-builder/InsertQueryBuilder.ts @@ -46,17 +46,7 @@ export class InsertQueryBuilder extends QueryBuilder { /** * Values needs to be inserted into table. */ - values(values: QueryPartialEntity): this; - - /** - * Values needs to be inserted into table. - */ - values(values: QueryPartialEntity[]): this; - - /** - * Values needs to be inserted into table. - */ - values(values: ObjectLiteral|ObjectLiteral[]): this { + values(values: QueryPartialEntity|QueryPartialEntity[]): this { this.expressionMap.valuesSet = values; return this; } @@ -92,7 +82,7 @@ export class InsertQueryBuilder extends QueryBuilder { const values = valueSets.map((valueSet, key) => { const columnNames = insertColumns.map(column => { const paramName = "_inserted_" + key + "_" + column.databaseName; - const value = valueSet[column.propertyName]; + const value = column.getEntityValue(valueSet); if (value instanceof Function) { // support for SQL expressions in update query return value(); diff --git a/src/repository/BaseEntity.ts b/src/repository/BaseEntity.ts index 116f82568..acddd4a15 100644 --- a/src/repository/BaseEntity.ts +++ b/src/repository/BaseEntity.ts @@ -191,7 +191,7 @@ export class BaseEntity { * Removes entity by a given entity id. */ static removeById(this: ObjectType, id: any, options?: RemoveOptions): Promise { - return (this as any).getRepository().removeById(id, options); + return (this as any).getRepository().deleteById(id, options); } /** diff --git a/src/repository/Repository.ts b/src/repository/Repository.ts index bc7947eb4..229a9b65b 100644 --- a/src/repository/Repository.ts +++ b/src/repository/Repository.ts @@ -133,25 +133,21 @@ export class Repository { } /** - * Updates entity partially. Entity can be found by a given conditions. + * Inserts a given entity into the database. + * Unlike save method executes a primitive operation without cascades, relations and other operations included. + * Does not modify source entity and does not execute listeners and subscribers. + * Executes fast and efficient INSERT query. + * Does not check if entity exist in the database, so query will fail if duplicate entity is being inserted. */ - async update(conditions: Partial, partialEntity: DeepPartial, options?: SaveOptions): Promise; - - /** - * Updates entity partially. Entity can be found by a given find options. - */ - async update(findOptions: FindOneOptions, partialEntity: DeepPartial, options?: SaveOptions): Promise; + async insert(entity: Partial|Partial[], options?: SaveOptions): Promise { + return this.manager.insert(this.metadata.target, entity, options); + } /** * Updates entity partially. Entity can be found by a given conditions. */ - async update(conditionsOrFindOptions: Partial|FindOneOptions, partialEntity: DeepPartial, options?: SaveOptions): Promise { - const entity = await this.findOne(conditionsOrFindOptions as any); // this is temporary, in the future can be refactored to perform better - if (!entity) - throw new Error(`Cannot find entity to update by a given criteria`); - - Object.assign(entity, partialEntity); - await this.save(entity, options); + async update(conditions: Partial, partialEntity: DeepPartial, options?: SaveOptions): Promise { + return this.manager.update(this.metadata.target, conditions, partialEntity, options); } /** @@ -179,14 +175,40 @@ export class Repository { } /** - * Removes entity by a given entity id. + * Deletes entities by a given conditions. + * Unlike save method executes a primitive operation without cascades, relations and other operations included. + * Does not modify source entity and does not execute listeners and subscribers. + * Executes fast and efficient DELETE query. + * Does not check if entity exist in the database. */ - async removeById(id: any, options?: RemoveOptions): Promise { - return this.manager.removeById(this.metadata.target, id, options); + async delete(conditions: Partial, options?: RemoveOptions): Promise { + return this.manager.delete(this.metadata.target, conditions, options); + } + + /** + * Deletes entities by a given conditions. + * Unlike save method executes a primitive operation without cascades, relations and other operations included. + * Does not modify source entity and does not execute listeners and subscribers. + * Executes fast and efficient DELETE query. + * Does not check if entity exist in the database. + */ + async deleteById(id: any, options?: RemoveOptions): Promise { + return this.manager.deleteById(this.metadata.target, id, options); } /** * Removes entity by a given entity id. + * + * @deprecated use deleteById method instead. + */ + async removeById(id: any, options?: RemoveOptions): Promise { + return this.manager.deleteById(this.metadata.target, id, options); + } + + /** + * Removes entity by a given entity id. + * + * @deprecated use deleteById method instead. */ async removeByIds(ids: any[], options?: RemoveOptions): Promise { return this.manager.removeByIds(this.metadata.target, ids, options); diff --git a/test/functional/repository/delete-by-id-and-ids/repository-remove-by-id-and-ids.ts b/test/functional/repository/delete-by-id-and-ids/repository-remove-by-id-and-ids.ts index 57942e418..16368cfc3 100644 --- a/test/functional/repository/delete-by-id-and-ids/repository-remove-by-id-and-ids.ts +++ b/test/functional/repository/delete-by-id-and-ids/repository-remove-by-id-and-ids.ts @@ -44,7 +44,7 @@ describe("repository > removeById and removeByIds methods", function() { ]); // remove one - await postRepository.removeById(1); + await postRepository.deleteById(1); // load to check const loadedPosts = await postRepository.find();