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

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/main/pr-11915-69be95c1d925bd876c71c11ec6cb2a646d54a1ad
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git

commit 4218aa177e0c668dfb08c94b8dccd95eecedd0cc
Author: NingLin Liu <[email protected]>
AuthorDate: Mon Oct 26 15:21:04 2026 +0000

    fix(arrow-avro): keep nulls when reading strings as Utf8View (#11915)
    
    # Which issue does this PR close?
    
    - Closes #11735
    
    # Rationale for this change
    
    With `ReaderBuilder::with_utf8_view(true)`, the `StringView` arm of
    `Decoder::flush` built a `StringArray` with the correct null buffer,
    then copied it into a `Vec<&str>` that replaced every NULL with `""`
    before building the `StringViewArray`. The resulting array had no null
    buffer, so every NULL of a nullable string field came back as a non-null
    empty string (`null_count == 0`), while the same file read with
    `with_utf8_view(false)` was correct.
    
    # What changes are included in this PR?
    
    - Build the `StringViewArray` with
    `StringViewArray::from(&StringArray)`, which keeps the null buffer and
    creates views into the existing values buffer instead of copying each
    string through a `Vec<&str>`.
    
    (#11737 proposed the same approach but was closed by its author without
    merging; this picks the fix back up.)
    
    # Are these changes tested?
    
    Yes. Added `test_utf8_view_keeps_nulls_of_nullable_strings`, which
    writes `["a", null, "", null, "b"]` with `AvroWriter` and reads it back
    with `with_utf8_view(true)`. It fails on main (`null_count` 0 instead of
    2) and passes with the fix. `cargo test -p arrow-avro` and `cargo clippy
    -p arrow-avro --all-targets --all-features -- -D warnings` pass.
    
    # Are there any user-facing changes?
    
    Yes, a bug fix: with `with_utf8_view(true)`, NULL strings are now read
    as nulls instead of empty strings. No API changes.
    
    This PR was prepared with AI assistance and reviewed/tested by the
    author.
---
 arrow-avro/src/reader/mod.rs    | 22 ++++++++++++++++++++++
 arrow-avro/src/reader/record.rs | 13 ++-----------
 2 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/arrow-avro/src/reader/mod.rs b/arrow-avro/src/reader/mod.rs
index 2faa90c83e..51a160d9a1 100644
--- a/arrow-avro/src/reader/mod.rs
+++ b/arrow-avro/src/reader/mod.rs
@@ -1550,6 +1550,28 @@ mod test {
         arrow::compute::concat_batches(&schema, &batches).unwrap()
     }
 
+    #[test]
+    fn test_utf8_view_keeps_nulls_of_nullable_strings() {
+        let schema = Schema::new(vec![Field::new("s", DataType::Utf8, true)]);
+        let values = vec![Some("a"), None, Some(""), None, Some("b")];
+        let col: ArrayRef = Arc::new(StringArray::from(values.clone()));
+        let batch = RecordBatch::try_new(Arc::new(schema.clone()), 
vec![col]).unwrap();
+        let mut writer = AvroWriter::new(Vec::<u8>::new(), schema).unwrap();
+        writer.write(&batch).unwrap();
+        writer.finish().unwrap();
+        let bytes = writer.into_inner();
+
+        let reader = ReaderBuilder::new()
+            .with_utf8_view(true)
+            .build(Cursor::new(bytes))
+            .unwrap();
+        let batches = reader.collect::<Result<Vec<_>, _>>().unwrap();
+        assert_eq!(batches.len(), 1);
+        let col = batches[0].column(0).as_string_view();
+        assert_eq!(col.null_count(), 2);
+        assert_eq!(col, &StringViewArray::from(values));
+    }
+
     #[test]
     fn test_block_sync_marker_mismatch_errors() {
         let path = arrow_test_data("avro/alltypes_plain.avro");
diff --git a/arrow-avro/src/reader/record.rs b/arrow-avro/src/reader/record.rs
index 5b8f4e607f..815c445742 100644
--- a/arrow-avro/src/reader/record.rs
+++ b/arrow-avro/src/reader/record.rs
@@ -1687,17 +1687,8 @@ impl Decoder {
             Self::StringView(offsets, values) => {
                 let offsets = flush_offsets(offsets);
                 let values = flush_values(values);
-                let array = StringArray::try_new(offsets, values.into(), 
nulls.clone())?;
-                let values: Vec<&str> = (0..array.len())
-                    .map(|i| {
-                        if array.is_valid(i) {
-                            array.value(i)
-                        } else {
-                            ""
-                        }
-                    })
-                    .collect();
-                Arc::new(StringViewArray::from(values))
+                let array = StringArray::try_new(offsets, values.into(), 
nulls)?;
+                Arc::new(StringViewArray::from(&array))
             }
             Self::Array(field, offsets, values) => {
                 let values = values.flush(None)?;

Reply via email to