1
use bonsaidb::core::document::{CollectionDocument, Emit};
2
use bonsaidb::core::schema::{
3
    Collection, CollectionMapReduce, ReduceResult, SerializedCollection, SerializedView, View,
4
    ViewMapResult, ViewMappedValue, ViewSchema,
5
};
6
use bonsaidb::local::config::{Builder, StorageConfiguration};
7
use bonsaidb::local::AsyncDatabase;
8
use serde::{Deserialize, Serialize};
9

            
10
// begin rustme snippet: snippet-a
11
72
#[derive(Debug, Serialize, Deserialize, Collection)]
12
#[collection(name = "shapes", views = [ShapesByNumberOfSides])]
13
struct Shape {
14
    pub sides: u32,
15
}
16

            
17
74
#[derive(Debug, Clone, View, ViewSchema)]
18
#[view(collection = Shape, key = u32, value = usize, name = "by-number-of-sides")]
19
struct ShapesByNumberOfSides;
20

            
21
impl CollectionMapReduce for ShapesByNumberOfSides {
22
21
    fn map<'doc>(&self, document: CollectionDocument<Shape>) -> ViewMapResult<'doc, Self::View> {
23
21
        document
24
21
            .header
25
21
            .emit_key_and_value(document.contents.sides, 1)
26
21
    }
27

            
28
20
    fn reduce(
29
20
        &self,
30
20
        mappings: &[ViewMappedValue<Self>],
31
20
        _rereduce: bool,
32
20
    ) -> ReduceResult<Self::View> {
33
33
        Ok(mappings.iter().map(|m| m.value).sum())
34
20
    }
35
}
36
// end rustme snippet
37

            
38
#[tokio::main]
39
1
async fn main() -> Result<(), bonsaidb::core::Error> {
40
    // begin rustme snippet: snippet-b
41
1
    let db =
42
1
        AsyncDatabase::open::<Shape>(StorageConfiguration::new("view-examples.bonsaidb")).await?;
43

            
44
    // Insert a new document into the Shape collection.
45
1
    Shape { sides: 3 }.push_into_async(&db).await?;
46
    // end rustme snippet
47

            
48
    // Views in BonsaiDb are written using a Map/Reduce approach. In this
49
    // example, we take a look at how document mapping can be used to filter and
50
    // retrieve data
51
    //
52
    // Let's start by seeding the database with some shapes of various sizes:
53
19
    for sides in 3..=20 {
54
18
        Shape { sides }.push_into_async(&db).await?;
55
    }
56

            
57
    // And, let's add a few shapes with the same number of sides
58
1
    Shape { sides: 3 }.push_into_async(&db).await?;
59
1
    Shape { sides: 4 }.push_into_async(&db).await?;
60

            
61
    // At this point, our database should have 3 triangles:
62
    // begin rustme snippet: snippet-c
63
1
    let triangles = ShapesByNumberOfSides::entries_async(&db)
64
1
        .with_key(&3)
65
1
        .query()
66
1
        .await?;
67
1
    println!("Number of triangles: {}", triangles.len());
68
1
    // end rustme snippet
69
1

            
70
1
    // What is returned is a list of entries containing the document id
71
1
    // (source), the key of the entry, and the value of the entry:
72
1
    println!("Triangles: {triangles:#?}");
73

            
74
    // If you want the associated documents, use query_with_collection_docs:
75
3
    for entry in &ShapesByNumberOfSides::entries_async(&db)
76
1
        .with_key(&3)
77
1
        .query_with_collection_docs()
78
2
        .await?
79
3
    {
80
3
        println!(
81
3
            "Shape ID {} has {} sides",
82
3
            entry.document.header.id, entry.document.contents.sides
83
3
        );
84
3
    }
85

            
86
    // The reduce() function takes the "values" emitted during the map()
87
    // function, and reduces a list down to a single value. In this example, the
88
    // reduce function is acting as a count. So, if you want to query for the
89
    // number of shapes, we don't need to fetch all the records, we can just
90
    // retrieve the result of the calculation directly.
91
    //
92
    // So, here we're using reduce() to count the number of shapes with 4 sides.
93
1
    println!(
94
1
        "Number of quads: {} (expected 2)",
95
1
        ShapesByNumberOfSides::entries_async(&db)
96
1
            .with_key(&4)
97
1
            .reduce()
98
1
            .await?
99
    );
100

            
101
    // Or, 5 shapes that are triangles or quads
102
1
    println!(
103
1
        "Number of quads and triangles: {} (expected 5)",
104
1
        ShapesByNumberOfSides::entries_async(&db)
105
1
            .with_keys(&[3, 4])
106
1
            .reduce()
107
1
            .await?
108
    );
109

            
110
    // And, 10 shapes that have more than 10 sides
111
1
    println!(
112
1
        "Number of shapes with more than 10 sides: {} (expected 10)",
113
1
        ShapesByNumberOfSides::entries_async(&db)
114
1
            .with_key_range(11..)
115
1
            .reduce()
116
1
            .await?
117
    );
118

            
119
1
    Ok(())
120
}
121

            
122
1
#[test]
123
1
fn runs() {
124
1
    drop(std::fs::remove_dir_all("view-examples.bonsaidb"));
125
1
    main().unwrap()
126
1
}