Changeset 0.17.1 (#76)
This commit is contained in:
@@ -4,8 +4,12 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
_ "modernc.org/sqlite"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/config"
|
||||
)
|
||||
@@ -13,21 +17,38 @@ import (
|
||||
// DB is the application-wide database connection pool.
|
||||
var DB *sql.DB
|
||||
|
||||
// Connect opens a connection pool to PostgreSQL using the
|
||||
// DATABASE_URL from config. It pings to verify connectivity.
|
||||
// Connect opens a connection pool to the configured database.
|
||||
// Driver is selected by DB_DRIVER env var: "postgres" (default) or "sqlite".
|
||||
// For SQLite the DATABASE_URL is treated as a file path; ":memory:" is supported.
|
||||
func Connect(cfg *config.Config) error {
|
||||
if cfg.DatabaseURL == "" {
|
||||
driver := strings.ToLower(cfg.DBDriver)
|
||||
if driver == "" {
|
||||
driver = detectDriver(cfg.DatabaseURL)
|
||||
}
|
||||
|
||||
switch driver {
|
||||
case "sqlite", "sqlite3":
|
||||
return connectSQLite(cfg.DatabaseURL)
|
||||
case "postgres", "postgresql", "":
|
||||
return connectPostgres(cfg.DatabaseURL)
|
||||
default:
|
||||
return fmt.Errorf("unsupported DB_DRIVER: %q (expected postgres or sqlite)", driver)
|
||||
}
|
||||
}
|
||||
|
||||
// connectPostgres opens a PostgreSQL connection pool.
|
||||
func connectPostgres(dsn string) error {
|
||||
if dsn == "" {
|
||||
log.Println("⚠ DATABASE_URL not set — running without database")
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
DB, err = sql.Open("postgres", cfg.DatabaseURL)
|
||||
DB, err = sql.Open("postgres", dsn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("database open: %w", err)
|
||||
}
|
||||
|
||||
// Connection pool tuning
|
||||
DB.SetMaxOpenConns(25)
|
||||
DB.SetMaxIdleConns(5)
|
||||
|
||||
@@ -35,10 +56,75 @@ func Connect(cfg *config.Config) error {
|
||||
return fmt.Errorf("database ping: %w", err)
|
||||
}
|
||||
|
||||
log.Println("✅ Database connected")
|
||||
CurrentDialect = DialectPostgres
|
||||
log.Println("✅ Database connected (postgres)")
|
||||
return nil
|
||||
}
|
||||
|
||||
// connectSQLite opens a SQLite database with WAL mode enabled.
|
||||
func connectSQLite(dsn string) error {
|
||||
if dsn == "" {
|
||||
dsn = "switchboard.db"
|
||||
}
|
||||
|
||||
// Ensure parent directory exists for file-based DBs.
|
||||
if dsn != ":memory:" && !strings.HasPrefix(dsn, "file:") {
|
||||
dir := filepath.Dir(dsn)
|
||||
if dir != "" && dir != "." {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return fmt.Errorf("create db directory %s: %w", dir, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// modernc.org/sqlite uses "sqlite" as the driver name.
|
||||
// Append pragmas via query string for file paths.
|
||||
// _time_format tells the driver how to convert between Go time.Time and SQLite TEXT timestamps.
|
||||
connStr := dsn
|
||||
if dsn != ":memory:" && !strings.Contains(dsn, "?") {
|
||||
connStr = fmt.Sprintf("file:%s?_pragma=journal_mode%%3Dwal&_pragma=busy_timeout%%3D5000&_pragma=foreign_keys%%3Don&_time_format=2006-01-02T15%%3A04%%3A05Z", dsn)
|
||||
} else if dsn == ":memory:" {
|
||||
connStr = "file::memory:?_pragma=foreign_keys%%3Don&_time_format=2006-01-02T15%%3A04%%3A05Z"
|
||||
}
|
||||
|
||||
var err error
|
||||
DB, err = sql.Open("sqlite", connStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("database open: %w", err)
|
||||
}
|
||||
|
||||
// SQLite performs best with a single writer.
|
||||
DB.SetMaxOpenConns(1)
|
||||
DB.SetMaxIdleConns(1)
|
||||
|
||||
if err := DB.Ping(); err != nil {
|
||||
return fmt.Errorf("database ping: %w", err)
|
||||
}
|
||||
|
||||
// Verify WAL mode took effect (some in-memory modes ignore it).
|
||||
var mode string
|
||||
DB.QueryRow("PRAGMA journal_mode").Scan(&mode)
|
||||
log.Printf("✅ Database connected (sqlite, journal_mode=%s, path=%s)", mode, dsn)
|
||||
|
||||
CurrentDialect = DialectSQLite
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectDriver infers the driver from DATABASE_URL format.
|
||||
func detectDriver(dsn string) string {
|
||||
if dsn == "" {
|
||||
return "postgres"
|
||||
}
|
||||
if strings.HasPrefix(dsn, "postgres") {
|
||||
return "postgres"
|
||||
}
|
||||
if strings.HasSuffix(dsn, ".db") || strings.HasSuffix(dsn, ".sqlite") ||
|
||||
dsn == ":memory:" || strings.HasPrefix(dsn, "file:") {
|
||||
return "sqlite"
|
||||
}
|
||||
return "postgres"
|
||||
}
|
||||
|
||||
// Close gracefully shuts down the connection pool.
|
||||
func Close() {
|
||||
if DB != nil {
|
||||
|
||||
Reference in New Issue
Block a user