pub struct Db { /* private fields */ }Expand description
A single-thread SQLite runtime with live queries. Clones share the same writer,
engine, and registrations through Rc; they do not create independent databases.
This handle is !Send. Query and write handles keep the shared runtime alive.
Implementations§
Source§impl Db
impl Db
Sourcepub fn enable_client_mutations(&self) -> Result<(), ReplicaError>
pub fn enable_client_mutations(&self) -> Result<(), ReplicaError>
One-time (idempotent) setup for the client-mutations protocol: create
CLIENT_MUTATIONS_TABLE and register it like any base table (CDC capture +
engine-hosted source), so lmid rides the change stream co-transactionally
with the data AND is queryable — each client’s one-row system query is an
ordinary registered query over it (§8.2). Rejected while a write transaction
is open (DDL would be invisible to the engine’s worker until commit, like
register_table).
Sourcepub fn enable_realtime_lifecycle(&self) -> Result<(), ReplicaError>
pub fn enable_realtime_lifecycle(&self) -> Result<(), ReplicaError>
One-time (idempotent) setup for the §4 realtime lifecycle
(RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md): create the lifecycle tables and
register them — plus ROOM_CLIENT_MUTATIONS_TABLE — like any base table (CDC
capture + engine-hosted source), because each is load-bearing as SUBSCRIBABLE
data, not just durable state:
SCOPE_SESSIONS_TABLE— the occupancy-row delta arriving through a solo client’s daemon subscription IS the upgrade doorbell (§4.1);ROOM_WATERMARK_TABLE— the downgrade fence clears only when the client’s daemon subscription delivers the room’s finalflush_seq(§4.2);ROOM_MUTATION_OUTCOMES_TABLE— outcome resolution with no room socket alive (created + registered here; POPULATED by Slice I-ii’s flush split);ROOM_CLIENT_MUTATIONS_TABLE— “after downgrade, the doc-scoped ledger row is ordinary footprint data” (§7.1, load-bearing for §7.5). Slice C created it deliberately direct-SQL-only; the lifecycle is what needs it readable through the daemon subscription plane, so its registration lands here, not inenable_client_mutations.
Requires enable_client_mutations to have run
first: that call owns the room ledger’s DDL, and re-issuing a second copy here
would be one edit away from two drifting schemas — enforced with a loud error
(the require_enabled idiom) instead. Rejected while a write transaction is
open, like the precedent.
Sourcepub fn client_lmid(&self, client_id: &str) -> Result<u64, ReplicaError>
pub fn client_lmid(&self, client_id: &str) -> Result<u64, ReplicaError>
The high-water mutation id durably recorded for client_id (0 if none — a new
client). What a server stamps on a (re)connecting client’s handshake so it can
drop already-confirmed pending mutations. Requires
enable_client_mutations.
Sourcepub fn apply_mutations(
&self,
registry: &MutatorRegistry,
envelopes: &[MutationEnvelope],
) -> Result<MutationOutcome, ReplicaError>
pub fn apply_mutations( &self, registry: &MutatorRegistry, envelopes: &[MutationEnvelope], ) -> Result<MutationOutcome, ReplicaError>
Apply a client’s mutation push: each envelope’s mutator runs in its own
transaction (with the co-transactional lmid upsert), in the order given.
Per envelope, against its client’s stored lmid:
mid ≤ lmid— already processed: skipped (idempotent redelivery).mid == lmid + 1— applied; on mutator failure (error / panic / unknown name) the effects roll back andlmidstill advances in an lmid-only commit (processed-as-no-op — there is no rejection signal).mid > lmid + 1— a gap (the client must send contiguously, so this should be impossible): the push fails withReplicaError::Mutationat that envelope (prior envelopes stay applied — they are already durable).
Every committed transaction fires the registered queries’ subscriptions as
usual and is reported (with its crate::CommitInfo) in the outcome, so the
caller can drive a progress tracker / poke layer. Must be called with no write
transaction open (it manages its own).
Source§impl Db
impl Db
Sourcepub fn open(path: impl AsRef<Path>) -> Result<Db, ReplicaError>
pub fn open(path: impl AsRef<Path>) -> Result<Db, ReplicaError>
Open a replica over a file-backed SQLite database (derivation needs WAL + multiple connections, which an in-memory DB cannot provide).
A fresh file gets ordinary journal_mode = wal (design 306 D5 — the file stays
openable by any stock SQLite build); an existing wal or wal2 file keeps its
mode. Asserts sqlite3_threadsafe() != 0, failing loud rather than silently
degrading.
Sourcepub fn open_with_planning(
path: impl AsRef<Path>,
plan_queries: bool,
) -> Result<Db, ReplicaError>
pub fn open_with_planning( path: impl AsRef<Path>, plan_queries: bool, ) -> Result<Db, ReplicaError>
Like open, but with explicit control over the cost-based join-flip
planner. When plan_queries is true, query runs the planner (a
SqliteCostModel over the worker connection, cached) at registration to annotate
flip before lowering. Result-preserving; the plan is frozen per registration.
The planner is server-side (table-source) only and open enables it by default;
pass false here to opt out (e.g. to pin the unplanned path).
Sourcepub fn open_with_options(
path: impl AsRef<Path>,
plan_queries: bool,
operator_storage: OperatorStorage,
) -> Result<Db, ReplicaError>
pub fn open_with_options( path: impl AsRef<Path>, plan_queries: bool, operator_storage: OperatorStorage, ) -> Result<Db, ReplicaError>
Like open_with_planning, but also selects the
operator-scratch-state backend. Pass OperatorStorage::SqliteSpill to spill
stateful-operator state (take/cap/reduce) to a private on-disk temp
database instead of keeping it in RAM. This reduces operator scratch memory;
it does not cap all memory used by the runtime or the application.
Sourcepub fn open_with(
path: impl AsRef<Path>,
opts: OpenOptions,
) -> Result<Db, ReplicaError>
pub fn open_with( path: impl AsRef<Path>, opts: OpenOptions, ) -> Result<Db, ReplicaError>
The full-combination opener: every OpenOptions field is honored, so any
planner × operator-storage × journal combination is one call — the named
rungs above are conveniences over this.
Sourcepub fn maintain(
&self,
opts: &MaintenanceOptions,
) -> Result<MaintenanceReport, ReplicaError>
pub fn maintain( &self, opts: &MaintenanceOptions, ) -> Result<MaintenanceReport, ReplicaError>
Run one online maintenance pass on the writer connection — see
Cluster::maintain for the full contract. Best-effort and
bounded; skipped (reporting MaintenanceReport::skipped()) while a write transaction
is open so it never disturbs in-flight work.
Sourcepub fn analyze_full(&self) -> Result<(), ReplicaError>
pub fn analyze_full(&self) -> Result<(), ReplicaError>
Run a full ANALYZE on the writer to build deep planner statistics (sqlite_stat4) —
the single-thread parity for Cluster::analyze_full. Call
once after a bulk load so the planner can seek (not scan) an ORDER BY … LIMIT n view’s
displacement re-fetch (GitHub #68). Restores the bounded analysis_limit afterward.
Rejected while a write transaction is open.
Sourcepub fn register_table(&self, table: &str) -> Result<(), ReplicaError>
pub fn register_table(&self, table: &str) -> Result<(), ReplicaError>
Register a base table with the engine: discover its columns + primary key from
the schema, ensure the PK has the UNIQUE index TableSource requires, and build
the shared source. Idempotent per table. Rejects BLOB-typed columns and tables
without a primary key.
The table must be plain — SQL triggers and generated columns are rejected (see the crate-level “supported usage” docs). Foreign-key cascades are represented by the explicit row deltas observed by the preupdate hook.
Sourcepub fn query(&self, query_id: QueryId, ast: Ast) -> Result<Query, ReplicaError>
pub fn query(&self, query_id: QueryId, ast: Ast) -> Result<Query, ReplicaError>
Register a live query from a Zero-wire [Ast] (build it with rindle::table(..)
or deserialize via Db::query_json) under the caller-supplied QueryId tag
(echoed via Query::id; the engine never interprets it — see QueryId).
Lowers the AST into the shared engine, hydrates it, and returns a Query handle
to subscribe to. BuildError (unknown table/column, unsupported shape) is
surfaced synchronously here.
Each call builds its own pipeline — the engine does not de-duplicate, even
for an identical query_id/AST. A caller that wants to share one pipeline across
requesters dedups at its own layer (it holds the per-requester hydration state the
raw change stream does not). Subscribe before any later writes: the handle’s
initial hydration is cached at registration, not kept current. Explicitly call
Query::destroy to unregister; dropping the handle does not stop the query.
Sourcepub fn query_json(
&self,
query_id: QueryId,
json: &str,
) -> Result<Query, ReplicaError>
pub fn query_json( &self, query_id: QueryId, json: &str, ) -> Result<Query, ReplicaError>
Convenience: parse a Zero-wire AST from JSON, then Db::query under query_id.
Sourcepub fn analyze_query(&self, ast: &Ast) -> Result<AnalyzeReport, ReplicaError>
pub fn analyze_query(&self, ast: &Ast) -> Result<AnalyzeReport, ReplicaError>
Analyze ast COLD and report where its time and rows go
(ANALYZE-QUERY-DESIGN.md) — the single-thread parity for
Cluster::analyze_query. Read-only: it builds and
drops a throwaway instrumented pipeline over a pinned read snapshot, registering no
materialization and leaving every live query untouched. BuildError (unknown
table/column, unsupported shape) surfaces as Err. Rejected while a write
transaction is open (its snapshot is not yet committed).
Sourcepub fn set_max_delta_bytes(&self, max_bytes: usize)
pub fn set_max_delta_bytes(&self, max_bytes: usize)
Tune the derivation memory budget (design 306 D4): the estimated bytes of
folded rows one transaction may hold — across every table it touches, not per
table — before its derivation gives up. The default is
[rindle_sqlite::DEFAULT_MAX_DELTA_BYTES] (256 MiB).
Overflow is a shed, not a failure: the transaction still commits; the engine
rebuilds and every registered query re-hydrates from the committed state
(subscribers receive a fresh Update::Hydrated — see Update). Lower it to
bound derivation memory harder (shedding sooner); raise it to keep huge bulk
loads deriving incrementally at the cost of memory. Applies to every registered
table, now and later, and survives the shed rebuild itself.
The accounting is an upper-bound estimate of the delta’s own heap — row buffers
plus per-index bookkeeping — described at [rindle_sqlite::DeltaBudget]. It is
not a process RSS limit: SQLite’s page cache, the query pipelines, and the
re-hydration this shed triggers are all outside it.
Sourcepub fn write(&self) -> Result<WriteTxn, ReplicaError>
pub fn write(&self) -> Result<WriteTxn, ReplicaError>
Open the single-writer transaction. Run ordinary SQL through the returned
WriteTxn; commit derives + delivers each query’s incremental events,
rollback (or drop) leaves every view untouched. Errors if a write txn is
already open (there is exactly one writer).
Sourcepub fn committed_tx_id(&self) -> TxId
pub fn committed_tx_id(&self) -> TxId
The last durably-committed global tx id (0 if none yet).
Sourcepub fn read<T>(
&self,
f: impl FnOnce(&Connection) -> Result<T>,
) -> Result<T, ReplicaError>
pub fn read<T>( &self, f: impl FnOnce(&Connection) -> Result<T>, ) -> Result<T, ReplicaError>
Run SQL against a physically read-only connection using SQLite snapshot semantics.
This separate connection cannot see an open WriteTxn’s uncommitted writes.
SQLITE_OPEN_READ_ONLY prevents a callback from bypassing capture with writes.
Schema changes belong in Self::exec_ddl, and row mutations in Self::write.
Sourcepub fn foreign_key_audit(
&self,
max_rows: usize,
) -> Result<ForeignKeyAudit, ReplicaError>
pub fn foreign_key_audit( &self, max_rows: usize, ) -> Result<ForeignKeyAudit, ReplicaError>
Walk every declared foreign key and report violating rows — the opt-in audit.
PRAGMA foreign_key_check reports what is in the file, not what SQLite would have
refused, so it answers the same question whatever
OpenOptions::foreign_keys this replica was opened with. It is a full scan of
the referencing tables — a deliberate operation, never a per-commit step. Pass
FOREIGN_KEY_AUDIT_ROW_CAP unless you have a reason not to, or 0 for no cap.
Sourcepub fn view_schema(&self, ast: &Ast) -> Result<Schema, ReplicaError>
pub fn view_schema(&self, ast: &Ast) -> Result<Schema, ReplicaError>
The hierarchical view [rindle::value::Schema] ast materializes to over the registered
tables (shape + sort + singular + in-view relationships) — derived identically to
query. A layer that ships views to a remote (the flat-change/wire
schema + fingerprint) needs this without registering the query. BuildError
(unknown table/column) surfaces as Err.
Sourcepub fn read_snapshot(
&self,
query_id: QueryId,
) -> Result<Vec<ChangeEvent>, ReplicaError>
pub fn read_snapshot( &self, query_id: QueryId, ) -> Result<Vec<ChangeEvent>, ReplicaError>
Fetch the registered query through its pipeline as a fresh set of hydration
Add events. This traverses current sources; it does not read a cached view
assembled from prior Changed events. It does not consume subscriber events.
An unregistered query yields an empty snapshot. Read failures return Err.
Use distinct query IDs when reading snapshots by ID.
Sourcepub fn exec_ddl(&self, sql: &str) -> Result<(), ReplicaError>
pub fn exec_ddl(&self, sql: &str) -> Result<(), ReplicaError>
Run schema DDL (CREATE TABLE …) against the writer connection in autocommit —
the supported way to define the plain base tables you then Self::register_table and
write through (crate-level “supported usage”). Rejected while a write transaction is
open (DDL there would be invisible to the engine’s separate worker until commit).
Accepts schema DDL (CREATE/ALTER/DROP/REINDEX) plus the historical bounded
ANALYZE maintenance call; row-changing statements are rejected. The complete batch and
schema-envelope validation commit atomically, and row-producing DDL is rejected too.