Skip to main content

nimiq_database/mdbx/
mod.rs

1mod cursor;
2mod database;
3mod iterators;
4mod transaction;
5
6pub use self::{cursor::*, database::*, iterators::*, transaction::*};
7use crate::traits::Database;
8
9/// A helper trait that is implemented on `Option<&T>` with `T: AsRef<MdbxReadTransaction<'db>>`.
10/// It allows to use the existing transaction or create a new one.
11/// Example: `let txn = opt_txn.or_new(db);`
12pub trait OptionalTransaction<'db, 'txn> {
13    fn or_new<'inner>(self, db: &'inner MdbxDatabase) -> TransactionProxy<'db, 'txn>
14    where
15        'db: 'txn,
16        'inner: 'db;
17}
18
19impl<'db, 'txn, T: AsRef<MdbxReadTransaction<'db>>> OptionalTransaction<'db, 'txn>
20    for Option<&'txn T>
21{
22    fn or_new<'inner>(self, db: &'inner MdbxDatabase) -> TransactionProxy<'db, 'txn>
23    where
24        'db: 'txn,
25        'inner: 'db,
26    {
27        match self {
28            Some(txn) => TransactionProxy::Read(txn.as_ref()),
29            None => TransactionProxy::OwnedRead(db.read_transaction()),
30        }
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use tempfile::tempdir;
37
38    use super::*;
39    use crate::{
40        declare_table,
41        traits::{Database, DupReadCursor, ReadCursor, ReadTransaction, WriteTransaction},
42    };
43
44    declare_table!(TestTable, "test", String => String);
45    declare_table!(DupTestTable, "dup_test", String => dup(u32));
46    declare_table!(U32DupTable, "u32_dup", u32 => dup(u32));
47    declare_table!(U32Table, "u32_nodup", u32 => u32);
48
49    #[test]
50    fn it_can_save_basic_objects() {
51        let tempdir = tempdir().unwrap();
52        {
53            let db = MdbxDatabase::new(
54                tempdir.path().join("test"),
55                DatabaseConfig {
56                    max_tables: Some(1),
57                    ..Default::default()
58                },
59            )
60            .unwrap();
61            let table = TestTable {};
62            db.create_regular_table(&table);
63
64            // Read non-existent value.
65            {
66                let tx = db.read_transaction();
67                assert!(tx.get(&table, &"test".to_string()).is_none());
68            }
69
70            // Read non-existent value.
71            let mut tx = db.write_transaction();
72            assert!(tx.get(&table, &"test".to_string()).is_none());
73
74            // Write and read value.
75            tx.put(&table, &"test".to_string(), &"one".to_string());
76            assert_eq!(tx.get(&table, &"test".to_string()), Some("one".to_string()));
77            // Overwrite and read value.
78            tx.put(&table, &"test".to_string(), &"two".to_string());
79            assert_eq!(tx.get(&table, &"test".to_string()), Some("two".to_string()));
80            tx.commit();
81
82            // Read value.
83            let tx = db.read_transaction();
84            assert_eq!(tx.get(&table, &"test".to_string()), Some("two".to_string()));
85            tx.close();
86
87            // Remove value.
88            let mut tx = db.write_transaction();
89            tx.remove(&table, &"test".to_string());
90            assert!(tx.get(&table, &"test".to_string()).is_none());
91            tx.commit();
92
93            // Check removal.
94            {
95                let tx = db.read_transaction();
96                assert!(tx.get(&table, &"test".to_string()).is_none());
97            }
98
99            // Write and abort.
100            let mut tx = db.write_transaction();
101            tx.put(&table, &"test".to_string(), &"one".to_string());
102            tx.abort();
103
104            // Check aborted transaction.
105            let tx = db.read_transaction();
106            assert!(tx.get(&table, &"test".to_string()).is_none());
107        }
108    }
109
110    #[test]
111    fn isolation_test() {
112        let tempdir = tempdir().unwrap();
113        {
114            let db = MdbxDatabase::new(
115                tempdir.path().join("test2"),
116                DatabaseConfig {
117                    max_tables: Some(1),
118                    ..Default::default()
119                },
120            )
121            .unwrap();
122            let table = TestTable {};
123            db.create_regular_table(&table);
124
125            // Read non-existent value.
126            let tx = db.read_transaction();
127            assert!(tx.get(&table, &"test".to_string()).is_none());
128
129            // WriteTransaction.
130            let mut txw = db.write_transaction();
131            assert!(txw.get(&table, &"test".to_string()).is_none());
132            txw.put(&table, &"test".to_string(), &"one".to_string());
133            assert_eq!(
134                txw.get(&table, &"test".to_string()),
135                Some("one".to_string())
136            );
137
138            // ReadTransaction should still have the old state.
139            assert!(tx.get(&table, &"test".to_string()).is_none());
140
141            // Commit WriteTransaction.
142            txw.commit();
143
144            // ReadTransaction should still have the old state.
145            assert!(tx.get(&table, &"test".to_string()).is_none());
146
147            // Have a new ReadTransaction read the new state.
148            let tx2 = db.read_transaction();
149            assert_eq!(
150                tx2.get(&table, &"test".to_string()),
151                Some("one".to_string())
152            );
153        }
154        tempdir.close().unwrap();
155    }
156
157    #[test]
158    fn duplicates_test() {
159        let tempdir = tempdir().unwrap();
160        {
161            let db = MdbxDatabase::new(
162                tempdir.path().join("test3"),
163                DatabaseConfig {
164                    max_tables: Some(1),
165                    ..Default::default()
166                },
167            )
168            .unwrap();
169            let table = DupTestTable {};
170            db.create_dup_table(&table);
171
172            // Write one value.
173            let mut txw = db.write_transaction();
174            assert!(txw.get(&table, &"test".to_string()).is_none());
175            txw.put(&table, &"test".to_string(), &125);
176            assert_eq!(txw.get(&table, &"test".to_string()), Some(125));
177            txw.commit();
178
179            // Have a new ReadTransaction read the new state.
180            {
181                let tx = db.read_transaction();
182                assert_eq!(tx.get(&table, &"test".to_string()), Some(125));
183            }
184
185            // Write a second smaller value.
186            let mut txw = db.write_transaction();
187            assert_eq!(txw.get(&table, &"test".to_string()), Some(125));
188            txw.put(&table, &"test".to_string(), &12);
189            assert_eq!(txw.get(&table, &"test".to_string()), Some(12));
190            txw.commit();
191
192            // Have a new ReadTransaction read the smaller value.
193            {
194                let tx = db.read_transaction();
195                assert_eq!(tx.get(&table, &"test".to_string()), Some(12));
196            }
197
198            // Remove smaller value and write larger value.
199            let mut txw = db.write_transaction();
200            assert_eq!(txw.get(&table, &"test".to_string()), Some(12));
201            txw.remove_item(&table, &"test".to_string(), &12);
202            txw.put(&table, &"test".to_string(), &5783);
203            assert_eq!(txw.get(&table, &"test".to_string()), Some(125));
204            txw.commit();
205
206            // Have a new ReadTransaction read the smaller value.
207            {
208                let tx = db.read_transaction();
209                assert_eq!(tx.get(&table, &"test".to_string()), Some(125));
210            }
211
212            // Remove everything.
213            let mut txw = db.write_transaction();
214            assert_eq!(txw.get(&table, &"test".to_string()), Some(125));
215            txw.remove(&table, &"test".to_string());
216            assert!(txw.get(&table, &"test".to_string()).is_none());
217            txw.commit();
218
219            // Have a new ReadTransaction read the new state.
220            {
221                let tx = db.read_transaction();
222                assert!(tx.get(&table, &"test".to_string()).is_none());
223            }
224        }
225        tempdir.close().unwrap();
226    }
227
228    #[test]
229    fn cursor_test() {
230        let tempdir = tempdir().unwrap();
231        {
232            let db = MdbxDatabase::new(
233                tempdir.path().join("test4"),
234                DatabaseConfig {
235                    max_tables: Some(1),
236                    ..Default::default()
237                },
238            )
239            .unwrap();
240            let table = DupTestTable {};
241            db.create_dup_table(&table);
242
243            let test1: String = "test1".to_string();
244            let test2: String = "test2".to_string();
245
246            // Write some values.
247            let mut txw = db.write_transaction();
248            assert!(txw.get(&table, &"test".to_string()).is_none());
249            txw.put(&table, &"test1".to_string(), &125);
250            txw.put(&table, &"test1".to_string(), &12);
251            txw.put(&table, &"test1".to_string(), &5783);
252            txw.put(&table, &"test2".to_string(), &5783);
253            txw.commit();
254
255            // Have a new ReadTransaction read the new state.
256            let tx = db.read_transaction();
257            let mut cursor = tx.dup_cursor(&table);
258            assert_eq!(cursor.first(), Some((test1.clone(), 12)));
259            assert_eq!(cursor.last(), Some((test2.clone(), 5783)));
260            assert_eq!(cursor.prev(), Some((test1.clone(), 5783)));
261            assert_eq!(cursor.first_duplicate(), Some(12));
262            assert_eq!(cursor.next_duplicate(), Some((test1.clone(), 125)));
263            assert_eq!(cursor.prev_duplicate(), Some((test1.clone(), 12)));
264            assert_eq!(cursor.next_no_duplicate(), Some((test2.clone(), 5783)));
265            assert!(cursor.set_key(&"test".to_string()).is_none());
266            assert_eq!(cursor.set_key(&"test1".to_string()), Some(12));
267            assert_eq!(cursor.count_duplicates(), 3);
268            assert_eq!(cursor.last_duplicate(), Some(5783));
269
270            assert_eq!(cursor.get_current(), Some((test1.clone(), 5783)));
271
272            assert_eq!(cursor.get_current(), Some((test1, 5783)));
273            assert!(cursor.prev_no_duplicate().is_none());
274            assert_eq!(cursor.next(), Some((test2, 5783)));
275        }
276        tempdir.close().unwrap();
277    }
278
279    #[test]
280    fn it_correctly_orders_u32() {
281        let tempdir = tempdir().unwrap();
282        {
283            let db = MdbxDatabase::new(
284                tempdir.path().join("test5"),
285                DatabaseConfig {
286                    max_tables: Some(2),
287                    ..Default::default()
288                },
289            )
290            .unwrap();
291            let dup_table = U32DupTable {};
292            let table = U32Table {};
293            db.create_dup_table(&dup_table);
294            db.create_regular_table(&table);
295
296            // Write some values.
297            let mut txw = db.write_transaction();
298
299            txw.put(&table, &256, &2);
300            txw.put(&table, &3, &2);
301
302            txw.put(&dup_table, &256, &3);
303            txw.put(&dup_table, &3, &3);
304            txw.put(&dup_table, &256, &2);
305            txw.put(&dup_table, &3, &2);
306            txw.commit();
307
308            // Have a new ReadTransaction read the new state.
309            let tx = db.read_transaction();
310
311            let mut cursor = tx.cursor(&table);
312            assert_eq!(cursor.first(), Some((3, 2)));
313            assert_eq!(cursor.last(), Some((256, 2)));
314
315            let mut cursor = tx.dup_cursor(&dup_table);
316            assert_eq!(cursor.first(), Some((3, 2)));
317            assert_eq!(cursor.last(), Some((256, 3)));
318            assert_eq!(cursor.prev(), Some((256, 2)));
319            assert_eq!(cursor.prev(), Some((3, 3)));
320            assert_eq!(cursor.first_duplicate(), Some(2));
321            assert_eq!(cursor.last_duplicate(), Some(3));
322            assert_eq!(cursor.next_duplicate(), None);
323            assert_eq!(cursor.next_no_duplicate(), Some((256, 2)));
324        }
325        tempdir.close().unwrap();
326    }
327}