diff --git a/packages/cli/src/databases/repositories/execution.repository.ts b/packages/cli/src/databases/repositories/execution.repository.ts index 44b607fb9d..2c962f28bb 100644 --- a/packages/cli/src/databases/repositories/execution.repository.ts +++ b/packages/cli/src/databases/repositories/execution.repository.ts @@ -407,7 +407,7 @@ export class ExecutionRepository extends Repository { } const query = this.createQueryBuilder('execution') - .select(['execution.id']) + .select(['execution.id', 'execution.workflowId']) .andWhere('execution.workflowId IN (:...accessibleWorkflowIds)', { accessibleWorkflowIds }); if (deleteConditions.deleteBefore) { @@ -433,12 +433,19 @@ export class ExecutionRepository extends Repository { return; } - const executionIds = executions.map(({ id }) => id); + const ids = executions.map(({ id, workflowId }) => ({ + executionId: id, + workflowId, + })); + do { // Delete in batches to avoid "SQLITE_ERROR: Expression tree is too large (maximum depth 1000)" error - const batch = executionIds.splice(0, this.hardDeletionBatchSize); - await this.delete(batch); - } while (executionIds.length > 0); + const batch = ids.splice(0, this.hardDeletionBatchSize); + await Promise.all([ + this.delete(batch.map(({ executionId }) => executionId)), + this.binaryDataService.deleteMany(batch), + ]); + } while (ids.length > 0); } async getIdsSince(date: Date) { diff --git a/packages/cli/test/unit/repositories/execution.repository.test.ts b/packages/cli/test/unit/repositories/execution.repository.test.ts index 1beae1a487..f09d22fe72 100644 --- a/packages/cli/test/unit/repositories/execution.repository.test.ts +++ b/packages/cli/test/unit/repositories/execution.repository.test.ts @@ -1,14 +1,20 @@ import Container from 'typedi'; + +import type { SelectQueryBuilder } from '@n8n/typeorm'; import { Not, LessThanOrEqual } from '@n8n/typeorm'; import config from '@/config'; import { ExecutionEntity } from '@db/entities/ExecutionEntity'; import { ExecutionRepository } from '@db/repositories/execution.repository'; - import { mockEntityManager } from '../../shared/mocking'; +import { mockInstance } from '../../shared/mocking'; +import { BinaryDataService } from 'n8n-core'; +import { nanoid } from 'nanoid'; +import { mock } from 'jest-mock-extended'; describe('ExecutionRepository', () => { const entityManager = mockEntityManager(ExecutionEntity); + const binaryDataService = mockInstance(BinaryDataService); const executionRepository = Container.get(ExecutionRepository); const mockDate = new Date('2023-12-28 12:34:56.789Z'); @@ -43,4 +49,22 @@ describe('ExecutionRepository', () => { }, ); }); + + describe('deleteExecutionsByFilter', () => { + test('should delete binary data', async () => { + const workflowId = nanoid(); + + jest.spyOn(executionRepository, 'createQueryBuilder').mockReturnValue( + mock>({ + select: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + getMany: jest.fn().mockResolvedValue([{ id: '1', workflowId }]), + }), + ); + + await executionRepository.deleteExecutionsByFilter({ id: '1' }, ['1'], { ids: ['1'] }); + + expect(binaryDataService.deleteMany).toHaveBeenCalledWith([{ executionId: '1', workflowId }]); + }); + }); });