@@ -8,7 +8,11 @@ use crate::{
88} ;
99use alloy_primitives:: B256 ;
1010use 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 ) ]
1418pub 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 ) ]
2134pub 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
0 commit comments