Skip to content
internal/db
Edit
On this page

API reference for the db package — SQLite database tracking all saferm deletions, with WAL mode, busy_timeout and a bounded retry for lock contention.

#internal/db

#internal/db

Package db manages the SQLite database tracking all saferm deletions.

Concurrency safety across simultaneous sessions comes in three layers: WAL mode, SQLite's own busy_timeout, and a bounded retry on top of both -- every operation that meets SQLITE_BUSY or SQLITE_LOCKED is run again, up to five attempts with a 50ms linear backoff, reported through a RetryNotifier. Contention that outlives the whole budget is returned as a *ContentionError, a type distinct from every other database failure, so a caller can tell "another process holds the write lock, try again" from "this archive is broken".

#SchemaSQL

Go go
const SchemaSQL = `

#ErrNotFound

Go go
var ErrNotFound = errors.New("record not found")

ErrNotFound is returned when a queried record does not exist.

#ErrOriginVersionWithoutName

Go go
var ErrOriginVersionWithoutName = errors.New("origin_version requires origin_name")

ErrOriginVersionWithoutName is returned by Insert when a record carries an origin version but no origin name.

SQLite cannot add a CHECK constraint to an existing table, so the invariant is enforced here instead of in the schema: one enforcement path, identical on a fresh database and on one that reached this schema through the migration ladder, and no table rebuild. The cost is stated rather than hidden -- a hand-edited database, or an older binary writing into a newer one, bypasses it.

#ErrOriginEmpty

Go go
var ErrOriginEmpty = errors.New("origin fields must be absent or non-empty")

ErrOriginEmpty is returned by Insert when an origin field is present but empty. Both fields are nullable and never empty: an empty string would be a third state beside "a tool claimed this" and "none did", and nothing can read it as either.

#RetryNotifier

Go go
type RetryNotifier func(attempt, maxAttempts int, delay time.Duration, err error)

RetryNotifier is called once before each contention retry, so a caller can report the wait under --verbose. It is never called for a failure that is not contention, and never for the final attempt (there is no wait after it).

#ContentionError

Go go
type ContentionError struct

ContentionError reports that an operation was still meeting a locked database after the whole retry budget was spent. It is deliberately its own type: a caller that collapses it into a generic database failure loses the one piece of information that distinguishes "another process is busy, try later" from "this database is broken".

#DB

Go go
type DB struct

DB wraps a *sql.DB connection to the saferm SQLite database.

#DeletionRecord

Go go
type DeletionRecord struct

DeletionRecord represents a single archived deletion in the database.

#IsContention

Go go
func IsContention(err error) bool

IsContention reports whether err is SQLITE_BUSY/SQLITE_LOCKED-class contention -- a lock held by another connection, which retrying can clear.

The driver reports a result code alongside the message, so the classification reads that code rather than matching on English. SQLite's extended codes carry the primary code in their low byte (SQLITE_BUSY_SNAPSHOT is SQLITE_BUSY | (2 << 8), and so on), so the low byte is what is compared -- every extended flavour of BUSY and LOCKED classifies with its primary.

#IsContentionExhausted

Go go
func IsContentionExhausted(err error) bool

IsContentionExhausted reports whether err is a ContentionError -- contention that outlived the retry budget. It is what maps a database failure onto saferm's distinct contention exit code.

#Open

Go go
func Open(dbPath string, notify RetryNotifier) (*DB, error)

Open opens (or creates) the SQLite database at dbPath with WAL mode and busy_timeout, then runs the schema DDL.

notify, when non-nil, is called before each contention retry -- for every operation on the returned DB as well as for the schema work below, which is why it is supplied here rather than set afterwards. Pass nil for no reporting.

#ContentionError.Error

Go go
func (e *ContentionError) Error() string

#ContentionError.Unwrap

Go go
func (e *ContentionError) Unwrap() error { return e.Err }

#DB.Close

Go go
func (d *DB) Close() error

Close closes the underlying database connection.

#DB.Insert

Go go
func (d *DB) Insert(rec *DeletionRecord) (int64, error)

Insert inserts a DeletionRecord and returns the auto-increment ID.

The origin invariants are checked here, before anything is written: see ErrOriginVersionWithoutName for why they live in code rather than in the schema.

#DB.QueryByID

Go go
func (d *DB) QueryByID(id int64) (*DeletionRecord, error)

QueryByID retrieves a single record by ID. Returns ErrNotFound if it does not exist.

#DB.QueryByUUID

Go go
func (d *DB) QueryByUUID(uuid string) (*DeletionRecord, error)

QueryByUUID retrieves a single record by its archive uuid. Returns ErrNotFound if it does not exist.

The uuid is the identifier a record keeps: the numeric id is this database's autoincrement counter, while the uuid names the archived entry on disk and is what delete hands back to its caller.

#DB.QueryByPath

Go go
func (d *DB) QueryByPath(path string) ([]*DeletionRecord, error)

QueryByPath returns all non-restored records matching the given original_path, ordered by deleted_at DESC (newest first).

#DB.QueryAll

Go go
func (d *DB) QueryAll(includeAll bool) ([]*DeletionRecord, error)

QueryAll returns all records ordered by deleted_at DESC. If includeAll is false, restored and purged records are excluded.

#DB.MarkRestored

Go go
func (d *DB) MarkRestored(id int64, restoredTo string) error

MarkRestored sets restored_at to now and restored_to to the given path. Returns ErrNotFound if the record does not exist.

#DB.MarkPurged

Go go
func (d *DB) MarkPurged(id int64) error

MarkPurged sets purged_at to now, preserving the metadata record. Returns ErrNotFound if the record does not exist.

#DB.QueryOlderThan

Go go
func (d *DB) QueryOlderThan(before time.Time) ([]*DeletionRecord, error)

QueryOlderThan returns all non-restored, non-purged records deleted before the given time.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search