You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by de...@apache.org on 2026/07/30 07:16:23 UTC

(opendal) branch main updated: feat(binding/cpp): expose positional read API (#7986)

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

dentiny 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 2fed9d89e feat(binding/cpp): expose positional read API (#7986)
2fed9d89e is described below

commit 2fed9d89ee70cd98775e444119a161587b992125
Author: dentiny <de...@gmail.com>
AuthorDate: Thu Jul 30 00:16:17 2026 -0700

    feat(binding/cpp): expose positional read API (#7986)
---
 bindings/cpp/include/opendal.hpp  |  3 +++
 bindings/cpp/src/lib.rs           | 10 ++++----
 bindings/cpp/src/reader.cpp       |  5 ++++
 bindings/cpp/src/reader.rs        | 54 +++++++++++++++++++++++++++++++--------
 bindings/cpp/tests/basic_test.cpp |  8 ++++++
 5 files changed, 64 insertions(+), 16 deletions(-)

diff --git a/bindings/cpp/include/opendal.hpp b/bindings/cpp/include/opendal.hpp
index 64447973f..7685956ba 100644
--- a/bindings/cpp/include/opendal.hpp
+++ b/bindings/cpp/include/opendal.hpp
@@ -19,6 +19,7 @@
 
 #pragma once
 
+#include <cstdint>
 #include <cstring>
 #include <iostream>
 #include <memory>
@@ -196,6 +197,8 @@ class Reader {
 
   std::streamsize Read(void *s, std::streamsize n);
 
+  std::streamsize ReadAt(void *s, std::streamsize n, uint64_t offset);
+
   std::streampos Seek(std::streamoff off, std::ios_base::seekdir way);
 
  private:
diff --git a/bindings/cpp/src/lib.rs b/bindings/cpp/src/lib.rs
index 737276ce7..6a8d219c3 100644
--- a/bindings/cpp/src/lib.rs
+++ b/bindings/cpp/src/lib.rs
@@ -176,7 +176,8 @@ mod ffi {
 
         unsafe fn delete_reader(reader: *mut Reader);
         fn read(self: &mut Reader, buf: &mut [u8]) -> Result<usize>;
-        fn seek(self: &mut Reader, offset: u64, dir: SeekFrom) -> Result<u64>;
+        fn read_at(self: &Reader, buf: &mut [u8], offset: u64) -> Result<usize>;
+        fn seek(self: &mut Reader, offset: i64, dir: SeekFrom) -> Result<u64>;
 
         unsafe fn delete_writer(writer: *mut Writer);
         fn write(self: &mut Writer, bs: Vec<u8>) -> Result<()>;
@@ -317,10 +318,9 @@ impl Operator {
 
     fn reader(&self, path: &str) -> Result<*mut Reader> {
         let meta = self.0.stat(path)?;
-        let reader = Box::into_raw(Box::new(Reader(
-            self.0
-                .reader(path)?
-                .into_std_read(0..meta.content_length())?,
+        let reader = Box::into_raw(Box::new(Reader::new(
+            self.0.reader(path)?,
+            meta.content_length(),
         )));
         Ok(reader)
     }
diff --git a/bindings/cpp/src/reader.cpp b/bindings/cpp/src/reader.cpp
index fa980c902..9ddfca1f1 100644
--- a/bindings/cpp/src/reader.cpp
+++ b/bindings/cpp/src/reader.cpp
@@ -41,6 +41,11 @@ std::streamsize Reader::Read(void *s, std::streamsize n) {
   return reader_->read(rust::Slice<uint8_t>(static_cast<uint8_t *>(s), n));
 }
 
+std::streamsize Reader::ReadAt(void *s, std::streamsize n, uint64_t offset) {
+  return reader_->read_at(rust::Slice<uint8_t>(static_cast<uint8_t *>(s), n),
+                          offset);
+}
+
 ffi::SeekDir rust_seek_dir(std::ios_base::seekdir dir) {
   switch (dir) {
     case std::ios_base::beg:
diff --git a/bindings/cpp/src/reader.rs b/bindings/cpp/src/reader.rs
index ed7d48fff..e042a864f 100644
--- a/bindings/cpp/src/reader.rs
+++ b/bindings/cpp/src/reader.rs
@@ -15,30 +15,62 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::io::Read;
-use std::io::Seek;
-
 use anyhow::Result;
 use opendal as od;
 
 use super::ffi;
 
-pub struct Reader(pub od::blocking::StdReader);
+pub struct Reader {
+    reader: od::blocking::Reader,
+    position: u64,
+    content_length: u64,
+}
 
 impl Reader {
+    pub fn new(reader: od::blocking::Reader, content_length: u64) -> Self {
+        Self {
+            reader,
+            position: 0,
+            content_length,
+        }
+    }
+
     pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
-        let n = self.0.read(buf)?;
+        if self.position >= self.content_length || buf.is_empty() {
+            return Ok(0);
+        }
+
+        let remaining = self.content_length - self.position;
+        let len = u64::try_from(buf.len())?.min(remaining);
+        let n = self.read_at(&mut buf[..usize::try_from(len)?], self.position)?;
+        self.position = self
+            .position
+            .checked_add(u64::try_from(n)?)
+            .ok_or_else(|| anyhow::anyhow!("reader position overflow"))?;
         Ok(n)
     }
 
-    pub fn seek(&mut self, offset: u64, dir: ffi::SeekFrom) -> Result<u64> {
-        let pos = match dir {
-            ffi::SeekFrom::Start => std::io::SeekFrom::Start(offset),
-            ffi::SeekFrom::Current => std::io::SeekFrom::Current(offset as i64),
-            ffi::SeekFrom::End => std::io::SeekFrom::End(offset as i64),
+    pub fn read_at(&self, mut buf: &mut [u8], offset: u64) -> Result<usize> {
+        let len = u64::try_from(buf.len())?;
+        let end = offset
+            .checked_add(len)
+            .ok_or_else(|| anyhow::anyhow!("read range end overflow"))?;
+        Ok(self.reader.read_into(&mut buf, offset..end)?)
+    }
+
+    pub fn seek(&mut self, offset: i64, dir: ffi::SeekFrom) -> Result<u64> {
+        let base = match dir {
+            ffi::SeekFrom::Start => 0,
+            ffi::SeekFrom::Current => i128::from(self.position),
+            ffi::SeekFrom::End => i128::from(self.content_length),
             _ => return Err(anyhow::anyhow!("invalid seek dir")),
         };
+        let pos = base + i128::from(offset);
+        if pos < 0 {
+            return Err(anyhow::anyhow!("invalid seek to negative position"));
+        }
 
-        Ok(self.0.seek(pos)?)
+        self.position = u64::try_from(pos)?;
+        Ok(self.position)
     }
 }
diff --git a/bindings/cpp/tests/basic_test.cpp b/bindings/cpp/tests/basic_test.cpp
index 84c9bcd37..e316d85ed 100644
--- a/bindings/cpp/tests/basic_test.cpp
+++ b/bindings/cpp/tests/basic_test.cpp
@@ -114,6 +114,14 @@ OPENDAL_TEST_F(OpenDALTest, ReaderTest) {
   for (int i = 0; i < 100; ++i) {
     EXPECT_EQ(part_data[i], data[200 + i]);
   }
+
+  std::string positional_data(100, 0);
+  auto positional_read_size = reader.ReadAt(positional_data.data(), /*size=*/ 100, /*offset=*/500);
+  EXPECT_EQ(positional_read_size, 100);
+  EXPECT_EQ(reader.Seek(0, std::ios::cur), 300);
+  for (int i = 0; i < 100; ++i) {
+    EXPECT_EQ(positional_data[i], data[500 + i]);
+  }
   reader.Seek(0, std::ios::beg);
 
   // reader stream