pub struct ApplyStore { /* private fields */ }Expand description
The apply plane’s connection pair + capture context. !Send (the preupdate hook and
the transaction cells are single-thread state) — one per thread, like the replica
handles built over it.
Implementations§
Source§impl ApplyStore
impl ApplyStore
Sourcepub fn open(
path: &Path,
journal: JournalMode,
writer_begin_sql: &'static str,
foreign_keys: ForeignKeys,
) -> Result<ApplyStore, ReplicaError>
pub fn open( path: &Path, journal: JournalMode, writer_begin_sql: &'static str, foreign_keys: ForeignKeys, ) -> Result<ApplyStore, ReplicaError>
Open the store over a file-backed SQLite database: the observed writer (with
the capture hook installed), the read-only reader, the __replica_meta
committed-tx watermark, and the journal negotiation (design 306 D3/D5 — a fresh
file gets journal’s mode; an existing wal/wal2 file keeps its mode unless
Wal2 is explicitly required). Asserts sqlite3_threadsafe() != 0, failing
loud rather than silently degrading. writer_begin_sql is the BEGIN flavor
every write transaction opens with (DEFAULT_WRITER_BEGIN_SQL unless a
derivation host maps its own mode onto it). foreign_keys is the writer’s
enforcement posture: a headless replay and a live follower pass
[ForeignKeys::Unenforced] (the stream’s rows were validated upstream and its
cascades already ran there), an origin write plane passes
[ForeignKeys::Enforced] — see [rindle_writeplane::foreign_keys].
Sourcepub fn open_scratch(
path: &Path,
writer_begin_sql: &'static str,
foreign_keys: ForeignKeys,
) -> Result<ApplyStore, ReplicaError>
pub fn open_scratch( path: &Path, writer_begin_sql: &'static str, foreign_keys: ForeignKeys, ) -> Result<ApplyStore, ReplicaError>
Open a scratch store: one connection, journal_mode=memory, synchronous=OFF
(see connection::open_scratch for exactly what that trades and what the caller
owes in return). Everything else — the pragmas, the capture hook,
the watermark, the transaction machinery — is identical to open, so a
scratch materializes the same store as a durable one; only its crash posture differs.
For a database DERIVED from an authority that can rebuild it (rindle-backup’s
producer scratch, rebuilt from the archive). Never for one anybody restores from.
Sourcepub fn writer_connection(&self) -> &Connection
pub fn writer_connection(&self) -> &Connection
The observed writer connection, for narrow host bookkeeping while no write transaction is open. Anything written here still passes through the capture hook — an application-table row landed outside the typed transaction machinery will trip the next commit’s capture accounting, not silently diverge — but hosts should stay on the typed methods and keep this for unregistered bookkeeping.
Sourcepub fn capture(&self) -> &CaptureCtx
pub fn capture(&self) -> &CaptureCtx
The capture context installed on the writer. Observation (has_table,
buffer_len) is always safe; the mutating methods (reset/drain/rewind)
belong to the transaction machinery — calling them under an open ApplyTxn
corrupts its capture accounting.
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 an arbitrary read against a physically read-only connection (a wal2 reader sees
the latest committed snapshot). Schema changes belong in Self::exec_ddl, and row
mutations in a write transaction.
On a single-connection scratch store the read runs on the writer instead, and so sees the open write transaction’s own uncommitted rows. Every caller here reads schema or bookkeeping it either just wrote or is about to depend on, so seeing more is never wrong; a future caller that needs the committed snapshot specifically must not assume this connection provides it.
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
that stands in for enforcement on an apply-plane store (see
[rindle_writeplane::foreign_keys]).
PRAGMA foreign_key_check reports what is in the file, not what SQLite would have
refused, so this answers the same question on a store opened
[ForeignKeys::Unenforced] as on one opened [ForeignKeys::Enforced] — which is
the whole reason the apply plane can turn enforcement off without giving up the
ability to prove referential integrity. It is a full scan of the referencing
tables: run it deliberately (an operator command, a post-restore gate, a soak
assertion), never per commit. Runs on the read connection, so it neither takes the
write lock nor disturbs an applier.
Stops after max_rows violations, reporting
[rindle_writeplane::ForeignKeyAudit::truncated]; pass
[rindle_writeplane::FOREIGN_KEY_AUDIT_ROW_CAP] unless
you have a reason not to, or 0 for no cap.
The database’s schema cookie (PRAGMA schema_version), read on the writer connection so
an open mutation transaction’s uncommitted DDL is visible. Hosts whose trusted SQL facade
runs no statement classification compare it across a transaction to detect DDL that must
invalidate reader pools and capture registration.
Sourcepub fn checkpoint_truncate(&self) -> Result<WalCheckpoint, ReplicaError>
pub fn checkpoint_truncate(&self) -> Result<WalCheckpoint, ReplicaError>
Checkpoint and truncate the WAL through the writer. Snapshot/install code uses this
after quiescing writes so the portable main file carries every committed frame. The
public Self::read connection is physically read-only and deliberately cannot perform a
checkpoint.
Sourcepub fn register_table_capture(
&self,
table: &str,
) -> Result<ReplicatedTableSchema, ReplicaError>
pub fn register_table_capture( &self, table: &str, ) -> Result<ReplicatedTableSchema, ReplicaError>
The engine-free half of base-table registration: introspect + validate the
table’s replicated shape, ensure the PK UNIQUE index row-identity point lookups
require, and teach the capture hook the column types + primary key. Returns the
introspected schema so a derivation host can build its sources from the SAME
introspection (rindle_replica::Cluster::register_table lifts it to the
engine’s ColumnDef shape). Rejects BLOB columns / PK-less tables; the table
must be plain (no triggers, no generated columns).
Sourcepub fn create_client_mutations_tables(&self) -> Result<(), ReplicaError>
pub fn create_client_mutations_tables(&self) -> Result<(), ReplicaError>
Create the client-mutations ledger tables (idempotent): the daemon-plane
[CLIENT_MUTATIONS_TABLE] and the room-scoped [ROOM_CLIENT_MUTATIONS_TABLE].
Both are replicated data (lmid-as-data rides the change stream), so a
headless applier consuming a master’s stream registers the former for capture
exactly like the live follower does. The registration itself is the caller’s
next step (register_table_capture, or the derivation host’s engine-inclusive
registration). Rejected mid-write-txn.
Sourcepub fn exec_ddl(&self, sql: &str) -> Result<(), ReplicaError>
pub fn exec_ddl(&self, sql: &str) -> Result<(), ReplicaError>
Run schema DDL (CREATE/ALTER/DROP/REINDEX) against the writer connection — the
supported way to define the plain base tables you then register and write through. The
historical bounded ANALYZE call is also accepted; row-changing statements and
row-producing DDL are rejected. The complete batch and schema-envelope validation commit
atomically. Rejected while a write transaction is open (DDL there would be invisible to a
derivation host’s workers until commit).
Sourcepub fn exec_ddl_with_marker(
&self,
marker_table: &str,
key: &str,
statements: &[String],
) -> Result<DdlApplyReport, ReplicaError>
pub fn exec_ddl_with_marker( &self, marker_table: &str, key: &str, statements: &[String], ) -> Result<DdlApplyReport, ReplicaError>
Apply schema statements AND stamp a durable idempotency marker (key → marker_table) in
ONE ordinary transaction on the writer connection. This is the atomic unit a BEGIN CONCURRENT
cursor advance can’t give a follower’s DDL (schema changes are illegal in a concurrent txn):
because the marker lands in the SAME plain transaction as the DDL, marker present ⇔ DDL applied, exactly. A crash-window replay then dedups by key (a migration id / entry offset)
instead of re-running the DDL and inferring “already applied?” from the error string. The
marker_table MUST be UNREGISTERED bookkeeping (like _rindle_source_offsets) so the capture
hook ignores its rows. The resulting schema is validated atomically before commit, and the
consumer reboots its table sources afterward. Rejected while a write transaction is open
(mirrors exec_ddl).
§This seam trusts its statements
Unlike exec_ddl, this path does NOT run ensure_embedded_ddl_batch and passes
allow_row_changes = true, so a historical create-copy-swap migration can land its data copy
atomically with the marker. Copied rows come from an already-managed table and so satisfy the
replicated value envelope by induction — but a table created inside the same batch is not yet
CDC-registered, and rindle-cdc only counts uncaptured events for unregistered tables rather
than validating their cells. A literal invalid value in a statement given to this function
is therefore not validated.
That is accepted deliberately: the only production caller is the follower’s replicated-DDL
apply path (ApplyConsumer::apply_ddl_with_marker and its ClusterConsumer delegate).
Its statements originate from the write master’s migrations, and the master’s migrate
surface is DDL-only. The guarantee is a reachability argument rather than an enforced check,
so if migrations ever admit DML, this seam needs a real boundary check — validate cells
for unregistered main tables in the capture hook, or register batch-created tables before
the copy runs. See
follow-ups/222-224-sql-client-second-review.md §2.6.
Sourcepub fn exec_ddl_with_marker_and_step_effects<F>(
&self,
marker_table: &str,
key: &str,
statements: &[String],
apply_step_effects: F,
) -> Result<DdlApplyReport, ReplicaError>
pub fn exec_ddl_with_marker_and_step_effects<F>( &self, marker_table: &str, key: &str, statements: &[String], apply_step_effects: F, ) -> Result<DdlApplyReport, ReplicaError>
exec_ddl_with_marker, with one caller-owned bookkeeping
hook invoked after every real statement and its authorizer actions. The hook runs inside
the SAME transaction as the DDL and marker. It may mutate unregistered bookkeeping tables,
but must not issue schema DDL (the recorder and schema validator are still active).
This is the follower’s atomic desired-state seam: schema-derived metadata must never lag a committed marker and be reconstructed during crash replay, because startup repair can act on that stale metadata before replay gets a chance to clean it up.