bonsaidb_core/schema/
view.rs

1use std::fmt::Debug;
2
3use serde::de::DeserializeOwned;
4use serde::{Deserialize, Serialize};
5use transmog::{Format, OwnedDeserializer};
6use transmog_pot::Pot;
7
8use crate::connection::{self, AsyncConnection, Connection};
9use crate::document::{BorrowedDocument, CollectionDocument};
10use crate::key::{ByteSource, Key, KeyDescription};
11use crate::schema::view::map::{MappedValue, Mappings, ViewMappedValue};
12use crate::schema::{Collection, CollectionName, Name, SerializedCollection, ViewName};
13use crate::AnyError;
14
15/// Types for defining a `Map` within a `View`.
16pub mod map;
17
18/// Errors that arise when interacting with views.
19#[derive(thiserror::Error, Debug)]
20// TODO add which view name and collection
21pub enum Error {
22    /// An error occurred while serializing or deserializing keys emitted in a view.
23    #[error("error serializing view keys {0}")]
24    KeySerialization(Box<dyn AnyError>),
25
26    /// An error unrelated to views.
27    #[error("core error: {0}")]
28    Core(#[from] crate::Error),
29}
30
31impl Error {
32    /// Returns a [`Self::KeySerialization`] instance after boxing the error.
33    pub fn key_serialization<E: AnyError>(error: E) -> Self {
34        Self::KeySerialization(Box::new(error))
35    }
36}
37
38impl From<pot::Error> for Error {
39    fn from(err: pot::Error) -> Self {
40        Self::Core(crate::Error::from(err))
41    }
42}
43
44/// A type alias for the result of `ViewSchema::map()`.
45pub type ViewMapResult<'doc, V> = Result<
46    Mappings<<V as ViewSchema>::MappedKey<'doc>, <<V as ViewSchema>::View as View>::Value>,
47    crate::Error,
48>;
49
50/// A type alias for the result of `ViewSchema::reduce()`.
51pub type ReduceResult<V> = Result<<V as View>::Value, crate::Error>;
52
53/// An lazy index of mapped and/or reduced data from a [`Collection`].
54///
55/// A view provides an efficient way to query data within a collection. BonsaiDb
56/// indexes the associated [`View::Collection`] by calling
57/// [`CollectionMapReduce::map()`]/[`MapReduce::map()`] every time a document is
58/// created or updated. The result [`Mappings`] form a sorted index that can be
59/// efficiently queried using the [`View::Key`] type.
60///
61/// A View behaves similarly to the standard library's BTreeMap with these
62/// types: `BTreeMap<View::Key, Vec<(Header, View::Value)>>`
63///
64/// This trait only defines the types and functionality required to interact
65/// with the view's query system. The [`MapReduce`]/[`CollectionMapReduce`]
66/// traits define the map/reduce behavior, and the [`ViewSchema`] trait defines
67/// additional metadata such as the [`ViewUpdatePolicy`] and view version.
68///
69/// For a deeper dive on Views, see [the section in our user's
70/// guide](https://dev.bonsaidb.io/main/guide/about/concepts/view.html).
71#[doc = "\n"]
72#[doc = include_str!("./view-overview.md")]
73pub trait View: Sized + Send + Sync + 'static {
74    /// The collection this view belongs to
75    type Collection: Collection;
76    /// The key for this view.
77    type Key: for<'k> Key<'k> + PartialEq + 'static;
78    /// An associated type that can be stored with each entry in the view.
79    type Value: Send + Sync;
80
81    /// The name of the view. Must be unique per collection.
82    fn name(&self) -> Name;
83
84    /// The namespaced name of the view.
85    fn view_name(&self) -> ViewName {
86        ViewName {
87            collection: Self::Collection::collection_name(),
88            name: self.name(),
89        }
90    }
91}
92
93/// Schema information for a [`View`].
94///
95/// This trait controls several behaviors for a view:
96///
97/// - [`MappedKey<'doc>`](Self::MappedKey): A [`Key`] type that is compatible
98///   with the associated [`View::Key`] type, but can borrow from the document
99///   by using the `'doc` generic associated lifetime.
100/// - [`update_policy()`](Self::update_policy): Controls when the view's data is
101///   updated and any restrictions on the data contained in the view. The
102///   default policy for views is [`ViewUpdatePolicy`]
103/// - [`version()`](Self::version): An integer representing the view's version.
104///   Changing this number will cause the view to be re-indexed. This is useful
105///   when there are fundamental changes in how the view is implemented.
106///
107/// ## Where is this trait used?
108///
109/// This trait is currently only used by `bonsaidb-local`, but is provided in
110/// `bonsaidb-core` to allow it to be used anywhere. It may be desireable to
111/// keep the implementation of `ViewSchema` in the "server" binary, while only
112/// exposing the `View` implementation to the "client".
113///
114/// ## Deriving this Trait
115///
116/// This trait can be derived, and all attributes are optional. The available
117/// options are:
118///
119/// - `view`: Sets the associated [`View`](Self::View) type. If not provided,
120///   `Self` will be used.
121/// - `version`: Sets the [version number](Self::version) of the view. If not
122///   provided, `0` will be used.
123/// - `mapped_key`: Sets the [`MappedKey<'doc>`](Self::MappedKey) type to the
124///   provided type. The `'doc` lifetime can be utilized to borrow data during
125///   the [`MapReduce::map()`] function.
126///
127///   If not provided, the `ViewSchema` implementation uses [`View::Key`].
128/// - `policy`: Sets the [`ViewUpdatePolicy`]. The accepted policies are:
129///   - [`Lazy`](ViewUpdatePolicy::Lazy)
130///   - [`Eager`](ViewUpdatePolicy::Eager)
131///   - [`Unique`](ViewUpdatePolicy::Unique)
132///
133///   If not provided, the [`Lazy`](ViewUpdatePolicy::Lazy) policy will be used.
134///
135/// Here is an example that showcases most of the options:
136/// ```rust
137/// # mod collection {
138/// # bonsaidb_core::__doctest_prelude!();
139/// # }
140/// # use collection::MyCollection;
141/// use std::borrow::Cow;
142///
143/// use bonsaidb_core::document::{BorrowedDocument, Emit};
144/// use bonsaidb_core::schema::view::{ReduceResult, ViewMapResult};
145/// use bonsaidb_core::schema::{MapReduce, View, ViewMappedValue, ViewSchema};
146///
147/// #[derive(View, ViewSchema)]
148/// #[view(collection = MyCollection, key = String, value = u32)]
149/// #[view_schema(mapped_key = Cow<'doc, str>, policy = Unique)]
150/// # #[view(core = bonsaidb_core)]
151/// # #[view_schema(core = bonsaidb_core)]
152/// struct UniqueByName;
153///
154/// impl MapReduce for UniqueByName {
155///     fn map<'doc>(&self, document: &'doc BorrowedDocument<'_>) -> ViewMapResult<'doc, Self> {
156///         let contents_as_str = std::str::from_utf8(&document.contents).expect("invalid utf-8");
157///         document
158///             .header
159///             .emit_key_and_value(Cow::Borrowed(contents_as_str), 1)
160///     }
161///
162///     fn reduce(
163///         &self,
164///         mappings: &[ViewMappedValue<'_, Self::View>],
165///         _rereduce: bool,
166///     ) -> ReduceResult<Self::View> {
167///         Ok(mappings.iter().map(|mapping| mapping.value).sum())
168///     }
169/// }
170/// ```
171#[doc = "\n"]
172#[doc = include_str!("./view-overview.md")]
173pub trait ViewSchema: Send + Sync + 'static {
174    /// The view this schema is defined for.
175    type View: SerializedView;
176    /// The key type used during the map/reduce operation.
177    ///
178    /// This can typically be specified as `<Self::View as View>::Key`. However,
179    /// if the view can take advantage of utilizing borrowed data from the
180    /// document in the `map()` and/or `reduce()` function calls, this type can
181    /// utilize the generic associated lifetime `'doc`. For example, `Cow<'doc,
182    /// str>` can be used when the related [`View::Key`] type is `String`, and
183    /// the `map()` function would be able to return a string slice that
184    /// borrowed from the document.
185    type MappedKey<'doc>: Key<'doc>;
186
187    /// Returns the update policy for this view, which controls when and how the
188    /// view's data is updated. The provided implementation returns
189    /// [`ViewUpdatePolicy::Lazy`].
190    fn update_policy(&self) -> ViewUpdatePolicy {
191        ViewUpdatePolicy::default()
192    }
193
194    /// The version of the view. Changing this value will cause indexes to be
195    /// rebuilt.
196    fn version(&self) -> u64 {
197        0
198    }
199}
200
201/// The policy under which a [`View`] is updated when documents are saved.
202#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
203pub enum ViewUpdatePolicy {
204    /// The view is updated when a query is made. If a document is updated
205    /// multiple times between queries, the view will only be updated when the
206    /// query is executed.
207    #[default]
208    Lazy,
209    /// The view is updated during the transaction where documents are being
210    /// inserted, updated, or removed.
211    Eager,
212    /// No two documents may emit the same key. Unique views are updated when
213    /// the document is saved, allowing for this check to be done atomically.
214    /// When a document is updated, all unique views will be updated, and if any
215    /// of them fail, the document will not be allowed to update and an
216    /// [`Error::UniqueKeyViolation`](crate::Error::UniqueKeyViolation) will be
217    /// returned.
218    Unique,
219}
220
221impl ViewUpdatePolicy {
222    /// Returns true if the view should be updated eagerly.
223    ///
224    /// This returns true if the policy is either [`Eager`](Self::Eager) or
225    /// [`Unique`](Self::Unique).
226    #[must_use]
227    pub const fn is_eager(&self) -> bool {
228        matches!(self, Self::Eager | Self::Unique)
229    }
230}
231
232impl std::fmt::Display for ViewUpdatePolicy {
233    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234        Debug::fmt(self, f)
235    }
236}
237
238/// The Map/Reduce functionality for a [`ViewSchema`].
239///
240/// This trait implementation provides the behavior for mapping data from
241/// documents into their key/value pairs for this view, as well as reducing
242/// multiple values into a single value.
243pub trait MapReduce: ViewSchema {
244    /// The map function for this view. This function is responsible for
245    /// emitting entries for any documents that should be contained in this
246    /// View. If None is returned, the View will not include the document. See [the user guide's chapter on
247    /// views for more information on how map
248    /// works](https://dev.bonsaidb.io/main/guide/about/concepts/view.html#map).
249    fn map<'doc>(&self, document: &'doc BorrowedDocument<'_>) -> ViewMapResult<'doc, Self>;
250
251    /// Returns a value that is produced by reducing a list of `mappings` into a
252    /// single value. If `rereduce` is true, the values contained in the
253    /// mappings have already been reduced at least one time. If an error of
254    /// [`ReduceUnimplemented`](crate::Error::ReduceUnimplemented) is returned,
255    /// queries that ask for a reduce operation will return an error. See [the
256    /// user guide's chapter on views for more information on how reduce
257    /// works](https://dev.bonsaidb.io/main/guide/about/concepts/view.html#reduce).
258    #[allow(unused_variables)]
259    fn reduce(
260        &self,
261        mappings: &[MappedValue<Self::MappedKey<'_>, <Self::View as View>::Value>],
262        rereduce: bool,
263    ) -> Result<<Self::View as View>::Value, crate::Error> {
264        Err(crate::Error::ReduceUnimplemented)
265    }
266}
267
268/// A [`View`] with additional tyes and logic to handle serializing view values.
269pub trait SerializedView: View {
270    /// The serialization format for this view.
271    type Format: OwnedDeserializer<Self::Value>;
272
273    /// Returns the configured instance of [`Self::Format`].
274    // TODO allow configuration to be passed here, such as max allocation bytes.
275    fn format() -> Self::Format;
276
277    /// Deserialize `data` as `Self::Value` using this views's format.
278    fn deserialize(data: &[u8]) -> Result<Self::Value, crate::Error> {
279        Self::format()
280            .deserialize_owned(data)
281            .map_err(|err| crate::Error::other("serialization", err))
282    }
283
284    /// Serialize `item` using this views's format.
285    fn serialize(item: &Self::Value) -> Result<Vec<u8>, crate::Error> {
286        Self::format()
287            .serialize(item)
288            .map_err(|err| crate::Error::other("serialization", err.to_string()))
289    }
290
291    /// Returns a builder for a view query or view reduce.
292    fn entries<Database: Connection>(
293        database: &Database,
294    ) -> connection::View<'_, Database, Self, Self::Key> {
295        database.view::<Self>()
296    }
297
298    /// Returns a builder for a view query or view reduce.
299    fn entries_async<Database: AsyncConnection>(
300        database: &Database,
301    ) -> connection::AsyncView<'_, Database, Self, Self::Key> {
302        database.view::<Self>()
303    }
304}
305
306/// A default serialization strategy for views. Uses equivalent settings as
307/// [`DefaultSerialization`](crate::schema::DefaultSerialization).
308pub trait DefaultViewSerialization: View {}
309
310impl<T> SerializedView for T
311where
312    T: DefaultViewSerialization,
313    T::Value: Serialize + DeserializeOwned,
314{
315    type Format = Pot;
316
317    fn format() -> Self::Format {
318        Pot::default()
319    }
320}
321
322/// A [`MapReduce`] implementation that automatically serializes/deserializes
323/// using [`CollectionDocument`] and [`SerializedCollection`].
324///
325/// Implementing this trait automatically implements [`ViewSchema`] for the same
326/// type.
327pub trait CollectionMapReduce: ViewSchema
328where
329    <Self::View as View>::Collection: SerializedCollection,
330{
331    /// The map function for this view. This function is responsible for
332    /// emitting entries for any documents that should be contained in this
333    /// View. If None is returned, the View will not include the document.
334    fn map<'doc>(
335        &self,
336        document: CollectionDocument<<Self::View as View>::Collection>,
337    ) -> ViewMapResult<'doc, Self>
338    where
339        CollectionDocument<<Self::View as View>::Collection>: 'doc;
340
341    /// The reduce function for this view. If `Err(Error::ReduceUnimplemented)`
342    /// is returned, queries that ask for a reduce operation will return an
343    /// error. See [`CouchDB`'s Reduce/Rereduce
344    /// documentation](https://docs.couchdb.org/en/stable/ddocs/views/intro.html#reduce-rereduce)
345    /// for the design this implementation will be inspired by
346    #[allow(unused_variables)]
347    fn reduce(
348        &self,
349        mappings: &[ViewMappedValue<'_, Self>],
350        rereduce: bool,
351    ) -> ReduceResult<Self::View> {
352        Err(crate::Error::ReduceUnimplemented)
353    }
354}
355
356impl<T> MapReduce for T
357where
358    T: CollectionMapReduce,
359    T::View: SerializedView,
360    <T::View as View>::Collection: SerializedCollection,
361{
362    fn map<'doc>(&self, document: &'doc BorrowedDocument<'_>) -> ViewMapResult<'doc, Self> {
363        T::map(self, CollectionDocument::try_from(document)?)
364    }
365
366    fn reduce(
367        &self,
368        mappings: &[ViewMappedValue<'_, Self>],
369        rereduce: bool,
370    ) -> Result<<Self::View as View>::Value, crate::Error> {
371        T::reduce(self, mappings, rereduce)
372    }
373}
374
375/// Wraps a [`View`] with serialization to erase the associated types
376pub trait Serialized: Send + Sync {
377    /// Wraps returing [`<View::Collection as Collection>::collection_name()`](crate::schema::Collection::collection_name)
378    fn collection(&self) -> CollectionName;
379    /// Returns the description of the view's `Key`.
380    fn key_description(&self) -> KeyDescription;
381    /// Wraps [`ViewSchema::update_policy`]
382    fn update_policy(&self) -> ViewUpdatePolicy;
383
384    /// Wraps [`ViewSchema::version`]
385    fn version(&self) -> u64;
386    /// Wraps [`View::view_name`]
387    fn view_name(&self) -> ViewName;
388    /// Wraps [`MapReduce::map`]
389    fn map(&self, document: &BorrowedDocument<'_>) -> Result<Vec<map::Serialized>, Error>;
390    /// Wraps [`MapReduce::reduce`]
391    fn reduce(&self, mappings: &[(&[u8], &[u8])], rereduce: bool) -> Result<Vec<u8>, Error>;
392}
393
394/// Defines an unique view named `$view_name` for `$collection` with the
395/// mapping provided.
396#[macro_export(local_inner_macros)]
397macro_rules! define_basic_unique_mapped_view {
398    ($view_name:ident, $collection:ty, $version:literal, $name:literal, $key:ty, $mapping:expr $(,)?) => {
399        define_mapped_view!(
400            $view_name,
401            $collection,
402            $version,
403            $name,
404            $key,
405            (),
406            true,
407            $mapping
408        );
409    };
410    ($view_name:ident, $collection:ty, $version:literal, $name:literal, $key:ty, $value:ty, $mapping:expr $(,)?) => {
411        define_mapped_view!(
412            $view_name,
413            $collection,
414            $version,
415            $name,
416            $key,
417            $value,
418            true,
419            $mapping
420        );
421    };
422}
423
424/// Defines a non-unique view named `$view_name` for `$collection` with the
425/// mapping provided.
426#[macro_export(local_inner_macros)]
427macro_rules! define_basic_mapped_view {
428    ($view_name:ident, $collection:ty, $version:literal, $name:literal, $key:ty, $mapping:expr $(,)?) => {
429        define_mapped_view!(
430            $view_name,
431            $collection,
432            $version,
433            $name,
434            $key,
435            (),
436            false,
437            $mapping
438        );
439    };
440    ($view_name:ident, $collection:ty, $version:literal, $name:literal, $key:ty, $value:ty, $mapping:expr $(,)?) => {
441        define_mapped_view!(
442            $view_name,
443            $collection,
444            $version,
445            $name,
446            $key,
447            $value,
448            false,
449            $mapping
450        );
451    };
452}
453
454/// Defines a view using the mapping provided.
455#[macro_export]
456macro_rules! define_mapped_view {
457    ($view_name:ident, $collection:ty, $version:literal, $name:literal, $key:ty, $value:ty, $unique:literal, $mapping:expr) => {
458        #[derive(Debug, Clone)]
459        pub struct $view_name;
460
461        impl $crate::schema::View for $view_name {
462            type Collection = $collection;
463            type Key = $key;
464            type Value = $value;
465
466            fn name(&self) -> $crate::schema::Name {
467                $crate::schema::Name::new($name)
468            }
469        }
470
471        impl $crate::schema::ViewSchema for $view_name {
472            type MappedKey<'doc> = <Self as $crate::schema::View>::Key;
473            type View = Self;
474
475            fn update_policy(&self) -> $crate::schema::view::ViewUpdatePolicy {
476                if $unique {
477                    $crate::schema::view::ViewUpdatePolicy::Unique
478                } else {
479                    $crate::schema::view::ViewUpdatePolicy::Lazy
480                }
481            }
482
483            fn version(&self) -> u64 {
484                $version
485            }
486        }
487
488        impl $crate::schema::CollectionMapReduce for $view_name {
489            fn map<'doc>(
490                &self,
491                document: $crate::document::CollectionDocument<$collection>,
492            ) -> $crate::schema::ViewMapResult<'doc, Self> {
493                #[allow(clippy::redundant_closure_call)]
494                $mapping(document)
495            }
496        }
497
498        impl $crate::schema::view::DefaultViewSerialization for $view_name {}
499    };
500}