Rindle docs and package mapSkip to main content

ApplyStore

Struct ApplyStore 

Source
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

Source

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].

Source

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.

Source

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.

Source

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.

Source

pub fn committed_tx_id(&self) -> TxId

The last durably-committed global tx id (0 if none yet).

Source

pub fn in_write(&self) -> bool

True while a write transaction (ApplyTxn) is open.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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).

Source

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 (keymarker_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 presentDDL 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.

Source

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>
where F: FnMut(&Connection, &DdlStep) -> Result<()>,

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.

Trait Implementations§

Source§

impl Drop for ApplyStore

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,