Skip to content

Commit 9980484

Browse files
authored
feat(core): support sharing one redb database between different Operators (#6173)
* support set redb database * create table if not exist at build * refactor: try checking table existence via `ReadTransaction` first * document `database` * fix clippy warning * document datadir
1 parent e481fdd commit 9980484

File tree

2 files changed

+89
-14
lines changed

2 files changed

+89
-14
lines changed

core/src/services/redb/backend.rs

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ use crate::*;
3333
impl Configurator for RedbConfig {
3434
type Builder = RedbBuilder;
3535
fn into_builder(self) -> Self::Builder {
36-
RedbBuilder { config: self }
36+
RedbBuilder {
37+
config: self,
38+
database: None,
39+
}
3740
}
3841
}
3942

@@ -42,16 +45,47 @@ impl Configurator for RedbConfig {
4245
#[derive(Default, Debug)]
4346
pub struct RedbBuilder {
4447
config: RedbConfig,
48+
49+
database: Option<Arc<redb::Database>>,
4550
}
4651

4752
impl RedbBuilder {
53+
/// Set the database for Redb.
54+
///
55+
/// This method should be called when you want to
56+
/// use multiple tables of one database because
57+
/// Redb doesn't allow opening a database that have been opened.
58+
///
59+
/// <div class="warning">
60+
///
61+
/// `datadir` and `database` should not be set simultaneously.
62+
/// If both are set, `database` will take precedence.
63+
///
64+
/// </div>
65+
pub fn database(mut self, db: Arc<redb::Database>) -> Self {
66+
self.database = Some(db);
67+
self
68+
}
69+
4870
/// Set the path to the redb data directory. Will create if not exists.
71+
///
72+
///
73+
/// <div class="warning">
74+
///
75+
/// Opening redb database via `datadir` takes away the ability to access multiple redb tables.
76+
/// If you need to access multiple redb tables, the correct solution is to
77+
/// create an `Arc<redb::database>` beforehand and then share it via [`database`]
78+
/// with multiple builders where every builder will open one redb table.
79+
///
80+
/// </div>
81+
///
82+
/// [`database`]: RedbBuilder::database
4983
pub fn datadir(mut self, path: &str) -> Self {
5084
self.config.datadir = Some(path.into());
5185
self
5286
}
5387

54-
/// Set the table name for Redb.
88+
/// Set the table name for Redb. Will create if not exists.
5589
pub fn table(mut self, table: &str) -> Self {
5690
self.config.table = Some(table.into());
5791
self
@@ -69,22 +103,30 @@ impl Builder for RedbBuilder {
69103
type Config = RedbConfig;
70104

71105
fn build(self) -> Result<impl Access> {
72-
let datadir_path = self.config.datadir.ok_or_else(|| {
73-
Error::new(ErrorKind::ConfigInvalid, "datadir is required but not set")
74-
.with_context("service", Scheme::Redb)
75-
})?;
76-
77106
let table_name = self.config.table.ok_or_else(|| {
78107
Error::new(ErrorKind::ConfigInvalid, "table is required but not set")
79108
.with_context("service", Scheme::Redb)
80109
})?;
81110

82-
let db = redb::Database::create(&datadir_path).map_err(parse_database_error)?;
111+
let (datadir, db) = if let Some(db) = self.database {
112+
(None, db)
113+
} else {
114+
let datadir = self.config.datadir.ok_or_else(|| {
115+
Error::new(ErrorKind::ConfigInvalid, "datadir is required but not set")
116+
.with_context("service", Scheme::Redb)
117+
})?;
118+
119+
let db = redb::Database::create(&datadir)
120+
.map_err(parse_database_error)?
121+
.into();
83122

84-
let db = Arc::new(db);
123+
(Some(datadir), db)
124+
};
125+
126+
create_table(&db, &table_name)?;
85127

86128
Ok(RedbBackend::new(Adapter {
87-
datadir: datadir_path,
129+
datadir,
88130
table: table_name,
89131
db,
90132
})
@@ -97,7 +139,7 @@ pub type RedbBackend = kv::Backend<Adapter>;
97139

98140
#[derive(Clone)]
99141
pub struct Adapter {
100-
datadir: String,
142+
datadir: Option<String>,
101143
table: String,
102144
db: Arc<redb::Database>,
103145
}
@@ -116,7 +158,7 @@ impl kv::Adapter for Adapter {
116158
fn info(&self) -> kv::Info {
117159
kv::Info::new(
118160
Scheme::Redb,
119-
&self.datadir,
161+
&self.table,
120162
Capability {
121163
read: true,
122164
write: true,
@@ -238,3 +280,35 @@ fn parse_database_error(e: redb::DatabaseError) -> Error {
238280
fn parse_commit_error(e: redb::CommitError) -> Error {
239281
Error::new(ErrorKind::Unexpected, "error from redb").set_source(e)
240282
}
283+
284+
/// Check if a table exists, otherwise create it.
285+
fn create_table(db: &redb::Database, table: &str) -> Result<()> {
286+
// Only one `WriteTransaction` is permitted at same time,
287+
// applying new one will block until it available.
288+
//
289+
// So we first try checking table existence via `ReadTransaction`.
290+
{
291+
let read_txn = db.begin_read().map_err(parse_transaction_error)?;
292+
293+
let table_define: redb::TableDefinition<&str, &[u8]> = redb::TableDefinition::new(table);
294+
295+
match read_txn.open_table(table_define) {
296+
Ok(_) => return Ok(()),
297+
Err(redb::TableError::TableDoesNotExist(_)) => (),
298+
Err(e) => return Err(parse_table_error(e)),
299+
}
300+
}
301+
302+
{
303+
let write_txn = db.begin_write().map_err(parse_transaction_error)?;
304+
305+
let table_define: redb::TableDefinition<&str, &[u8]> = redb::TableDefinition::new(table);
306+
307+
write_txn
308+
.open_table(table_define)
309+
.map_err(parse_table_error)?;
310+
write_txn.commit().map_err(parse_commit_error)?;
311+
}
312+
313+
Ok(())
314+
}

core/src/services/redb/docs.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ This service can be used to:
1515

1616
## Configuration
1717

18-
- `datadir`: Set the path to the redb data directory
18+
- `datadir`: Set the path to the redb data directory.
19+
- `table`: Set the table name for Redb.
1920

20-
You can refer to [`RedbBuilder`]'s docs for more information
21+
You can refer to [`RedbBuilder`]'s docs for more information.
2122

2223
## Example
2324

0 commit comments

Comments
 (0)