internal/db/db.go (view raw)
1package db
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7 "strings"
8
9 _ "modernc.org/sqlite"
10)
11
12type Config struct {
13 DBPath string
14}
15type DB struct {
16 write *sql.DB
17 read *sql.DB
18}
19
20func OpenDB(config *Config) (*DB, error) {
21 ctx := context.Background()
22 dsn := config.DBPath + "?_pragma=foreign_keys(1)"
23
24 writeDB, err := sql.Open("sqlite", dsn)
25 if err != nil {
26 return nil, fmt.Errorf("opening meta.db for writes: %w", err)
27 }
28 // SQLite only supports one writer at a time; enforce it at the pool level.
29 writeDB.SetMaxOpenConns(1)
30
31 readDB, err := sql.Open("sqlite", dsn)
32 if err != nil {
33 _ = writeDB.Close()
34 return nil, fmt.Errorf("opening meta.db for reads: %w", err)
35 }
36
37 db := &DB{write: writeDB, read: readDB}
38
39 if err := db.applyPragmas(ctx); err != nil {
40 return nil, fmt.Errorf("applying pragmas: %w", err)
41 }
42
43 if err := db.applySchema(ctx); err != nil {
44 return nil, fmt.Errorf("applying schema: %w", err)
45 }
46
47 if err := writeDB.Ping(); err != nil {
48 _ = writeDB.Close()
49 return nil, fmt.Errorf("pinging meta.db write pool: %w", err)
50 }
51
52 if err := readDB.Ping(); err != nil {
53 _ = writeDB.Close()
54 _ = readDB.Close()
55 return nil, fmt.Errorf("pinging meta.db read pool: %w", err)
56 }
57
58 return db, nil
59}
60
61func (db *DB) GetWriteTx(ctx context.Context) (*sql.Tx, error) {
62 return db.write.BeginTx(ctx, nil)
63}
64
65func escapeLike(s string) string {
66 s = strings.ReplaceAll(s, `\`, `\\`)
67 s = strings.ReplaceAll(s, `%`, `\%`)
68 s = strings.ReplaceAll(s, `_`, `\_`)
69 return s
70}