From 8a5e6715e2d32da22c2fa71a14a7cf1fe897a159 Mon Sep 17 00:00:00 2001 From: spotykatch Date: Thu, 18 Nov 2021 15:33:23 +0300 Subject: [PATCH] fix: resolve issue delete column null on after update event subscriber (#8318) Closes: #6327 --- src/persistence/SubjectExecutor.ts | 4 +-- .../ReturningResultsEntityUpdator.ts | 14 +++++++-- src/query-builder/SoftDeleteQueryBuilder.ts | 2 +- test/github-issues/6327/entity/Post.ts | 13 ++++++++ test/github-issues/6327/issue-6327.ts | 31 +++++++++++++++++++ .../6327/subscriber/PostSubscriber.ts | 24 ++++++++++++++ 6 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 test/github-issues/6327/entity/Post.ts create mode 100644 test/github-issues/6327/issue-6327.ts create mode 100644 test/github-issues/6327/subscriber/PostSubscriber.ts diff --git a/src/persistence/SubjectExecutor.ts b/src/persistence/SubjectExecutor.ts index 5cbe647115..40a9d3586b 100644 --- a/src/persistence/SubjectExecutor.ts +++ b/src/persistence/SubjectExecutor.ts @@ -735,11 +735,11 @@ export class SubjectExecutor { this.updateSpecialColumnsInInsertedAndUpdatedEntities(this.updateSubjects); // update soft-removed entity properties - if (this.updateSubjects.length) + if (this.softRemoveSubjects.length) this.updateSpecialColumnsInInsertedAndUpdatedEntities(this.softRemoveSubjects); // update recovered entity properties - if (this.updateSubjects.length) + if (this.recoverSubjects.length) this.updateSpecialColumnsInInsertedAndUpdatedEntities(this.recoverSubjects); // remove ids from the entities that were removed diff --git a/src/query-builder/ReturningResultsEntityUpdator.ts b/src/query-builder/ReturningResultsEntityUpdator.ts index 7a258d551c..a2191d39fb 100644 --- a/src/query-builder/ReturningResultsEntityUpdator.ts +++ b/src/query-builder/ReturningResultsEntityUpdator.ts @@ -50,7 +50,7 @@ export class ReturningResultsEntityUpdator { } else { // for driver which do not support returning/output statement we need to perform separate query and load what we need - const updationColumns = this.getUpdationReturningColumns(); + const updationColumns = this.expressionMap.extraReturningColumns; if (updationColumns.length > 0) { // get entity id by which we will get needed data @@ -62,9 +62,10 @@ export class ReturningResultsEntityUpdator { const loadedReturningColumns = await this.queryRunner.manager .createQueryBuilder() .select(metadata.primaryColumns.map(column => metadata.targetName + "." + column.propertyPath)) - .addSelect(this.getUpdationReturningColumns().map(column => metadata.targetName + "." + column.propertyPath)) + .addSelect(updationColumns.map(column => metadata.targetName + "." + column.propertyPath)) .from(metadata.target, metadata.targetName) .where(entityId) + .withDeleted() .setOption("create-pojo") // use POJO because created object can contain default values, e.g. property = null and those properties maight be overridden by merge process .getOne() as any; @@ -194,4 +195,13 @@ export class ReturningResultsEntityUpdator { }); } + /** + * Columns we need to be returned from the database when we soft delete and restore entity. + */ + getSoftDeletionReturningColumns(): ColumnMetadata[] { + return this.expressionMap.mainAlias!.metadata.columns.filter(column => { + return column.isUpdateDate || column.isVersion || column.isDeleteDate; + }); + } + } diff --git a/src/query-builder/SoftDeleteQueryBuilder.ts b/src/query-builder/SoftDeleteQueryBuilder.ts index 85c83e1ea1..9748681368 100644 --- a/src/query-builder/SoftDeleteQueryBuilder.ts +++ b/src/query-builder/SoftDeleteQueryBuilder.ts @@ -73,7 +73,7 @@ export class SoftDeleteQueryBuilder extends QueryBuilder impleme if (this.expressionMap.updateEntity === true && this.expressionMap.mainAlias!.hasMetadata && this.expressionMap.whereEntities.length > 0) { - this.expressionMap.extraReturningColumns = returningResultsEntityUpdator.getUpdationReturningColumns(); + this.expressionMap.extraReturningColumns = returningResultsEntityUpdator.getSoftDeletionReturningColumns(); } // execute update query diff --git a/test/github-issues/6327/entity/Post.ts b/test/github-issues/6327/entity/Post.ts new file mode 100644 index 0000000000..cf9be9f128 --- /dev/null +++ b/test/github-issues/6327/entity/Post.ts @@ -0,0 +1,13 @@ +import { DeleteDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from "../../../../src"; + +@Entity() +export class Post { + @PrimaryGeneratedColumn() + id: number; + + @UpdateDateColumn() + updatedAt: Date; + + @DeleteDateColumn() + deletedAt: Date; +} diff --git a/test/github-issues/6327/issue-6327.ts b/test/github-issues/6327/issue-6327.ts new file mode 100644 index 0000000000..59f67160f4 --- /dev/null +++ b/test/github-issues/6327/issue-6327.ts @@ -0,0 +1,31 @@ +import "reflect-metadata"; +import { createTestingConnections, closeTestingConnections, reloadTestingDatabases } from "../../utils/test-utils"; +import { Connection } from "../../../src/connection/Connection"; +import { Post } from "./entity/Post"; + +describe("github issues > #6327 softRemove DeleteDateColumn is null at Susbscriber's AfterUpdate method", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + subscribers: [__dirname + "/subscriber/*{.js,.ts}"], + schemaCreate: true, + dropSchema: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("should send correct update and delete date columns to after update subscriber", () => Promise.all(connections.map(async connection => { + + const manager = connection.manager; + + const entity = new Post(); + await manager.save(entity); + + const deletedEntity = await manager.softRemove(entity, { data: { action: "soft-delete" } }); + + await manager.recover(deletedEntity, { data: { action: "restore" } }); + + }))); + +}); diff --git a/test/github-issues/6327/subscriber/PostSubscriber.ts b/test/github-issues/6327/subscriber/PostSubscriber.ts new file mode 100644 index 0000000000..01544b04d6 --- /dev/null +++ b/test/github-issues/6327/subscriber/PostSubscriber.ts @@ -0,0 +1,24 @@ +import { expect } from "chai"; +import { EntitySubscriberInterface, EventSubscriber, UpdateEvent } from "../../../../src"; +import { Post } from "../entity/Post"; + +@EventSubscriber() +export class PostSubscriber implements EntitySubscriberInterface { + listenTo() { + return Post; + } + + afterUpdate(event: UpdateEvent): void { + const { entity, queryRunner: { data } } = event; + + expect(["soft-delete", "restore"]).to.include(data!.action); + + if (data!.action === "soft-delete") { + expect(Object.prototype.toString.call(entity!.deletedAt)).to.be.eq("[object Date]"); + } + + if (data!.action === "restore") { + expect(entity!.deletedAt).to.be.null; + } + } +}