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/pressly/goose/v3"
15)
16
17func Connect(ctx context.Context, dataDir string) (*sql.DB, error) {
18	if dataDir == "" {
19		return nil, fmt.Errorf("data.dir is not set")
20	}
21	if err := os.MkdirAll(dataDir, 0o700); err != nil {
22		return nil, fmt.Errorf("failed to create data directory: %w", err)
23	}
24	dbPath := filepath.Join(dataDir, "crush.db")
25	// Open the SQLite database
26	db, err := sql.Open("sqlite3", dbPath)
27	if err != nil {
28		return nil, fmt.Errorf("failed to open database: %w", err)
29	}
30
31	// Verify connection
32	if err = db.PingContext(ctx); err != nil {
33		db.Close()
34		return nil, fmt.Errorf("failed to connect to database: %w", err)
35	}
36
37	// Set pragmas for better performance
38	pragmas := []string{
39		"PRAGMA foreign_keys = ON;",
40		"PRAGMA journal_mode = WAL;",
41		"PRAGMA page_size = 4096;",
42		"PRAGMA cache_size = -8000;",
43		"PRAGMA synchronous = NORMAL;",
44	}
45
46	for _, pragma := range pragmas {
47		if _, err = db.ExecContext(ctx, pragma); err != nil {
48			slog.Error("Failed to set pragma", pragma, err)
49		} else {
50			slog.Debug("Set pragma", "pragma", pragma)
51		}
52	}
53
54	goose.SetBaseFS(FS)
55
56	if err := goose.SetDialect("sqlite3"); err != nil {
57		slog.Error("Failed to set dialect", "error", err)
58		return nil, fmt.Errorf("failed to set dialect: %w", err)
59	}
60
61	if err := goose.Up(db, "migrations"); err != nil {
62		slog.Error("Failed to apply migrations", "error", err)
63		return nil, fmt.Errorf("failed to apply migrations: %w", err)
64	}
65	return db, nil
66}