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}