Skip to main content

uqa_storage/sqlite/document_store/
trait_impl.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Complete `DocumentStore` query, mutation, iteration, and snapshot contract.
8
9use super::{
10    allocation_error, blob_marker_info, chunk_bind_values, decode_legacy_document_body,
11    doc_id_in_placeholders, document_id_from_sqlite, hydrate_document_blobs,
12    load_marked_document_blob, params, read_doc_id, should_probe_doc_ids, sorted_unique_doc_ids,
13    sqlite_doc_id, take_requested_field, Arc, BTreeMap, DocId, Document, DocumentMetadata,
14    DocumentStore, OptionalExtension, SQLiteDocumentStore, SQLiteError, SQLiteResult,
15    StorageBackendResult, StoredDocument, Value, DOCUMENT_BLOBS_TABLE, DOC_ID_IN_CHUNK,
16};
17
18impl DocumentStore for SQLiteDocumentStore {
19    fn put(&mut self, doc_id: DocId, document: Document) -> StorageBackendResult<()> {
20        let metadata = self.get_metadata(doc_id)?.unwrap_or_default();
21        self.put_stored_inner(doc_id, &document, metadata)?;
22        Ok(())
23    }
24
25    fn get(&self, doc_id: DocId) -> StorageBackendResult<Option<Document>> {
26        Ok(self.get_inner(doc_id)?)
27    }
28
29    fn put_stored(&mut self, doc_id: DocId, document: StoredDocument) -> StorageBackendResult<()> {
30        let (fields, metadata) = document.into_parts();
31        self.put_stored_inner(doc_id, &fields, metadata)?;
32        Ok(())
33    }
34
35    fn get_stored(&self, doc_id: DocId) -> StorageBackendResult<Option<StoredDocument>> {
36        Ok(self.get_stored_inner(doc_id)?)
37    }
38
39    fn get_stored_many(
40        &self,
41        doc_ids: &[DocId],
42    ) -> StorageBackendResult<BTreeMap<DocId, StoredDocument>> {
43        let mut out = BTreeMap::new();
44        if doc_ids.is_empty() {
45            return Ok(out);
46        }
47
48        let decode_row = |connection: &rusqlite::Connection,
49                          row: &rusqlite::Row<'_>|
50         -> SQLiteResult<(DocId, StoredDocument)> {
51            let doc_id = read_doc_id(row, 0)?;
52            let body = row.get::<_, String>(1)?;
53            let tuple_xmin = row.get::<_, Option<i64>>(2)?;
54            let mut fields = decode_legacy_document_body(&body)?;
55            hydrate_document_blobs(connection, &self.table, doc_id, &mut fields)?;
56            let metadata = tuple_xmin.map_or_else(
57                || Ok(DocumentMetadata::default()),
58                |tuple_xmin| {
59                    u32::try_from(tuple_xmin)
60                        .map(DocumentMetadata::with_tuple_xmin)
61                        .map_err(|_| {
62                            SQLiteError::StorageBackend(format!(
63                                "document `{}` row {doc_id} has an out-of-range tuple xmin",
64                                self.table
65                            ))
66                        })
67                },
68            )?;
69            Ok((doc_id, StoredDocument::with_metadata(fields, metadata)))
70        };
71
72        let should_probe =
73            doc_ids.len() <= DOC_ID_IN_CHUNK || should_probe_doc_ids(doc_ids.len(), self.len()?);
74        if should_probe {
75            let leading = [rusqlite::types::Value::Text(self.table.clone())];
76            let sql = format!(
77                "SELECT doc_id, body, tuple_xmin FROM _documents
78                 WHERE table_name = ?1 AND doc_id IN ({})",
79                doc_id_in_placeholders(2, DOC_ID_IN_CHUNK)?
80            );
81            self.conn.with(|connection| {
82                for chunk in doc_ids.chunks(DOC_ID_IN_CHUNK) {
83                    let mut statement = connection.prepare_cached(&sql)?;
84                    let bind = chunk_bind_values(&leading, chunk)?;
85                    let mut rows = statement.query(rusqlite::params_from_iter(bind))?;
86                    while let Some(row) = rows.next()? {
87                        let (doc_id, document) = decode_row(connection, row)?;
88                        out.insert(doc_id, document);
89                    }
90                }
91                Ok(())
92            })?;
93            return Ok(out);
94        }
95
96        let requested = sorted_unique_doc_ids(doc_ids)?;
97        self.conn.with(|connection| {
98            let mut statement = connection.prepare_cached(
99                "SELECT doc_id, body, tuple_xmin FROM _documents
100                 WHERE table_name = ?1
101                 ORDER BY doc_id",
102            )?;
103            let mut rows = statement.query(params![self.table])?;
104            while let Some(row) = rows.next()? {
105                let doc_id = read_doc_id(row, 0)?;
106                if requested.binary_search(&doc_id).is_err() {
107                    continue;
108                }
109                let (doc_id, document) = decode_row(connection, row)?;
110                out.insert(doc_id, document);
111            }
112            Ok(())
113        })?;
114        Ok(out)
115    }
116
117    fn get_metadata(&self, doc_id: DocId) -> StorageBackendResult<Option<DocumentMetadata>> {
118        let sqlite_doc_id = sqlite_doc_id(doc_id)?;
119        let tuple_xmin = self.conn.with(|connection| {
120            Ok(connection
121                .prepare_cached(
122                    "SELECT tuple_xmin FROM _documents
123                     WHERE table_name = ?1 AND doc_id = ?2",
124                )?
125                .query_row(params![self.table, sqlite_doc_id], |row| {
126                    row.get::<_, Option<i64>>(0)
127                })
128                .optional()?)
129        })?;
130        tuple_xmin
131            .map(|tuple_xmin| {
132                tuple_xmin.map_or_else(
133                    || Ok(DocumentMetadata::default()),
134                    |tuple_xmin| {
135                        u32::try_from(tuple_xmin)
136                            .map(DocumentMetadata::with_tuple_xmin)
137                            .map_err(|_| {
138                                SQLiteError::StorageBackend(format!(
139                                    "document `{}` row {doc_id} has an out-of-range tuple xmin",
140                                    self.table
141                                ))
142                                .into()
143                            })
144                    },
145                )
146            })
147            .transpose()
148    }
149
150    fn contains_doc_id(&self, doc_id: DocId) -> StorageBackendResult<bool> {
151        let sqlite_doc_id = sqlite_doc_id(doc_id)?;
152        Ok(self.conn.with(|c| {
153            let found: Option<i64> = c
154                .prepare_cached(
155                    "SELECT 1 FROM _documents
156                         WHERE table_name = ?1 AND doc_id = ?2
157                         LIMIT 1",
158                )?
159                .query_row(params![self.table, sqlite_doc_id], |r| r.get(0))
160                .optional()?;
161            Ok(found.is_some())
162        })?)
163    }
164
165    fn get_field(
166        &self,
167        doc_id: DocId,
168        field: &str,
169    ) -> StorageBackendResult<Option<uqa_core::Value>> {
170        Ok(self.get_field_inner(doc_id, field)?)
171    }
172
173    fn find_doc_id_by_field(
174        &self,
175        field: &str,
176        value: &Value,
177    ) -> StorageBackendResult<Option<DocId>> {
178        Ok(self.find_doc_id_by_field_inner(field, value)?)
179    }
180
181    fn get_fields_bulk(
182        &self,
183        doc_ids: &[DocId],
184        field: &str,
185    ) -> StorageBackendResult<BTreeMap<DocId, Value>> {
186        let mut out: BTreeMap<DocId, Value> = doc_ids
187            .iter()
188            .copied()
189            .map(|doc_id| (doc_id, Value::Null))
190            .collect();
191        if doc_ids.is_empty() {
192            return Ok(out);
193        }
194        // Fetch the document body and extract the field in Rust: one
195        // JSON parse per row. Extracting through `json_type` +
196        // `json_extract` made `SQLite` parse the same body twice per
197        // requested field.
198        let mut decode_row = |c: &rusqlite::Connection,
199                              row: &rusqlite::Row<'_>|
200         -> SQLiteResult<()> {
201            let doc_id = read_doc_id(row, 0)?;
202            let body = row.get::<_, String>(1)?;
203            let mut document = decode_legacy_document_body(&body)?;
204            if let Some(value) = take_requested_field(c, &self.table, doc_id, &mut document, field)?
205            {
206                out.insert(doc_id, value);
207            }
208            Ok(())
209        };
210
211        // Selective requests probe by id; wide requests (half the
212        // table or more) sequential-scan once instead of issuing many
213        // B-tree probes.
214        let should_probe =
215            doc_ids.len() <= DOC_ID_IN_CHUNK || should_probe_doc_ids(doc_ids.len(), self.len()?);
216        if should_probe {
217            let leading = [rusqlite::types::Value::Text(self.table.clone())];
218            let sql = format!(
219                "SELECT doc_id, body FROM _documents
220                 WHERE table_name = ?1 AND doc_id IN ({})",
221                doc_id_in_placeholders(2, DOC_ID_IN_CHUNK)?
222            );
223            self.conn.with(|c| {
224                for chunk in doc_ids.chunks(DOC_ID_IN_CHUNK) {
225                    let mut stmt = c.prepare_cached(&sql)?;
226                    let bind = chunk_bind_values(&leading, chunk)?;
227                    let mut rows = stmt.query(rusqlite::params_from_iter(bind))?;
228                    while let Some(row) = rows.next()? {
229                        decode_row(c, row)?;
230                    }
231                }
232                Ok(())
233            })?;
234            return Ok(out);
235        }
236
237        let requested = sorted_unique_doc_ids(doc_ids)?;
238        self.conn.with(|c| {
239            let mut stmt = c.prepare_cached(
240                "SELECT doc_id, body FROM _documents
241                 WHERE table_name = ?1
242                 ORDER BY doc_id",
243            )?;
244            let mut rows = stmt.query(params![self.table])?;
245            while let Some(row) = rows.next()? {
246                let doc_id = read_doc_id(row, 0)?;
247                if requested.binary_search(&doc_id).is_err() {
248                    continue;
249                }
250                decode_row(c, row)?;
251            }
252            Ok(())
253        })?;
254        Ok(out)
255    }
256
257    fn get_fields_multi(
258        &self,
259        doc_ids: &[DocId],
260        fields: &[&str],
261    ) -> StorageBackendResult<BTreeMap<DocId, Vec<Value>>> {
262        let mut out: BTreeMap<DocId, Vec<Value>> = BTreeMap::new();
263        if doc_ids.is_empty() || fields.is_empty() {
264            return Ok(out);
265        }
266        // Fetch the document body and extract every requested field in
267        // Rust: one JSON parse per row, however many fields the caller
268        // asked for. The previous `json_type` + `json_extract` pair per
269        // field made `SQLite` parse the same body twice per field.
270        let decode_row = |c: &rusqlite::Connection,
271                          row: &rusqlite::Row<'_>|
272         -> SQLiteResult<(DocId, Vec<Value>)> {
273            let doc_id = read_doc_id(row, 0)?;
274            let body = row.get::<_, String>(1)?;
275            let document = decode_legacy_document_body(&body)?;
276            let mut values = Vec::new();
277            values
278                .try_reserve_exact(fields.len())
279                .map_err(|error| allocation_error("multi-field document values", error))?;
280            for field in fields {
281                let mut value = document.get(*field).cloned().unwrap_or(Value::Null);
282                if let Some(marker) = blob_marker_info(&value) {
283                    if let Some(decoded) =
284                        load_marked_document_blob(c, &self.table, doc_id, field, &marker)?
285                    {
286                        value = decoded;
287                    }
288                }
289                values.push(value);
290            }
291            Ok((doc_id, values))
292        };
293
294        let should_probe =
295            doc_ids.len() <= DOC_ID_IN_CHUNK || should_probe_doc_ids(doc_ids.len(), self.len()?);
296        if should_probe {
297            let leading = [rusqlite::types::Value::Text(self.table.clone())];
298            let sql = format!(
299                "SELECT doc_id, body FROM _documents
300                 WHERE table_name = ?1 AND doc_id IN ({})",
301                doc_id_in_placeholders(2, DOC_ID_IN_CHUNK)?
302            );
303            self.conn.with(|c| {
304                for chunk in doc_ids.chunks(DOC_ID_IN_CHUNK) {
305                    let mut stmt = c.prepare_cached(&sql)?;
306                    let bind = chunk_bind_values(&leading, chunk)?;
307                    let mut rows = stmt.query(rusqlite::params_from_iter(bind))?;
308                    while let Some(row) = rows.next()? {
309                        let (doc_id, values) = decode_row(c, row)?;
310                        out.insert(doc_id, values);
311                    }
312                }
313                Ok(())
314            })?;
315            return Ok(out);
316        }
317
318        let requested = sorted_unique_doc_ids(doc_ids)?;
319        self.conn.with(|c| {
320            let mut stmt = c.prepare_cached(
321                "SELECT doc_id, body FROM _documents
322                 WHERE table_name = ?1
323                 ORDER BY doc_id",
324            )?;
325            let mut rows = stmt.query(params![self.table])?;
326            while let Some(row) = rows.next()? {
327                let doc_id = read_doc_id(row, 0)?;
328                if requested.binary_search(&doc_id).is_err() {
329                    continue;
330                }
331                let (doc_id, values) = decode_row(c, row)?;
332                out.insert(doc_id, values);
333            }
334            Ok(())
335        })?;
336        Ok(out)
337    }
338
339    fn get_many(&self, doc_ids: &[DocId]) -> StorageBackendResult<BTreeMap<DocId, Document>> {
340        self.get_stored_many(doc_ids).map(|documents| {
341            documents
342                .into_iter()
343                .map(|(doc_id, document)| (doc_id, document.into_fields()))
344                .collect()
345        })
346    }
347
348    fn patch_fields(
349        &mut self,
350        doc_id: DocId,
351        updates: &BTreeMap<String, Value>,
352    ) -> StorageBackendResult<bool> {
353        Ok(self.patch_fields_inner(doc_id, updates)?)
354    }
355
356    fn delete(&mut self, doc_id: DocId) -> StorageBackendResult<()> {
357        let sqlite_doc_id = sqlite_doc_id(doc_id)?;
358        self.conn.with(|c| {
359            c.prepare_cached(&format!(
360                "DELETE FROM {DOCUMENT_BLOBS_TABLE}
361                 WHERE table_name = ?1 AND doc_id = ?2"
362            ))?
363            .execute(params![self.table, sqlite_doc_id])?;
364            c.prepare_cached("DELETE FROM _documents WHERE table_name = ?1 AND doc_id = ?2")?
365                .execute(params![self.table, sqlite_doc_id])?;
366            Ok(())
367        })?;
368        Ok(())
369    }
370
371    fn clear(&mut self) -> StorageBackendResult<()> {
372        self.conn.with(|c| {
373            c.execute(
374                &format!("DELETE FROM {DOCUMENT_BLOBS_TABLE} WHERE table_name = ?1"),
375                params![self.table],
376            )?;
377            c.execute(
378                "DELETE FROM _documents WHERE table_name = ?1",
379                params![self.table],
380            )?;
381            Ok(())
382        })?;
383        Ok(())
384    }
385
386    fn doc_ids(&self) -> StorageBackendResult<Vec<DocId>> {
387        Ok(self.conn.with(|c| {
388            let mut stmt = c.prepare_cached(
389                "SELECT doc_id FROM _documents WHERE table_name = ?1 ORDER BY doc_id",
390            )?;
391            let rows = stmt.query_map(params![self.table], |r| r.get::<_, i64>(0))?;
392            let mut out = Vec::new();
393            for row in rows {
394                out.push(document_id_from_sqlite(row?)?);
395            }
396            Ok(out)
397        })?)
398    }
399
400    fn next_doc_id(&self, after: Option<DocId>) -> StorageBackendResult<Option<DocId>> {
401        let after = after.map(sqlite_doc_id).transpose()?;
402        Ok(self.conn.with(|connection| {
403            let doc_id: Option<i64> = match after {
404                Some(after) => connection
405                    .prepare_cached(
406                        "SELECT doc_id FROM _documents
407                         WHERE table_name = ?1 AND doc_id > ?2
408                         ORDER BY doc_id LIMIT 1",
409                    )?
410                    .query_row(params![self.table, after], |row| row.get::<_, i64>(0))
411                    .optional()?,
412                None => connection
413                    .prepare_cached(
414                        "SELECT doc_id FROM _documents
415                         WHERE table_name = ?1
416                         ORDER BY doc_id LIMIT 1",
417                    )?
418                    .query_row(params![self.table], |row| row.get::<_, i64>(0))
419                    .optional()?,
420            };
421            doc_id.map(document_id_from_sqlite).transpose()
422        })?)
423    }
424
425    fn next_doc_ids(&self, after: Option<DocId>, limit: usize) -> StorageBackendResult<Vec<DocId>> {
426        if limit == 0 {
427            return Ok(Vec::new());
428        }
429        let after = after.map(sqlite_doc_id).transpose()?;
430        let limit = i64::try_from(limit).map_err(|_| {
431            SQLiteError::StorageBackend(format!(
432                "document cursor limit {limit} is outside SQLite's integer range"
433            ))
434        })?;
435        Ok(self.conn.with(|connection| {
436            let mut out = Vec::new();
437            if let Some(after) = after {
438                let mut stmt = connection.prepare_cached(
439                    "SELECT doc_id FROM _documents
440                     WHERE table_name = ?1 AND doc_id > ?2
441                     ORDER BY doc_id LIMIT ?3",
442                )?;
443                let rows = stmt.query_map(params![self.table, after, limit], |row| {
444                    row.get::<_, i64>(0)
445                })?;
446                for row in rows {
447                    out.push(document_id_from_sqlite(row?)?);
448                }
449            } else {
450                let mut stmt = connection.prepare_cached(
451                    "SELECT doc_id FROM _documents
452                     WHERE table_name = ?1
453                     ORDER BY doc_id LIMIT ?2",
454                )?;
455                let rows =
456                    stmt.query_map(params![self.table, limit], |row| row.get::<_, i64>(0))?;
457                for row in rows {
458                    out.push(document_id_from_sqlite(row?)?);
459                }
460            }
461            Ok(out)
462        })?)
463    }
464
465    fn max_doc_id(&self) -> StorageBackendResult<DocId> {
466        SQLiteDocumentStore::max_doc_id(self)
467    }
468
469    fn len(&self) -> StorageBackendResult<usize> {
470        Ok(self.conn.with(|c| {
471            let n: i64 = c
472                .prepare_cached("SELECT COUNT(*) FROM _documents WHERE table_name = ?1")?
473                .query_row(params![self.table], |r| r.get(0))?;
474            usize::try_from(n).map_err(|_| {
475                SQLiteError::StorageBackend(format!(
476                    "document count {n} is outside the addressable range"
477                ))
478            })
479        })?)
480    }
481
482    fn snapshot(&self) -> StorageBackendResult<Arc<dyn DocumentStore>> {
483        Ok(Arc::new(self.clone()))
484    }
485}