prolly-store-sqlite
SQLite storage adapter for prolly-map.
It stores content-addressed nodes, traversal hints, and named roots in one
portable SQLite database.
When to use it
Use this adapter for desktop software, CLIs, local services, durable tests, and single-host applications that value a self-contained database file and SQLite tooling. Choose RocksDB for an embedded key-value engine tuned for sustained write workloads. Choose a remote adapter when multiple hosts must share the same store.
Installation
[]
= "0.5.1"
= "0.3.3"
The crate enables rusqlite's bundled SQLite build, so a system SQLite library
is not required.
Quick start
use ;
use SqliteStore;
let store = open_in_memory?;
let prolly = new;
let tree = prolly.put?;
prolly.publish_named_root?;
let loaded = prolly.load_named_root?.expect;
assert_eq!;
Ok::
Use SqliteStore::open("./data/app.prolly.sqlite") for persistence. Reopen the
same file in a later process and load a published named root to recover the
tree handle.
Schema and configuration
open, open_with_config, and open_in_memory create these tables if needed:
prolly_nodes(cid, encoding, node)stores nodes by 32-byte CID, using raw or LZ4-encoded payloads.prolly_hints(namespace, key, value)stores optional traversal hints.prolly_roots(name, manifest)stores named root manifests.
SqliteStoreConfig controls the busy timeout, WAL journaling, SQLite's
synchronous=NORMAL setting, database page size, page cache, WAL checkpoint
interval, the decoded-node read cache, the minimum node size considered for
compression, the maximum memory-mapped read window, the read-connection pool,
adaptive multi-key reads, background checkpoints, and optional group commit.
Defaults are a 5-second busy timeout, WAL for file-backed databases,
synchronous=NORMAL, 64 KiB database pages, a 64 MiB page cache, a
128 MiB sharded decoded-node cache, a 256 MiB mapping, four thread-affine read
connections, and a dedicated passive checkpoint worker. The worker begins
checkpointing after 64 MiB of WAL allocation and caps retained WAL
allocation at 256 MiB. Nodes of at least 8 KiB are compressed when LZ4 makes
them smaller; typical compact nodes remain raw to avoid spending more CPU than
the write saves. Group commit remains disabled unless
group_commit_delay_micros is set.
use ;
let store = open_with_config?;
Ok::
Use a separate database file for physical tenant or environment isolation.
Named-root prefixes such as tenant/42/main organize roots logically, while
content-addressed nodes may be shared by multiple roots in the file.
Opening existing databases safely
On Unix, SqliteStore::open_existing opens an existing database without
creating the file or running schema DDL. The caller is responsible for
validating that the required tables exist.
On Unix, open_existing_verified exposes the device, inode, and length of
SQLite's actual open main-database file descriptor to a verifier before any
pragma or SQL statement runs. It is useful when a caller must prove that SQLite
opened an expected file. Both existing-only entry points fail closed on
non-Unix platforms.
Transactions and named roots
The adapter implements Store, ManifestStore, scanning, and
TransactionalStore. SQLite transactions atomically validate named-root
preconditions and apply node and root writes. A stale precondition returns a
conflict without committing staged changes.
File-backed stores use one serialized writer and a pool of read-only
connections. By default, the first reading thread reuses the writer connection
to preserve SQLite's single-thread page and statement cache fast path. Other
calling threads remain affine to one reader, allowing independent threads to
read in parallel with WAL publication. Set primary_reader_uses_writer to
false when every application thread should use the read-only pool. In-memory
and caller-supplied connections retain the single-connection behavior.
Set group_commit_delay_micros to a small non-zero window when many threads
publish independent immutable-node batches. Concurrent publications collected
inside that window share one durable SQLite transaction; every caller returns
only after that transaction commits. Root compare-and-swap transactions remain
strictly serialized and are never merged implicitly.
Operations and maintenance
metrics() reports decoded-cache activity, publication and transaction counts,
compression savings, checkpoint activity, and SQLite pager hit, miss, write,
and spill counters. storage_stats() reports page, freelist, database, and WAL
frame sizes.
Use checkpoint for an explicit passive, full, restart, or truncate checkpoint.
The default background worker keeps checkpoint synchronization out of the
writer's commit path. quick_check provides a fast structural health check,
backup_to creates a consistent online backup, optimize runs bounded SQLite
planner maintenance, and compact runs an explicit blocking VACUUM after the
application has quiesced traffic.
Tree reachability and garbage collection are provided by prolly-map:
plan_store_gc_for_retention performs the dry run and
sweep_store_gc_for_retention deletes unreachable nodes through one SQLite
batch transaction. Inspect storage_stats().free_bytes after sweeping and run
compact during an appropriate maintenance window when filesystem reclamation
is needed.
Deleting a named root does not immediately delete unreachable nodes. Plan retention and garbage collection before pruning content-addressed data.
Durable semantic RAG example
examples/semantic_rag.rs
builds a native ProximityMap over six support-documentation chunks, publishes
its descriptor as the SQLite named root rag/corpus/main, and performs exact
cosine retrieval. Run it twice against the same database to see initial
construction followed by a process-independent reopen:
The example prints three ranked chunks with stable keys and source citations,
then renders an offline <context> block ready to pass to an LLM. It does not
call an embedding or generation service.
The checked-in fixture uses synthetic, precomputed, normalized vectors with
1,536 dimensions. Replace embedding_for_query and corpus ingestion with the
embedding provider used by your application; keep the model identifier and
dimensions in named-root metadata so incompatible indexes fail closed on
reopen.
Testing
The default suite is credential-free and exercises in-memory and file-backed storage, reopen behavior, transactions, scanning, and indexed maps:
See the prolly-map API documentation for map,
transaction, diff, merge, and proximity-map operations available with this
store.