1package db
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7 "log/slog"
8 "os"
9 "path/filepath"
10
11 _ "github.com/ncruces/go-sqlite3/driver"
12 _ "github.com/ncruces/go-sqlite3/embed"
13
14 "github.com/charmbracelet/crush/internal/config"
15
16 "github.com/pressly/goose/v3"
17)
18
19func Connect(ctx context.Context) (*sql.DB, error) {
20 dataDir := config.Get().Options.DataDirectory
21 if dataDir == "" {
22 return nil, fmt.Errorf("data.dir is not set")
23 }
24 if err := os.MkdirAll(dataDir, 0o700); err != nil {
25 return nil, fmt.Errorf("failed to create data directory: %w", err)
26 }
27 dbPath := filepath.Join(dataDir, "crush.db")
28 // Open the SQLite database
29 db, err := sql.Open("sqlite3", dbPath)
30 if err != nil {
31 return nil, fmt.Errorf("failed to open database: %w", err)
32 }
33
34 // Verify connection
35 if err = db.PingContext(ctx); err != nil {
36 db.Close()
37 return nil, fmt.Errorf("failed to connect to database: %w", err)
38 }
39
40 // Set pragmas for better performance
41 pragmas := []string{
42 "PRAGMA foreign_keys = ON;",
43 "PRAGMA journal_mode = WAL;",
44 "PRAGMA page_size = 4096;",
45 "PRAGMA cache_size = -8000;",
46 "PRAGMA synchronous = NORMAL;",
47 }
48
49 for _, pragma := range pragmas {
50 if _, err = db.ExecContext(ctx, pragma); err != nil {
51 slog.Error("Failed to set pragma", pragma, err)
52 } else {
53 slog.Debug("Set pragma", "pragma", pragma)
54 }
55 }
56
57 goose.SetBaseFS(FS)
58
59 if err := goose.SetDialect("sqlite3"); err != nil {
60 slog.Error("Failed to set dialect", "error", err)
61 return nil, fmt.Errorf("failed to set dialect: %w", err)
62 }
63
64 if err := goose.Up(db, "migrations"); err != nil {
65 slog.Error("Failed to apply migrations", "error", err)
66 return nil, fmt.Errorf("failed to apply migrations: %w", err)
67 }
68 return db, nil
69}