Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete / Update Cascade Feature #1546

Open
mystyle2006 opened this issue Jun 25, 2022 · 0 comments
Open

Delete / Update Cascade Feature #1546

mystyle2006 opened this issue Jun 25, 2022 · 0 comments
Labels
enhancement New feature or request

Comments

@mystyle2006
Copy link

mystyle2006 commented Jun 25, 2022

Is your feature request related to a problem? Please describe.
In conclusion, the cascade function should be strengthened to use nestjs-query more efficiently.

In the case of Read / Create, Cascade has confirmed that there is no problem.

However, cascade does not seem to work normally when deleting and modifying.

Suppose you are creating a Todo app.

Two entities are required. TodoItem / SubTask.
An item can have multiple subtasks. That is the relationship of oneToMany.

Then, when the TodoItem is deleted, we expect that the Subtask will also be deleted due to Cascade.

But I get an error as below.

스크린샷 2022-06-26 오전 12 15 01

If you look at the query, an error occurs because of the foreign key constraint of the database because it deletes the TodoItem first, not the subtask first.

An error also occurs when correcting an incorrect query sequence similar to the one above.
(In case of modification, subtasks are always inserted)

Have you read the Contributing Guidelines?

(Write your answer here.)

Describe the solution you'd like
To solve this problem, the method used in typeorm-query.service.ts of the query-typeorm package has been overridden.
It may not be the best way, but after thinking about it, I couldn't come up with a better way other than this one.

when deleting method,

export class CustomTypeormQueryService<Entity> extends TypeOrmQueryService<Entity> {
  async deleteOne(id: string | number): Promise<Entity> {
    const relations = this.repo.metadata.relations.filter((i) => i.isCascadeSoftRemove).map((i) => i.propertyPath); // getting cascade relations
    const entity = await this.repo.findOne(id, {
      relations,
    });

    if (!entity) {
      throw new NotFoundException(`Unable to find ${this.EntityClass.name} with id: ${id}`);
    }

    if (this.useSoftDelete) {
      return this.repo.softRemove(entity as DeepPartial<Entity>);
    }
    return this.repo.remove(entity);
  }
}

when updating method (It's a bit long to consider all the exceptions below.)

  • When all subtasks are deleted
  • When only some of the subtasks are deleted
  • When a newly created subtask exists
export class CustomTypeormQueryService<Entity> extends TypeOrmQueryService<Entity> {
  async updateOne(id: number | string, update: DeepPartial<Entity>, opts?: UpdateOneOptions<Entity>): Promise<Entity> {
      if (this.repo.hasId(update as unknown as Entity)) {
        throw new Error('Id cannot be specified when updating');
      }
  
      const cascadeRelations = this.repo.metadata.relations // getting cascade relation infos
        .filter((i) => i.isCascadeUpdate)
        .map((i) => {
          const primaryKeyObj = i.inverseEntityMetadata.columns.find((column) => column.isPrimary);
          const deletedAtObj = i.inverseEntityMetadata.columns.find((column) => column.isDeleteDate);
          return {
            relationName: i.propertyPath,
            targetEntity: i.inverseRelation.target,
            primaryColumn: {
              database: primaryKeyObj?.databaseName,
              property: primaryKeyObj?.propertyName,
            },
            deletedColumn: {
              database: deletedAtObj?.databaseName,
              property: deletedAtObj?.propertyName,
            },
            columnPropertyNames: i.inverseEntityMetadata.columns
              .filter((j) => !j.isCreateDate && !j.isUpdateDate)
              .map((j) => j.databaseName),
          };
        });
  
      const entity = await this.repo.findOne(id, {
        ...opts,
        relations: cascadeRelations.map((relation) => relation.relationName),
      });
  
      if (!entity) {
        throw new NotFoundException(`Unable to find ${this.EntityClass.name} with id: ${id}`);
      }
  
      // inserting or updating or removing subtasks
      if (cascadeRelations.length) {
        await Promise.all( // When it does not exist, it is created and modified when it is created.
          cascadeRelations.map((relation) =>
            this.repo
              .createQueryBuilder()
              .insert()
              .into(relation.targetEntity)
              .values(update[relation.relationName].map((i) => ({ ...i, [relation.deletedColumn.property]: null })))
              .orUpdate({
                conflict_target: [relation.primaryColumn.database],
                overwrite: relation.columnPropertyNames,
              })
              .updateEntity(false)
              .execute(),
          ),
        );
  
        await Promise.all(
          cascadeRelations.map((relation) => { // If there are cases that need to be deleted during the update
            const updateKeys = update[relation.relationName].map((i) => i[relation.primaryColumn.property]);
            const removeIds = entity[relation.relationName]
              .filter((i) => !updateKeys.includes(i[relation.primaryColumn.property]))
              .map((i) => i[relation.primaryColumn.property]);
            if (!removeIds.length) {
              return null;
            }
  
            return this.repo
              .createQueryBuilder()
              .softDelete()
              .from(relation.targetEntity)
              .where({
                [relation.primaryColumn.property]: In(removeIds),
              })
              .execute();
          }),
        );
  
        // removing subtasks in TodoItem
        cascadeRelations.forEach((relation) => {
          delete entity[relation.relationName];
          delete update[relation.relationName];
        });
      }
  
      // updating TodoItem
      return this.repo.save(this.repo.merge(entity, update));
    }
}

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

@mystyle2006 mystyle2006 added the enhancement New feature or request label Jun 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant