1package db
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7 "log/slog"
8 "path/filepath"
9
10 "github.com/ncruces/go-sqlite3"
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 dbPath := filepath.Join(dataDir, "crush.db")
22
23 // Set pragmas for better performance
24 pragmas := []string{
25 "PRAGMA foreign_keys = ON;",
26 "PRAGMA journal_mode = DELETE;",
27 "PRAGMA page_size = 4096;",
28 "PRAGMA cache_size = -8000;",
29 "PRAGMA synchronous = NORMAL;",
30 "PRAGMA secure_delete = ON;",
31 }
32
33 db, err := driver.Open(dbPath, func(c *sqlite3.Conn) error {
34 for _, pragma := range pragmas {
35 if err := c.Exec(pragma); err != nil {
36 return fmt.Errorf("failed to set pragma `%s`: %w", pragma, err)
37 }
38 }
39 return nil
40 })
41 if err != nil {
42 return nil, fmt.Errorf("failed to open database: %w", err)
43 }
44
45 // Verify connection
46 if err = db.PingContext(ctx); err != nil {
47 db.Close()
48 return nil, fmt.Errorf("failed to connect to database: %w", err)
49 }
50
51 goose.SetBaseFS(FS)
52
53 if err := goose.SetDialect("sqlite3"); err != nil {
54 slog.Error("Failed to set dialect", "error", err)
55 return nil, fmt.Errorf("failed to set dialect: %w", err)
56 }
57
58 if err := goose.Up(db, "migrations"); err != nil {
59 slog.Error("Failed to apply migrations", "error", err)
60 return nil, fmt.Errorf("failed to apply migrations: %w", err)
61 }
62
63 return db, nil
64}