mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-29 05:25:35 +00:00
102 lines
3.0 KiB
Go
102 lines
3.0 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/tursodatabase/go-libsql"
|
|
)
|
|
|
|
// LibsqlConfig holds optional libSQL sync settings for embedded replicas.
|
|
// When SyncURL is empty, the database operates in local-only mode.
|
|
type LibsqlConfig struct {
|
|
SyncURL string
|
|
AuthToken string
|
|
SyncInterval time.Duration
|
|
}
|
|
|
|
// HoldDB wraps the shared *sql.DB and optional connector for lifecycle management.
|
|
type HoldDB struct {
|
|
DB *sql.DB
|
|
connector io.Closer // non-nil only in embedded replica mode
|
|
}
|
|
|
|
// Close closes the database connection and connector (if any).
|
|
// The connector must be closed to release file locks.
|
|
func (h *HoldDB) Close() error {
|
|
var dbErr, connErr error
|
|
if h.DB != nil {
|
|
dbErr = h.DB.Close()
|
|
}
|
|
if h.connector != nil {
|
|
connErr = h.connector.Close()
|
|
}
|
|
if dbErr != nil {
|
|
return dbErr
|
|
}
|
|
return connErr
|
|
}
|
|
|
|
// OpenHoldDB initializes the hold's shared database connection.
|
|
// Uses libSQL embedded replica when cfg.SyncURL is set, otherwise local-only.
|
|
// The caller must call HoldDB.Close() on shutdown.
|
|
func OpenHoldDB(path string, cfg LibsqlConfig) (*HoldDB, error) {
|
|
var db *sql.DB
|
|
var connector io.Closer
|
|
|
|
if cfg.SyncURL != "" {
|
|
// Embedded replica mode: local file + sync to remote
|
|
opts := []libsql.Option{
|
|
libsql.WithAuthToken(cfg.AuthToken),
|
|
}
|
|
if cfg.SyncInterval > 0 {
|
|
opts = append(opts, libsql.WithSyncInterval(cfg.SyncInterval))
|
|
}
|
|
conn, err := libsql.NewEmbeddedReplicaConnector(path, cfg.SyncURL, opts...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create libsql embedded replica connector: %w", err)
|
|
}
|
|
db = sql.OpenDB(conn)
|
|
connector = conn
|
|
slog.Info("Hold database opened in embedded replica mode", "path", path, "sync_url", cfg.SyncURL)
|
|
} else {
|
|
// Local-only mode: plain file via libsql driver
|
|
dsn := path
|
|
if !strings.HasPrefix(path, "file:") && !strings.HasPrefix(path, ":memory:") {
|
|
dsn = "file:" + path
|
|
}
|
|
var err error
|
|
db, err = sql.Open("libsql", dsn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open hold database: %w", err)
|
|
}
|
|
slog.Info("Hold database opened in local-only mode", "path", path)
|
|
}
|
|
|
|
// In local-only mode, configure WAL and busy_timeout locally.
|
|
// In embedded replica mode, the remote server manages these settings
|
|
// and PRAGMA assignments are rejected as "unsupported statement"
|
|
// (observed with Bunny Database).
|
|
if cfg.SyncURL == "" {
|
|
var journalMode string
|
|
if err := db.QueryRow("PRAGMA journal_mode = WAL").Scan(&journalMode); err != nil {
|
|
return nil, fmt.Errorf("failed to set journal mode: %w", err)
|
|
}
|
|
var busyTimeout int
|
|
if err := db.QueryRow("PRAGMA busy_timeout = 5000").Scan(&busyTimeout); err != nil {
|
|
return nil, fmt.Errorf("failed to set busy_timeout: %w", err)
|
|
}
|
|
}
|
|
|
|
// Foreign keys work in both modes
|
|
if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil {
|
|
return nil, fmt.Errorf("failed to enable foreign keys: %w", err)
|
|
}
|
|
|
|
return &HoldDB{DB: db, connector: connector}, nil
|
|
}
|