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

tustvold pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/master by this push:
     new 2e806b0c6 fix(ffi): handle null data buffers from empty arrays (#3276)
2e806b0c6 is described below

commit 2e806b0c634e7c5487811c28329b8ef4c5f1ba77
Author: Will Jones <[email protected]>
AuthorDate: Thu Dec 8 02:07:01 2022 -0800

    fix(ffi): handle null data buffers from empty arrays (#3276)
    
    * test(python): validate roundtripping of empty arrays
    
    * test: check more types
    
    * test: parameterize rust side
    
    * fix: also check for zero length buffers
    
    * fix: handle null data buffers
    
    * fix: only allow zero-length buffers to be null
---
 arrow-pyarrow-integration-testing/src/lib.rs       |  9 ++++++
 .../tests/test_sql.py                              | 32 ++++++++++++++++++++++
 arrow/src/ffi.rs                                   | 25 +++++++++++------
 3 files changed, 57 insertions(+), 9 deletions(-)

diff --git a/arrow-pyarrow-integration-testing/src/lib.rs 
b/arrow-pyarrow-integration-testing/src/lib.rs
index 2e74f0cf6..cf94b0dd4 100644
--- a/arrow-pyarrow-integration-testing/src/lib.rs
+++ b/arrow-pyarrow-integration-testing/src/lib.rs
@@ -20,6 +20,7 @@
 
 use std::sync::Arc;
 
+use arrow::array::new_empty_array;
 use pyo3::prelude::*;
 use pyo3::wrap_pyfunction;
 
@@ -70,6 +71,13 @@ fn double_py(lambda: &PyAny, py: Python) -> PyResult<bool> {
     Ok(array == expected)
 }
 
+#[pyfunction]
+fn make_empty_array(datatype: PyArrowType<DataType>, py: Python) -> 
PyResult<PyObject> {
+    let array = new_empty_array(&datatype.0);
+
+    array.data().to_pyarrow(py)
+}
+
 /// Returns the substring
 #[pyfunction]
 fn substring(
@@ -134,6 +142,7 @@ fn round_trip_record_batch_reader(
 fn arrow_pyarrow_integration_testing(_py: Python, m: &PyModule) -> 
PyResult<()> {
     m.add_wrapped(wrap_pyfunction!(double))?;
     m.add_wrapped(wrap_pyfunction!(double_py))?;
+    m.add_wrapped(wrap_pyfunction!(make_empty_array))?;
     m.add_wrapped(wrap_pyfunction!(substring))?;
     m.add_wrapped(wrap_pyfunction!(concatenate))?;
     m.add_wrapped(wrap_pyfunction!(round_trip_type))?;
diff --git a/arrow-pyarrow-integration-testing/tests/test_sql.py 
b/arrow-pyarrow-integration-testing/tests/test_sql.py
index a19edf0cc..5a8bec792 100644
--- a/arrow-pyarrow-integration-testing/tests/test_sql.py
+++ b/arrow-pyarrow-integration-testing/tests/test_sql.py
@@ -193,6 +193,38 @@ def test_time32_python():
     del b
     del expected
 
+
[email protected]("datatype", _supported_pyarrow_types, ids=str)
+def test_empty_array_python(datatype):
+    """
+    Python -> Rust -> Python
+    """
+    if datatype == pa.float16():
+        pytest.skip("Float 16 is not implemented in Rust")
+
+    a = pa.array([], datatype)
+    b = rust.round_trip_array(a)
+    b.validate(full=True)
+    assert a.to_pylist() == b.to_pylist()
+    assert a.type == b.type
+    del a
+    del b
+
+
[email protected]("datatype", _supported_pyarrow_types, ids=str)
+def test_empty_array_rust(datatype):
+    """
+    Rust -> Python
+    """
+    a = pa.array([], type=datatype)
+    b = rust.make_empty_array(datatype)
+    b.validate(full=True)
+    assert a.to_pylist() == b.to_pylist()
+    assert a.type == b.type
+    del a
+    del b
+
+
 def test_binary_array():
     """
     Python -> Rust -> Python
diff --git a/arrow/src/ffi.rs b/arrow/src/ffi.rs
index fc8dc654a..0c1c1fa54 100644
--- a/arrow/src/ffi.rs
+++ b/arrow/src/ffi.rs
@@ -123,7 +123,7 @@ use std::{
 use bitflags::bitflags;
 
 use crate::array::{layout, ArrayData};
-use crate::buffer::Buffer;
+use crate::buffer::{Buffer, MutableBuffer};
 use crate::datatypes::DataType;
 use crate::error::{ArrowError, Result};
 use crate::util::bit_util;
@@ -578,7 +578,7 @@ unsafe fn create_buffer(
     index: usize,
     len: usize,
 ) -> Option<Buffer> {
-    if array.buffers.is_null() {
+    if array.buffers.is_null() || array.n_buffers == 0 {
         return None;
     }
     let buffers = array.buffers as *mut *const u8;
@@ -657,13 +657,20 @@ pub trait ArrowArrayRef {
 
                 let len = self.buffer_len(index)?;
 
-                unsafe { create_buffer(self.owner().clone(), self.array(), 
index, len) }
-                    .ok_or_else(|| {
-                        ArrowError::CDataInterface(format!(
-                            "The external buffer at position {} is null.",
-                            index - 1
-                        ))
-                    })
+                match unsafe {
+                    create_buffer(self.owner().clone(), self.array(), index, 
len)
+                } {
+                    Some(buf) => Ok(buf),
+                    None if len == 0 => {
+                        // Null data buffer, which Rust doesn't allow. So 
create
+                        // an empty buffer.
+                        Ok(MutableBuffer::new(0).into())
+                    }
+                    None => Err(ArrowError::CDataInterface(format!(
+                        "The external buffer at position {} is null.",
+                        index - 1
+                    ))),
+                }
             })
             .collect()
     }

Reply via email to