mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
This PR makes connection string parsing more sensible in Bun.SQL, without breaking the default fallback of postgres Added some tests checking for connection string precedence --------- Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
476 lines
18 KiB
TypeScript
476 lines
18 KiB
TypeScript
import { SQL } from "bun";
|
|
import { afterAll, beforeEach, describe, expect, test } from "bun:test";
|
|
import { isWindows } from "harness";
|
|
import { unlinkSync } from "js/node/fs/export-star-from";
|
|
|
|
declare module "bun" {
|
|
namespace SQL {
|
|
export interface PostgresOrMySQLOptions {
|
|
sslMode?: number;
|
|
}
|
|
}
|
|
}
|
|
|
|
describe("SQL adapter environment variable precedence", () => {
|
|
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',
|
|
'MARIADB_URL', 'MARIADBURL',
|
|
'TLS_MARIADB_DATABASE_URL',
|
|
'SQLITE_URL', 'SQLITEURL',
|
|
'PGHOST', 'PGUSER', 'PGPASSWORD', 'PGDATABASE', 'PGPORT',
|
|
'MYSQL_HOST', 'MYSQL_USER', 'MYSQL_PASSWORD', 'MYSQL_DATABASE', 'MYSQL_PORT'
|
|
];
|
|
|
|
beforeEach(() => {
|
|
for (const key of Object.keys(process.env).concat(...Object.keys(Bun.env), ...Object.keys(import.meta.env))) {
|
|
delete process.env[key];
|
|
delete Bun.env[key];
|
|
delete import.meta.env[key];
|
|
}
|
|
|
|
for (const key in originalEnv) {
|
|
process.env[key] = originalEnv[key];
|
|
Bun.env[key] = originalEnv[key];
|
|
import.meta.env[key] = originalEnv[key];
|
|
}
|
|
|
|
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 Object.keys(process.env).concat(...Object.keys(Bun.env), ...Object.keys(import.meta.env))) {
|
|
delete process.env[key];
|
|
delete Bun.env[key];
|
|
delete import.meta.env[key];
|
|
}
|
|
|
|
for (const key in originalEnv) {
|
|
process.env[key] = originalEnv[key];
|
|
Bun.env[key] = originalEnv[key];
|
|
import.meta.env[key] = originalEnv[key];
|
|
}
|
|
|
|
for (const key of SQL_ENV_VARS) {
|
|
delete process.env[key];
|
|
delete Bun.env[key];
|
|
delete import.meta.env[key];
|
|
}
|
|
});
|
|
|
|
test("should not prioritize DATABASE_URL over explicit options (issue #22147)", () => {
|
|
process.env.DATABASE_URL = "foo_url";
|
|
|
|
const options = new SQL({
|
|
hostname: "bar_url",
|
|
username: "postgres",
|
|
password: "postgres",
|
|
port: 5432,
|
|
});
|
|
|
|
expect(options.options.adapter).toBe("postgres");
|
|
expect(options.options.hostname).toBe("bar_url");
|
|
expect(options.options.port).toBe(5432);
|
|
expect(options.options.username).toBe("postgres");
|
|
});
|
|
|
|
test("should only read PostgreSQL env vars when adapter is postgres", () => {
|
|
process.env.PGHOST = "pg-host";
|
|
process.env.PGUSER = "pg-user";
|
|
process.env.PGPASSWORD = "pg-pass";
|
|
process.env.MYSQL_URL = "mysql://mysql-host/db";
|
|
|
|
const options = new SQL({
|
|
adapter: "postgres",
|
|
});
|
|
|
|
expect(options.options.hostname).toBe("pg-host");
|
|
expect(options.options.username).toBe("pg-user");
|
|
expect(options.options.password).toBe("pg-pass");
|
|
// Should not use MYSQL_URL
|
|
expect(options.options.hostname).not.toBe("mysql-host");
|
|
});
|
|
|
|
test("should only read MySQL env vars when adapter is mysql", () => {
|
|
process.env.PGHOST = "pg-host";
|
|
process.env.PGUSER = "pg-user";
|
|
process.env.MYSQL_URL = "mysql://mysql-host/db";
|
|
|
|
const options = new SQL({
|
|
adapter: "mysql",
|
|
});
|
|
|
|
// Should use MYSQL_URL and not read PostgreSQL env vars
|
|
expect(options.options.hostname).toBe("mysql-host");
|
|
expect(options.options.username).not.toBe("pg-user");
|
|
});
|
|
|
|
test("should infer postgres adapter from postgres:// protocol", () => {
|
|
const options = new SQL("postgres://user:pass@host:5432/db");
|
|
expect(options.options.adapter).toBe("postgres");
|
|
});
|
|
|
|
test("should infer mysql adapter from mysql:// protocol", () => {
|
|
const options = new SQL("mysql://user:pass@host:3306/db");
|
|
expect(options.options.adapter).toBe("mysql");
|
|
});
|
|
|
|
test("should default to postgres when no protocol specified", () => {
|
|
const options = new SQL("user:pass@host/db");
|
|
expect(options.options.adapter).toBe("postgres");
|
|
});
|
|
|
|
test("adapter-specific env vars should take precedence over generic ones", () => {
|
|
process.env.USER = "generic-user";
|
|
process.env.PGUSER = "postgres-user";
|
|
|
|
const options = new SQL({
|
|
adapter: "postgres",
|
|
});
|
|
|
|
expect(options.options.username).toBe("postgres-user");
|
|
});
|
|
|
|
test("should infer mysql adapter from MYSQL_URL env var", () => {
|
|
process.env.MYSQL_URL = "mysql://user:pass@host:3306/db";
|
|
|
|
const options = new SQL();
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.hostname).toBe("host");
|
|
expect(options.options.port).toBe(3306);
|
|
});
|
|
|
|
test("should default to port 3306 for MySQL when no port specified", () => {
|
|
process.env.MYSQL_URL = "mysql://user:pass@host/db";
|
|
|
|
const options = new SQL();
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.hostname).toBe("host");
|
|
expect(options.options.port).toBe(3306); // Should default to MySQL port
|
|
});
|
|
|
|
test("should default to port 3306 for explicit MySQL adapter", () => {
|
|
const options = new SQL({
|
|
adapter: "mysql",
|
|
hostname: "localhost",
|
|
});
|
|
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.port).toBe(3306); // Should default to MySQL port
|
|
});
|
|
|
|
test("should infer postgres adapter from POSTGRES_URL env var", () => {
|
|
process.env.POSTGRES_URL = "postgres://user:pass@host:5432/db";
|
|
|
|
const options = new SQL();
|
|
expect(options.options.adapter).toBe("postgres");
|
|
expect(options.options.hostname).toBe("host");
|
|
expect(options.options.port).toBe(5432);
|
|
});
|
|
|
|
test("POSTGRES_URL should take precedence over MYSQL_URL", () => {
|
|
process.env.POSTGRES_URL = "postgres://pg-host:5432/pgdb";
|
|
process.env.MYSQL_URL = "mysql://mysql-host:3306/mysqldb";
|
|
|
|
const options = new SQL();
|
|
expect(options.options.adapter).toBe("postgres");
|
|
expect(options.options.hostname).toBe("pg-host");
|
|
expect(options.options.port).toBe(5432);
|
|
});
|
|
|
|
test("should infer mysql from MYSQL_URL even without protocol", () => {
|
|
process.env.MYSQL_URL = "root@localhost:3306/test";
|
|
|
|
const options = new SQL();
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.hostname).toBe("localhost");
|
|
expect(options.options.port).toBe(3306);
|
|
expect(options.options.username).toBe("root");
|
|
});
|
|
|
|
test("should infer postgres from POSTGRES_URL even without protocol", () => {
|
|
process.env.POSTGRES_URL = "user@localhost:5432/test";
|
|
|
|
const options = new SQL();
|
|
expect(options.options.adapter).toBe("postgres");
|
|
expect(options.options.hostname).toBe("localhost");
|
|
expect(options.options.port).toBe(5432);
|
|
expect(options.options.username).toBe("user");
|
|
});
|
|
|
|
test("environment variable name should override protocol (PGURL with mysql protocol should be postgres)", () => {
|
|
process.env.PGURL = "mysql://host:3306/db";
|
|
|
|
const options = new SQL();
|
|
expect(options.options.adapter).toBe("postgres");
|
|
expect(options.options.hostname).toBe("host");
|
|
expect(options.options.port).toBe(3306);
|
|
});
|
|
|
|
test("environment variable name should override protocol (MYSQL_URL with postgres protocol should be mysql)", () => {
|
|
process.env.MYSQL_URL = "postgres://host:5432/db";
|
|
|
|
const options = new SQL();
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.hostname).toBe("host");
|
|
expect(options.options.port).toBe(5432);
|
|
});
|
|
test("should use MySQL-specific environment variables", () => {
|
|
process.env.MYSQL_HOST = "mysql-server";
|
|
process.env.MYSQL_PORT = "3307";
|
|
process.env.MYSQL_USER = "admin";
|
|
process.env.MYSQL_PASSWORD = "secret";
|
|
process.env.MYSQL_DATABASE = "production";
|
|
|
|
const options = new SQL({ adapter: "mysql" });
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.hostname).toBe("mysql-server");
|
|
expect(options.options.port).toBe(3307);
|
|
expect(options.options.username).toBe("admin");
|
|
expect(options.options.password).toBe("secret");
|
|
expect(options.options.database).toBe("production");
|
|
});
|
|
|
|
test("MySQL-specific env vars should take precedence over generic ones", () => {
|
|
process.env.USER = "generic-user";
|
|
process.env.MYSQL_USER = "mysql-user";
|
|
|
|
const options = new SQL({ adapter: "mysql" });
|
|
expect(options.options.username).toBe("mysql-user");
|
|
});
|
|
|
|
test("should default to database name 'mysql' for MySQL adapter", () => {
|
|
const options = new SQL({ adapter: "mysql", hostname: "localhost" });
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.database).toBe("mysql");
|
|
});
|
|
|
|
test("should default to username as database name for PostgreSQL adapter", () => {
|
|
const options = new SQL({ adapter: "postgres", hostname: "localhost", username: "testuser" });
|
|
expect(options.options.adapter).toBe("postgres");
|
|
expect(options.options.database).toBe("testuser");
|
|
});
|
|
|
|
test("should infer mysql adapter from TLS_MYSQL_DATABASE_URL", () => {
|
|
process.env.TLS_MYSQL_DATABASE_URL = "mysql://user:pass@host:3306/db";
|
|
|
|
const options = new SQL();
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.hostname).toBe("host");
|
|
expect(options.options.port).toBe(3306);
|
|
expect(options.options.sslMode).toBe(2); // SSLMode.require
|
|
});
|
|
|
|
test("should infer postgres adapter from TLS_POSTGRES_DATABASE_URL", () => {
|
|
process.env.TLS_POSTGRES_DATABASE_URL = "postgres://user:pass@host:5432/db";
|
|
|
|
const options = new SQL();
|
|
expect(options.options.adapter).toBe("postgres");
|
|
expect(options.options.hostname).toBe("host");
|
|
expect(options.options.port).toBe(5432);
|
|
expect(options.options.sslMode).toBe(2); // SSLMode.require
|
|
});
|
|
|
|
test("should infer adapter from TLS_DATABASE_URL using protocol", () => {
|
|
process.env.TLS_DATABASE_URL = "mysql://user:pass@host:3306/db";
|
|
|
|
const options = new SQL();
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.hostname).toBe("host");
|
|
expect(options.options.port).toBe(3306);
|
|
expect(options.options.sslMode).toBe(2); // SSLMode.require
|
|
});
|
|
|
|
describe("Adapter-Protocol Validation", () => {
|
|
test("should work with explicit adapter and URL without protocol", () => {
|
|
const options = new SQL("user:pass@host:3306/db", { adapter: "mysql" });
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.hostname).toBe("host");
|
|
expect(options.options.port).toBe(3306);
|
|
});
|
|
|
|
test("should work with explicit adapter and matching protocol", () => {
|
|
const options = new SQL("mysql://user:pass@host:3306/db", { adapter: "mysql" });
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.hostname).toBe("host");
|
|
expect(options.options.port).toBe(3306);
|
|
});
|
|
|
|
test.skipIf(isWindows)("should work with unix:// protocol and explicit adapter", () => {
|
|
using sock = Bun.listen({
|
|
unix: "/tmp/thisisacoolmysql.sock",
|
|
socket: {
|
|
data: console.log,
|
|
},
|
|
});
|
|
|
|
const options = new SQL(`unix://${sock.unix}`, { adapter: "mysql" });
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.path).toBe("/tmp/thisisacoolmysql.sock");
|
|
|
|
unlinkSync(sock.unix);
|
|
});
|
|
|
|
test("should work with sqlite:// protocol and sqlite adapter", () => {
|
|
const options = new SQL("sqlite:///tmp/test.db", { adapter: "sqlite" });
|
|
expect(options.options.adapter).toBe("sqlite");
|
|
expect(options.options.filename).toBe("/tmp/test.db");
|
|
});
|
|
|
|
test("should work with sqlite:// protocol without adapter", () => {
|
|
const options = new SQL("sqlite:///tmp/test.db");
|
|
expect(options.options.adapter).toBe("sqlite");
|
|
expect(options.options.filename).toBe("/tmp/test.db");
|
|
});
|
|
|
|
describe("Explicit options override URL parameters", () => {
|
|
test("explicit hostname should override URL hostname", () => {
|
|
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
|
|
hostname: "explicithost",
|
|
});
|
|
|
|
expect(options.options.hostname).toBe("explicithost");
|
|
expect(options.options.port).toBe(1234); // URL port should remain
|
|
expect(options.options.username).toBe("urluser"); // URL username should remain
|
|
expect(options.options.database).toBe("urldb"); // URL database should remain
|
|
});
|
|
|
|
test("explicit port should override URL port", () => {
|
|
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
|
|
port: 5432,
|
|
});
|
|
|
|
expect(options.options.hostname).toBe("urlhost"); // URL hostname should remain
|
|
expect(options.options.port).toBe(5432);
|
|
expect(options.options.username).toBe("urluser"); // URL username should remain
|
|
expect(options.options.database).toBe("urldb"); // URL database should remain
|
|
});
|
|
|
|
test("explicit username should override URL username", () => {
|
|
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
|
|
username: "explicituser",
|
|
});
|
|
|
|
expect(options.options.hostname).toBe("urlhost"); // URL hostname should remain
|
|
expect(options.options.port).toBe(1234); // URL port should remain
|
|
expect(options.options.username).toBe("explicituser");
|
|
expect(options.options.database).toBe("urldb"); // URL database should remain
|
|
});
|
|
|
|
test("explicit password should override URL password", () => {
|
|
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
|
|
password: "explicitpass",
|
|
});
|
|
|
|
expect(options.options.hostname).toBe("urlhost"); // URL hostname should remain
|
|
expect(options.options.port).toBe(1234); // URL port should remain
|
|
expect(options.options.username).toBe("urluser"); // URL username should remain
|
|
expect(options.options.password).toBe("explicitpass");
|
|
expect(options.options.database).toBe("urldb"); // URL database should remain
|
|
});
|
|
|
|
test("explicit database should override URL database", () => {
|
|
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
|
|
database: "explicitdb",
|
|
});
|
|
|
|
expect(options.options.hostname).toBe("urlhost"); // URL hostname should remain
|
|
expect(options.options.port).toBe(1234); // URL port should remain
|
|
expect(options.options.username).toBe("urluser"); // URL username should remain
|
|
expect(options.options.database).toBe("explicitdb");
|
|
});
|
|
|
|
test("multiple explicit options should override corresponding URL parameters", () => {
|
|
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
|
|
hostname: "explicithost",
|
|
port: 5432,
|
|
username: "explicituser",
|
|
password: "explicitpass",
|
|
database: "explicitdb",
|
|
});
|
|
|
|
expect(options.options.hostname).toBe("explicithost");
|
|
expect(options.options.port).toBe(5432);
|
|
expect(options.options.username).toBe("explicituser");
|
|
expect(options.options.password).toBe("explicitpass");
|
|
expect(options.options.database).toBe("explicitdb");
|
|
});
|
|
|
|
test("should work with MySQL URLs and explicit options", () => {
|
|
const options = new SQL("mysql://urluser:urlpass@urlhost:3306/urldb", {
|
|
hostname: "explicithost",
|
|
port: 3307,
|
|
username: "explicituser",
|
|
});
|
|
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.hostname).toBe("explicithost");
|
|
expect(options.options.port).toBe(3307);
|
|
expect(options.options.username).toBe("explicituser");
|
|
expect(options.options.password).toBe("urlpass"); // URL password should remain
|
|
expect(options.options.database).toBe("urldb"); // URL database should remain
|
|
});
|
|
|
|
test("should work with alternative option names (user, pass, db, host)", () => {
|
|
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
|
|
host: "explicithost",
|
|
user: "explicituser",
|
|
pass: "explicitpass",
|
|
db: "explicitdb",
|
|
});
|
|
|
|
expect(options.options.hostname).toBe("explicithost");
|
|
expect(options.options.username).toBe("explicituser");
|
|
expect(options.options.password).toBe("explicitpass");
|
|
expect(options.options.database).toBe("explicitdb");
|
|
});
|
|
|
|
test("explicit options should override URL even when environment variables are present", () => {
|
|
process.env.PGHOST = "envhost";
|
|
process.env.PGPORT = "9999";
|
|
process.env.PGUSER = "envuser";
|
|
|
|
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
|
|
hostname: "explicithost",
|
|
port: 5432,
|
|
username: "explicituser",
|
|
});
|
|
|
|
expect(options.options.hostname).toBe("explicithost");
|
|
expect(options.options.port).toBe(5432);
|
|
expect(options.options.username).toBe("explicituser");
|
|
expect(options.options.password).toBe("urlpass"); // URL password should remain since no explicit password
|
|
expect(options.options.database).toBe("urldb"); // URL database should remain
|
|
});
|
|
|
|
test("explicit options should have higher precedence than environment-specific variables", () => {
|
|
process.env.MYSQL_HOST = "mysqlhost";
|
|
process.env.MYSQL_USER = "mysqluser";
|
|
process.env.MYSQL_PASSWORD = "mysqlpass";
|
|
|
|
const options = new SQL("mysql://urluser:urlpass@urlhost:3306/urldb", {
|
|
hostname: "explicithost",
|
|
username: "explicituser",
|
|
});
|
|
|
|
expect(options.options.adapter).toBe("mysql");
|
|
expect(options.options.hostname).toBe("explicithost");
|
|
expect(options.options.username).toBe("explicituser");
|
|
expect(options.options.password).toBe("urlpass"); // URL password (not env)
|
|
expect(options.options.database).toBe("urldb"); // URL database should remain
|
|
});
|
|
});
|
|
});
|
|
});
|