1use std::str::FromStr;
19use std::sync::Arc;
20
21use asyncband::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 type Composer = ();
216
217 fn info(&self) -> ServiceInfo {
218 self.info.clone()
219 }
220
221 fn capability(&self) -> Capability {
222 self.capability
223 }
224
225 async fn stat(&self, _ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
226 let p = build_abs_path(&self.root, path);
227
228 if p == build_abs_path(&self.root, "") {
229 Ok(RpStat::new(MetadataBuilder::dir().build()))
230 } else {
231 let length = self.core.get_length(&p).await?;
232 match length {
233 Some(length) => {
234 let metadata = if p.ends_with('/') {
235 MetadataBuilder::dir()
236 } else {
237 MetadataBuilder::file(length as u64)
238 };
239 Ok(RpStat::new(metadata.build()))
240 }
241 None => {
242 let dir_path = if p.ends_with('/') {
244 p.clone()
245 } else {
246 format!("{}/", p)
247 };
248 let count = self.core.count_under(&dir_path).await?;
249
250 if count > 0 {
251 Ok(RpStat::new(MetadataBuilder::dir().build()))
253 } else {
254 Err(Error::new(ErrorKind::NotFound, "key not found in sqlite"))
255 }
256 }
257 }
258 }
259 }
260 fn read(&self, _ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
261 let output: oio::StreamReader<SqliteReader> = {
262 Ok(oio::StreamReader::new(SqliteReader::new(
263 self.clone(),
264 path,
265 args,
266 )))
267 }?;
268
269 Ok(output)
270 }
271
272 fn write(&self, _ctx: &OperationContext, path: &str, _: OpWrite) -> Result<Self::Writer> {
273 let output: SqliteWriter = {
274 let p = build_abs_path(&self.root, path);
275 Ok(SqliteWriter::new(self.core.clone(), &p))
276 }?;
277
278 Ok(output)
279 }
280
281 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
282 let output: oio::OneShotDeleter<SqliteDeleter> = {
283 Ok(oio::OneShotDeleter::new(SqliteDeleter::new(
284 self.core.clone(),
285 self.root.clone(),
286 )))
287 }?;
288
289 Ok(output)
290 }
291
292 async fn create_dir(
293 &self,
294 _ctx: &OperationContext,
295 path: &str,
296 _: OpCreateDir,
297 ) -> Result<RpCreateDir> {
298 let p = build_abs_path(&self.root, path);
299
300 let dir_path = if p.ends_with('/') {
302 p
303 } else {
304 format!("{}/", p)
305 };
306
307 self.core.set(&dir_path, Buffer::new()).await?;
309
310 Ok(RpCreateDir::default())
311 }
312
313 fn list(&self, _ctx: &OperationContext, _path: &str, _args: OpList) -> Result<Self::Lister> {
314 Err(Error::new(
315 ErrorKind::Unsupported,
316 "operation is not supported",
317 ))
318 }
319
320 fn copy(
321 &self,
322 _ctx: &OperationContext,
323 _from: &str,
324 _to: &str,
325 _args: OpCopy,
326 ) -> Result<Self::Copier> {
327 Err(Error::new(
328 ErrorKind::Unsupported,
329 "operation is not supported",
330 ))
331 }
332
333 async fn rename(
334 &self,
335 _ctx: &OperationContext,
336 _from: &str,
337 _to: &str,
338 _args: OpRename,
339 ) -> Result<RpRename> {
340 Err(Error::new(
341 ErrorKind::Unsupported,
342 "operation is not supported",
343 ))
344 }
345
346 async fn presign(
347 &self,
348 _ctx: &OperationContext,
349 _path: &str,
350 _args: OpPresign,
351 ) -> Result<RpPresign> {
352 Err(Error::new(
353 ErrorKind::Unsupported,
354 "operation is not supported",
355 ))
356 }
357}
358
359#[cfg(test)]
360mod test {
361 use super::*;
362 use opendal_core::raw::oio::Read as _;
363 use opendal_core::raw::oio::ReadStream as _;
364 use opendal_core::raw::oio::Write as _;
365 use sqlx::SqlitePool;
366
367 async fn build_client() -> OnceCell<SqlitePool> {
368 let config = SqliteConnectOptions::from_str("sqlite::memory:").unwrap();
369 let pool = SqlitePool::connect_with(config).await.unwrap();
370 OnceCell::from_value(pool)
371 }
372
373 async fn build_backend() -> SqliteBackend {
374 let core = SqliteCore {
375 pool: build_client().await,
376 config: Default::default(),
377 table: "test_table".to_string(),
378 key_field: "key".to_string(),
379 value_field: "value".to_string(),
380 };
381
382 SqliteBackend::new(core)
383 }
384
385 #[tokio::test]
386 async fn test_sqlite_backend_creation() {
387 let backend = build_backend().await;
388
389 assert_eq!(backend.root, "/");
391 assert_eq!(backend.info.scheme(), SQLITE_SCHEME);
392 assert!(backend.capability().read);
393 assert!(backend.capability().write);
394 assert!(backend.capability().delete);
395 assert!(backend.capability().stat);
396 }
397
398 #[tokio::test]
399 async fn test_sqlite_backend_with_root() {
400 let backend = build_backend()
401 .await
402 .with_normalized_root("/test/".to_string());
403
404 assert_eq!(backend.root, "/test/");
405 assert_eq!(backend.info.root(), Arc::from("/test/"));
406 }
407
408 #[tokio::test]
409 async fn test_sqlite_read_range_from_offset_reads_to_eof() {
410 let backend = build_backend().await;
411
412 let pool = backend.core.get_client().await.unwrap();
413 sqlx::query("CREATE TABLE test_table (key TEXT PRIMARY KEY, value BLOB)")
414 .execute(pool)
415 .await
416 .unwrap();
417
418 let ctx = OperationContext::new();
419 let mut writer = backend.write(&ctx, "hello", OpWrite::default()).unwrap();
420 writer.write(Buffer::from("hello world")).await.unwrap();
421 writer.close().await.unwrap();
422
423 let reader = backend.read(&ctx, "hello", OpRead::default()).unwrap();
424 let (_, mut stream) = reader.open(BytesRange::from(6_u64..)).await.unwrap();
425 let buffer = stream.read_all().await.unwrap();
426
427 assert_eq!(buffer.to_vec(), b"world");
428 }
429
430 #[tokio::test]
431 async fn test_sqlite_stat_uses_value_length() {
432 let backend = build_backend().await;
433
434 let pool = backend.core.get_client().await.unwrap();
435 sqlx::query("CREATE TABLE test_table (key TEXT PRIMARY KEY, value BLOB)")
436 .execute(pool)
437 .await
438 .unwrap();
439
440 let ctx = OperationContext::new();
441 let mut writer = backend.write(&ctx, "key_id", OpWrite::default()).unwrap();
442 writer.write(Buffer::from("hello world")).await.unwrap();
443 writer.close().await.unwrap();
444
445 let rp = backend
446 .stat(&ctx, "key_id", OpStat::default())
447 .await
448 .unwrap();
449
450 assert_eq!(rp.into_metadata().content_length(), 11);
451 }
452
453 #[tokio::test]
454 async fn test_sqlite_stat_returns_byte_length_for_text_value() {
455 let backend = build_backend().await;
456
457 let pool = backend.core.get_client().await.unwrap();
458 sqlx::query("CREATE TABLE test_table (key TEXT PRIMARY KEY, value BLOB)")
459 .execute(pool)
460 .await
461 .unwrap();
462 sqlx::query("INSERT INTO test_table (key, value) VALUES ($1, $2)")
463 .bind("key_id")
464 .bind("你好")
465 .execute(pool)
466 .await
467 .unwrap();
468
469 let ctx = OperationContext::new();
470
471 let rp = backend
472 .stat(&ctx, "key_id", OpStat::default())
473 .await
474 .unwrap();
475 assert_eq!(rp.into_metadata().content_length(), 6);
476
477 let reader = backend.read(&ctx, "key_id", OpRead::default()).unwrap();
478 let (rp, mut stream) = reader.open(BytesRange::from(0_u64..3)).await.unwrap();
479 let buffer = stream.read_all().await.unwrap();
480
481 assert_eq!(rp.into_metadata().unwrap().content_length(), 6);
482 assert_eq!(buffer.to_vec(), "你".as_bytes());
483 }
484
485 #[tokio::test]
486 async fn test_sqlite_stat_returns_byte_length_for_text_column() {
487 let backend = build_backend().await;
488 let pool = backend.core.get_client().await.unwrap();
489
490 sqlx::query("CREATE TABLE test_table (key TEXT PRIMARY KEY, value TEXT)")
491 .execute(pool)
492 .await
493 .unwrap();
494 sqlx::query("INSERT INTO test_table (key, value) VALUES ($1, $2)")
495 .bind("key_id")
496 .bind("你好")
497 .execute(pool)
498 .await
499 .unwrap();
500
501 let ctx = OperationContext::new();
502
503 let rp = backend
504 .stat(&ctx, "key_id", OpStat::default())
505 .await
506 .unwrap();
507 assert_eq!(rp.into_metadata().content_length(), 6);
508
509 let reader = backend.read(&ctx, "key_id", OpRead::default()).unwrap();
510 let (rp, mut stream) = reader.open(BytesRange::from(0_u64..3)).await.unwrap();
511 let buffer = stream.read_all().await.unwrap();
512
513 assert_eq!(rp.into_metadata().unwrap().content_length(), 6);
514 assert_eq!(buffer.to_vec(), "你".as_bytes());
515 }
516}