You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2026/08/27 10:14:35 UTC

(opendal) branch main updated: fix: repair azfile, b2, and conditional read behavior (#8175)

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

Xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new d5dd6dfa39 fix: repair azfile, b2, and conditional read behavior (#8175)
d5dd6dfa39 is described below

commit d5dd6dfa39ea97189aad46a6be611c6cb32b5351
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Thu Aug 27 18:14:27 2026 +0800

    fix: repair azfile, b2, and conditional read behavior (#8175)
---
 .../dotnet/OpenDAL.Tests/Behavior/ReadBehaviorTest.cs | 11 +++++++++--
 core/services/azdls/src/backend.rs                    |  8 ++++++--
 core/services/azdls/src/core.rs                       | 19 +++++++++++++++++--
 core/services/azdls/src/writer.rs                     |  2 +-
 core/services/azfile/src/backend.rs                   |  3 +++
 core/services/b2/src/backend.rs                       |  7 +++++--
 6 files changed, 41 insertions(+), 9 deletions(-)

diff --git a/bindings/dotnet/OpenDAL.Tests/Behavior/ReadBehaviorTest.cs b/bindings/dotnet/OpenDAL.Tests/Behavior/ReadBehaviorTest.cs
index b90d7a0409..11772c418c 100644
--- a/bindings/dotnet/OpenDAL.Tests/Behavior/ReadBehaviorTest.cs
+++ b/bindings/dotnet/OpenDAL.Tests/Behavior/ReadBehaviorTest.cs
@@ -77,7 +77,10 @@ public sealed class ReadBehaviorTest : BehaviorTestBase
         // Covers the whole path end to end at a size worth measuring. The chunk
         // walking itself is pinned by the unit tests in src/buffer.rs, which build
         // multi-part buffers a backend may or may not produce.
-        const int size = 8 * 1024 * 1024;
+        const int defaultSize = 8 * 1024 * 1024;
+        var size = (int)Math.Min(
+            (ulong)defaultSize,
+            Capability.WriteTotalMaxSize ?? ulong.MaxValue);
         var path = NewPath("read-large");
         var content = RandomBytes(size);
 
@@ -96,8 +99,12 @@ public sealed class ReadBehaviorTest : BehaviorTestBase
             return;
         }
 
+        const int defaultSize = 8 * 1024 * 1024;
+        var size = (int)Math.Min(
+            (ulong)defaultSize,
+            Capability.WriteTotalMaxSize ?? ulong.MaxValue);
         var path = NewPath("read-large-async");
-        var content = RandomBytes(8 * 1024 * 1024);
+        var content = RandomBytes(size);
 
         await Op.WriteAsync(path, content, CT);
         var actual = await Op.ReadAsync(path, CT);
diff --git a/core/services/azdls/src/backend.rs b/core/services/azdls/src/backend.rs
index 287a03081f..0a321d28b5 100644
--- a/core/services/azdls/src/backend.rs
+++ b/core/services/azdls/src/backend.rs
@@ -334,6 +334,10 @@ impl Builder for AzdlsBuilder {
         let info = ServiceInfo::new(AZDLS_SCHEME, &root, filesystem);
         let capability = Capability {
             stat: true,
+            stat_with_if_match: true,
+            stat_with_if_none_match: true,
+            stat_with_if_modified_since: true,
+            stat_with_if_unmodified_since: true,
 
             read: true,
             read_with_if_match: true,
@@ -417,14 +421,14 @@ impl Service for AzdlsBackend {
         }
     }
 
-    async fn stat(&self, ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
+    async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
         // Stat root always returns a DIR.
         // TODO: include metadata for the root (#4746)
         if path == "/" {
             return Ok(RpStat::new(Metadata::new(EntryMode::DIR)));
         }
 
-        let metadata = self.core.azdls_stat_metadata(ctx, path).await?;
+        let metadata = self.core.azdls_stat_metadata(ctx, path, &args).await?;
         Ok(RpStat::new(metadata))
     }
     fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
diff --git a/core/services/azdls/src/core.rs b/core/services/azdls/src/core.rs
index a63b226d38..f509ccd0c6 100644
--- a/core/services/azdls/src/core.rs
+++ b/core/services/azdls/src/core.rs
@@ -339,6 +339,7 @@ impl AzdlsCore {
         &self,
         ctx: &OperationContext,
         path: &str,
+        args: &OpStat,
     ) -> Result<Response<Buffer>> {
         let p = build_abs_path(&self.root, path)
             .trim_end_matches('/')
@@ -351,7 +352,20 @@ impl AzdlsCore {
             percent_encode_path(&p)
         );
 
-        let req = Request::head(&url);
+        let mut req = Request::head(&url);
+
+        if let Some(if_match) = args.if_match() {
+            req = req.header(IF_MATCH, if_match);
+        }
+        if let Some(if_none_match) = args.if_none_match() {
+            req = req.header(IF_NONE_MATCH, if_none_match);
+        }
+        if let Some(if_modified_since) = args.if_modified_since() {
+            req = req.header(IF_MODIFIED_SINCE, if_modified_since.format_http_date());
+        }
+        if let Some(if_unmodified_since) = args.if_unmodified_since() {
+            req = req.header(IF_UNMODIFIED_SINCE, if_unmodified_since.format_http_date());
+        }
 
         let req = req
             .extension(Operation::Stat)
@@ -367,8 +381,9 @@ impl AzdlsCore {
         &self,
         ctx: &OperationContext,
         path: &str,
+        args: &OpStat,
     ) -> Result<Metadata> {
-        let resp = self.azdls_get_properties(ctx, path).await?;
+        let resp = self.azdls_get_properties(ctx, path, args).await?;
 
         if resp.status() != StatusCode::OK {
             return Err(parse_error(resp));
diff --git a/core/services/azdls/src/writer.rs b/core/services/azdls/src/writer.rs
index 3bea6a0051..8e40933e56 100644
--- a/core/services/azdls/src/writer.rs
+++ b/core/services/azdls/src/writer.rs
@@ -162,7 +162,7 @@ impl oio::AppendWrite for AzdlsWriter {
     async fn offset(&self) -> Result<u64> {
         let resp = self
             .core
-            .azdls_get_properties(&self.ctx, &self.path)
+            .azdls_get_properties(&self.ctx, &self.path, &OpStat::default())
             .await?;
 
         let status = resp.status();
diff --git a/core/services/azfile/src/backend.rs b/core/services/azfile/src/backend.rs
index d3437bc07c..57b0dfedd9 100644
--- a/core/services/azfile/src/backend.rs
+++ b/core/services/azfile/src/backend.rs
@@ -237,6 +237,9 @@ impl Builder for AzfileBuilder {
 
             write: true,
             write_with_user_metadata: true,
+            // Azure Files limits each Put Range update to 4 MiB.
+            // ref: <https://learn.microsoft.com/en-us/rest/api/storageservices/put-range>
+            write_total_max_size: Some(4 * 1024 * 1024),
 
             create_dir: true,
             delete: true,
diff --git a/core/services/b2/src/backend.rs b/core/services/b2/src/backend.rs
index 883c0b0c09..bf3047e062 100644
--- a/core/services/b2/src/backend.rs
+++ b/core/services/b2/src/backend.rs
@@ -393,7 +393,7 @@ impl Service for B2Backend {
                     parts.headers,
                 )))
             }
-            PresignOperation::Read(_, _) => {
+            PresignOperation::Read(range, _) => {
                 let resp = self
                     .core
                     .get_download_authorization(ctx, path, args.expire())
@@ -410,7 +410,10 @@ impl Service for B2Backend {
                     resp.authorization_token
                 );
 
-                let req = Request::get(url);
+                let mut req = Request::get(url);
+                if !range.is_full() {
+                    req = req.header(http::header::RANGE, range.to_header());
+                }
 
                 let req = req.body(Buffer::new()).map_err(new_request_build_error)?;