Skip to main content

uqa_storage/
key_value.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Backend-neutral Key/Value storage.
8//!
9//! This module is the logical storage boundary for non-relational
10//! persistence. Concrete stores only need ordered byte keys, atomic
11//! batches, prefix scans, and transaction hooks. Catalog, document,
12//! inverted-index, and vector-index behavior stays above that boundary.
13
14use std::collections::{BTreeMap, BTreeSet};
15use std::sync::Arc;
16
17use parking_lot::Mutex;
18use serde::{Deserialize, Serialize};
19use uqa_analysis::Analyzer;
20use uqa_core::{DocId, FieldName, IndexStats, Payload, PostingEntry, PostingList, Value};
21
22use crate::backend::{PersistentStorageBackend, PersistentStorageIdentity};
23use crate::document_store::{Document, DocumentStore};
24use crate::inverted_index::{AnalyzerPhase, InvertedIndex};
25use crate::vector_index::{
26    validate_vector_values, VectorIndex, VectorIndexOpenMode, VectorIndexSpec,
27};
28use crate::{StorageBackendError, StorageBackendResult};
29
30mod catalog;
31pub use catalog::KeyValueCatalog;
32mod diskann;
33pub use diskann::identifiers as diskann_identifiers;
34pub use diskann::{
35    publication, DiskANNMaintenanceStatus, DiskANNMaintenanceStep, DiskANNStageStatus,
36    KeyValueDiskANNMaintenance, KeyValueDiskANNMappingMaintenance, KeyValueDiskANNPruner,
37    KeyValueDiskANNSource, KeyValueDiskANNStage, KeyValueDiskANNStore,
38};
39mod graph_commit;
40mod table_owners;
41pub use graph_commit::KeyValueGraphRecords;
42mod hnsw_records;
43mod index_view;
44mod ivf_records;
45mod maintenance_records;
46mod notification_records;
47pub use notification_records::KeyValueNotificationRecords;
48pub(crate) mod record_json;
49pub use maintenance_records::KeyValueMaintenanceRecords;
50mod vector_records;
51pub use hnsw_records::KeyValueHNSWRecords;
52pub use ivf_records::KeyValueIVFRecords;
53pub(crate) mod occurrence_records;
54pub use occurrence_records::KeyValueOccurrenceRecords;
55mod view;
56pub use view::{
57    KeyPresenceVisitor, KeyValueMutation, KeyValuePointVisitor, KeyValueRead,
58    KeyValueReadKeyIterator, KeyValueReadRevision, KeyValueReadScope, KeyValueVersionedMutation,
59};
60
61const TAG_METADATA: u8 = b'm';
62const TAG_TABLE: u8 = b't';
63const TAG_MODEL: u8 = b'M';
64const TAG_SCORING_PARAMS: u8 = b'S';
65const TAG_NAMED_GRAPH: u8 = b'g';
66const TAG_VERTEX: u8 = b'V';
67const TAG_EDGE: u8 = b'E';
68const TAG_GRAPH_MEMBERSHIP: u8 = b'G';
69const TAG_GRAPH_LOOKUP: u8 = b'J';
70const TAG_ANALYZER: u8 = b'a';
71const TAG_ANALYZER_DESCRIPTOR: u8 = b'D';
72const TAG_FIELD_ANALYZER_BINDING: u8 = b'U';
73const TAG_TABLE_FIELD_ANALYZER: u8 = b'A';
74const TAG_FOREIGN_SERVER: u8 = b'F';
75const TAG_FOREIGN_TABLE: u8 = b'T';
76const TAG_CATALOG_INDEX: u8 = b'C';
77const TAG_PATH_INDEX: u8 = b'P';
78const TAG_PATH_INDEX_DATA: u8 = b'Q';
79const TAG_COLUMN_STATS: u8 = b'c';
80const TAG_SCHEMA: u8 = b's';
81const TAG_SEQUENCE: u8 = b'q';
82/// Value records of sequence definition generations, keyed by object identity and generation.
83const TAG_SEQUENCE_VALUE: u8 = b'L';
84const TAG_RELATION: u8 = b'R';
85const TAG_VIEW: u8 = b'w';
86const TAG_DOCUMENT: u8 = b'd';
87const TAG_POSTING: u8 = b'p';
88const TAG_OCCURRENCE_INDEX: u8 = b'e';
89const TAG_POSTING_CLUSTER_SCORE: u8 = b'k';
90const TAG_POSTING_CLUSTER_POSITIONS: u8 = b'o';
91const TAG_POSTING_DOCUMENT: u8 = b'x';
92const TAG_DOC_LENGTH: u8 = b'l';
93const TAG_FIELD_STATS: u8 = b'f';
94const TAG_REVERSE_POSTING: u8 = b'r';
95const TAG_VECTOR: u8 = b'v';
96const TAG_BTREE_INDEX: u8 = b'B';
97const TAG_BTREE_ENTRY: u8 = b'b';
98const TAG_NAMED_BTREE_INDEX: u8 = b'N';
99const TAG_NAMED_BTREE_ENTRY: u8 = b'n';
100const TAG_IVF_METADATA: u8 = b'I';
101const TAG_IVF_CENTROID: u8 = b'i';
102const TAG_IVF_ASSIGNMENT: u8 = b'j';
103const TAG_HNSW_METADATA: u8 = b'H';
104const TAG_HNSW_NODE: u8 = b'h';
105
106/// Prefix for the unambiguous document encoding introduced after JSON arrays
107/// became ordinary [`Value::List`] values. A legacy document is plain JSON and
108/// therefore cannot start with NUL; the prefix lets reads preserve the old
109/// `Bytes`-before-`List` interpretation without misreading newly written lists.
110const DOCUMENT_VALUE_V1_PREFIX: &[u8] = b"\0uqa-document-json-v1\0";
111const DOCUMENT_VALUE_V2_PREFIX: &[u8] = b"\0uqa-document-record-v2\0";
112
113#[derive(Debug, Clone)]
114enum KeyValueBatchOperation {
115    Put(Vec<u8>, Vec<u8>),
116    Delete(Vec<u8>),
117    DeletePrefix(Vec<u8>),
118}
119
120/// Atomic mutation buffer for a [`KeyValueStore`].
121pub trait KeyValueBatch {
122    /// Original SSI participant captured with this evaluated mutation. Wrappers must forward this capability; a batch cannot move to another participant before publication.
123    fn serializable_participant(&self) -> Option<crate::mvcc::SerializableTransactionId> {
124        None
125    }
126    /// Stage a logical write with the evaluated records. Failed application discards its new intents and records together while retaining already established dependencies. Providers without this capability reject the request.
127    fn observe_serializable_write(
128        &mut self,
129        _predicate: crate::mvcc::SerializablePredicate<'_>,
130    ) -> StorageBackendResult<()> {
131        Err(StorageBackendError::Other(
132            "atomic serializable write observations are not supported".into(),
133        ))
134    }
135    /// Write a record at a key that never had one, as the caller has established. Such a key has no committed revision, so the write expects none without reading one, and a record that exists after all fails the commit as a write conflict instead of being replaced. The default reads the revision as `put` does; capable wrappers must forward this method to keep the write blind.
136    fn put_unused(&mut self, key: &[u8], value: &[u8]) -> StorageBackendResult<()> {
137        self.put(key, value)
138    }
139    /// Require this record's original committed revision at publication without replacing it. This permits independent data writers to share a definition. Stores without commit-time read validation reject this operation; capable wrappers must forward it.
140    fn require_unchanged(&mut self, _key: &[u8]) -> StorageBackendResult<()> {
141        Err(StorageBackendError::Other(
142            "commit-time record requirements are not supported".into(),
143        ))
144    }
145    /// Require an actual committed record observed through an independently retained metadata view, without advancing the transaction's data snapshot. The revision must identify this same key in this database; private and unversioned provenance is rejected. Reserved for storage-owned independently staged resources, not SQL row writes.
146    fn require_observed(
147        &mut self,
148        _key: &[u8],
149        _revision: &KeyValueReadRevision,
150    ) -> StorageBackendResult<()> {
151        Err(StorageBackendError::Other(
152            "observed metadata requirements are not supported".into(),
153        ))
154    }
155    /// Replace an independently staged metadata record using its observed committed precondition. Existing private changes to the key are rejected. Commit conflict, savepoint and receipt semantics are the same as other evaluated writes; this does not refresh the caller's snapshot.
156    fn put_observed(
157        &mut self,
158        _key: &[u8],
159        _value: &[u8],
160        _revision: &KeyValueReadRevision,
161    ) -> StorageBackendResult<()> {
162        Err(StorageBackendError::Other(
163            "observed metadata replacements are not supported".into(),
164        ))
165    }
166    /// Replace metadata and retain its independently staged read source in the same evaluated private write. The source follows savepoint undo, retained views and original receipt resolution; replacing or deleting the key releases it from the new view. Only the bytes are persisted. Capable wrappers must forward the source unchanged.
167    fn put_with_retained_source(
168        &mut self,
169        _key: &[u8],
170        _value: &[u8],
171        _source: Arc<dyn KeyValueRead + Send + Sync>,
172    ) -> StorageBackendResult<()> {
173        Err(StorageBackendError::Other(
174            "private metadata read sources are not supported".into(),
175        ))
176    }
177    /// Publish a new revision of an immutable marker, merging concurrent touches of the same value. Structural changes fence the marker and replace their definition; data changes require the definition and touch the marker. The payload must contain only immutable owner/format data. Capable wrappers must forward this operation.
178    fn touch_marker(&mut self, _key: &[u8], _value: &[u8]) -> StorageBackendResult<()> {
179        Err(StorageBackendError::Other(
180            "mergeable revision markers are not supported".into(),
181        ))
182    }
183
184    /// Stage an actual canonical origin with its evaluated complete tensor. Its original write precondition remains strict; common MVCC maintains all selected-generation populations without replaying the mutation. Capable wrappers must forward this operation.
185    fn replace_diskann_origin(&mut self, _key: &[u8], _value: &[u8]) -> StorageBackendResult<()> {
186        Err(StorageBackendError::Other(
187            "DiskANN population mutations are not supported".into(),
188        ))
189    }
190
191    /// Remove the canonical origin invalidated by a raw writer. Concurrent stores reject an existing population unless their complete publication census validates the final canonical view. Validation follows private application, command refresh and final publication, including an originally absent origin. Capable wrappers must forward this operation.
192    fn invalidate_diskann_origin(&mut self, key: &[u8]) -> StorageBackendResult<()> {
193        self.delete(key)
194    }
195
196    /// Retain already verified complete build origins for one authorized generation publication. The template supplies physical row encoding, not trusted counts; common MVCC computes the private and committed census on their actual canonical views. The catalog/head owner must authorize and guard this publication in the same batch.
197    fn publish_diskann_population(
198        &mut self,
199        _key: &[u8],
200        _template: &[u8],
201        _origins: crate::diskann_index::pages::DiskANNOriginReader,
202    ) -> StorageBackendResult<()> {
203        Err(StorageBackendError::Other(
204            "DiskANN population publication is not supported".into(),
205        ))
206    }
207
208    /// Retire a generation's population header and witnesses, including witnesses added by intervening disjoint writers. The generation/head owner must retain its original publication guards. This operation follows private undo and original receipt resolution.
209    fn retire_diskann_population(&mut self, _key: &[u8]) -> StorageBackendResult<()> {
210        Err(StorageBackendError::Other(
211            "DiskANN population retirement is not supported".into(),
212        ))
213    }
214    /// Stage a durable identifier observation before publishing this batch's records. Successful observations survive later transaction/savepoint rollback. Dropping an unevaluated batch consumes nothing. Stores without autonomous allocation reject this operation; capable wrappers must forward it.
215    fn observe_identifier(&mut self, _namespace: &[u8], _value: u64) -> StorageBackendResult<()> {
216        Err(StorageBackendError::Other(
217            "durable identifier observations are not supported".into(),
218        ))
219    }
220    /// Carry the source's durable identifier watermark into the target before this batch publishes its records. Successful watermark updates survive undo, just like observations. Owning lifecycle code must coordinate the accompanying definition change with data writers. Capable wrappers must forward this namespace operation.
221    fn inherit_identifiers(&mut self, _from: &[u8], _to: &[u8]) -> StorageBackendResult<()> {
222        Err(StorageBackendError::Other(
223            "durable identifier inheritance is not supported".into(),
224        ))
225    }
226    fn put(&mut self, key: &[u8], value: &[u8]) -> StorageBackendResult<()>;
227    fn delete(&mut self, key: &[u8]) -> StorageBackendResult<()>;
228    fn delete_prefix(&mut self, prefix: &[u8]) -> StorageBackendResult<()>;
229    /// Delete only the keys selected by this evaluated prefix, accepting concurrent deletion of those keys. A concurrent live replacement still conflicts, and explicit read requirements remain strict. Reserved for obsolete internal records; callers must separately fence structural changes and new insertions. Concurrent wrappers must forward this operation.
230    fn delete_prefix_allow_absent(&mut self, prefix: &[u8]) -> StorageBackendResult<()> {
231        self.delete_prefix(prefix)
232    }
233    /// Retain already evaluated document input in the same atomic batch as its canonical values and IVF preview. Concurrent wrappers must forward this call.
234    fn ivf_mutation(
235        &mut self,
236        _metadata: &[u8],
237        _mutation: crate::ivf_index::IVFMutation<'_>,
238    ) -> StorageBackendResult<()> {
239        Ok(())
240    }
241    /// The immutable preview serves private reads; concurrent commit preparation may replace it with an internally recalculated generation.
242    fn preview_ivf_record(&mut self, key: &[u8], value: Option<&[u8]>) -> StorageBackendResult<()> {
243        match value {
244            Some(value) => self.put(key, value),
245            None => self.delete(key),
246        }
247    }
248    fn preview_ivf_prefix(&mut self, prefix: &[u8]) -> StorageBackendResult<()> {
249        self.delete_prefix(prefix)
250    }
251    /// Fence each existing IVF definition under this metadata prefix, including definitions with no vectors. Serialized stores already exclude competing writers.
252    fn fence_ivf_prefix(&mut self, _prefix: &[u8]) -> StorageBackendResult<()> {
253        Ok(())
254    }
255    /// Retain already evaluated document input in the same atomic batch as its canonical values and HNSW preview. Concurrent wrappers must forward this call.
256    fn hnsw_mutation(
257        &mut self,
258        _metadata: &[u8],
259        _mutation: crate::hnsw_index::HNSWMutation<'_>,
260    ) -> StorageBackendResult<()> {
261        Ok(())
262    }
263    /// The immutable preview serves private reads; concurrent commit preparation may replace it with an internally recalculated generation.
264    fn preview_hnsw_record(
265        &mut self,
266        key: &[u8],
267        value: Option<&[u8]>,
268    ) -> StorageBackendResult<()> {
269        match value {
270            Some(value) => self.put(key, value),
271            None => self.delete(key),
272        }
273    }
274    fn preview_hnsw_prefix(&mut self, prefix: &[u8]) -> StorageBackendResult<()> {
275        self.delete_prefix(prefix)
276    }
277    /// Fence each existing HNSW definition under this metadata prefix, including definitions with no vectors. Serialized stores already exclude competing writers.
278    fn fence_hnsw_prefix(&mut self, _prefix: &[u8]) -> StorageBackendResult<()> {
279        Ok(())
280    }
281    /// Stage an evaluated occurrence cluster, field total or source marker using the persistence's declared record layout. Source mutations must include their format marker and document guards. Concurrent stores merge only these explicitly typed replacements; ordinary byte writes remain conditional replacements.
282    fn replace_occurrence_record(
283        &mut self,
284        key: &[u8],
285        value: Option<&[u8]>,
286    ) -> StorageBackendResult<()> {
287        match value {
288            Some(value) => self.put(key, value),
289            None => self.delete(key),
290        }
291    }
292    /// Stage source-owned occurrence-cache invalidation. The source marker in the same batch also drives discovery of caches published after the retained source view.
293    fn invalidate_occurrence_prefix(&mut self, prefix: &[u8]) -> StorageBackendResult<()> {
294        self.delete_prefix(prefix)
295    }
296    /// Protect whole-document replacement even when two writers select different fields of an originally absent document.
297    fn occurrence_document(&mut self, _table: &str, _document: DocId) -> StorageBackendResult<()> {
298        Ok(())
299    }
300    /// Fence a structural occurrence change, including an empty rebuild, drop, purge or rename. Serialized projections already retain their native mutation boundary.
301    fn reset_occurrences(&mut self, _table: &str) -> StorageBackendResult<()> {
302        Ok(())
303    }
304    /// Retain the original precondition and current value of a record, including an absent tombstone, before later replacements. Concurrent wrappers must forward this call; serialized stores already exclude intervening writers.
305    fn fence_record(&mut self, _key: &[u8]) -> StorageBackendResult<()> {
306        Ok(())
307    }
308    /// Stage an evaluated statistics-maintenance replacement using the provider's declared layout. Keep ordinary metadata writes canonical.
309    fn replace_statistics_maintenance(
310        &mut self,
311        key: &[u8],
312        value: &[u8],
313    ) -> StorageBackendResult<()> {
314        self.put(key, value)
315    }
316    /// Record graph-cache dependencies in the same atomic batch. Concurrent MVCC stores must resolve these logical effects before admitting a commit; serialized legacy stores use the ordinary preview writes already included by the catalog.
317    fn graph_mutation(
318        &mut self,
319        _mutation: crate::mvcc::GraphMutation<'_>,
320    ) -> StorageBackendResult<()> {
321        Ok(())
322    }
323    /// Maintain read-your-writes for a graph invalidation. MVCC replaces this preview with effects selected from current graph ownership at commit.
324    fn preview_graph_invalidation(
325        &mut self,
326        key: &[u8],
327        value: Option<&[u8]>,
328    ) -> StorageBackendResult<()> {
329        match value {
330            Some(value) => self.put(key, value),
331            None => self.delete(key),
332        }
333    }
334    /// Replace or clear derived graph cache state. Publishing a completed build must also record `GraphMutation::PublishPath` so its source dependencies are validated.
335    fn replace_graph_cache(
336        &mut self,
337        key: &[u8],
338        value: Option<&[u8]>,
339    ) -> StorageBackendResult<()> {
340        match value {
341            Some(value) => self.put(key, value),
342            None => self.delete(key),
343        }
344    }
345    fn commit(self: Box<Self>) -> StorageBackendResult<()>;
346}
347
348pub use vector_index::guards::KeyValueVectorFieldGuards;
349
350pub(crate) fn diskann_tombstone_prefixes() -> [&'static [u8]; 5] {
351    [
352        diskann::READ_PREFIX,
353        vector_index::origin::ROOT,
354        vector_index::origin::journal::ROOT,
355        vector_index::guards::ROOT,
356        vector_index::origin::populations::ROOT,
357    ]
358}
359
360/// Ordered byte-key storage used by Key/Value catalog and index backends.
361pub trait KeyValueStore: Send + Sync {
362    /// Physical resource ownership for retained builds and recovery. Versioned wrappers must forward this capability.
363    fn resource_leases(&self) -> Option<&dyn crate::mvcc::ResourceLeaseProvider> {
364        None
365    }
366
367    /// Credential for database-owned auxiliary files. Wrappers over an encrypted store must forward it; `None` permits unencrypted auxiliary storage.
368    fn auxiliary_encryption_key(&self) -> Option<crate::StorageEncryptionKey> {
369        None
370    }
371
372    /// Shared session allowance, including resources retained by readers after their producer finishes. Versioned wrappers must forward this capability.
373    fn retention_control(&self) -> Option<crate::read_control::StorageReadControl> {
374        None
375    }
376
377    /// Atomic auxiliary notification publication and fresh committed recovery. Session wrappers must forward this capability.
378    fn notification_publications(
379        &self,
380    ) -> Option<&dyn crate::notifications::NotificationPublicationStore> {
381        None
382    }
383
384    /// Original participant admission and logical observations. Wrappers must forward this capability, including original attribution on retained read-only sessions; capability presence does not establish complete SQL SSI support.
385    fn serializable_session(&self) -> Option<&dyn crate::mvcc::SerializableSession> {
386        None
387    }
388
389    /// Reclaim obsolete records and committed history without a whole-file rewrite. Versioned wrappers must forward maintenance; serialized stores that eagerly remove obsolete values may keep the default.
390    fn reclaim_obsolete(&self) -> StorageBackendResult<()> {
391        if self.transaction_model().is_versioned() {
392            return Err(StorageBackendError::Other(
393                "versioned KeyValue maintenance is not implemented by this store".into(),
394            ));
395        }
396        Ok(())
397    }
398
399    /// Reclaim obsolete records and compact physical storage where supported, outside this session's transaction.
400    fn vacuum(&self) -> StorageBackendResult<()> {
401        self.reclaim_obsolete()
402    }
403
404    /// Token shared with the execution that owns this session's writes. Cancellation must not prevent rollback cleanup or diagnostic reads. Versioned wrappers must forward this capability.
405    fn write_cancellation(&self) -> Option<uqa_core::CancellationToken> {
406        None
407    }
408
409    /// Open an independent writer sharing the invoking maintenance operation's allowance and write cancellation. Retained reads and cleanup keep independent cancellation. Capable wrappers must forward this without granting another memory limit.
410    fn open_controlled_session(
411        &self,
412        _control: &crate::read_control::StorageReadControl,
413    ) -> StorageBackendResult<Arc<dyn KeyValueStore>> {
414        Err(StorageBackendError::Other(
415            "controlled independent sessions are not implemented for this store".into(),
416        ))
417    }
418
419    /// Create a transaction-isolated session with the caller's write cancellation and a fresh retention budget.
420    fn open_session_with_cancellation(
421        &self,
422        cancellation: &uqa_core::CancellationToken,
423    ) -> StorageBackendResult<Arc<dyn KeyValueStore>> {
424        cancellation.check()?;
425        if self.transaction_model().is_versioned() {
426            return Err(StorageBackendError::Other(
427                "cancellable independent KeyValue sessions are not implemented by this store"
428                    .into(),
429            ));
430        }
431        self.open_session()
432    }
433
434    /// Retain this exact committed/private view in a separate read-only session, including its original logical reader attribution. The returned session cannot publish records or complete the source transaction. Versioned wrappers must forward this capability; opening a newer independent snapshot is not equivalent.
435    fn open_retained_read_session(
436        &self,
437        cancellation: &uqa_core::CancellationToken,
438    ) -> StorageBackendResult<Arc<dyn KeyValueStore>> {
439        cancellation.check()?;
440        Err(StorageBackendError::Other(
441            "retained read sessions are not implemented by this KeyValue store".into(),
442        ))
443    }
444
445    /// Whether writes remain private while independent sessions read and write. Versioned stores must provide command refresh, retained reads, savepoint undo and conditional publication under one reported affinity.
446    fn transaction_model(&self) -> crate::StorageTransactionModel {
447        crate::StorageTransactionModel::ProviderSerialized
448    }
449
450    /// Durable reservations outside private record undo. Serialized custom stores may omit this capability; wrappers over a capable store must forward it.
451    fn identifier_allocator(&self) -> Option<&dyn crate::mvcc::IdentifierAllocator> {
452        None
453    }
454
455    /// Evaluate a compound read once against one fixed committed/private view. The callback must use the supplied reader and must not reenter this session. Stores without this capability reject it explicitly.
456    fn with_read_view(&self, _read: &mut KeyValueReadScope<'_>) -> StorageBackendResult<()> {
457        Err(StorageBackendError::Other(
458            "compound KeyValue reads are not supported by this store".into(),
459        ))
460    }
461
462    /// Evaluate once against the same view that supplies write preconditions, then atomically stage the batch. Errors and unwinds discard that batch; failed publication retains the evaluated attempt. The callback must not reenter this session or perform transaction control.
463    fn with_mutation(&self, _mutate: &mut KeyValueMutation<'_>) -> StorageBackendResult<()> {
464        Err(StorageBackendError::Other(
465            "atomic KeyValue evaluation is not supported by this store".into(),
466        ))
467    }
468
469    /// Stage once and optionally certify the resulting prefix identity before another mutation can intervene. The identity may name private state before a later commit. A provider without this capability performs the ordinary mutation and returns no certificate; opening a later read view cannot certify this mutation's result.
470    fn with_mutation_revision(
471        &self,
472        _prefixes: &[&[u8]],
473        mutate: &mut KeyValueMutation<'_>,
474    ) -> StorageBackendResult<Option<KeyValueReadRevision>> {
475        self.with_mutation(mutate)?;
476        Ok(None)
477    }
478
479    /// Evaluate and stage once with the actual durable writer and a non-reused mutation revision. Origins may be persisted alongside canonical values, but do not prove publication or visibility. The callback has the same non-reentrancy rules as `with_mutation`; capable wrappers must preserve the original origin and atomic batch.
480    fn with_versioned_mutation(
481        &self,
482        _mutate: &mut KeyValueVersionedMutation<'_>,
483    ) -> StorageBackendResult<()> {
484        Err(StorageBackendError::Other(
485            "versioned KeyValue mutation origins are not supported by this store".into(),
486        ))
487    }
488
489    /// Identity of the transaction context shared by this store's catalog and data handles. Independent sessions must report different identities, even over the same file.
490    fn transaction_affinity(&self) -> Option<crate::StorageSessionAffinity> {
491        None
492    }
493
494    fn storage_identity(&self) -> StorageBackendResult<Option<PersistentStorageIdentity>> {
495        Ok(None)
496    }
497
498    /// Open an independent transaction session over the same logical store. The default keeps simple test/custom stores source-compatible while making the missing MVCC capability explicit when a persistent engine needs a committed reader alongside a pinned statement snapshot.
499    fn open_session(&self) -> StorageBackendResult<Arc<dyn KeyValueStore>> {
500        Err(StorageBackendError::Other(
501            "independent sessions are not implemented for this KeyValue store".into(),
502        ))
503    }
504
505    fn get(&self, key: &[u8]) -> StorageBackendResult<Option<Vec<u8>>>;
506    /// Visit one borrowed value under the retained read. Providers reserve any temporary encoded payload before fetching it; callbacks must not reenter the store.
507    fn visit_value(
508        &self,
509        _key: &[u8],
510        control: &crate::read_control::StorageReadControl,
511        _visit: &mut crate::read_control::ValueReadVisitor<'_>,
512    ) -> StorageBackendResult<()> {
513        control.check()?;
514        Err(StorageBackendError::Other(
515            "controlled value reads are not supported by this KeyValue store".into(),
516        ))
517    }
518    /// Visit one value with an encoded-size cap checked before provider materialization. This is distinct from the shared workspace allowance; unsupported stores must not fetch an unbounded value as a fallback.
519    fn visit_value_bounded(
520        &self,
521        _key: &[u8],
522        _max_bytes: usize,
523        control: &crate::read_control::StorageReadControl,
524        _visit: &mut crate::read_control::ValueReadVisitor<'_>,
525    ) -> StorageBackendResult<()> {
526        control.check()?;
527        Err(StorageBackendError::Other(
528            "size-bounded value reads are not supported by this KeyValue store".into(),
529        ))
530    }
531
532    /// Visit at most `limit` entries in key order, strictly after `after` when supplied. Encoded values remain borrowed from their provider owner and callbacks must not reenter the store.
533    fn visit_prefix_after(
534        &self,
535        _prefix: &[u8],
536        _after: Option<&[u8]>,
537        _limit: usize,
538        control: &crate::read_control::StorageReadControl,
539        _visit: &mut crate::read_control::KeyValueReadVisitor<'_>,
540    ) -> StorageBackendResult<()> {
541        control.check()?;
542        Err(StorageBackendError::Other(
543            "controlled prefix reads are not supported by this KeyValue store".into(),
544        ))
545    }
546    /// Test prefix existence under the read allowance. Providers that materialize values should implement a key-only probe.
547    fn contains_prefix_budgeted(
548        &self,
549        prefix: &[u8],
550        control: &crate::read_control::StorageReadControl,
551    ) -> StorageBackendResult<bool> {
552        let mut found = false;
553        self.visit_prefix_after(prefix, None, 1, control, &mut |_, _| {
554            found = true;
555            Ok(())
556        })?;
557        control.check()?;
558        Ok(found)
559    }
560
561    fn contains_key(&self, key: &[u8]) -> StorageBackendResult<bool> {
562        self.get(key).map(|value| value.is_some())
563    }
564    fn put(&self, key: &[u8], value: &[u8]) -> StorageBackendResult<()>;
565    fn delete(&self, key: &[u8]) -> StorageBackendResult<()>;
566    fn scan_prefix(&self, prefix: &[u8]) -> StorageBackendResult<Vec<(Vec<u8>, Vec<u8>)>>;
567    /// Return at most `limit` key/value pairs in key order, strictly after
568    /// `after` when supplied. This is the bounded value cursor used by large
569    /// format migrations and other paged consumers.
570    fn scan_prefix_after(
571        &self,
572        prefix: &[u8],
573        after: Option<&[u8]>,
574        limit: usize,
575    ) -> StorageBackendResult<Vec<(Vec<u8>, Vec<u8>)>> {
576        if limit == 0 {
577            return Ok(Vec::new());
578        }
579        Ok(self
580            .scan_prefix(prefix)?
581            .into_iter()
582            .filter(|(key, _)| after.is_none_or(|after| key.as_slice() > after))
583            .take(limit)
584            .collect())
585    }
586    /// Return at most `limit` keys in key order, strictly after `after` when
587    /// it is present. Backends should override this method with a key-only,
588    /// bounded range scan so cursor consumers neither materialize the entire
589    /// prefix nor read values they do not need on every page.
590    fn scan_prefix_keys_after(
591        &self,
592        prefix: &[u8],
593        after: Option<&[u8]>,
594        limit: usize,
595    ) -> StorageBackendResult<Vec<Vec<u8>>> {
596        if limit == 0 {
597            return Ok(Vec::new());
598        }
599        Ok(self
600            .scan_prefix(prefix)?
601            .into_iter()
602            .filter(|(key, _)| after.is_none_or(|after| key.as_slice() > after))
603            .take(limit)
604            .map(|(key, _)| key)
605            .collect())
606    }
607    fn first_prefix_after(
608        &self,
609        prefix: &[u8],
610        after: Option<&[u8]>,
611    ) -> StorageBackendResult<Option<(Vec<u8>, Vec<u8>)>> {
612        Ok(self
613            .scan_prefix(prefix)?
614            .into_iter()
615            .find(|(key, _)| after.is_none_or(|after| key.as_slice() > after)))
616    }
617    fn delete_prefix(&self, prefix: &[u8]) -> StorageBackendResult<usize>;
618    fn batch(&self) -> Box<dyn KeyValueBatch + '_>;
619
620    fn begin_transaction(&self) -> StorageBackendResult<()> {
621        Err(StorageBackendError::Other(
622            "KeyValue transaction begin is not implemented for this store".into(),
623        ))
624    }
625
626    fn begin_read_transaction(&self) -> StorageBackendResult<()> {
627        self.begin_transaction()
628    }
629
630    fn begin_upgradeable_transaction(&self) -> StorageBackendResult<()> {
631        self.begin_transaction()
632    }
633
634    fn in_transaction(&self) -> bool;
635
636    /// Report ordinary record and derived-index writes, excluding typed auxiliary notification effects. Ordinary writes remain forbidden in read-only transactions.
637    fn transaction_has_written(&self) -> StorageBackendResult<bool>;
638
639    fn change_version(&self) -> StorageBackendResult<Option<u64>> {
640        Ok(None)
641    }
642
643    /// Complete committed/private visibility identity, including savepoint undo. `None` requires consumers to use their existing refresh path. Tokens are process-local and do not replace transaction isolation or logical read observations.
644    fn read_view_revision(&self) -> StorageBackendResult<Option<KeyValueReadRevision>> {
645        Ok(None)
646    }
647
648    /// Receipt of this session's last successfully completed write transaction. This is not the latest database commit. A new transaction, rollback or read-only completion clears it; unsupported stores return `None`.
649    fn completed_commit(&self) -> Option<crate::mvcc::CommitReceipt> {
650        None
651    }
652
653    fn change_version_monitor_is_nonblocking(&self) -> StorageBackendResult<bool> {
654        Ok(true)
655    }
656
657    fn pin_transaction_snapshot(&self) -> StorageBackendResult<()> {
658        Ok(())
659    }
660
661    /// Advance an active logical transaction's committed view while preserving its private writes and retained readers. The caller chooses command boundaries allowed by SQL isolation. Stores without this capability must reject the request; ending or replaying the caller's transaction is not a valid fallback.
662    fn refresh_transaction_snapshot(
663        &self,
664        _cancellation: &uqa_core::CancellationToken,
665    ) -> StorageBackendResult<()> {
666        Err(StorageBackendError::Other(
667            "transaction snapshot refresh is not supported by this KeyValue store".into(),
668        ))
669    }
670
671    fn commit_transaction(&self) -> StorageBackendResult<()> {
672        Err(StorageBackendError::Other(
673            "KeyValue transaction commit is not implemented for this store".into(),
674        ))
675    }
676
677    fn rollback_transaction(&self) -> StorageBackendResult<()> {
678        Err(StorageBackendError::Other(
679            "KeyValue transaction rollback is not implemented for this store".into(),
680        ))
681    }
682
683    fn savepoint(&self, _name: &str) -> StorageBackendResult<()> {
684        Err(StorageBackendError::Other(
685            "KeyValue savepoints are not implemented for this store".into(),
686        ))
687    }
688
689    fn release_savepoint(&self, _name: &str) -> StorageBackendResult<()> {
690        Err(StorageBackendError::Other(
691            "KeyValue savepoint release is not implemented for this store".into(),
692        ))
693    }
694
695    fn rollback_to_savepoint(&self, _name: &str) -> StorageBackendResult<()> {
696        Err(StorageBackendError::Other(
697            "KeyValue savepoint rollback is not implemented for this store".into(),
698        ))
699    }
700}
701
702mod btree_index;
703mod codec;
704pub mod conformance;
705mod document_store;
706mod hnsw_index;
707mod hnsw_persistence;
708mod index_keys;
709mod inverted_index;
710mod ivf_index;
711mod ivf_persistence;
712mod memory_store;
713pub mod occurrence_format;
714mod occurrence_keys;
715mod storage_backend;
716mod vector_index;
717
718pub use codec::prefix_upper_bound;
719pub use document_store::KeyValueDocumentStore;
720pub use hnsw_index::KeyValueHNSWIndex;
721pub use inverted_index::{KeyValueInvertedIndex, OccurrenceStorage};
722pub use ivf_index::KeyValueIVFIndex;
723pub use memory_store::MemoryKeyValueStore;
724pub use storage_backend::KeyValueStorageBackend;
725pub use vector_index::{
726    DiskANNCanonicalVectorVisitor, KeyValueDiskANNCanonical, KeyValueDiskANNHandle,
727    KeyValueDiskANNPopulationRecords, KeyValueVectorIndex, RetainedDiskANNCanonical,
728};
729
730#[cfg(test)]
731mod tests;