// SPDX-License-Identifier: GPL-2.0-or-later // Copyright (C) 2026 Bryan Joshua Pedini package main import ( "context" "database/sql" _ "embed" "errors" "fmt" "log/slog" "path/filepath" "time" // Pure-Go SQLite driver, registered as "sqlite" "modernc.org/sqlite" ) // schemaSQL holds the authoritative, idempotent table definitions // //go:embed schema.sql var schemaSQL string // dsnParams are applied by the driver to every pooled connection // foreign_keys: enforce the operations -> tokens reference // busy_timeout: wait instead of failing when the server and the CLI write concurrently // journal_mode WAL: readers never block the writer // _txlock=immediate: transactions take the write lock at BEGIN, so read-then-write never fails with SQLITE_BUSY const dsnParams = "_pragma=foreign_keys(1)&_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_txlock=immediate" // maxOpenConns bounds the SQLite connection pool const maxOpenConns = 4 // sqliteDSN builds the driver DSN for a filesystem path // The path is made absolute and percent-encoded so that spaces, '?', '#', '%' and other bytes are taken literally func sqliteDSN(path string) (string, error) { abs, err := filepath.Abs(path) if err != nil { return "", fmt.Errorf("resolve database path: %w", err) } return "file://" + escapeURIPath(abs) + "?" + dsnParams, nil } // escapeURIPath percent-encodes every byte except RFC 3986 unreserved characters and '/' func escapeURIPath(p string) string { const hexDigits = "0123456789ABCDEF" out := make([]byte, 0, len(p)) for i := 0; i < len(p); i++ { c := p[i] switch { case c >= 'A' && c <= 'Z', c >= 'a' && c <= 'z', c >= '0' && c <= '9', c == '-', c == '.', c == '_', c == '~', c == '/': out = append(out, c) default: out = append(out, '%', hexDigits[c>>4], hexDigits[c&0x0f]) } } return string(out) } // OpenDatabase opens (creating if needed) the SQLite database at path and initializes the schema func OpenDatabase(ctx context.Context, path string, log *slog.Logger) (*sql.DB, error) { defer traceFunc(ctx, log, "OpenDatabase")() // Build the DSN dsn, err := sqliteDSN(path) if err != nil { return nil, err } // Open the connection pool (lazy: the file is opened on first use) db, err := sql.Open("sqlite", dsn) if err != nil { return nil, fmt.Errorf("open database: %w", err) } db.SetMaxOpenConns(maxOpenConns) db.SetMaxIdleConns(maxOpenConns) // Create the tables if needed if err := InitializeSchema(ctx, db, log); err != nil { db.Close() return nil, err } log.DebugContext(ctx, "database opened", "path", path) return db, nil } // InitializeSchema executes the embedded schema; every statement is idempotent func InitializeSchema(ctx context.Context, db *sql.DB, log *slog.Logger) error { defer traceFunc(ctx, log, "InitializeSchema")() if _, err := db.ExecContext(ctx, schemaSQL); err != nil { return fmt.Errorf("initialize database schema: %w", err) } log.DebugContext(ctx, "database schema initialized") return nil } // isSQLiteCode reports whether err is a SQLite error with the given (extended) result code func isSQLiteCode(err error, code int) bool { var se *sqlite.Error return errors.As(err, &se) && se.Code() == code } // formatTime renders a timestamp as stored in the database: UTC RFC3339Nano func formatTime(t time.Time) string { return t.UTC().Format(time.RFC3339Nano) } // parseTime parses a timestamp stored by formatTime func parseTime(s string) (time.Time, error) { t, err := time.Parse(time.RFC3339Nano, s) if err != nil { return time.Time{}, fmt.Errorf("parse stored timestamp: %w", err) } return t.UTC(), nil }