This is an automated email from the ASF dual-hosted git repository.

erickguan pushed a commit to branch fix-sqlite-stat
in repository https://gitbox.apache.org/repos/asf/opendal.git

commit 52bc23fee84df621d417a73484fffe6495f8206c
Author: Erick Guan <[email protected]>
AuthorDate: Sat Jul 4 17:18:01 2026 +0800

    Fix sqlite stat getting the entire object
---
 core/services/sqlite/src/backend.rs | 75 +++++++++++++++++++++++++++++++++++--
 core/services/sqlite/src/core.rs    |  6 +--
 2 files changed, 74 insertions(+), 7 deletions(-)

diff --git a/core/services/sqlite/src/backend.rs 
b/core/services/sqlite/src/backend.rs
index 63feabbac..ad1571017 100644
--- a/core/services/sqlite/src/backend.rs
+++ b/core/services/sqlite/src/backend.rs
@@ -228,10 +228,10 @@ impl Service for SqliteBackend {
         if p == build_abs_path(&self.root, "") {
             Ok(RpStat::new(Metadata::new(EntryMode::DIR)))
         } else {
-            let bs = self.core.get(&p).await?;
-            match bs {
-                Some(bs) => Ok(RpStat::new(
-                    
Metadata::new(EntryMode::from_path(&p)).with_content_length(bs.len() as u64),
+            let length = self.core.get_length(&p).await?;
+            match length {
+                Some(length) => Ok(RpStat::new(
+                    
Metadata::new(EntryMode::from_path(&p)).with_content_length(length as u64),
                 )),
                 None => {
                     // Check if this might be a directory by looking for keys 
with this prefix
@@ -437,4 +437,71 @@ mod test {
 
         assert_eq!(buffer.to_vec(), b"world");
     }
+
+    #[tokio::test]
+    async fn test_sqlite_stat_uses_value_length() {
+        let core = SqliteCore {
+            pool: build_client().await,
+            config: Default::default(),
+            table: "test".to_string(),
+            key_field: "key".to_string(),
+            value_field: "value".to_string(),
+        };
+        let pool = core.get_client().await.unwrap();
+        sqlx::query("CREATE TABLE test (key TEXT PRIMARY KEY, value BLOB)")
+            .execute(pool)
+            .await
+            .unwrap();
+
+        let accessor = SqliteBackend::new(core);
+        let ctx = OperationContext::new();
+        let mut writer = accessor.write(&ctx, "hello", 
OpWrite::default()).unwrap();
+        writer.write(Buffer::from("hello world")).await.unwrap();
+        writer.close().await.unwrap();
+
+        let rp = accessor
+            .stat(&ctx, "hello", OpStat::default())
+            .await
+            .unwrap();
+
+        assert_eq!(rp.into_metadata().content_length(), 11);
+    }
+
+    #[tokio::test]
+    async fn test_sqlite_stat_returns_byte_length_for_text_value() {
+        let core = SqliteCore {
+            pool: build_client().await,
+            config: Default::default(),
+            table: "test".to_string(),
+            key_field: "key".to_string(),
+            value_field: "value".to_string(),
+        };
+        let pool = core.get_client().await.unwrap();
+        sqlx::query("CREATE TABLE test (key TEXT PRIMARY KEY, value BLOB)")
+            .execute(pool)
+            .await
+            .unwrap();
+        sqlx::query("INSERT INTO test (key, value) VALUES ($1, $2)")
+            .bind("hello")
+            .bind("你好")
+            .execute(pool)
+            .await
+            .unwrap();
+
+        let accessor = SqliteBackend::new(core);
+        let ctx = OperationContext::new();
+
+        let rp = accessor
+            .stat(&ctx, "hello", OpStat::default())
+            .await
+            .unwrap();
+        assert_eq!(rp.into_metadata().content_length(), 6);
+
+        let reader = accessor.read(&ctx, "hello", OpRead::default()).unwrap();
+        let (rp, mut stream) = 
reader.open(BytesRange::from(0_u64..3)).await.unwrap();
+        let buffer = stream.read_all().await.unwrap();
+
+        assert_eq!(rp.into_metadata().unwrap().content_length(), 6);
+        assert_eq!(buffer.to_vec(), "你".as_bytes());
+    }
 }
diff --git a/core/services/sqlite/src/core.rs b/core/services/sqlite/src/core.rs
index 9cc82440d..16ee5f68a 100644
--- a/core/services/sqlite/src/core.rs
+++ b/core/services/sqlite/src/core.rs
@@ -64,7 +64,7 @@ impl SqliteCore {
         let pool = self.get_client().await?;
 
         let value: Option<i64> = sqlx::query_scalar(&format!(
-            "SELECT LENGTH(`{}`) FROM `{}` WHERE `{}` = $1 LIMIT 1",
+            "SELECT LENGTH(CAST(`{}` AS BLOB)) FROM `{}` WHERE `{}` = $1 LIMIT 
1",
             self.value_field, self.table, self.key_field
         ))
         .bind(path)
@@ -91,7 +91,7 @@ impl SqliteCore {
         let pool = self.get_client().await?;
         let query = match limit {
             Some(limit) => format!(
-                "SELECT SUBSTR(`{}`, {}, {}), LENGTH(`{}`) FROM `{}` WHERE 
`{}` = $1 LIMIT 1",
+                "SELECT SUBSTR(CAST(`{}` AS BLOB), {}, {}), LENGTH(CAST(`{}` 
AS BLOB)) FROM `{}` WHERE `{}` = $1 LIMIT 1",
                 self.value_field,
                 start + 1,
                 limit,
@@ -100,7 +100,7 @@ impl SqliteCore {
                 self.key_field
             ),
             None => format!(
-                "SELECT SUBSTR(`{}`, {}), LENGTH(`{}`) FROM `{}` WHERE `{}` = 
$1 LIMIT 1",
+                "SELECT SUBSTR(CAST(`{}` AS BLOB), {}), LENGTH(CAST(`{}` AS 
BLOB)) FROM `{}` WHERE `{}` = $1 LIMIT 1",
                 self.value_field,
                 start + 1,
                 self.value_field,

Reply via email to