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

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

            
20
use crate::{
21
    config::{Builder, StorageConfiguration},
22
    Database, Storage,
23
};
24

            
25
12
#[derive(Schema, Debug)]
26
#[schema(name = "schema-a", collections = [Basic], core = bonsaidb_core)]
27
struct SchemaA;
28

            
29
20
#[derive(Schema, Debug)]
30
#[schema(name = "schema-b", collections = [Unique, Basic], core = bonsaidb_core)]
31
struct SchemaB;
32

            
33
72
#[derive(Collection, Debug, Serialize, Deserialize)]
34
#[collection(name = "unique", core = bonsaidb_core, views = [UniqueView])]
35
struct Unique {
36
    name: String,
37
}
38

            
39
86
#[derive(View, Debug, Clone)]
40
#[view(collection = Unique, key = String, core = bonsaidb_core)]
41
struct UniqueView;
42

            
43
impl CollectionViewSchema for UniqueView {
44
    type View = UniqueView;
45

            
46
62
    fn unique(&self) -> bool {
47
62
        true
48
62
    }
49

            
50
18
    fn map(
51
18
        &self,
52
18
        document: CollectionDocument<<Self::View as View>::Collection>,
53
18
    ) -> bonsaidb_core::schema::ViewMapResult<Self::View> {
54
18
        document.header.emit_key(document.contents.name)
55
18
    }
56
}
57

            
58
720
#[derive(Collection, Clone, Debug, Serialize, Deserialize)]
59
#[collection(name = "basic", views = [Scores], core = bonsaidb_core)]
60
struct Basic {
61
    key: String,
62
    value: u32,
63
}
64

            
65
216
#[derive(Clone, View, Debug)]
66
#[view(collection = Basic, key = String, value = u32, core = bonsaidb_core)]
67
struct Scores;
68

            
69
impl CollectionViewSchema for Scores {
70
    type View = Scores;
71

            
72
60
    fn map(
73
60
        &self,
74
60
        document: CollectionDocument<<Self::View as View>::Collection>,
75
60
    ) -> bonsaidb_core::schema::ViewMapResult<Self::View> {
76
60
        document
77
60
            .header
78
60
            .emit_key_and_value(document.contents.key, document.contents.value)
79
60
    }
80

            
81
36
    fn reduce(
82
36
        &self,
83
36
        mappings: &[ViewMappedValue<Self::View>],
84
36
        _rereduce: bool,
85
36
    ) -> ReduceResult<Self::View> {
86
60
        Ok(mappings.iter().map(|map| map.value).sum())
87
36
    }
88
}
89

            
90
1
fn create_databases(path: impl AsRef<Path>) {
91
1
    let storage = Storage::open(
92
1
        StorageConfiguration::new(path)
93
1
            .with_schema::<SchemaA>()
94
1
            .unwrap()
95
1
            .with_schema::<SchemaB>()
96
1
            .unwrap(),
97
1
    )
98
1
    .unwrap();
99
1

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

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

            
104
1
    write_unique(&storage.create_database::<SchemaB>("b-1", false).unwrap());
105
1
    write_basic(&storage.database::<SchemaB>("b-1").unwrap());
106
1

            
107
1
    write_unique(&storage.create_database::<SchemaB>("b-2", false).unwrap());
108
1
    write_basic(&storage.database::<SchemaB>("b-2").unwrap());
109
1
    drop(storage);
110
1
}
111

            
112
4
fn write_basic(db: &Database) {
113
4
    db.set_numeric_key("integer", 1_u64).execute().unwrap();
114
4
    db.set_key("string", &"test").execute().unwrap();
115
4
    // Give the kv-store time to persist
116
4
    std::thread::sleep(Duration::from_millis(100));
117
4

            
118
4
    Basic {
119
4
        key: String::from("a"),
120
4
        value: 1,
121
4
    }
122
4
    .push_into(db)
123
4
    .unwrap();
124
4
    Basic {
125
4
        key: String::from("a"),
126
4
        value: 2,
127
4
    }
128
4
    .push_into(db)
129
4
    .unwrap();
130
4
    Basic {
131
4
        key: String::from("b"),
132
4
        value: 3,
133
4
    }
134
4
    .push_into(db)
135
4
    .unwrap();
136
4
    Basic {
137
4
        key: String::from("b"),
138
4
        value: 4,
139
4
    }
140
4
    .push_into(db)
141
4
    .unwrap();
142
4
    Basic {
143
4
        key: String::from("c"),
144
4
        value: 5,
145
4
    }
146
4
    .push_into(db)
147
4
    .unwrap();
148
4
}
149

            
150
2
fn write_unique(db: &Database) {
151
2
    Unique {
152
2
        name: String::from("jon"),
153
2
    }
154
2
    .push_into(db)
155
2
    .unwrap();
156
2
    Unique {
157
2
        name: String::from("jane"),
158
2
    }
159
2
    .push_into(db)
160
2
    .unwrap();
161
2
}
162

            
163
3
fn test_databases(path: impl AsRef<Path>) {
164
3
    let storage = Storage::open(
165
3
        StorageConfiguration::new(path)
166
3
            .with_schema::<SchemaA>()
167
3
            .unwrap()
168
3
            .with_schema::<SchemaB>()
169
3
            .unwrap(),
170
3
    )
171
3
    .unwrap();
172
3

            
173
3
    test_basic(&storage.database::<SchemaA>("a-1").unwrap());
174
3
    test_basic(&storage.database::<SchemaA>("a-2").unwrap());
175
3
    test_unique(&storage.database::<SchemaB>("b-1").unwrap());
176
3
    test_basic(&storage.database::<SchemaB>("b-1").unwrap());
177
3
    test_unique(&storage.database::<SchemaB>("b-2").unwrap());
178
3
    test_basic(&storage.database::<SchemaB>("b-2").unwrap());
179
3
}
180

            
181
12
fn test_basic(db: &Database) {
182
12
    assert_eq!(db.get_key("integer").into_u64().unwrap(), Some(1));
183
12
    assert_eq!(
184
12
        db.get_key("string").into::<String>().unwrap().as_deref(),
185
12
        Some("test")
186
12
    );
187

            
188
12
    let all_docs = Basic::all(db).query().unwrap();
189
12
    assert_eq!(all_docs.len(), 5);
190

            
191
12
    let a_scores = db
192
12
        .view::<Scores>()
193
12
        .with_key(String::from("a"))
194
12
        .query_with_collection_docs()
195
12
        .unwrap();
196
12
    assert_eq!(a_scores.mappings.len(), 2);
197
12
    assert_eq!(a_scores.documents.len(), 2);
198

            
199
36
    for mapping in db.view::<Scores>().reduce_grouped().unwrap() {
200
36
        let expected_value = match mapping.key.as_str() {
201
36
            "a" => 3,
202
24
            "b" => 7,
203
12
            "c" => 5,
204
            _ => unreachable!(),
205
        };
206
36
        assert_eq!(mapping.value, expected_value);
207
    }
208

            
209
12
    let transactions = db.list_executed_transactions(None, None).unwrap();
210
12
    let kv_transactions = transactions
211
12
        .iter()
212
96
        .filter_map(|t| t.changes.keys())
213
12
        .collect::<Vec<_>>();
214
12
    assert_eq!(kv_transactions.len(), 2);
215
12
    let keys = kv_transactions
216
12
        .iter()
217
24
        .flat_map(|changed_keys| {
218
24
            changed_keys
219
24
                .iter()
220
24
                .map(|changed_key| changed_key.key.as_str())
221
24
        })
222
12
        .collect::<HashSet<_>>();
223
12
    assert_eq!(keys.len(), 2);
224
12
    assert!(keys.contains("string"));
225
12
    assert!(keys.contains("integer"));
226

            
227
96
    let basic_transactions = transactions.iter().filter_map(|t| {
228
96
        t.changes.documents().and_then(|changes| {
229
72
            changes
230
72
                .collections
231
72
                .contains(&Basic::collection_name())
232
72
                .then(|| &changes.documents)
233
96
        })
234
96
    });
235
12
    assert_eq!(basic_transactions.count(), 5);
236
12
}
237

            
238
6
fn test_unique(db: &Database) {
239
6
    // Attempt to violate a unique key violation before accessing the view. This
240
6
    // tests the upgrade fpath for view verisons, if things have changed.
241
6
    Unique {
242
6
        name: String::from("jon"),
243
6
    }
244
6
    .push_into(db)
245
6
    .unwrap_err();
246
6
    let mappings = db
247
6
        .view::<UniqueView>()
248
6
        .with_key(String::from("jane"))
249
6
        .query_with_collection_docs()
250
6
        .unwrap();
251
6
    assert_eq!(mappings.len(), 1);
252
6
    let jane = mappings.into_iter().next().unwrap().document;
253
6
    assert_eq!(jane.contents.name, "jane");
254
6
}
255

            
256
2
fn test_compatibility(dir: &str) {
257
2
    let project_dir = PathBuf::from(
258
2
        std::env::var("CARGO_MANIFEST_DIR")
259
2
            .unwrap_or_else(|_| String::from("./crates/bonsaidb-local")),
260
2
    );
261
2

            
262
2
    let test_dir = TestDirectory::new(format!("v{}-compatibility.nebari", dir));
263
2
    dir::copy(
264
2
        project_dir
265
2
            .join("src")
266
2
            .join("tests")
267
2
            .join("compatibility")
268
2
            .join(dir),
269
2
        &test_dir,
270
2
        &dir::CopyOptions {
271
2
            content_only: true,
272
2
            copy_inside: true,
273
2
            ..dir::CopyOptions::default()
274
2
        },
275
2
    )
276
2
    .unwrap();
277
2

            
278
2
    test_databases(&test_dir);
279
2
}
280

            
281
1
#[test]
282
1
fn self_compatibility() {
283
1
    let dir = TestDirectory::new("self-compatibiltiy.bonsaidb");
284
1
    create_databases(&dir);
285
1
    test_databases(&dir);
286
1
    if std::env::var("UPDATE_COMPATIBILITY")
287
1
        .map(|v| !v.is_empty())
288
1
        .unwrap_or_default()
289
    {
290
1
        let version = format!(
291
1
            "{}.{}",
292
1
            std::env::var("CARGO_PKG_VERSION_MAJOR").unwrap(),
293
1
            std::env::var("CARGO_PKG_VERSION_MINOR").unwrap()
294
1
        );
295
1
        let path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
296
1
        let version_path = path
297
1
            .join("src")
298
1
            .join("tests")
299
1
            .join("compatibility")
300
1
            .join(version);
301
1
        if version_path.exists() {
302
            std::fs::remove_dir_all(&version_path).unwrap();
303
1
        }
304
1
        dir::copy(
305
1
            &dir,
306
1
            version_path,
307
1
            &dir::CopyOptions {
308
1
                content_only: true,
309
1
                copy_inside: true,
310
1
                ..dir::CopyOptions::default()
311
1
            },
312
1
        )
313
1
        .unwrap();
314
    }
315
1
}
316

            
317
1
#[test]
318
1
fn compatible_with_0_1_x() {
319
1
    test_compatibility("0.1");
320
1
}
321

            
322
1
#[test]
323
1
fn compatible_with_0_2_x() {
324
1
    test_compatibility("0.2");
325
1
}