meerkat_sqlite/lib.rs
1//! Shared SQLite mechanics for Meerkat stores.
2//!
3//! This crate is the single home for the connection policy, schema-evolution,
4//! and maintenance-fence machinery that was previously copy-pasted across the
5//! store crates (six independently authored PRAGMA setups with divergent busy
6//! timeouts, `CREATE TABLE IF NOT EXISTS` everywhere, and no version ledger).
7//! See `docs/plans/storage-unification-plan.md` (Phase 3) in the workspace
8//! root for the design rationale.
9//!
10//! What lives here:
11//!
12//! - [`profile`]: DDL-free connection opening under named policy profiles
13//! ([`ConnectionProfile::Primary`], [`ConnectionProfile::ReadOnly`],
14//! [`ConnectionProfile::Maintenance`]). Opening a connection never runs
15//! schema DDL; stores apply their [`ledger`] domain after opening. The
16//! optional [`OpenOptions::schema_preflight`] proves an exact current,
17//! released-predecessor, or fresh-domain shape before any mutating pragma,
18//! and [`WriteContact`] types each profile's honest no-write guarantee
19//! (WAL reads may need sidecar files).
20//! - [`ledger`]: the per-file migration ledger (`meerkat_schema(domain,
21//! version)`) with the pinned concurrent-open transaction protocol and the
22//! typed refusals for future, pre-floor, gap, unledgered-owned, and
23//! fingerprint-mismatched shapes. Ledger state that is malformed (wrong
24//! shape, duplicate rows, non-positive versions) is refused typed, never
25//! healed.
26//! - [`json_column`]: the TEXT-or-BLOB tolerant JSON column codec.
27//! - [`fence`]: the per-operation maintenance-fence guards. Store operations
28//! take a shared guard; offline migration takes the exclusive side and
29//! waits for in-flight operations to drain. Stores built on this crate hold
30//! no bespoke opener, which is what makes them fence-aware for free.
31//! - [`error`]: the crate error type plus the storage-level error
32//! classification (transient / corrupt) that store crates layer their
33//! staleness semantics on top of. Adoption contract: store crates route
34//! every raw rusqlite error through [`classify_sqlite_error`] instead of
35//! re-matching SQLite error codes locally.
36//!
37//! # Retryability
38//!
39//! Error classification alone does not authorize a retry: a transient failure
40//! can land after a write committed but before success became observable, and
41//! blind retry duplicates non-idempotent effects. Automatic retry is only
42//! sound for idempotent or CAS-keyed operations; an indeterminate
43//! non-idempotent write requires outcome reconciliation (read back, then
44//! decide) before any retry. Bounded waiting for lock contention is the
45//! connection busy handler's job, not a caller retry loop.
46
47pub mod error;
48pub mod fence;
49pub mod json_column;
50pub mod ledger;
51pub mod profile;
52
53pub use error::{SqliteErrorClass, SqliteStoreError, classify_sqlite_error, is_busy_or_locked};
54pub use fence::{ExclusiveFence, OperationGuard, fence_lock_path};
55pub use json_column::JsonColumnBytes;
56pub use ledger::{
57 LedgerReport, Migration, SchemaDomain, SchemaObject, SchemaObjectKind, SchemaPredecessor,
58 apply_domain_migrations, domain_version, preflight_schema_eligibility,
59 verify_released_schema_fingerprint,
60};
61pub use profile::{
62 ConnectionProfile, OpenOptions, SHARED_BUSY_TIMEOUT, WriteContact, begin_immediate, open,
63 open_with,
64};