Rindle docs and package mapSkip to main content

rindle_cdc_apply/
lib.rs

1//! # rindle-cdc-apply — the engine-free CDC apply plane (design 309)
2//!
3//! "Apply a rindle CDC stream to a SQLite database" as a complete, closed capability —
4//! **a follower without IVM**. The CDC family has capture (`rindle-cdc`), transport
5//! (`rindle-stream`/`rindle-replicator`), and image distribution (`rindle-cdc-image`);
6//! this crate is the apply quarter:
7//!
8//! - **table registration** — declared ([`ApplyConsumer::register_table`]) or
9//!   introspected ([`ApplyConsumer::register_existing_table`], with the `_rindle_columns`
10//!   PG-mirror sidecar override);
11//! - **row apply** — decode a normalized op, build its SQL, execute it on the single
12//!   observed writer connection ([`ApplyConsumer::apply_muts`]);
13//! - **the cursor discipline** — the `_rindle_source_offsets` upsert riding the SAME
14//!   transaction as the effects (a crash can never land data without its cursor or
15//!   vice-versa), run-fencing (`run_id`), mid-run `chunk_seq`, and the durable head
16//!   stamps ([`ApplyConsumer::commit_follower_txn_with_head`]);
17//! - **replicated DDL** — statements + idempotency marker + caller bookkeeping in ONE
18//!   ordinary transaction, `marker present ⇔ DDL applied` exactly
19//!   ([`ApplyStore::exec_ddl_with_marker`]);
20//! - **resume + portability** — [`ApplyConsumer::source_checkpoint`],
21//!   [`ApplyStore::checkpoint_truncate`], and (in `rindle-writeplane`) the restore
22//!   bootstrap.
23//!
24//! ## The two hosts
25//!
26//! A **headless** consumer — `rindle-backup-sqlite`'s portable journal replay, or any
27//! external process mirroring a rindle stream into its own SQLite — opens with
28//! [`ApplyConsumer::open`] and composes nothing else: no worker threads, no engines, no
29//! drain. The **live replica** (`rindle_replica::Cluster`/`ClusterConsumer`) recomposes
30//! on top: its worker pool implements [`CommitFanout`], and every apply method there
31//! DELEGATES here — one implementation, moved, never forked — so the live follower and
32//! a headless replay stay **byte-identical** by construction.
33//!
34//! The seam is [`CommitFanout`]: the write transaction hands its captured chunks to
35//! whatever observes the commit, and *absence* of a derivation pool is a type
36//! ([`NoFanout`]), not a zero-worker parameter. Control flow is identical either way —
37//! the capture buffer still drains at [`PUSH_CHUNK_ROWS`], the guards still gate every
38//! commit, the barriers just have no one to hold.
39//!
40//! Capture (the `rindle-cdc` preupdate hook) is mandatory in both hosts: its divergence
41//! guards (`uncaptured_user_event_count`, capture-error poisoning) protect the headless
42//! paths too — a DDL bounce that misses a registration surfaces as uncaptured rows
43//! instead of silent drift.
44//!
45//! The derivation (query) surface deliberately does not exist here — an
46//! [`ApplyConsumer`] has no `query`/`read_snapshot`/`view_schema`, so a host that
47//! cannot derive cannot express the call (the type system replaces the refusal rule,
48//! design 309 §3).
49
50mod connection;
51mod consumer;
52mod fanout;
53mod store;
54mod txn;
55
56pub use connection::{
57    open_journal, open_journal_read_only, set_wal_autocheckpoint, ANALYSIS_LIMIT,
58};
59pub use consumer::{
60    client_mutations_table_meta, ensure_source_offsets_table_on_conn, source_offsets_table_ddl,
61    upsert_source_offset, upsert_source_offset_hashed, ApplyConsumer, SourceHead,
62    APPLIED_DDL_TABLE,
63};
64pub use fanout::{CommitFanout, FanoutGate, FanoutStream, NoFanout, StreamBegin, PUSH_CHUNK_ROWS};
65pub use store::{
66    ensure_unique_pk_index_for, ApplyStore, DdlMigrationError, WalCheckpoint,
67    DEFAULT_WRITER_BEGIN_SQL,
68};
69pub use txn::{ApplyTxn, CommitInfo};