mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(db): bind every raw-sql Date through its column encoder (#6337)
* fix(db): bind every raw-sql Date through its column encoder `drizzle()` overwrites postgres-js's temporal serializers (OIDs 1082/1083/ 1114/1184/1182/1185/1115/1231) with an identity function because drizzle maps timestamps itself through the column's `mapToDriverValue`. A raw `sql` template carries no column context, so an interpolated `Date` skips that mapping, reaches the identity serializer unchanged, and the wire encoder throws `ERR_INVALID_ARG_TYPE`. The pools' `prepare` / `fetch_types` options are irrelevant: the serializer swap happens for all four combinations. Five live sites still interpolated a bare `Date`, the stale schedule-job filter among them — it has no try/catch, so a database async backend would surface a 500 from the schedule tick. Bind each cutoff with `sql.param(date, column)`. The testing `sql` mock's guard cannot see untested code or the tests that override the drizzle-orm mock, so add `check:sql-date-binding`: a Babel-AST audit over apps/** and packages/** that resolves Date-valued bindings per file and rejects any that reach a raw template unbound. Correct the mock's comment, which attributed the failure to postgres-js under `fetch_types: false`. * fix(scripts): require the documented sql-date-bound annotation form and a reason
This commit is contained in:
@@ -3,17 +3,24 @@ import { vi } from 'vitest'
|
||||
/**
|
||||
* Creates mock SQL template literal function.
|
||||
* Mimics drizzle-orm's sql tagged template.
|
||||
*
|
||||
* The `Date` guards below are a best-effort backstop, not the gate: tests that
|
||||
* override the `drizzle-orm` mock bypass them entirely. `bun run check:sql-date-binding`
|
||||
* is the repo-wide authority. `drizzle()` overwrites postgres-js's temporal
|
||||
* serializers (OIDs 1082/1083/1114/1184/1182/1185/1115/1231) with an identity
|
||||
* function because drizzle maps timestamps itself through the column's
|
||||
* `mapToDriverValue`. Outside column context that mapping never runs, so the Date
|
||||
* reaches the wire encoder unserialized. The pools' `prepare` / `fetch_types`
|
||||
* options are irrelevant to this failure.
|
||||
*/
|
||||
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).'
|
||||
'sql`…${date}` interpolates a Date without an encoder, so drizzle never runs ' +
|
||||
'the column mapping and postgres-js receives an unserialized Date ' +
|
||||
'(ERR_INVALID_ARG_TYPE). Bind through the matching column: ' +
|
||||
'sql.param(date, table.timestampColumn).'
|
||||
)
|
||||
}
|
||||
const fragment = {
|
||||
@@ -56,9 +63,9 @@ export function createMockSql() {
|
||||
}
|
||||
if (encoder === undefined && value instanceof Date) {
|
||||
throw new Error(
|
||||
'sql.param(date) without an encoder reaches postgres-js as a Date object, ' +
|
||||
'which its unsafe path cannot serialize (ERR_INVALID_ARG_TYPE). Bind ' +
|
||||
'through the matching column: sql.param(date, table.timestampColumn).'
|
||||
'sql.param(date) without an encoder skips the column mapping and reaches ' +
|
||||
'postgres-js as an unserialized Date (ERR_INVALID_ARG_TYPE). Bind through ' +
|
||||
'the matching column: sql.param(date, table.timestampColumn).'
|
||||
)
|
||||
}
|
||||
return { value, toSQL: () => ({ sql: '?', params: [value] }) }
|
||||
|
||||
Reference in New Issue
Block a user