From 9bdc83a399592a2ca0761070f0e7074a6a3ffa7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 22 May 2024 17:20:01 +0200 Subject: [PATCH] perf(core): Optimize executions filtering by metadata (#9477) --- .../repositories/execution.repository.ts | 15 +++++++---- .../execution.service.integration.test.ts | 26 +++++++++++++++++++ .../test/integration/shared/db/executions.ts | 19 ++++++++++++-- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/databases/repositories/execution.repository.ts b/packages/cli/src/databases/repositories/execution.repository.ts index 8043ee5e5d396..33ec7ed7ec993 100644 --- a/packages/cli/src/databases/repositories/execution.repository.ts +++ b/packages/cli/src/databases/repositories/execution.repository.ts @@ -728,12 +728,17 @@ export class ExecutionRepository extends Repository { if (startedBefore) qb.andWhere({ startedAt: lessThanOrEqual(startedBefore) }); if (startedAfter) qb.andWhere({ startedAt: moreThanOrEqual(startedAfter) }); - if (metadata) { - qb.leftJoin(ExecutionMetadata, 'md', 'md.executionId = execution.id'); + if (metadata?.length === 1) { + const [{ key, value }] = metadata; - for (const item of metadata) { - qb.andWhere('md.key = :key AND md.value = :value', item); - } + qb.innerJoin( + ExecutionMetadata, + 'md', + 'md.executionId = execution.id AND md.key = :key AND md.value = :value', + ); + + qb.setParameter('key', key); + qb.setParameter('value', value); } return qb; diff --git a/packages/cli/test/integration/execution.service.integration.test.ts b/packages/cli/test/integration/execution.service.integration.test.ts index 834720696d582..ba8cc89d369dc 100644 --- a/packages/cli/test/integration/execution.service.integration.test.ts +++ b/packages/cli/test/integration/execution.service.integration.test.ts @@ -257,6 +257,32 @@ describe('ExecutionService', () => { ]); }); + test('should filter executions by `metadata`', async () => { + const workflow = await createWorkflow(); + + const metadata = [{ key: 'myKey', value: 'myValue' }]; + + await Promise.all([ + createExecution({ status: 'success', metadata }, workflow), + createExecution({ status: 'error' }, workflow), + ]); + + const query: ExecutionSummaries.RangeQuery = { + kind: 'range', + range: { limit: 20 }, + accessibleWorkflowIds: [workflow.id], + metadata, + }; + + const output = await executionService.findRangeWithCount(query); + + expect(output).toEqual({ + count: 1, + estimated: false, + results: [expect.objectContaining({ status: 'success' })], + }); + }); + test('should exclude executions by inaccessible `workflowId`', async () => { const accessibleWorkflow = await createWorkflow(); const inaccessibleWorkflow = await createWorkflow(); diff --git a/packages/cli/test/integration/shared/db/executions.ts b/packages/cli/test/integration/shared/db/executions.ts index 4ac838888c591..199cf9c90a535 100644 --- a/packages/cli/test/integration/shared/db/executions.ts +++ b/packages/cli/test/integration/shared/db/executions.ts @@ -4,6 +4,7 @@ import type { ExecutionEntity } from '@db/entities/ExecutionEntity'; import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; import { ExecutionRepository } from '@db/repositories/execution.repository'; import { ExecutionDataRepository } from '@db/repositories/executionData.repository'; +import { ExecutionMetadataRepository } from '@/databases/repositories/executionMetadata.repository'; export async function createManyExecutions( amount: number, @@ -18,10 +19,14 @@ export async function createManyExecutions( * Store a execution in the DB and assign it to a workflow. */ export async function createExecution( - attributes: Partial, + attributes: Partial< + Omit & + ExecutionData & { metadata: Array<{ key: string; value: string }> } + >, workflow: WorkflowEntity, ) { - const { data, finished, mode, startedAt, stoppedAt, waitTill, status, deletedAt } = attributes; + const { data, finished, mode, startedAt, stoppedAt, waitTill, status, deletedAt, metadata } = + attributes; const execution = await Container.get(ExecutionRepository).save({ finished: finished ?? true, @@ -34,6 +39,16 @@ export async function createExecution( deletedAt, }); + if (metadata?.length) { + const metadataToSave = metadata.map(({ key, value }) => ({ + key, + value, + execution: { id: execution.id }, + })); + + await Container.get(ExecutionMetadataRepository).save(metadataToSave); + } + await Container.get(ExecutionDataRepository).save({ data: data ?? '[]', workflowData: workflow ?? {},