Skip to main content

opendal_service_surrealdb/
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 mea::once::OnceCell;
22
23use super::SURREALDB_SCHEME;
24use super::config::SurrealdbConfig;
25use super::core::*;
26use super::deleter::SurrealdbDeleter;
27use super::reader::*;
28use super::writer::SurrealdbWriter;
29use opendal_core::raw::*;
30use opendal_core::*;
31
32#[doc = include_str!("docs.md")]
33#[derive(Debug, Default)]
34pub struct SurrealdbBuilder {
35    pub(super) config: SurrealdbConfig,
36}
37
38impl SurrealdbBuilder {
39    /// Set the connection_string of the surrealdb service.
40    ///
41    /// This connection string is used to connect to the surrealdb service. There are url based formats:
42    ///
43    /// ## Url
44    ///
45    /// - `ws://ip:port`
46    /// - `wss://ip:port`
47    /// - `http://ip:port`
48    /// - `https://ip:port`
49    pub fn connection_string(mut self, connection_string: &str) -> Self {
50        if !connection_string.is_empty() {
51            self.config.connection_string = Some(connection_string.to_string());
52        }
53        self
54    }
55
56    /// set the working directory, all operations will be performed under it.
57    ///
58    /// default: "/"
59    pub fn root(mut self, root: &str) -> Self {
60        self.config.root = if root.is_empty() {
61            None
62        } else {
63            Some(root.to_string())
64        };
65
66        self
67    }
68
69    /// Set the table name of the surrealdb service for read/write.
70    pub fn table(mut self, table: &str) -> Self {
71        if !table.is_empty() {
72            self.config.table = Some(table.to_string());
73        }
74        self
75    }
76
77    /// Set the username of the surrealdb service for signin.
78    pub fn username(mut self, username: &str) -> Self {
79        if !username.is_empty() {
80            self.config.username = Some(username.to_string());
81        }
82        self
83    }
84
85    /// Set the password of the surrealdb service for signin.
86    pub fn password(mut self, password: &str) -> Self {
87        if !password.is_empty() {
88            self.config.password = Some(password.to_string());
89        }
90        self
91    }
92
93    /// Set the namespace of the surrealdb service for read/write.
94    pub fn namespace(mut self, namespace: &str) -> Self {
95        if !namespace.is_empty() {
96            self.config.namespace = Some(namespace.to_string());
97        }
98        self
99    }
100
101    /// Set the database of the surrealdb service for read/write.
102    pub fn database(mut self, database: &str) -> Self {
103        if !database.is_empty() {
104            self.config.database = Some(database.to_string());
105        }
106        self
107    }
108
109    /// Set the key field name of the surrealdb service for read/write.
110    ///
111    /// Default to `key` if not specified.
112    pub fn key_field(mut self, key_field: &str) -> Self {
113        if !key_field.is_empty() {
114            self.config.key_field = Some(key_field.to_string());
115        }
116        self
117    }
118
119    /// Set the value field name of the surrealdb service for read/write.
120    ///
121    /// Default to `value` if not specified.
122    pub fn value_field(mut self, value_field: &str) -> Self {
123        if !value_field.is_empty() {
124            self.config.value_field = Some(value_field.to_string());
125        }
126        self
127    }
128}
129
130impl Builder for SurrealdbBuilder {
131    type Config = SurrealdbConfig;
132
133    fn build(self) -> Result<impl Service> {
134        let connection_string = match self.config.connection_string.clone() {
135            Some(v) => v,
136            None => {
137                return Err(
138                    Error::new(ErrorKind::ConfigInvalid, "connection_string is empty")
139                        .with_context("service", SURREALDB_SCHEME),
140                );
141            }
142        };
143
144        let namespace = match self.config.namespace.clone() {
145            Some(v) => v,
146            None => {
147                return Err(Error::new(ErrorKind::ConfigInvalid, "namespace is empty")
148                    .with_context("service", SURREALDB_SCHEME));
149            }
150        };
151        let database = match self.config.database.clone() {
152            Some(v) => v,
153            None => {
154                return Err(Error::new(ErrorKind::ConfigInvalid, "database is empty")
155                    .with_context("service", SURREALDB_SCHEME));
156            }
157        };
158        let table = match self.config.table.clone() {
159            Some(v) => v,
160            None => {
161                return Err(Error::new(ErrorKind::ConfigInvalid, "table is empty")
162                    .with_context("service", SURREALDB_SCHEME));
163            }
164        };
165
166        let username = self.config.username.clone().unwrap_or_default();
167        let password = self.config.password.clone().unwrap_or_default();
168        let key_field = self
169            .config
170            .key_field
171            .clone()
172            .unwrap_or_else(|| "key".to_string());
173        let value_field = self
174            .config
175            .value_field
176            .clone()
177            .unwrap_or_else(|| "value".to_string());
178        let root = normalize_root(
179            self.config
180                .root
181                .clone()
182                .unwrap_or_else(|| "/".to_string())
183                .as_str(),
184        );
185
186        Ok(SurrealdbBackend::new(SurrealdbCore {
187            db: OnceCell::new(),
188            connection_string,
189            username,
190            password,
191            namespace,
192            database,
193            table,
194            key_field,
195            value_field,
196        })
197        .with_normalized_root(root))
198    }
199}
200
201/// Backend for Surrealdb service
202#[derive(Clone, Debug)]
203pub struct SurrealdbBackend {
204    pub(crate) core: Arc<SurrealdbCore>,
205    pub(crate) root: String,
206    pub(crate) info: ServiceInfo,
207    pub(crate) capability: Capability,
208}
209
210impl SurrealdbBackend {
211    pub fn new(core: SurrealdbCore) -> Self {
212        let info = ServiceInfo::new(SURREALDB_SCHEME, "/", &core.table);
213        let capability = Capability {
214            read: true,
215            stat: true,
216            write: true,
217            write_can_empty: true,
218            delete: true,
219            shared: true,
220            ..Default::default()
221        };
222
223        Self {
224            core: Arc::new(core),
225            root: "/".to_string(),
226            info,
227            capability,
228        }
229    }
230
231    fn with_normalized_root(mut self, root: String) -> Self {
232        self.info = self.info.with_root(&root);
233        self.root = root;
234        self
235    }
236}
237
238impl Service for SurrealdbBackend {
239    type Reader = oio::StreamReader<SurrealdbReader>;
240    type Writer = SurrealdbWriter;
241    type Lister = ();
242    type Deleter = oio::OneShotDeleter<SurrealdbDeleter>;
243    type Copier = ();
244
245    fn info(&self) -> ServiceInfo {
246        self.info.clone()
247    }
248
249    fn capability(&self) -> Capability {
250        self.capability
251    }
252
253    async fn create_dir(
254        &self,
255        _ctx: &OperationContext,
256        _path: &str,
257        _args: OpCreateDir,
258    ) -> Result<RpCreateDir> {
259        Err(Error::new(
260            ErrorKind::Unsupported,
261            "operation is not supported",
262        ))
263    }
264
265    async fn stat(&self, _ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
266        let p = build_abs_path(&self.root, path);
267
268        if p == build_abs_path(&self.root, "") {
269            Ok(RpStat::new(Metadata::new(EntryMode::DIR)))
270        } else {
271            match self.core.get_length(&p).await? {
272                Some(length) => Ok(RpStat::new(
273                    Metadata::new(EntryMode::FILE).with_content_length(length as u64),
274                )),
275                None => Err(Error::new(ErrorKind::NotFound, "kv not found in surrealdb")),
276            }
277        }
278    }
279    fn read(&self, _ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
280        let output: oio::StreamReader<SurrealdbReader> = {
281            Ok(oio::StreamReader::new(SurrealdbReader::new(
282                self.clone(),
283                path,
284                args,
285            )))
286        }?;
287
288        Ok(output)
289    }
290
291    fn write(&self, _ctx: &OperationContext, path: &str, _: OpWrite) -> Result<Self::Writer> {
292        let output: SurrealdbWriter = {
293            let p = build_abs_path(&self.root, path);
294            Ok(SurrealdbWriter::new(self.core.clone(), p))
295        }?;
296
297        Ok(output)
298    }
299
300    fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
301        let output: oio::OneShotDeleter<SurrealdbDeleter> = {
302            Ok(oio::OneShotDeleter::new(SurrealdbDeleter::new(
303                self.core.clone(),
304                self.root.clone(),
305            )))
306        }?;
307
308        Ok(output)
309    }
310
311    fn list(&self, _ctx: &OperationContext, _path: &str, _args: OpList) -> Result<Self::Lister> {
312        Err(Error::new(
313            ErrorKind::Unsupported,
314            "operation is not supported",
315        ))
316    }
317
318    fn copy(
319        &self,
320        _ctx: &OperationContext,
321        _from: &str,
322        _to: &str,
323        _args: OpCopy,
324        _opts: OpCopier,
325    ) -> Result<Self::Copier> {
326        Err(Error::new(
327            ErrorKind::Unsupported,
328            "operation is not supported",
329        ))
330    }
331
332    async fn rename(
333        &self,
334        _ctx: &OperationContext,
335        _from: &str,
336        _to: &str,
337        _args: OpRename,
338    ) -> Result<RpRename> {
339        Err(Error::new(
340            ErrorKind::Unsupported,
341            "operation is not supported",
342        ))
343    }
344
345    async fn presign(
346        &self,
347        _ctx: &OperationContext,
348        _path: &str,
349        _args: OpPresign,
350    ) -> Result<RpPresign> {
351        Err(Error::new(
352            ErrorKind::Unsupported,
353            "operation is not supported",
354        ))
355    }
356}