fix(cleanup): compare live-paused statuses with IN, not ANY over a row constructor (#6004)

This commit is contained in:
Theodore Li
2026-07-28 13:23:12 -04:00
committed by GitHub
parent e5ae4451fc
commit bf376dd023
3 changed files with 49 additions and 9 deletions
+4 -4
View File
@@ -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}
)`
)
)
@@ -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}
)
)
)
@@ -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*\(/)
})
})