Skip to main content

nimiq_database/mdbx/
database.rs

1use 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
16/// Database config options.
17pub struct DatabaseConfig {
18    /// The maximum number of tables that can be opened.
19    pub max_tables: Option<u64>,
20    /// The maximum number of reader slots.
21    pub max_readers: Option<u32>,
22    /// Whether to enable/disable readahead.
23    pub no_rdahead: bool,
24    /// The minimum/maximum file size of the database. Default max size is 2TB.
25    pub size: Option<Range<isize>>,
26    /// Aims to coalesce a Garbage Collection items.
27    pub coalesce: bool,
28    /// The growth step by which the database file will be increased when lacking space.
29    pub growth_step: Option<isize>,
30    /// The threshold of unused space, after which the database file will be shrunk.
31    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            // Default max database size: 2TB
41            size: Some(0..(2 * TERABYTE as isize)),
42            coalesce: false,
43            // Default growth step: 4GB
44            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/// Wrapper around the mdbx database handle.
69/// A database can hold multiple tables.
70#[derive(Clone, Debug)]
71pub struct MdbxDatabase {
72    /// The database handle.
73    db: Arc<libmdbx::Database<NoWriteMap>>,
74    /// For volatile databases, this is the temporary directory handle,
75    /// which will clean up on `Drop`.
76    temp_dir: Option<Arc<TempDir>>,
77}
78
79impl MdbxDatabase {
80    /// Create a table with additional flags.
81    fn create_table<T: Table>(&self, _table: &T, mut flags: libmdbx::TableFlags) {
82        // Automatically set the integer key flag.
83        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        // Create the table with an implicit transaction.
89        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    /// Creates a new database at the given path.
96    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    /// Creates a volatile database (in a temporary directory, which cleans itself after use).
115    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    /// Creates a regular table (no-duplicates).
130    fn create_regular_table<T: RegularTable>(&self, table: &T) {
131        self.create_table(table, libmdbx::TableFlags::empty())
132    }
133
134    /// Creates a regular table (no-duplicates).
135    fn create_dup_table<T: DupTable>(&self, table: &T) {
136        let mut dup_flags = libmdbx::TableFlags::DUP_SORT;
137
138        // Set the fixed size flag if given.
139        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}