1
use std::collections::HashSet;
2
use std::path::{Path, PathBuf};
3
use std::time::Duration;
4

            
5
use bonsaidb_core::connection::{Connection, StorageConnection};
6
use bonsaidb_core::document::{CollectionDocument, Emit};
7
use bonsaidb_core::keyvalue::KeyValue;
8
use bonsaidb_core::schema::{
9
    Collection, CollectionMapReduce, ReduceResult, Schema, SerializedCollection, View,
10
    ViewMappedValue, ViewSchema,
11
};
12
use bonsaidb_core::test_util::TestDirectory;
13
use fs_extra::dir;
14
use serde::{Deserialize, Serialize};
15

            
16
use crate::config::{Builder, StorageConfiguration};
17
use crate::{Database, Storage};
18

            
19
12
#[derive(Schema, Debug)]
20
#[schema(name = "schema-a", collections = [Basic], core = bonsaidb_core)]
21
struct SchemaA;
22

            
23
20
#[derive(Schema, Debug)]
24
#[schema(name = "schema-b", collections = [Unique, Basic], core = bonsaidb_core)]
25
struct SchemaB;
26

            
27
72
#[derive(Collection, Debug, Serialize, Deserialize)]
28
#[collection(name = "unique", core = bonsaidb_core, views = [UniqueView])]
29
struct Unique {
30
    name: String,
31
}
32

            
33
86
#[derive(View, Debug, Clone, ViewSchema)]
34
#[view(collection = Unique, key = String, core = bonsaidb_core)]
35
#[view_schema(core = bonsaidb_core, policy = Unique)]
36
struct UniqueView;
37

            
38
impl CollectionMapReduce for UniqueView {
39
18
    fn map<'doc>(
40
18
        &self,
41
18
        document: CollectionDocument<<Self::View as View>::Collection>,
42
18
    ) -> bonsaidb_core::schema::ViewMapResult<'doc, Self> {
43
18
        document.header.emit_key(document.contents.name)
44
18
    }
45
}
46

            
47
720
#[derive(Collection, Clone, Debug, Serialize, Deserialize)]
48
#[collection(name = "basic", views = [Scores], core = bonsaidb_core)]
49
struct Basic {
50
    key: String,
51
    value: u32,
52
}
53

            
54
216
#[derive(Clone, View, ViewSchema, Debug)]
55
#[view(collection = Basic, key = String, value = u32, core = bonsaidb_core)]
56
#[view_schema(core = bonsaidb_core)]
57
struct Scores;
58

            
59
impl CollectionMapReduce for Scores {
60
60
    fn map<'doc>(
61
60
        &self,
62
60
        document: CollectionDocument<<Self::View as View>::Collection>,
63
60
    ) -> bonsaidb_core::schema::ViewMapResult<'doc, Self> {
64
60
        document
65
60
            .header
66
60
            .emit_key_and_value(document.contents.key, document.contents.value)
67
60
    }
68

            
69
36
    fn reduce(
70
36
        &self,
71
36
        mappings: &[ViewMappedValue<'_, Self::View>],
72
36
        _rereduce: bool,
73
36
    ) -> ReduceResult<Self::View> {
74
60
        Ok(mappings.iter().map(|map| map.value).sum())
75
36
    }
76
}
77

            
78
1
fn create_databases(path: impl AsRef<Path>) {
79
1
    let storage = Storage::open(
80
1
        StorageConfiguration::new(path)
81
1
            .with_schema::<SchemaA>()
82
1
            .unwrap()
83
1
            .with_schema::<SchemaB>()
84
1
            .unwrap(),
85
1
    )
86
1
    .unwrap();
87
1

            
88
1
    write_basic(&storage.create_database::<SchemaA>("a-1", false).unwrap());
89
1

            
90
1
    write_basic(&storage.create_database::<SchemaA>("a-2", false).unwrap());
91
1

            
92
1
    write_unique(&storage.create_database::<SchemaB>("b-1", false).unwrap());
93
1
    write_basic(&storage.database::<SchemaB>("b-1").unwrap());
94
1

            
95
1
    write_unique(&storage.create_database::<SchemaB>("b-2", false).unwrap());
96
1
    write_basic(&storage.database::<SchemaB>("b-2").unwrap());
97
1
    drop(storage);
98
1
}
99

            
100
4
fn write_basic(db: &Database) {
101
4
    db.set_numeric_key("integer", 1_u64).execute().unwrap();
102
4
    db.set_key("string", &"test").execute().unwrap();
103
4
    // Give the kv-store time to persist
104
4
    std::thread::sleep(Duration::from_millis(100));
105
4

            
106
4
    Basic {
107
4
        key: String::from("a"),
108
4
        value: 1,
109
4
    }
110
4
    .push_into(db)
111
4
    .unwrap();
112
4
    Basic {
113
4
        key: String::from("a"),
114
4
        value: 2,
115
4
    }
116
4
    .push_into(db)
117
4
    .unwrap();
118
4
    Basic {
119
4
        key: String::from("b"),
120
4
        value: 3,
121
4
    }
122
4
    .push_into(db)
123
4
    .unwrap();
124
4
    Basic {
125
4
        key: String::from("b"),
126
4
        value: 4,
127
4
    }
128
4
    .push_into(db)
129
4
    .unwrap();
130
4
    Basic {
131
4
        key: String::from("c"),
132
4
        value: 5,
133
4
    }
134
4
    .push_into(db)
135
4
    .unwrap();
136
4
}
137

            
138
2
fn write_unique(db: &Database) {
139
2
    Unique {
140
2
        name: String::from("jon"),
141
2
    }
142
2
    .push_into(db)
143
2
    .unwrap();
144
2
    Unique {
145
2
        name: String::from("jane"),
146
2
    }
147
2
    .push_into(db)
148
2
    .unwrap();
149
2
}
150

            
151
3
fn test_databases(path: impl AsRef<Path>) {
152
3
    let storage = Storage::open(
153
3
        StorageConfiguration::new(path)
154
3
            .with_schema::<SchemaA>()
155
3
            .unwrap()
156
3
            .with_schema::<SchemaB>()
157
3
            .unwrap(),
158
3
    )
159
3
    .unwrap();
160
3

            
161
3
    test_basic(&storage.database::<SchemaA>("a-1").unwrap());
162
3
    test_basic(&storage.database::<SchemaA>("a-2").unwrap());
163
3
    test_unique(&storage.database::<SchemaB>("b-1").unwrap());
164
3
    test_basic(&storage.database::<SchemaB>("b-1").unwrap());
165
3
    test_unique(&storage.database::<SchemaB>("b-2").unwrap());
166
3
    test_basic(&storage.database::<SchemaB>("b-2").unwrap());
167
3
}
168

            
169
12
fn test_basic(db: &Database) {
170
12
    assert_eq!(db.get_key("integer").into_u64().unwrap(), Some(1));
171
12
    assert_eq!(
172
12
        db.get_key("string").into::<String>().unwrap().as_deref(),
173
12
        Some("test")
174
12
    );
175

            
176
12
    let all_docs = Basic::all(db).query().unwrap();
177
12
    assert_eq!(all_docs.len(), 5);
178

            
179
12
    let a_scores = db
180
12
        .view::<Scores>()
181
12
        .with_key("a")
182
12
        .query_with_collection_docs()
183
12
        .unwrap();
184
12
    assert_eq!(a_scores.mappings.len(), 2);
185
12
    assert_eq!(a_scores.documents.len(), 2);
186

            
187
36
    for mapping in db.view::<Scores>().reduce_grouped().unwrap() {
188
36
        let expected_value = match mapping.key.as_str() {
189
36
            "a" => 3,
190
24
            "b" => 7,
191
12
            "c" => 5,
192
            _ => unreachable!(),
193
        };
194
36
        assert_eq!(mapping.value, expected_value);
195
    }
196

            
197
12
    let transactions = db.list_executed_transactions(None, None).unwrap();
198
12
    let kv_transactions = transactions
199
12
        .iter()
200
96
        .filter_map(|t| t.changes.keys())
201
12
        .collect::<Vec<_>>();
202
12
    assert_eq!(kv_transactions.len(), 2);
203
12
    let keys = kv_transactions
204
12
        .iter()
205
24
        .flat_map(|changed_keys| {
206
24
            changed_keys
207
24
                .iter()
208
24
                .map(|changed_key| changed_key.key.as_str())
209
24
        })
210
12
        .collect::<HashSet<_>>();
211
12
    assert_eq!(keys.len(), 2);
212
12
    assert!(keys.contains("string"));
213
12
    assert!(keys.contains("integer"));
214

            
215
96
    let basic_transactions = transactions.iter().filter_map(|t| {
216
96
        t.changes.documents().and_then(|changes| {
217
72
            changes
218
72
                .collections
219
72
                .contains(&Basic::collection_name())
220
72
                .then_some(&changes.documents)
221
96
        })
222
96
    });
223
12
    assert_eq!(basic_transactions.count(), 5);
224
12
}
225

            
226
6
fn test_unique(db: &Database) {
227
6
    // Attempt to violate a unique key violation before accessing the view. This
228
6
    // tests the upgrade fpath for view verisons, if things have changed.
229
6
    Unique {
230
6
        name: String::from("jon"),
231
6
    }
232
6
    .push_into(db)
233
6
    .unwrap_err();
234
6
    let mappings = db
235
6
        .view::<UniqueView>()
236
6
        .with_key("jane")
237
6
        .query_with_collection_docs()
238
6
        .unwrap();
239
6
    assert_eq!(mappings.len(), 1);
240
6
    let jane = mappings.into_iter().next().unwrap().document;
241
6
    assert_eq!(jane.contents.name, "jane");
242
6
}
243

            
244
2
fn test_compatibility(dir: &str) {
245
2
    let project_dir = PathBuf::from(
246
2
        std::env::var("CARGO_MANIFEST_DIR")
247
2
            .unwrap_or_else(|_| String::from("./crates/bonsaidb-local")),
248
2
    );
249
2

            
250
2
    let test_dir = TestDirectory::new(format!("v{dir}-compatibility.nebari"));
251
2
    dir::copy(
252
2
        project_dir
253
2
            .join("src")
254
2
            .join("tests")
255
2
            .join("compatibility")
256
2
            .join(dir),
257
2
        &test_dir,
258
2
        &dir::CopyOptions {
259
2
            content_only: true,
260
2
            copy_inside: true,
261
2
            ..dir::CopyOptions::default()
262
2
        },
263
2
    )
264
2
    .unwrap();
265
2

            
266
2
    test_databases(&test_dir);
267
2
}
268

            
269
1
#[test]
270
1
fn self_compatibility() {
271
1
    let dir = TestDirectory::new("self-compatibiltiy.bonsaidb");
272
1
    create_databases(&dir);
273
1
    test_databases(&dir);
274
1
    if std::env::var("UPDATE_COMPATIBILITY")
275
1
        .map(|v| !v.is_empty())
276
1
        .unwrap_or_default()
277
    {
278
1
        let version = format!(
279
1
            "{}.{}",
280
1
            std::env::var("CARGO_PKG_VERSION_MAJOR").unwrap(),
281
1
            std::env::var("CARGO_PKG_VERSION_MINOR").unwrap()
282
1
        );
283
1
        let path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
284
1
        let version_path = path
285
1
            .join("src")
286
1
            .join("tests")
287
1
            .join("compatibility")
288
1
            .join(version);
289
1
        if version_path.exists() {
290
            std::fs::remove_dir_all(&version_path).unwrap();
291
1
        }
292
1
        dir::copy(
293
1
            &dir,
294
1
            version_path,
295
1
            &dir::CopyOptions {
296
1
                content_only: true,
297
1
                copy_inside: true,
298
1
                ..dir::CopyOptions::default()
299
1
            },
300
1
        )
301
1
        .unwrap();
302
    }
303
1
}
304

            
305
1
#[test]
306
1
fn compatible_with_0_1_x() {
307
1
    test_compatibility("0.1");
308
1
}
309

            
310
1
#[test]
311
1
fn compatible_with_0_2_x() {
312
1
    test_compatibility("0.2");
313
1
}