Skip to content

Commit dfeac7b

Browse files
fix(cleanup): bind live-paused statuses as a Postgres array, not a row constructor
1 parent 6aa3e0e commit dfeac7b

4 files changed

Lines changed: 63 additions & 9 deletions

File tree

apps/sim/background/cleanup-logs.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ vi.mock('@/lib/cleanup/batch-delete', () => ({
6161

6262
vi.mock('@/lib/execution/payloads/large-value-metadata', () => ({
6363
LIVE_PAUSED_REFERENCE_STATUSES: ['paused', 'partially_resumed', 'cancelling'],
64+
livePausedStatusArray: { op: 'livePausedStatusArray' },
6465
markLargeValuesDeleted: mockMarkLargeValuesDeleted,
6566
pruneLargeValueMetadata: mockPruneLargeValueMetadata,
6667
unreferencedLargeValuePredicate: vi.fn(() => ({ op: 'unreferencedLargeValuePredicate' })),

apps/sim/background/cleanup-logs.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from '@/lib/cleanup/batch-delete'
2121
import {
2222
LIVE_PAUSED_REFERENCE_STATUSES,
23+
livePausedStatusArray,
2324
markLargeValuesDeleted,
2425
pruneLargeValueMetadata,
2526
unreferencedLargeValuePredicate,
@@ -256,7 +257,7 @@ async function cleanupLegacyLargeExecutionValues(
256257
SELECT 1
257258
FROM ${pausedExecutions} AS ref_pe
258259
WHERE ref_pe.execution_id = ref.execution_id
259-
AND ref_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[])
260+
AND ref_pe.status = ANY(${livePausedStatusArray})
260261
)
261262
)
262263
)
@@ -279,7 +280,7 @@ async function cleanupLegacyLargeExecutionValues(
279280
SELECT 1
280281
FROM ${pausedExecutions} AS parent_owner_pe
281282
WHERE parent_owner_pe.execution_id = parent_value.owner_execution_id
282-
AND parent_owner_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[])
283+
AND parent_owner_pe.status = ANY(${livePausedStatusArray})
283284
)
284285
OR EXISTS (
285286
SELECT 1
@@ -300,7 +301,7 @@ async function cleanupLegacyLargeExecutionValues(
300301
SELECT 1
301302
FROM ${pausedExecutions} AS parent_ref_pe
302303
WHERE parent_ref_pe.execution_id = parent_ref.execution_id
303-
AND parent_ref_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[])
304+
AND parent_ref_pe.status = ANY(${livePausedStatusArray})
304305
)
305306
)
306307
)
@@ -316,7 +317,7 @@ async function cleanupLegacyLargeExecutionValues(
316317
SELECT 1
317318
FROM ${pausedExecutions} AS pe
318319
WHERE pe.execution_id = split_part(${workspaceFiles.key}, '/', 4)
319-
AND pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[])
320+
AND pe.status = ANY(${livePausedStatusArray})
320321
)`
321322
)
322323
)

apps/sim/lib/execution/payloads/large-value-metadata.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ const LARGE_VALUE_METADATA_PRUNE_BATCH_SIZE = 1_000
2222
const LARGE_VALUE_METADATA_PRUNE_MAX_ROWS_PER_TABLE = 5_000
2323
export const LIVE_PAUSED_REFERENCE_STATUSES = ['paused', 'partially_resumed', 'cancelling'] as const
2424

25+
/**
26+
* `LIVE_PAUSED_REFERENCE_STATUSES` as a Postgres `text[]` for `= ANY(...)`.
27+
*
28+
* Interpolating the raw JS array into a `sql` template renders a row
29+
* constructor — `($1, $2, $3)::text[]` — which Postgres rejects with
30+
* "cannot cast type record to text[]", failing the whole statement. Building
31+
* `ARRAY[$1, $2, $3]::text[]` explicitly keeps one bind param per status.
32+
*/
33+
export const livePausedStatusArray = sql`ARRAY[${sql.join(
34+
LIVE_PAUSED_REFERENCE_STATUSES.map((status) => sql`${status}`),
35+
sql`, `
36+
)}]::text[]`
37+
2538
export interface LargeValueOwner {
2639
key: string
2740
workspaceId: string
@@ -412,7 +425,7 @@ async function pruneStaleReferences(
412425
SELECT 1
413426
FROM ${pausedExecutions} AS pe
414427
WHERE pe.execution_id = ref.execution_id
415-
AND pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[])
428+
AND pe.status = ANY(${livePausedStatusArray})
416429
)
417430
)
418431
OR ref.source NOT IN ('execution_log', 'paused_snapshot')
@@ -568,7 +581,7 @@ export function unreferencedLargeValuePredicate() {
568581
SELECT 1
569582
FROM ${pausedExecutions} AS pe
570583
WHERE pe.execution_id = elvr.execution_id
571-
AND pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[])
584+
AND pe.status = ANY(${livePausedStatusArray})
572585
)
573586
)
574587
)
@@ -582,7 +595,7 @@ export function unreferencedLargeValuePredicate() {
582595
SELECT 1
583596
FROM ${pausedExecutions} AS owner_pe
584597
WHERE owner_pe.execution_id = ${executionLargeValues.ownerExecutionId}
585-
AND owner_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[])
598+
AND owner_pe.status = ANY(${livePausedStatusArray})
586599
)
587600
AND NOT EXISTS (
588601
SELECT 1
@@ -602,7 +615,7 @@ export function unreferencedLargeValuePredicate() {
602615
SELECT 1
603616
FROM ${pausedExecutions} AS parent_owner_pe
604617
WHERE parent_owner_pe.execution_id = parent_value.owner_execution_id
605-
AND parent_owner_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[])
618+
AND parent_owner_pe.status = ANY(${livePausedStatusArray})
606619
)
607620
OR EXISTS (
608621
SELECT 1
@@ -623,7 +636,7 @@ export function unreferencedLargeValuePredicate() {
623636
SELECT 1
624637
FROM ${pausedExecutions} AS parent_ref_pe
625638
WHERE parent_ref_pe.execution_id = parent_ref.execution_id
626-
AND parent_ref_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[])
639+
AND parent_ref_pe.status = ANY(${livePausedStatusArray})
627640
)
628641
)
629642
)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
5+
// Renders against the real drizzle dialect: the bug this guards against is a
6+
// rendering bug, so the global drizzle-orm mock would hide it.
7+
import { describe, expect, it, vi } from 'vitest'
8+
9+
vi.unmock('drizzle-orm')
10+
11+
import { sql } from 'drizzle-orm'
12+
import { PgDialect } from 'drizzle-orm/pg-core'
13+
import {
14+
LIVE_PAUSED_REFERENCE_STATUSES,
15+
livePausedStatusArray,
16+
} from '@/lib/execution/payloads/large-value-metadata'
17+
18+
describe('livePausedStatusArray', () => {
19+
const dialect = new PgDialect()
20+
21+
it('renders an ARRAY literal, not a row constructor', () => {
22+
const { sql: text, params } = dialect.sqlToQuery(
23+
sql`select 1 where s = ANY(${livePausedStatusArray})`
24+
)
25+
26+
expect(text).toContain('ARRAY[')
27+
expect(text).toContain('::text[]')
28+
// `($1, $2, $3)::text[]` is what interpolating the raw JS array produces;
29+
// Postgres rejects it with "cannot cast type record to text[]".
30+
expect(text).not.toMatch(/\(\$\d+(, \$\d+)+\)::text\[\]/)
31+
expect(params).toEqual([...LIVE_PAUSED_REFERENCE_STATUSES])
32+
})
33+
34+
it('binds one parameter per status', () => {
35+
const { sql: text } = dialect.sqlToQuery(sql`${livePausedStatusArray}`)
36+
const placeholders = text.match(/\$\d+/g) ?? []
37+
expect(placeholders).toHaveLength(LIVE_PAUSED_REFERENCE_STATUSES.length)
38+
})
39+
})

0 commit comments

Comments
 (0)