From 1caaa7df6834a3832a40158838c0d9452133e593 Mon Sep 17 00:00:00 2001 From: TD-Sky Date: Mon, 12 May 2025 12:46:44 +0800 Subject: [PATCH 1/6] support set redb database --- core/src/services/redb/backend.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/core/src/services/redb/backend.rs b/core/src/services/redb/backend.rs index b8951ac1880d..7ab51cafd63d 100644 --- a/core/src/services/redb/backend.rs +++ b/core/src/services/redb/backend.rs @@ -33,7 +33,10 @@ use crate::*; impl Configurator for RedbConfig { type Builder = RedbBuilder; fn into_builder(self) -> Self::Builder { - RedbBuilder { config: self } + RedbBuilder { + config: self, + database: None, + } } } @@ -42,6 +45,8 @@ impl Configurator for RedbConfig { #[derive(Default, Debug)] pub struct RedbBuilder { config: RedbConfig, + + database: Option>, } impl RedbBuilder { @@ -62,6 +67,12 @@ impl RedbBuilder { self.config.root = Some(path.into()); self } + + /// Set the database for Redb + pub fn database(mut self, db: &Arc) -> Self { + self.database = Some(db.clone()); + self + } } impl Builder for RedbBuilder { @@ -79,9 +90,13 @@ impl Builder for RedbBuilder { .with_context("service", Scheme::Redb) })?; - let db = redb::Database::create(&datadir_path).map_err(parse_database_error)?; - - let db = Arc::new(db); + let db = if let Some(db) = self.database { + db + } else { + redb::Database::create(&datadir_path) + .map_err(parse_database_error)? + .into() + }; Ok(RedbBackend::new(Adapter { datadir: datadir_path, From 4b63e46667db34a4a4d507373594b81327211611 Mon Sep 17 00:00:00 2001 From: TD-Sky Date: Mon, 12 May 2025 12:47:55 +0800 Subject: [PATCH 2/6] create table if not exist at build --- core/src/services/redb/backend.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/src/services/redb/backend.rs b/core/src/services/redb/backend.rs index 7ab51cafd63d..b5efa47d4b5e 100644 --- a/core/src/services/redb/backend.rs +++ b/core/src/services/redb/backend.rs @@ -98,6 +98,18 @@ impl Builder for RedbBuilder { .into() }; + { + let write_txn = db.begin_write().map_err(parse_transaction_error)?; + + let table_define: redb::TableDefinition<&str, &[u8]> = + redb::TableDefinition::new(&table_name); + + write_txn + .open_table(table_define) + .map_err(parse_table_error)?; + write_txn.commit().map_err(parse_commit_error)?; + } + Ok(RedbBackend::new(Adapter { datadir: datadir_path, table: table_name, From 9929da5230c5c7507d10aafb73ecc39a41ed48b7 Mon Sep 17 00:00:00 2001 From: TD-Sky Date: Mon, 12 May 2025 22:36:51 +0800 Subject: [PATCH 3/6] refactor: try checking table existence via `ReadTransaction` first --- core/src/services/redb/backend.rs | 44 +++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/core/src/services/redb/backend.rs b/core/src/services/redb/backend.rs index b5efa47d4b5e..3de392ac07a7 100644 --- a/core/src/services/redb/backend.rs +++ b/core/src/services/redb/backend.rs @@ -98,17 +98,7 @@ impl Builder for RedbBuilder { .into() }; - { - let write_txn = db.begin_write().map_err(parse_transaction_error)?; - - let table_define: redb::TableDefinition<&str, &[u8]> = - redb::TableDefinition::new(&table_name); - - write_txn - .open_table(table_define) - .map_err(parse_table_error)?; - write_txn.commit().map_err(parse_commit_error)?; - } + create_table(&db, &table_name)?; Ok(RedbBackend::new(Adapter { datadir: datadir_path, @@ -265,3 +255,35 @@ fn parse_database_error(e: redb::DatabaseError) -> Error { fn parse_commit_error(e: redb::CommitError) -> Error { Error::new(ErrorKind::Unexpected, "error from redb").set_source(e) } + +/// Check if a table exists, otherwise create it. +fn create_table(db: &redb::Database, table: &str) -> Result<()> { + // Only one `WriteTransaction` is permitted at same time, + // applying new one will block until it available. + // + // So we first try checking table existence via `ReadTransaction`. + { + let read_txn = db.begin_read().map_err(parse_transaction_error)?; + + let table_define: redb::TableDefinition<&str, &[u8]> = redb::TableDefinition::new(table); + + match read_txn.open_table(table_define) { + Ok(_) => return Ok(()), + Err(e) if matches!(e, redb::TableError::TableDoesNotExist(_)) => (), + Err(e) => return Err(parse_table_error(e)), + } + } + + { + let write_txn = db.begin_write().map_err(parse_transaction_error)?; + + let table_define: redb::TableDefinition<&str, &[u8]> = redb::TableDefinition::new(table); + + write_txn + .open_table(table_define) + .map_err(parse_table_error)?; + write_txn.commit().map_err(parse_commit_error)?; + } + + Ok(()) +} From 1f7b3e65c0c5bfef3be1d99a205b9c58eec4ea7d Mon Sep 17 00:00:00 2001 From: TD-Sky Date: Mon, 12 May 2025 22:59:12 +0800 Subject: [PATCH 4/6] document `database` --- core/src/services/redb/backend.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/core/src/services/redb/backend.rs b/core/src/services/redb/backend.rs index 3de392ac07a7..34e7a09731c9 100644 --- a/core/src/services/redb/backend.rs +++ b/core/src/services/redb/backend.rs @@ -50,6 +50,18 @@ pub struct RedbBuilder { } impl RedbBuilder { + /// Set the database for Redb. + /// + /// This method should be called when you want to + /// use multiple tables of one database because + /// Redb doesn't allow opening a database that have been opened. + /// + /// Don't forget to set Builder's `datadir` to the origin datadir of `db`. + pub fn database(mut self, db: Arc) -> Self { + self.database = Some(db); + self + } + /// Set the path to the redb data directory. Will create if not exists. pub fn datadir(mut self, path: &str) -> Self { self.config.datadir = Some(path.into()); @@ -67,12 +79,6 @@ impl RedbBuilder { self.config.root = Some(path.into()); self } - - /// Set the database for Redb - pub fn database(mut self, db: &Arc) -> Self { - self.database = Some(db.clone()); - self - } } impl Builder for RedbBuilder { From 6b186ad219e91f64e2fa304306af89167d2e5755 Mon Sep 17 00:00:00 2001 From: TD-Sky Date: Mon, 12 May 2025 23:05:48 +0800 Subject: [PATCH 5/6] fix clippy warning --- core/src/services/redb/backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/services/redb/backend.rs b/core/src/services/redb/backend.rs index 34e7a09731c9..fa5794599198 100644 --- a/core/src/services/redb/backend.rs +++ b/core/src/services/redb/backend.rs @@ -275,7 +275,7 @@ fn create_table(db: &redb::Database, table: &str) -> Result<()> { match read_txn.open_table(table_define) { Ok(_) => return Ok(()), - Err(e) if matches!(e, redb::TableError::TableDoesNotExist(_)) => (), + Err(redb::TableError::TableDoesNotExist(_)) => (), Err(e) => return Err(parse_table_error(e)), } } From e4453207cb13023ca161b2318f58bbfd75c2ed7c Mon Sep 17 00:00:00 2001 From: TD-Sky Date: Tue, 13 May 2025 11:59:26 +0800 Subject: [PATCH 6/6] document datadir --- core/src/services/redb/backend.rs | 47 ++++++++++++++++++++++--------- core/src/services/redb/docs.md | 5 ++-- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/core/src/services/redb/backend.rs b/core/src/services/redb/backend.rs index fa5794599198..d51dae1af2c4 100644 --- a/core/src/services/redb/backend.rs +++ b/core/src/services/redb/backend.rs @@ -56,19 +56,36 @@ impl RedbBuilder { /// use multiple tables of one database because /// Redb doesn't allow opening a database that have been opened. /// - /// Don't forget to set Builder's `datadir` to the origin datadir of `db`. + ///
+ /// + /// `datadir` and `database` should not be set simultaneously. + /// If both are set, `database` will take precedence. + /// + ///
pub fn database(mut self, db: Arc) -> Self { self.database = Some(db); self } /// Set the path to the redb data directory. Will create if not exists. + /// + /// + ///
+ /// + /// Opening redb database via `datadir` takes away the ability to access multiple redb tables. + /// If you need to access multiple redb tables, the correct solution is to + /// create an `Arc` beforehand and then share it via [`database`] + /// with multiple builders where every builder will open one redb table. + /// + ///
+ /// + /// [`database`]: RedbBuilder::database pub fn datadir(mut self, path: &str) -> Self { self.config.datadir = Some(path.into()); self } - /// Set the table name for Redb. + /// Set the table name for Redb. Will create if not exists. pub fn table(mut self, table: &str) -> Self { self.config.table = Some(table.into()); self @@ -86,28 +103,30 @@ impl Builder for RedbBuilder { type Config = RedbConfig; fn build(self) -> Result { - let datadir_path = self.config.datadir.ok_or_else(|| { - Error::new(ErrorKind::ConfigInvalid, "datadir is required but not set") - .with_context("service", Scheme::Redb) - })?; - let table_name = self.config.table.ok_or_else(|| { Error::new(ErrorKind::ConfigInvalid, "table is required but not set") .with_context("service", Scheme::Redb) })?; - let db = if let Some(db) = self.database { - db + let (datadir, db) = if let Some(db) = self.database { + (None, db) } else { - redb::Database::create(&datadir_path) + let datadir = self.config.datadir.ok_or_else(|| { + Error::new(ErrorKind::ConfigInvalid, "datadir is required but not set") + .with_context("service", Scheme::Redb) + })?; + + let db = redb::Database::create(&datadir) .map_err(parse_database_error)? - .into() + .into(); + + (Some(datadir), db) }; create_table(&db, &table_name)?; Ok(RedbBackend::new(Adapter { - datadir: datadir_path, + datadir, table: table_name, db, }) @@ -120,7 +139,7 @@ pub type RedbBackend = kv::Backend; #[derive(Clone)] pub struct Adapter { - datadir: String, + datadir: Option, table: String, db: Arc, } @@ -139,7 +158,7 @@ impl kv::Adapter for Adapter { fn info(&self) -> kv::Info { kv::Info::new( Scheme::Redb, - &self.datadir, + &self.table, Capability { read: true, write: true, diff --git a/core/src/services/redb/docs.md b/core/src/services/redb/docs.md index 353b78bf5baa..a964edb28b18 100644 --- a/core/src/services/redb/docs.md +++ b/core/src/services/redb/docs.md @@ -15,9 +15,10 @@ This service can be used to: ## Configuration -- `datadir`: Set the path to the redb data directory +- `datadir`: Set the path to the redb data directory. +- `table`: Set the table name for Redb. -You can refer to [`RedbBuilder`]'s docs for more information +You can refer to [`RedbBuilder`]'s docs for more information. ## Example