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