1package db
2
3import (
4 "context"
5 "database/sql"
6 "embed"
7 "fmt"
8 "log/slog"
9 "path/filepath"
10 "sync"
11 "testing"
12
13 "github.com/pressly/goose/v3"
14)
15
16var (
17 pragmas = map[string]string{
18 "foreign_keys": "ON",
19 "journal_mode": "WAL",
20 "page_size": "4096",
21 "cache_size": "-8000",
22 "synchronous": "NORMAL",
23 "secure_delete": "ON",
24 "busy_timeout": "30000",
25 }
26 gooseInitOnce sync.Once
27 gooseInitErr error
28)
29
30//go:embed migrations/*.sql
31var FS embed.FS
32
33func init() {
34 goose.SetBaseFS(FS)
35
36 if testing.Testing() {
37 goose.SetLogger(goose.NopLogger())
38 }
39}
40
41// Connect opens a SQLite database connection and runs migrations.
42func Connect(ctx context.Context, dataDir string) (*sql.DB, error) {
43 if dataDir == "" {
44 return nil, fmt.Errorf("data.dir is not set")
45 }
46 dbPath := filepath.Join(dataDir, "crush.db")
47
48 db, err := openDB(dbPath)
49 if err != nil {
50 return nil, err
51 }
52
53 if err = db.PingContext(ctx); err != nil {
54 db.Close()
55 return nil, fmt.Errorf("failed to connect to database: %w", err)
56 }
57
58 if err := initGoose(); err != nil {
59 slog.Error("Failed to initialize goose", "error", err)
60 return nil, fmt.Errorf("failed to initialize goose: %w", err)
61 }
62
63 if err := goose.Up(db, "migrations"); err != nil {
64 slog.Error("Failed to apply migrations", "error", err)
65 return nil, fmt.Errorf("failed to apply migrations: %w", err)
66 }
67
68 return db, nil
69}
70
71func initGoose() error {
72 gooseInitOnce.Do(func() {
73 goose.SetBaseFS(FS)
74 gooseInitErr = goose.SetDialect("sqlite3")
75 })
76
77 return gooseInitErr
78}