Skip to main content

uqa_storage/
document_store.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Document storage abstraction.
8//!
9//! A `DocumentStore` maps [`DocId`] keys to field maps and supports
10//! field-level access. Implementations include in-memory, provider-owned `SQLite`,
11//! and Key/Value-backed implementations behind the same trait.
12
13use std::collections::BTreeMap;
14use std::sync::Arc;
15
16use uqa_core::{memory::BudgetedVec, DocId, FieldName, PathSegment, Value};
17
18use crate::backend::{StorageBackendError, StorageBackendResult};
19use crate::read_control::StorageReadControl;
20
21mod controlled_ids;
22pub use controlled_ids::read_document_ids;
23mod controlled_field;
24pub use controlled_field::read_selected_field;
25mod controlled_rows;
26pub use controlled_rows::{read_stored_documents, RetainedDocumentPage};
27mod controlled_presence;
28pub use controlled_presence::read_field_presence;
29pub mod decoding;
30pub mod identifiers;
31mod retained;
32pub use retained::{RetainedDocumentFields, RetainedStoredDocument};
33mod retained_store;
34pub use retained_store::{RetainedDocumentStore, RetainedDocumentStoreBuilder};
35
36/// Document field map. Keys are field names; values are dynamic.
37pub type Document = BTreeMap<FieldName, Value>;
38
39/// Storage-owned tuple metadata that must never share the user field namespace.
40#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
41pub struct DocumentMetadata {
42    tuple_xmin: Option<u32>,
43}
44
45impl DocumentMetadata {
46    #[must_use]
47    pub const fn with_tuple_xmin(tuple_xmin: u32) -> Self {
48        Self {
49            tuple_xmin: Some(tuple_xmin),
50        }
51    }
52
53    #[must_use]
54    pub const fn tuple_xmin(self) -> Option<u32> {
55        self.tuple_xmin
56    }
57}
58
59/// One persisted tuple, split into its public fields and storage-owned metadata.
60#[derive(Debug, Clone, Default, PartialEq)]
61pub struct StoredDocument {
62    fields: Document,
63    metadata: DocumentMetadata,
64}
65
66impl StoredDocument {
67    #[must_use]
68    pub fn new(fields: Document) -> Self {
69        Self {
70            fields,
71            metadata: DocumentMetadata::default(),
72        }
73    }
74
75    #[must_use]
76    pub fn with_metadata(fields: Document, metadata: DocumentMetadata) -> Self {
77        Self { fields, metadata }
78    }
79
80    #[must_use]
81    pub fn fields(&self) -> &Document {
82        &self.fields
83    }
84
85    #[must_use]
86    pub fn fields_mut(&mut self) -> &mut Document {
87        &mut self.fields
88    }
89
90    #[must_use]
91    pub fn metadata(&self) -> DocumentMetadata {
92        self.metadata
93    }
94
95    #[must_use]
96    pub fn into_fields(self) -> Document {
97        self.fields
98    }
99
100    #[must_use]
101    pub fn into_parts(self) -> (Document, DocumentMetadata) {
102        (self.fields, self.metadata)
103    }
104}
105
106const MISSING_SHARED_SLOT: usize = usize::MAX;
107static SHARED_NULL_VALUE: Value = Value::Null;
108
109/// A positional projection that shares an in-memory document's stored values.
110///
111/// Persistent backends still decode owned rows through the ordinary bulk
112/// methods. The memory backend exposes this optional representation so a
113/// physical scan can carry storage-owned values through joins without
114/// cloning them.
115#[derive(Debug, Clone, PartialEq)]
116pub struct SharedDocumentRow {
117    values: Arc<Vec<Value>>,
118    projection: Arc<[usize]>,
119}
120
121impl SharedDocumentRow {
122    pub(crate) fn new(values: Arc<Vec<Value>>, projection: Arc<[usize]>) -> Self {
123        debug_assert!(projection
124            .iter()
125            .all(|slot| *slot == MISSING_SHARED_SLOT || *slot < values.len()));
126        Self { values, projection }
127    }
128
129    /// Populate a reusable borrowed projection for predicate evaluation.
130    pub fn project<'a>(&'a self, output: &mut Vec<&'a Value>) {
131        output.clear();
132        output.extend(self.projection.iter().map(|slot| {
133            if *slot == MISSING_SHARED_SLOT {
134                &SHARED_NULL_VALUE
135            } else {
136                &self.values[*slot]
137            }
138        }));
139    }
140
141    /// Borrow the projection through an inline reference array. Relational
142    /// tables normally have far fewer than 32 requested fields, so predicate
143    /// evaluation needs no scratch allocation.
144    pub fn with_projected<R>(&self, visitor: impl FnOnce(&[&Value]) -> R) -> R {
145        const INLINE_FIELDS: usize = 32;
146        if self.projection.len() <= INLINE_FIELDS {
147            let mut projected = [&SHARED_NULL_VALUE; INLINE_FIELDS];
148            for (output, slot) in projected.iter_mut().zip(self.projection.iter()) {
149                if *slot != MISSING_SHARED_SLOT {
150                    *output = &self.values[*slot];
151                }
152            }
153            visitor(&projected[..self.projection.len()])
154        } else {
155            let projected = self
156                .projection
157                .iter()
158                .map(|slot| {
159                    if *slot == MISSING_SHARED_SLOT {
160                        &SHARED_NULL_VALUE
161                    } else {
162                        &self.values[*slot]
163                    }
164                })
165                .collect::<Vec<_>>();
166            visitor(&projected)
167        }
168    }
169
170    /// Borrow the storage-owned values and the projection from requested field positions to value slots. A `usize::MAX` projection slot represents a missing field and therefore SQL NULL.
171    pub fn indexed_values(&self) -> (&[Value], &[usize]) {
172        (&self.values, &self.projection)
173    }
174
175    /// Transfer the shared vector and its fragment-local projection into a
176    /// physical row without cloning either allocation.
177    pub fn into_parts(self) -> (Arc<Vec<Value>>, Arc<[usize]>) {
178        (self.values, self.projection)
179    }
180}
181
182/// Mutating methods are fallible: persistent backends surface their write failures so callers (engine DML, upserts, referential rewrites) can abort the enclosing transaction instead of silently committing a partially-applied statement. A rewrite that deletes a row and then fails to re-insert it must never look like success.
183pub trait DocumentStore: Send + Sync {
184    /// Persist one typed storage record. Every backend owns the physical representation of tuple metadata and must keep it outside the public field map.
185    fn put_stored(&mut self, doc_id: DocId, document: StoredDocument) -> StorageBackendResult<()>;
186
187    /// Persist a document whose identity no document of the table ever had, as the caller has established from the identity watermark of `namespace`. A provider that keys its records by that namespace may write them without reading what they would replace, and only while it stores the table under that namespace; a record that exists after all fails the commit. The default replaces as `put_stored` does.
188    fn put_stored_unused(
189        &mut self,
190        doc_id: DocId,
191        document: StoredDocument,
192        namespace: identifiers::DocumentIdNamespace,
193    ) -> StorageBackendResult<()> {
194        let _ = namespace;
195        self.put_stored(doc_id, document)
196    }
197
198    /// Read one typed storage record without projecting metadata into user fields.
199    fn get_stored(&self, doc_id: DocId) -> StorageBackendResult<Option<StoredDocument>>;
200
201    /// Read one fixed view into a charged page in exactly the requested order, preserving duplicates and missing rows. The page and every decoded field payload retain the invoking allowance. Unsupported providers must not fall back to unbounded owned materialization.
202    fn get_stored_many_controlled(
203        &self,
204        doc_ids: &[DocId],
205        control: &StorageReadControl,
206    ) -> StorageBackendResult<RetainedDocumentPage> {
207        control.check()?;
208        if doc_ids.is_empty() {
209            return Ok(BudgetedVec::new(control.memory()));
210        }
211        Err(StorageBackendError::Other(
212            "controlled whole-document reads are not supported by this store".into(),
213        ))
214    }
215
216    /// Read field existence on one selected view, in document-major and then field order. A stored NULL is present; an absent field or document is not. The returned boolean buffer retains the invoking allowance. The default uses controlled rows without invoking legacy owned materializers; stores can inspect their existing field layout without copying values.
217    fn field_presence_controlled(
218        &self,
219        doc_ids: &[DocId],
220        fields: &[&str],
221        control: &StorageReadControl,
222    ) -> StorageBackendResult<BudgetedVec<bool>> {
223        controlled_presence::from_controlled_rows(self, doc_ids, fields, control)
224    }
225
226    /// Borrow one selected field, preserving absence versus stored NULL. The visitor runs exactly once while the provider keeps the value and its decoding allowance alive. Immutable stores can lend an existing scalar without allocating a projection or presence page.
227    fn with_field_ref_controlled(
228        &self,
229        doc_id: DocId,
230        field: &str,
231        control: &StorageReadControl,
232        visitor: &mut dyn FnMut(Option<&Value>) -> StorageBackendResult<()>,
233    ) -> StorageBackendResult<()> {
234        controlled_field::visit_selected_field(self, doc_id, field, control, visitor)
235    }
236
237    /// Replace public fields while preserving metadata already owned by the stored tuple. Engine code that creates a new tuple version must call [`DocumentStore::put_stored`] with the new metadata explicitly.
238    fn put(&mut self, doc_id: DocId, document: Document) -> StorageBackendResult<()> {
239        let metadata = self.get_metadata(doc_id)?.unwrap_or_default();
240        self.put_stored(doc_id, StoredDocument::with_metadata(document, metadata))
241    }
242
243    fn get(&self, doc_id: DocId) -> StorageBackendResult<Option<Document>> {
244        self.get_stored(doc_id)
245            .map(|document| document.map(StoredDocument::into_fields))
246    }
247
248    /// Bulk variant of [`DocumentStore::get_stored`].
249    fn get_stored_many(
250        &self,
251        doc_ids: &[DocId],
252    ) -> StorageBackendResult<BTreeMap<DocId, StoredDocument>> {
253        let mut out = BTreeMap::new();
254        for doc_id in doc_ids {
255            if let Some(document) = self.get_stored(*doc_id)? {
256                out.insert(*doc_id, document);
257            }
258        }
259        Ok(out)
260    }
261
262    /// Read one tuple's storage metadata without exposing it as a field.
263    fn get_metadata(&self, doc_id: DocId) -> StorageBackendResult<Option<DocumentMetadata>> {
264        self.get_stored(doc_id)
265            .map(|document| document.map(|document| document.metadata()))
266    }
267    fn contains_doc_id(&self, doc_id: DocId) -> StorageBackendResult<bool> {
268        Ok(self.get(doc_id)?.is_some())
269    }
270    fn delete(&mut self, doc_id: DocId) -> StorageBackendResult<()>;
271    fn clear(&mut self) -> StorageBackendResult<()>;
272
273    /// Read a single field. Returns an owned [`Value`] so persistent
274    /// backends (`SQLite`, ...) can decode on demand without reaching
275    /// for a reference into a transient row.
276    fn get_field(&self, doc_id: DocId, field: &str) -> StorageBackendResult<Option<Value>> {
277        Ok(self
278            .get(doc_id)?
279            .and_then(|document| document.get(field).cloned()))
280    }
281
282    /// Find the first document whose top-level field equals `value`.
283    /// Persistent stores can override this with an indexed or JSON-path
284    /// lookup so point updates do not have to materialise every row.
285    fn find_doc_id_by_field(
286        &self,
287        field: &str,
288        value: &Value,
289    ) -> StorageBackendResult<Option<DocId>> {
290        for doc_id in self.doc_ids()? {
291            if self.get_field(doc_id, field)?.as_ref() == Some(value) {
292                return Ok(Some(doc_id));
293            }
294        }
295        Ok(None)
296    }
297
298    /// Apply top-level field updates without requiring callers to
299    /// materialise the whole document. `Value::Null` matches `put` by
300    /// removing the stored field. `Ok(false)` means the document does
301    /// not exist; write failures surface as `Err`.
302    fn patch_fields(
303        &mut self,
304        doc_id: DocId,
305        updates: &BTreeMap<String, Value>,
306    ) -> StorageBackendResult<bool> {
307        let Some(mut document) = self.get_stored(doc_id)? else {
308            return Ok(false);
309        };
310        for (field, value) in updates {
311            if matches!(value, Value::Null) {
312                document.fields_mut().remove(field);
313            } else {
314                document.fields_mut().insert(field.clone(), value.clone());
315            }
316        }
317        self.put_stored(doc_id, document)?;
318        Ok(true)
319    }
320
321    /// Bulk variant of [`DocumentStore::get`]. Ids without a stored
322    /// document are absent from the result. The default implementation
323    /// walks each id one at a time; persistent backends should
324    /// override to batch the reads into few queries.
325    fn get_many(&self, doc_ids: &[DocId]) -> StorageBackendResult<BTreeMap<DocId, Document>> {
326        let mut out = BTreeMap::new();
327        for doc_id in doc_ids {
328            if let Some(document) = self.get(*doc_id)? {
329                out.insert(*doc_id, document);
330            }
331        }
332        Ok(out)
333    }
334
335    /// Fetch several top-level fields for many documents. The result
336    /// vector is aligned with `fields`; missing fields come back as
337    /// [`Value::Null`], ids without a document are absent. Persistent
338    /// backends override this to extract all fields in one scan
339    /// instead of materialising whole documents.
340    fn get_fields_multi(
341        &self,
342        doc_ids: &[DocId],
343        fields: &[&str],
344    ) -> StorageBackendResult<BTreeMap<DocId, Vec<Value>>> {
345        let mut out = BTreeMap::new();
346        for doc_id in doc_ids {
347            let Some(document) = self.get(*doc_id)? else {
348                continue;
349            };
350            let values = fields
351                .iter()
352                .map(|field| document.get(*field).cloned().unwrap_or(Value::Null))
353                .collect();
354            out.insert(*doc_id, values);
355        }
356        Ok(out)
357    }
358
359    /// Visit a column projection in the caller's document-id order.
360    /// The callback receives one owned row at a time, allowing scan and
361    /// aggregate pipelines to avoid materialising a second doc-id map.
362    /// Returning `false` stops the visit early. Missing documents yield
363    /// a row of NULLs, matching row-evaluator semantics.
364    fn for_each_fields_multi(
365        &self,
366        doc_ids: &[DocId],
367        fields: &[&str],
368        visitor: &mut dyn FnMut(DocId, Vec<Value>) -> bool,
369    ) -> StorageBackendResult<()> {
370        let mut projected = self.get_fields_multi(doc_ids, fields)?;
371        for doc_id in doc_ids {
372            let values = projected
373                .remove(doc_id)
374                .unwrap_or_else(|| vec![Value::Null; fields.len()]);
375            if !visitor(*doc_id, values) {
376                break;
377            }
378        }
379        Ok(())
380    }
381
382    /// Visit a column projection by reference when the backend can keep
383    /// decoded values alive for the duration of the callback. The default
384    /// adapter preserves the backend's owned/batched projection path;
385    /// in-memory stores override it to avoid cloning every projected value.
386    fn for_each_fields_multi_ref(
387        &self,
388        doc_ids: &[DocId],
389        fields: &[&str],
390        visitor: &mut dyn FnMut(DocId, &[&Value]) -> bool,
391    ) -> StorageBackendResult<()> {
392        self.for_each_fields_multi(doc_ids, fields, &mut |doc_id, values| {
393            let references: Vec<&Value> = values.iter().collect();
394            visitor(doc_id, &references)
395        })
396    }
397
398    /// Visit a projection together with whether each requested document
399    /// actually exists. This avoids a separate `contains_doc_id` probe when a
400    /// caller must distinguish a missing document from an existing document
401    /// whose requested fields are all NULL.
402    fn for_each_fields_multi_ref_with_presence(
403        &self,
404        doc_ids: &[DocId],
405        fields: &[&str],
406        visitor: &mut dyn FnMut(DocId, bool, &[&Value]) -> bool,
407    ) -> StorageBackendResult<()> {
408        if fields.is_empty() {
409            for doc_id in doc_ids {
410                if !visitor(*doc_id, self.contains_doc_id(*doc_id)?, &[]) {
411                    break;
412                }
413            }
414            return Ok(());
415        }
416
417        let projected = self.get_fields_multi(doc_ids, fields)?;
418        let null = Value::Null;
419        let missing = vec![&null; fields.len()];
420        for doc_id in doc_ids {
421            let Some(values) = projected.get(doc_id) else {
422                if !visitor(*doc_id, false, &missing) {
423                    break;
424                }
425                continue;
426            };
427            let references = values.iter().collect::<Vec<_>>();
428            if !visitor(*doc_id, true, &references) {
429                break;
430            }
431        }
432        Ok(())
433    }
434
435    /// Return rows aligned with `doc_ids` as shared positional projections
436    /// when the backend owns stable decoded value vectors. `None` means the
437    /// backend does not support zero-copy projection; entries inside the
438    /// returned vector are `None` only for missing document ids.
439    fn get_shared_fields(
440        &self,
441        _doc_ids: &[DocId],
442        _fields: &[&str],
443    ) -> StorageBackendResult<Option<Vec<Option<SharedDocumentRow>>>> {
444        Ok(None)
445    }
446
447    /// Bulk variant of [`DocumentStore::get_field`]. The default
448    /// implementation walks each id one at a time; persistent backends
449    /// should override to run a single batched query.
450    fn get_fields_bulk(
451        &self,
452        doc_ids: &[DocId],
453        field: &str,
454    ) -> StorageBackendResult<BTreeMap<DocId, Value>> {
455        let mut out = BTreeMap::new();
456        for doc_id in doc_ids {
457            out.insert(
458                *doc_id,
459                self.get_field(*doc_id, field)?.unwrap_or(Value::Null),
460            );
461        }
462        Ok(out)
463    }
464
465    /// Return `true` if any document has `field == value`.
466    fn has_value(&self, field: &str, value: &Value) -> StorageBackendResult<bool> {
467        for doc_id in self.doc_ids()? {
468            if self.get_field(doc_id, field)?.as_ref() == Some(value) {
469                return Ok(true);
470            }
471        }
472        Ok(false)
473    }
474
475    /// Find the first document whose top-level fields match every
476    /// requested value.
477    fn find_doc_id_by_fields(
478        &self,
479        fields: &[String],
480        values: &[Value],
481    ) -> StorageBackendResult<Option<DocId>> {
482        if fields.is_empty() || fields.len() != values.len() {
483            return Ok(None);
484        }
485        for doc_id in self.doc_ids()? {
486            let mut matches = true;
487            for (field, value) in fields.iter().zip(values) {
488                if self.get_field(doc_id, field)?.unwrap_or(Value::Null) != *value {
489                    matches = false;
490                    break;
491                }
492            }
493            if matches {
494                return Ok(Some(doc_id));
495            }
496        }
497        Ok(None)
498    }
499
500    /// Evaluate a hierarchical path expression against a document.
501    fn eval_path(
502        &self,
503        doc_id: DocId,
504        path: &[PathSegment],
505    ) -> StorageBackendResult<Option<Value>> {
506        let Some(document) = self.get(doc_id)? else {
507            return Ok(None);
508        };
509        Ok(eval_path_in_document(&document, path))
510    }
511
512    fn doc_ids(&self) -> StorageBackendResult<Vec<DocId>>;
513
514    /// Return the first stored document id strictly greater than `after`, or
515    /// the first id when `after` is `None`. Scan operators use this cursor API
516    /// so a full table scan does not need a cardinality-sized id vector before
517    /// it can yield its first row.
518    fn next_doc_id(&self, after: Option<DocId>) -> StorageBackendResult<Option<DocId>> {
519        Ok(self
520            .doc_ids()?
521            .into_iter()
522            .filter(|doc_id| after.is_none_or(|after| *doc_id > after))
523            .min())
524    }
525
526    /// Return up to `limit` document ids strictly greater than `after`, in
527    /// ascending order. Scan operators use this bounded cursor instead of
528    /// reacquiring their store lock and issuing one backend lookup per row.
529    fn next_doc_ids(&self, after: Option<DocId>, limit: usize) -> StorageBackendResult<Vec<DocId>> {
530        if limit == 0 {
531            return Ok(Vec::new());
532        }
533        let mut doc_ids = self.doc_ids()?;
534        doc_ids.sort_unstable();
535        Ok(doc_ids
536            .into_iter()
537            .filter(|doc_id| after.is_none_or(|after| *doc_id > after))
538            .take(limit)
539            .collect())
540    }
541
542    /// Read at most `limit` identities in strictly ascending order after `after`, retaining the supplied allowance with the output. Implementations reserve producer scratch and output before allocation, preserve this store's selected view, and never read document payloads. The default rejects unsupported providers without calling an unbounded owned cursor; empty requests still honor cancellation. Controlled consumers use [`read_document_ids`] to validate the returned page and its allowance.
543    fn next_doc_ids_controlled(
544        &self,
545        _after: Option<DocId>,
546        limit: usize,
547        control: &StorageReadControl,
548    ) -> StorageBackendResult<BudgetedVec<DocId>> {
549        control.check()?;
550        if limit == 0 {
551            return Ok(BudgetedVec::new(control.memory()));
552        }
553        Err(StorageBackendError::Other(
554            "controlled document identity reads are not supported".into(),
555        ))
556    }
557
558    /// Return the next bounded id range and its shared positional projections
559    /// in one storage traversal when stable decoded rows are available.
560    /// `None` lets persistent backends use the ordinary id + projection path.
561    fn next_shared_fields(
562        &self,
563        _after: Option<DocId>,
564        _limit: usize,
565        _fields: &[&str],
566    ) -> StorageBackendResult<Option<Vec<(DocId, SharedDocumentRow)>>> {
567        Ok(None)
568    }
569
570    /// Visit the next bounded id range through a reusable borrowed projection of each backend-owned row. Missing fields are exposed as SQL NULL. `Some(count)` means the backend supports this borrowed cursor and reports how many rows it visited; `None` selects the ordinary cursor path without invoking `visitor`.
571    fn for_each_next_fields(
572        &self,
573        _after: Option<DocId>,
574        _limit: usize,
575        _fields: &[&str],
576        _visitor: &mut dyn FnMut(DocId, &[&Value]) -> bool,
577    ) -> StorageBackendResult<Option<usize>> {
578        Ok(None)
579    }
580
581    /// Visit a bounded ascending projection while permitting the provider to retain a physical read borrow. The caller must not reenter storage, invoke host callbacks, or execute nested SQL from `visitor`. Missing fields are SQL NULL; stopping the visitor must not decode later rows. `None` must leave the visitor uncalled. Providers without a physical borrow reuse their ordinary projected cursor.
582    fn for_each_next_fields_borrowed(
583        &self,
584        after: Option<DocId>,
585        limit: usize,
586        fields: &[&str],
587        visitor: &mut dyn FnMut(DocId, &[&Value]) -> bool,
588    ) -> StorageBackendResult<Option<usize>> {
589        self.for_each_next_fields(after, limit, fields, visitor)
590    }
591
592    /// Visit requested point projections, including missing rows and duplicate IDs, in caller order while allowing a physical read borrow. Neither the visitor nor its callees may reenter storage, invoke host callbacks, or execute nested SQL. A stopped visitor must not read later IDs. `Some(count)` counts callbacks; `None` must leave the visitor uncalled and selects the ordinary projection path.
593    fn for_each_fields_multi_borrowed(
594        &self,
595        _ids: &[DocId],
596        _fields: &[&str],
597        _visitor: &mut dyn FnMut(DocId, bool, &[&Value]) -> bool,
598    ) -> StorageBackendResult<Option<usize>> {
599        Ok(None)
600    }
601
602    fn max_doc_id(&self) -> StorageBackendResult<DocId> {
603        Ok(self.doc_ids()?.into_iter().max().unwrap_or(0))
604    }
605
606    fn len(&self) -> StorageBackendResult<usize>;
607
608    fn is_empty(&self) -> StorageBackendResult<bool> {
609        Ok(self.len()? == 0)
610    }
611
612    /// Iterate over `(doc_id, document)` pairs in id order. The default
613    /// implementation fetches each document individually; SQLite-backed
614    /// stores override with a single query.
615    fn iter_all(&self) -> StorageBackendResult<Box<dyn Iterator<Item = (DocId, Document)> + '_>> {
616        let mut ids = self.doc_ids()?;
617        ids.sort_unstable();
618        let snapshot = self.snapshot()?;
619        let mut rows = Vec::with_capacity(ids.len());
620        for doc_id in ids {
621            if let Some(document) = snapshot.get(doc_id)? {
622                rows.push((doc_id, document));
623            }
624        }
625        Ok(Box::new(rows.into_iter()))
626    }
627
628    /// Read-only handle suitable for an `ExecutionContext`. Persistent
629    /// backends share their connection; memory backends deep-clone so the
630    /// snapshot is isolated from later mutations.
631    fn snapshot(&self) -> StorageBackendResult<Arc<dyn DocumentStore>>;
632
633    /// Share an already immutable retained view, preserving its selected rows, allocation owners and cancellation boundary. This capability must not capture a new live view or copy row payloads. The default reports that controlled copying is required without invoking the legacy `snapshot` method, whose persistent implementations may share a mutable connection.
634    fn retained_snapshot(&self) -> StorageBackendResult<Option<Arc<dyn DocumentStore>>> {
635        Ok(None)
636    }
637
638    /// Independent writable copy used by the in-memory engine transaction
639    /// rollback path. Persistent engines restore through their backend
640    /// transaction and need not implement this operation.
641    fn writable_snapshot(&self) -> StorageBackendResult<Box<dyn DocumentStore>> {
642        Err(StorageBackendError::Other(
643            "writable document-store snapshots are not supported by this backend".into(),
644        ))
645    }
646}
647
648/// Walk a document along a [`PathSegment`] sequence: strings descend into
649/// maps, integers descend into lists, and the implicit array-wildcard rule
650/// applies a string component over every map element of an array.
651pub fn eval_path_in_document(doc: &Document, path: &[PathSegment]) -> Option<Value> {
652    let mut current: Value = match path.first()? {
653        PathSegment::Key(k) => doc.get(k)?.clone(),
654        PathSegment::Index(_) => return None,
655    };
656    for seg in path.iter().skip(1) {
657        current = match (current, seg) {
658            (Value::Map(m), PathSegment::Key(k)) => m.get(k)?.clone(),
659            (Value::List(items), PathSegment::Index(i)) => items.get(*i)?.clone(),
660            (Value::List(items), PathSegment::Key(k)) => {
661                let collected: Vec<Value> = items
662                    .into_iter()
663                    .filter_map(|v| match v {
664                        Value::Map(m) => m.get(k).cloned(),
665                        _ => None,
666                    })
667                    .collect();
668                Value::List(collected)
669            }
670            _ => return None,
671        };
672    }
673    Some(current)
674}
675
676mod memory;
677
678pub use memory::MemoryDocumentStore;