opendal/services/redb/
backend.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::fmt::Debug;
19use std::sync::Arc;
20
21use super::REDB_SCHEME;
22use super::config::RedbConfig;
23use super::core::*;
24use super::deleter::RedbDeleter;
25use super::writer::RedbWriter;
26use crate::raw::*;
27use crate::*;
28
29/// Redb service support.
30#[doc = include_str!("docs.md")]
31#[derive(Default)]
32pub struct RedbBuilder {
33    pub(super) config: RedbConfig,
34
35    pub(super) database: Option<Arc<redb::Database>>,
36}
37
38impl Debug for RedbBuilder {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        f.debug_struct("RedbBuilder")
41            .field("config", &self.config)
42            .finish_non_exhaustive()
43    }
44}
45
46impl RedbBuilder {
47    /// Set the database for Redb.
48    ///
49    /// This method should be called when you want to
50    /// use multiple tables of one database because
51    /// Redb doesn't allow opening a database that have been opened.
52    ///
53    /// <div class="warning">
54    ///
55    /// `datadir` and `database` should not be set simultaneously.
56    /// If both are set, `database` will take precedence.
57    ///
58    /// </div>
59    pub fn database(mut self, db: Arc<redb::Database>) -> Self {
60        self.database = Some(db);
61        self
62    }
63
64    /// Set the path to the redb data directory. Will create if not exists.
65    ///
66    ///
67    /// <div class="warning">
68    ///
69    /// Opening redb database via `datadir` takes away the ability to access multiple redb tables.
70    /// If you need to access multiple redb tables, the correct solution is to
71    /// create an `Arc<redb::database>` beforehand and then share it via [`database`]
72    /// with multiple builders where every builder will open one redb table.
73    ///
74    /// </div>
75    ///
76    /// [`database`]: RedbBuilder::database
77    pub fn datadir(mut self, path: &str) -> Self {
78        self.config.datadir = Some(path.into());
79        self
80    }
81
82    /// Set the table name for Redb. Will create if not exists.
83    pub fn table(mut self, table: &str) -> Self {
84        self.config.table = Some(table.into());
85        self
86    }
87
88    /// Set the root for Redb.
89    pub fn root(mut self, path: &str) -> Self {
90        self.config.root = Some(path.into());
91        self
92    }
93}
94
95impl Builder for RedbBuilder {
96    type Config = RedbConfig;
97
98    fn build(self) -> Result<impl Access> {
99        let table_name = self.config.table.ok_or_else(|| {
100            Error::new(ErrorKind::ConfigInvalid, "table is required but not set")
101                .with_context("service", REDB_SCHEME)
102        })?;
103
104        let (datadir, db) = if let Some(db) = self.database {
105            (None, db)
106        } else {
107            let datadir = self.config.datadir.ok_or_else(|| {
108                Error::new(ErrorKind::ConfigInvalid, "datadir is required but not set")
109                    .with_context("service", REDB_SCHEME)
110            })?;
111
112            let db = redb::Database::create(&datadir)
113                .map_err(parse_database_error)?
114                .into();
115
116            (Some(datadir), db)
117        };
118
119        create_table(&db, &table_name)?;
120
121        let root = normalize_root(&self.config.root.unwrap_or_default());
122
123        Ok(RedbBackend::new(RedbCore {
124            datadir,
125            table: table_name,
126            db,
127        })
128        .with_normalized_root(root))
129    }
130}
131
132/// Backend for Redb services.
133#[derive(Clone, Debug)]
134pub struct RedbBackend {
135    core: Arc<RedbCore>,
136    root: String,
137    info: Arc<AccessorInfo>,
138}
139
140impl RedbBackend {
141    pub fn new(core: RedbCore) -> Self {
142        let info = AccessorInfo::default();
143        info.set_scheme(REDB_SCHEME);
144        info.set_name(&core.table);
145        info.set_root("/");
146        info.set_native_capability(Capability {
147            read: true,
148            stat: true,
149            write: true,
150            write_can_empty: true,
151            delete: true,
152            shared: false,
153            ..Default::default()
154        });
155
156        Self {
157            core: Arc::new(core),
158            root: "/".to_string(),
159            info: Arc::new(info),
160        }
161    }
162
163    fn with_normalized_root(mut self, root: String) -> Self {
164        self.info.set_root(&root);
165        self.root = root;
166        self
167    }
168}
169
170impl Access for RedbBackend {
171    type Reader = Buffer;
172    type Writer = RedbWriter;
173    type Lister = ();
174    type Deleter = oio::OneShotDeleter<RedbDeleter>;
175
176    fn info(&self) -> Arc<AccessorInfo> {
177        self.info.clone()
178    }
179
180    async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
181        let p = build_abs_path(&self.root, path);
182
183        if p == build_abs_path(&self.root, "") {
184            Ok(RpStat::new(Metadata::new(EntryMode::DIR)))
185        } else {
186            let bs = self.core.get(&p)?;
187            match bs {
188                Some(bs) => Ok(RpStat::new(
189                    Metadata::new(EntryMode::FILE).with_content_length(bs.len() as u64),
190                )),
191                None => Err(Error::new(ErrorKind::NotFound, "kv not found in redb")),
192            }
193        }
194    }
195
196    async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
197        let p = build_abs_path(&self.root, path);
198        let bs = match self.core.get(&p)? {
199            Some(bs) => bs,
200            None => {
201                return Err(Error::new(ErrorKind::NotFound, "kv not found in redb"));
202            }
203        };
204        Ok((RpRead::new(), bs.slice(args.range().to_range_as_usize())))
205    }
206
207    async fn write(&self, path: &str, _: OpWrite) -> Result<(RpWrite, Self::Writer)> {
208        let p = build_abs_path(&self.root, path);
209        Ok((RpWrite::new(), RedbWriter::new(self.core.clone(), p)))
210    }
211
212    async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
213        Ok((
214            RpDelete::default(),
215            oio::OneShotDeleter::new(RedbDeleter::new(self.core.clone(), self.root.clone())),
216        ))
217    }
218}