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