1use std::str::FromStr;
19use std::sync::Arc;
20
21use mea::once::OnceCell;
22use sqlx::sqlite::SqliteConnectOptions;
23
24use super::SQLITE_SCHEME;
25use super::config::SqliteConfig;
26use super::core::SqliteCore;
27use super::deleter::SqliteDeleter;
28use super::reader::*;
29use super::writer::SqliteWriter;
30use opendal_core::raw::oio;
31use opendal_core::raw::*;
32use opendal_core::*;
33
34#[doc = include_str!("docs.md")]
35#[derive(Debug, Default)]
36pub struct SqliteBuilder {
37 pub(super) config: SqliteConfig,
38}
39
40impl SqliteBuilder {
41 pub fn connection_string(mut self, v: &str) -> Self {
55 if !v.is_empty() {
56 self.config.connection_string = Some(v.to_string());
57 }
58 self
59 }
60
61 pub fn root(mut self, root: &str) -> Self {
65 self.config.root = if root.is_empty() {
66 None
67 } else {
68 Some(root.to_string())
69 };
70
71 self
72 }
73
74 pub fn table(mut self, table: &str) -> Self {
76 if !table.is_empty() {
77 self.config.table = Some(table.to_string());
78 }
79 self
80 }
81
82 pub fn key_field(mut self, key_field: &str) -> Self {
86 if !key_field.is_empty() {
87 self.config.key_field = Some(key_field.to_string());
88 }
89 self
90 }
91
92 pub fn value_field(mut self, value_field: &str) -> Self {
96 if !value_field.is_empty() {
97 self.config.value_field = Some(value_field.to_string());
98 }
99 self
100 }
101}
102
103impl Builder for SqliteBuilder {
104 type Config = SqliteConfig;
105
106 fn build(self) -> Result<impl Service> {
107 let conn = match self.config.connection_string {
108 Some(v) => v,
109 None => {
110 return Err(Error::new(
111 ErrorKind::ConfigInvalid,
112 "connection_string is required but not set",
113 )
114 .with_context("service", SQLITE_SCHEME));
115 }
116 };
117
118 let config = SqliteConnectOptions::from_str(&conn).map_err(|err| {
119 Error::new(ErrorKind::ConfigInvalid, "connection_string is invalid")
120 .with_context("service", SQLITE_SCHEME)
121 .set_source(err)
122 })?;
123
124 let table = match self.config.table {
125 Some(v) => v,
126 None => {
127 return Err(Error::new(ErrorKind::ConfigInvalid, "table is empty")
128 .with_context("service", SQLITE_SCHEME));
129 }
130 };
131
132 let key_field = self.config.key_field.unwrap_or_else(|| "key".to_string());
133
134 let value_field = self
135 .config
136 .value_field
137 .unwrap_or_else(|| "value".to_string());
138
139 let root = normalize_root(self.config.root.as_deref().unwrap_or("/"));
140
141 Ok(SqliteBackend::new(SqliteCore {
142 pool: OnceCell::new(),
143 config,
144 table,
145 key_field,
146 value_field,
147 })
148 .with_normalized_root(root))
149 }
150}
151
152pub fn parse_sqlite_error(err: sqlx::Error) -> Error {
153 let is_temporary = matches!(
154 &err,
155 sqlx::Error::Database(db_err) if db_err.code().is_some_and(|c| c == "5" || c == "6")
156 );
157
158 let message = if is_temporary {
159 "database is locked or busy"
160 } else {
161 "unhandled error from sqlite"
162 };
163
164 let mut error = Error::new(ErrorKind::Unexpected, message).set_source(err);
165 if is_temporary {
166 error = error.set_temporary();
167 }
168 error
169}
170
171#[derive(Debug, Clone)]
173pub struct SqliteBackend {
174 pub(crate) core: Arc<SqliteCore>,
175 pub(crate) root: String,
176 pub(crate) info: ServiceInfo,
177 pub(crate) capability: Capability,
178}
179
180impl SqliteBackend {
181 fn new(core: SqliteCore) -> Self {
182 let info = ServiceInfo::new(SQLITE_SCHEME, "/", &core.table);
183 let capability = Capability {
184 read: true,
185 write: true,
186 create_dir: true,
187 delete: true,
188 stat: true,
189 write_can_empty: true,
190 list: false,
191 ..Default::default()
192 };
193
194 Self {
195 core: Arc::new(core),
196 root: "/".to_string(),
197 info,
198 capability,
199 }
200 }
201
202 fn with_normalized_root(mut self, root: String) -> Self {
203 self.info = self.info.with_root(&root);
204 self.root = root;
205 self
206 }
207}
208
209impl Service for SqliteBackend {
210 type Reader = oio::StreamReader<SqliteReader>;
211 type Writer = SqliteWriter;
212 type Lister = ();
213 type Deleter = oio::OneShotDeleter<SqliteDeleter>;
214 type Copier = ();
215
216 fn info(&self) -> ServiceInfo {
217 self.info.clone()
218 }
219
220 fn capability(&self) -> Capability {
221 self.capability
222 }
223
224 async fn stat(&self, _ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
225 let p = build_abs_path(&self.root, path);
226
227 if p == build_abs_path(&self.root, "") {
228 Ok(RpStat::new(Metadata::new(EntryMode::DIR)))
229 } else {
230 let length = self.core.get_length(&p).await?;
231 match length {
232 Some(length) => Ok(RpStat::new(
233 Metadata::new(EntryMode::from_path(&p)).with_content_length(length as u64),
234 )),
235 None => {
236 let dir_path = if p.ends_with('/') {
238 p.clone()
239 } else {
240 format!("{}/", p)
241 };
242 let count = self.core.count_under(&dir_path).await?;
243
244 if count > 0 {
245 Ok(RpStat::new(Metadata::new(EntryMode::DIR)))
247 } else {
248 Err(Error::new(ErrorKind::NotFound, "key not found in sqlite"))
249 }
250 }
251 }
252 }
253 }
254 fn read(&self, _ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
255 let output: oio::StreamReader<SqliteReader> = {
256 Ok(oio::StreamReader::new(SqliteReader::new(
257 self.clone(),
258 path,
259 args,
260 )))
261 }?;
262
263 Ok(output)
264 }
265
266 fn write(&self, _ctx: &OperationContext, path: &str, _: OpWrite) -> Result<Self::Writer> {
267 let output: SqliteWriter = {
268 let p = build_abs_path(&self.root, path);
269 Ok(SqliteWriter::new(self.core.clone(), &p))
270 }?;
271
272 Ok(output)
273 }
274
275 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
276 let output: oio::OneShotDeleter<SqliteDeleter> = {
277 Ok(oio::OneShotDeleter::new(SqliteDeleter::new(
278 self.core.clone(),
279 self.root.clone(),
280 )))
281 }?;
282
283 Ok(output)
284 }
285
286 async fn create_dir(
287 &self,
288 _ctx: &OperationContext,
289 path: &str,
290 _: OpCreateDir,
291 ) -> Result<RpCreateDir> {
292 let p = build_abs_path(&self.root, path);
293
294 let dir_path = if p.ends_with('/') {
296 p
297 } else {
298 format!("{}/", p)
299 };
300
301 self.core.set(&dir_path, Buffer::new()).await?;
303
304 Ok(RpCreateDir::default())
305 }
306
307 fn list(&self, _ctx: &OperationContext, _path: &str, _args: OpList) -> Result<Self::Lister> {
308 Err(Error::new(
309 ErrorKind::Unsupported,
310 "operation is not supported",
311 ))
312 }
313
314 fn copy(
315 &self,
316 _ctx: &OperationContext,
317 _from: &str,
318 _to: &str,
319 _args: OpCopy,
320 _opts: OpCopier,
321 ) -> Result<Self::Copier> {
322 Err(Error::new(
323 ErrorKind::Unsupported,
324 "operation is not supported",
325 ))
326 }
327
328 async fn rename(
329 &self,
330 _ctx: &OperationContext,
331 _from: &str,
332 _to: &str,
333 _args: OpRename,
334 ) -> Result<RpRename> {
335 Err(Error::new(
336 ErrorKind::Unsupported,
337 "operation is not supported",
338 ))
339 }
340
341 async fn presign(
342 &self,
343 _ctx: &OperationContext,
344 _path: &str,
345 _args: OpPresign,
346 ) -> Result<RpPresign> {
347 Err(Error::new(
348 ErrorKind::Unsupported,
349 "operation is not supported",
350 ))
351 }
352}
353
354#[cfg(test)]
355mod test {
356 use super::*;
357 use opendal_core::raw::oio::Read as _;
358 use opendal_core::raw::oio::ReadStream as _;
359 use opendal_core::raw::oio::Write as _;
360 use sqlx::SqlitePool;
361
362 async fn build_client() -> OnceCell<SqlitePool> {
363 let config = SqliteConnectOptions::from_str("sqlite::memory:").unwrap();
364 let pool = SqlitePool::connect_with(config).await.unwrap();
365 OnceCell::from_value(pool)
366 }
367
368 async fn build_backend() -> SqliteBackend {
369 let core = SqliteCore {
370 pool: build_client().await,
371 config: Default::default(),
372 table: "test_table".to_string(),
373 key_field: "key".to_string(),
374 value_field: "value".to_string(),
375 };
376
377 SqliteBackend::new(core)
378 }
379
380 #[tokio::test]
381 async fn test_sqlite_backend_creation() {
382 let backend = build_backend().await;
383
384 assert_eq!(backend.root, "/");
386 assert_eq!(backend.info.scheme(), SQLITE_SCHEME);
387 assert!(backend.capability().read);
388 assert!(backend.capability().write);
389 assert!(backend.capability().delete);
390 assert!(backend.capability().stat);
391 }
392
393 #[tokio::test]
394 async fn test_sqlite_backend_with_root() {
395 let backend = build_backend()
396 .await
397 .with_normalized_root("/test/".to_string());
398
399 assert_eq!(backend.root, "/test/");
400 assert_eq!(backend.info.root(), Arc::from("/test/"));
401 }
402
403 #[tokio::test]
404 async fn test_sqlite_read_range_from_offset_reads_to_eof() {
405 let backend = build_backend().await;
406
407 let pool = backend.core.get_client().await.unwrap();
408 sqlx::query("CREATE TABLE test_table (key TEXT PRIMARY KEY, value BLOB)")
409 .execute(pool)
410 .await
411 .unwrap();
412
413 let ctx = OperationContext::new();
414 let mut writer = backend.write(&ctx, "hello", OpWrite::default()).unwrap();
415 writer.write(Buffer::from("hello world")).await.unwrap();
416 writer.close().await.unwrap();
417
418 let reader = backend.read(&ctx, "hello", OpRead::default()).unwrap();
419 let (_, mut stream) = reader.open(BytesRange::from(6_u64..)).await.unwrap();
420 let buffer = stream.read_all().await.unwrap();
421
422 assert_eq!(buffer.to_vec(), b"world");
423 }
424
425 #[tokio::test]
426 async fn test_sqlite_stat_uses_value_length() {
427 let backend = build_backend().await;
428
429 let pool = backend.core.get_client().await.unwrap();
430 sqlx::query("CREATE TABLE test_table (key TEXT PRIMARY KEY, value BLOB)")
431 .execute(pool)
432 .await
433 .unwrap();
434
435 let ctx = OperationContext::new();
436 let mut writer = backend.write(&ctx, "key_id", OpWrite::default()).unwrap();
437 writer.write(Buffer::from("hello world")).await.unwrap();
438 writer.close().await.unwrap();
439
440 let rp = backend
441 .stat(&ctx, "key_id", OpStat::default())
442 .await
443 .unwrap();
444
445 assert_eq!(rp.into_metadata().content_length(), 11);
446 }
447
448 #[tokio::test]
449 async fn test_sqlite_stat_returns_byte_length_for_text_value() {
450 let backend = build_backend().await;
451
452 let pool = backend.core.get_client().await.unwrap();
453 sqlx::query("CREATE TABLE test_table (key TEXT PRIMARY KEY, value BLOB)")
454 .execute(pool)
455 .await
456 .unwrap();
457 sqlx::query("INSERT INTO test_table (key, value) VALUES ($1, $2)")
458 .bind("key_id")
459 .bind("你好")
460 .execute(pool)
461 .await
462 .unwrap();
463
464 let ctx = OperationContext::new();
465
466 let rp = backend
467 .stat(&ctx, "key_id", OpStat::default())
468 .await
469 .unwrap();
470 assert_eq!(rp.into_metadata().content_length(), 6);
471
472 let reader = backend.read(&ctx, "key_id", OpRead::default()).unwrap();
473 let (rp, mut stream) = reader.open(BytesRange::from(0_u64..3)).await.unwrap();
474 let buffer = stream.read_all().await.unwrap();
475
476 assert_eq!(rp.into_metadata().unwrap().content_length(), 6);
477 assert_eq!(buffer.to_vec(), "你".as_bytes());
478 }
479
480 #[tokio::test]
481 async fn test_sqlite_stat_returns_byte_length_for_text_column() {
482 let backend = build_backend().await;
483 let pool = backend.core.get_client().await.unwrap();
484
485 sqlx::query("CREATE TABLE test_table (key TEXT PRIMARY KEY, value TEXT)")
486 .execute(pool)
487 .await
488 .unwrap();
489 sqlx::query("INSERT INTO test_table (key, value) VALUES ($1, $2)")
490 .bind("key_id")
491 .bind("你好")
492 .execute(pool)
493 .await
494 .unwrap();
495
496 let ctx = OperationContext::new();
497
498 let rp = backend
499 .stat(&ctx, "key_id", OpStat::default())
500 .await
501 .unwrap();
502 assert_eq!(rp.into_metadata().content_length(), 6);
503
504 let reader = backend.read(&ctx, "key_id", OpRead::default()).unwrap();
505 let (rp, mut stream) = reader.open(BytesRange::from(0_u64..3)).await.unwrap();
506 let buffer = stream.read_all().await.unwrap();
507
508 assert_eq!(rp.into_metadata().unwrap().content_length(), 6);
509 assert_eq!(buffer.to_vec(), "你".as_bytes());
510 }
511}