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-11734-f7a72ebaa0386b93227e36f52171297391e49634 in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
commit efb25a30d3deeaac40af73443a441d1a0252c129 Author: Tzu-Chiao Yeh <[email protected]> AuthorDate: Tue Oct 27 02:37:13 2026 +0000 fix(arrow-array): make `record_batch!` usable without a direct `arrow_schema` dependency (#11734) # Which issue does this PR close? None. This is a small, self-contained fix, so no issue was filed. # Rationale for this change `record_batch!` expands to `arrow_schema::*` paths, which resolve in the caller's crate. Users who depend only on the `arrow` umbrella crate and call `arrow::array::record_batch!` get: ``` error[E0433]: cannot find module or crate `arrow_schema` in this scope ``` and have to add `arrow_schema` as a direct dependency to use the macro, or work around it with `use arrow::datatypes as arrow_schema;`. # What changes are included in this PR? `record_batch!` now builds the batch with `RecordBatch::try_from_iter_with_nullable`, using `create_array!` for the columns. As a side effect, types supported by `create_array!` that are not unit `DataType` variants (e.g. `TimestampSecond`) now work in `record_batch!`; previously they failed to compile. # Are these changes tested? Added unit tests for the macro. The missing-dependency case itself can't be reproduced in-tree since workspace crates already depend on `arrow_schema`; I verified it with a standalone crate depending only on `arrow`. # Are there any significant user-facing changes? No breaking changes. `record_batch!` now works with only the `arrow` dependency and also accepts the additional types mentioned above. This change was written with help from an AI assistant (Claude Code). I have reviewed and understand all of it. Co-authored-by: RIchard Baah <[email protected]> --- arrow-array/src/record_batch.rs | 81 +++++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/arrow-array/src/record_batch.rs b/arrow-array/src/record_batch.rs index 78177a4d42..0933f61589 100644 --- a/arrow-array/src/record_batch.rs +++ b/arrow-array/src/record_batch.rs @@ -156,7 +156,6 @@ macro_rules! create_array { /// /// ```rust /// use arrow_array::record_batch; -/// use arrow_schema; /// /// let batch = record_batch!( /// ("a", Int32, [1, 2, 3]), @@ -180,20 +179,13 @@ macro_rules! create_array { #[macro_export] macro_rules! record_batch { ($(($name: expr, $type: ident, $($values: tt)+)),*) => { - { - let schema = std::sync::Arc::new(arrow_schema::Schema::new(vec![ - $( - arrow_schema::Field::new($name, arrow_schema::DataType::$type, true), - )* - ])); - - $crate::RecordBatch::try_new( - schema, - vec![$( - $crate::create_array!($type, $($values)+), - )*] - ) - } + $crate::RecordBatch::try_from_iter_with_nullable(vec![$( + ( + ::std::string::String::from($name), + $crate::create_array!($type, $($values)+) as $crate::ArrayRef, + true, + ), + )*]) }; } @@ -962,12 +954,13 @@ where #[cfg(test)] mod tests { use super::*; + use crate::types::{Int32Type, Int64Type}; use crate::{ BooleanArray, Int8Array, Int32Array, Int64Array, ListArray, StringArray, StringViewArray, }; use arrow_buffer::{Buffer, NullBuffer, ToByteSlice}; use arrow_data::{ArrayData, ArrayDataBuilder}; - use arrow_schema::Fields; + use arrow_schema::{Fields, TimeUnit}; use std::collections::HashMap; #[test] @@ -1038,6 +1031,62 @@ mod tests { assert_eq!(b"xxx", large_binary.value(0)); } + #[test] + fn create_record_batch_macro() { + let values = vec![7, 8, 9]; + let name = String::from("c"); + + let record_batch = record_batch!( + ("a", Int32, [1, 2, 3]), + ("b", Utf8, [Some("x"), None, Some("z")]), + (name, Int64, values) + ) + .unwrap(); + + let expected = Schema::new(vec![ + Field::new("a", DataType::Int32, true), + Field::new("b", DataType::Utf8, true), + Field::new("c", DataType::Int64, true), + ]); + assert_eq!(&expected, record_batch.schema().as_ref()); + assert_eq!(3, record_batch.num_rows()); + assert_eq!( + &[1, 2, 3], + record_batch.column(0).as_primitive::<Int32Type>().values() + ); + let b = record_batch.column(1).as_string::<i32>(); + assert_eq!( + vec![Some("x"), None, Some("z")], + b.iter().collect::<Vec<_>>() + ); + assert_eq!( + &[7, 8, 9], + record_batch.column(2).as_primitive::<Int64Type>().values() + ); + } + + #[test] + fn create_record_batch_macro_parameterized_types() { + let record_batch = record_batch!( + ("ts", TimestampSecond, [1, 2]), + ("d", Decimal128, [Some(100), None]) + ) + .unwrap(); + + let expected = Schema::new(vec![ + Field::new("ts", DataType::Timestamp(TimeUnit::Second, None), true), + Field::new("d", DataType::Decimal128(38, 10), true), + ]); + assert_eq!(&expected, record_batch.schema().as_ref()); + assert_eq!(1, record_batch.column(1).null_count()); + } + + #[test] + fn create_record_batch_macro_length_mismatch() { + let err = record_batch!(("a", Int32, [1, 2, 3]), ("b", Int32, [1, 2])).unwrap_err(); + assert!(matches!(err, ArrowError::InvalidArgumentError(_))); + } + #[test] fn byte_size_should_not_regress() { let schema = Schema::new(vec