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 2023/06/21 10:58:13 UTC

[incubator-opendal] 04/05: Implement sink

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

xuanwo pushed a commit to branch implement-sink-for-s3
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git

commit 13e85c9c00a5c35be93397c252fa4c93b6cffc5d
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Wed Jun 21 18:46:04 2023 +0800

    Implement sink
    
    Signed-off-by: Xuanwo <gi...@xuanwo.io>
---
 core/src/types/writer.rs     | 23 +++++++++++++++++++++++
 core/tests/behavior/write.rs | 37 +++++++++++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+)

diff --git a/core/src/types/writer.rs b/core/src/types/writer.rs
index 049116566..98eebdac0 100644
--- a/core/src/types/writer.rs
+++ b/core/src/types/writer.rs
@@ -26,6 +26,7 @@ use bytes::Bytes;
 use futures::future::BoxFuture;
 use futures::AsyncWrite;
 use futures::FutureExt;
+use futures::TryStreamExt;
 
 use crate::raw::oio::Write;
 use crate::raw::*;
@@ -90,6 +91,28 @@ impl Writer {
         }
     }
 
+    /// Sink into writer.
+    ///
+    /// sink will read data from given streamer and write them into writer
+    /// directly without extra in-memory buffer.
+    pub async fn sink<S, T>(&mut self, size: u64, s: S) -> Result<()>
+    where
+        S: futures::Stream<Item = Result<T>> + Send + Sync + Unpin + 'static,
+        T: Into<Bytes>,
+    {
+        if let State::Idle(Some(w)) = &mut self.state {
+            let s = Box::new(oio::into_stream::from_futures_stream(
+                s.map_ok(|v| v.into()),
+            ));
+            w.sink(size, s).await
+        } else {
+            unreachable!(
+                "writer state invalid while pipe_form, expect Idle, actual {}",
+                self.state
+            );
+        }
+    }
+
     /// Abort the writer and clean up all written data.
     ///
     /// ## Notes
diff --git a/core/tests/behavior/write.rs b/core/tests/behavior/write.rs
index cff78532b..f3b935a25 100644
--- a/core/tests/behavior/write.rs
+++ b/core/tests/behavior/write.rs
@@ -21,6 +21,7 @@ use std::time::Duration;
 use anyhow::Result;
 use futures::io::BufReader;
 use futures::io::Cursor;
+use futures::stream;
 use futures::AsyncReadExt;
 use futures::AsyncSeekExt;
 use futures::StreamExt;
@@ -111,6 +112,7 @@ macro_rules! behavior_write_tests {
                 test_delete_stream,
                 test_remove_one_file,
                 test_writer_write,
+                test_writer_sink,
                 test_writer_abort,
                 test_writer_futures_copy,
                 test_fuzz_unsized_writer,
@@ -1121,6 +1123,41 @@ pub async fn test_writer_write(op: Operator) -> Result<()> {
     Ok(())
 }
 
+/// Streaming data into writer
+pub async fn test_writer_sink(op: Operator) -> Result<()> {
+    let path = uuid::Uuid::new_v4().to_string();
+    let size = 5 * 1024 * 1024; // write file with 5 MiB
+    let content_a = gen_fixed_bytes(size);
+    let content_b = gen_fixed_bytes(size);
+    let stream = stream::iter(vec![content_a.clone(), content_b.clone()]).map(Ok);
+
+    let mut w = op
+        .writer_with(&path)
+        .content_length(2 * size as u64)
+        .await?;
+    w.sink(2 * size as u64, stream).await?;
+    w.close().await?;
+
+    let meta = op.stat(&path).await.expect("stat must succeed");
+    assert_eq!(meta.content_length(), (size * 2) as u64);
+
+    let bs = op.read(&path).await?;
+    assert_eq!(bs.len(), size * 2, "read size");
+    assert_eq!(
+        format!("{:x}", Sha256::digest(&bs[..size])),
+        format!("{:x}", Sha256::digest(content_a)),
+        "read content a"
+    );
+    assert_eq!(
+        format!("{:x}", Sha256::digest(&bs[size..])),
+        format!("{:x}", Sha256::digest(content_b)),
+        "read content b"
+    );
+
+    op.delete(&path).await.expect("delete must succeed");
+    Ok(())
+}
+
 /// Copy data from reader to writer
 pub async fn test_writer_futures_copy(op: Operator) -> Result<()> {
     let path = uuid::Uuid::new_v4().to_string();