package ui import ( "context" "database/sql" "fmt" "os" "strings" "time" "github.com/jackc/pgx/v5" _ "github.com/microsoft/go-mssqldb" _ "modernc.org/sqlite" ) // connTestTimeout bounds how long "Test connection" may block. const connTestTimeout = 5 * time.Second // TestConnection opens and pings the database described by f. Any occurrence // of the password in the returned error is masked. func TestConnection(f ConnFields) error { ctx, cancel := context.WithTimeout(context.Background(), connTestTimeout) defer cancel() err := testConnection(ctx, f) if err != nil && f.Password != "" { err = fmt.Errorf("%s", strings.ReplaceAll(err.Error(), f.Password, maskedPassword)) } return err } func testConnection(ctx context.Context, f ConnFields) error { switch f.Kind { case ConnPostgres: conn, err := pgx.Connect(ctx, BuildConnString(f, false)) if err != nil { return err } return conn.Close(ctx) case ConnMSSQL: return pingSQL(ctx, "sqlserver", BuildConnString(f, false)) case ConnSQLite: if f.FilePath == "" { return fmt.Errorf("file path is required") } // Opening a missing SQLite file would silently create it. if _, err := os.Stat(f.FilePath); err != nil { return err } return pingSQL(ctx, "sqlite", f.FilePath) } return fmt.Errorf("unsupported connection type %q", f.Kind) } func pingSQL(ctx context.Context, driver, dsn string) error { db, err := sql.Open(driver, dsn) if err != nil { return err } defer db.Close() return db.PingContext(ctx) }