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

JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new 1cd4527f fix(arrow): map BLOB to LargeBinary (#874)
1cd4527f is described below

commit 1cd4527ffd2b11ec7628176919d4acec86454310
Author: Jingsong Lee <[email protected]>
AuthorDate: Fri Sep 18 20:20:22 2026 +0800

    fix(arrow): map BLOB to LargeBinary (#874)
---
 bindings/go/blob_reader.go                         |   4 +-
 .../datafusion/src/blob_descriptor_functions.rs    |   8 +-
 crates/integrations/datafusion/src/blob_view.rs    |   8 +-
 .../datafusion/tests/blob_descriptor_functions.rs  |   6 +-
 crates/integrations/datafusion/tests/blob_tests.rs |  14 +--
 crates/paimon/src/arrow/format/avro.rs             |  17 +++-
 crates/paimon/src/arrow/format/blob.rs             |  85 +++++++----------
 crates/paimon/src/arrow/format/row.rs              | 101 +++++++++++++++++----
 crates/paimon/src/arrow/mod.rs                     |  34 ++++++-
 crates/paimon/src/arrow/residual.rs                |  28 +++++-
 crates/paimon/src/arrow/shredding/variant.rs       |  51 ++++++++---
 crates/paimon/src/table/aggregator/numeric.rs      |   4 +-
 crates/paimon/src/table/blob_resolver.rs           |  12 +--
 crates/paimon/src/table/data_evolution_reader.rs   |  63 ++++++-------
 .../table/data_evolution_reader/blob_fallback.rs   |   4 +-
 .../src/table/dedicated_format_file_writer.rs      |   4 +-
 crates/paimon/src/table/table_write.rs             |   8 +-
 crates/paimon/tests/rest_catalog_test.rs           |   8 +-
 18 files changed, 292 insertions(+), 167 deletions(-)

diff --git a/bindings/go/blob_reader.go b/bindings/go/blob_reader.go
index 529ae9b0..5e2e7ee5 100644
--- a/bindings/go/blob_reader.go
+++ b/bindings/go/blob_reader.go
@@ -102,9 +102,9 @@ func StringBlobMapDescriptors(column arrow.Array, row int) 
(map[string][]byte, e
        if !ok {
                return nil, fmt.Errorf("paimon: BLOB map keys are %T, want 
*array.String", m.Keys())
        }
-       descriptors, ok := m.Items().(*array.Binary)
+       descriptors, ok := m.Items().(*array.LargeBinary)
        if !ok {
-               return nil, fmt.Errorf("paimon: BLOB map values are %T, want 
*array.Binary", m.Items())
+               return nil, fmt.Errorf("paimon: BLOB map values are %T, want 
*array.LargeBinary", m.Items())
        }
        start, end := m.ValueOffsets(row)
        result := make(map[string][]byte, end-start)
diff --git a/crates/integrations/datafusion/src/blob_descriptor_functions.rs 
b/crates/integrations/datafusion/src/blob_descriptor_functions.rs
index de000412..a49fffb2 100644
--- a/crates/integrations/datafusion/src/blob_descriptor_functions.rs
+++ b/crates/integrations/datafusion/src/blob_descriptor_functions.rs
@@ -18,7 +18,7 @@
 use std::sync::Arc;
 
 use datafusion::arrow::array::{
-    Array, BinaryArray, BinaryBuilder, BinaryViewArray, LargeBinaryArray, 
LargeStringArray,
+    Array, BinaryArray, BinaryViewArray, LargeBinaryArray, LargeBinaryBuilder, 
LargeStringArray,
     StringArray, StringBuilder, StringViewArray,
 };
 use datafusion::arrow::datatypes::DataType as ArrowDataType;
@@ -71,7 +71,7 @@ impl ScalarUDFImpl for PathToDescriptorFunc {
     }
 
     fn return_type(&self, _arg_types: &[ArrowDataType]) -> 
DFResult<ArrowDataType> {
-        Ok(ArrowDataType::Binary)
+        Ok(ArrowDataType::LargeBinary)
     }
 
     fn invoke_with_args(&self, args: ScalarFunctionArgs) -> 
DFResult<ColumnarValue> {
@@ -147,7 +147,7 @@ fn path_scalar(value: ScalarValue) -> 
DFResult<ColumnarValue> {
         ScalarValue::Null => None,
         other => return unexpected_type(PATH_TO_DESCRIPTOR, 
&other.data_type()),
     };
-    Ok(ColumnarValue::Scalar(ScalarValue::Binary(
+    Ok(ColumnarValue::Scalar(ScalarValue::LargeBinary(
         path.as_deref().map(serialize_path),
     )))
 }
@@ -166,7 +166,7 @@ fn path_array(input: &dyn Array) -> DFResult<ColumnarValue> 
{
 }
 
 fn descriptor_array_from_paths<'a>(paths: impl Iterator<Item = Option<&'a 
str>>) -> ColumnarValue {
-    let mut builder = BinaryBuilder::new();
+    let mut builder = LargeBinaryBuilder::new();
     for path in paths {
         match path {
             Some(path) => builder.append_value(serialize_path(path)),
diff --git a/crates/integrations/datafusion/src/blob_view.rs 
b/crates/integrations/datafusion/src/blob_view.rs
index a91d4db1..b2fed25f 100644
--- a/crates/integrations/datafusion/src/blob_view.rs
+++ b/crates/integrations/datafusion/src/blob_view.rs
@@ -20,7 +20,7 @@ use std::hash::{Hash, Hasher};
 use std::sync::Arc;
 
 use datafusion::arrow::array::{
-    Array, BinaryBuilder, Int16Array, Int32Array, Int64Array, Int8Array, 
LargeStringArray,
+    Array, Int16Array, Int32Array, Int64Array, Int8Array, LargeBinaryBuilder, 
LargeStringArray,
     StringArray, StringViewArray, UInt16Array, UInt32Array, UInt64Array, 
UInt8Array,
 };
 use datafusion::arrow::datatypes::{DataType as ArrowDataType, Field, FieldRef};
@@ -133,13 +133,13 @@ impl ScalarUDFImpl for BlobViewFunc {
     }
 
     fn return_type(&self, _arg_types: &[ArrowDataType]) -> 
DFResult<ArrowDataType> {
-        Ok(ArrowDataType::Binary)
+        Ok(ArrowDataType::LargeBinary)
     }
 
     fn return_field_from_args(&self, _args: ReturnFieldArgs) -> 
DFResult<FieldRef> {
         Ok(Arc::new(Field::new(
             FUNCTION_NAME,
-            ArrowDataType::Binary,
+            ArrowDataType::LargeBinary,
             true,
         )))
     }
@@ -156,7 +156,7 @@ impl ScalarUDFImpl for BlobViewFunc {
         let fields = arrays[1].as_ref();
         let row_ids = arrays[2].as_ref();
         let mut field_id_cache = HashMap::new();
-        let mut builder = BinaryBuilder::new();
+        let mut builder = LargeBinaryBuilder::new();
 
         for row in 0..table_names.len() {
             let Some(table_name) = string_at(table_names, row)? else {
diff --git a/crates/integrations/datafusion/tests/blob_descriptor_functions.rs 
b/crates/integrations/datafusion/tests/blob_descriptor_functions.rs
index 029dcc4b..33f24db0 100644
--- a/crates/integrations/datafusion/tests/blob_descriptor_functions.rs
+++ b/crates/integrations/datafusion/tests/blob_descriptor_functions.rs
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use datafusion::arrow::array::{Array, BinaryArray, StringArray};
+use datafusion::arrow::array::{Array, LargeBinaryArray, StringArray};
 use paimon_datafusion::SQLContext;
 
 const JAVA_V2_HEX: &str =
@@ -61,7 +61,7 @@ async fn 
test_blob_descriptor_functions_are_registered_with_aliases_and_java_for
         let descriptors = batch
             .column(column)
             .as_any()
-            .downcast_ref::<BinaryArray>()
+            .downcast_ref::<LargeBinaryArray>()
             .unwrap();
         assert_eq!(to_hex(descriptors.value(0)), JAVA_V2_HEX);
     }
@@ -100,7 +100,7 @@ async fn test_blob_descriptor_functions_propagate_nulls() {
     let descriptors = batch
         .column(1)
         .as_any()
-        .downcast_ref::<BinaryArray>()
+        .downcast_ref::<LargeBinaryArray>()
         .unwrap();
     let strings = batch
         .column(2)
diff --git a/crates/integrations/datafusion/tests/blob_tests.rs 
b/crates/integrations/datafusion/tests/blob_tests.rs
index 361e8ced..47364ead 100644
--- a/crates/integrations/datafusion/tests/blob_tests.rs
+++ b/crates/integrations/datafusion/tests/blob_tests.rs
@@ -21,7 +21,7 @@
 
 mod common;
 
-use arrow_array::{Array, BinaryArray, Int32Array, RecordBatch};
+use arrow_array::{Array, Int32Array, LargeBinaryArray, RecordBatch};
 use common::{assert_sql_error, create_sql_context, create_test_env, exec, 
string_value};
 use paimon::catalog::Identifier;
 use paimon::spec::{BlobDescriptor, BlobViewStruct};
@@ -58,7 +58,7 @@ fn collect_id_name_picture(batches: &[RecordBatch]) -> 
Vec<(i32, String, Option<
         let pics = batch
             .column(2)
             .as_any()
-            .downcast_ref::<BinaryArray>()
+            .downcast_ref::<LargeBinaryArray>()
             .unwrap();
         for i in 0..batch.num_rows() {
             let pic = if pics.is_null(i) {
@@ -175,12 +175,12 @@ async fn test_blob_multiple_columns() {
     let p1 = batch
         .column(1)
         .as_any()
-        .downcast_ref::<BinaryArray>()
+        .downcast_ref::<LargeBinaryArray>()
         .unwrap();
     let p2 = batch
         .column(2)
         .as_any()
-        .downcast_ref::<BinaryArray>()
+        .downcast_ref::<LargeBinaryArray>()
         .unwrap();
     assert_eq!(ids.value(0), 1);
     assert_eq!(p1.value(0), b"AAA");
@@ -496,7 +496,7 @@ async fn test_blob_with_partition() {
         let pics = batch
             .column(1)
             .as_any()
-            .downcast_ref::<BinaryArray>()
+            .downcast_ref::<LargeBinaryArray>()
             .unwrap();
         for i in 0..batch.num_rows() {
             rows.push((ids.value(i), pics.value(i).to_vec()));
@@ -1106,8 +1106,8 @@ async fn test_blob_view_preserves_branch_reference() {
     let value = batches[0]
         .column(0)
         .as_any()
-        .downcast_ref::<BinaryArray>()
-        .expect("binary blob view")
+        .downcast_ref::<LargeBinaryArray>()
+        .expect("large binary blob view")
         .value(0);
     let view = BlobViewStruct::deserialize(value).expect("deserialize blob 
view");
     assert_eq!(view.identifier().full_name(), "test_db.src$branch_b1");
diff --git a/crates/paimon/src/arrow/format/avro.rs 
b/crates/paimon/src/arrow/format/avro.rs
index 546d963e..4757f0a0 100644
--- a/crates/paimon/src/arrow/format/avro.rs
+++ b/crates/paimon/src/arrow/format/avro.rs
@@ -25,9 +25,9 @@ use apache_avro::types::Value;
 use apache_avro::Reader;
 use arrow_array::{
     BinaryArray, BooleanArray, Date32Array, Decimal128Array, Float32Array, 
Float64Array,
-    Int16Array, Int32Array, Int64Array, Int8Array, ListArray, MapArray, 
RecordBatch, StringArray,
-    StructArray, Time32MillisecondArray, TimestampMicrosecondArray, 
TimestampMillisecondArray,
-    TimestampNanosecondArray,
+    Int16Array, Int32Array, Int64Array, Int8Array, LargeBinaryArray, 
ListArray, MapArray,
+    RecordBatch, StringArray, StructArray, Time32MillisecondArray, 
TimestampMicrosecondArray,
+    TimestampMillisecondArray, TimestampNanosecondArray,
 };
 use arrow_buffer::{BooleanBuffer, NullBuffer, OffsetBuffer, ScalarBuffer};
 use arrow_schema::SchemaRef;
@@ -322,13 +322,20 @@ fn build_column(
                 .collect();
             Arc::new(arr)
         }
-        DataType::Binary(_) | DataType::VarBinary(_) | DataType::Blob(_) => {
+        DataType::Binary(_) | DataType::VarBinary(_) => {
             let values: Vec<Option<&[u8]>> = (0..num_rows)
                 .map(|i| get_field_at(&records[i], 
idx).and_then(value_as_bytes))
                 .collect();
             let arr: BinaryArray = values.into_iter().collect();
             Arc::new(arr)
         }
+        DataType::Blob(_) => {
+            let values: Vec<Option<&[u8]>> = (0..num_rows)
+                .map(|i| get_field_at(&records[i], 
idx).and_then(value_as_bytes))
+                .collect();
+            let arr: LargeBinaryArray = values.into_iter().collect();
+            Arc::new(arr)
+        }
         DataType::Date(_) => {
             let arr: Date32Array = (0..num_rows)
                 .map(|i| {
@@ -1110,7 +1117,7 @@ mod tests {
             vec![("b", av_null())],
         ]);
         let col = build_column(&records, "b", 
&DataType::Blob(BlobType::new()), 2).unwrap();
-        let arr = col.as_any().downcast_ref::<BinaryArray>().unwrap();
+        let arr = col.as_any().downcast_ref::<LargeBinaryArray>().unwrap();
         assert_eq!(arr.value(0), &[0xDE, 0xAD]);
         assert!(arr.is_null(1));
     }
diff --git a/crates/paimon/src/arrow/format/blob.rs 
b/crates/paimon/src/arrow/format/blob.rs
index f03db782..79f2a0ca 100644
--- a/crates/paimon/src/arrow/format/blob.rs
+++ b/crates/paimon/src/arrow/format/blob.rs
@@ -21,11 +21,11 @@ use crate::io::{FileRead, FileWrite};
 use crate::spec::{BlobDescriptor, DataField, DataType};
 use crate::table::{ArrowRecordBatchStream, RowRange};
 use crate::Error;
-use arrow_array::builder::{BinaryBuilder, ListBuilder};
+use arrow_array::builder::{LargeBinaryBuilder, ListBuilder};
 use arrow_array::{
     Array, ArrayRef, BinaryArray, BooleanArray, Date32Array, Decimal128Array, 
Int16Array,
-    Int32Array, Int64Array, Int8Array, MapArray, RecordBatch, 
RecordBatchOptions, StringArray,
-    StructArray, Time32MillisecondArray,
+    Int32Array, Int64Array, Int8Array, LargeBinaryArray, MapArray, 
RecordBatch, RecordBatchOptions,
+    StringArray, StructArray, Time32MillisecondArray,
 };
 use arrow_buffer::{BooleanBuffer, NullBuffer, OffsetBuffer, ScalarBuffer};
 use arrow_schema::DataType as ArrowDataType;
@@ -322,7 +322,7 @@ pub(crate) fn build_blob_batch(
     target_schema: &Arc<arrow_schema::Schema>,
     values: Vec<BlobReadValue>,
 ) -> crate::Result<RecordBatch> {
-    let mut builder = BinaryBuilder::new();
+    let mut builder = LargeBinaryBuilder::new();
     for value in values {
         match value {
             BlobReadValue::Value(bytes) => 
builder.append_value(bytes.as_ref()),
@@ -352,13 +352,13 @@ pub(crate) fn build_blob_array_batch(
         other => {
             return Err(Error::UnexpectedError {
                 message: format!(
-                    "Expected Array<Blob> to map to Arrow List<Binary>, got 
{other:?}"
+                    "Expected Array<Blob> to map to Arrow List<LargeBinary>, 
got {other:?}"
                 ),
                 source: None,
             });
         }
     };
-    let mut builder = 
ListBuilder::new(BinaryBuilder::new()).with_field(element_field);
+    let mut builder = 
ListBuilder::new(LargeBinaryBuilder::new()).with_field(element_field);
     for value in values {
         match value {
             BlobReadValue::Array(elements) => {
@@ -407,7 +407,6 @@ pub(crate) fn build_blob_map_batch(
 
     let mut keys = Vec::new();
     let mut blobs = Vec::new();
-    let mut blob_data_length = 0u64;
     let mut offsets = vec![0i32];
     let mut validity = Vec::with_capacity(values.len());
     for value in values {
@@ -430,13 +429,6 @@ pub(crate) fn build_blob_map_batch(
                         source: None,
                     })?;
                 for (key, blob) in entries {
-                    if let Some(blob) = &blob {
-                        blob_data_length = checked_arrow_binary_data_length(
-                            blob_data_length,
-                            blob.len() as u64,
-                            "MAP<X, BLOB> batch value data",
-                        )?;
-                    }
                     keys.push(key);
                     blobs.push(blob);
                 }
@@ -456,7 +448,7 @@ pub(crate) fn build_blob_map_batch(
     }
 
     let key_array = decode_blob_map_keys(&keys, key_type)?;
-    let value_array = Arc::new(BinaryArray::from_iter(
+    let value_array = Arc::new(LargeBinaryArray::from_iter(
         blobs.iter().map(|value| value.as_deref()),
     )) as ArrayRef;
     let entries = StructArray::try_new(entry_fields.clone(), vec![key_array, 
value_array], None)
@@ -926,19 +918,6 @@ fn parse_blob_array_layout(
     })
 }
 
-fn validate_inline_blob_array_data_length(layout: &BlobArrayLayout) -> 
crate::Result<()> {
-    let data_length = layout.element_data_range.end - 
layout.element_data_range.start;
-    if data_length > i32::MAX as u64 {
-        return Err(Error::DataInvalid {
-            message: format!(
-                "ARRAY<BLOB> inline element data is too large for Arrow 
Binary: {data_length} bytes"
-            ),
-            source: None,
-        });
-    }
-    Ok(())
-}
-
 fn decode_blob_array_metadata(
     layout: BlobArrayLayout,
     index_bytes: &[u8],
@@ -1020,8 +999,7 @@ async fn read_inline_blob_array_entry(
     reader: &dyn FileRead,
     payload_range: Range<u64>,
 ) -> crate::Result<BlobReadValue> {
-    let preflight_layout = read_blob_array_layout(reader, 
payload_range.clone()).await?;
-    validate_inline_blob_array_data_length(&preflight_layout)?;
+    let _preflight_layout = read_blob_array_layout(reader, 
payload_range.clone()).await?;
 
     let payload = read_blob_entry(reader, 
blob_entry_range(&payload_range)).await?;
     let payload_length = validate_blob_array_payload_range(&payload_range)?;
@@ -1042,7 +1020,6 @@ async fn read_inline_blob_array_entry(
         &payload[..BLOB_ARRAY_HEADER_SIZE as usize],
         &payload[index_length_position..],
     )?;
-    validate_inline_blob_array_data_length(&layout)?;
     let index_start = (layout.element_index_range.start - payload_range.start) 
as usize;
     let index_end = (layout.element_index_range.end - payload_range.start) as 
usize;
     let metadata = decode_blob_array_metadata(layout, 
&payload[index_start..index_end])?;
@@ -1289,9 +1266,6 @@ async fn read_blob_map_entry(
             source: None,
         });
     }
-    if !descriptor_mode {
-        checked_arrow_binary_data_length(0, total_value_length, "MAP<X, BLOB> 
inline value data")?;
-    }
     if blob_map_key_uses_binary_offsets(key_type) {
         checked_arrow_binary_data_length(0, key_data_length, "MAP<X, BLOB> key 
data")?;
     }
@@ -1789,9 +1763,9 @@ impl FormatFileWriter for BlobFormatWriter {
         let col = batch
             .column(0)
             .as_any()
-            .downcast_ref::<arrow_array::BinaryArray>()
+            .downcast_ref::<arrow_array::LargeBinaryArray>()
             .ok_or_else(|| Error::DataInvalid {
-                message: "BlobFormatWriter expects a single Binary 
column".to_string(),
+                message: "BlobFormatWriter expects a single LargeBinary 
column".to_string(),
                 source: None,
             })?;
 
@@ -2243,20 +2217,19 @@ mod tests {
     }
 
     #[tokio::test]
-    async fn 
test_inline_blob_map_reader_rejects_oversized_data_before_entry_read() {
+    async fn test_inline_blob_map_reader_accepts_large_binary_value_data() {
         let value_length = i32::MAX as u64 + 1;
-        let (reader, payload_range) = sparse_blob_map_entry(&[1], 
&[value_length as i64]);
+        let (reader, payload_range) = sparse_blob_map_entry(&[0], 
&[value_length as i64]);
         let key_type = 
DataType::VarChar(VarCharType::new(VarCharType::MAX_LENGTH).unwrap());
 
-        let error = read_blob_map_entry(&reader, payload_range.clone(), "", 
false, &key_type)
+        let _error = read_blob_map_entry(&reader, payload_range.clone(), "", 
false, &key_type)
             .await
             .unwrap_err();
 
         assert!(
-            !reader.ranges().contains(&blob_entry_range(&payload_range)),
-            "oversized inline MAP<X, BLOB> must be rejected before reading the 
complete entry"
+            reader.ranges().contains(&blob_entry_range(&payload_range)),
+            "LargeBinary MAP values must not be rejected by the i32 Arrow 
Binary limit"
         );
-        assert_data_invalid(error, "too large");
     }
 
     #[tokio::test]
@@ -2302,10 +2275,13 @@ mod tests {
     }
 
     #[test]
-    fn test_blob_map_batch_rejects_oversized_binary_data() {
-        let error =
-            checked_arrow_binary_data_length(i32::MAX as u64, 1, "MAP<X, BLOB> 
batch value data")
-                .unwrap_err();
+    fn test_blob_map_batch_rejects_oversized_binary_key_data() {
+        let error = checked_arrow_binary_data_length(
+            i32::MAX as u64,
+            1,
+            "MAP<BINARY, BLOB> batch key data",
+        )
+        .unwrap_err();
 
         assert_data_invalid(error, "too large");
     }
@@ -2334,7 +2310,7 @@ mod tests {
     }
 
     #[tokio::test]
-    async fn 
test_inline_blob_array_reader_rejects_oversized_data_before_entry_read() {
+    async fn test_inline_blob_array_reader_accepts_large_binary_element_data() 
{
         let element_data_length = i32::MAX as u64 + 1;
         let element_index = encode_delta_varints_write(&[element_data_length 
as i64]);
         let payload_length =
@@ -2355,15 +2331,14 @@ mod tests {
             ),
         ]);
 
-        let error = read_inline_blob_array_entry(&reader, 
payload_range.clone())
+        let _error = read_inline_blob_array_entry(&reader, 
payload_range.clone())
             .await
             .unwrap_err();
 
         assert!(
-            !reader.ranges().contains(&blob_entry_range(&payload_range)),
-            "oversized inline ARRAY<BLOB> must be rejected before reading the 
complete entry"
+            reader.ranges().contains(&blob_entry_range(&payload_range)),
+            "LargeBinary ARRAY elements must not be rejected by the i32 Arrow 
Binary limit"
         );
-        assert_data_invalid(error, "too large");
     }
 
     #[tokio::test]
@@ -3126,7 +3101,7 @@ mod tests {
         let array = batch
             .column(0)
             .as_any()
-            .downcast_ref::<arrow_array::BinaryArray>()
+            .downcast_ref::<arrow_array::LargeBinaryArray>()
             .unwrap();
         (0..array.len())
             .map(|idx| (!array.is_null(idx)).then(|| 
array.value(idx).to_vec()))
@@ -3148,7 +3123,7 @@ mod tests {
                 let values = array.value(row_idx);
                 let values = values
                     .as_any()
-                    .downcast_ref::<arrow_array::BinaryArray>()
+                    .downcast_ref::<arrow_array::LargeBinaryArray>()
                     .unwrap();
                 Some(
                     (0..values.len())
@@ -3167,7 +3142,7 @@ mod tests {
         let values = array
             .values()
             .as_any()
-            .downcast_ref::<BinaryArray>()
+            .downcast_ref::<LargeBinaryArray>()
             .unwrap();
         (0..array.len())
             .map(|row| {
@@ -3277,6 +3252,7 @@ mod tests {
         let lengths_start = payload_range.end - BLOB_MAP_INDEX_LENGTHS_SIZE;
         let value_index_start = lengths_start - value_index.len() as u64;
         let key_index_start = value_index_start - key_index.len() as u64;
+        let data_start = payload_range.start + BLOB_MAP_HEADER_SIZE;
         let mut index_lengths = Vec::with_capacity(BLOB_MAP_INDEX_LENGTHS_SIZE 
as usize);
         index_lengths.extend_from_slice(&(key_index.len() as 
i32).to_le_bytes());
         index_lengths.extend_from_slice(&(value_index.len() as 
i32).to_le_bytes());
@@ -3288,6 +3264,7 @@ mod tests {
             (lengths_start..payload_range.end, Bytes::from(index_lengths)),
             (key_index_start..value_index_start, Bytes::from(key_index)),
             (value_index_start..lengths_start, Bytes::from(value_index)),
+            (data_start..data_start, Bytes::new()),
         ]);
         (reader, payload_range)
     }
diff --git a/crates/paimon/src/arrow/format/row.rs 
b/crates/paimon/src/arrow/format/row.rs
index fa00773a..0c65645a 100644
--- a/crates/paimon/src/arrow/format/row.rs
+++ b/crates/paimon/src/arrow/format/row.rs
@@ -31,14 +31,14 @@ use crate::table::{ArrowRecordBatchStream, RowRange};
 use crate::Error;
 use arrow_array::builder::{
     BinaryBuilder, BooleanBuilder, Date32Builder, Decimal128Builder, 
Float32Builder,
-    Float64Builder, Int16Builder, Int32Builder, Int64Builder, Int8Builder, 
StringBuilder,
-    Time32MillisecondBuilder, TimestampMicrosecondBuilder, 
TimestampMillisecondBuilder,
-    TimestampNanosecondBuilder,
+    Float64Builder, Int16Builder, Int32Builder, Int64Builder, Int8Builder, 
LargeBinaryBuilder,
+    StringBuilder, Time32MillisecondBuilder, TimestampMicrosecondBuilder,
+    TimestampMillisecondBuilder, TimestampNanosecondBuilder,
 };
 use arrow_array::{
     Array, ArrayRef, BinaryArray, BooleanArray, Date32Array, Decimal128Array, 
Float32Array,
-    Float64Array, Int16Array, Int32Array, Int64Array, Int8Array, ListArray, 
MapArray, RecordBatch,
-    RecordBatchOptions, StringArray, StructArray, Time32MillisecondArray,
+    Float64Array, Int16Array, Int32Array, Int64Array, Int8Array, 
LargeBinaryArray, ListArray,
+    MapArray, RecordBatch, RecordBatchOptions, StringArray, StructArray, 
Time32MillisecondArray,
     TimestampMicrosecondArray, TimestampMillisecondArray, 
TimestampNanosecondArray,
 };
 use arrow_buffer::{BooleanBuffer, NullBuffer, OffsetBuffer, ScalarBuffer};
@@ -411,10 +411,8 @@ fn validate_arrow_type_for_row_field(
         | (ArrowDataType::Float32, DataType::Float(_))
         | (ArrowDataType::Float64, DataType::Double(_))
         | (ArrowDataType::Utf8, DataType::Char(_) | DataType::VarChar(_))
-        | (
-            ArrowDataType::Binary,
-            DataType::Binary(_) | DataType::VarBinary(_) | DataType::Blob(_),
-        )
+        | (ArrowDataType::Binary, DataType::Binary(_) | DataType::VarBinary(_))
+        | (ArrowDataType::LargeBinary, DataType::Blob(_))
         | (ArrowDataType::Date32, DataType::Date(_))
         | (ArrowDataType::Time32(TimeUnit::Millisecond), DataType::Time(_)) => 
true,
         (ArrowDataType::Struct(fields), DataType::Variant(_)) => 
is_variant_arrow_fields(fields),
@@ -671,10 +669,14 @@ fn write_field_value(
                 .value(row_idx)
                 .as_bytes(),
         ),
-        DataType::Binary(_) | DataType::VarBinary(_) | DataType::Blob(_) => 
write_bytes(
+        DataType::Binary(_) | DataType::VarBinary(_) => write_bytes(
             out,
             downcast::<BinaryArray>(array, data_type)?.value(row_idx),
         ),
+        DataType::Blob(_) => write_bytes(
+            out,
+            downcast::<LargeBinaryArray>(array, data_type)?.value(row_idx),
+        ),
         DataType::Variant(_) => {
             let row = downcast::<StructArray>(array, data_type)?;
             write_variant_struct(out, row, row_idx)?;
@@ -1168,6 +1170,7 @@ enum ColumnBuilder {
     F64(Float64Builder),
     String(StringBuilder),
     Binary(BinaryBuilder),
+    LargeBinary(LargeBinaryBuilder),
     Date(Date32Builder),
     Time(Time32MillisecondBuilder),
     TimestampMs(TimestampMillisecondBuilder),
@@ -1207,9 +1210,8 @@ impl ColumnBuilder {
             DataType::Float(_) => 
Self::F32(Float32Builder::with_capacity(capacity)),
             DataType::Double(_) => 
Self::F64(Float64Builder::with_capacity(capacity)),
             DataType::Char(_) | DataType::VarChar(_) => 
Self::String(StringBuilder::new()),
-            DataType::Binary(_) | DataType::VarBinary(_) | DataType::Blob(_) 
=> {
-                Self::Binary(BinaryBuilder::new())
-            }
+            DataType::Binary(_) | DataType::VarBinary(_) => 
Self::Binary(BinaryBuilder::new()),
+            DataType::Blob(_) => Self::LargeBinary(LargeBinaryBuilder::new()),
             DataType::Variant(_) => Self::Row {
                 fields: variant_arrow_fields(),
                 columns: vec![
@@ -1308,6 +1310,7 @@ impl ColumnBuilder {
             Self::F64(b) => b.append_null(),
             Self::String(b) => b.append_null(),
             Self::Binary(b) => b.append_null(),
+            Self::LargeBinary(b) => b.append_null(),
             Self::Date(b) => b.append_null(),
             Self::Time(b) => b.append_null(),
             Self::TimestampMs(b) => b.append_null(),
@@ -1363,9 +1366,10 @@ impl ColumnBuilder {
                 })?;
                 b.append_value(value);
             }
-            (Self::Binary(b), DataType::Binary(_) | DataType::VarBinary(_) | 
DataType::Blob(_)) => {
+            (Self::Binary(b), DataType::Binary(_) | DataType::VarBinary(_)) => 
{
                 b.append_value(input.read_bytes()?);
             }
+            (Self::LargeBinary(b), DataType::Blob(_)) => 
b.append_value(input.read_bytes()?),
             (Self::Date(b), DataType::Date(_)) => 
b.append_value(input.read_i32()?),
             (Self::Time(b), DataType::Time(_)) => 
b.append_value(input.read_i32()?),
             (Self::TimestampMs(b), DataType::Timestamp(_) | 
DataType::LocalZonedTimestamp(_)) => {
@@ -1492,6 +1496,7 @@ impl ColumnBuilder {
             Self::F64(mut b) => Arc::new(b.finish()),
             Self::String(mut b) => Arc::new(b.finish()),
             Self::Binary(mut b) => Arc::new(b.finish()),
+            Self::LargeBinary(mut b) => Arc::new(b.finish()),
             Self::Date(mut b) => Arc::new(b.finish()),
             Self::Time(mut b) => Arc::new(b.finish()),
             Self::TimestampMs(mut b) => Arc::new(b.finish()),
@@ -2335,9 +2340,9 @@ mod tests {
     use crate::btree::test_util::BytesFileRead;
     use crate::io::FileIOBuilder;
     use crate::spec::{
-        ArrayType, BigIntType, BooleanType, DataType, DateType, Datum, 
DecimalType, DoubleType,
-        FloatType, IntType, MapType, MultisetType, Predicate, 
PredicateOperator, RowType, TimeType,
-        TimestampType, VarBinaryType, VarCharType, VariantType,
+        ArrayType, BigIntType, BlobType, BooleanType, DataType, DateType, 
Datum, DecimalType,
+        DoubleType, FloatType, IntType, MapType, MultisetType, Predicate, 
PredicateOperator,
+        RowType, TimeType, TimestampType, VarBinaryType, VarCharType, 
VariantType,
     };
     use crate::variant::GenericVariant;
     use futures::TryStreamExt;
@@ -2636,6 +2641,68 @@ mod tests {
         assert_eq!(names.value(1), "ccc");
     }
 
+    #[tokio::test]
+    async fn row_writer_reader_preserves_binary_and_large_binary_types() {
+        let file_io = FileIOBuilder::new("memory").build().unwrap();
+        let path = "memory:/row-binary-types/data.row";
+        let fields = vec![
+            DataField::new(
+                0,
+                "bytes".to_string(),
+                DataType::VarBinary(
+                    VarBinaryType::try_new(true, 
VarBinaryType::MAX_LENGTH).unwrap(),
+                ),
+            ),
+            DataField::new(1, "blob".to_string(), 
DataType::Blob(BlobType::new())),
+        ];
+        let schema = build_target_arrow_schema(&fields).unwrap();
+        let output = file_io.new_output(path).unwrap();
+        let mut writer = RowFormatWriter::new(&output, schema.clone(), 
fields.clone(), 1)
+            .await
+            .unwrap();
+        let batch = RecordBatch::try_new(
+            schema,
+            vec![
+                Arc::new(BinaryArray::from(vec![Some(b"bytes".as_slice()), 
None])),
+                Arc::new(LargeBinaryArray::from(vec![Some(b"blob".as_slice()), 
None])),
+            ],
+        )
+        .unwrap();
+        writer.write(&batch).await.unwrap();
+        Box::new(writer).close().await.unwrap();
+
+        let bytes = file_io.new_input(path).unwrap().read().await.unwrap();
+        let batches = RowFormatReader
+            .read_batch_stream(
+                Box::new(BytesFileRead(bytes.clone())),
+                bytes.len() as u64,
+                &fields,
+                None,
+                Some(8),
+                None,
+            )
+            .await
+            .unwrap()
+            .try_collect::<Vec<_>>()
+            .await
+            .unwrap();
+        let batch = &batches[0];
+        let binary = batch
+            .column(0)
+            .as_any()
+            .downcast_ref::<BinaryArray>()
+            .unwrap();
+        let blob = batch
+            .column(1)
+            .as_any()
+            .downcast_ref::<LargeBinaryArray>()
+            .unwrap();
+        assert_eq!(binary.value(0), b"bytes");
+        assert!(binary.is_null(1));
+        assert_eq!(blob.value(0), b"blob");
+        assert!(blob.is_null(1));
+    }
+
     #[tokio::test]
     async fn row_writer_reader_roundtrip_variant() {
         let file_io = FileIOBuilder::new("memory").build().unwrap();
diff --git a/crates/paimon/src/arrow/mod.rs b/crates/paimon/src/arrow/mod.rs
index e30d83e5..1f017aee 100644
--- a/crates/paimon/src/arrow/mod.rs
+++ b/crates/paimon/src/arrow/mod.rs
@@ -52,9 +52,8 @@ pub fn paimon_type_to_arrow(dt: &PaimonDataType) -> 
crate::Result<ArrowDataType>
         PaimonDataType::Float(_) => ArrowDataType::Float32,
         PaimonDataType::Double(_) => ArrowDataType::Float64,
         PaimonDataType::VarChar(_) | PaimonDataType::Char(_) => 
ArrowDataType::Utf8,
-        PaimonDataType::Binary(_) | PaimonDataType::VarBinary(_) | 
PaimonDataType::Blob(_) => {
-            ArrowDataType::Binary
-        }
+        PaimonDataType::Binary(_) | PaimonDataType::VarBinary(_) => 
ArrowDataType::Binary,
+        PaimonDataType::Blob(_) => ArrowDataType::LargeBinary,
         PaimonDataType::Variant(_) => variant_arrow_type(),
         PaimonDataType::Date(_) => ArrowDataType::Date32,
         PaimonDataType::Time(_) => 
ArrowDataType::Time32(TimeUnit::Millisecond),
@@ -419,9 +418,11 @@ mod tests {
 
     #[test]
     fn test_binary_types() {
+        let binary = PaimonDataType::Binary(BinaryType::new(16).unwrap());
         let varbinary = PaimonDataType::VarBinary(
             VarBinaryType::try_new(true, VarBinaryType::MAX_LENGTH).unwrap(),
         );
+        assert_paimon_to_arrow(&binary, &ArrowDataType::Binary);
         assert_paimon_to_arrow(&varbinary, &ArrowDataType::Binary);
 
         for arrow in &[
@@ -434,14 +435,37 @@ mod tests {
     }
 
     #[test]
-    fn test_blob_type_maps_one_way_to_arrow_binary() {
+    fn test_blob_type_maps_one_way_to_arrow_large_binary() {
         let blob = PaimonDataType::Blob(BlobType::new());
         let varbinary = PaimonDataType::VarBinary(
             VarBinaryType::try_new(true, VarBinaryType::MAX_LENGTH).unwrap(),
         );
 
-        assert_paimon_to_arrow(&blob, &ArrowDataType::Binary);
+        assert_paimon_to_arrow(&blob, &ArrowDataType::LargeBinary);
         assert_arrow_to_paimon(&ArrowDataType::Binary, true, &varbinary);
+
+        let array = PaimonDataType::Array(ArrayType::new(blob.clone()));
+        assert_paimon_to_arrow(
+            &array,
+            &ArrowDataType::List(Arc::new(ArrowField::new(
+                "element",
+                ArrowDataType::LargeBinary,
+                true,
+            ))),
+        );
+
+        let map = PaimonDataType::Map(MapType::new(
+            PaimonDataType::VarChar(VarCharType::string_type()),
+            blob,
+        ));
+        let map_arrow = paimon_type_to_arrow(&map).unwrap();
+        let ArrowDataType::Map(entries, _) = map_arrow else {
+            panic!("expected Arrow Map");
+        };
+        let ArrowDataType::Struct(fields) = entries.data_type() else {
+            panic!("expected Arrow Map entries Struct");
+        };
+        assert_eq!(fields[1].data_type(), &ArrowDataType::LargeBinary);
     }
 
     #[test]
diff --git a/crates/paimon/src/arrow/residual.rs 
b/crates/paimon/src/arrow/residual.rs
index 5ddeb95d..50d716c4 100644
--- a/crates/paimon/src/arrow/residual.rs
+++ b/crates/paimon/src/arrow/residual.rs
@@ -50,8 +50,9 @@ use crate::Error;
 use arrow_array::{
     Array, ArrayRef, BinaryArray, BooleanArray, Date32Array, Datum as 
ArrowDatum, Decimal128Array,
     FixedSizeListArray, Float32Array, Float64Array, Int16Array, Int32Array, 
Int64Array, Int8Array,
-    LargeListArray, ListArray, RecordBatch, Scalar, StringArray, 
Time32MillisecondArray,
-    TimestampMicrosecondArray, TimestampMillisecondArray, 
TimestampNanosecondArray,
+    LargeBinaryArray, LargeListArray, ListArray, RecordBatch, Scalar, 
StringArray,
+    Time32MillisecondArray, TimestampMicrosecondArray, 
TimestampMillisecondArray,
+    TimestampNanosecondArray,
 };
 use arrow_ord::cmp::{
     eq as arrow_eq, gt as arrow_gt, gt_eq as arrow_gt_eq, lt as arrow_lt, 
lt_eq as arrow_lt_eq,
@@ -1047,10 +1048,16 @@ pub(crate) fn literal_scalar_for_arrow_filter(
             Datum::String(value) => 
Arc::new(StringArray::new_scalar(value.as_str()).into_inner()),
             _ => return Ok(None),
         },
-        DataType::Binary(_) | DataType::VarBinary(_) | DataType::Blob(_) => 
match literal {
+        DataType::Binary(_) | DataType::VarBinary(_) => match literal {
             Datum::Bytes(value) => 
Arc::new(BinaryArray::new_scalar(value.as_slice()).into_inner()),
             _ => return Ok(None),
         },
+        DataType::Blob(_) => match literal {
+            Datum::Bytes(value) => {
+                
Arc::new(LargeBinaryArray::new_scalar(value.as_slice()).into_inner())
+            }
+            _ => return Ok(None),
+        },
         DataType::Date(_) => match literal {
             Datum::Date(value) => 
Arc::new(Date32Array::new_scalar(*value).into_inner()),
             _ => return Ok(None),
@@ -1522,6 +1529,21 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_blob_literal_uses_large_binary_scalar() {
+        let scalar = literal_scalar_for_arrow_filter(
+            &Datum::Bytes(b"blob".to_vec()),
+            &DataType::Blob(crate::spec::BlobType::new()),
+        )
+        .unwrap()
+        .unwrap();
+
+        assert_eq!(
+            scalar.get().0.data_type(),
+            &arrow_schema::DataType::LargeBinary
+        );
+    }
+
     #[test]
     fn test_in_unconvertible_literal_still_errors() {
         // An out-of-range literal must keep the general path's error behavior:
diff --git a/crates/paimon/src/arrow/shredding/variant.rs 
b/crates/paimon/src/arrow/shredding/variant.rs
index 0f82c470..85475be5 100644
--- a/crates/paimon/src/arrow/shredding/variant.rs
+++ b/crates/paimon/src/arrow/shredding/variant.rs
@@ -31,9 +31,9 @@ use crate::variant::{
 use crate::{Error, Result};
 use arrow_array::{
     new_null_array, Array, ArrayRef, BinaryArray, BooleanArray, Date32Array, 
Decimal128Array,
-    Float32Array, Float64Array, Int16Array, Int32Array, Int64Array, Int8Array, 
ListArray,
-    RecordBatch, StringArray, StructArray, TimestampMicrosecondArray, 
TimestampMillisecondArray,
-    TimestampNanosecondArray,
+    Float32Array, Float64Array, Int16Array, Int32Array, Int64Array, Int8Array, 
LargeBinaryArray,
+    ListArray, RecordBatch, StringArray, StructArray, 
TimestampMicrosecondArray,
+    TimestampMillisecondArray, TimestampNanosecondArray,
 };
 use arrow_buffer::{BooleanBuffer, NullBuffer, OffsetBuffer, ScalarBuffer};
 use arrow_schema::{DataType as ArrowDataType, Field as ArrowField, Fields, 
TimeUnit};
@@ -1002,7 +1002,7 @@ fn array_from_values(values: &[Option<ShreddedValue>], 
data_type: &DataType) ->
                 })
                 .collect::<Vec<_>>(),
         ))),
-        DataType::VarBinary(_) | DataType::Binary(_) | DataType::Blob(_) => {
+        DataType::VarBinary(_) | DataType::Binary(_) => {
             let values = values
                 .iter()
                 .map(|value| match value {
@@ -1012,6 +1012,16 @@ fn array_from_values(values: &[Option<ShreddedValue>], 
data_type: &DataType) ->
                 .collect::<Vec<_>>();
             Ok(Arc::new(BinaryArray::from(values)))
         }
+        DataType::Blob(_) => {
+            let values = values
+                .iter()
+                .map(|value| match value {
+                    Some(ShreddedValue::Binary(v)) => Some(v.as_slice()),
+                    _ => None,
+                })
+                .collect::<Vec<_>>();
+            Ok(Arc::new(LargeBinaryArray::from(values)))
+        }
         DataType::Date(_) => Ok(Arc::new(Date32Array::from(
             values
                 .iter()
@@ -1196,13 +1206,16 @@ fn value_at(array: &dyn Array, row: usize, data_type: 
&DataType) -> Result<Optio
                 .value(row)
                 .to_string(),
         )),
-        DataType::VarBinary(_) | DataType::Binary(_) | DataType::Blob(_) => {
-            Some(ShreddedValue::Binary(
-                downcast_array::<BinaryArray>(array, "Binary")?
-                    .value(row)
-                    .to_vec(),
-            ))
-        }
+        DataType::VarBinary(_) | DataType::Binary(_) => 
Some(ShreddedValue::Binary(
+            downcast_array::<BinaryArray>(array, "Binary")?
+                .value(row)
+                .to_vec(),
+        )),
+        DataType::Blob(_) => Some(ShreddedValue::Binary(
+            downcast_array::<LargeBinaryArray>(array, "LargeBinary")?
+                .value(row)
+                .to_vec(),
+        )),
         DataType::Date(_) => Some(ShreddedValue::Date32(
             downcast_array::<Date32Array>(array, "Date")?.value(row),
         )),
@@ -1323,7 +1336,7 @@ fn null_buffer(validities: Vec<bool>) -> NullBuffer {
 mod tests {
     use super::*;
     use crate::arrow::variant_arrow_type;
-    use crate::spec::{variant_extraction_row, IntType, VarCharType, 
VariantType};
+    use crate::spec::{variant_extraction_row, BlobType, IntType, VarCharType, 
VariantType};
 
     fn variant_array_for_test(values: &[GenericVariant]) -> ArrayRef {
         let value_items = values
@@ -1351,6 +1364,20 @@ mod tests {
         )
     }
 
+    #[test]
+    fn blob_typed_values_use_large_binary() {
+        let data_type = DataType::Blob(BlobType::new());
+        let values = vec![Some(ShreddedValue::Binary(b"blob".to_vec())), None];
+        let array = array_from_values(&values, &data_type).unwrap();
+
+        assert_eq!(array.data_type(), &ArrowDataType::LargeBinary);
+        assert_eq!(
+            value_at(array.as_ref(), 0, &data_type).unwrap(),
+            Some(ShreddedValue::Binary(b"blob".to_vec()))
+        );
+        assert_eq!(value_at(array.as_ref(), 1, &data_type).unwrap(), None);
+    }
+
     #[test]
     fn configured_schema_transforms_and_reassembles_variant_batch() {
         let logical_fields = vec![
diff --git a/crates/paimon/src/table/aggregator/numeric.rs 
b/crates/paimon/src/table/aggregator/numeric.rs
index f093827b..5546be3a 100644
--- a/crates/paimon/src/table/aggregator/numeric.rs
+++ b/crates/paimon/src/table/aggregator/numeric.rs
@@ -1388,8 +1388,8 @@ mod tests {
     #[test]
     fn test_min_rejects_unsupported_type() {
         // Java's `TypeCheckUtils#isComparable` rejects exactly MAP, MULTISET, 
ROW,
-        // ARRAY, VECTOR, VARIANT and BLOB. BLOB is the interesting one here: 
it
-        // shares Arrow's `Binary` with BINARY and VARBINARY, which min/max 
accepts.
+        // ARRAY, VECTOR, VARIANT and BLOB. BINARY and VARBINARY remain 
comparable,
+        // while BLOB is intentionally rejected even though it is represented 
as LargeBinary.
         let err = MinAgg::new("v", 
&DataType::Blob(crate::spec::BlobType::new())).unwrap_err();
         assert!(matches!(err, crate::Error::ConfigInvalid { message } if 
message.contains("min")));
     }
diff --git a/crates/paimon/src/table/blob_resolver.rs 
b/crates/paimon/src/table/blob_resolver.rs
index e66e4dcc..4d41aa93 100644
--- a/crates/paimon/src/table/blob_resolver.rs
+++ b/crates/paimon/src/table/blob_resolver.rs
@@ -19,8 +19,8 @@ use crate::arrow::format::blob::DEFAULT_BLOB_READ_PARALLELISM;
 use crate::io::{FileIO, FileRead};
 use crate::spec::BlobDescriptor;
 use crate::Result;
-use arrow_array::builder::BinaryBuilder;
-use arrow_array::{Array, BinaryArray};
+use arrow_array::builder::LargeBinaryBuilder;
+use arrow_array::{Array, LargeBinaryArray};
 use bytes::Bytes;
 use futures::{stream, StreamExt, TryStreamExt};
 use std::collections::HashMap;
@@ -122,7 +122,7 @@ impl BlobReader {
                                 blob_error_with_context(error, &indices, 
Some(&uri))
                             })?,
                     };
-                    let mut builder = 
BinaryBuilder::with_capacity(entries.len(), 0);
+                    let mut builder = 
LargeBinaryBuilder::with_capacity(entries.len(), 0);
                     for (_, descriptor) in &entries {
                         builder.append_value(
                             BlobDescriptor::new(
@@ -405,10 +405,10 @@ impl BlobReadLimiter {
 /// resolve it by reading the actual data from the referenced 
URI+offset+length.
 /// Raw data values are passed through unchanged.
 pub(crate) async fn resolve_blob_column(
-    col: &BinaryArray,
+    col: &LargeBinaryArray,
     file_io: &FileIO,
     limiter: BlobReadLimiter,
-) -> Result<BinaryArray> {
+) -> Result<LargeBinaryArray> {
     let mut needs_resolve = false;
     for i in 0..col.len() {
         if !col.is_null(i) && BlobDescriptor::is_blob_descriptor(col.value(i)) 
{
@@ -538,7 +538,7 @@ pub(crate) async fn resolve_blob_column(
         }
     }
 
-    let mut builder = BinaryBuilder::with_capacity(col.len(), value_capacity);
+    let mut builder = LargeBinaryBuilder::with_capacity(col.len(), 
value_capacity);
     for cell in cells {
         match cell {
             ResolvedBlobCell::Null => builder.append_null(),
diff --git a/crates/paimon/src/table/data_evolution_reader.rs 
b/crates/paimon/src/table/data_evolution_reader.rs
index a4480878..c162c7e0 100644
--- a/crates/paimon/src/table/data_evolution_reader.rs
+++ b/crates/paimon/src/table/data_evolution_reader.rs
@@ -35,7 +35,7 @@ use crate::table::schema_manager::SchemaManager;
 use crate::table::source::any_range_overlaps_file;
 use crate::table::{ArrowRecordBatchStream, RESTEnv, RowRange};
 use crate::{DataSplit, Error};
-use arrow_array::{Array, BinaryArray, Int64Array, RecordBatch};
+use arrow_array::{Array, Int64Array, LargeBinaryArray, RecordBatch};
 use async_stream::try_stream;
 use futures::{StreamExt, TryStreamExt};
 use roaring::RoaringBitmap;
@@ -903,8 +903,8 @@ async fn resolve_descriptor_columns_with<F, Fut>(
     resolve: F,
 ) -> crate::Result<RecordBatch>
 where
-    F: Fn(BinaryArray) -> Fut,
-    Fut: Future<Output = crate::Result<BinaryArray>>,
+    F: Fn(LargeBinaryArray) -> Fut,
+    Fut: Future<Output = crate::Result<LargeBinaryArray>>,
 {
     let schema = batch.schema();
     let mut columns = batch.columns().to_vec();
@@ -915,7 +915,7 @@ where
             if let Some(bin_col) = batch
                 .column(idx)
                 .as_any()
-                .downcast_ref::<arrow_array::BinaryArray>()
+                .downcast_ref::<LargeBinaryArray>()
             {
                 descriptor_columns.push((idx, bin_col.clone()));
             }
@@ -927,14 +927,15 @@ where
     }
 
     let resolve = &resolve;
-    let resolved_columns: Vec<(usize, BinaryArray)> = 
futures::stream::iter(descriptor_columns)
-        .map(move |(idx, column)| {
-            let future = resolve(column);
-            async move { future.await.map(|resolved| (idx, resolved)) }
-        })
-        .buffer_unordered(blob_parallelism)
-        .try_collect()
-        .await?;
+    let resolved_columns: Vec<(usize, LargeBinaryArray)> =
+        futures::stream::iter(descriptor_columns)
+            .map(move |(idx, column)| {
+                let future = resolve(column);
+                async move { future.await.map(|resolved| (idx, resolved)) }
+            })
+            .buffer_unordered(blob_parallelism)
+            .try_collect()
+            .await?;
     for (idx, resolved) in resolved_columns {
         columns[idx] = Arc::new(resolved);
     }
@@ -989,7 +990,7 @@ fn collect_blob_view_structs(
         if !blob_view_fields.contains(field.name()) {
             continue;
         }
-        let col = binary_column(batch, idx, field.name())?;
+        let col = large_binary_column(batch, idx, field.name())?;
         for row in 0..col.len() {
             if col.is_null(row) {
                 continue;
@@ -1025,8 +1026,8 @@ fn replace_blob_view_columns(
             continue;
         }
 
-        let col = binary_column(&batch, idx, field.name())?;
-        let mut builder = arrow_array::builder::BinaryBuilder::new();
+        let col = large_binary_column(&batch, idx, field.name())?;
+        let mut builder = arrow_array::builder::LargeBinaryBuilder::new();
         for row in 0..col.len() {
             if col.is_null(row) {
                 builder.append_null();
@@ -1063,17 +1064,17 @@ fn replace_blob_view_columns(
     })
 }
 
-fn binary_column<'a>(
+fn large_binary_column<'a>(
     batch: &'a RecordBatch,
     idx: usize,
     field_name: &str,
-) -> crate::Result<&'a BinaryArray> {
+) -> crate::Result<&'a LargeBinaryArray> {
     batch
         .column(idx)
         .as_any()
-        .downcast_ref::<BinaryArray>()
+        .downcast_ref::<LargeBinaryArray>()
         .ok_or_else(|| Error::DataInvalid {
-            message: format!("blob-view-field '{field_name}' requires a 
BinaryArray column"),
+            message: format!("blob-view-field '{field_name}' requires a 
LargeBinaryArray column"),
             source: None,
         })
 }
@@ -1142,10 +1143,10 @@ impl BlobViewLookup {
                 let blob_col = batch
                     .column(0)
                     .as_any()
-                    .downcast_ref::<BinaryArray>()
+                    .downcast_ref::<LargeBinaryArray>()
                     .ok_or_else(|| Error::DataInvalid {
                         message: format!(
-                            "Upstream blob field '{}' did not read as 
BinaryArray",
+                            "Upstream blob field '{}' did not read as 
LargeBinaryArray",
                             field.name()
                         ),
                         source: None,
@@ -2649,8 +2650,8 @@ mod tests {
         CommitMessage, DataSplitBuilder, DeletionFile, Table, TableCommit, 
TableRead,
     };
     use arrow_array::{
-        Array, BinaryArray, FixedSizeListArray, Float32Array, Int32Array, 
Int64Array, ListArray,
-        RecordBatch,
+        Array, FixedSizeListArray, Float32Array, Int32Array, Int64Array, 
LargeBinaryArray,
+        ListArray, RecordBatch,
     };
     use bytes::Bytes;
     use futures::TryStreamExt;
@@ -2692,16 +2693,16 @@ mod tests {
     #[tokio::test]
     async fn test_descriptor_columns_resolve_concurrently_and_preserve_order() 
{
         let schema = Arc::new(arrow_schema::Schema::new(vec![
-            arrow_schema::Field::new("blob_a", arrow_schema::DataType::Binary, 
true),
+            arrow_schema::Field::new("blob_a", 
arrow_schema::DataType::LargeBinary, true),
             arrow_schema::Field::new("id", arrow_schema::DataType::Int32, 
false),
-            arrow_schema::Field::new("blob_b", arrow_schema::DataType::Binary, 
true),
+            arrow_schema::Field::new("blob_b", 
arrow_schema::DataType::LargeBinary, true),
         ]));
         let batch = RecordBatch::try_new(
             schema,
             vec![
-                Arc::new(BinaryArray::from(vec![Some(b"a".as_slice())])),
+                Arc::new(LargeBinaryArray::from(vec![Some(b"a".as_slice())])),
                 Arc::new(Int32Array::from(vec![7])),
-                Arc::new(BinaryArray::from(vec![Some(b"b".as_slice())])),
+                Arc::new(LargeBinaryArray::from(vec![Some(b"b".as_slice())])),
             ],
         )
         .unwrap();
@@ -2730,7 +2731,7 @@ mod tests {
             resolved
                 .column(0)
                 .as_any()
-                .downcast_ref::<BinaryArray>()
+                .downcast_ref::<LargeBinaryArray>()
                 .unwrap()
                 .value(0),
             b"a"
@@ -2739,7 +2740,7 @@ mod tests {
             resolved
                 .column(2)
                 .as_any()
-                .downcast_ref::<BinaryArray>()
+                .downcast_ref::<LargeBinaryArray>()
                 .unwrap()
                 .value(0),
             b"b"
@@ -6530,7 +6531,7 @@ mod tests {
                 let array = batch
                     .column(idx)
                     .as_any()
-                    .downcast_ref::<BinaryArray>()
+                    .downcast_ref::<LargeBinaryArray>()
                     .unwrap();
                 (0..array.len())
                     .map(|row| (!array.is_null(row)).then(|| 
array.value(row).to_vec()))
@@ -6558,7 +6559,7 @@ mod tests {
                             return None;
                         }
                         let values = array.value(row);
-                        let values = 
values.as_any().downcast_ref::<BinaryArray>().unwrap();
+                        let values = 
values.as_any().downcast_ref::<LargeBinaryArray>().unwrap();
                         Some(
                             (0..values.len())
                                 .map(|idx| {
diff --git a/crates/paimon/src/table/data_evolution_reader/blob_fallback.rs 
b/crates/paimon/src/table/data_evolution_reader/blob_fallback.rs
index c0674c2c..adcab7d5 100644
--- a/crates/paimon/src/table/data_evolution_reader/blob_fallback.rs
+++ b/crates/paimon/src/table/data_evolution_reader/blob_fallback.rs
@@ -331,7 +331,7 @@ mod tests {
     use super::*;
     use crate::io::FileRead;
     use crate::spec::{BlobType, DataType};
-    use arrow_array::{Array, BinaryArray};
+    use arrow_array::{Array, LargeBinaryArray};
     use bytes::Bytes;
     use std::ops::Range;
     use std::sync::atomic::{AtomicUsize, Ordering};
@@ -457,7 +457,7 @@ mod tests {
         let values = batch
             .column(0)
             .as_any()
-            .downcast_ref::<BinaryArray>()
+            .downcast_ref::<LargeBinaryArray>()
             .unwrap();
 
         assert_eq!(values.value(0), b"new-0");
diff --git a/crates/paimon/src/table/dedicated_format_file_writer.rs 
b/crates/paimon/src/table/dedicated_format_file_writer.rs
index 6417b9cf..4f3ee315 100644
--- a/crates/paimon/src/table/dedicated_format_file_writer.rs
+++ b/crates/paimon/src/table/dedicated_format_file_writer.rs
@@ -268,11 +268,11 @@ impl AppendDedicatedFormatFileWriter {
             let Some(col) = batch
                 .column(*column_index)
                 .as_any()
-                .downcast_ref::<arrow_array::BinaryArray>()
+                .downcast_ref::<arrow_array::LargeBinaryArray>()
             else {
                 return Err(crate::Error::DataInvalid {
                     message: format!(
-                        "blob-view-field '{field_name}' requires a BinaryArray 
value column"
+                        "blob-view-field '{field_name}' requires a 
LargeBinaryArray value column"
                     ),
                     source: None,
                 });
diff --git a/crates/paimon/src/table/table_write.rs 
b/crates/paimon/src/table/table_write.rs
index 2d945184..4fc57a58 100644
--- a/crates/paimon/src/table/table_write.rs
+++ b/crates/paimon/src/table/table_write.rs
@@ -2282,13 +2282,13 @@ pub(in crate::table) mod tests {
 
         let schema = Arc::new(ArrowSchema::new(vec![
             ArrowField::new("id", ArrowDataType::Int32, false),
-            ArrowField::new("payload", ArrowDataType::Binary, true),
+            ArrowField::new("payload", ArrowDataType::LargeBinary, true),
         ]));
         let batch = RecordBatch::try_new(
             schema,
             vec![
                 Arc::new(Int32Array::from(vec![1, 2, 3])),
-                Arc::new(arrow_array::BinaryArray::from(vec![
+                Arc::new(arrow_array::LargeBinaryArray::from(vec![
                     Some(b"hello" as &[u8]),
                     None,
                     Some(b"world"),
@@ -2354,12 +2354,12 @@ pub(in crate::table) mod tests {
         );
         let batch = RecordBatch::try_new(
             Arc::new(ArrowSchema::new(vec![
-                ArrowField::new("payload", ArrowDataType::Binary, true),
+                ArrowField::new("payload", ArrowDataType::LargeBinary, true),
                 ArrowField::new("a", ArrowDataType::Int32, false),
                 ArrowField::new("b", ArrowDataType::Int32, false),
             ])),
             vec![
-                Arc::new(arrow_array::BinaryArray::from(vec![Some(
+                Arc::new(arrow_array::LargeBinaryArray::from(vec![Some(
                     b"payload" as &[u8],
                 )])),
                 Arc::new(Int32Array::from(vec![100])),
diff --git a/crates/paimon/tests/rest_catalog_test.rs 
b/crates/paimon/tests/rest_catalog_test.rs
index 7deb412f..bb617d05 100644
--- a/crates/paimon/tests/rest_catalog_test.rs
+++ b/crates/paimon/tests/rest_catalog_test.rs
@@ -23,7 +23,7 @@
 use std::collections::HashMap;
 use std::sync::Arc;
 
-use arrow_array::{Array, BinaryArray, Int32Array, Int64Array, RecordBatch, 
StringArray};
+use arrow_array::{Array, Int32Array, Int64Array, LargeBinaryArray, 
RecordBatch, StringArray};
 use arrow_schema::{DataType as ArrowDataType, Field as ArrowField, Schema as 
ArrowSchema};
 use axum::http::StatusCode;
 use futures::TryStreamExt;
@@ -132,7 +132,7 @@ fn blob_batch(ids: Vec<i32>, names: Vec<&str>, pictures: 
Vec<Vec<u8>>) -> Record
     let schema = Arc::new(ArrowSchema::new(vec![
         ArrowField::new("id", ArrowDataType::Int32, false),
         ArrowField::new("name", ArrowDataType::Utf8, true),
-        ArrowField::new("picture", ArrowDataType::Binary, true),
+        ArrowField::new("picture", ArrowDataType::LargeBinary, true),
     ]));
     let picture_refs = pictures
         .iter()
@@ -143,7 +143,7 @@ fn blob_batch(ids: Vec<i32>, names: Vec<&str>, pictures: 
Vec<Vec<u8>>) -> Record
         vec![
             Arc::new(Int32Array::from(ids)),
             Arc::new(StringArray::from(names)),
-            Arc::new(BinaryArray::from(picture_refs)),
+            Arc::new(LargeBinaryArray::from(picture_refs)),
         ],
     )
     .unwrap()
@@ -180,7 +180,7 @@ fn collect_blob_rows(batches: &[RecordBatch]) -> Vec<(i32, 
String, Option<Vec<u8
         let pictures = batch
             .column(2)
             .as_any()
-            .downcast_ref::<BinaryArray>()
+            .downcast_ref::<LargeBinaryArray>()
             .unwrap();
         for row in 0..batch.num_rows() {
             rows.push((

Reply via email to