diff --git a/apps/sim/background/cleanup-logs.ts b/apps/sim/background/cleanup-logs.ts index 343e388ed2b..2ee922fb7e0 100644 --- a/apps/sim/background/cleanup-logs.ts +++ b/apps/sim/background/cleanup-logs.ts @@ -256,7 +256,7 @@ async function cleanupLegacyLargeExecutionValues( SELECT 1 FROM ${pausedExecutions} AS ref_pe WHERE ref_pe.execution_id = ref.execution_id - AND ref_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + AND ref_pe.status IN ${LIVE_PAUSED_REFERENCE_STATUSES} ) ) ) @@ -279,7 +279,7 @@ async function cleanupLegacyLargeExecutionValues( SELECT 1 FROM ${pausedExecutions} AS parent_owner_pe WHERE parent_owner_pe.execution_id = parent_value.owner_execution_id - AND parent_owner_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + AND parent_owner_pe.status IN ${LIVE_PAUSED_REFERENCE_STATUSES} ) OR EXISTS ( SELECT 1 @@ -300,7 +300,7 @@ async function cleanupLegacyLargeExecutionValues( SELECT 1 FROM ${pausedExecutions} AS parent_ref_pe WHERE parent_ref_pe.execution_id = parent_ref.execution_id - AND parent_ref_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + AND parent_ref_pe.status IN ${LIVE_PAUSED_REFERENCE_STATUSES} ) ) ) @@ -316,7 +316,7 @@ async function cleanupLegacyLargeExecutionValues( SELECT 1 FROM ${pausedExecutions} AS pe WHERE pe.execution_id = split_part(${workspaceFiles.key}, '/', 4) - AND pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + AND pe.status IN ${LIVE_PAUSED_REFERENCE_STATUSES} )` ) ) diff --git a/apps/sim/lib/execution/payloads/large-value-metadata.ts b/apps/sim/lib/execution/payloads/large-value-metadata.ts index 41d56fbf2e2..c98f4756c5e 100644 --- a/apps/sim/lib/execution/payloads/large-value-metadata.ts +++ b/apps/sim/lib/execution/payloads/large-value-metadata.ts @@ -412,7 +412,7 @@ async function pruneStaleReferences( SELECT 1 FROM ${pausedExecutions} AS pe WHERE pe.execution_id = ref.execution_id - AND pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + AND pe.status IN ${LIVE_PAUSED_REFERENCE_STATUSES} ) ) OR ref.source NOT IN ('execution_log', 'paused_snapshot') @@ -568,7 +568,7 @@ export function unreferencedLargeValuePredicate() { SELECT 1 FROM ${pausedExecutions} AS pe WHERE pe.execution_id = elvr.execution_id - AND pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + AND pe.status IN ${LIVE_PAUSED_REFERENCE_STATUSES} ) ) ) @@ -582,7 +582,7 @@ export function unreferencedLargeValuePredicate() { SELECT 1 FROM ${pausedExecutions} AS owner_pe WHERE owner_pe.execution_id = ${executionLargeValues.ownerExecutionId} - AND owner_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + AND owner_pe.status IN ${LIVE_PAUSED_REFERENCE_STATUSES} ) AND NOT EXISTS ( SELECT 1 @@ -602,7 +602,7 @@ export function unreferencedLargeValuePredicate() { SELECT 1 FROM ${pausedExecutions} AS parent_owner_pe WHERE parent_owner_pe.execution_id = parent_value.owner_execution_id - AND parent_owner_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + AND parent_owner_pe.status IN ${LIVE_PAUSED_REFERENCE_STATUSES} ) OR EXISTS ( SELECT 1 @@ -623,7 +623,7 @@ export function unreferencedLargeValuePredicate() { SELECT 1 FROM ${pausedExecutions} AS parent_ref_pe WHERE parent_ref_pe.execution_id = parent_ref.execution_id - AND parent_ref_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + AND parent_ref_pe.status IN ${LIVE_PAUSED_REFERENCE_STATUSES} ) ) ) diff --git a/apps/sim/lib/execution/payloads/live-paused-statuses-sql.test.ts b/apps/sim/lib/execution/payloads/live-paused-statuses-sql.test.ts new file mode 100644 index 00000000000..812b1d64944 --- /dev/null +++ b/apps/sim/lib/execution/payloads/live-paused-statuses-sql.test.ts @@ -0,0 +1,40 @@ +/** + * @vitest-environment node + */ + +// Renders the real predicate against the real drizzle dialect and schema: the +// bug this guards against is a SQL *rendering* bug, so the global drizzle-orm +// and schema mocks would hide it entirely. +import { describe, expect, it, vi } from 'vitest' + +vi.unmock('drizzle-orm') +vi.unmock('@sim/db') +vi.unmock('@sim/db/schema') + +process.env.DATABASE_URL ??= 'postgresql://user:pass@localhost:5432/test' + +const { PgDialect } = await import('drizzle-orm/pg-core') +const { LIVE_PAUSED_REFERENCE_STATUSES, unreferencedLargeValuePredicate } = await import( + '@/lib/execution/payloads/large-value-metadata' +) + +describe('unreferencedLargeValuePredicate SQL', () => { + const { sql: text, params } = new PgDialect().sqlToQuery(unreferencedLargeValuePredicate()) + + it('compares paused status against an IN list', () => { + expect(text).toMatch(/\.status IN \(\$\d+, \$\d+, \$\d+\)/) + expect(params).toEqual(expect.arrayContaining([...LIVE_PAUSED_REFERENCE_STATUSES])) + }) + + it('never emits ANY() over drizzle-expanded statuses', () => { + // Drizzle renders a JS array as `($1, $2, $3)` — correct for IN, but + // `ANY((...)::text[])` makes Postgres reject the whole statement with + // "cannot cast type record to text[]", which killed cleanup-logs for two months. + expect(text).not.toMatch(/ANY\(\(\$\d+/) + }) + + it('never emits a dangling comparison operator before IN', () => { + // `status = IN (...)` is a syntax error Postgres only reports at execution time. + expect(text).not.toMatch(/=\s*IN\s*\(/) + }) +})