Compare commits

...

3 Commits

Author SHA1 Message Date
Claude Bot
bf42798d9d ci: retry build (previous failed due to Buildkite infra issue)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-03 13:25:29 +00:00
Claude Bot
3cd29a93a0 fix(test): address review feedback on test cleanup
- Restrict afterAll env cleanup to only SQL_ENV_VARS instead of
  wiping all environment variables, preventing cross-test coupling
- Use unique per-run socket path with process.pid suffix to avoid
  collisions across parallel test runs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-03 12:59:10 +00:00
Claude Bot
d7616e1046 fix(sql): don't treat URL pathname as Unix socket path
When parsing a Postgres/MySQL connection URL like
`postgres://user:pass@host:5432/mydb`, the URL pathname `/mydb`
(the database name) was incorrectly assigned to the `path` variable
used for Unix domain socket connections. This caused the native code
to attempt a Unix socket connection to `/mydb` instead of TCP,
resulting in FailedToOpenSocket.

Only use `url.pathname` as the socket path when the protocol is
`unix://`. For all other protocols (postgres://, mysql://, etc.),
the pathname is the database name and should not be used as a
socket path.

Fixes #27713

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-03 12:46:12 +00:00
2 changed files with 136 additions and 1 deletions

View File

@@ -617,7 +617,14 @@ function parseOptions(
username ||= options.user || options.username || decodeIfValid(url.username);
password ||= options.pass || options.password || decodeIfValid(url.password);
path ||= options.path || url.pathname;
// Only use url.pathname as the Unix socket path for unix:// URLs.
// For postgres://, mysql://, etc., the pathname is the database name (e.g. "/mydb"),
// not a socket path. Using it as the socket path causes FailedToOpenSocket (#27713).
if (url.protocol === "unix:") {
path ||= options.path || url.pathname;
} else {
path ||= options.path;
}
const queryObject = url.searchParams.toJSON();
for (const key in queryObject) {

View File

@@ -0,0 +1,128 @@
import { SQL } from "bun";
import { afterAll, beforeEach, describe, expect, test } from "bun:test";
import { isWindows } from "harness";
// Regression test for https://github.com/oven-sh/bun/issues/27713
// Bun SQL was treating the Postgres URL path component (the database name)
// as a Unix domain socket path, causing FailedToOpenSocket on any URL with
// a database name.
describe("SQL should not treat URL pathname as Unix socket path (#27713)", () => {
const originalEnv = { ...process.env };
// prettier-ignore
const SQL_ENV_VARS = [
"DATABASE_URL", "DATABASEURL",
"TLS_DATABASE_URL",
"POSTGRES_URL", "PGURL", "PG_URL",
"TLS_POSTGRES_DATABASE_URL",
"MYSQL_URL", "MYSQLURL",
"TLS_MYSQL_DATABASE_URL",
"PGHOST", "PGUSER", "PGPASSWORD", "PGDATABASE", "PGPORT",
"PG_HOST", "PG_USER", "PG_PASSWORD", "PG_DATABASE", "PG_PORT",
"MYSQL_HOST", "MYSQL_USER", "MYSQL_PASSWORD", "MYSQL_DATABASE", "MYSQL_PORT",
];
beforeEach(() => {
for (const key of SQL_ENV_VARS) {
delete process.env[key];
delete Bun.env[key];
delete import.meta.env[key];
}
});
afterAll(() => {
for (const key of SQL_ENV_VARS) {
if (key in originalEnv) {
process.env[key] = originalEnv[key]!;
Bun.env[key] = originalEnv[key]!;
import.meta.env[key] = originalEnv[key]!;
} else {
delete process.env[key];
delete Bun.env[key];
delete import.meta.env[key];
}
}
});
test("postgres URL with database name should not set path", () => {
const sql = new SQL("postgres://user:pass@myhost:5432/mydb");
expect(sql.options.hostname).toBe("myhost");
expect(sql.options.port).toBe(5432);
expect(sql.options.database).toBe("mydb");
// path must not be the database name "/mydb"
expect(sql.options.path).toBeUndefined();
});
test("postgres URL passed via url option should not set path", () => {
const sql = new SQL({
url: "postgres://user:pass@myhost:5432/mydb",
});
expect(sql.options.hostname).toBe("myhost");
expect(sql.options.port).toBe(5432);
expect(sql.options.database).toBe("mydb");
expect(sql.options.path).toBeUndefined();
});
test("DATABASE_URL with database name should not set path when using explicit options", () => {
process.env.DATABASE_URL = "postgres://user:pass@envhost:5432/envdb";
const sql = new SQL({
hostname: "myhost",
port: 5432,
username: "user",
password: "pass",
database: "mydb",
});
expect(sql.options.hostname).toBe("myhost");
expect(sql.options.database).toBe("mydb");
// path must not be "/envdb" from DATABASE_URL
expect(sql.options.path).toBeUndefined();
});
test("DATABASE_URL with database name should not set path when used implicitly", () => {
process.env.DATABASE_URL = "postgres://user:pass@envhost:5432/envdb";
const sql = new SQL();
expect(sql.options.hostname).toBe("envhost");
expect(sql.options.port).toBe(5432);
expect(sql.options.database).toBe("envdb");
// path must not be "/envdb"
expect(sql.options.path).toBeUndefined();
});
test("postgres URL with database name matching existing directory should not set path", () => {
// This is the actual bug: when the URL pathname matches an existing filesystem
// path (like /tmp), the old code would pass it as a Unix socket path.
// The database name in postgres://.../<dbname> is "/tmp" here, which exists.
const sql = new SQL("postgres://user:pass@myhost:5432/tmp");
expect(sql.options.hostname).toBe("myhost");
expect(sql.options.port).toBe(5432);
expect(sql.options.database).toBe("tmp");
// Before the fix, this would be "/tmp" (or "/tmp/.s.PGSQL.5432" if that exists),
// causing the connection to use Unix domain socket instead of TCP.
expect(sql.options.path).toBeUndefined();
});
test("mysql URL with database name should not set path", () => {
const sql = new SQL("mysql://user:pass@myhost:3306/mydb");
expect(sql.options.hostname).toBe("myhost");
expect(sql.options.port).toBe(3306);
expect(sql.options.database).toBe("mydb");
expect(sql.options.path).toBeUndefined();
});
test.skipIf(isWindows)("unix:// protocol should still use pathname as socket path", () => {
const socketPath = `/tmp/bun-test-27713-${process.pid}.sock`;
using sock = Bun.listen({
unix: socketPath,
socket: {
data: () => {},
},
});
const sql = new SQL(`unix://${sock.unix}`, { adapter: "postgres" });
expect(sql.options.path).toBe(socketPath);
});
});