mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-01 14:59:19 +08:00
fix(cron): bind stale async-job cutoffs through the column encoder (#6327)
The stale-processing predicate interpolated `Date` values straight into a raw `sql` template. A raw template carries no column context, so drizzle skips `PgTimestamp.mapToDriverValue` (which stringifies via `toISOString`) and postgres-js receives a `Date` it cannot serialize under the pools' `prepare: false` / `fetch_types: false` options. Every run of the job has failed its async-job sweep since the change shipped, leaving stuck jobs unreaped while the surrounding typed `lt(column, date)` sweeps succeeded. Bind both cutoffs with `sql.param(date, column)`, matching the workflow sweep in the same handler. The testing `sql` mock already rejected `sql.param(date)` for this reason but not the interpolated form that shipped, so extend it to cover both. That guard alone fails three existing tests when the fix is reverted, and the full suite shows no other route binding a bare Date this way.
This commit is contained in:
@@ -245,10 +245,14 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
|
||||
= (${asyncJobs.metadata}->>'maxDurationSeconds')::numeric
|
||||
ELSE FALSE
|
||||
END`
|
||||
// A bare `Date` in a raw template reaches the driver unserialized; `sql.param`
|
||||
// binds it through the column encoder, as `lt(column, date)` does elsewhere here.
|
||||
const staleProcessingCutoff = sql.param(now, asyncJobs.startedAt)
|
||||
const staleProcessingFallbackCutoff = sql.param(staleThreshold, asyncJobs.startedAt)
|
||||
const staleProcessingDurationPredicate = sql<boolean>`CASE
|
||||
WHEN ${hasPositiveMaxDuration}
|
||||
THEN ${asyncJobs.startedAt} + ((${asyncJobs.metadata}->>'maxDurationSeconds')::double precision * interval '1 second') < ${now}
|
||||
ELSE ${asyncJobs.startedAt} < ${staleThreshold}
|
||||
THEN ${asyncJobs.startedAt} + ((${asyncJobs.metadata}->>'maxDurationSeconds')::double precision * interval '1 second') < ${staleProcessingCutoff}
|
||||
ELSE ${asyncJobs.startedAt} < ${staleProcessingFallbackCutoff}
|
||||
END`
|
||||
const staleProcessingPredicate = and(
|
||||
eq(asyncJobs.status, JOB_STATUS.PROCESSING),
|
||||
|
||||
@@ -6,6 +6,16 @@ import { vi } from 'vitest'
|
||||
*/
|
||||
export function createMockSql() {
|
||||
const sqlFn = (strings: TemplateStringsArray, ...values: any[]) => {
|
||||
// Same hazard as `sql.param(date)` below, and the form that actually shipped:
|
||||
// an interpolated `Date` carries no column context, so drizzle skips
|
||||
// `PgTimestamp.mapToDriverValue` and postgres-js receives a Date it cannot serialize.
|
||||
if (values.some((value) => value instanceof Date)) {
|
||||
throw new Error(
|
||||
'sql`…${date}` interpolates a Date without an encoder, which reaches ' +
|
||||
'postgres-js as a Date object its unsafe path cannot serialize. Bind ' +
|
||||
'through the matching column: sql.param(date, table.timestampColumn).'
|
||||
)
|
||||
}
|
||||
const fragment = {
|
||||
strings,
|
||||
values,
|
||||
|
||||
Reference in New Issue
Block a user