bonsaidb_core/connection/
lowlevel.rs

1use std::borrow::Borrow;
2use std::collections::BTreeMap;
3
4use arc_bytes::serde::Bytes;
5use async_trait::async_trait;
6
7use super::GroupedReductions;
8use crate::connection::{
9    AccessPolicy, HasSession, QueryKey, Range, RangeRef, SerializedQueryKey, Sort,
10};
11use crate::document::{
12    CollectionDocument, CollectionHeader, Document, DocumentId, HasHeader, Header, OwnedDocument,
13};
14use crate::key::{self, ByteSource, Key, KeyEncoding};
15use crate::schema::view::map::{
16    CollectionMap, MappedDocuments, MappedSerializedValue, ViewMappings,
17};
18use crate::schema::view::{self};
19use crate::schema::{self, CollectionName, MappedValue, Schematic, SerializedCollection, ViewName};
20use crate::transaction::{OperationResult, Transaction};
21use crate::Error;
22
23/// The low-level interface to a database's [`schema::Schema`], giving access to
24/// [`Collection`s](crate::schema::Collection) and
25/// [`Views`s](crate::schema::View). This trait is not safe to use within async
26/// contexts and will block the current thread. For async access, use
27/// [`AsyncLowLevelConnection`].
28///
29/// This trait's methods are not designed for ergonomics. See
30/// [`Connection`](super::Connection) for a higher-level interface.
31pub trait LowLevelConnection: HasSchema + HasSession {
32    /// Inserts a newly created document into the connected [`schema::Schema`]
33    /// for the [`Collection`](schema::Collection) `C`. If `id` is `None` a unique id will be
34    /// generated. If an id is provided and a document already exists with that
35    /// id, a conflict error will be returned.
36    ///
37    /// This is the lower-level API. For better ergonomics, consider using
38    /// one of:
39    ///
40    /// - [`SerializedCollection::push_into()`]
41    /// - [`SerializedCollection::insert_into()`]
42    /// - [`self.collection::<Collection>().insert()`](super::Collection::insert)
43    /// - [`self.collection::<Collection>().push()`](super::Collection::push)
44    fn insert<C, PrimaryKey, B>(
45        &self,
46        id: Option<&PrimaryKey>,
47        contents: B,
48    ) -> Result<CollectionHeader<C::PrimaryKey>, Error>
49    where
50        C: schema::Collection,
51        B: Into<Bytes> + Send,
52        PrimaryKey: KeyEncoding<C::PrimaryKey> + Send + ?Sized,
53    {
54        let contents = contents.into();
55        let results = self.apply_transaction(Transaction::insert(
56            C::collection_name(),
57            id.map(|id| DocumentId::new(id)).transpose()?,
58            contents,
59        ))?;
60        if let Some(OperationResult::DocumentUpdated { header, .. }) = results.into_iter().next() {
61            CollectionHeader::try_from(header)
62        } else {
63            unreachable!(
64                "apply_transaction on a single insert should yield a single DocumentUpdated entry"
65            )
66        }
67    }
68
69    /// Updates an existing document in the connected [`schema::Schema`] for the
70    /// [`Collection`](schema::Collection) `C`. Upon success, `doc.revision` will be updated with
71    /// the new revision.
72    ///
73    /// This is the lower-level API. For better ergonomics, consider using
74    /// one of:
75    ///
76    /// - [`CollectionDocument::update()`]
77    /// - [`self.collection::<Collection>().update()`](super::Collection::update)
78    fn update<C: schema::Collection, D: Document<C> + Send + Sync>(
79        &self,
80        doc: &mut D,
81    ) -> Result<(), Error> {
82        let results = self.apply_transaction(Transaction::update(
83            C::collection_name(),
84            doc.header().into_header()?,
85            doc.bytes()?,
86        ))?;
87        if let Some(OperationResult::DocumentUpdated { header, .. }) = results.into_iter().next() {
88            doc.set_header(header)?;
89            Ok(())
90        } else {
91            unreachable!(
92                "apply_transaction on a single update should yield a single DocumentUpdated entry"
93            )
94        }
95    }
96
97    /// Overwrites an existing document, or inserts a new document. Upon success,
98    /// `doc.revision` will be updated with the new revision information.
99    ///
100    /// This is the lower-level API. For better ergonomics, consider using
101    /// one of:
102    ///
103    /// - [`SerializedCollection::overwrite()`]
104    /// - [`SerializedCollection::overwrite_into()`]
105    /// - [`self.collection::<Collection>().overwrite()`](super::Collection::overwrite)
106    fn overwrite<C, PrimaryKey>(
107        &self,
108        id: &PrimaryKey,
109        contents: Vec<u8>,
110    ) -> Result<CollectionHeader<C::PrimaryKey>, Error>
111    where
112        C: schema::Collection,
113        PrimaryKey: KeyEncoding<C::PrimaryKey>,
114    {
115        let results = self.apply_transaction(Transaction::overwrite(
116            C::collection_name(),
117            DocumentId::new(id)?,
118            contents,
119        ))?;
120        if let Some(OperationResult::DocumentUpdated { header, .. }) = results.into_iter().next() {
121            CollectionHeader::try_from(header)
122        } else {
123            unreachable!(
124                "apply_transaction on a single update should yield a single DocumentUpdated entry"
125            )
126        }
127    }
128
129    /// Retrieves a stored document from [`Collection`](schema::Collection) `C` identified by `id`.
130    ///
131    /// This is a lower-level API. For better ergonomics, consider using one of:
132    ///
133    /// - [`SerializedCollection::get()`]
134    /// - [`self.collection::<Collection>().get()`](super::Collection::get)
135    fn get<C, PrimaryKey>(&self, id: &PrimaryKey) -> Result<Option<OwnedDocument>, Error>
136    where
137        C: schema::Collection,
138        PrimaryKey: KeyEncoding<C::PrimaryKey> + ?Sized,
139    {
140        self.get_from_collection(DocumentId::new(id)?, &C::collection_name())
141    }
142
143    /// Retrieves all documents matching `ids`. Documents that are not found are
144    /// not returned, but no error will be generated.
145    ///
146    /// This is a lower-level API. For better ergonomics, consider using one of:
147    ///
148    /// - [`SerializedCollection::get_multiple()`]
149    /// - [`self.collection::<Collection>().get_multiple()`](super::Collection::get_multiple)
150    fn get_multiple<'id, C, PrimaryKey, DocumentIds, I>(
151        &self,
152        ids: DocumentIds,
153    ) -> Result<Vec<OwnedDocument>, Error>
154    where
155        C: schema::Collection,
156        DocumentIds: IntoIterator<Item = &'id PrimaryKey, IntoIter = I> + Send + Sync,
157        I: Iterator<Item = &'id PrimaryKey> + Send + Sync,
158        PrimaryKey: KeyEncoding<C::PrimaryKey> + 'id + ?Sized,
159    {
160        let ids = ids
161            .into_iter()
162            .map(|id| DocumentId::new(id))
163            .collect::<Result<Vec<_>, _>>()?;
164        self.get_multiple_from_collection(&ids, &C::collection_name())
165    }
166
167    /// Retrieves all documents within the range of `ids`. To retrieve all
168    /// documents, pass in `..` for `ids`.
169    ///
170    /// This is a lower-level API. For better ergonomics, consider using one of:
171    ///
172    /// - [`SerializedCollection::all()`]
173    /// - [`self.collection::<Collection>().all()`](super::Collection::all)
174    /// - [`SerializedCollection::list()`]
175    /// - [`self.collection::<Collection>().list()`](super::Collection::list)
176    fn list<'id, C, R, PrimaryKey>(
177        &self,
178        ids: R,
179        order: Sort,
180        limit: Option<u32>,
181    ) -> Result<Vec<OwnedDocument>, Error>
182    where
183        C: schema::Collection,
184        R: Into<RangeRef<'id, C::PrimaryKey, PrimaryKey>> + Send,
185        PrimaryKey: KeyEncoding<C::PrimaryKey> + PartialEq + 'id + ?Sized,
186        C::PrimaryKey: Borrow<PrimaryKey> + PartialEq<PrimaryKey>,
187    {
188        let ids = ids.into().map_result(|id| DocumentId::new(id))?;
189        self.list_from_collection(ids, order, limit, &C::collection_name())
190    }
191
192    /// Retrieves all documents within the range of `ids`. To retrieve all
193    /// documents, pass in `..` for `ids`.
194    ///
195    /// This is the lower-level API. For better ergonomics, consider using one
196    /// of:
197    ///
198    /// - [`SerializedCollection::all_async().headers()`](schema::List::headers)
199    /// - [`self.collection::<Collection>().all().headers()`](super::List::headers)
200    /// - [`SerializedCollection::list_async().headers()`](schema::List::headers)
201    /// - [`self.collection::<Collection>().list().headers()`](super::List::headers)
202    fn list_headers<'id, C, R, PrimaryKey>(
203        &self,
204        ids: R,
205        order: Sort,
206        limit: Option<u32>,
207    ) -> Result<Vec<Header>, Error>
208    where
209        C: schema::Collection,
210        R: Into<RangeRef<'id, C::PrimaryKey, PrimaryKey>> + Send,
211        PrimaryKey: KeyEncoding<C::PrimaryKey> + PartialEq + 'id + ?Sized,
212        C::PrimaryKey: Borrow<PrimaryKey> + PartialEq<PrimaryKey>,
213    {
214        let ids = ids.into().map_result(|id| DocumentId::new(id))?;
215        self.list_headers_from_collection(ids, order, limit, &C::collection_name())
216    }
217
218    /// Counts the number of documents within the range of `ids`.
219    ///
220    /// This is a lower-level API. For better ergonomics, consider using one of:
221    ///
222    /// - [`SerializedCollection::all().count()`](schema::List::count)
223    /// - [`self.collection::<Collection>().all().count()`](super::List::count)
224    /// - [`SerializedCollection::list().count()`](schema::List::count)
225    /// - [`self.collection::<Collection>().list().count()`](super::List::count)
226    fn count<'id, C, R, PrimaryKey>(&self, ids: R) -> Result<u64, Error>
227    where
228        C: schema::Collection,
229        R: Into<RangeRef<'id, C::PrimaryKey, PrimaryKey>> + Send,
230        PrimaryKey: KeyEncoding<C::PrimaryKey> + PartialEq + 'id + ?Sized,
231        C::PrimaryKey: Borrow<PrimaryKey> + PartialEq<PrimaryKey>,
232    {
233        self.count_from_collection(
234            ids.into().map_result(|id| DocumentId::new(id))?,
235            &C::collection_name(),
236        )
237    }
238
239    /// Removes a `Document` from the database.
240    ///
241    /// This is a lower-level API. For better ergonomics, consider using
242    /// one of:
243    ///
244    /// - [`CollectionDocument::delete()`]
245    /// - [`self.collection::<Collection>().delete()`](super::Collection::delete)
246    fn delete<C: schema::Collection, H: HasHeader + Send + Sync>(
247        &self,
248        doc: &H,
249    ) -> Result<(), Error> {
250        let results =
251            self.apply_transaction(Transaction::delete(C::collection_name(), doc.header()?))?;
252        if let OperationResult::DocumentDeleted { .. } = &results[0] {
253            Ok(())
254        } else {
255            unreachable!(
256                "apply_transaction on a single update should yield a single DocumentUpdated entry"
257            )
258        }
259    }
260
261    /// Queries for view entries matching [`View`](schema::View).
262    ///
263    /// This is a lower-level API. For better ergonomics, consider querying the
264    /// view using [`View::entries(self).query()`](super::View::query) instead. The
265    /// parameters for the query can be customized on the builder returned from
266    /// [`SerializedView::entries()`](schema::SerializedView::entries),
267    /// [`SerializedView::entries_async()`](schema::SerializedView::entries_async),
268    /// or [`Connection::view()`](super::Connection::view).
269    fn query<V: schema::SerializedView, Key>(
270        &self,
271        key: Option<QueryKey<'_, V::Key, Key>>,
272        order: Sort,
273        limit: Option<u32>,
274        access_policy: AccessPolicy,
275    ) -> Result<ViewMappings<V>, Error>
276    where
277        Key: KeyEncoding<V::Key> + PartialEq + ?Sized,
278        V::Key: Borrow<Key> + PartialEq<Key>,
279    {
280        let view = self.schematic().view::<V>()?;
281        let mappings = self.query_by_name(
282            &view.view_name(),
283            key.map(|key| key.serialized()).transpose()?,
284            order,
285            limit,
286            access_policy,
287        )?;
288        mappings
289            .into_iter()
290            .map(|mapping| {
291                Ok(CollectionMap {
292                    key: <V::Key as key::Key>::from_ord_bytes(ByteSource::Borrowed(&mapping.key))
293                        .map_err(view::Error::key_serialization)
294                        .map_err(Error::from)?,
295                    value: V::deserialize(&mapping.value)?,
296                    source: mapping.source.try_into()?,
297                })
298            })
299            .collect::<Result<Vec<_>, Error>>()
300    }
301
302    /// Queries for view entries matching [`View`](schema::View) with their
303    /// source documents.
304    ///
305    /// This is a lower-level API. For better ergonomics, consider querying the
306    /// view using
307    /// [`View::entries(self).query_with_docs()`](super::View::query_with_docs)
308    /// instead. The parameters for the query can be customized on the builder
309    /// returned from
310    /// [`SerializedView::entries()`](schema::SerializedView::entries),
311    /// [`SerializedView::entries_async()`](schema::SerializedView::entries_async),
312    /// or [`Connection::view()`](super::Connection::view).
313    fn query_with_docs<V: schema::SerializedView, Key>(
314        &self,
315        key: Option<QueryKey<'_, V::Key, Key>>,
316        order: Sort,
317        limit: Option<u32>,
318        access_policy: AccessPolicy,
319    ) -> Result<MappedDocuments<OwnedDocument, V>, Error>
320    where
321        Key: KeyEncoding<V::Key> + PartialEq + ?Sized,
322        V::Key: Borrow<Key> + PartialEq<Key>,
323    {
324        // Query permission is checked by the query call
325        let results = self.query::<V, Key>(key, order, limit, access_policy)?;
326
327        // Verify that there is permission to fetch each document
328        let documents = self
329            .get_multiple::<V::Collection, _, _, _>(results.iter().map(|m| &m.source.id))?
330            .into_iter()
331            .map(|doc| {
332                let id = doc.header.id.deserialize()?;
333                Ok((id, doc))
334            })
335            .collect::<Result<BTreeMap<_, _>, Error>>()?;
336
337        Ok(MappedDocuments {
338            mappings: results,
339            documents,
340        })
341    }
342
343    /// Queries for view entries matching [`View`](schema::View) with their
344    /// source documents, deserialized.
345    ///
346    /// This is a lower-level API. For better ergonomics, consider querying the
347    /// view using
348    /// [`View::entries(self).query_with_collection_docs()`](super::View::query_with_collection_docs)
349    /// instead. The parameters for the query can be customized on the builder
350    /// returned from
351    /// [`SerializedView::entries()`](schema::SerializedView::entries),
352    /// [`SerializedView::entries_async()`](schema::SerializedView::entries_async),
353    /// or [`Connection::view()`](super::Connection::view).
354    fn query_with_collection_docs<V, Key>(
355        &self,
356        key: Option<QueryKey<'_, V::Key, Key>>,
357        order: Sort,
358        limit: Option<u32>,
359        access_policy: AccessPolicy,
360    ) -> Result<MappedDocuments<CollectionDocument<V::Collection>, V>, Error>
361    where
362        Key: KeyEncoding<V::Key> + PartialEq + ?Sized,
363        V::Key: Borrow<Key> + PartialEq<Key>,
364        V: schema::SerializedView,
365        V::Collection: SerializedCollection,
366        <V::Collection as SerializedCollection>::Contents: std::fmt::Debug,
367    {
368        let mapped_docs = self.query_with_docs::<V, Key>(key, order, limit, access_policy)?;
369        let mut collection_docs = BTreeMap::new();
370        for (id, doc) in mapped_docs.documents {
371            collection_docs.insert(id, CollectionDocument::<V::Collection>::try_from(&doc)?);
372        }
373        Ok(MappedDocuments {
374            mappings: mapped_docs.mappings,
375            documents: collection_docs,
376        })
377    }
378
379    /// Reduces the view entries matching [`View`](schema::View).
380    ///
381    /// This is a lower-level API. For better ergonomics, consider reducing the
382    /// view using [`View::entries(self).reduce()`](super::View::reduce)
383    /// instead. The parameters for the query can be customized on the builder
384    /// returned from
385    /// [`SerializedView::entries()`](schema::SerializedView::entries),
386    /// [`SerializedView::entries_async()`](schema::SerializedView::entries_async),
387    /// or [`Connection::view()`](super::Connection::view).
388    fn reduce<V: schema::SerializedView, Key>(
389        &self,
390        key: Option<QueryKey<'_, V::Key, Key>>,
391        access_policy: AccessPolicy,
392    ) -> Result<V::Value, Error>
393    where
394        Key: KeyEncoding<V::Key> + PartialEq + ?Sized,
395        V::Key: Borrow<Key> + PartialEq<Key>,
396    {
397        let view = self.schematic().view::<V>()?;
398        self.reduce_by_name(
399            &view.view_name(),
400            key.map(|key| key.serialized()).transpose()?,
401            access_policy,
402        )
403        .and_then(|value| V::deserialize(&value))
404    }
405
406    /// Reduces the view entries matching [`View`](schema::View), reducing the
407    /// values by each unique key.
408    ///
409    /// This is a lower-level API. For better ergonomics, consider reducing the
410    /// view using
411    /// [`View::entries(self).reduce_grouped()`](super::View::reduce_grouped)
412    /// instead. The parameters for the query can be customized on the builder
413    /// returned from
414    /// [`SerializedView::entries()`](schema::SerializedView::entries),
415    /// [`SerializedView::entries_async()`](schema::SerializedView::entries_async),
416    /// or [`Connection::view()`](super::Connection::view).
417    fn reduce_grouped<V: schema::SerializedView, Key>(
418        &self,
419        key: Option<QueryKey<'_, V::Key, Key>>,
420        access_policy: AccessPolicy,
421    ) -> Result<GroupedReductions<V>, Error>
422    where
423        Key: KeyEncoding<V::Key> + PartialEq + ?Sized,
424        V::Key: Borrow<Key> + PartialEq<Key>,
425    {
426        let view = self.schematic().view::<V>()?;
427        self.reduce_grouped_by_name(
428            &view.view_name(),
429            key.map(|key| key.serialized()).transpose()?,
430            access_policy,
431        )?
432        .into_iter()
433        .map(|map| {
434            Ok(MappedValue::new(
435                V::Key::from_ord_bytes(ByteSource::Borrowed(&map.key))
436                    .map_err(view::Error::key_serialization)?,
437                V::deserialize(&map.value)?,
438            ))
439        })
440        .collect::<Result<Vec<_>, Error>>()
441    }
442
443    /// Deletes all of the documents associated with this view.
444    ///
445    /// This is a lower-level API. For better ergonomics, consider querying the
446    /// view using
447    /// [`View::entries(self).delete_docs()`](super::View::delete_docs())
448    /// instead. The parameters for the query can be customized on the builder
449    /// returned from
450    /// [`SerializedView::entries()`](schema::SerializedView::entries),
451    /// [`SerializedView::entries_async()`](schema::SerializedView::entries_async),
452    /// or [`Connection::view()`](super::Connection::view).
453    fn delete_docs<V: schema::SerializedView, Key>(
454        &self,
455        key: Option<QueryKey<'_, V::Key, Key>>,
456        access_policy: AccessPolicy,
457    ) -> Result<u64, Error>
458    where
459        Key: KeyEncoding<V::Key> + PartialEq + ?Sized,
460        V::Key: Borrow<Key> + PartialEq<Key>,
461    {
462        let view = self.schematic().view::<V>()?;
463        self.delete_docs_by_name(
464            &view.view_name(),
465            key.map(|key| key.serialized()).transpose()?,
466            access_policy,
467        )
468    }
469
470    /// Applies a [`Transaction`] to the [`schema::Schema`]. If any operation in the
471    /// [`Transaction`] fails, none of the operations will be applied to the
472    /// [`schema::Schema`].
473    fn apply_transaction(&self, transaction: Transaction) -> Result<Vec<OperationResult>, Error>;
474
475    /// Retrieves the document with `id` stored within the named `collection`.
476    ///
477    /// This is a lower-level API. For better ergonomics, consider using
478    /// one of:
479    ///
480    /// - [`SerializedCollection::get()`]
481    /// - [`self.collection::<Collection>().get()`](super::Collection::get)
482    fn get_from_collection(
483        &self,
484        id: DocumentId,
485        collection: &CollectionName,
486    ) -> Result<Option<OwnedDocument>, Error>;
487
488    /// Retrieves all documents matching `ids` from the named `collection`.
489    /// Documents that are not found are not returned, but no error will be
490    /// generated.
491    ///
492    /// This is a lower-level API. For better ergonomics, consider using one of:
493    ///
494    /// - [`SerializedCollection::get_multiple()`]
495    /// - [`self.collection::<Collection>().get_multiple()`](super::Collection::get_multiple)
496    fn get_multiple_from_collection(
497        &self,
498        ids: &[DocumentId],
499        collection: &CollectionName,
500    ) -> Result<Vec<OwnedDocument>, Error>;
501
502    /// Retrieves all documents within the range of `ids` from the named
503    /// `collection`. To retrieve all documents, pass in `..` for `ids`.
504    ///
505    /// This is a lower-level API. For better ergonomics, consider using one of:
506    ///
507    /// - [`SerializedCollection::all()`]
508    /// - [`self.collection::<Collection>().all()`](super::Collection::all)
509    /// - [`SerializedCollection::list()`]
510    /// - [`self.collection::<Collection>().list()`](super::Collection::list)
511    fn list_from_collection(
512        &self,
513        ids: Range<DocumentId>,
514        order: Sort,
515        limit: Option<u32>,
516        collection: &CollectionName,
517    ) -> Result<Vec<OwnedDocument>, Error>;
518
519    /// Retrieves all headers within the range of `ids` from the named
520    /// `collection`. To retrieve all documents, pass in `..` for `ids`.
521    ///
522    /// This is a lower-level API. For better ergonomics, consider using one of:
523    ///
524    /// - [`SerializedCollection::all().headers()`](schema::List::headers)
525    /// - [`self.collection::<Collection>().all().headers()`](super::AsyncCollection::all)
526    /// - [`SerializedCollection::list().headers()`](schema::List::headers)
527    /// - [`self.collection::<Collection>().list()`](super::AsyncCollection::list)
528    fn list_headers_from_collection(
529        &self,
530        ids: Range<DocumentId>,
531        order: Sort,
532        limit: Option<u32>,
533        collection: &CollectionName,
534    ) -> Result<Vec<Header>, Error>;
535
536    /// Counts the number of documents within the range of `ids` from the named
537    /// `collection`.
538    ///
539    /// This is a lower-level API. For better ergonomics, consider using one of:
540    ///
541    /// - [`SerializedCollection::all().count()`](schema::List::count)
542    /// - [`self.collection::<Collection>().all().count()`](super::List::count)
543    /// - [`SerializedCollection::list().count()`](schema::List::count)
544    /// - [`self.collection::<Collection>().list().count()`](super::List::count)
545    fn count_from_collection(
546        &self,
547        ids: Range<DocumentId>,
548        collection: &CollectionName,
549    ) -> Result<u64, Error>;
550
551    /// Compacts the collection to reclaim unused disk space.
552    ///
553    /// This process is done by writing data to a new file and swapping the file
554    /// once the process completes. This ensures that if a hardware failure,
555    /// power outage, or crash occurs that the original collection data is left
556    /// untouched.
557    ///
558    /// ## Errors
559    ///
560    /// * [`Error::CollectionNotFound`]: database `name` does not exist.
561    /// * [`Error::Other`]: an error occurred while compacting the database.
562    fn compact_collection_by_name(&self, collection: CollectionName) -> Result<(), Error>;
563
564    /// Queries for view entries from the named `view`.
565    ///
566    /// This is a lower-level API. For better ergonomics, consider querying the
567    /// view using [`View::entries(self).query()`](super::View::query) instead. The
568    /// parameters for the query can be customized on the builder returned from
569    /// [`Connection::view()`](super::Connection::view).
570    fn query_by_name(
571        &self,
572        view: &ViewName,
573        key: Option<SerializedQueryKey>,
574        order: Sort,
575        limit: Option<u32>,
576        access_policy: AccessPolicy,
577    ) -> Result<Vec<schema::view::map::Serialized>, Error>;
578
579    /// Queries for view entries from the named `view` with their source
580    /// documents.
581    ///
582    /// This is a lower-level API. For better ergonomics, consider querying the
583    /// view using
584    /// [`View::entries(self).query_with_docs()`](super::View::query_with_docs)
585    /// instead. The parameters for the query can be customized on the builder
586    /// returned from [`Connection::view()`](super::Connection::view).
587    fn query_by_name_with_docs(
588        &self,
589        view: &ViewName,
590        key: Option<SerializedQueryKey>,
591        order: Sort,
592        limit: Option<u32>,
593        access_policy: AccessPolicy,
594    ) -> Result<schema::view::map::MappedSerializedDocuments, Error>;
595
596    /// Reduces the view entries from the named `view`.
597    ///
598    /// This is a lower-level API. For better ergonomics, consider reducing the
599    /// view using [`View::entries(self).reduce()`](super::View::reduce)
600    /// instead. The parameters for the query can be customized on the builder
601    /// returned from [`Connection::view()`](super::Connection::view).
602    fn reduce_by_name(
603        &self,
604        view: &ViewName,
605        key: Option<SerializedQueryKey>,
606        access_policy: AccessPolicy,
607    ) -> Result<Vec<u8>, Error>;
608
609    /// Reduces the view entries from the named `view`, reducing the values by each
610    /// unique key.
611    ///
612    /// This is a lower-level API. For better ergonomics, consider reducing
613    /// the view using
614    /// [`View::entries(self).reduce_grouped()`](super::View::reduce_grouped) instead.
615    /// The parameters for the query can be customized on the builder returned
616    /// from [`Connection::view()`](super::Connection::view).
617    fn reduce_grouped_by_name(
618        &self,
619        view: &ViewName,
620        key: Option<SerializedQueryKey>,
621        access_policy: AccessPolicy,
622    ) -> Result<Vec<MappedSerializedValue>, Error>;
623
624    /// Deletes all source documents for entries that match within the named
625    /// `view`.
626    ///
627    /// This is a lower-level API. For better ergonomics, consider querying the
628    /// view using
629    /// [`View::entries(self).delete_docs()`](super::View::delete_docs())
630    /// instead. The parameters for the query can be customized on the builder
631    /// returned from [`Connection::view()`](super::Connection::view).
632    fn delete_docs_by_name(
633        &self,
634        view: &ViewName,
635        key: Option<SerializedQueryKey>,
636        access_policy: AccessPolicy,
637    ) -> Result<u64, Error>;
638}
639
640/// The low-level interface to a database's [`schema::Schema`], giving access to
641/// [`Collection`s](crate::schema::Collection) and
642/// [`Views`s](crate::schema::View). This trait is for use within async
643/// contexts. For access outside of async contexts, use [`LowLevelConnection`].
644///
645/// This trait's methods are not designed for ergonomics. See
646/// [`AsyncConnection`](super::AsyncConnection) for a higher-level interface.
647#[async_trait]
648pub trait AsyncLowLevelConnection: HasSchema + HasSession + Send + Sync {
649    /// Inserts a newly created document into the connected [`schema::Schema`]
650    /// for the [`Collection`](schema::Collection) `C`. If `id` is `None` a unique id will be
651    /// generated. If an id is provided and a document already exists with that
652    /// id, a conflict error will be returned.
653    ///
654    /// This is the lower-level API. For better ergonomics, consider using
655    /// one of:
656    ///
657    /// - [`SerializedCollection::push_into_async()`]
658    /// - [`SerializedCollection::insert_into_async()`]
659    /// - [`self.collection::<Collection>().insert()`](super::AsyncCollection::insert)
660    /// - [`self.collection::<Collection>().push()`](super::AsyncCollection::push)
661    async fn insert<C: schema::Collection, PrimaryKey: Send, B: Into<Bytes> + Send>(
662        &self,
663        id: Option<&PrimaryKey>,
664        contents: B,
665    ) -> Result<CollectionHeader<C::PrimaryKey>, Error>
666    where
667        PrimaryKey: KeyEncoding<C::PrimaryKey> + ?Sized,
668    {
669        let contents = contents.into();
670        let results = self
671            .apply_transaction(Transaction::insert(
672                C::collection_name(),
673                id.map(|id| DocumentId::new(id)).transpose()?,
674                contents,
675            ))
676            .await?;
677        if let Some(OperationResult::DocumentUpdated { header, .. }) = results.into_iter().next() {
678            CollectionHeader::try_from(header)
679        } else {
680            unreachable!(
681                "apply_transaction on a single insert should yield a single DocumentUpdated entry"
682            )
683        }
684    }
685
686    /// Updates an existing document in the connected [`schema::Schema`] for the
687    /// [`Collection`](schema::Collection)(schema::Collection) `C`. Upon success, `doc.revision`
688    /// will be updated with the new revision.
689    ///
690    /// This is the lower-level API. For better ergonomics, consider using one
691    /// of:
692    ///
693    /// - [`CollectionDocument::update_async()`]
694    /// - [`self.collection::<Collection>().update()`](super::AsyncCollection::update)
695    async fn update<C: schema::Collection, D: Document<C> + Send + Sync>(
696        &self,
697        doc: &mut D,
698    ) -> Result<(), Error> {
699        let results = self
700            .apply_transaction(Transaction::update(
701                C::collection_name(),
702                doc.header().into_header()?,
703                doc.bytes()?,
704            ))
705            .await?;
706        if let Some(OperationResult::DocumentUpdated { header, .. }) = results.into_iter().next() {
707            doc.set_header(header)?;
708            Ok(())
709        } else {
710            unreachable!(
711                "apply_transaction on a single update should yield a single DocumentUpdated entry"
712            )
713        }
714    }
715
716    /// Overwrites an existing document, or inserts a new document. Upon success,
717    /// `doc.revision` will be updated with the new revision information.
718    ///
719    /// This is the lower-level API. For better ergonomics, consider using
720    /// one of:
721    ///
722    /// - [`SerializedCollection::overwrite_async()`]
723    /// - [`SerializedCollection::overwrite_into_async()`]
724    /// - [`self.collection::<Collection>().overwrite()`](super::AsyncCollection::overwrite)
725    async fn overwrite<'a, C, PrimaryKey>(
726        &self,
727        id: &PrimaryKey,
728        contents: Vec<u8>,
729    ) -> Result<CollectionHeader<C::PrimaryKey>, Error>
730    where
731        C: schema::Collection,
732        PrimaryKey: KeyEncoding<C::PrimaryKey>,
733    {
734        let results = self
735            .apply_transaction(Transaction::overwrite(
736                C::collection_name(),
737                DocumentId::new(id)?,
738                contents,
739            ))
740            .await?;
741        if let Some(OperationResult::DocumentUpdated { header, .. }) = results.into_iter().next() {
742            CollectionHeader::try_from(header)
743        } else {
744            unreachable!(
745                "apply_transaction on a single update should yield a single DocumentUpdated entry"
746            )
747        }
748    }
749
750    /// Retrieves a stored document from [`Collection`](schema::Collection) `C` identified by `id`.
751    ///
752    /// This is the lower-level API. For better ergonomics, consider using
753    /// one of:
754    ///
755    /// - [`SerializedCollection::get_async()`]
756    /// - [`self.collection::<Collection>().get()`](super::AsyncCollection::get)
757    async fn get<C, PrimaryKey>(&self, id: &PrimaryKey) -> Result<Option<OwnedDocument>, Error>
758    where
759        C: schema::Collection,
760        PrimaryKey: KeyEncoding<C::PrimaryKey> + ?Sized,
761    {
762        self.get_from_collection(DocumentId::new(id)?, &C::collection_name())
763            .await
764    }
765
766    /// Retrieves all documents matching `ids`. Documents that are not found
767    /// are not returned, but no error will be generated.
768    ///
769    /// This is the lower-level API. For better ergonomics, consider using
770    /// one of:
771    ///
772    /// - [`SerializedCollection::get_multiple_async()`]
773    /// - [`self.collection::<Collection>().get_multiple()`](super::AsyncCollection::get_multiple)
774    async fn get_multiple<'id, C, PrimaryKey, DocumentIds, I>(
775        &self,
776        ids: DocumentIds,
777    ) -> Result<Vec<OwnedDocument>, Error>
778    where
779        C: schema::Collection,
780        DocumentIds: IntoIterator<Item = &'id PrimaryKey, IntoIter = I> + Send + Sync,
781        I: Iterator<Item = &'id PrimaryKey> + Send + Sync,
782        PrimaryKey: KeyEncoding<C::PrimaryKey> + 'id + ?Sized,
783    {
784        let ids = ids
785            .into_iter()
786            .map(DocumentId::new)
787            .collect::<Result<Vec<_>, _>>()?;
788        self.get_multiple_from_collection(&ids, &C::collection_name())
789            .await
790    }
791
792    /// Retrieves all documents within the range of `ids`. To retrieve all
793    /// documents, pass in `..` for `ids`.
794    ///
795    /// This is the lower-level API. For better ergonomics, consider using one
796    /// of:
797    ///
798    /// - [`SerializedCollection::all_async()`]
799    /// - [`self.collection::<Collection>().all()`](super::AsyncCollection::all)
800    /// - [`SerializedCollection::list_async()`]
801    /// - [`self.collection::<Collection>().list()`](super::AsyncCollection::list)
802    async fn list<'id, C, R, PrimaryKey>(
803        &self,
804        ids: R,
805        order: Sort,
806        limit: Option<u32>,
807    ) -> Result<Vec<OwnedDocument>, Error>
808    where
809        C: schema::Collection,
810        R: Into<RangeRef<'id, C::PrimaryKey, PrimaryKey>> + Send,
811        PrimaryKey: KeyEncoding<C::PrimaryKey> + PartialEq + 'id + ?Sized,
812        C::PrimaryKey: Borrow<PrimaryKey> + PartialEq<PrimaryKey>,
813    {
814        let ids = ids.into().map_result(|id| DocumentId::new(id))?;
815        self.list_from_collection(ids, order, limit, &C::collection_name())
816            .await
817    }
818
819    /// Retrieves all documents within the range of `ids`. To retrieve all
820    /// documents, pass in `..` for `ids`.
821    ///
822    /// This is the lower-level API. For better ergonomics, consider using one
823    /// of:
824    ///
825    /// - [`SerializedCollection::all_async().headers()`](schema::AsyncList::headers)
826    /// - [`self.collection::<Collection>().all()`](super::AsyncList::headers)
827    /// - [`SerializedCollection::list_async().headers()`](schema::AsyncList::headers)
828    /// - [`self.collection::<Collection>().list().headers()`](super::AsyncList::headers)
829    async fn list_headers<'id, C, R, PrimaryKey>(
830        &self,
831        ids: R,
832        order: Sort,
833        limit: Option<u32>,
834    ) -> Result<Vec<Header>, Error>
835    where
836        C: schema::Collection,
837        R: Into<RangeRef<'id, C::PrimaryKey, PrimaryKey>> + Send,
838        PrimaryKey: KeyEncoding<C::PrimaryKey> + PartialEq + 'id + ?Sized,
839        C::PrimaryKey: Borrow<PrimaryKey> + PartialEq<PrimaryKey>,
840    {
841        let ids = ids.into().map_result(|id| DocumentId::new(id))?;
842        self.list_headers_from_collection(ids, order, limit, &C::collection_name())
843            .await
844    }
845
846    /// Counts the number of documents within the range of `ids`.
847    ///
848    /// This is the lower-level API. For better ergonomics, consider using
849    /// one of:
850    ///
851    /// - [`SerializedCollection::all_async().count()`](schema::AsyncList::count)
852    /// - [`self.collection::<Collection>().all().count()`](super::AsyncList::count)
853    /// - [`SerializedCollection::list_async().count()`](schema::AsyncList::count)
854    /// - [`self.collection::<Collection>().list().count()`](super::AsyncList::count)
855    async fn count<'id, C, R, PrimaryKey>(&self, ids: R) -> Result<u64, Error>
856    where
857        C: schema::Collection,
858        R: Into<RangeRef<'id, C::PrimaryKey, PrimaryKey>> + Send,
859        PrimaryKey: KeyEncoding<C::PrimaryKey> + PartialEq + 'id + ?Sized,
860        C::PrimaryKey: Borrow<PrimaryKey> + PartialEq<PrimaryKey>,
861    {
862        self.count_from_collection(
863            ids.into().map_result(|id| DocumentId::new(id))?,
864            &C::collection_name(),
865        )
866        .await
867    }
868
869    /// Removes a `Document` from the database.
870    ///
871    /// This is the lower-level API. For better ergonomics, consider using
872    /// one of:
873    ///
874    /// - [`CollectionDocument::delete_async()`]
875    /// - [`self.collection::<Collection>().delete()`](super::AsyncCollection::delete)
876    async fn delete<C: schema::Collection, H: HasHeader + Send + Sync>(
877        &self,
878        doc: &H,
879    ) -> Result<(), Error> {
880        let results = self
881            .apply_transaction(Transaction::delete(C::collection_name(), doc.header()?))
882            .await?;
883        if let OperationResult::DocumentDeleted { .. } = &results[0] {
884            Ok(())
885        } else {
886            unreachable!(
887                "apply_transaction on a single update should yield a single DocumentUpdated entry"
888            )
889        }
890    }
891    /// Queries for view entries matching [`View`](schema::View)(super::AsyncView).
892    ///
893    /// This is the lower-level API. For better ergonomics, consider querying
894    /// the view using [`View::entries(self).query()`](super::AsyncView::query)
895    /// instead. The parameters for the query can be customized on the builder
896    /// returned from [`AsyncConnection::view()`](super::AsyncConnection::view).
897    async fn query<V: schema::SerializedView, Key>(
898        &self,
899        key: Option<QueryKey<'_, V::Key, Key>>,
900        order: Sort,
901        limit: Option<u32>,
902        access_policy: AccessPolicy,
903    ) -> Result<ViewMappings<V>, Error>
904    where
905        Key: KeyEncoding<V::Key> + PartialEq + ?Sized,
906        V::Key: Borrow<Key> + PartialEq<Key>,
907    {
908        let view = self.schematic().view::<V>()?;
909        let mappings = self
910            .query_by_name(
911                &view.view_name(),
912                key.map(|key| key.serialized()).transpose()?,
913                order,
914                limit,
915                access_policy,
916            )
917            .await?;
918        mappings
919            .into_iter()
920            .map(|mapping| {
921                Ok(CollectionMap {
922                    key: <V::Key as key::Key>::from_ord_bytes(ByteSource::Borrowed(&mapping.key))
923                        .map_err(view::Error::key_serialization)
924                        .map_err(Error::from)?,
925                    value: V::deserialize(&mapping.value)?,
926                    source: mapping.source.try_into()?,
927                })
928            })
929            .collect::<Result<Vec<_>, Error>>()
930    }
931
932    /// Queries for view entries matching [`View`](schema::View) with their source documents.
933    ///
934    /// This is the lower-level API. For better ergonomics, consider querying
935    /// the view using [`View::entries(self).query_with_docs()`](super::AsyncView::query_with_docs) instead.
936    /// The parameters for the query can be customized on the builder returned
937    /// from [`AsyncConnection::view()`](super::AsyncConnection::view).
938    #[must_use]
939    async fn query_with_docs<V: schema::SerializedView, Key>(
940        &self,
941        key: Option<QueryKey<'_, V::Key, Key>>,
942        order: Sort,
943        limit: Option<u32>,
944        access_policy: AccessPolicy,
945    ) -> Result<MappedDocuments<OwnedDocument, V>, Error>
946    where
947        Key: KeyEncoding<V::Key> + PartialEq + ?Sized,
948        V::Key: Borrow<Key> + PartialEq<Key>,
949    {
950        // Query permission is checked by the query call
951        let results = self
952            .query::<V, Key>(key, order, limit, access_policy)
953            .await?;
954
955        // Verify that there is permission to fetch each document
956        let documents = self
957            .get_multiple::<V::Collection, _, _, _>(results.iter().map(|m| &m.source.id))
958            .await?
959            .into_iter()
960            .map(|doc| {
961                let id = doc.header.id.deserialize()?;
962                Ok((id, doc))
963            })
964            .collect::<Result<BTreeMap<_, _>, Error>>()?;
965
966        Ok(MappedDocuments {
967            mappings: results,
968            documents,
969        })
970    }
971
972    /// Queries for view entries matching [`View`](schema::View) with their source documents,
973    /// deserialized.
974    ///
975    /// This is the lower-level API. For better ergonomics, consider querying
976    /// the view using
977    /// [`View::entries(self).query_with_collection_docs()`](super::AsyncView::query_with_collection_docs)
978    /// instead. The parameters for the query can be customized on the builder
979    /// returned from [`AsyncConnection::view()`](super::AsyncConnection::view).
980    #[must_use]
981    async fn query_with_collection_docs<V, Key>(
982        &self,
983        key: Option<QueryKey<'_, V::Key, Key>>,
984        order: Sort,
985        limit: Option<u32>,
986        access_policy: AccessPolicy,
987    ) -> Result<MappedDocuments<CollectionDocument<V::Collection>, V>, Error>
988    where
989        Key: KeyEncoding<V::Key> + PartialEq + ?Sized,
990        V::Key: Borrow<Key> + PartialEq<Key>,
991        V: schema::SerializedView,
992        V::Collection: SerializedCollection,
993        <V::Collection as SerializedCollection>::Contents: std::fmt::Debug,
994    {
995        let mapped_docs = self
996            .query_with_docs::<V, Key>(key, order, limit, access_policy)
997            .await?;
998        let mut collection_docs = BTreeMap::new();
999        for (id, doc) in mapped_docs.documents {
1000            collection_docs.insert(id, CollectionDocument::<V::Collection>::try_from(&doc)?);
1001        }
1002        Ok(MappedDocuments {
1003            mappings: mapped_docs.mappings,
1004            documents: collection_docs,
1005        })
1006    }
1007
1008    /// Reduces the view entries matching [`View`](schema::View).
1009    ///
1010    /// This is the lower-level API. For better ergonomics, consider querying
1011    /// the view using
1012    /// [`View::entries(self).reduce()`](super::AsyncView::reduce)
1013    /// instead. The parameters for the query can be customized on the builder
1014    /// returned from [`AsyncConnection::view()`](super::AsyncConnection::view).
1015    #[must_use]
1016    async fn reduce<V: schema::SerializedView, Key>(
1017        &self,
1018        key: Option<QueryKey<'_, V::Key, Key>>,
1019        access_policy: AccessPolicy,
1020    ) -> Result<V::Value, Error>
1021    where
1022        Key: KeyEncoding<V::Key> + PartialEq + ?Sized,
1023        V::Key: Borrow<Key> + PartialEq<Key>,
1024    {
1025        let view = self.schematic().view::<V>()?;
1026        self.reduce_by_name(
1027            &view.view_name(),
1028            key.map(|key| key.serialized()).transpose()?,
1029            access_policy,
1030        )
1031        .await
1032        .and_then(|value| V::deserialize(&value))
1033    }
1034
1035    /// Reduces the view entries matching [`View`](schema::View), reducing the values by each
1036    /// unique key.
1037    ///
1038    /// This is the lower-level API. For better ergonomics, consider querying
1039    /// the view using
1040    /// [`View::entries(self).reduce_grouped()`](super::AsyncView::reduce_grouped)
1041    /// instead. The parameters for the query can be customized on the builder
1042    /// returned from [`AsyncConnection::view()`](super::AsyncConnection::view).
1043    #[must_use]
1044    async fn reduce_grouped<V: schema::SerializedView, Key>(
1045        &self,
1046        key: Option<QueryKey<'_, V::Key, Key>>,
1047        access_policy: AccessPolicy,
1048    ) -> Result<GroupedReductions<V>, Error>
1049    where
1050        Key: KeyEncoding<V::Key> + PartialEq + ?Sized,
1051        V::Key: Borrow<Key> + PartialEq<Key>,
1052    {
1053        let view = self.schematic().view::<V>()?;
1054        self.reduce_grouped_by_name(
1055            &view.view_name(),
1056            key.map(|key| key.serialized()).transpose()?,
1057            access_policy,
1058        )
1059        .await?
1060        .into_iter()
1061        .map(|map| {
1062            Ok(MappedValue::new(
1063                V::Key::from_ord_bytes(ByteSource::Borrowed(&map.key))
1064                    .map_err(view::Error::key_serialization)?,
1065                V::deserialize(&map.value)?,
1066            ))
1067        })
1068        .collect::<Result<Vec<_>, Error>>()
1069    }
1070
1071    /// Deletes all of the documents associated with this view.
1072    ///
1073    /// This is the lower-level API. For better ergonomics, consider querying
1074    /// the view using
1075    /// [`View::entries(self).delete_docs()`](super::AsyncView::delete_docs)
1076    /// instead. The parameters for the query can be customized on the builder
1077    /// returned from [`AsyncConnection::view()`](super::AsyncConnection::view).
1078    #[must_use]
1079    async fn delete_docs<V: schema::SerializedView, Key>(
1080        &self,
1081        key: Option<QueryKey<'_, V::Key, Key>>,
1082        access_policy: AccessPolicy,
1083    ) -> Result<u64, Error>
1084    where
1085        Key: KeyEncoding<V::Key> + PartialEq + ?Sized,
1086        V::Key: Borrow<Key> + PartialEq<Key>,
1087    {
1088        let view = self.schematic().view::<V>()?;
1089        self.delete_docs_by_name(
1090            &view.view_name(),
1091            key.map(|key| key.serialized()).transpose()?,
1092            access_policy,
1093        )
1094        .await
1095    }
1096
1097    /// Applies a [`Transaction`] to the [`Schema`](schema::Schema). If any
1098    /// operation in the [`Transaction`] fails, none of the operations will be
1099    /// applied to the [`Schema`](schema::Schema).
1100    async fn apply_transaction(
1101        &self,
1102        transaction: Transaction,
1103    ) -> Result<Vec<OperationResult>, Error>;
1104
1105    /// Retrieves the document with `id` stored within the named `collection`.
1106    ///
1107    /// This is a lower-level API. For better ergonomics, consider using one of:
1108    ///
1109    /// - [`SerializedCollection::get_async()`]
1110    /// - [`self.collection::<Collection>().get()`](super::AsyncCollection::get)
1111    async fn get_from_collection(
1112        &self,
1113        id: DocumentId,
1114        collection: &CollectionName,
1115    ) -> Result<Option<OwnedDocument>, Error>;
1116
1117    /// Retrieves all documents matching `ids` from the named `collection`.
1118    /// Documents that are not found are not returned, but no error will be
1119    /// generated.
1120    ///
1121    /// This is a lower-level API. For better ergonomics, consider using one of:
1122    ///
1123    /// - [`SerializedCollection::get_multiple_async()`]
1124    /// - [`self.collection::<Collection>().get_multiple()`](super::AsyncCollection::get_multiple)
1125    async fn get_multiple_from_collection(
1126        &self,
1127        ids: &[DocumentId],
1128        collection: &CollectionName,
1129    ) -> Result<Vec<OwnedDocument>, Error>;
1130
1131    /// Retrieves all documents within the range of `ids` from the named
1132    /// `collection`. To retrieve all documents, pass in `..` for `ids`.
1133    ///
1134    /// This is a lower-level API. For better ergonomics, consider using one of:
1135    ///
1136    /// - [`SerializedCollection::all().headers()`](schema::List::headers)
1137    /// - [`self.collection::<Collection>().all().headers()`](super::List::headers)
1138    /// - [`SerializedCollection::list().headers()`](schema::List::headers)
1139    /// - [`self.collection::<Collection>().list().headers()`](super::List::headers)
1140    async fn list_from_collection(
1141        &self,
1142        ids: Range<DocumentId>,
1143        order: Sort,
1144        limit: Option<u32>,
1145        collection: &CollectionName,
1146    ) -> Result<Vec<OwnedDocument>, Error>;
1147
1148    /// Retrieves all headers within the range of `ids` from the named
1149    /// `collection`. To retrieve all documents, pass in `..` for `ids`.
1150    ///
1151    /// This is a lower-level API. For better ergonomics, consider using one of:
1152    ///
1153    /// - [`SerializedCollection::all().headers()`](schema::AsyncList::headers)
1154    /// - [`self.collection::<Collection>().all().headers()`](super::AsyncList::headers)
1155    /// - [`SerializedCollection::list().headers()`](schema::AsyncList::headers)
1156    /// - [`self.collection::<Collection>().list().headers()`](super::AsyncList::headers)
1157    async fn list_headers_from_collection(
1158        &self,
1159        ids: Range<DocumentId>,
1160        order: Sort,
1161        limit: Option<u32>,
1162        collection: &CollectionName,
1163    ) -> Result<Vec<Header>, Error>;
1164
1165    /// Counts the number of documents within the range of `ids` from the named
1166    /// `collection`.
1167    ///
1168    /// This is a lower-level API. For better ergonomics, consider using one of:
1169    ///
1170    /// - [`SerializedCollection::all_async().count()`](schema::AsyncList::count)
1171    /// - [`self.collection::<Collection>().all().count()`](super::AsyncList::count)
1172    /// - [`SerializedCollection::list_async().count()`](schema::AsyncList::count)
1173    /// - [`self.collection::<Collection>().list().count()`](super::AsyncList::count)
1174    async fn count_from_collection(
1175        &self,
1176        ids: Range<DocumentId>,
1177        collection: &CollectionName,
1178    ) -> Result<u64, Error>;
1179
1180    /// Compacts the collection to reclaim unused disk space.
1181    ///
1182    /// This process is done by writing data to a new file and swapping the file
1183    /// once the process completes. This ensures that if a hardware failure,
1184    /// power outage, or crash occurs that the original collection data is left
1185    /// untouched.
1186    ///
1187    /// ## Errors
1188    ///
1189    /// * [`Error::CollectionNotFound`]: database `name` does not exist.
1190    /// * [`Error::Other`]: an error occurred while compacting the database.
1191    async fn compact_collection_by_name(&self, collection: CollectionName) -> Result<(), Error>;
1192
1193    /// Queries for view entries from the named `view`.
1194    ///
1195    /// This is the lower-level API. For better ergonomics, consider querying
1196    /// the view using [`View::entries(self).query()`](super::AsyncView::query)
1197    /// instead. The parameters for the query can be customized on the builder
1198    /// returned from [`AsyncConnection::view()`](super::AsyncConnection::view).
1199    async fn query_by_name(
1200        &self,
1201        view: &ViewName,
1202        key: Option<SerializedQueryKey>,
1203        order: Sort,
1204        limit: Option<u32>,
1205        access_policy: AccessPolicy,
1206    ) -> Result<Vec<schema::view::map::Serialized>, Error>;
1207
1208    /// Queries for view entries from the named `view` with their source
1209    /// documents.
1210    ///
1211    /// This is the lower-level API. For better ergonomics, consider querying
1212    /// the view using [`View::entries(self).query_with_docs()`](super::AsyncView::query_with_docs) instead.
1213    /// The parameters for the query can be customized on the builder returned
1214    /// from [`AsyncConnection::view()`](super::AsyncConnection::view).
1215    async fn query_by_name_with_docs(
1216        &self,
1217        view: &ViewName,
1218        key: Option<SerializedQueryKey>,
1219        order: Sort,
1220        limit: Option<u32>,
1221        access_policy: AccessPolicy,
1222    ) -> Result<schema::view::map::MappedSerializedDocuments, Error>;
1223
1224    /// Reduces the view entries from the named `view`.
1225    ///
1226    /// This is the lower-level API. For better ergonomics, consider querying
1227    /// the view using
1228    /// [`View::entries(self).reduce()`](super::AsyncView::reduce)
1229    /// instead. The parameters for the query can be customized on the builder
1230    /// returned from [`AsyncConnection::view()`](super::AsyncConnection::view).
1231    async fn reduce_by_name(
1232        &self,
1233        view: &ViewName,
1234        key: Option<SerializedQueryKey>,
1235        access_policy: AccessPolicy,
1236    ) -> Result<Vec<u8>, Error>;
1237
1238    /// Reduces the view entries from the named `view`, reducing the values by each
1239    /// unique key.
1240    ///
1241    /// This is the lower-level API. For better ergonomics, consider querying
1242    /// the view using
1243    /// [`View::entries(self).reduce_grouped()`](super::AsyncView::reduce_grouped)
1244    /// instead. The parameters for the query can be customized on the builder
1245    /// returned from [`AsyncConnection::view()`](super::AsyncConnection::view).
1246    async fn reduce_grouped_by_name(
1247        &self,
1248        view: &ViewName,
1249        key: Option<SerializedQueryKey>,
1250        access_policy: AccessPolicy,
1251    ) -> Result<Vec<MappedSerializedValue>, Error>;
1252
1253    /// Deletes all source documents for entries that match within the named
1254    /// `view`.
1255    ///
1256    /// This is the lower-level API. For better ergonomics, consider querying
1257    /// the view using
1258    /// [`View::entries(self).delete_docs()`](super::AsyncView::delete_docs)
1259    /// instead. The parameters for the query can be customized on the builder
1260    /// returned from [`AsyncConnection::view()`](super::AsyncConnection::view).
1261    async fn delete_docs_by_name(
1262        &self,
1263        view: &ViewName,
1264        key: Option<SerializedQueryKey>,
1265        access_policy: AccessPolicy,
1266    ) -> Result<u64, Error>;
1267}
1268
1269/// Access to a connection's schema.
1270pub trait HasSchema {
1271    /// Returns the schema for the database.
1272    fn schematic(&self) -> &Schematic;
1273}