1package db
2
3import (
4 "database/sql"
5 "fmt"
6 "os"
7 "path/filepath"
8
9 "github.com/golang-migrate/migrate/v4"
10 "github.com/golang-migrate/migrate/v4/source/iofs"
11
12 "github.com/golang-migrate/migrate/v4/database/sqlite3"
13 _ "github.com/mattn/go-sqlite3"
14
15 "github.com/kujtimiihoxha/opencode/internal/config"
16 "github.com/kujtimiihoxha/opencode/internal/logging"
17)
18
19func Connect() (*sql.DB, error) {
20 dataDir := config.Get().Data.Directory
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, "opencode.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.Ping(); 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.Exec(pragma); err != nil {
51 logging.Warn("Failed to set pragma", pragma, err)
52 } else {
53 logging.Warn("Set pragma", "pragma", pragma)
54 }
55 }
56
57 // Initialize schema from embedded file
58 d, err := iofs.New(FS, "migrations")
59 if err != nil {
60 logging.Error("Failed to open embedded migrations", "error", err)
61 db.Close()
62 return nil, fmt.Errorf("failed to open embedded migrations: %w", err)
63 }
64
65 driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
66 if err != nil {
67 logging.Error("Failed to create SQLite driver", "error", err)
68 db.Close()
69 return nil, fmt.Errorf("failed to create SQLite driver: %w", err)
70 }
71
72 m, err := migrate.NewWithInstance("iofs", d, "ql", driver)
73 if err != nil {
74 logging.Error("Failed to create migration instance", "error", err)
75 db.Close()
76 return nil, fmt.Errorf("failed to create migration instance: %w", err)
77 }
78
79 err = m.Up()
80 if err != nil && err != migrate.ErrNoChange {
81 logging.Error("Migration failed", "error", err)
82 db.Close()
83 return nil, fmt.Errorf("failed to apply schema: %w", err)
84 } else if err == migrate.ErrNoChange {
85 logging.Info("No schema changes to apply")
86 } else {
87 logging.Info("Schema migration applied successfully")
88 }
89
90 return db, nil
91}