diff --git a/packages/bun-types/sql.d.ts b/packages/bun-types/sql.d.ts index b074e9d2a4..b792170381 100644 --- a/packages/bun-types/sql.d.ts +++ b/packages/bun-types/sql.d.ts @@ -272,14 +272,11 @@ declare module "bun" { */ ssl?: TLSOptions | boolean | undefined; - // `.path` is currently unsupported in Bun, the implementation is - // incomplete. - // - // /** - // * Unix domain socket path for connection - // * @default "" - // */ - // path?: string | undefined; + /** + * Unix domain socket path for connection + * @default undefined + */ + path?: string | undefined; /** * Callback executed when a connection attempt completes diff --git a/src/js/internal/sql/shared.ts b/src/js/internal/sql/shared.ts index adabcbbcf2..062e27a005 100644 --- a/src/js/internal/sql/shared.ts +++ b/src/js/internal/sql/shared.ts @@ -305,7 +305,7 @@ function parseOptions( onclose: ((client: Bun.SQL) => void) | undefined, max: number | null | undefined, bigint: boolean | undefined, - path: string | string[], + path: string, adapter: Bun.SQL.__internal.Adapter; let prepare = true; @@ -421,9 +421,19 @@ function parseOptions( port ||= Number(options.port || env.PGPORT || (adapter === "mysql" ? 3306 : 5432)); path ||= (options as { path?: string }).path || ""; - // add /.s.PGSQL.${port} if it doesn't exist - if (path && path?.indexOf("/.s.PGSQL.") === -1 && adapter === "postgres") { - path = `${path}/.s.PGSQL.${port}`; + + if (adapter === "postgres") { + // add /.s.PGSQL.${port} if the unix domain socket is listening on that path + if (path && Number.isSafeInteger(port) && path?.indexOf("/.s.PGSQL.") === -1) { + const pathWithSocket = `${path}/.s.PGSQL.${port}`; + + // Only add the path if it actually exists. It would be better to just + // always respect whatever the user passes in, but that would technically + // be a breakpoint change at this point. + if (require("node:fs").existsSync(pathWithSocket)) { + path = pathWithSocket; + } + } } username ||= @@ -579,6 +589,12 @@ function parseOptions( ret.onclose = onclose; } + if (path) { + if (require("node:fs").existsSync(path)) { + ret.path = path; + } + } + return ret; } diff --git a/test/js/sql/sql.test.ts b/test/js/sql/sql.test.ts index 16930ff773..a0879b132f 100644 --- a/test/js/sql/sql.test.ts +++ b/test/js/sql/sql.test.ts @@ -147,9 +147,88 @@ if (isDockerEnabled()) { // --- Expected pg_hba.conf --- process.env.DATABASE_URL = `postgres://bun_sql_test@localhost:${container.port}/bun_sql_test`; + const net = require("node:net"); + const fs = require("node:fs"); + const path = require("node:path"); + const os = require("node:os"); + + // Create a temporary unix domain socket path + const socketPath = path.join(os.tmpdir(), `postgres_echo_${Date.now()}.sock`); + + // Clean up any existing socket file + try { + fs.unlinkSync(socketPath); + } catch {} + + // Create a unix domain socket server that proxies to the PostgreSQL container + const socketServer = net.createServer(clientSocket => { + console.log("PostgreSQL connection received on unix socket"); + + // Create connection to the actual PostgreSQL container + const containerSocket = net.createConnection({ + host: login.host, + port: login.port, + }); + + // Handle container connection + containerSocket.on("connect", () => { + console.log("Connected to PostgreSQL container"); + }); + + containerSocket.on("error", err => { + console.error("Container connection error:", err); + clientSocket.destroy(); + }); + + containerSocket.on("close", () => { + console.log("Container connection closed"); + clientSocket.end(); + }); + + // Handle client socket + clientSocket.on("data", data => { + // Forward client data to container + containerSocket.write(data); + }); + + clientSocket.on("error", err => { + console.error("Client socket error:", err); + containerSocket.destroy(); + }); + + clientSocket.on("close", () => { + console.log("Client connection closed"); + containerSocket.end(); + }); + + // Forward container responses back to client + containerSocket.on("data", data => { + clientSocket.write(data); + }); + }); + + socketServer.listen(socketPath, () => { + console.log(`Unix domain socket server listening on ${socketPath}`); + }); + + // Clean up the socket on exit + afterAll(() => { + socketServer.close(); + try { + fs.unlinkSync(socketPath); + } catch {} + }); + const login: Bun.SQL.PostgresOrMySQLOptions = { username: "bun_sql_test", port: container.port, + path: socketPath, + }; + + const login_domain_socket: Bun.SQL.PostgresOrMySQLOptions = { + username: "bun_sql_test", + port: container.port, + path: socketPath, }; const login_md5: Bun.SQL.PostgresOrMySQLOptions = { @@ -1036,6 +1115,11 @@ if (isDockerEnabled()) { expect((await sql`select true as x`)[0].x).toBe(true); }); + test("unix domain socket can send query", async () => { + await using sql = postgres({ ...options, ...login_domain_socket }); + expect((await sql`select true as x`)[0].x).toBe(true); + }); + test("Login using MD5", async () => { await using sql = postgres({ ...options, ...login_md5 }); expect(await sql`select true as x`).toEqual([{ x: true }]);