Help us shape the future of Go! Take the 2026 Go Developer Survey

store

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2026 License: GPL-3.0 Imports: 35 Imported by: 0

Documentation

Overview

Package store opens Ether's database and applies each component's migrations. Components own their tables: they register migrations under their own name (table names prefixed with it) and receive *sql.DB.

func init() {
	store.Register("catalog", 1, `CREATE TABLE catalog_titles (...)`)
}

A standalone server uses SQLite in its data directory. A clustered node uses an owner-supplied PostgreSQL database; component SQL is written once with `?` placeholders (rebound to $n by the "ether-pgx" driver) and migrations are translated from SQLite DDL (see TranslateDDL). DialectOf tells a component which engine it is talking to for the few constructs that differ.

Index

Constants

View Source
const (
	DefaultOutboxCap     = 10000
	DefaultOutboxTimeout = 2 * time.Second
)

Outbox defaults.

View Source
const (
	PreMigrateSuffix      = ".pre-migrate.bak"
	PreMigrateFilesSuffix = ".files"
	PreMigrateKeep        = 3
)

PreMigrateSuffix names the snapshot taken next to a SQLite database before pending migrations change its schema (ether.db.pre-migrate.bak). The PreMigrateKeep newest are kept: older ones are renamed .pre-migrate.bak.1, .2 by each upgrade that migrates. Files the caller supplies (the settings and credential key that match the database) are stored with each one in a directory named after it plus PreMigrateFilesSuffix.

PostgreSQL databases get no pre-migrate snapshot; scheduled and manual backups (internal/backup) are their only copies.

View Source
const (
	StageSnapshot = "snapshot"
	StageMigrate  = "migrate"
)

Progress stages.

View Source
const PostgresDriverName = "ether-pgx"

PostgresDriverName is the database/sql driver registered by this package: pgx's stdlib driver with `?` placeholders rebound to $n at execute time, so components keep one SQL text for both engines.

View Source
const TestPostgresEnv = "ETHER_TEST_POSTGRES"

TestPostgresEnv names the environment variable that points package tests at a PostgreSQL server instead of in-memory SQLite, e.g.

ETHER_TEST_POSTGRES='postgres://ether:ether@127.0.0.1:5432/ether?sslmode=disable' go test ./...

Each test database is a fresh, randomly named schema that is dropped when the *sql.DB is closed.

Variables

View Source
var ErrMigrationDeferred = errors.New("store: schema migration deferred")

ErrMigrationDeferred means this node must not migrate the shared schema yet (another live node runs a binary that does not know the pending migrations and the owner asked to wait for it).

View Source
var ErrNoLastInsertID = errors.New("store: LastInsertId is not supported on PostgreSQL; use RETURNING")

ErrNoLastInsertID is returned by Result.LastInsertId on PostgreSQL; use INSERT ... RETURNING instead.

View Source
var ErrNotEmpty = errors.New("store: restore target database is not empty")

ErrNotEmpty means a logical restore target already holds data.

Functions

func AppliedMigrations

func AppliedMigrations(ctx context.Context, db *sql.DB) (map[string]int, error)

AppliedMigrations returns the recorded (component -> highest version).

func CaptureCallSites added in v0.3.0

func CaptureCallSites(on bool)

CaptureCallSites makes every statement's first execution in a QueryStats record where it was issued. It costs a stack walk per new statement, so only the performance harness turns it on.

func Checkpoint added in v0.3.0

func Checkpoint(ctx context.Context, db *sql.DB, truncate bool) error

Checkpoint copies WAL frames back into the database. truncate also truncates the WAL, and only when this checkpoint itself found no readers in the way and no other connection is inside a query. TRUNCATE takes locks a request would have to wait on, so callers pass false while the server is busy.

PostgreSQL checkpoints in its own background process. There is nothing to run on the client, and this does not issue CHECKPOINT.

func ComponentOrder added in v0.3.0

func ComponentOrder() []string

ComponentOrder is the migration component sequence Migrate will apply.

func Contains

func Contains(d Dialect, haystack, needle string) string

Contains is a literal (non-pattern) substring test of needle in haystack, both SQL expressions.

func ExportLogical

func ExportLogical(ctx context.Context, db *sql.DB, w io.Writer) error

ExportLogical writes a consistent logical dump of every table in db to w.

func HasDependency added in v0.3.0

func HasDependency(component, dependency string) bool

HasDependency reports whether component declared a RegisterAfter dependency that is present. Absent dependencies are ignored, matching Migrate.

func ImportLogical

func ImportLogical(ctx context.Context, db *sql.DB, r io.Reader) (err error)

ImportLogical loads a logical dump into db, which must be migrated and hold no rows in any table the dump contains. It runs in one transaction.

func IsCorrupt added in v0.3.0

func IsCorrupt(err error) bool

IsCorrupt reports whether err is SQLite reporting a damaged or foreign file (SQLITE_CORRUPT or SQLITE_NOTADB).

func IsPermanent

func IsPermanent(err error) bool

IsPermanent reports whether a PostgreSQL connection error will never succeed on retry: wrong password or user, a database or schema that does not exist, or missing privileges. A node starting against such a URL should stop and tell the owner instead of retrying forever.

func IsUnavailable

func IsUnavailable(err error) bool

IsUnavailable reports whether err means the database server could not be reached or dropped the connection — a condition that clears on its own (network outage, server restart or failover) — rather than a problem with the statement or the data. Deadline errors count: a write bounded by a short timeout that expires is treated as an outage.

func JSONInt

func JSONInt(d Dialect, col, key string) string

JSONInt extracts the top-level key from the JSON text column col as an integer (NULL when absent). key is not escaped.

func JSONRemove

func JSONRemove(d Dialect, col, key string) string

JSONRemove is the JSON text column col without the top-level key, as text. key is not escaped.

func JSONText

func JSONText(d Dialect, col, key string) string

JSONText extracts the top-level key from the JSON text column col as text. key must be a constant identifier-like name; it is not escaped.

func KnownMigrations

func KnownMigrations() map[string]int

KnownMigrations is the highest migration version this binary registers for each component.

func Like

func Like(d Dialect) string

Like is the case-insensitive pattern operator for user-facing search: SQLite's LIKE already ignores ASCII case, PostgreSQL needs ILIKE.

func LockKey

func LockKey(ctx context.Context, d Dialect, tx *sql.Tx, key string) error

LockKey serialises transactions that use the same key until tx ends: a check-then-insert ("count a user's streams, then reserve one") done inside the lock is atomic across every node sharing the database. d is the database's dialect (DialectOf; *sql.Tx does not expose its driver).

On PostgreSQL it takes pg_advisory_xact_lock on a 64-bit hash of key (the single-key space, which never overlaps the cluster's two-key leader lock); the lock is released at commit or rollback. On SQLite it is a no-op: Ether opens SQLite with _txlock=immediate, so every transaction already holds the database's write lock from BEGIN.

func Maintain

func Maintain(ctx context.Context, db *sql.DB) error

Maintain is the periodic (daily) upkeep for a long-running database: it checkpoints the WAL and truncates it when no request is using the database, then refreshes query-planner statistics. It deliberately never VACUUMs; that rewrites the whole file and is left to the owner.

On PostgreSQL the server manages its own WAL and autovacuum; Maintain only refreshes planner statistics with ANALYZE.

func MergeViewer added in v0.3.2

func MergeViewer(ctx context.Context, tx *sql.Tx, fromID, intoID int64) error

MergeViewer runs every registered merge from one viewer into another inside tx, early registrations first.

func Migrate

func Migrate(ctx context.Context, db *sql.DB) error

Migrate applies every registered migration db has not yet recorded.

func Open

func Open(ctx context.Context, dataDir string) (*sql.DB, error)

Open opens (creating if needed) ether.db in dataDir and migrates it.

func OpenMemory

func OpenMemory(ctx context.Context) (*sql.DB, error)

OpenMemory is a migrated, empty database for tests: in-memory SQLite (a single connection), or a fresh PostgreSQL schema when TestPostgresEnv is set.

func OpenPath

func OpenPath(ctx context.Context, path string) (*sql.DB, error)

OpenPath opens a database file; ":memory:" works for tests (single conn). Like OpenMemory, ":memory:" is a fresh PostgreSQL schema instead when TestPostgresEnv is set.

func OpenTest

func OpenTest(t TB) *sql.DB

OpenTest is OpenMemory for a test: it fails the test on error and closes (on PostgreSQL: drops) the database when the test ends.

func OpenTestPostgres

func OpenTestPostgres(ctx context.Context, rawURL string) (*sql.DB, error)

OpenTestPostgres creates a uniquely named schema on the server at rawURL, migrates it and returns a *sql.DB whose connections use it as their search_path. Closing the DB drops the schema.

func PurgeViewer added in v0.3.2

func PurgeViewer(ctx context.Context, tx *sql.Tx, viewerID int64) error

PurgeViewer runs every registered purge for viewerID inside tx, early registrations first. The caller deletes the auth_users row itself, after PurgeViewer, so ON DELETE CASCADE remains a backstop.

func Rebind

func Rebind(q string) (string, int)

Rebind rewrites `?` placeholders as PostgreSQL's $1..$n (a numbered `?NNN` keeps its number), leaving any `?` inside string literals, quoted identifiers and comments untouched. The second result is the number of parameters.

func RedactURL

func RedactURL(raw string) string

RedactURL hides the password (and any password query parameter) in a database URL so it can be logged or shown. Non-URL text that looks like a key=value DSN has its password value masked.

func Register

func Register(component string, version int, sql string)

Register adds a migration written in SQLite's dialect. Versions are per component and start at 1. Call from init(); a migration must never change after release. On PostgreSQL the DDL is translated by TranslateDDL unless RegisterDialect supplied an override.

func RegisterAfter added in v0.3.0

func RegisterAfter(component string, dependsOn ...string)

RegisterAfter declares component-level migration dependencies. Every migration of a dependency is applied before any migration of component. Unrelated components retain their alphabetical order. Dependencies absent from a partial binary are ignored; cycles fail migration startup.

func RegisterApply added in v0.3.2

func RegisterApply(component string, version int, apply func(context.Context, *sql.Tx) error)

RegisterApply binds one Go data migration to both dialects. The function must use portable SQL with `?` placeholders.

func RegisterDialect

func RegisterDialect(component string, version int, d Dialect, sql string)

RegisterDialect overrides one registered migration for dialect d, for DDL the mechanical translation cannot express. It may be called before or after the matching Register (both from init()).

func RegisterPostgresApply added in v0.3.2

func RegisterPostgresApply(component string, version int, apply func(context.Context, *sql.Tx) error)

RegisterPostgresApply binds apply to a migration Register or RegisterDialect already recorded, for PostgreSQL. It is the counterpart of RegisterSQLiteApply, for data migrations that need Go (for example label normalisation) rather than SQL. PostgreSQL Migrate calls apply inside the migration transaction instead of that version's PostgreSQL SQL; `?` placeholders are rebound as for every other statement. SQLite never calls it. Version bookkeeping is unchanged.

func RegisterSQLiteApply added in v0.3.0

func RegisterSQLiteApply(component string, version int, apply func(context.Context, *sql.Tx) error)

RegisterSQLiteApply binds apply to a migration Register or RegisterDialect already recorded. It does not create a version. SQLite Migrate calls apply instead of that version's SQLite SQL. PostgreSQL never calls apply; it still runs the registered SQL or the RegisterDialect override. Success and failure use the same version bookkeeping as every other migration: the schema_migrations row is inserted only after apply returns nil, and an error rolls the version's transaction back with no row written. apply does not disable foreign keys.

func RegisterViewerExempt added in v0.3.2

func RegisterViewerExempt(component, table, reason string)

RegisterViewerExempt records that a table holding a viewer column needs no purge or merge of its own, and why (for example its rows cascade from a registered parent table). The coverage guard accepts the exemption.

func RegisterViewerMerge added in v0.3.2

func RegisterViewerMerge(component string, tables []string, fn ViewerMergeFunc)

RegisterViewerMerge registers a custom merge for the named tables.

func RegisterViewerPurge added in v0.3.2

func RegisterViewerPurge(component string, tables []string, fn ViewerPurgeFunc)

RegisterViewerPurge registers a custom purge for the named tables. Most components use RegisterViewerTable instead; a custom purge suits rows that must be transformed rather than deleted (for example detaching a shared collection from its creator).

func RegisterViewerTable added in v0.3.2

func RegisterViewerTable(component, table, column string, merge ViewerMergeMode, conflictColumns ...string)

RegisterViewerTable registers the standard purge and merge for one table keyed by a viewer column ("user_id", "viewer_id" or "viewer"). For ViewerMergeMove, conflictColumns names the remaining key columns that decide a collision with the target's rows; omit them when the viewer column alone is the table's key.

func RegisterViewerTableEarly added in v0.3.2

func RegisterViewerTableEarly(component, table, column string, merge ViewerMergeMode, conflictColumns ...string)

RegisterViewerTableEarly is RegisterViewerTable for a table other tables reference with ON DELETE RESTRICT: its purge and merge run before all ordinary registrations.

func RunCheckpointer added in v0.3.0

func RunCheckpointer(ctx context.Context, db *sql.DB, truncate func() bool, onErr func(error))

RunCheckpointer PASSIVE-checkpoints until ctx ends. truncate, when it reports true, allows a TRUNCATE on that pass; a nil truncate never truncates. onErr receives checkpoint failures and may be nil.

func SchemaAhead added in v0.2.48

func SchemaAhead(ctx context.Context, db *sql.DB) ([]string, error)

SchemaAhead lists the components this binary registers whose applied migrations in db go beyond the newest it knows ("auth 9 > 7"): the database was migrated by a newer Ether. Components this binary does not register (removed features, or added by a newer release) are ignored.

It only informs. Migrations are additive, so an older binary keeps working on a newer schema; that is what lets the updater's crash-loop rollback restart the previous version and a cluster run mixed versions during a rolling update, and why Ether never refuses to start over it.

func SessionLock

func SessionLock(ctx context.Context, db *sql.DB, key string) (unlock func(), err error)

SessionLock holds the lock for key across every node sharing a PostgreSQL database until unlock is called, for work that spans more than one transaction (a site login, for example). It uses the same key space as LockKey on a connection reserved for the lock; if releasing fails, that connection is discarded, which drops the lock with the session. Waiting honours ctx. On SQLite it returns nil, nil: there is one node, so callers keep their in-process lock.

func SetCallSiteDepth added in v0.3.0

func SetCallSiteDepth(depth int)

SetCallSiteDepth records up to depth callers per site (default 1), to find which caller issues a statement once per item.

func TranslateDDL

func TranslateDDL(sqliteSQL string) string

Migrations are written once, in SQLite's dialect. TranslateDDL turns them into PostgreSQL mechanically; a migration the rules cannot express gets a hand-written override through RegisterDialect.

Rules, applied only outside literals and comments, case-insensitively:

  • `INTEGER PRIMARY KEY AUTOINCREMENT` becomes `BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY`.
  • A bare `id INTEGER PRIMARY KEY` (SQLite's rowid alias used as a generated id) becomes the same identity column. When it is followed by a CHECK (a singleton row such as `CHECK(id=1)`), or the column has any other name (a natural key such as rating_key or user_id that callers always supply), it becomes `BIGINT PRIMARY KEY`.
  • INTEGER becomes BIGINT, REAL becomes DOUBLE PRECISION and BLOB becomes BYTEA (SQLite integers are 64-bit, reals are doubles).

Everything else - TEXT, CHECK constraints, DEFAULTs, partial and DESC indexes, REFERENCES ... ON DELETE CASCADE, ALTER TABLE ADD COLUMN and seed INSERTs - is already valid PostgreSQL and passes through unchanged.

func UnixDay

func UnixDay(d Dialect, expr string) string

UnixDay formats the Unix-seconds expression expr as a UTC 'YYYY-MM-DD' text value.

func UnixHour

func UnixHour(d Dialect, expr string) string

UnixHour formats the Unix-seconds expression expr as a zero-padded UTC hour of day ('00'..'23').

func UnknownApplied

func UnknownApplied(ctx context.Context, db *sql.DB) ([]string, error)

UnknownApplied lists migrations ("component/version") recorded in db that this binary does not register: the database was migrated by a newer version of Ether.

func ViewerCoverage added in v0.3.2

func ViewerCoverage() (covered, exempt map[string]string)

ViewerCoverage reports the registered per-viewer tables (table -> component) and the exemptions (table -> reason).

Types

type Availability

type Availability struct {
	// contains filtered or unexported fields
}

Availability is a node's shared view of whether the database is reachable. Writers that can defer their work (Outbox) and the cluster heartbeat report outcomes to it; once one of them sees an outage the others queue at once instead of each waiting out its own timeout, and all of them flush as soon as any write succeeds again.

A nil *Availability is always available and ignores reports.

func NewAvailability

func NewAvailability(now func() time.Time) *Availability

NewAvailability returns a tracker that starts out available. now may be nil (time.Now).

func (*Availability) Down

func (a *Availability) Down() bool

Down reports whether the database is currently believed unreachable.

func (*Availability) Fail

func (a *Availability) Fail(err error)

Fail marks the database unreachable.

func (*Availability) OK

func (a *Availability) OK()

OK marks the database reachable again.

func (*Availability) Report

func (a *Availability) Report(err error) bool

Report records the outcome of a database operation: nil marks the database reachable; an error that IsUnavailable marks it unreachable; any other error says nothing about reachability. It returns whether err was an outage.

func (*Availability) State

func (a *Availability) State() AvailabilityState

State snapshots the tracker.

func (*Availability) Subscribe

func (a *Availability) Subscribe(fn func(reachable bool)) (unsubscribe func())

Subscribe calls fn(reachable) on every transition, synchronously from the reporting goroutine (keep it quick).

func (*Availability) Up

func (a *Availability) Up() <-chan struct{}

Up returns a channel that is closed while (or as soon as) the database is reachable.

type AvailabilityState

type AvailabilityState struct {
	Reachable bool `json:"reachable"`
	// DownSince is when the current outage was first observed.
	DownSince time.Time `json:"downSince,omitzero"`
	LastError string    `json:"lastError,omitempty"`
}

AvailabilityState is a snapshot for health reports.

type CorruptionError added in v0.3.0

type CorruptionError struct{ Integrity Integrity }

CorruptionError is returned by Maintain when the database fails its integrity check.

func (*CorruptionError) Error added in v0.3.0

func (e *CorruptionError) Error() string

type Dialect

type Dialect string

Dialect names the SQL engine behind a *sql.DB. Components write portable SQL with `?` placeholders; the few constructs that differ between engines go through the helpers below.

const (
	// SQLite is the standalone backend (modernc.org/sqlite).
	SQLite Dialect = "sqlite"
	// Postgres is the clustered backend (owner-supplied PostgreSQL).
	Postgres Dialect = "postgres"
)

func DialectOf

func DialectOf(db *sql.DB) Dialect

DialectOf reports which engine db talks to. Databases opened through this package's PostgreSQL connector report Postgres; everything else SQLite.

func OpenWith

func OpenWith(ctx context.Context, o Options) (*sql.DB, Dialect, error)

OpenWith opens and migrates the database the options select and reports its dialect.

func OpenWithRetry

func OpenWithRetry(ctx context.Context, o Options, r RetryOptions) (*sql.DB, Dialect, error)

OpenWithRetry is OpenWith that keeps retrying a PostgreSQL database that cannot be reached (refused, timed out, shutting down), with backoff, until it opens or ctx ends. It gives up at once on errors retrying cannot fix: an invalid URL, failed authentication, a database that does not exist, an unsupported server or a failing migration. SQLite is opened once, as by OpenWith.

func ParseDialect

func ParseDialect(name string) Dialect

ParseDialect maps a configured driver name to a Dialect; anything that is not recognisably PostgreSQL is SQLite.

func (Dialect) String

func (d Dialect) String() string

type Integrity added in v0.3.0

type Integrity struct {
	// OK is false when the database is damaged.
	OK bool `json:"ok"`
	// Skipped is set on PostgreSQL, which owns its own physical integrity
	// (OK is then true).
	Skipped bool `json:"skipped,omitempty"`
	// Findings are SQLite's first problem reports when OK is false.
	Findings []string  `json:"findings,omitempty"`
	Checked  time.Time `json:"checked"`
	Took     float64   `json:"tookSeconds"`
}

Integrity is the result of an integrity check of a SQLite database.

func QuickCheck added in v0.3.0

func QuickCheck(ctx context.Context, db *sql.DB) (Integrity, error)

QuickCheck runs SQLite's PRAGMA quick_check, which reads every page and catches the "database disk image is malformed" class of damage without integrity_check's index cross-checks (about twice as fast). It never fails on damage: a database too broken for the check to run is reported as a finding. The error is only for a check that could not run at all (cancelled, or the database is closed). PostgreSQL is skipped.

type LogicalHeader

type LogicalHeader struct {
	Format     string         `json:"format"`
	Version    int            `json:"version"`
	Dialect    Dialect        `json:"dialect"`
	Created    time.Time      `json:"created"`
	Migrations map[string]int `json:"migrations"`
}

LogicalHeader is the first line of a logical dump.

func ValidateLogical

func ValidateLogical(r io.Reader) (LogicalHeader, error)

ValidateLogical reads a whole dump and checks its structure and trailer without touching a database. It returns the header.

type MigrationGate

type MigrationGate func(ctx context.Context, q Querier, pending []string) error

MigrationGate is consulted while the cluster-wide migration lock is held and before any of pending ("component/version", in apply order) is applied. Returning an error wrapping ErrMigrationDeferred makes OpenWithRetry wait and try again; any other error fails the open. q reads inside the migration transaction.

type Options

type Options struct {
	// DataDir holds ether.db for SQLite.
	DataDir string
	// Driver is "sqlite" (default) or "postgres".
	Driver string
	// URL is the PostgreSQL connection URL (postgres://...). A postgres://
	// or postgresql:// URL implies Driver "postgres".
	URL string
	// Logger receives open-time warnings; nil uses slog.Default().
	Logger *slog.Logger
	// MigrationGate, when set, decides on PostgreSQL whether this node may
	// apply its pending migrations now (see MigrationGate).
	MigrationGate MigrationGate
	// SnapshotFiles are stored with a SQLite pre-migrate snapshot (the
	// settings and credential key matching the database), if one is taken.
	SnapshotFiles []SnapshotFile
	// Progress, when set, hears what opening is doing: the pre-migrate
	// snapshot and each pending migration (for a startup status page).
	Progress func(Progress)
}

Options selects and configures the database backend.

func (Options) Dialect

func (o Options) Dialect() Dialect

Dialect is the backend these options select.

type Outbox

type Outbox[K comparable, V any] struct {
	// contains filtered or unexported fields
}

Outbox makes deferrable writes survive a database outage. A write goes straight to the database while it is reachable. When the database cannot be reached (an error IsUnavailable, or the attempt outlives Timeout) the write is kept in memory instead — only the latest value per key, merged with Merge — and replayed oldest key first, with backoff, once the database answers again. While the database is known to be down new writes are queued without being attempted, so callers (HTTP handlers) return at once.

Errors that are not outages (a constraint violation, a bug) are returned to the caller from Write, or logged and dropped during a replay.

func NewOutbox

func NewOutbox[K comparable, V any](o OutboxOptions[K, V]) *Outbox[K, V]

NewOutbox starts an outbox's replay goroutine; Close stops it.

func (*Outbox[K, V]) Close

func (ob *Outbox[K, V]) Close(ctx context.Context) error

Close stops the replay goroutine, then makes one last attempt (bounded by ctx) to write what is still held. It returns an error naming how many writes were lost.

func (*Outbox[K, V]) Depth

func (ob *Outbox[K, V]) Depth() int

Depth is the number of queued keys.

func (*Outbox[K, V]) Status

func (ob *Outbox[K, V]) Status() OutboxStatus

Status snapshots the outbox.

func (*Outbox[K, V]) Write

func (ob *Outbox[K, V]) Write(ctx context.Context, key K, v V) (queued bool, err error)

Write applies v for key now, or queues it when the database is unreachable. queued reports the latter; err is a non-outage failure of an immediate write.

type OutboxOptions

type OutboxOptions[K comparable, V any] struct {
	// Name identifies the outbox in logs and status ("watch progress").
	Name string
	// Apply performs one write. It must be safe to run again for the same
	// key (a write whose outcome was lost to a timeout is retried).
	Apply func(ctx context.Context, key K, v V) error
	// Merge combines a queued value with a newer one for the same key;
	// nil keeps only the newer value.
	Merge func(old, new V) V
	// Cap bounds the number of queued keys (DefaultOutboxCap); when full
	// the oldest key is dropped.
	Cap int
	// Timeout bounds every write attempt (DefaultOutboxTimeout), so a
	// caller never waits on an outage longer than this.
	Timeout time.Duration
	// MinBackoff / MaxBackoff space retries while the database is
	// unreachable (1s doubling to 30s).
	MinBackoff, MaxBackoff time.Duration
	// Availability is shared with the node's other outboxes and the
	// cluster heartbeat; nil gives this outbox a private tracker.
	Availability *Availability
	Logger       *slog.Logger
}

OutboxOptions configure an Outbox. Apply is required.

type OutboxStatus

type OutboxStatus struct {
	Name    string `json:"name"`
	Depth   int    `json:"depth"`
	Dropped int64  `json:"dropped,omitempty"`
	// Oldest is when the oldest held write was queued.
	Oldest    time.Time `json:"oldest,omitzero"`
	LastError string    `json:"lastError,omitempty"`
}

OutboxStatus is an outbox's state for health reports.

type Progress added in v0.3.0

type Progress struct {
	Stage       string
	Done, Total int
}

Progress is reported while a database opens. Stage is StageSnapshot (copying a SQLite database before migrating it) or StageMigrate, where Done of Total pending migrations have been applied.

type Querier

type Querier interface {
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}

Querier is the read side of *sql.DB and *sql.Tx.

type QueryStats added in v0.3.0

type QueryStats struct {
	// contains filtered or unexported fields
}

QueryStats accumulates the database work done on behalf of one unit of work, usually one HTTP request. Both drivers (SQLite and PostgreSQL) record into the QueryStats carried by the statement's context, so a handler's cost is measured where it is paid: statement count, rows read back into Go, time inside the driver, and time spent waiting for the write lock. It is safe for concurrent use; a request may fan out.

The counters are what the performance gate asserts on: a request path whose statement or row count grows with the library is O(library).

func QueryStatsFrom added in v0.3.0

func QueryStatsFrom(ctx context.Context) *QueryStats

QueryStatsFrom returns the QueryStats ctx carries, or nil.

func WithQueryStats added in v0.3.0

func WithQueryStats(ctx context.Context) (context.Context, *QueryStats)

WithQueryStats returns ctx carrying a fresh QueryStats. Work already carrying stats keeps recording into the outer one as well, so a nested measurement never hides cost from the request that paid for it.

func (*QueryStats) DBTime added in v0.3.0

func (s *QueryStats) DBTime() time.Duration

DBTime is the time spent inside the driver (executing, stepping rows).

func (*QueryStats) LockWait added in v0.3.0

func (s *QueryStats) LockWait() time.Duration

LockWait is the time spent waiting to begin a transaction (SQLite's write lock, a PostgreSQL BEGIN).

func (*QueryStats) Queries added in v0.3.0

func (s *QueryStats) Queries() int64

Queries is the number of statements executed.

func (*QueryStats) Rows added in v0.3.0

func (s *QueryStats) Rows() int64

Rows is the number of result rows read back from the database.

func (*QueryStats) Statements added in v0.3.0

func (s *QueryStats) Statements() []StatementStat

Statements returns the per-SQL breakdown, most expensive first.

func (*QueryStats) Writes added in v0.3.0

func (s *QueryStats) Writes() int64

Writes is the number of statements executed for their effect (Exec): inserts, updates and deletes. A read path should have none; each one waits for SQLite's single writer.

type RetryOptions

type RetryOptions struct {
	MinBackoff time.Duration // first wait (1s), doubling
	MaxBackoff time.Duration // longest wait (30s)
	// OnRetry is called before every wait with the failed attempt's error
	// (attempt counts from 1).
	OnRetry func(attempt int, wait time.Duration, err error)
}

RetryOptions configure OpenWithRetry. Zero durations take the defaults in parentheses.

type SnapshotFile added in v0.2.48

type SnapshotFile struct {
	Name string
	Data []byte
}

SnapshotFile is a file stored alongside a pre-migrate snapshot. Name is a plain file name; Data is captured by the caller before anything could rewrite it.

type StatementStat added in v0.3.0

type StatementStat struct {
	SQL string `json:"sql"`
	// Site is the first caller outside database/sql and this package, set
	// only while CaptureCallSites is on (the performance harness).
	Site  string        `json:"site,omitempty"`
	Count int64         `json:"count"`
	Rows  int64         `json:"rows"`
	Time  time.Duration `json:"time"`
}

StatementStat is one SQL text's share of a QueryStats.

type TB

type TB interface {
	Helper()
	Fatalf(format string, args ...any)
	Cleanup(func())
}

TB is the part of testing.TB that OpenTest uses.

type TestProxy

type TestProxy struct {
	// contains filtered or unexported fields
}

TestProxy is an in-process TCP proxy for outage tests: clients connect to Addr and are forwarded to the target until the test cuts or pauses the link. It never touches the database server itself.

func NewTestProxy

func NewTestProxy(target string) (*TestProxy, error)

NewTestProxy listens on 127.0.0.1 and forwards to target (host:port).

func (*TestProxy) Addr

func (p *TestProxy) Addr() string

Addr is the proxy's listening address.

func (*TestProxy) Close

func (p *TestProxy) Close() error

Close stops the proxy and drops every connection.

func (*TestProxy) Cut

func (p *TestProxy) Cut()

Cut drops every connection and refuses new ones until Restore.

func (*TestProxy) Pause

func (p *TestProxy) Pause()

Pause stops all traffic (existing connections hang, new ones are accepted and hang) until Restore.

func (*TestProxy) Restore

func (p *TestProxy) Restore()

Restore forwards traffic again. Connections that hung during a pause resume; connections dropped by Cut stay closed.

func (*TestProxy) URL

func (p *TestProxy) URL(raw string) (string, error)

URL rewrites a database URL to connect through the proxy.

type ViewerMergeFunc added in v0.3.2

type ViewerMergeFunc func(ctx context.Context, tx *sql.Tx, fromID, intoID int64) error

ViewerMergeFunc moves one viewer's data to another inside tx.

type ViewerMergeMode added in v0.3.2

type ViewerMergeMode int

ViewerMergeMode says how a table's rows move when one viewer is merged into another.

const (
	// ViewerMergeMove reassigns the source viewer's rows to the target.
	// Where a row would collide with one the target already has (the same
	// values in the registered conflict columns, or any row at all when no
	// conflict columns are registered because the viewer column alone is
	// the key), the target's row wins and the source row is discarded.
	ViewerMergeMove ViewerMergeMode = iota
	// ViewerMergeUnion reassigns every source row to the target. Use it
	// for history and play logs whose key does not involve the viewer.
	ViewerMergeUnion
	// ViewerMergeDrop deletes the source viewer's rows. Use it for
	// sessions, credentials-in-flight and other ephemeral state that must
	// not transfer to another person.
	ViewerMergeDrop
)

type ViewerPurgeFunc added in v0.3.2

type ViewerPurgeFunc func(ctx context.Context, tx *sql.Tx, viewerID int64) error

ViewerPurgeFunc deletes one viewer's data inside tx.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL