Skip to main content

uqa_storage/key_value/
document_store.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Document access and mutations use one provider-owned view of the exact storage name.
8
9mod migration;
10mod projection;
11mod read;
12
13use std::collections::BTreeMap;
14use std::sync::Arc;
15
16use super::codec::{document_key, document_key_prefix, encode_stored_document_value, other_error};
17use super::{table_owners, KeyValueBatch, KeyValueRead, KeyValueStore};
18use crate::document_store::Document;
19use crate::read_control::StorageReadControl;
20use crate::{DocumentStore, StorageBackendResult, StoredDocument};
21use read::Documents;
22use uqa_core::memory::BudgetedVec;
23use uqa_core::{DocId, Value};
24
25#[derive(Clone)]
26enum Source {
27    Live(Arc<dyn KeyValueStore>),
28    Retained(Arc<dyn KeyValueRead + Send + Sync>),
29}
30
31/// Document store implemented over [`KeyValueStore`].
32#[derive(Clone)]
33pub struct KeyValueDocumentStore {
34    source: Source,
35    table: String,
36}
37
38impl KeyValueDocumentStore {
39    pub fn new(store: Arc<dyn KeyValueStore>, table: impl Into<String>) -> Self {
40        Self {
41            source: Source::Live(store),
42            table: table.into(),
43        }
44    }
45
46    fn read<T>(
47        &self,
48        query: impl FnOnce(Documents<'_>) -> StorageBackendResult<T>,
49    ) -> StorageBackendResult<T> {
50        let mut query = Some(query);
51        let mut result = None;
52        let mut visit = |read: &dyn KeyValueRead| {
53            let query = query
54                .take()
55                .ok_or_else(|| other_error("document read was evaluated twice"))?;
56            result = Some(query(Documents {
57                read,
58                table: &self.table,
59            })?);
60            Ok(())
61        };
62        match &self.source {
63            Source::Live(store) => store.with_read_view(&mut visit)?,
64            Source::Retained(read) => visit(&**read)?,
65        }
66        result.ok_or_else(|| other_error("document read was not evaluated"))
67    }
68
69    fn mutate<T>(
70        &self,
71        change: impl FnOnce(
72            Documents<'_>,
73            &mut dyn KeyValueBatch,
74            Option<table_owners::Owner>,
75        ) -> StorageBackendResult<T>,
76    ) -> StorageBackendResult<T> {
77        let Source::Live(store) = &self.source else {
78            return Err(other_error("document snapshot is read-only"));
79        };
80        let durable = store.identifier_allocator().is_some();
81        let mut change = Some(change);
82        let mut result = None;
83        store.with_mutation(&mut |read, batch| {
84            let change = change
85                .take()
86                .ok_or_else(|| other_error("document mutation was evaluated twice"))?;
87            let owner = if durable {
88                Some(table_owners::document_owner(read, batch, &self.table)?)
89            } else {
90                None
91            };
92            result = Some(change(
93                Documents {
94                    read,
95                    table: &self.table,
96                },
97                batch,
98                owner,
99            )?);
100            Ok(())
101        })?;
102        result.ok_or_else(|| other_error("document mutation was not evaluated"))
103    }
104}
105
106fn put_document(
107    batch: &mut dyn KeyValueBatch,
108    table: &str,
109    owner: Option<table_owners::Owner>,
110    id: DocId,
111    document: StoredDocument,
112) -> StorageBackendResult<()> {
113    let (mut fields, metadata) = document.into_parts();
114    fields.retain(|_, value| !matches!(value, Value::Null));
115    let bytes = encode_stored_document_value(&StoredDocument::with_metadata(fields, metadata))?;
116    if let Some(owner) = owner {
117        owner.observe(batch, id)?;
118    }
119    batch.put(&document_key(table, id)?, &bytes)
120}
121
122impl DocumentStore for KeyValueDocumentStore {
123    fn put(&mut self, id: DocId, document: Document) -> StorageBackendResult<()> {
124        self.mutate(|view, batch, owner| {
125            let metadata = view
126                .get(id)?
127                .map(|document| document.metadata())
128                .unwrap_or_default();
129            put_document(
130                batch,
131                view.table,
132                owner,
133                id,
134                StoredDocument::with_metadata(document, metadata),
135            )
136        })
137    }
138
139    fn put_stored(&mut self, id: DocId, document: StoredDocument) -> StorageBackendResult<()> {
140        self.mutate(|view, batch, owner| put_document(batch, view.table, owner, id, document))
141    }
142
143    fn get_stored(&self, id: DocId) -> StorageBackendResult<Option<StoredDocument>> {
144        self.read(|view| view.get(id))
145    }
146
147    fn get_stored_many_controlled(
148        &self,
149        ids: &[DocId],
150        control: &StorageReadControl,
151    ) -> StorageBackendResult<crate::RetainedDocumentPage> {
152        control.check()?;
153        if ids.is_empty() {
154            return Ok(BudgetedVec::new(control.memory()));
155        }
156        self.read(|view| view.retained_many_controlled(ids, control))
157    }
158
159    fn get_stored_many(
160        &self,
161        ids: &[DocId],
162    ) -> StorageBackendResult<BTreeMap<DocId, StoredDocument>> {
163        self.read(|view| view.many(ids))
164    }
165
166    fn get_many(&self, ids: &[DocId]) -> StorageBackendResult<BTreeMap<DocId, Document>> {
167        Ok(self
168            .get_stored_many(ids)?
169            .into_iter()
170            .map(|(id, document)| (id, document.into_fields()))
171            .collect())
172    }
173
174    fn get_fields_multi(
175        &self,
176        ids: &[DocId],
177        fields: &[&str],
178    ) -> StorageBackendResult<BTreeMap<DocId, Vec<Value>>> {
179        self.read(|view| view.project(ids, fields))
180    }
181
182    fn for_each_fields_multi_ref_with_presence(
183        &self,
184        ids: &[DocId],
185        fields: &[&str],
186        visitor: &mut dyn FnMut(DocId, bool, &[&Value]) -> bool,
187    ) -> StorageBackendResult<()> {
188        self.visit_projection(ids, fields, visitor)
189    }
190
191    fn for_each_fields_multi_ref(
192        &self,
193        ids: &[DocId],
194        fields: &[&str],
195        visitor: &mut dyn FnMut(DocId, &[&Value]) -> bool,
196    ) -> StorageBackendResult<()> {
197        self.visit_projection(ids, fields, &mut |id, _, values| visitor(id, values))
198    }
199
200    fn for_each_fields_multi(
201        &self,
202        ids: &[DocId],
203        fields: &[&str],
204        visitor: &mut dyn FnMut(DocId, Vec<Value>) -> bool,
205    ) -> StorageBackendResult<()> {
206        self.visit_projection(ids, fields, &mut |id, _, values| {
207            visitor(id, values.iter().map(|value| (*value).clone()).collect())
208        })
209    }
210
211    fn get_fields_bulk(
212        &self,
213        ids: &[DocId],
214        field: &str,
215    ) -> StorageBackendResult<BTreeMap<DocId, Value>> {
216        let projected = self.get_fields_multi(ids, &[field])?;
217        Ok(ids
218            .iter()
219            .map(|id| {
220                (
221                    *id,
222                    projected
223                        .get(id)
224                        .and_then(|row| row.first().cloned())
225                        .unwrap_or(Value::Null),
226                )
227            })
228            .collect())
229    }
230
231    fn contains_doc_id(&self, id: DocId) -> StorageBackendResult<bool> {
232        self.read(|view| view.contains(id))
233    }
234
235    fn patch_fields(
236        &mut self,
237        id: DocId,
238        updates: &BTreeMap<String, Value>,
239    ) -> StorageBackendResult<bool> {
240        self.mutate(|view, batch, owner| {
241            let Some(mut document) = view.get(id)? else {
242                return Ok(false);
243            };
244            for (field, value) in updates {
245                if matches!(value, Value::Null) {
246                    document.fields_mut().remove(field);
247                } else {
248                    document.fields_mut().insert(field.clone(), value.clone());
249                }
250            }
251            put_document(batch, view.table, owner, id, document)?;
252            Ok(true)
253        })
254    }
255
256    fn delete(&mut self, id: DocId) -> StorageBackendResult<()> {
257        self.mutate(|view, batch, _| batch.delete(&document_key(view.table, id)?))
258    }
259
260    fn clear(&mut self) -> StorageBackendResult<()> {
261        self.mutate(|view, batch, owner| {
262            if let Some(owner) = owner {
263                owner.fence(batch)?;
264            }
265            batch.delete_prefix(&document_key_prefix(view.table)?)
266        })
267    }
268
269    fn doc_ids(&self) -> StorageBackendResult<Vec<DocId>> {
270        self.next_doc_ids(None, usize::MAX)
271    }
272
273    fn next_doc_id(&self, after: Option<DocId>) -> StorageBackendResult<Option<DocId>> {
274        Ok(self.next_doc_ids(after, 1)?.into_iter().next())
275    }
276
277    fn next_doc_ids(&self, after: Option<DocId>, limit: usize) -> StorageBackendResult<Vec<DocId>> {
278        self.read(|view| view.ids(after, limit))
279    }
280
281    fn next_doc_ids_controlled(
282        &self,
283        after: Option<DocId>,
284        limit: usize,
285        control: &StorageReadControl,
286    ) -> StorageBackendResult<BudgetedVec<DocId>> {
287        control.check()?;
288        if limit == 0 {
289            return Ok(BudgetedVec::new(control.memory()));
290        }
291        self.read(|view| view.id_page_controlled(after, limit, control))
292    }
293
294    fn for_each_next_fields(
295        &self,
296        after: Option<DocId>,
297        limit: usize,
298        fields: &[&str],
299        visitor: &mut dyn FnMut(DocId, &[&Value]) -> bool,
300    ) -> StorageBackendResult<Option<usize>> {
301        if !fields.is_empty() {
302            return Ok(None);
303        }
304        let (ids, control) =
305            self.read(|view| Ok((view.id_page(after, limit)?, view.read.control().clone())))?;
306        let mut visited = 0;
307        for id in ids.iter().copied() {
308            control.check()?;
309            visited += 1;
310            let keep_going = visitor(id, &[]);
311            control.check()?;
312            if !keep_going {
313                break;
314            }
315        }
316        control.check()?;
317        Ok(Some(visited))
318    }
319
320    fn len(&self) -> StorageBackendResult<usize> {
321        self.read(|view| view.count())
322    }
323
324    fn find_doc_id_by_field(
325        &self,
326        field: &str,
327        value: &Value,
328    ) -> StorageBackendResult<Option<DocId>> {
329        self.read(|view| view.find(|document| document.get(field) == Some(value)))
330    }
331
332    fn has_value(&self, field: &str, value: &Value) -> StorageBackendResult<bool> {
333        Ok(self.find_doc_id_by_field(field, value)?.is_some())
334    }
335
336    fn find_doc_id_by_fields(
337        &self,
338        fields: &[String],
339        values: &[Value],
340    ) -> StorageBackendResult<Option<DocId>> {
341        if fields.is_empty() || fields.len() != values.len() {
342            return Ok(None);
343        }
344        self.read(|view| {
345            view.find(|document| {
346                fields
347                    .iter()
348                    .zip(values)
349                    .all(|(field, value)| document.get(field).unwrap_or(&Value::Null) == value)
350            })
351        })
352    }
353
354    fn iter_all(&self) -> StorageBackendResult<Box<dyn Iterator<Item = (DocId, Document)> + '_>> {
355        let rows = self.read(|view| view.all())?;
356        Ok(Box::new(rows.into_iter()))
357    }
358
359    fn snapshot(&self) -> StorageBackendResult<Arc<dyn DocumentStore>> {
360        let source = match &self.source {
361            Source::Live(_) => self.read(|view| {
362                Ok(Source::Retained(
363                    view.read.retain(&[&document_key_prefix(view.table)?])?,
364                ))
365            })?,
366            Source::Retained(_) => self.source.clone(),
367        };
368        Ok(Arc::new(Self {
369            source,
370            table: self.table.clone(),
371        }))
372    }
373}