package db import ( "context" "database/sql" "fmt" "strings" _ "modernc.org/sqlite" ) type Config struct { DBPath string } type DB struct { write *sql.DB read *sql.DB } func OpenDB(config *Config) (*DB, error) { ctx := context.Background() dsn := config.DBPath + "?_pragma=foreign_keys(1)" writeDB, err := sql.Open("sqlite", dsn) if err != nil { return nil, fmt.Errorf("opening meta.db for writes: %w", err) } // SQLite only supports one writer at a time; enforce it at the pool level. writeDB.SetMaxOpenConns(1) readDB, err := sql.Open("sqlite", dsn) if err != nil { _ = writeDB.Close() return nil, fmt.Errorf("opening meta.db for reads: %w", err) } db := &DB{write: writeDB, read: readDB} if err := db.applyPragmas(ctx); err != nil { return nil, fmt.Errorf("applying pragmas: %w", err) } if err := db.applySchema(ctx); err != nil { return nil, fmt.Errorf("applying schema: %w", err) } if err := writeDB.Ping(); err != nil { _ = writeDB.Close() return nil, fmt.Errorf("pinging meta.db write pool: %w", err) } if err := readDB.Ping(); err != nil { _ = writeDB.Close() _ = readDB.Close() return nil, fmt.Errorf("pinging meta.db read pool: %w", err) } return db, nil } func (db *DB) GetWriteTx(ctx context.Context) (*sql.Tx, error) { return db.write.BeginTx(ctx, nil) } func escapeLike(s string) string { s = strings.ReplaceAll(s, `\`, `\\`) s = strings.ReplaceAll(s, `%`, `\%`) s = strings.ReplaceAll(s, `_`, `\_`) return s }