Skip to content

Commit e207624

Browse files
authored
Add DatabaseOptions to let callers specify the metadata file path (#141)
Also add a way to create a new database, without failing if it already exists.
1 parent 9bab6d6 commit e207624

File tree

5 files changed

+172
-27
lines changed

5 files changed

+172
-27
lines changed

benches/benchmark_common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub fn get_base_database(
6060

6161
let main_file_name_path = dir.path().join("triedb");
6262
let meta_file_name_path = dir.path().join("triedb.meta");
63-
let db = Database::create(main_file_name_path.to_str().unwrap()).unwrap();
63+
let db = Database::create_new(&main_file_name_path).unwrap();
6464

6565
setup_database(&db, fallback_eoa_size, fallback_contract_size, fallback_storage_per_contract)
6666
.unwrap();

src/database.rs

Lines changed: 153 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ use crate::{
88
};
99
use alloy_primitives::B256;
1010
use parking_lot::Mutex;
11-
use std::{io, path::Path};
11+
use std::{
12+
fs::File,
13+
io,
14+
path::{Path, PathBuf},
15+
};
1216

1317
#[derive(Debug)]
1418
pub struct Database {
@@ -17,6 +21,15 @@ pub struct Database {
1721
metrics: DatabaseMetrics,
1822
}
1923

24+
#[must_use]
25+
#[derive(Default, Debug)]
26+
pub struct DatabaseOptions {
27+
create: bool,
28+
create_new: bool,
29+
wipe: bool,
30+
meta_path: Option<PathBuf>,
31+
}
32+
2033
#[derive(Debug)]
2134
pub enum Error {
2235
PageError(PageError),
@@ -30,36 +43,97 @@ pub enum OpenError {
3043
IO(io::Error),
3144
}
3245

33-
impl Database {
34-
pub fn create(path: impl AsRef<Path>) -> Result<Self, OpenError> {
35-
let db_file_path = path.as_ref();
46+
impl DatabaseOptions {
47+
/// Sets the option to create a new database, or open it if it already exists.
48+
///
49+
/// The semantics of this method are equivalent to [`std::fs::OpenOptions::create()`].
50+
pub fn create(&mut self, create: bool) -> &mut Self {
51+
self.create = create;
52+
self
53+
}
3654

37-
let mut meta_file_path = db_file_path.to_path_buf();
38-
meta_file_path.as_mut_os_string().push(".meta");
55+
/// Sets the option to create a new database, failing if it already exists.
56+
///
57+
/// The semantics of this method are equivalent to [`std::fs::OpenOptions::create_new()`].
58+
///
59+
/// If `create_new(true)` is set, then [`create()`](Self::create) and [`wipe()`](Self::wipe)
60+
/// are ignored.
61+
pub fn create_new(&mut self, create_new: bool) -> &mut Self {
62+
self.create_new = create_new;
63+
self
64+
}
3965

40-
let meta_manager =
41-
MetadataManager::open(meta_file_path).map_err(OpenError::MetadataError)?;
42-
let page_manager = PageManager::options()
43-
.create_new(true)
44-
.open(db_file_path)
45-
.map_err(OpenError::PageError)?;
66+
/// Sets the option to erase the database if it gets successfully opened.
67+
pub fn wipe(&mut self, wipe: bool) -> &mut Self {
68+
self.wipe = wipe;
69+
self
70+
}
4671

47-
Ok(Self::new(StorageEngine::new(page_manager, meta_manager)))
72+
/// Specifies the path of the metadata file.
73+
///
74+
/// By default, the metadata file path is generated by appending `".meta"` to the database file
75+
/// path.
76+
pub fn meta_path(&mut self, meta_path: impl Into<PathBuf>) -> &mut Self {
77+
self.meta_path = Some(meta_path.into());
78+
self
4879
}
4980

50-
pub fn open(path: impl AsRef<Path>) -> Result<Self, OpenError> {
51-
let db_file_path = path.as_ref();
81+
/// Opens the database file at the given path.
82+
pub fn open(&self, db_path: impl AsRef<Path>) -> Result<Database, OpenError> {
83+
let db_path = db_path.as_ref();
84+
let meta_path = self.meta_path.clone().unwrap_or_else(|| {
85+
let mut meta_path = db_path.to_path_buf();
86+
meta_path.as_mut_os_string().push(".meta");
87+
meta_path
88+
});
5289

53-
let mut meta_file_path = db_file_path.to_path_buf();
54-
meta_file_path.as_mut_os_string().push(".meta");
55-
let meta_manager =
56-
MetadataManager::open(meta_file_path).map_err(OpenError::MetadataError)?;
90+
Database::open_with_options(db_path, meta_path, self)
91+
}
92+
}
93+
94+
impl Database {
95+
pub fn open(db_path: impl AsRef<Path>) -> Result<Self, OpenError> {
96+
Self::options().open(db_path)
97+
}
98+
99+
pub fn create_new(db_path: impl AsRef<Path>) -> Result<Self, OpenError> {
100+
Self::options().create_new(true).open(db_path)
101+
}
102+
103+
pub fn options() -> DatabaseOptions {
104+
DatabaseOptions::default()
105+
}
106+
107+
fn open_with_options(
108+
db_path: impl AsRef<Path>,
109+
meta_path: impl AsRef<Path>,
110+
opts: &DatabaseOptions,
111+
) -> Result<Self, OpenError> {
112+
let db_path = db_path.as_ref();
113+
let meta_path = meta_path.as_ref();
114+
115+
let meta_file = File::options()
116+
.read(true)
117+
.write(true)
118+
.create(opts.create)
119+
.create_new(opts.create_new)
120+
.truncate(false)
121+
.open(meta_path)
122+
.map_err(OpenError::IO)?;
123+
let mut meta_manager =
124+
MetadataManager::from_file(meta_file).map_err(OpenError::MetadataError)?;
125+
126+
if opts.wipe {
127+
meta_manager.wipe().map_err(OpenError::IO)?
128+
}
57129

58130
let page_count = meta_manager.active_slot().page_count();
59131
let page_manager = PageManager::options()
60-
.create(false)
132+
.create(opts.create)
133+
.create_new(opts.create_new)
134+
.wipe(opts.wipe)
61135
.page_count(page_count)
62-
.open(db_file_path)
136+
.open(db_path)
63137
.map_err(OpenError::PageError)?;
64138

65139
Ok(Self::new(StorageEngine::new(page_manager, meta_manager)))
@@ -176,13 +250,67 @@ mod tests {
176250
use crate::{account::Account, path::AddressPath};
177251
use alloy_primitives::{address, Address, U256};
178252
use alloy_trie::{EMPTY_ROOT_HASH, KECCAK_EMPTY};
253+
use std::fs;
179254
use tempdir::TempDir;
180255

256+
#[test]
257+
fn test_open() {
258+
let tmp_dir = TempDir::new("test_db").expect("temporary dir creation failed");
259+
let db_path = tmp_dir.path().join("db-file");
260+
let auto_meta_path = tmp_dir.path().join("db-file.meta");
261+
let custom_meta_path = tmp_dir.path().join("meta-file");
262+
263+
// Try to open a non-existing database
264+
Database::options()
265+
.open(&db_path)
266+
.expect_err("opening a non-existing database should have failed");
267+
assert!(!db_path.exists());
268+
assert!(!auto_meta_path.exists());
269+
270+
// Open with create(true)
271+
Database::options()
272+
.create(true)
273+
.open(&db_path)
274+
.expect("database creation should have succeeded");
275+
assert!(db_path.exists());
276+
assert!(auto_meta_path.exists());
277+
278+
// Open again with create_new(true)
279+
Database::options()
280+
.create_new(true)
281+
.open(&db_path)
282+
.expect_err("database creation should have failed");
283+
assert!(db_path.exists());
284+
assert!(auto_meta_path.exists());
285+
286+
// Remove the files and open with create_new(true)
287+
fs::remove_file(&db_path).expect("database file removal failed");
288+
fs::remove_file(&auto_meta_path).expect("metadata file removal failed");
289+
Database::options()
290+
.create_new(true)
291+
.open(&db_path)
292+
.expect("database creation should have succeeded");
293+
assert!(db_path.exists());
294+
assert!(auto_meta_path.exists());
295+
296+
// Remove all files, and open with a custom metadata path
297+
fs::remove_file(&db_path).expect("database file removal failed");
298+
fs::remove_file(&auto_meta_path).expect("metadata file removal failed");
299+
Database::options()
300+
.create(true)
301+
.meta_path(&custom_meta_path)
302+
.open(&db_path)
303+
.expect("database creation should have succeeded");
304+
assert!(db_path.exists());
305+
assert!(custom_meta_path.exists());
306+
assert!(!auto_meta_path.exists());
307+
}
308+
181309
#[test]
182310
fn test_set_get_account() {
183311
let tmp_dir = TempDir::new("test_db").unwrap();
184312
let file_path = tmp_dir.path().join("test.db");
185-
let db = Database::create(file_path).unwrap();
313+
let db = Database::create_new(file_path).unwrap();
186314

187315
let address = address!("0xd8da6bf26964af9d7eed9e03e53415d37aa96045");
188316

@@ -223,7 +351,7 @@ mod tests {
223351
// create the database on disk. currently this will create a database with 0 pages
224352
let tmp_dir = TempDir::new("test_db").unwrap();
225353
let file_path = tmp_dir.path().join("test.db");
226-
let _db = Database::create(&file_path).unwrap();
354+
let _db = Database::create_new(&file_path).unwrap();
227355

228356
// WHEN: the database is opened
229357
let db = Database::open(&file_path).unwrap();
@@ -239,7 +367,7 @@ mod tests {
239367
fn test_data_persistence() {
240368
let tmp_dir = TempDir::new("test_db").unwrap();
241369
let file_path = tmp_dir.path().join("test.db");
242-
let db = Database::create(&file_path).unwrap();
370+
let db = Database::create_new(&file_path).unwrap();
243371

244372
let address1 = address!("0xd8da6bf26964af9d7eed9e03e53415d37aa96045");
245373
let account1 = Account::new(1, U256::from(100), EMPTY_ROOT_HASH, KECCAK_EMPTY);
@@ -302,7 +430,7 @@ mod tests {
302430
// Create a new database and verify it has no pages
303431
let tmp_dir = TempDir::new("test_db").unwrap();
304432
let file_path = tmp_dir.path().join("test.db");
305-
let db = Database::create(file_path).unwrap();
433+
let db = Database::create_new(file_path).unwrap();
306434
assert_eq!(db.storage_engine.page_manager.size(), 0);
307435

308436
// Add 1000 accounts

src/meta/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,14 @@ impl MetadataManager {
577577
Ok(())
578578
}
579579

580+
/// Erases the metadata contents.
581+
pub fn wipe(&mut self) -> io::Result<()> {
582+
let size = Self::SIZE_MIN;
583+
self.file.set_len(size)?;
584+
self.mmap.as_mut()[size as usize..].fill(0);
585+
Ok(())
586+
}
587+
580588
/// Saves the metadata to the storage device.
581589
pub fn sync(&self) -> io::Result<()> {
582590
if cfg!(not(miri)) {

src/page/manager/options.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ impl PageManagerOptions {
6161
self
6262
}
6363

64+
/// Causes the file length to be set to 0 after opening it.
65+
///
66+
/// Note that if `wipe(true)` is set, then setting [`page_count()`](Self::page_count) with any
67+
/// number greater than `0` will cause the open to fail.
68+
pub fn wipe(&mut self, wipe: bool) -> &mut Self {
69+
self.open_options.truncate(wipe);
70+
self
71+
}
72+
6473
/// Opens the file at `path` with the options specified by `self`.
6574
pub fn open(&self, path: impl AsRef<Path>) -> Result<PageManager, PageError> {
6675
PageManager::open_with_options(self, path)

tests/ethereum_execution_spec.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ fn run_ethereum_execution_spec_state_tests() {
3434
.as_str()
3535
.replace("/", "_")[0..min(test_case_name.len(), 100)];
3636
let file_path = tmp_dir.path().join(database_file_name).to_str().unwrap().to_owned();
37-
let test_database = Database::create(file_path.as_str()).unwrap();
37+
let test_database = Database::create_new(file_path).unwrap();
3838

3939
// will track accounts and storage that need to be deleted. this is essentially the
4040
// "diff" between the pre state and post state.

0 commit comments

Comments
 (0)