fix(db): PG adapter coerce 0/1 to boolean for PG strict typing

PG rejects integer values for BOOLEAN columns. B() helper returns 0/1
for SQLite compat. PG adapter now converts 0→false, 1→true in params
before sending — safe for both INTEGER and BOOLEAN column types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mitchell R 2026-05-23 12:50:07 +02:00
parent e71189b874
commit 5cefa04a45
No known key found for this signature in database

View file

@ -59,6 +59,19 @@ export class PgAdapter implements DbAdapter {
return out; return out;
} }
private coerceParams(params: ReadonlyArray<SqlValue>): unknown[] {
return params.map((v) => {
if (v === 0 || v === 1) {
// Could be integer or boolean. PG is strict about boolean columns
// receiving integer values. We can't know the column type here, but
// the `pg` driver accepts JS booleans for both INTEGER and BOOLEAN
// columns, so converting 0/1 to false/true is always safe.
return v === 1;
}
return v;
});
}
private async runner<T>(fn: (c: PoolClient) => Promise<T>): Promise<T> { private async runner<T>(fn: (c: PoolClient) => Promise<T>): Promise<T> {
if (this.currentTxClient) return fn(this.currentTxClient); if (this.currentTxClient) return fn(this.currentTxClient);
const client = await this.pool.connect(); const client = await this.pool.connect();
@ -68,8 +81,9 @@ export class PgAdapter implements DbAdapter {
async run(sql: string, params: ReadonlyArray<SqlValue> = []): Promise<RunResult> { async run(sql: string, params: ReadonlyArray<SqlValue> = []): Promise<RunResult> {
const pgSql = this.rewriteSql(sql); const pgSql = this.rewriteSql(sql);
const pgParams = this.coerceParams(params);
return this.runner(async (c) => { return this.runner(async (c) => {
const res = await c.query(pgSql, params as unknown[]); const res = await c.query(pgSql, pgParams);
let lastInsertRowid = 0n; let lastInsertRowid = 0n;
// If the caller added RETURNING id, pluck it. // If the caller added RETURNING id, pluck it.
if (res.rows.length > 0 && res.rows[0] && "id" in res.rows[0]) { if (res.rows.length > 0 && res.rows[0] && "id" in res.rows[0]) {
@ -84,16 +98,18 @@ export class PgAdapter implements DbAdapter {
async get<T = Row>(sql: string, params: ReadonlyArray<SqlValue> = []): Promise<T | undefined> { async get<T = Row>(sql: string, params: ReadonlyArray<SqlValue> = []): Promise<T | undefined> {
const pgSql = this.rewriteSql(sql); const pgSql = this.rewriteSql(sql);
const pgParams = this.coerceParams(params);
return this.runner(async (c) => { return this.runner(async (c) => {
const res = await c.query(pgSql, params as unknown[]); const res = await c.query(pgSql, pgParams);
return (res.rows[0] as T | undefined); return (res.rows[0] as T | undefined);
}); });
} }
async all<T = Row>(sql: string, params: ReadonlyArray<SqlValue> = []): Promise<T[]> { async all<T = Row>(sql: string, params: ReadonlyArray<SqlValue> = []): Promise<T[]> {
const pgSql = this.rewriteSql(sql); const pgSql = this.rewriteSql(sql);
const pgParams = this.coerceParams(params);
return this.runner(async (c) => { return this.runner(async (c) => {
const res = await c.query(pgSql, params as unknown[]); const res = await c.query(pgSql, pgParams);
return res.rows as T[]; return res.rows as T[];
}); });
} }