Skip to main content

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//!   [`JournalPolicy`] states per profile whether an open establishes WAL or
19//!   preserves the mode it finds, and [`WriteContact`] types each profile's
20//!   honest no-write guarantee (WAL reads may need sidecar files).
21//! - [`ledger`]: the per-file migration ledger (`meerkat_schema(domain,
22//!   version)`) with the pinned concurrent-open transaction protocol and the
23//!   typed refusals for future, pre-floor, gap, unledgered-owned, and
24//!   fingerprint-mismatched shapes. Ledger state that is malformed (wrong
25//!   shape, duplicate rows, non-positive versions) is refused typed, never
26//!   healed.
27//! - [`json_column`]: the TEXT-or-BLOB tolerant JSON column codec.
28//! - [`lock_profile`]: the process-wide lock profile of the bundled SQLite:
29//!   the one declared `LIBSQLITE3_FLAGS` value that turns off its three
30//!   process-wide mutexes, a read of the profile the process actually runs
31//!   with, and the runtime allocator-statistics switch host binaries call
32//!   first in `main`.
33//! - [`fence`]: the per-operation maintenance-fence guards. Store operations
34//!   take a shared guard; offline migration takes the exclusive side and
35//!   waits for in-flight operations to drain and for kept connections (which
36//!   hold the presence lock) to close. Stores built on this crate hold no
37//!   bespoke opener, which is what makes them fence-aware for free.
38//! - [`watch`]: the cross-process change watch over a database file and its
39//!   sidecars (coalesced notification ticks plus a bounded sweep; a hint,
40//!   never permission or custody).
41//! - [`error`]: the crate error type plus the storage-level error
42//!   classification (transient / corrupt) that store crates layer their
43//!   staleness semantics on top of. Adoption contract: store crates route
44//!   every raw rusqlite error through [`classify_sqlite_error`] instead of
45//!   re-matching SQLite error codes locally.
46//!
47//! # Retryability
48//!
49//! Error classification alone does not authorize a retry: a transient failure
50//! can land after a write committed but before success became observable, and
51//! blind retry duplicates non-idempotent effects. Automatic retry is only
52//! sound for idempotent or CAS-keyed operations; an indeterminate
53//! non-idempotent write requires outcome reconciliation (read back, then
54//! decide) before any retry. Bounded waiting for lock contention is the
55//! connection busy handler's job, not a caller retry loop.
56
57pub mod error;
58pub mod fence;
59pub mod json_column;
60pub mod ledger;
61pub mod lock_profile;
62pub mod profile;
63#[cfg(not(target_arch = "wasm32"))]
64pub mod watch;
65
66pub use error::{
67    BridgeEligibility, CurrentCatalogUnavailability, SqliteErrorClass, SqliteStoreError,
68    classify_sqlite_error, is_busy_or_locked,
69};
70pub use fence::{
71    ExclusiveFence, KeptConnectionPresence, OperationGuard, fence_lock_path, presence_lock_path,
72};
73pub use json_column::JsonColumnBytes;
74#[cfg(any(test, feature = "test-support"))]
75#[doc(hidden)]
76pub use ledger::arm_preflight_probe_for_test;
77pub use ledger::{
78    LedgerReport, MaintenanceBridgeReport, MaintenancePrepareFn, MaintenancePrepareReport,
79    MaintenanceRecordRefusal, Migration, SchemaDomain, SchemaObject, SchemaObjectKind,
80    SchemaPredecessor, apply_domain_migrations, bridge_unledgered_domain, domain_version,
81    preflight_domain_version, preflight_schema_eligibility, verify_released_schema_fingerprint,
82    verify_released_schema_structure,
83};
84pub use lock_profile::{
85    MemoryStatisticsOutcome, REQUIRED_LIBSQLITE3_FLAGS, SqliteLockProfile,
86    SqliteLockProfileMismatch, check_lock_profile, disable_memory_statistics_at_process_start,
87    memory_statistics_outcome,
88};
89pub use profile::{
90    ConnectionProfile, DatabaseFileIdentity, JournalPolicy, OpenOptions, SHARED_BUSY_TIMEOUT,
91    WriteContact, begin_immediate, open, open_with, validate_database_file,
92};
93/// The connection type [`open`] returns, so callers can name it without
94/// taking their own direct `rusqlite` dependency.
95pub use rusqlite::Connection;