diff --git a/src/selfhost/pg-adapter.ts b/src/selfhost/pg-adapter.ts index 888891c5a..1d666fa48 100644 --- a/src/selfhost/pg-adapter.ts +++ b/src/selfhost/pg-adapter.ts @@ -39,7 +39,10 @@ class PgStatement implements SelfHostD1PreparedStatement { const { rows } = await this.exec(); const row = rows[0]; if (!row) return null; - return (colName ? row[colName] : row) as T; + // Coalesce to null so a SQL NULL (or an absent key) never leaks `undefined` through the documented + // Promise contract -- matches d1-adapter.ts's first(), the reference implementation callers + // rely on when treating the two backends interchangeably (backend-contracts.ts). + return ((colName != null ? row[colName] : row) ?? null) as T | null; } async run(): Promise<{ success: true; meta: Record }> { diff --git a/test/unit/selfhost-pg-adapter.test.ts b/test/unit/selfhost-pg-adapter.test.ts index 2e9d6d4f7..ba9ef0a85 100644 --- a/test/unit/selfhost-pg-adapter.test.ts +++ b/test/unit/selfhost-pg-adapter.test.ts @@ -46,6 +46,20 @@ describe("createPgAdapter (#977 self-host D1-over-Postgres)", () => { expect(await db.prepare("SELECT name FROM t").first("name")).toBe("a"); }); + it("first(colName) returns null when the row exists but the column value is NULL (#8361 D1 parity)", async () => { + // pg represents a SQL NULL as null; the adapter must surface null, never undefined, per its + // documented Promise contract -- the same guarantee d1-adapter.ts's first() already makes. + const db = createPgAdapter(makeMockPool([{ name: null }])); + expect(await db.prepare("SELECT name FROM t").first("name")).toBeNull(); + }); + + it("first(colName) returns null when the column is absent from the row entirely (#8361 D1 parity)", async () => { + // A driver/query shape that omits the key would previously return `undefined` cast as T, breaking the + // contract for callers that treat the Postgres and D1 backends interchangeably. + const db = createPgAdapter(makeMockPool([{ other: "a" }])); + expect(await db.prepare("SELECT name FROM t").first("name")).toBeNull(); + }); + it("run() reports success:true and a meta object carrying the row count", async () => { const db = createPgAdapter(makeMockPool([])); const result = await db.prepare("INSERT INTO t (name) VALUES (?)").bind("x").run();