From 8a56c9fa0c32d370ba5b599f98868d3f41a2b86f Mon Sep 17 00:00:00 2001 From: CoolCoolTomato <2983315455@qq.com> Date: Fri, 5 Jun 2026 20:48:04 +0800 Subject: [PATCH] fix(setup): bootstrap postgres connection with maintenance db --- backend/internal/setup/setup.go | 25 +++++++++++++++---------- backend/internal/setup/setup_test.go | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/backend/internal/setup/setup.go b/backend/internal/setup/setup.go index 9256d24575..51baf3dfe6 100644 --- a/backend/internal/setup/setup.go +++ b/backend/internal/setup/setup.go @@ -160,13 +160,23 @@ func NeedsSetup() bool { return true } +func buildPostgresDSN(cfg *DatabaseConfig, dbName string) string { + return fmt.Sprintf( + "host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", + cfg.Host, cfg.Port, cfg.User, cfg.Password, dbName, cfg.SSLMode, + ) +} + +func buildDatabaseConnectionDSNs(cfg *DatabaseConfig) (bootstrapDSN, targetDSN string) { + return buildPostgresDSN(cfg, "postgres"), buildPostgresDSN(cfg, cfg.DBName) +} + // TestDatabaseConnection tests the database connection and creates database if not exists func TestDatabaseConnection(cfg *DatabaseConfig) error { - // First, connect to the default 'postgres' database to check/create target database - defaultDSN := fmt.Sprintf( - "host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", - cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.DBName, cfg.SSLMode, - ) + // First, connect to the default 'postgres' database to check/create target database. + // Connecting to cfg.DBName here fails when the target database has not been + // created yet, so the bootstrap connection must use PostgreSQL's maintenance DB. + defaultDSN, targetDSN := buildDatabaseConnectionDSNs(cfg) db, err := sql.Open("postgres", defaultDSN) if err != nil { @@ -214,11 +224,6 @@ func TestDatabaseConnection(cfg *DatabaseConfig) error { } db = nil - targetDSN := fmt.Sprintf( - "host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", - cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.DBName, cfg.SSLMode, - ) - targetDB, err := sql.Open("postgres", targetDSN) if err != nil { return fmt.Errorf("failed to connect to database '%s': %w", cfg.DBName, err) diff --git a/backend/internal/setup/setup_test.go b/backend/internal/setup/setup_test.go index a01dd00c4d..a2aa2f4cc1 100644 --- a/backend/internal/setup/setup_test.go +++ b/backend/internal/setup/setup_test.go @@ -87,3 +87,26 @@ func TestWriteConfigFileKeepsDefaultUserConcurrency(t *testing.T) { t.Fatalf("config missing default user concurrency, got:\n%s", string(data)) } } + +func TestBuildDatabaseConnectionDSNsUsesPostgresForBootstrap(t *testing.T) { + cfg := &DatabaseConfig{ + Host: "db", + Port: 5432, + User: "sub2api", + Password: "secret", + DBName: "sub2api", + SSLMode: "disable", + } + + bootstrapDSN, targetDSN := buildDatabaseConnectionDSNs(cfg) + + if !strings.Contains(bootstrapDSN, "dbname=postgres") { + t.Fatalf("bootstrap DSN = %q, want default postgres database", bootstrapDSN) + } + if strings.Contains(bootstrapDSN, "dbname=sub2api") { + t.Fatalf("bootstrap DSN = %q, should not connect to target database before checking/creating it", bootstrapDSN) + } + if !strings.Contains(targetDSN, "dbname=sub2api") { + t.Fatalf("target DSN = %q, want configured database", targetDSN) + } +}