nimiq_database/mdbx/
database.rs1use std::{any::TypeId, fs, ops::Range, path::Path, sync::Arc};
2
3use libmdbx::NoWriteMap;
4use log::{debug, info};
5use tempfile::TempDir;
6
7use super::{MdbxReadTransaction, MdbxWriteTransaction};
8use crate::{
9 traits::{AsDatabaseBytes, Database, DupTable, RegularTable, Table},
10 Error,
11};
12
13const GIGABYTE: usize = 1024 * 1024 * 1024;
14const TERABYTE: usize = GIGABYTE * 1024;
15
16pub struct DatabaseConfig {
18 pub max_tables: Option<u64>,
20 pub max_readers: Option<u32>,
22 pub no_rdahead: bool,
24 pub size: Option<Range<isize>>,
26 pub coalesce: bool,
28 pub growth_step: Option<isize>,
30 pub shrink_threshold: Option<isize>,
32}
33
34impl Default for DatabaseConfig {
35 fn default() -> Self {
36 DatabaseConfig {
37 max_tables: Some(20),
38 max_readers: None,
39 no_rdahead: true,
40 size: Some(0..(2 * TERABYTE as isize)),
42 coalesce: false,
43 growth_step: Some(4 * GIGABYTE as isize),
45 shrink_threshold: None,
46 }
47 }
48}
49
50impl From<DatabaseConfig> for libmdbx::DatabaseOptions {
51 fn from(value: DatabaseConfig) -> Self {
52 libmdbx::DatabaseOptions {
53 max_tables: value.max_tables,
54 max_readers: value.max_readers,
55 no_rdahead: value.no_rdahead,
56 mode: libmdbx::Mode::ReadWrite(libmdbx::ReadWriteOptions {
57 sync_mode: libmdbx::SyncMode::Durable,
58 min_size: value.size.as_ref().map(|r| r.start),
59 max_size: value.size.map(|r| r.end),
60 ..Default::default()
61 }),
62 liforeclaim: true,
63 ..Default::default()
64 }
65 }
66}
67
68#[derive(Clone, Debug)]
71pub struct MdbxDatabase {
72 db: Arc<libmdbx::Database<NoWriteMap>>,
74 temp_dir: Option<Arc<TempDir>>,
77}
78
79impl MdbxDatabase {
80 fn create_table<T: Table>(&self, _table: &T, mut flags: libmdbx::TableFlags) {
82 let key_type = TypeId::of::<T::Key>();
84 if key_type == TypeId::of::<u32>() || key_type == TypeId::of::<u64>() {
85 flags.insert(libmdbx::TableFlags::INTEGER_KEY);
86 }
87
88 let txn = self.db.begin_rw_txn().unwrap();
90 debug!("Creating table: {}, flags: {:?}", T::NAME, flags);
91 txn.create_table(Some(T::NAME), flags).unwrap();
92 txn.commit().unwrap();
93 }
94
95 pub fn new<P: AsRef<Path>>(path: P, config: DatabaseConfig) -> Result<Self, Error> {
97 fs::create_dir_all(path.as_ref()).map_err(Error::CreateDirectory)?;
98
99 let db =
100 libmdbx::Database::open_with_options(path, libmdbx::DatabaseOptions::from(config))?;
101
102 let info = db.info()?;
103 let cur_mapsize = info.map_size();
104 info!(cur_mapsize, "MDBX memory map size");
105
106 let mdbx = MdbxDatabase {
107 db: Arc::new(db),
108 temp_dir: None,
109 };
110
111 Ok(mdbx)
112 }
113
114 pub fn new_volatile(config: DatabaseConfig) -> Result<Self, Error> {
116 let temp_dir = Arc::new(TempDir::new()?);
117 let mut mdbx = MdbxDatabase::new(temp_dir.path(), config)?;
118 mdbx.temp_dir = Some(temp_dir);
119
120 Ok(mdbx)
121 }
122}
123
124impl Database for MdbxDatabase {
125 type ReadTransaction<'db> = MdbxReadTransaction<'db>;
126
127 type WriteTransaction<'db> = MdbxWriteTransaction<'db>;
128
129 fn create_regular_table<T: RegularTable>(&self, table: &T) {
131 self.create_table(table, libmdbx::TableFlags::empty())
132 }
133
134 fn create_dup_table<T: DupTable>(&self, table: &T) {
136 let mut dup_flags = libmdbx::TableFlags::DUP_SORT;
137
138 if T::Value::FIXED_SIZE.is_some() {
140 dup_flags.insert(libmdbx::TableFlags::DUP_FIXED);
141 }
142
143 self.create_table(table, dup_flags)
144 }
145
146 fn read_transaction(&self) -> Self::ReadTransaction<'_> {
147 MdbxReadTransaction::new_read(self.db.begin_ro_txn().unwrap())
148 }
149
150 fn write_transaction(&self) -> Self::WriteTransaction<'_> {
151 MdbxWriteTransaction::new(self.db.begin_rw_txn().unwrap())
152 }
153}