1#![expect(
5 deprecated,
6 reason = "This module defines and implements a deprecated trait `ArrowArrayExecutor`"
7)]
8
9pub mod bool;
10mod byte;
11pub mod byte_view;
12mod decimal;
13mod dictionary;
14mod fixed_size_list;
15mod list;
16mod list_view;
17mod map;
18pub mod null;
19pub mod primitive;
20mod run_end;
21mod struct_;
22mod temporal;
23mod validity;
24
25use arrow_array::ArrayRef as ArrowArrayRef;
26use arrow_array::RecordBatch;
27use arrow_array::cast::AsArray;
28use arrow_array::types::*;
29use arrow_schema::DataType;
30use arrow_schema::Field;
31use arrow_schema::FieldRef;
32use arrow_schema::Schema;
33use itertools::Itertools;
34use vortex_array::ArrayRef;
35use vortex_array::ExecutionCtx;
36use vortex_array::arrays::List;
37use vortex_array::arrays::VarBin;
38use vortex_array::arrays::list::ListArraySlotsExt;
39use vortex_array::arrays::varbin::VarBinArraySlotsExt;
40use vortex_array::dtype::DType;
41use vortex_array::dtype::PType;
42use vortex_error::VortexResult;
43use vortex_error::vortex_bail;
44use vortex_error::vortex_ensure_eq;
45
46use crate::ArrowExporter;
47use crate::executor::bool::to_arrow_bool;
48use crate::executor::byte::to_arrow_byte_array;
49use crate::executor::byte_view::to_arrow_byte_view;
50use crate::executor::decimal::to_arrow_decimal;
51use crate::executor::dictionary::to_arrow_dictionary;
52use crate::executor::fixed_size_list::to_arrow_fixed_list;
53use crate::executor::list::to_arrow_list;
54use crate::executor::list_view::to_arrow_list_view;
55use crate::executor::map::to_arrow_map;
56use crate::executor::null::to_arrow_null;
57use crate::executor::primitive::to_arrow_primitive;
58use crate::executor::run_end::to_arrow_run_end;
59use crate::executor::struct_::to_arrow_struct;
60use crate::executor::temporal::to_arrow_date;
61use crate::executor::temporal::to_arrow_time;
62use crate::executor::temporal::to_arrow_timestamp;
63use crate::session::ArrowSessionExt;
64
65#[deprecated(note = "Use an `ArrowSession` to perform conversions to/from Arrow arrays")]
67pub trait ArrowArrayExecutor: Sized {
68 #[deprecated(note = "Use an `ArrowSession` to perform conversions to/from Arrow arrays")]
73 fn execute_arrow(
74 self,
75 data_type: Option<&DataType>,
76 ctx: &mut ExecutionCtx,
77 ) -> VortexResult<ArrowArrayRef>;
78
79 #[deprecated(note = "Use an `ArrowSession` to perform conversions to/from Arrow arrays")]
81 fn execute_record_batch(
82 self,
83 schema: &Schema,
84 ctx: &mut ExecutionCtx,
85 ) -> VortexResult<RecordBatch> {
86 let array = self.execute_arrow(Some(&DataType::Struct(schema.fields.clone())), ctx)?;
87 Ok(RecordBatch::from(array.as_struct()))
88 }
89
90 #[deprecated(note = "Use an `ArrowSession` to perform conversions to/from Arrow arrays")]
92 fn execute_record_batches(
93 self,
94 schema: &Schema,
95 ctx: &mut ExecutionCtx,
96 ) -> VortexResult<Vec<RecordBatch>>;
97}
98
99#[expect(deprecated, reason = "backward compatibility")]
100impl ArrowArrayExecutor for ArrayRef {
101 fn execute_arrow(
102 self,
103 data_type: Option<&DataType>,
104 ctx: &mut ExecutionCtx,
105 ) -> VortexResult<ArrowArrayRef> {
106 let target = data_type.map(|dt| Field::new("", dt.clone(), self.dtype().is_nullable()));
107 let session = ctx.session().clone();
108 session.arrow().execute_arrow(self, target.as_ref(), ctx)
109 }
110
111 fn execute_record_batches(
112 self,
113 schema: &Schema,
114 ctx: &mut ExecutionCtx,
115 ) -> VortexResult<Vec<RecordBatch>> {
116 self.to_array_iterator()
117 .map(|a| a?.execute_record_batch(schema, ctx))
118 .try_collect()
119 }
120}
121
122pub(crate) fn execute_arrow_naive(
128 array: ArrayRef,
129 data_type: Option<&DataType>,
130 exporter: &ArrowExporter<'_>,
131 ctx: &mut ExecutionCtx,
132) -> VortexResult<ArrowArrayRef> {
133 let len = array.len();
134
135 let resolved_type: DataType = match data_type {
136 Some(dt) => dt.clone(),
137 None => infer_nearest_arrow_type(&array, ctx)?,
138 };
139
140 let arrow = match &resolved_type {
141 DataType::Null => to_arrow_null(array, ctx),
142 DataType::Boolean => to_arrow_bool(array, ctx),
143 DataType::Int8 => to_arrow_primitive::<Int8Type>(array, ctx),
144 DataType::Int16 => to_arrow_primitive::<Int16Type>(array, ctx),
145 DataType::Int32 => to_arrow_primitive::<Int32Type>(array, ctx),
146 DataType::Int64 => to_arrow_primitive::<Int64Type>(array, ctx),
147 DataType::UInt8 => to_arrow_primitive::<UInt8Type>(array, ctx),
148 DataType::UInt16 => to_arrow_primitive::<UInt16Type>(array, ctx),
149 DataType::UInt32 => to_arrow_primitive::<UInt32Type>(array, ctx),
150 DataType::UInt64 => to_arrow_primitive::<UInt64Type>(array, ctx),
151 DataType::Float16 => to_arrow_primitive::<Float16Type>(array, ctx),
152 DataType::Float32 => to_arrow_primitive::<Float32Type>(array, ctx),
153 DataType::Float64 => to_arrow_primitive::<Float64Type>(array, ctx),
154 DataType::Binary => to_arrow_byte_array::<BinaryType>(array, ctx),
155 DataType::LargeBinary => to_arrow_byte_array::<LargeBinaryType>(array, ctx),
156 DataType::Utf8 => to_arrow_byte_array::<Utf8Type>(array, ctx),
157 DataType::LargeUtf8 => to_arrow_byte_array::<LargeUtf8Type>(array, ctx),
158 DataType::BinaryView => to_arrow_byte_view::<BinaryViewType>(array, exporter, ctx),
159 DataType::Utf8View => to_arrow_byte_view::<StringViewType>(array, exporter, ctx),
160 DataType::List(elements_field) => {
162 to_arrow_list::<i32>(array, elements_field, exporter, ctx)
163 }
164 DataType::LargeList(elements_field) => {
166 to_arrow_list::<i64>(array, elements_field, exporter, ctx)
167 }
168 DataType::FixedSizeList(elements_field, list_size) => {
170 to_arrow_fixed_list(array, *list_size, elements_field, exporter, ctx)
171 }
172 DataType::ListView(elements_field) => {
174 to_arrow_list_view::<i32>(array, elements_field, exporter, ctx)
175 }
176 DataType::LargeListView(elements_field) => {
178 to_arrow_list_view::<i64>(array, elements_field, exporter, ctx)
179 }
180 DataType::Struct(fields) => {
181 let fields = if data_type.is_none() {
182 None
183 } else {
184 Some(fields)
185 };
186 to_arrow_struct(array, fields, exporter, ctx)
187 }
188 DataType::Dictionary(codes_type, values_type) => {
190 to_arrow_dictionary(array, codes_type, values_type, exporter, ctx)
191 }
192 dt @ DataType::Decimal32(..) => to_arrow_decimal(array, dt, ctx),
193 dt @ DataType::Decimal64(..) => to_arrow_decimal(array, dt, ctx),
194 dt @ DataType::Decimal128(..) => to_arrow_decimal(array, dt, ctx),
195 dt @ DataType::Decimal256(..) => to_arrow_decimal(array, dt, ctx),
196 DataType::RunEndEncoded(ends_type, values_type) => {
198 to_arrow_run_end(array, ends_type.data_type(), values_type, exporter, ctx)
199 }
200 dt @ (DataType::Date32 | DataType::Date64) => to_arrow_date(array, dt, ctx),
201 dt @ (DataType::Time32(_) | DataType::Time64(_)) => to_arrow_time(array, dt, ctx),
202 dt @ DataType::Timestamp(..) => to_arrow_timestamp(array, dt, ctx),
203 DataType::Map(entries_field, keys_sorted) => {
204 to_arrow_map(array, entries_field, *keys_sorted, exporter, ctx)
205 }
206 DataType::FixedSizeBinary(_)
207 | DataType::Duration(_)
208 | DataType::Interval(_)
209 | DataType::Union(..) => {
210 vortex_bail!("Conversion to Arrow type {resolved_type} is not supported");
211 }
212 }?;
213
214 vortex_ensure_eq!(
215 arrow.len(),
216 len,
217 "Arrow array length does not match Vortex array length after conversion to {:?}",
218 arrow
219 );
220
221 Ok(arrow)
222}
223
224fn infer_nearest_arrow_type(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<DataType> {
231 if let Some(varbin) = array.as_opt::<VarBin>() {
233 let offsets_ptype = PType::try_from(varbin.offsets().dtype())?;
234 let use_large = matches!(offsets_ptype, PType::I64 | PType::U64);
235
236 return Ok(match (varbin.dtype(), use_large) {
237 (DType::Utf8(_), false) => DataType::Utf8,
238 (DType::Utf8(_), true) => DataType::LargeUtf8,
239 (DType::Binary(_), false) => DataType::Binary,
240 (DType::Binary(_), true) => DataType::LargeBinary,
241 _ => unreachable!("VarBinArray must have Utf8 or Binary dtype"),
242 });
243 }
244
245 if let Some(list) = array.as_opt::<List>() {
247 let offsets_ptype = PType::try_from(list.offsets().dtype())?;
248 let use_large = matches!(offsets_ptype, PType::I64 | PType::U64);
249 let field = FieldRef::new(infer_nearest_arrow_field(
252 list.elements(),
253 Field::LIST_FIELD_DEFAULT_NAME,
254 ctx,
255 )?);
256
257 return Ok(if use_large {
258 DataType::LargeList(field)
259 } else {
260 DataType::List(field)
261 });
262 }
263
264 Ok(infer_nearest_arrow_field(array, "", ctx)?
267 .data_type()
268 .clone())
269}
270
271pub(crate) fn infer_nearest_arrow_field(
276 array: &ArrayRef,
277 name: &str,
278 ctx: &mut ExecutionCtx,
279) -> VortexResult<Field> {
280 if array.is::<VarBin>() || array.is::<List>() {
283 let data_type = infer_nearest_arrow_type(array, ctx)?;
284 return Ok(Field::new(name, data_type, array.dtype().is_nullable()));
285 }
286 ctx.session()
287 .clone()
288 .arrow()
289 .to_arrow_field(name, array.dtype())
290}
291
292#[cfg(test)]
293mod tests {
294 use std::sync::Arc;
295
296 use arrow_schema::DataType;
297 use arrow_schema::Field;
298 use arrow_schema::TimeUnit;
299 use rstest::rstest;
300 use vortex_array::ArrayRef;
301 use vortex_array::IntoArray;
302 use vortex_array::VortexSessionExecute;
303 use vortex_array::array_session;
304 use vortex_array::arrays::BoolArray;
305 use vortex_array::arrays::PrimitiveArray;
306 use vortex_array::arrays::VarBinViewArray;
307
308 use crate::ArrowSessionExt;
309
310 fn utf8() -> ArrayRef {
311 VarBinViewArray::from_iter_str(["a", "bb"]).into_array()
312 }
313
314 fn primitive() -> ArrayRef {
315 PrimitiveArray::from_iter([1i32, 2]).into_array()
316 }
317
318 fn boolean() -> ArrayRef {
319 BoolArray::from_iter([true, false]).into_array()
320 }
321
322 fn list_target() -> DataType {
323 DataType::List(Arc::new(Field::new("item", DataType::Int32, true)))
324 }
325
326 #[rstest]
328 #[case::bool_from_utf8(utf8(), DataType::Boolean)]
329 #[case::bool_from_primitive(primitive(), DataType::Boolean)]
330 #[case::null_from_utf8(utf8(), DataType::Null)]
331 #[case::null_from_primitive(primitive(), DataType::Null)]
332 #[case::decimal_from_utf8(utf8(), DataType::Decimal128(10, 2))]
333 #[case::decimal_from_primitive(primitive(), DataType::Decimal128(10, 2))]
334 #[case::list_from_utf8(utf8(), list_target())]
335 #[case::list_from_primitive(primitive(), list_target())]
336 #[case::list_view_from_primitive(
337 primitive(),
338 DataType::ListView(Arc::new(Field::new("item", DataType::Int32, true)))
339 )]
340 #[case::fixed_size_list_from_utf8(
341 utf8(),
342 DataType::FixedSizeList(Arc::new(Field::new("item", DataType::Int32, true)), 2)
343 )]
344 #[case::byte_from_primitive(primitive(), DataType::Utf8)]
345 #[case::byte_from_bool(boolean(), DataType::Binary)]
346 fn incompatible_target_returns_error(#[case] array: ArrayRef, #[case] target: DataType) {
347 let session = array_session();
348 let mut ctx = session.create_execution_ctx();
349 let field = Field::new("f", target.clone(), array.dtype().is_nullable());
350
351 let result = session.arrow().execute_arrow(array, Some(&field), &mut ctx);
352
353 assert!(
354 result.is_err(),
355 "expected an error exporting to {target:?}, got Ok"
356 );
357 }
358
359 #[rstest]
361 #[case::bool_to_int32(boolean(), DataType::Int32)]
362 #[case::bool_to_float64(boolean(), DataType::Float64)]
363 #[case::primitive_to_int64(primitive(), DataType::Int64)]
364 #[case::primitive_to_date32(primitive(), DataType::Date32)]
365 #[case::primitive_to_timestamp(primitive(), DataType::Timestamp(TimeUnit::Microsecond, None))]
366 #[case::utf8_to_binary(utf8(), DataType::Binary)]
367 #[case::utf8_to_large_utf8(utf8(), DataType::LargeUtf8)]
368 fn supported_cross_class_target_still_works(#[case] array: ArrayRef, #[case] target: DataType) {
369 let session = array_session();
370 let mut ctx = session.create_execution_ctx();
371 let field = Field::new("f", target.clone(), array.dtype().is_nullable());
372
373 let result = session.arrow().execute_arrow(array, Some(&field), &mut ctx);
374
375 assert!(
376 result.is_ok(),
377 "expected {target:?} export to succeed, got {:?}",
378 result.err()
379 );
380 }
381}