Changeset 0.17.1 (#76)

This commit is contained in:
2026-02-28 01:40:31 +00:00
parent c9141a6896
commit 856dc9b0ac
64 changed files with 8037 additions and 1657 deletions

View File

@@ -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 {