1
use std::fmt::Debug;
2

            
3
use serde::de::DeserializeOwned;
4
use serde::{Deserialize, Serialize};
5
use transmog::{Format, OwnedDeserializer};
6
use transmog_pot::Pot;
7

            
8
use crate::connection::{self, AsyncConnection, Connection};
9
use crate::document::{BorrowedDocument, CollectionDocument};
10
use crate::key::{ByteSource, Key, KeyDescription};
11
use crate::schema::view::map::{MappedValue, Mappings, ViewMappedValue};
12
use crate::schema::{Collection, CollectionName, Name, SerializedCollection, ViewName};
13
use crate::AnyError;
14

            
15
/// Types for defining a `Map` within a `View`.
16
pub mod map;
17

            
18
/// Errors that arise when interacting with views.
19
#[derive(thiserror::Error, Debug)]
20
// TODO add which view name and collection
21
pub 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

            
31
impl 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

            
38
impl 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()`.
45
pub 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()`.
51
pub 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")]
73
pub 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
24774896
    fn view_name(&self) -> ViewName {
86
24774896
        ViewName {
87
24774896
            collection: Self::Collection::collection_name(),
88
24774896
            name: self.name(),
89
24774896
        }
90
24774896
    }
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")]
173
pub 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
18664693
    fn update_policy(&self) -> ViewUpdatePolicy {
191
18664693
        ViewUpdatePolicy::default()
192
18664693
    }
193

            
194
    /// The version of the view. Changing this value will cause indexes to be
195
    /// rebuilt.
196
8873
    fn version(&self) -> u64 {
197
8873
        0
198
8873
    }
199
}
200

            
201
/// The policy under which a [`View`] is updated when documents are saved.
202
18664693
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
203
pub 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

            
221
impl 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
27475086
    pub const fn is_eager(&self) -> bool {
228
27475086
        matches!(self, Self::Eager | Self::Unique)
229
27475086
    }
230
}
231

            
232
impl 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.
243
pub 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
1960
    fn reduce(
260
1960
        &self,
261
1960
        mappings: &[MappedValue<Self::MappedKey<'_>, <Self::View as View>::Value>],
262
1960
        rereduce: bool,
263
1960
    ) -> Result<<Self::View as View>::Value, crate::Error> {
264
1960
        Err(crate::Error::ReduceUnimplemented)
265
1960
    }
266
}
267

            
268
/// A [`View`] with additional tyes and logic to handle serializing view values.
269
pub 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
161004858
    fn deserialize(data: &[u8]) -> Result<Self::Value, crate::Error> {
279
161004858
        Self::format()
280
161004858
            .deserialize_owned(data)
281
161004858
            .map_err(|err| crate::Error::other("serialization", err))
282
161004858
    }
283

            
284
    /// Serialize `item` using this views's format.
285
1073305
    fn serialize(item: &Self::Value) -> Result<Vec<u8>, crate::Error> {
286
1073305
        Self::format()
287
1073305
            .serialize(item)
288
1073305
            .map_err(|err| crate::Error::other("serialization", err.to_string()))
289
1073305
    }
290

            
291
    /// Returns a builder for a view query or view reduce.
292
16
    fn entries<Database: Connection>(
293
16
        database: &Database,
294
16
    ) -> connection::View<'_, Database, Self, Self::Key> {
295
16
        database.view::<Self>()
296
16
    }
297

            
298
    /// Returns a builder for a view query or view reduce.
299
9
    fn entries_async<Database: AsyncConnection>(
300
9
        database: &Database,
301
9
    ) -> connection::AsyncView<'_, Database, Self, Self::Key> {
302
9
        database.view::<Self>()
303
9
    }
304
}
305

            
306
/// A default serialization strategy for views. Uses equivalent settings as
307
/// [`DefaultSerialization`](crate::schema::DefaultSerialization).
308
pub trait DefaultViewSerialization: View {}
309

            
310
impl<T> SerializedView for T
311
where
312
    T: DefaultViewSerialization,
313
    T::Value: Serialize + DeserializeOwned,
314
{
315
    type Format = Pot;
316

            
317
162077731
    fn format() -> Self::Format {
318
162077731
        Pot::default()
319
162077731
    }
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.
327
pub trait CollectionMapReduce: ViewSchema
328
where
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
55927
    fn reduce(
348
55927
        &self,
349
55927
        mappings: &[ViewMappedValue<'_, Self>],
350
55927
        rereduce: bool,
351
55927
    ) -> ReduceResult<Self::View> {
352
55927
        Err(crate::Error::ReduceUnimplemented)
353
55927
    }
354
}
355

            
356
impl<T> MapReduce for T
357
where
358
    T: CollectionMapReduce,
359
    T::View: SerializedView,
360
    <T::View as View>::Collection: SerializedCollection,
361
{
362
58526
    fn map<'doc>(&self, document: &'doc BorrowedDocument<'_>) -> ViewMapResult<'doc, Self> {
363
58526
        T::map(self, CollectionDocument::try_from(document)?)
364
58526
    }
365

            
366
61456
    fn reduce(
367
61456
        &self,
368
61456
        mappings: &[ViewMappedValue<'_, Self>],
369
61456
        rereduce: bool,
370
61456
    ) -> Result<<Self::View as View>::Value, crate::Error> {
371
61456
        T::reduce(self, mappings, rereduce)
372
61456
    }
373
}
374

            
375
/// Wraps a [`View`] with serialization to erase the associated types
376
pub 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)]
397
macro_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)]
427
macro_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]
456
macro_rules! define_mapped_view {
457
    ($view_name:ident, $collection:ty, $version:literal, $name:literal, $key:ty, $value:ty, $unique:literal, $mapping:expr) => {
458
506389
        #[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
1015184
            fn name(&self) -> $crate::schema::Name {
467
1015184
                $crate::schema::Name::new($name)
468
1015184
            }
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
709282
                    $crate::schema::view::ViewUpdatePolicy::Unique
478
                } else {
479
6718
                    $crate::schema::view::ViewUpdatePolicy::Lazy
480
                }
481
716000
            }
482

            
483
71185
            fn version(&self) -> u64 {
484
71185
                $version
485
71185
            }
486
        }
487

            
488
        impl $crate::schema::CollectionMapReduce for $view_name {
489
56410
            fn map<'doc>(
490
56410
                &self,
491
56410
                document: $crate::document::CollectionDocument<$collection>,
492
56410
            ) -> $crate::schema::ViewMapResult<'doc, Self> {
493
56410
                #[allow(clippy::redundant_closure_call)]
494
56410
                $mapping(document)
495
56410
            }
496
        }
497

            
498
        impl $crate::schema::view::DefaultViewSerialization for $view_name {}
499
    };
500
}