1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
//! The main [`Config`] struct, holding all configurable values.

//---------------------------------------------------------------------------------------------------- Import
use std::{borrow::Cow, num::NonZeroUsize, path::Path};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::{config::SyncMode, constants::DATABASE_DATA_FILENAME, resize::ResizeAlgorithm};

//---------------------------------------------------------------------------------------------------- Constants
/// Default value for [`Config::reader_threads`].
///
/// ```rust
/// use cuprate_database::config::*;
/// assert_eq!(READER_THREADS_DEFAULT.get(), 126);
/// ```
pub const READER_THREADS_DEFAULT: NonZeroUsize = match NonZeroUsize::new(126) {
    Some(n) => n,
    None => unreachable!(),
};

//---------------------------------------------------------------------------------------------------- ConfigBuilder
/// Builder for [`Config`].
///
// SOMEDAY: there's are many more options to add in the future.
#[derive(Debug, Clone, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ConfigBuilder {
    /// [`Config::db_directory`].
    db_directory: Cow<'static, Path>,

    /// [`Config::sync_mode`].
    sync_mode: Option<SyncMode>,

    /// [`Config::reader_threads`].
    reader_threads: Option<NonZeroUsize>,

    /// [`Config::resize_algorithm`].
    resize_algorithm: Option<ResizeAlgorithm>,
}

impl ConfigBuilder {
    /// Create a new [`ConfigBuilder`].
    ///
    /// [`ConfigBuilder::build`] can be called immediately
    /// after this function to use default values.
    pub const fn new(db_directory: Cow<'static, Path>) -> Self {
        Self {
            db_directory,
            sync_mode: None,
            reader_threads: Some(READER_THREADS_DEFAULT),
            resize_algorithm: None,
        }
    }

    /// Build into a [`Config`].
    ///
    /// # Default values
    /// - [`READER_THREADS_DEFAULT`] is used for [`Config::reader_threads`]
    /// - [`Default::default`] is used for all other values (except the `db_directory`)
    pub fn build(self) -> Config {
        // Add the database filename to the directory.
        let db_file = {
            let mut db_file = self.db_directory.to_path_buf();
            db_file.push(DATABASE_DATA_FILENAME);
            Cow::Owned(db_file)
        };

        Config {
            db_directory: self.db_directory,
            db_file,
            sync_mode: self.sync_mode.unwrap_or_default(),
            reader_threads: self.reader_threads.unwrap_or(READER_THREADS_DEFAULT),
            resize_algorithm: self.resize_algorithm.unwrap_or_default(),
        }
    }

    /// Set a custom database directory (and file) [`Path`].
    #[must_use]
    pub fn db_directory(mut self, db_directory: Cow<'static, Path>) -> Self {
        self.db_directory = db_directory;
        self
    }

    /// Tune the [`ConfigBuilder`] for the highest performing,
    /// but also most resource-intensive & maybe risky settings.
    ///
    /// Good default for testing, and resource-available machines.
    #[must_use]
    pub fn fast(mut self) -> Self {
        self.sync_mode = Some(SyncMode::Fast);
        self.resize_algorithm = Some(ResizeAlgorithm::default());
        self
    }

    /// Tune the [`ConfigBuilder`] for the lowest performing,
    /// but also least resource-intensive settings.
    ///
    /// Good default for resource-limited machines, e.g. a cheap VPS.
    #[must_use]
    pub fn low_power(mut self) -> Self {
        self.sync_mode = Some(SyncMode::default());
        self.resize_algorithm = Some(ResizeAlgorithm::default());
        self
    }

    /// Set a custom [`SyncMode`].
    #[must_use]
    pub const fn sync_mode(mut self, sync_mode: SyncMode) -> Self {
        self.sync_mode = Some(sync_mode);
        self
    }

    /// Set a custom [`Config::reader_threads`].
    #[must_use]
    pub const fn reader_threads(mut self, reader_threads: NonZeroUsize) -> Self {
        self.reader_threads = Some(reader_threads);
        self
    }

    /// Set a custom [`ResizeAlgorithm`].
    #[must_use]
    pub const fn resize_algorithm(mut self, resize_algorithm: ResizeAlgorithm) -> Self {
        self.resize_algorithm = Some(resize_algorithm);
        self
    }
}

//---------------------------------------------------------------------------------------------------- Config
/// Database [`Env`](crate::Env) configuration.
///
/// This is the struct passed to [`Env::open`](crate::Env::open) that
/// allows the database to be configured in various ways.
///
/// For construction, use [`ConfigBuilder`].
///
// SOMEDAY: there's are many more options to add in the future.
#[derive(Debug, Clone, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Config {
    //------------------------ Database PATHs
    // These are private since we don't want
    // users messing with them after construction.
    /// The directory used to store all database files.
    ///
    // SOMEDAY: we should also support `/etc/cuprated.conf`.
    // This could be represented with an `enum DbPath { Default, Custom, Etc, }`
    pub(crate) db_directory: Cow<'static, Path>,
    /// The actual database data file.
    ///
    /// This is private, and created from the above `db_directory`.
    pub(crate) db_file: Cow<'static, Path>,

    /// Disk synchronization mode.
    pub sync_mode: SyncMode,

    /// Database reader thread count.
    ///
    /// Set the number of slots in the reader table.
    ///
    /// This is only used in LMDB, see
    /// [here](https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/mdb.c#L794-L799).
    ///
    /// By default, this value is [`READER_THREADS_DEFAULT`].
    pub reader_threads: NonZeroUsize,

    /// Database memory map resizing algorithm.
    ///
    /// This is used as the default fallback, but
    /// custom algorithms can be used as well with
    /// [`Env::resize_map`](crate::Env::resize_map).
    pub resize_algorithm: ResizeAlgorithm,
}

impl Config {
    /// Create a new [`Config`] with sane default settings.
    ///
    /// The [`Config::db_directory`] must be passed.
    ///
    /// All other values will be [`Default::default`].
    ///
    /// ```rust
    /// use cuprate_database::{config::*, resize::*, DATABASE_DATA_FILENAME};
    ///
    /// let tmp_dir = tempfile::tempdir().unwrap();
    /// let db_directory = tmp_dir.path().to_owned();
    /// let config = Config::new(db_directory.clone().into());
    ///
    /// assert_eq!(*config.db_directory(), db_directory);
    /// assert!(config.db_file().starts_with(db_directory));
    /// assert!(config.db_file().ends_with(DATABASE_DATA_FILENAME));
    /// assert_eq!(config.sync_mode, SyncMode::default());
    /// assert_eq!(config.reader_threads, READER_THREADS_DEFAULT);
    /// assert_eq!(config.resize_algorithm, ResizeAlgorithm::default());
    /// ```
    pub fn new(db_directory: Cow<'static, Path>) -> Self {
        ConfigBuilder::new(db_directory).build()
    }

    /// Return the absolute [`Path`] to the database directory.
    pub const fn db_directory(&self) -> &Cow<'_, Path> {
        &self.db_directory
    }

    /// Return the absolute [`Path`] to the database data file.
    pub const fn db_file(&self) -> &Cow<'_, Path> {
        &self.db_file
    }
}