Skip to main content

arrow_ipc/
reader.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership
3// SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements
4
5//! Arrow IPC File and Stream Readers
6//!
7//! # Notes
8//!
9//! The [`FileReader`] and [`StreamReader`] have similar interfaces,
10//! however the [`FileReader`] expects a reader that supports [`Seek`]ing
11//!
12//! [`Seek`]: std::io::Seek
13
14mod stream;
15pub use stream::*;
16
17use arrow_select::concat;
18
19use flatbuffers::{VectorIter, VerifierOptions};
20use std::collections::{HashMap, VecDeque};
21use std::fmt;
22use std::io::{BufReader, Read, Seek, SeekFrom};
23use std::sync::Arc;
24
25use arrow_array::*;
26use arrow_buffer::{
27    ArrowNativeType, BooleanBuffer, Buffer, MutableBuffer, NullBuffer, ScalarBuffer,
28};
29use arrow_data::{ArrayData, ArrayDataBuilder, UnsafeFlag};
30use arrow_schema::*;
31
32use crate::compression::{CompressionCodec, DecompressionContext};
33use crate::r#gen::Message;
34use crate::{Block, CONTINUATION_MARKER, FieldNode, MetadataVersion};
35use DataType::*;
36
37/// Read a buffer based on offset and length
38/// From <https://github.com/apache/arrow/blob/6a936c4ff5007045e86f65f1a6b6c3c955ad5103/format/Message.fbs#L58>
39/// Each constituent buffer is first compressed with the indicated
40/// compressor, and then written with the uncompressed length in the first 8
41/// bytes as a 64-bit little-endian signed integer followed by the compressed
42/// buffer bytes (and then padding as required by the protocol). The
43/// uncompressed length may be set to -1 to indicate that the data that
44/// follows is not compressed, which can be useful for cases where
45/// compression does not yield appreciable savings.
46fn read_buffer(
47    buf: &crate::Buffer,
48    a_data: &Buffer,
49    compression_codec: Option<CompressionCodec>,
50    decompression_context: &mut DecompressionContext,
51) -> Result<Buffer, ArrowError> {
52    let start_offset = buf.offset() as usize;
53    let buf_data = a_data
54        .try_slice_with_length(start_offset, buf.length() as usize)
55        .map_err(|e| ArrowError::IpcError(e.to_string()))?;
56    // corner case: empty buffer
57    match (buf_data.is_empty(), compression_codec) {
58        (true, _) | (_, None) => Ok(buf_data),
59        (false, Some(decompressor)) => {
60            decompressor.decompress_to_buffer(&buf_data, decompression_context)
61        }
62    }
63}
64
65impl RecordBatchDecoder<'_> {
66    /// Coordinates reading arrays based on data types.
67    ///
68    /// `variadic_counts` encodes the number of buffers to read for variadic types (e.g., Utf8View, BinaryView)
69    /// When encounter such types, we pop from the front of the queue to get the number of buffers to read.
70    ///
71    /// Notes:
72    /// * In the IPC format, null buffers are always set, but may be empty. We discard them if an array has 0 nulls
73    /// * Numeric values inside list arrays are often stored as 64-bit values regardless of their data type size.
74    ///   We thus:
75    ///     - check if the bit width of non-64-bit numbers is 64, and
76    ///     - read the buffer as 64-bit (signed integer or float), and
77    ///     - cast the 64-bit array to the appropriate data type
78    fn create_array(
79        &mut self,
80        field: &Field,
81        variadic_counts: &mut VecDeque<i64>,
82    ) -> Result<ArrayRef, ArrowError> {
83        let data_type = field.data_type();
84        match data_type {
85            Utf8 | Binary | LargeBinary | LargeUtf8 => {
86                let field_node = self.next_node(field)?;
87                let buffers = [
88                    self.next_buffer()?,
89                    self.next_buffer()?,
90                    self.next_buffer()?,
91                ];
92                self.create_primitive_array(field_node, data_type, &buffers)
93            }
94            BinaryView | Utf8View => {
95                let count = self.next_variadic_buffer_count(variadic_counts, data_type)?;
96                let buffers = (0..count)
97                    .map(|_| self.next_buffer())
98                    .collect::<Result<Vec<_>, _>>()?;
99                let field_node = self.next_node(field)?;
100                self.create_primitive_array(field_node, data_type, &buffers)
101            }
102            FixedSizeBinary(_) => {
103                let field_node = self.next_node(field)?;
104                let buffers = [self.next_buffer()?, self.next_buffer()?];
105                self.create_primitive_array(field_node, data_type, &buffers)
106            }
107            List(list_field) | LargeList(list_field) | Map(list_field, _) => {
108                let list_node = self.next_node(field)?;
109                let list_buffers = [self.next_buffer()?, self.next_buffer()?];
110                let values = self.create_array(list_field, variadic_counts)?;
111                self.create_list_array(list_node, data_type, &list_buffers, values)
112            }
113            ListView(list_field) | LargeListView(list_field) => {
114                let list_node = self.next_node(field)?;
115                let list_buffers = [
116                    self.next_buffer()?, // null buffer
117                    self.next_buffer()?, // offsets
118                    self.next_buffer()?, // sizes
119                ];
120                let values = self.create_array(list_field, variadic_counts)?;
121                self.create_list_view_array(list_node, data_type, &list_buffers, values)
122            }
123            FixedSizeList(list_field, _) => {
124                let list_node = self.next_node(field)?;
125                let list_buffers = [self.next_buffer()?];
126                let values = self.create_array(list_field, variadic_counts)?;
127                self.create_list_array(list_node, data_type, &list_buffers, values)
128            }
129            Struct(struct_fields) => {
130                let struct_node = self.next_node(field)?;
131                let null_buffer = self.next_buffer()?;
132
133                // read the arrays for each field
134                let mut struct_arrays = Vec::with_capacity(struct_fields.len());
135                // TODO investigate whether just knowing the number of buffers could
136                // still work
137                for struct_field in struct_fields {
138                    let child = self.create_array(struct_field, variadic_counts)?;
139                    struct_arrays.push(child);
140                }
141                self.create_struct_array(struct_node, null_buffer, struct_fields, struct_arrays)
142            }
143            RunEndEncoded(run_ends_field, values_field) => {
144                let run_node = self.next_node(field)?;
145                let run_ends = self.create_array(run_ends_field, variadic_counts)?;
146                let values = self.create_array(values_field, variadic_counts)?;
147
148                let run_array_length = run_node.length() as usize;
149                let builder = ArrayData::builder(data_type.clone())
150                    .len(run_array_length)
151                    .offset(0)
152                    .add_child_data(run_ends.into_data())
153                    .add_child_data(values.into_data())
154                    .null_count(run_node.null_count() as usize);
155
156                self.create_array_from_builder(builder)
157            }
158            // Create dictionary array from RecordBatch
159            Dictionary(_, _) => {
160                let index_node = self.next_node(field)?;
161                let index_buffers = [self.next_buffer()?, self.next_buffer()?];
162
163                #[expect(deprecated)]
164                let dict_id = field.dict_id().ok_or_else(|| {
165                    ArrowError::ParseError(format!("Field {field} does not have dict id"))
166                })?;
167
168                let value_array = match self.dictionaries_by_id.get(&dict_id) {
169                    Some(array) => array.clone(),
170                    None => {
171                        // Per the IPC spec, dictionary batches may be omitted when all
172                        // values in the column are null. In that case we synthesize an
173                        // empty values array so decoding can proceed.
174                        if let Dictionary(_, value_type) = data_type {
175                            arrow_array::new_empty_array(value_type.as_ref())
176                        } else {
177                            unreachable!()
178                        }
179                    }
180                };
181
182                self.create_dictionary_array(index_node, data_type, &index_buffers, value_array)
183            }
184            Union(fields, mode) => {
185                let union_node = self.next_node(field)?;
186                let len = union_node.length() as usize;
187
188                // In V4, union types has validity bitmap
189                // In V5 and later, union types have no validity bitmap
190                if self.version < MetadataVersion::V5 {
191                    self.next_buffer()?;
192                }
193
194                let type_ids: ScalarBuffer<i8> = self
195                    .next_buffer()?
196                    .try_slice_with_length(0, len)
197                    .map_err(|e| ArrowError::IpcError(e.to_string()))?
198                    .into();
199
200                let value_offsets = match mode {
201                    UnionMode::Dense => {
202                        let offsets: ScalarBuffer<i32> = self
203                            .next_buffer()?
204                            .try_slice_with_length(0, len * 4)
205                            .map_err(|e| ArrowError::IpcError(e.to_string()))?
206                            .into();
207                        Some(offsets)
208                    }
209                    UnionMode::Sparse => None,
210                };
211
212                let mut children = Vec::with_capacity(fields.len());
213
214                for (_id, field) in fields.iter() {
215                    let child = self.create_array(field, variadic_counts)?;
216                    children.push(child);
217                }
218
219                let array = if self.skip_validation.get() {
220                    // safety: flag can only be set via unsafe code
221                    unsafe {
222                        UnionArray::new_unchecked(fields.clone(), type_ids, value_offsets, children)
223                    }
224                } else {
225                    UnionArray::try_new(fields.clone(), type_ids, value_offsets, children)?
226                };
227                Ok(Arc::new(array))
228            }
229            Null => {
230                let node = self.next_node(field)?;
231                let length = node.length();
232                let null_count = node.null_count();
233
234                if length != null_count {
235                    return Err(ArrowError::SchemaError(format!(
236                        "Field {field} of NullArray has unequal null_count {null_count} and len {length}"
237                    )));
238                }
239
240                let builder = ArrayData::builder(data_type.clone())
241                    .len(length as usize)
242                    .offset(0);
243                self.create_array_from_builder(builder)
244            }
245            _ => {
246                let field_node = self.next_node(field)?;
247                let buffers = [self.next_buffer()?, self.next_buffer()?];
248                self.create_primitive_array(field_node, data_type, &buffers)
249            }
250        }
251    }
252
253    /// Reads the correct number of buffers based on data type and null_count, and creates a
254    /// primitive array ref
255    fn create_primitive_array(
256        &self,
257        field_node: &FieldNode,
258        data_type: &DataType,
259        buffers: &[Buffer],
260    ) -> Result<ArrayRef, ArrowError> {
261        let length = field_node.length() as usize;
262        let null_buffer = (field_node.null_count() > 0).then_some(buffers[0].clone());
263        let mut builder = match data_type {
264            Utf8 | Binary | LargeBinary | LargeUtf8 => {
265                // read 3 buffers: null buffer (optional), offsets buffer and data buffer
266                ArrayData::builder(data_type.clone())
267                    .len(length)
268                    .buffers(buffers[1..3].to_vec())
269                    .null_bit_buffer(null_buffer)
270            }
271            BinaryView | Utf8View => ArrayData::builder(data_type.clone())
272                .len(length)
273                .buffers(buffers[1..].to_vec())
274                .null_bit_buffer(null_buffer),
275            _ if data_type.is_primitive() || matches!(data_type, Boolean | FixedSizeBinary(_)) => {
276                // read 2 buffers: null buffer (optional) and data buffer
277                ArrayData::builder(data_type.clone())
278                    .len(length)
279                    .add_buffer(buffers[1].clone())
280                    .null_bit_buffer(null_buffer)
281            }
282            t => unreachable!("Data type {:?} either unsupported or not primitive", t),
283        };
284
285        builder = builder.null_count(field_node.null_count() as usize);
286
287        self.create_array_from_builder(builder)
288    }
289
290    /// Update the ArrayDataBuilder based on settings in this decoder
291    fn create_array_from_builder(&self, builder: ArrayDataBuilder) -> Result<ArrayRef, ArrowError> {
292        let mut builder = builder.align_buffers(!self.require_alignment);
293        if self.skip_validation.get() {
294            // SAFETY: flag can only be set via unsafe code
295            unsafe { builder = builder.skip_validation(true) }
296        }
297        Ok(make_array(builder.build()?))
298    }
299
300    /// Reads the correct number of buffers based on list type and null_count, and creates a
301    /// list array ref
302    fn create_list_array(
303        &self,
304        field_node: &FieldNode,
305        data_type: &DataType,
306        buffers: &[Buffer],
307        child_array: ArrayRef,
308    ) -> Result<ArrayRef, ArrowError> {
309        let null_buffer = (field_node.null_count() > 0).then_some(buffers[0].clone());
310        let length = field_node.length() as usize;
311        let child_data = child_array.into_data();
312        let mut builder = match data_type {
313            List(_) | LargeList(_) | Map(_, _) => ArrayData::builder(data_type.clone())
314                .len(length)
315                .add_buffer(buffers[1].clone())
316                .add_child_data(child_data)
317                .null_bit_buffer(null_buffer),
318
319            FixedSizeList(_, _) => ArrayData::builder(data_type.clone())
320                .len(length)
321                .add_child_data(child_data)
322                .null_bit_buffer(null_buffer),
323
324            _ => unreachable!("Cannot create list or map array from {:?}", data_type),
325        };
326
327        builder = builder.null_count(field_node.null_count() as usize);
328
329        self.create_array_from_builder(builder)
330    }
331
332    fn create_list_view_array(
333        &self,
334        field_node: &FieldNode,
335        data_type: &DataType,
336        buffers: &[Buffer],
337        child_array: ArrayRef,
338    ) -> Result<ArrayRef, ArrowError> {
339        assert!(matches!(data_type, ListView(_) | LargeListView(_)));
340
341        let null_buffer = (field_node.null_count() > 0).then_some(buffers[0].clone());
342        let length = field_node.length() as usize;
343        let child_data = child_array.into_data();
344
345        self.create_array_from_builder(
346            ArrayData::builder(data_type.clone())
347                .len(length)
348                .add_buffer(buffers[1].clone()) // offsets
349                .add_buffer(buffers[2].clone()) // sizes
350                .add_child_data(child_data)
351                .null_bit_buffer(null_buffer)
352                .null_count(field_node.null_count() as usize),
353        )
354    }
355
356    fn create_struct_array(
357        &self,
358        struct_node: &FieldNode,
359        null_buffer: Buffer,
360        struct_fields: &Fields,
361        struct_arrays: Vec<ArrayRef>,
362    ) -> Result<ArrayRef, ArrowError> {
363        let null_count = struct_node.null_count() as usize;
364        let len = struct_node.length() as usize;
365        let skip_validation = self.skip_validation.get();
366
367        let nulls = if null_count > 0 {
368            if !skip_validation {
369                // Check before constructing the BooleanBuffer, which would otherwise panic.
370                let needed_len = len.div_ceil(8);
371                if null_buffer.len() < needed_len {
372                    return Err(ArrowError::InvalidArgumentError(format!(
373                        "null_bit_buffer size too small. got {} needed {}",
374                        null_buffer.len(),
375                        needed_len
376                    )));
377                }
378            }
379            let validity_buffer = BooleanBuffer::new(null_buffer, 0, len);
380            let null_buffer = if skip_validation {
381                // safety: flag can only be set via unsafe code
382                unsafe { NullBuffer::new_unchecked(validity_buffer, null_count) }
383            } else {
384                let null_buffer = NullBuffer::new(validity_buffer);
385
386                if null_buffer.null_count() != null_count {
387                    return Err(ArrowError::InvalidArgumentError(format!(
388                        "null_count value ({}) doesn't match actual number of nulls in array ({})",
389                        null_count,
390                        null_buffer.null_count()
391                    )));
392                }
393
394                null_buffer
395            };
396
397            Some(null_buffer)
398        } else {
399            None
400        };
401        if struct_arrays.is_empty() {
402            // `StructArray::from` can't infer the correct row count
403            // if we have zero fields
404            return Ok(Arc::new(StructArray::new_empty_fields(len, nulls)));
405        }
406
407        let struct_array = if skip_validation {
408            // safety: flag can only be set via unsafe code
409            unsafe { StructArray::new_unchecked(struct_fields.clone(), struct_arrays, nulls) }
410        } else {
411            StructArray::try_new(struct_fields.clone(), struct_arrays, nulls)?
412        };
413
414        Ok(Arc::new(struct_array))
415    }
416
417    /// Reads the correct number of buffers based on list type and null_count, and creates a
418    /// list array ref
419    fn create_dictionary_array(
420        &self,
421        field_node: &FieldNode,
422        data_type: &DataType,
423        buffers: &[Buffer],
424        value_array: ArrayRef,
425    ) -> Result<ArrayRef, ArrowError> {
426        if let Dictionary(_, _) = *data_type {
427            let null_buffer = (field_node.null_count() > 0).then_some(buffers[0].clone());
428            let builder = ArrayData::builder(data_type.clone())
429                .len(field_node.length() as usize)
430                .add_buffer(buffers[1].clone())
431                .add_child_data(value_array.into_data())
432                .null_bit_buffer(null_buffer)
433                .null_count(field_node.null_count() as usize);
434            self.create_array_from_builder(builder)
435        } else {
436            unreachable!("Cannot create dictionary array from {:?}", data_type)
437        }
438    }
439}
440
441/// State for decoding Arrow arrays from an [IPC RecordBatch] structure to
442/// [`RecordBatch`]
443///
444/// [IPC RecordBatch]: crate::RecordBatch
445///
446pub struct RecordBatchDecoder<'a> {
447    /// The flatbuffers encoded record batch
448    batch: crate::RecordBatch<'a>,
449    /// The output schema
450    schema: SchemaRef,
451    /// Decoded dictionaries indexed by dictionary id
452    dictionaries_by_id: &'a HashMap<i64, ArrayRef>,
453    /// Optional compression codec
454    compression: Option<CompressionCodec>,
455    /// Decompression context for reusing zstd decompressor state
456    decompression_context: DecompressionContext,
457    /// The format version
458    version: MetadataVersion,
459    /// The raw data buffer
460    data: &'a Buffer,
461    /// The fields comprising this array
462    nodes: VectorIter<'a, FieldNode>,
463    /// The buffers comprising this array
464    buffers: VectorIter<'a, crate::Buffer>,
465    /// Projection (subset of columns) to read, if any
466    /// See [`RecordBatchDecoder::with_projection`] for details
467    projection: Option<&'a [usize]>,
468    /// Are buffers required to already be aligned? See
469    /// [`RecordBatchDecoder::with_require_alignment`] for details
470    require_alignment: bool,
471    /// Should validation be skipped when reading data? Defaults to false.
472    ///
473    /// See [`FileDecoder::with_skip_validation`] for details.
474    skip_validation: UnsafeFlag,
475}
476
477impl<'a> RecordBatchDecoder<'a> {
478    /// Create a reader for decoding arrays from an encoded [`RecordBatch`]
479    pub fn try_new(
480        buf: &'a Buffer,
481        batch: crate::RecordBatch<'a>,
482        schema: SchemaRef,
483        dictionaries_by_id: &'a HashMap<i64, ArrayRef>,
484        metadata: &'a MetadataVersion,
485    ) -> Result<Self, ArrowError> {
486        let buffers = batch.buffers().ok_or_else(|| {
487            ArrowError::IpcError("Unable to get buffers from IPC RecordBatch".to_string())
488        })?;
489        let field_nodes = batch.nodes().ok_or_else(|| {
490            ArrowError::IpcError("Unable to get field nodes from IPC RecordBatch".to_string())
491        })?;
492
493        let batch_compression = batch.compression();
494        let compression = batch_compression
495            .map(|batch_compression| batch_compression.codec().try_into())
496            .transpose()?;
497
498        Ok(Self {
499            batch,
500            schema,
501            dictionaries_by_id,
502            compression,
503            decompression_context: DecompressionContext::new(),
504            version: *metadata,
505            data: buf,
506            nodes: field_nodes.iter(),
507            buffers: buffers.iter(),
508            projection: None,
509            require_alignment: false,
510            skip_validation: UnsafeFlag::new(),
511        })
512    }
513
514    /// Set the projection (default: None)
515    ///
516    /// If set, the projection is the list  of column indices
517    /// that will be read
518    pub fn with_projection(mut self, projection: Option<&'a [usize]>) -> Self {
519        self.projection = projection;
520        self
521    }
522
523    /// Set require_alignment (default: false)
524    ///
525    /// If true, buffers must be aligned appropriately or error will
526    /// result. If false, buffers will be copied to aligned buffers
527    /// if necessary.
528    pub fn with_require_alignment(mut self, require_alignment: bool) -> Self {
529        self.require_alignment = require_alignment;
530        self
531    }
532
533    /// Specifies if validation should be skipped when reading data (defaults to `false`)
534    ///
535    /// When enabled, the following checks are bypassed:
536    /// - Offset bounds (e.g. list/string offsets pointing past the end of their value buffer)
537    /// - UTF-8 validity of string columns (`Utf8` / `LargeUtf8`)
538    /// - Null count consistency and buffer length checks
539    ///
540    /// # Undefined behavior
541    ///
542    /// Relies on the caller only passing a flag with `true` value if they are
543    /// certain that the data is valid. Invalid data that bypasses these checks
544    /// may cause undefined behavior when the arrays are later accessed.
545    pub fn with_skip_validation(mut self, skip_validation: UnsafeFlag) -> Self {
546        self.skip_validation = skip_validation;
547        self
548    }
549
550    /// Read the record batch, consuming the reader
551    ///
552    /// # Errors
553    ///
554    /// Returns an error if the message does not describe a batch matching the schema,
555    /// for example if it declares more variadic buffer counts than the schema uses.
556    pub fn read_record_batch(mut self) -> Result<RecordBatch, ArrowError> {
557        let mut variadic_counts: VecDeque<i64> = self
558            .batch
559            .variadicBufferCounts()
560            .into_iter()
561            .flatten()
562            .collect();
563
564        let options = RecordBatchOptions::new().with_row_count(Some(self.batch.length() as usize));
565
566        let schema = Arc::clone(&self.schema);
567        if let Some(projection) = self.projection {
568            let mut arrays = Vec::with_capacity(projection.len());
569            // project fields
570            for (idx, field) in schema.fields().iter().enumerate() {
571                // A projected field can appear more than once, so collect all matching positions.
572                let mut decoded = None;
573                for (proj_idx, projected_idx) in projection.iter().enumerate() {
574                    if *projected_idx == idx {
575                        // Reuse the decoded array for duplicate projection entries.
576                        let child = match decoded.clone() {
577                            Some(child) => child,
578                            None => {
579                                let child = self.create_array(field, &mut variadic_counts)?;
580                                decoded = Some(Arc::clone(&child));
581                                child
582                            }
583                        };
584                        arrays.push((proj_idx, child));
585                    }
586                }
587
588                if decoded.is_none() {
589                    self.skip_field(field, &mut variadic_counts)?;
590                }
591            }
592
593            arrays.sort_by_key(|t| t.0);
594
595            let schema = Arc::new(schema.project(projection)?);
596            let columns = arrays.into_iter().map(|t| t.1).collect::<Vec<_>>();
597
598            if self.skip_validation.get() {
599                // Safety: setting `skip_validation` requires `unsafe`, user assures data is valid
600                unsafe {
601                    Ok(RecordBatch::new_unchecked(
602                        schema,
603                        columns,
604                        self.batch.length() as usize,
605                    ))
606                }
607            } else {
608                check_variadic_counts_consumed(&variadic_counts)?;
609                RecordBatch::try_new_with_options(schema, columns, &options)
610            }
611        } else {
612            let mut children = Vec::with_capacity(schema.fields().len());
613            // keep track of index as lists require more than one node
614            for field in schema.fields() {
615                let child = self.create_array(field, &mut variadic_counts)?;
616                children.push(child);
617            }
618
619            if self.skip_validation.get() {
620                // Safety: setting `skip_validation` requires `unsafe`, user assures data is valid
621                unsafe {
622                    Ok(RecordBatch::new_unchecked(
623                        schema,
624                        children,
625                        self.batch.length() as usize,
626                    ))
627                }
628            } else {
629                check_variadic_counts_consumed(&variadic_counts)?;
630                RecordBatch::try_new_with_options(schema, children, &options)
631            }
632        }
633    }
634
635    fn next_buffer(&mut self) -> Result<Buffer, ArrowError> {
636        let buffer = self.buffers.next().ok_or_else(|| {
637            ArrowError::IpcError("Buffer count mismatched with metadata".to_string())
638        })?;
639        read_buffer(
640            buffer,
641            self.data,
642            self.compression,
643            &mut self.decompression_context,
644        )
645    }
646
647    fn skip_buffer(&mut self) -> Result<(), ArrowError> {
648        self.buffers.next().ok_or_else(|| {
649            ArrowError::IpcError("Buffer count mismatched with metadata".to_string())
650        })?;
651        Ok(())
652    }
653
654    fn next_node(&mut self, field: &Field) -> Result<&'a FieldNode, ArrowError> {
655        self.nodes.next().ok_or_else(|| {
656            ArrowError::SchemaError(format!(
657                "Invalid data for schema. {field} refers to node not found in schema",
658            ))
659        })
660    }
661
662    fn skip_field(
663        &mut self,
664        field: &Field,
665        variadic_count: &mut VecDeque<i64>,
666    ) -> Result<(), ArrowError> {
667        self.next_node(field)?;
668
669        match field.data_type() {
670            Utf8 | Binary | LargeBinary | LargeUtf8 => {
671                for _ in 0..3 {
672                    self.skip_buffer()?;
673                }
674            }
675            Utf8View | BinaryView => {
676                let count = self.next_variadic_buffer_count(variadic_count, field.data_type())?;
677                for _ in 0..count {
678                    self.skip_buffer()?;
679                }
680            }
681            FixedSizeBinary(_) => {
682                self.skip_buffer()?;
683                self.skip_buffer()?;
684            }
685            List(list_field) | LargeList(list_field) | Map(list_field, _) => {
686                self.skip_buffer()?;
687                self.skip_buffer()?;
688                self.skip_field(list_field, variadic_count)?;
689            }
690            ListView(list_field) | LargeListView(list_field) => {
691                self.skip_buffer()?; // Null buffer
692                self.skip_buffer()?; // Offsets
693                self.skip_buffer()?; // Sizes
694                self.skip_field(list_field, variadic_count)?;
695            }
696            FixedSizeList(list_field, _) => {
697                self.skip_buffer()?;
698                self.skip_field(list_field, variadic_count)?;
699            }
700            Struct(struct_fields) => {
701                self.skip_buffer()?;
702
703                // skip for each field
704                for struct_field in struct_fields {
705                    self.skip_field(struct_field, variadic_count)?
706                }
707            }
708            RunEndEncoded(run_ends_field, values_field) => {
709                self.skip_field(run_ends_field, variadic_count)?;
710                self.skip_field(values_field, variadic_count)?;
711            }
712            Dictionary(_, _) => {
713                self.skip_buffer()?; // Nulls
714                self.skip_buffer()?; // Indices
715            }
716            Union(fields, mode) => {
717                if self.version < MetadataVersion::V5 {
718                    self.skip_buffer()?; // Null buffer
719                }
720                self.skip_buffer()?; // Type ids
721
722                match mode {
723                    UnionMode::Dense => self.skip_buffer()?, // Offsets
724                    UnionMode::Sparse => {}
725                }
726
727                for (_, field) in fields.iter() {
728                    self.skip_field(field, variadic_count)?
729                }
730            }
731            // Null has no buffers to skip
732            Null => {}
733
734            // Fixed-width and boolean types: skip null buffer + values buffer
735            Boolean
736            | Int8
737            | Int16
738            | Int32
739            | Int64
740            | UInt8
741            | UInt16
742            | UInt32
743            | UInt64
744            | Float16
745            | Float32
746            | Float64
747            | Timestamp(_, _)
748            | Date32
749            | Date64
750            | Time32(_)
751            | Time64(_)
752            | Duration(_)
753            | Interval(_)
754            | Decimal32(_, _)
755            | Decimal64(_, _)
756            | Decimal128(_, _)
757            | Decimal256(_, _) => {
758                self.skip_buffer()?;
759                self.skip_buffer()?;
760            }
761        }
762        Ok(())
763    }
764}
765
766impl RecordBatchDecoder<'_> {
767    /// Takes the number of variadic buffers declared for one `BinaryView` or `Utf8View`
768    /// column, and returns the total number of buffers to read for it.
769    ///
770    /// The count comes from the IPC message, so it may be missing, negative, or larger
771    /// than the number of buffers the message actually has.
772    fn next_variadic_buffer_count(
773        &self,
774        variadic_counts: &mut VecDeque<i64>,
775        data_type: &DataType,
776    ) -> Result<usize, ArrowError> {
777        let count = variadic_counts.pop_front().ok_or_else(|| {
778            ArrowError::IpcError(format!("Missing variadic count for {data_type} column"))
779        })?;
780
781        let remaining = self.buffers.len();
782
783        // The view buffer and the null buffer are not counted as variadic.
784        usize::try_from(count)
785            .ok()
786            .and_then(|count| count.checked_add(2))
787            .filter(|total| *total <= remaining)
788            .ok_or_else(|| {
789                ArrowError::IpcError(format!(
790                    "Invalid variadic count {count} for {data_type} column, \
791                     with {remaining} buffer(s) left in the message"
792                ))
793            })
794    }
795}
796
797/// Creates a record batch from binary data using the `crate::RecordBatch` indexes and the `Schema`.
798///
799/// If `require_alignment` is true, this function will return an error if any array data in the
800/// input `buf` is not properly aligned.
801/// Under the hood it will use [`arrow_data::ArrayDataBuilder::build`] to construct [`arrow_data::ArrayData`].
802///
803/// If `require_alignment` is false, this function will automatically allocate a new aligned buffer
804/// and copy over the data if any array data in the input `buf` is not properly aligned.
805/// (Properly aligned array data will remain zero-copy.)
806/// Under the hood it will use [`arrow_data::ArrayDataBuilder::align_buffers`] to construct [`arrow_data::ArrayData`].
807pub fn read_record_batch(
808    buf: &Buffer,
809    batch: crate::RecordBatch,
810    schema: SchemaRef,
811    dictionaries_by_id: &HashMap<i64, ArrayRef>,
812    projection: Option<&[usize]>,
813    metadata: &MetadataVersion,
814) -> Result<RecordBatch, ArrowError> {
815    RecordBatchDecoder::try_new(buf, batch, schema, dictionaries_by_id, metadata)?
816        .with_projection(projection)
817        .with_require_alignment(false)
818        .read_record_batch()
819}
820
821/// Read the dictionary from the buffer and provided metadata,
822/// updating the `dictionaries_by_id` with the resulting dictionary
823pub fn read_dictionary(
824    buf: &Buffer,
825    batch: crate::DictionaryBatch,
826    schema: &Schema,
827    dictionaries_by_id: &mut HashMap<i64, ArrayRef>,
828    metadata: &MetadataVersion,
829) -> Result<(), ArrowError> {
830    read_dictionary_impl(
831        buf,
832        batch,
833        schema,
834        dictionaries_by_id,
835        metadata,
836        false,
837        UnsafeFlag::new(),
838    )
839}
840
841/// Low-level version of [`read_dictionary`] with alignment and validation controls
842pub fn read_dictionary_impl(
843    buf: &Buffer,
844    batch: crate::DictionaryBatch,
845    schema: &Schema,
846    dictionaries_by_id: &mut HashMap<i64, ArrayRef>,
847    metadata: &MetadataVersion,
848    require_alignment: bool,
849    skip_validation: UnsafeFlag,
850) -> Result<(), ArrowError> {
851    let id = batch.id();
852
853    let dictionary_values = get_dictionary_values(
854        buf,
855        batch,
856        schema,
857        dictionaries_by_id,
858        metadata,
859        require_alignment,
860        skip_validation,
861    )?;
862
863    update_dictionaries(dictionaries_by_id, batch.isDelta(), id, dictionary_values)?;
864
865    Ok(())
866}
867
868/// Updates the `dictionaries_by_id` with the provided dictionary values and id.
869///
870/// # Errors
871/// - If `is_delta` is true and there is no existing dictionary for the given
872///   `dict_id`
873/// - If `is_delta` is true and the concatenation of the existing and new
874///   dictionary fails. This usually signals a type mismatch between the old and
875///   new values.
876fn update_dictionaries(
877    dictionaries_by_id: &mut HashMap<i64, ArrayRef>,
878    is_delta: bool,
879    dict_id: i64,
880    dict_values: ArrayRef,
881) -> Result<(), ArrowError> {
882    if !is_delta {
883        // We don't currently record the isOrdered field. This could be general
884        // attributes of arrays.
885        // Add (possibly multiple) array refs to the dictionaries array.
886        dictionaries_by_id.insert(dict_id, dict_values.clone());
887        return Ok(());
888    }
889
890    let existing = dictionaries_by_id.get(&dict_id).ok_or_else(|| {
891        ArrowError::InvalidArgumentError(format!(
892            "No existing dictionary for delta dictionary with id '{dict_id}'"
893        ))
894    })?;
895
896    let combined = concat::concat(&[existing, &dict_values]).map_err(|e| {
897        ArrowError::InvalidArgumentError(format!("Failed to concat delta dictionary: {e}"))
898    })?;
899
900    dictionaries_by_id.insert(dict_id, combined);
901
902    Ok(())
903}
904
905/// Returns the first field with dictionary `id`, in [`Schema::fields_with_dict_id`] order.
906///
907/// Unlike that method this stops at the first match without collecting every nested field,
908/// which is significant when a stream has many dictionary batches.
909fn first_field_with_dict_id(fields: &Fields, id: i64) -> Option<&Field> {
910    fields
911        .iter()
912        .find_map(|field| field_with_dict_id(field, id))
913}
914
915fn field_with_dict_id(field: &Field, id: i64) -> Option<&Field> {
916    #[expect(deprecated)]
917    let dict_id = field.dict_id();
918    if dict_id == Some(id) {
919        return Some(field);
920    }
921    child_field_with_dict_id(field.data_type(), id)
922}
923
924fn child_field_with_dict_id(data_type: &DataType, id: i64) -> Option<&Field> {
925    match data_type {
926        DataType::Struct(fields) => first_field_with_dict_id(fields, id),
927        DataType::Union(fields, _) => fields.iter().find_map(|(_, f)| field_with_dict_id(f, id)),
928        DataType::List(field)
929        | DataType::LargeList(field)
930        | DataType::ListView(field)
931        | DataType::LargeListView(field)
932        | DataType::FixedSizeList(field, _)
933        | DataType::Map(field, _)
934        | DataType::RunEndEncoded(_, field) => field_with_dict_id(field, id),
935        DataType::Dictionary(_, value_type) => child_field_with_dict_id(value_type, id),
936        _ => None,
937    }
938}
939
940/// Given a dictionary batch IPC message/body along with the full state of a
941/// stream including schema, dictionary cache, metadata, and other flags, this
942/// function will parse the buffer into an array of dictionary values.
943fn get_dictionary_values(
944    buf: &Buffer,
945    batch: crate::DictionaryBatch,
946    schema: &Schema,
947    dictionaries_by_id: &HashMap<i64, ArrayRef>,
948    metadata: &MetadataVersion,
949    require_alignment: bool,
950    skip_validation: UnsafeFlag,
951) -> Result<ArrayRef, ArrowError> {
952    let id = batch.id();
953    let first_field = first_field_with_dict_id(schema.fields(), id).ok_or_else(|| {
954        ArrowError::InvalidArgumentError(format!("dictionary id {id} not found in schema"))
955    })?;
956
957    // As the dictionary batch does not contain the type of the
958    // values array, we need to retrieve this from the schema.
959    // Get an array representing this dictionary's values.
960    let dictionary_values: ArrayRef = match first_field.data_type() {
961        DataType::Dictionary(_, value_type) => {
962            // Make a fake schema for the dictionary batch.
963            let value = value_type.as_ref().clone();
964            let schema = Schema::new(vec![Field::new("", value, true)]);
965            // Read a single column
966            let Some(data) = batch.data() else {
967                return Err(ArrowError::ParseError(
968                    "Dictionary batch is missing its data".to_string(),
969                ));
970            };
971            let record_batch = RecordBatchDecoder::try_new(
972                buf,
973                data,
974                Arc::new(schema),
975                dictionaries_by_id,
976                metadata,
977            )?
978            .with_require_alignment(require_alignment)
979            .with_skip_validation(skip_validation)
980            .read_record_batch()?;
981
982            Some(record_batch.column(0).clone())
983        }
984        _ => None,
985    }
986    .ok_or_else(|| {
987        ArrowError::InvalidArgumentError(format!("dictionary id {id} not found in schema"))
988    })?;
989
990    Ok(dictionary_values)
991}
992
993/// Read the data for a given block
994fn read_block<R: Read + Seek>(mut reader: R, block: &Block) -> Result<Buffer, ArrowError> {
995    reader.seek(SeekFrom::Start(block.offset() as u64))?;
996    let body_len = block.bodyLength().to_usize().unwrap();
997    let metadata_len = block.metaDataLength().to_usize().unwrap();
998    let total_len = body_len.checked_add(metadata_len).unwrap();
999
1000    let mut buf = MutableBuffer::try_from_len_zeroed(total_len)
1001        .map_err(|e| ArrowError::MemoryError(e.to_string()))?;
1002    reader.read_exact(&mut buf)?;
1003    Ok(buf.into())
1004}
1005
1006/// One variadic buffer count is consumed per `BinaryView` or `Utf8View` column in
1007/// the schema, so any count left over means the message and the schema disagree.
1008///
1009/// The opposite case, too few counts, is reported by [`RecordBatchDecoder::create_array`].
1010fn check_variadic_counts_consumed(variadic_counts: &VecDeque<i64>) -> Result<(), ArrowError> {
1011    if variadic_counts.is_empty() {
1012        Ok(())
1013    } else {
1014        Err(ArrowError::IpcError(format!(
1015            "Mismatch between schema and data: the IPC message declares {} more variadic \
1016             buffer count(s) than the schema has BinaryView or Utf8View columns",
1017            variadic_counts.len()
1018        )))
1019    }
1020}
1021
1022/// Parse an encapsulated message
1023///
1024/// <https://arrow.apache.org/docs/format/Columnar.html#encapsulated-message-format>
1025fn parse_message(buf: &[u8]) -> Result<Message::Message<'_>, ArrowError> {
1026    let buf = match buf[..4] == CONTINUATION_MARKER {
1027        true => &buf[8..],
1028        false => &buf[4..],
1029    };
1030    crate::root_as_message(buf)
1031        .map_err(|err| ArrowError::ParseError(format!("Unable to get root as message: {err:?}")))
1032}
1033
1034/// Read the footer length from the last 10 bytes of an Arrow IPC file
1035///
1036/// Expects a 4 byte footer length followed by `b"ARROW1"`
1037pub fn read_footer_length(buf: [u8; 10]) -> Result<usize, ArrowError> {
1038    if buf[4..] != super::ARROW_MAGIC {
1039        return Err(ArrowError::ParseError(
1040            "Arrow file does not contain correct footer".to_string(),
1041        ));
1042    }
1043
1044    // read footer length
1045    let footer_len = i32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
1046    footer_len
1047        .try_into()
1048        .map_err(|_| ArrowError::ParseError(format!("Invalid footer length: {footer_len}")))
1049}
1050
1051/// A low-level, push-based interface for reading an IPC file
1052///
1053/// For a higher-level interface see [`FileReader`]
1054///
1055/// For an example of using this API with `mmap` see the [`zero_copy_ipc`] example.
1056///
1057/// [`zero_copy_ipc`]: https://github.com/apache/arrow-rs/blob/main/arrow/examples/zero_copy_ipc.rs
1058///
1059/// ```
1060/// # use std::sync::Arc;
1061/// # use arrow_array::*;
1062/// # use arrow_array::types::Int32Type;
1063/// # use arrow_buffer::Buffer;
1064/// # use arrow_ipc::convert::try_fb_to_schema;
1065/// # use arrow_ipc::reader::{FileDecoder, read_footer_length};
1066/// # use arrow_ipc::root_as_footer;
1067/// # use arrow_ipc::writer::FileWriter;
1068/// // Write an IPC file
1069///
1070/// let batch = RecordBatch::try_from_iter([
1071///     ("a", Arc::new(Int32Array::from(vec![1, 2, 3])) as _),
1072///     ("b", Arc::new(Int32Array::from(vec![1, 2, 3])) as _),
1073///     ("c", Arc::new(DictionaryArray::<Int32Type>::from_iter(["hello", "hello", "world"])) as _),
1074/// ]).unwrap();
1075///
1076/// let schema = batch.schema();
1077///
1078/// let mut out = Vec::with_capacity(1024);
1079/// let mut writer = FileWriter::try_new(&mut out, schema.as_ref()).unwrap();
1080/// writer.write(&batch).unwrap();
1081/// writer.finish().unwrap();
1082///
1083/// drop(writer);
1084///
1085/// // Read IPC file
1086///
1087/// let buffer = Buffer::from_vec(out);
1088/// let trailer_start = buffer.len() - 10;
1089/// let footer_len = read_footer_length(buffer[trailer_start..].try_into().unwrap()).unwrap();
1090/// let footer = root_as_footer(&buffer[trailer_start - footer_len..trailer_start]).unwrap();
1091///
1092/// let back = try_fb_to_schema(footer.schema().unwrap()).unwrap();
1093/// assert_eq!(&back, schema.as_ref());
1094///
1095/// let mut decoder = FileDecoder::new(schema, footer.version());
1096///
1097/// // Read dictionaries
1098/// for block in footer.dictionaries().iter().flatten() {
1099///     let block_len = block.bodyLength() as usize + block.metaDataLength() as usize;
1100///     let data = buffer.slice_with_length(block.offset() as _, block_len);
1101///     decoder.read_dictionary(&block, &data).unwrap();
1102/// }
1103///
1104/// // Read record batch
1105/// let batches = footer.recordBatches().unwrap();
1106/// assert_eq!(batches.len(), 1); // Only wrote a single batch
1107///
1108/// let block = batches.get(0);
1109/// let block_len = block.bodyLength() as usize + block.metaDataLength() as usize;
1110/// let data = buffer.slice_with_length(block.offset() as _, block_len);
1111/// let back = decoder.read_record_batch(block, &data).unwrap().unwrap();
1112///
1113/// assert_eq!(batch, back);
1114/// ```
1115#[derive(Debug)]
1116pub struct FileDecoder {
1117    schema: SchemaRef,
1118    dictionaries: HashMap<i64, ArrayRef>,
1119    version: MetadataVersion,
1120    projection: Option<Vec<usize>>,
1121    require_alignment: bool,
1122    skip_validation: UnsafeFlag,
1123}
1124
1125impl FileDecoder {
1126    /// Create a new [`FileDecoder`] with the given schema and version
1127    pub fn new(schema: SchemaRef, version: MetadataVersion) -> Self {
1128        Self {
1129            schema,
1130            version,
1131            dictionaries: Default::default(),
1132            projection: None,
1133            require_alignment: false,
1134            skip_validation: UnsafeFlag::new(),
1135        }
1136    }
1137
1138    /// Specify a projection
1139    pub fn with_projection(mut self, projection: Vec<usize>) -> Self {
1140        self.projection = Some(projection);
1141        self
1142    }
1143
1144    /// Specifies if the array data in input buffers is required to be properly aligned.
1145    ///
1146    /// If `require_alignment` is true, this decoder will return an error if any array data in the
1147    /// input `buf` is not properly aligned.
1148    /// Under the hood it will use [`arrow_data::ArrayDataBuilder::build`] to construct
1149    /// [`arrow_data::ArrayData`].
1150    ///
1151    /// If `require_alignment` is false (the default), this decoder will automatically allocate a
1152    /// new aligned buffer and copy over the data if any array data in the input `buf` is not
1153    /// properly aligned. (Properly aligned array data will remain zero-copy.)
1154    /// Under the hood it will use [`arrow_data::ArrayDataBuilder::align_buffers`] to construct
1155    /// [`arrow_data::ArrayData`].
1156    pub fn with_require_alignment(mut self, require_alignment: bool) -> Self {
1157        self.require_alignment = require_alignment;
1158        self
1159    }
1160
1161    /// Specifies if validation should be skipped when reading data (defaults to `false`)
1162    ///
1163    /// # Safety
1164    ///
1165    /// This flag must only be set to `true` when you trust the input data and are sure the data you are
1166    /// reading is a valid Arrow IPC file, otherwise undefined behavior may
1167    /// result.
1168    ///
1169    /// For example, some programs may wish to trust reading IPC files written
1170    /// by the same process that created the files.
1171    pub unsafe fn with_skip_validation(mut self, skip_validation: bool) -> Self {
1172        unsafe { self.skip_validation.set(skip_validation) };
1173        self
1174    }
1175
1176    fn read_message<'a>(&self, buf: &'a [u8]) -> Result<Message::Message<'a>, ArrowError> {
1177        let message = parse_message(buf)?;
1178
1179        // some old test data's footer metadata is not set, so we account for that
1180        if self.version != MetadataVersion::V1 && message.version() != self.version {
1181            return Err(ArrowError::IpcError(
1182                "Could not read IPC message as metadata versions mismatch".to_string(),
1183            ));
1184        }
1185        Ok(message)
1186    }
1187
1188    /// Read the dictionary with the given block and data buffer
1189    pub fn read_dictionary(&mut self, block: &Block, buf: &Buffer) -> Result<(), ArrowError> {
1190        let message = self.read_message(buf)?;
1191        match message.header_type() {
1192            crate::MessageHeader::DictionaryBatch => {
1193                let batch = message.header_as_dictionary_batch().unwrap();
1194                read_dictionary_impl(
1195                    &buf.slice(block.metaDataLength() as _),
1196                    batch,
1197                    &self.schema,
1198                    &mut self.dictionaries,
1199                    &message.version(),
1200                    self.require_alignment,
1201                    self.skip_validation.clone(),
1202                )
1203            }
1204            t => Err(ArrowError::ParseError(format!(
1205                "Expecting DictionaryBatch in dictionary blocks, found {t:?}."
1206            ))),
1207        }
1208    }
1209
1210    /// Read the RecordBatch with the given block and data buffer
1211    pub fn read_record_batch(
1212        &self,
1213        block: &Block,
1214        buf: &Buffer,
1215    ) -> Result<Option<RecordBatch>, ArrowError> {
1216        let message = self.read_message(buf)?;
1217        match message.header_type() {
1218            crate::MessageHeader::Schema => Err(ArrowError::IpcError(
1219                "Not expecting a schema when messages are read".to_string(),
1220            )),
1221            crate::MessageHeader::RecordBatch => {
1222                let batch = message.header_as_record_batch().ok_or_else(|| {
1223                    ArrowError::IpcError("Unable to read IPC message as record batch".to_string())
1224                })?;
1225                // read the block that makes up the record batch into a buffer
1226                RecordBatchDecoder::try_new(
1227                    &buf.slice(block.metaDataLength() as _),
1228                    batch,
1229                    self.schema.clone(),
1230                    &self.dictionaries,
1231                    &message.version(),
1232                )?
1233                .with_projection(self.projection.as_deref())
1234                .with_require_alignment(self.require_alignment)
1235                .with_skip_validation(self.skip_validation.clone())
1236                .read_record_batch()
1237                .map(Some)
1238            }
1239            crate::MessageHeader::NONE => Ok(None),
1240            t => Err(ArrowError::InvalidArgumentError(format!(
1241                "Reading types other than record batches not yet supported, unable to read {t:?}"
1242            ))),
1243        }
1244    }
1245}
1246
1247/// Build an Arrow [`FileReader`] with custom options.
1248#[derive(Debug)]
1249pub struct FileReaderBuilder {
1250    /// Optional projection for which columns to load (zero-based column indices)
1251    projection: Option<Vec<usize>>,
1252    /// Passed through to construct [`VerifierOptions`]
1253    max_footer_fb_tables: usize,
1254    /// Passed through to construct [`VerifierOptions`]
1255    max_footer_fb_depth: usize,
1256}
1257
1258impl Default for FileReaderBuilder {
1259    fn default() -> Self {
1260        let verifier_options = VerifierOptions::default();
1261        Self {
1262            max_footer_fb_tables: verifier_options.max_tables,
1263            max_footer_fb_depth: verifier_options.max_depth,
1264            projection: None,
1265        }
1266    }
1267}
1268
1269impl FileReaderBuilder {
1270    /// Options for creating a new [`FileReader`].
1271    ///
1272    /// To convert a builder into a reader, call [`FileReaderBuilder::build`].
1273    pub fn new() -> Self {
1274        Self::default()
1275    }
1276
1277    /// Optional projection for which columns to load (zero-based column indices).
1278    pub fn with_projection(mut self, projection: Vec<usize>) -> Self {
1279        self.projection = Some(projection);
1280        self
1281    }
1282
1283    /// Flatbuffers option for parsing the footer. Controls the max number of fields and
1284    /// metadata key-value pairs that can be parsed from the schema of the footer.
1285    ///
1286    /// By default this is set to `1_000_000` which roughly translates to a schema with
1287    /// no metadata key-value pairs but 499,999 fields.
1288    ///
1289    /// This default limit is enforced to protect against malicious files with a massive
1290    /// amount of flatbuffer tables which could cause a denial of service attack.
1291    ///
1292    /// If you need to ingest a trusted file with a massive number of fields and/or
1293    /// metadata key-value pairs and are facing the error `"Unable to get root as
1294    /// footer: TooManyTables"` then increase this parameter as necessary.
1295    pub fn with_max_footer_fb_tables(mut self, max_footer_fb_tables: usize) -> Self {
1296        self.max_footer_fb_tables = max_footer_fb_tables;
1297        self
1298    }
1299
1300    /// Flatbuffers option for parsing the footer. Controls the max depth for schemas with
1301    /// nested fields parsed from the footer.
1302    ///
1303    /// By default this is set to `64` which roughly translates to a schema with
1304    /// a field nested 60 levels down through other struct fields.
1305    ///
1306    /// This default limit is enforced to protect against malicious files with a extremely
1307    /// deep flatbuffer structure which could cause a denial of service attack.
1308    ///
1309    /// If you need to ingest a trusted file with a deeply nested field and are facing the
1310    /// error `"Unable to get root as footer: DepthLimitReached"` then increase this
1311    /// parameter as necessary.
1312    pub fn with_max_footer_fb_depth(mut self, max_footer_fb_depth: usize) -> Self {
1313        self.max_footer_fb_depth = max_footer_fb_depth;
1314        self
1315    }
1316
1317    /// Build [`FileReader`] with given reader.
1318    pub fn build<R: Read + Seek>(self, mut reader: R) -> Result<FileReader<R>, ArrowError> {
1319        // Space for ARROW_MAGIC (6 bytes) and length (4 bytes)
1320        let mut buffer = [0; 10];
1321        reader.seek(SeekFrom::End(-10))?;
1322        reader.read_exact(&mut buffer)?;
1323
1324        let footer_len = read_footer_length(buffer)?;
1325
1326        // read footer
1327        let mut footer_data = vec![0; footer_len];
1328        reader.seek(SeekFrom::End(-10 - footer_len as i64))?;
1329        reader.read_exact(&mut footer_data)?;
1330
1331        let verifier_options = VerifierOptions {
1332            max_tables: self.max_footer_fb_tables,
1333            max_depth: self.max_footer_fb_depth,
1334            ..Default::default()
1335        };
1336        let footer = crate::root_as_footer_with_opts(&verifier_options, &footer_data[..]).map_err(
1337            |err| ArrowError::ParseError(format!("Unable to get root as footer: {err:?}")),
1338        )?;
1339
1340        let blocks = footer.recordBatches().ok_or_else(|| {
1341            ArrowError::ParseError("Unable to get record batches from IPC Footer".to_string())
1342        })?;
1343
1344        let total_blocks = blocks.len();
1345
1346        let ipc_schema = footer.schema().ok_or_else(|| {
1347            ArrowError::ParseError("Unable to get schema from IPC Footer".to_string())
1348        })?;
1349        if !ipc_schema.endianness().equals_to_target_endianness() {
1350            return Err(ArrowError::IpcError(
1351                "the endianness of the source system does not match the endianness of the target system.".to_owned()
1352            ));
1353        }
1354
1355        let schema = Arc::new(crate::convert::try_fb_to_schema(ipc_schema)?);
1356
1357        let projected_schema = match &self.projection {
1358            Some(projection) => Arc::new(schema.project(projection)?),
1359            None => schema.clone(),
1360        };
1361
1362        let mut custom_metadata = HashMap::new();
1363        if let Some(fb_custom_metadata) = footer.custom_metadata() {
1364            for kv in fb_custom_metadata {
1365                let (Some(key), Some(value)) = (kv.key(), kv.value()) else {
1366                    return Err(ArrowError::ParseError(
1367                        "Custom metadata in the IPC footer is missing a key or a value".to_string(),
1368                    ));
1369                };
1370                custom_metadata.insert(key.to_string(), value.to_string());
1371            }
1372        }
1373
1374        let mut decoder = FileDecoder::new(schema, footer.version());
1375        if let Some(projection) = self.projection {
1376            decoder = decoder.with_projection(projection)
1377        }
1378
1379        // Create an array of optional dictionary value arrays, one per field.
1380        if let Some(dictionaries) = footer.dictionaries() {
1381            for block in dictionaries {
1382                let buf = read_block(&mut reader, block)?;
1383                decoder.read_dictionary(block, &buf)?;
1384            }
1385        }
1386
1387        Ok(FileReader {
1388            reader,
1389            blocks: blocks.iter().copied().collect(),
1390            current_block: 0,
1391            total_blocks,
1392            decoder,
1393            schema: projected_schema,
1394            custom_metadata,
1395        })
1396    }
1397}
1398
1399/// Arrow File Reader
1400///
1401/// Reads Arrow [`RecordBatch`]es from bytes in the [IPC File Format],
1402/// providing random access to the record batches.
1403///
1404/// # See Also
1405///
1406/// * [`Self::set_index`] for random access
1407/// * [`StreamReader`] for reading streaming data
1408///
1409/// # Example: Reading from a `File`
1410/// ```
1411/// # use std::io::Cursor;
1412/// use arrow_array::record_batch;
1413/// # use arrow_ipc::reader::FileReader;
1414/// # use arrow_ipc::writer::FileWriter;
1415/// # let batch = record_batch!(("a", Int32, [1, 2, 3])).unwrap();
1416/// # let mut file = vec![]; // mimic a stream for the example
1417/// # {
1418/// #  let mut writer = FileWriter::try_new(&mut file, &batch.schema()).unwrap();
1419/// #  writer.write(&batch).unwrap();
1420/// #  writer.write(&batch).unwrap();
1421/// #  writer.finish().unwrap();
1422/// # }
1423/// # let mut file = Cursor::new(&file);
1424/// let projection = None; // read all columns
1425/// let mut reader = FileReader::try_new(&mut file, projection).unwrap();
1426/// // Position the reader to the second batch
1427/// reader.set_index(1).unwrap();
1428/// // read batches from the reader using the Iterator trait
1429/// let mut num_rows = 0;
1430/// for batch in reader {
1431///    let batch = batch.unwrap();
1432///    num_rows += batch.num_rows();
1433/// }
1434/// assert_eq!(num_rows, 3);
1435/// ```
1436/// # Example: Reading from `mmap`ed file
1437///
1438/// For an example creating Arrays without copying using  memory mapped (`mmap`)
1439/// files see the [`zero_copy_ipc`] example.
1440///
1441/// [IPC File Format]: https://arrow.apache.org/docs/format/Columnar.html#ipc-file-format
1442/// [`zero_copy_ipc`]: https://github.com/apache/arrow-rs/blob/main/arrow/examples/zero_copy_ipc.rs
1443pub struct FileReader<R> {
1444    /// File reader that supports reading and seeking
1445    reader: R,
1446
1447    /// The decoder
1448    decoder: FileDecoder,
1449
1450    /// Schema of the record batches produced by this reader
1451    schema: SchemaRef,
1452
1453    /// The blocks in the file
1454    ///
1455    /// A block indicates the regions in the file to read to get data
1456    blocks: Vec<Block>,
1457
1458    /// A counter to keep track of the current block that should be read
1459    current_block: usize,
1460
1461    /// The total number of blocks, which may contain record batches and other types
1462    total_blocks: usize,
1463
1464    /// User defined metadata
1465    custom_metadata: HashMap<String, String>,
1466}
1467
1468impl<R> fmt::Debug for FileReader<R> {
1469    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1470        f.debug_struct("FileReader<R>")
1471            .field("decoder", &self.decoder)
1472            .field("blocks", &self.blocks)
1473            .field("current_block", &self.current_block)
1474            .field("total_blocks", &self.total_blocks)
1475            .finish_non_exhaustive()
1476    }
1477}
1478
1479impl<R: Read + Seek> FileReader<BufReader<R>> {
1480    /// Try to create a new file reader with the reader wrapped in a BufReader.
1481    ///
1482    /// See [`FileReader::try_new`] for an unbuffered version.
1483    pub fn try_new_buffered(reader: R, projection: Option<Vec<usize>>) -> Result<Self, ArrowError> {
1484        Self::try_new(BufReader::new(reader), projection)
1485    }
1486}
1487
1488impl<R: Read + Seek> FileReader<R> {
1489    /// Try to create a new file reader.
1490    ///
1491    /// There is no internal buffering. If buffered reads are needed you likely want to use
1492    /// [`FileReader::try_new_buffered`] instead.
1493    ///
1494    /// # Errors
1495    ///
1496    /// An [`Err`] may be returned if:
1497    /// - the file does not meet the Arrow Format footer requirements,
1498    /// - file endianness does not match the target endianness, or
1499    /// - the projection contains an index outside the file schema.
1500    pub fn try_new(reader: R, projection: Option<Vec<usize>>) -> Result<Self, ArrowError> {
1501        let builder = FileReaderBuilder {
1502            projection,
1503            ..Default::default()
1504        };
1505        builder.build(reader)
1506    }
1507
1508    /// Return user defined customized metadata
1509    pub fn custom_metadata(&self) -> &HashMap<String, String> {
1510        &self.custom_metadata
1511    }
1512
1513    /// Return the number of batches in the file
1514    pub fn num_batches(&self) -> usize {
1515        self.total_blocks
1516    }
1517
1518    /// Return the schema of the record batches produced by this reader
1519    pub fn schema(&self) -> SchemaRef {
1520        self.schema.clone()
1521    }
1522
1523    /// See to a specific [`RecordBatch`]
1524    ///
1525    /// Sets the current block to the index, allowing random reads
1526    pub fn set_index(&mut self, index: usize) -> Result<(), ArrowError> {
1527        if index >= self.total_blocks {
1528            Err(ArrowError::InvalidArgumentError(format!(
1529                "Cannot set batch to index {} from {} total batches",
1530                index, self.total_blocks
1531            )))
1532        } else {
1533            self.current_block = index;
1534            Ok(())
1535        }
1536    }
1537
1538    fn maybe_next(&mut self) -> Result<Option<RecordBatch>, ArrowError> {
1539        let block = &self.blocks[self.current_block];
1540        self.current_block += 1;
1541
1542        // read length
1543        let buffer = read_block(&mut self.reader, block)?;
1544        self.decoder.read_record_batch(block, &buffer)
1545    }
1546
1547    /// Gets a reference to the underlying reader.
1548    ///
1549    /// It is inadvisable to directly read from the underlying reader.
1550    pub fn get_ref(&self) -> &R {
1551        &self.reader
1552    }
1553
1554    /// Gets a mutable reference to the underlying reader.
1555    ///
1556    /// It is inadvisable to directly read from the underlying reader.
1557    pub fn get_mut(&mut self) -> &mut R {
1558        &mut self.reader
1559    }
1560
1561    /// Specifies if validation should be skipped when reading data (defaults to `false`)
1562    ///
1563    /// # Safety
1564    ///
1565    /// See [`FileDecoder::with_skip_validation`]
1566    pub unsafe fn with_skip_validation(mut self, skip_validation: bool) -> Self {
1567        self.decoder = unsafe { self.decoder.with_skip_validation(skip_validation) };
1568        self
1569    }
1570}
1571
1572impl<R: Read + Seek> Iterator for FileReader<R> {
1573    type Item = Result<RecordBatch, ArrowError>;
1574
1575    fn next(&mut self) -> Option<Self::Item> {
1576        // get current block
1577        if self.current_block < self.total_blocks {
1578            self.maybe_next().transpose()
1579        } else {
1580            None
1581        }
1582    }
1583}
1584
1585impl<R: Read + Seek> RecordBatchReader for FileReader<R> {
1586    fn schema(&self) -> SchemaRef {
1587        self.schema()
1588    }
1589}
1590
1591/// Arrow Stream Reader
1592///
1593/// Reads Arrow [`RecordBatch`]es from bytes in the [IPC Streaming Format].
1594///
1595/// # See Also
1596///
1597/// * [`FileReader`] for random access.
1598///
1599/// # Example
1600/// ```
1601/// # use arrow_array::record_batch;
1602/// # use arrow_ipc::reader::StreamReader;
1603/// # use arrow_ipc::writer::StreamWriter;
1604/// # let batch = record_batch!(("a", Int32, [1, 2, 3])).unwrap();
1605/// # let mut stream = vec![]; // mimic a stream for the example
1606/// # {
1607/// #  let mut writer = StreamWriter::try_new(&mut stream, &batch.schema()).unwrap();
1608/// #  writer.write(&batch).unwrap();
1609/// #  writer.finish().unwrap();
1610/// # }
1611/// # let stream = stream.as_slice();
1612/// let projection = None; // read all columns
1613/// let mut reader = StreamReader::try_new(stream, projection).unwrap();
1614/// // read batches from the reader using the Iterator trait
1615/// let mut num_rows = 0;
1616/// for batch in reader {
1617///    let batch = batch.unwrap();
1618///    num_rows += batch.num_rows();
1619/// }
1620/// assert_eq!(num_rows, 3);
1621/// ```
1622///
1623/// [IPC Streaming Format]: https://arrow.apache.org/docs/format/Columnar.html#ipc-streaming-format
1624pub struct StreamReader<R> {
1625    /// Stream reader
1626    reader: MessageReader<R>,
1627
1628    /// The schema that is read from the stream's first message
1629    schema: SchemaRef,
1630
1631    /// Optional dictionaries for each schema field.
1632    ///
1633    /// Dictionaries may be appended to in the streaming format.
1634    dictionaries_by_id: HashMap<i64, ArrayRef>,
1635
1636    /// An indicator of whether the stream is complete.
1637    ///
1638    /// This value is set to `true` the first time the reader's `next()` returns `None`.
1639    finished: bool,
1640
1641    /// Optional projection: column indices and the resulting projected schema
1642    projection: Option<(Vec<usize>, SchemaRef)>,
1643
1644    /// Should validation be skipped when reading data? Defaults to false.
1645    ///
1646    /// See [`FileDecoder::with_skip_validation`] for details.
1647    skip_validation: UnsafeFlag,
1648}
1649
1650impl<R> fmt::Debug for StreamReader<R> {
1651    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
1652        f.debug_struct("StreamReader<R>")
1653            .field("reader", &"R")
1654            .field("schema", &self.schema)
1655            .field("dictionaries_by_id", &self.dictionaries_by_id)
1656            .field("finished", &self.finished)
1657            .field("projection", &self.projection)
1658            .finish()
1659    }
1660}
1661
1662impl<R: Read> StreamReader<BufReader<R>> {
1663    /// Try to create a new stream reader with the reader wrapped in a BufReader.
1664    ///
1665    /// See [`StreamReader::try_new`] for an unbuffered version.
1666    pub fn try_new_buffered(reader: R, projection: Option<Vec<usize>>) -> Result<Self, ArrowError> {
1667        Self::try_new(BufReader::new(reader), projection)
1668    }
1669}
1670
1671impl<R: Read> StreamReader<R> {
1672    /// Try to create a new stream reader.
1673    ///
1674    /// To check if the reader is done, use [`is_finished(self)`](StreamReader::is_finished).
1675    ///
1676    /// There is no internal buffering. If buffered reads are needed you likely want to use
1677    /// [`StreamReader::try_new_buffered`] instead.
1678    ///
1679    /// # Errors
1680    ///
1681    /// An [`Err`] may be returned if the reader does not encounter a schema
1682    /// as the first message in the stream.
1683    pub fn try_new(
1684        reader: R,
1685        projection: Option<Vec<usize>>,
1686    ) -> Result<StreamReader<R>, ArrowError> {
1687        let mut msg_reader = MessageReader::new(reader);
1688        let message = msg_reader.maybe_next()?;
1689        let Some((message, _)) = message else {
1690            return Err(ArrowError::IpcError(
1691                "Expected schema message, found empty stream.".to_string(),
1692            ));
1693        };
1694
1695        if message.header_type() != Message::MessageHeader::Schema {
1696            return Err(ArrowError::IpcError(format!(
1697                "Expected a schema as the first message in the stream, got: {:?}",
1698                message.header_type()
1699            )));
1700        }
1701
1702        let schema = message.header_as_schema().ok_or_else(|| {
1703            ArrowError::ParseError("Failed to parse schema from message header".to_string())
1704        })?;
1705        let schema = crate::convert::try_fb_to_schema(schema)?;
1706
1707        // Create an array of optional dictionary value arrays, one per field.
1708        let dictionaries_by_id = HashMap::new();
1709
1710        let projection = match projection {
1711            Some(projection_indices) => {
1712                let schema = Arc::new(schema.project(&projection_indices)?);
1713                Some((projection_indices, schema))
1714            }
1715            _ => None,
1716        };
1717
1718        Ok(Self {
1719            reader: msg_reader,
1720            schema: Arc::new(schema),
1721            finished: false,
1722            dictionaries_by_id,
1723            projection,
1724            skip_validation: UnsafeFlag::new(),
1725        })
1726    }
1727
1728    /// Return the schema of the record batches produced by this reader
1729    pub fn schema(&self) -> SchemaRef {
1730        match &self.projection {
1731            Some((_, projected_schema)) => projected_schema.clone(),
1732            None => self.schema.clone(),
1733        }
1734    }
1735
1736    /// Check if the stream is finished
1737    pub fn is_finished(&self) -> bool {
1738        self.finished
1739    }
1740
1741    fn maybe_next(&mut self) -> Result<Option<RecordBatch>, ArrowError> {
1742        if self.finished {
1743            return Ok(None);
1744        }
1745
1746        // Read messages until we get a record batch or end of stream
1747        loop {
1748            let message = self.next_ipc_message()?;
1749            let Some(message) = message else {
1750                // If the message is None, we have reached the end of the stream.
1751                self.finished = true;
1752                return Ok(None);
1753            };
1754
1755            match message {
1756                IpcMessage::Schema(_) => {
1757                    return Err(ArrowError::IpcError(
1758                        "Expected a record batch, but found a schema".to_string(),
1759                    ));
1760                }
1761                IpcMessage::RecordBatch(record_batch) => {
1762                    return Ok(Some(record_batch));
1763                }
1764                IpcMessage::DictionaryBatch { .. } => {}
1765            }
1766        }
1767    }
1768
1769    /// Reads and fully parses the next IPC message from the stream. Whereas
1770    /// [`Self::maybe_next`] is a higher level method focused on reading
1771    /// `RecordBatch`es, this method returns the individual fully parsed IPC
1772    /// messages from the underlying stream.
1773    ///
1774    /// This is useful primarily for testing reader/writer behaviors as it
1775    /// allows a full view into the messages that have been written to a stream.
1776    pub(crate) fn next_ipc_message(&mut self) -> Result<Option<IpcMessage>, ArrowError> {
1777        let message = self.reader.maybe_next()?;
1778        let Some((message, body)) = message else {
1779            // If the message is None, we have reached the end of the stream.
1780            return Ok(None);
1781        };
1782
1783        let ipc_message = match message.header_type() {
1784            Message::MessageHeader::Schema => {
1785                let schema = message.header_as_schema().ok_or_else(|| {
1786                    ArrowError::ParseError("Failed to parse schema from message header".to_string())
1787                })?;
1788                let arrow_schema = crate::convert::try_fb_to_schema(schema)?;
1789                IpcMessage::Schema(arrow_schema)
1790            }
1791            Message::MessageHeader::RecordBatch => {
1792                let batch = message.header_as_record_batch().ok_or_else(|| {
1793                    ArrowError::IpcError("Unable to read IPC message as record batch".to_string())
1794                })?;
1795
1796                let version = message.version();
1797                let schema = self.schema.clone();
1798                let record_batch = RecordBatchDecoder::try_new(
1799                    &body.into(),
1800                    batch,
1801                    schema,
1802                    &self.dictionaries_by_id,
1803                    &version,
1804                )?
1805                .with_projection(self.projection.as_ref().map(|x| x.0.as_ref()))
1806                .with_require_alignment(false)
1807                .with_skip_validation(self.skip_validation.clone())
1808                .read_record_batch()?;
1809                IpcMessage::RecordBatch(record_batch)
1810            }
1811            Message::MessageHeader::DictionaryBatch => {
1812                let dict = message.header_as_dictionary_batch().ok_or_else(|| {
1813                    ArrowError::ParseError(
1814                        "Failed to parse dictionary batch from message header".to_string(),
1815                    )
1816                })?;
1817
1818                let version = message.version();
1819                let dict_values = get_dictionary_values(
1820                    &body.into(),
1821                    dict,
1822                    &self.schema,
1823                    &self.dictionaries_by_id,
1824                    &version,
1825                    false,
1826                    self.skip_validation.clone(),
1827                )?;
1828
1829                update_dictionaries(
1830                    &mut self.dictionaries_by_id,
1831                    dict.isDelta(),
1832                    dict.id(),
1833                    dict_values.clone(),
1834                )?;
1835
1836                IpcMessage::DictionaryBatch {
1837                    id: dict.id(),
1838                    is_delta: (dict.isDelta()),
1839                    values: (dict_values),
1840                }
1841            }
1842            x => {
1843                return Err(ArrowError::ParseError(format!(
1844                    "Unsupported message header type in IPC stream: '{x:?}'"
1845                )));
1846            }
1847        };
1848
1849        Ok(Some(ipc_message))
1850    }
1851
1852    /// Gets a reference to the underlying reader.
1853    ///
1854    /// It is inadvisable to directly read from the underlying reader.
1855    pub fn get_ref(&self) -> &R {
1856        self.reader.inner()
1857    }
1858
1859    /// Gets a mutable reference to the underlying reader.
1860    ///
1861    /// It is inadvisable to directly read from the underlying reader.
1862    pub fn get_mut(&mut self) -> &mut R {
1863        self.reader.inner_mut()
1864    }
1865
1866    /// Specifies if validation should be skipped when reading data (defaults to `false`)
1867    ///
1868    /// # Safety
1869    ///
1870    /// See [`FileDecoder::with_skip_validation`]
1871    pub unsafe fn with_skip_validation(mut self, skip_validation: bool) -> Self {
1872        unsafe { self.skip_validation.set(skip_validation) };
1873        self
1874    }
1875}
1876
1877impl<R: Read> Iterator for StreamReader<R> {
1878    type Item = Result<RecordBatch, ArrowError>;
1879
1880    fn next(&mut self) -> Option<Self::Item> {
1881        self.maybe_next().transpose()
1882    }
1883}
1884
1885impl<R: Read> RecordBatchReader for StreamReader<R> {
1886    fn schema(&self) -> SchemaRef {
1887        self.schema()
1888    }
1889}
1890
1891/// Representation of a fully parsed IpcMessage from the underlying stream.
1892/// Parsing this kind of message is done by higher level constructs such as
1893/// [`StreamReader`], because fully interpreting the messages into a record
1894/// batch or dictionary batch requires access to stream state such as schema
1895/// and the full dictionary cache.
1896#[derive(Debug)]
1897#[expect(dead_code)]
1898pub(crate) enum IpcMessage {
1899    Schema(arrow_schema::Schema),
1900    RecordBatch(RecordBatch),
1901    DictionaryBatch {
1902        id: i64,
1903        is_delta: bool,
1904        values: ArrayRef,
1905    },
1906}
1907
1908/// Upper bound on how much memory a single message length is allowed to reserve before any
1909/// of the bytes it promises have been read.
1910///
1911/// Message lengths come from the stream itself, so they cannot be trusted: a truncated or
1912/// corrupted stream can declare a body of arbitrary size. Reserving that up front turns a
1913/// malformed input into an allocation failure, which aborts the process rather than
1914/// returning an error the caller can handle. Beyond this size the buffer grows as the data
1915/// arrives instead, so an implausible length costs one bounded allocation and then fails as
1916/// a short read.
1917///
1918/// The value trades the size of that bounded allocation against how large a body still gets
1919/// read in a single allocation: bodies up to this size behave exactly as before, larger ones
1920/// start here and the buffer doubles as the data arrives, so a body of `n` bytes costs
1921/// `log2(n / MAX_PREALLOC_BYTES)` reallocations and the buffer never runs more than a factor
1922/// of two ahead of the bytes actually received.
1923pub(crate) const MAX_PREALLOC_BYTES: usize = 64 * 1024 * 1024;
1924
1925/// Reads exactly `len` bytes of message body, without reserving `len` before reading it.
1926fn read_body_bounded<R: Read>(reader: &mut R, len: usize) -> Result<MutableBuffer, ArrowError> {
1927    let mut buf = MutableBuffer::try_from_len_zeroed(len.min(MAX_PREALLOC_BYTES))
1928        .map_err(|e| ArrowError::MemoryError(e.to_string()))?;
1929    let mut filled = 0;
1930    while filled < len {
1931        let target = buf.len();
1932        reader.read_exact(&mut buf.as_slice_mut()[filled..target])?;
1933        filled = target;
1934        if filled < len {
1935            buf.try_resize(len.min(target.saturating_mul(2)), 0)
1936                .map_err(|e| ArrowError::MemoryError(e.to_string()))?;
1937        }
1938    }
1939    Ok(buf)
1940}
1941
1942/// A low-level construct that reads [`Message::Message`]s from a reader while
1943/// re-using a buffer for metadata. This is composed into [`StreamReader`].
1944struct MessageReader<R> {
1945    reader: R,
1946    buf: Vec<u8>,
1947}
1948
1949impl<R: Read> MessageReader<R> {
1950    fn new(reader: R) -> Self {
1951        Self {
1952            reader,
1953            buf: Vec::new(),
1954        }
1955    }
1956
1957    /// Reads the entire next message from the underlying reader which includes
1958    /// the metadata length, the metadata, and the body.
1959    ///
1960    /// # Returns
1961    /// - `Ok(None)` if the the reader signals the end of stream with EOF on
1962    ///   the first read
1963    /// - `Err(_)` if the reader returns an error other than on the first
1964    ///   read, or if the metadata length is invalid
1965    /// - `Ok(Some(_))` with the Message and buffer containing the
1966    ///   body bytes otherwise.
1967    fn maybe_next(&mut self) -> Result<Option<(Message::Message<'_>, MutableBuffer)>, ArrowError> {
1968        let meta_len = self.read_meta_len()?;
1969        let Some(meta_len) = meta_len else {
1970            return Ok(None);
1971        };
1972
1973        // `read_to_end` on a `Take` grows with the bytes that arrive, so an implausible
1974        // `meta_len` costs a short read rather than the allocation it asks for.
1975        self.buf.clear();
1976        let read = (&mut self.reader)
1977            .take(meta_len as u64)
1978            .read_to_end(&mut self.buf)?;
1979        if read != meta_len {
1980            return Err(ArrowError::ParseError(format!(
1981                "Unexpected end of stream: expected {meta_len} metadata bytes, got {read}"
1982            )));
1983        }
1984
1985        let message = crate::root_as_message(self.buf.as_slice()).map_err(|err| {
1986            ArrowError::ParseError(format!("Unable to get root as message: {err:?}"))
1987        })?;
1988
1989        let body_len = usize::try_from(message.bodyLength()).map_err(|_| {
1990            ArrowError::ParseError(format!(
1991                "Invalid IPC message body length: {}",
1992                message.bodyLength()
1993            ))
1994        })?;
1995        let buf = read_body_bounded(&mut self.reader, body_len)?;
1996
1997        Ok(Some((message, buf)))
1998    }
1999
2000    /// Get a mutable reference to the underlying reader.
2001    fn inner_mut(&mut self) -> &mut R {
2002        &mut self.reader
2003    }
2004
2005    /// Get an immutable reference to the underlying reader.
2006    fn inner(&self) -> &R {
2007        &self.reader
2008    }
2009
2010    /// Read the metadata length for the next message from the underlying stream.
2011    ///
2012    /// # Returns
2013    /// - `Ok(None)` if the the reader signals the end of stream with EOF on
2014    ///   the first read
2015    /// - `Err(_)` if the reader returns an error other than on the first
2016    ///   read, or if the metadata length is less than 0.
2017    /// - `Ok(Some(_))` with the length otherwise.
2018    pub fn read_meta_len(&mut self) -> Result<Option<usize>, ArrowError> {
2019        let mut meta_len: [u8; 4] = [0; 4];
2020        match self.reader.read_exact(&mut meta_len) {
2021            Ok(()) => {}
2022            Err(e) => {
2023                return if e.kind() == std::io::ErrorKind::UnexpectedEof {
2024                    // Handle EOF without the "0xFFFFFFFF 0x00000000"
2025                    // valid according to:
2026                    // https://arrow.apache.org/docs/format/Columnar.html#ipc-streaming-format
2027                    Ok(None)
2028                } else {
2029                    Err(ArrowError::from(e))
2030                };
2031            }
2032        }
2033
2034        let meta_len = {
2035            // If a continuation marker is encountered, skip over it and read
2036            // the size from the next four bytes.
2037            if meta_len == CONTINUATION_MARKER {
2038                self.reader.read_exact(&mut meta_len)?;
2039            }
2040
2041            i32::from_le_bytes(meta_len)
2042        };
2043
2044        if meta_len == 0 {
2045            return Ok(None);
2046        }
2047
2048        let meta_len = usize::try_from(meta_len)
2049            .map_err(|_| ArrowError::ParseError(format!("Invalid metadata length: {meta_len}")))?;
2050
2051        Ok(Some(meta_len))
2052    }
2053}
2054
2055#[cfg(test)]
2056mod tests {
2057    use std::io::Cursor;
2058
2059    use crate::convert::try_fb_to_schema;
2060    use crate::writer::{
2061        DictionaryTracker, IpcDataGenerator, IpcWriteOptions, unslice_run_array, write_message,
2062    };
2063
2064    use super::*;
2065
2066    use crate::{root_as_footer, root_as_message, size_prefixed_root_as_message};
2067    use arrow_array::builder::{PrimitiveRunBuilder, UnionBuilder};
2068    use arrow_array::types::*;
2069    use arrow_buffer::{NullBuffer, OffsetBuffer};
2070    use arrow_data::ArrayDataBuilder;
2071
2072    fn create_test_projection_schema() -> Schema {
2073        // define field types
2074        let list_data_type = DataType::List(Arc::new(Field::new_list_field(DataType::Int32, true)));
2075
2076        let fixed_size_list_data_type =
2077            DataType::FixedSizeList(Arc::new(Field::new_list_field(DataType::Int32, false)), 3);
2078
2079        let union_fields = UnionFields::from_fields(vec![
2080            Field::new("a", DataType::Int32, false),
2081            Field::new("b", DataType::Float64, false),
2082        ]);
2083
2084        let union_data_type = DataType::Union(union_fields, UnionMode::Dense);
2085
2086        let struct_fields = Fields::from(vec![
2087            Field::new("id", DataType::Int32, false),
2088            Field::new_list("list", Field::new_list_field(DataType::Int8, true), false),
2089        ]);
2090        let struct_data_type = DataType::Struct(struct_fields);
2091
2092        let run_encoded_data_type = DataType::RunEndEncoded(
2093            Arc::new(Field::new(
2094                Field::REE_RUN_ENDS_FIELD_DEFAULT_NAME,
2095                DataType::Int16,
2096                false,
2097            )),
2098            Arc::new(Field::new(
2099                Field::REE_VALUES_FIELD_DEFAULT_NAME,
2100                DataType::Int32,
2101                true,
2102            )),
2103        );
2104
2105        // define schema
2106        Schema::new(vec![
2107            Field::new("f0", DataType::UInt32, false),
2108            Field::new("f1", DataType::Utf8, false),
2109            Field::new("f2", DataType::Boolean, false),
2110            Field::new("f3", union_data_type, true),
2111            Field::new("f4", DataType::Null, true),
2112            Field::new("f5", DataType::Float64, true),
2113            Field::new("f6", list_data_type, false),
2114            Field::new("f7", DataType::FixedSizeBinary(3), true),
2115            Field::new("f8", fixed_size_list_data_type, false),
2116            Field::new("f9", struct_data_type, false),
2117            Field::new("f10", run_encoded_data_type, false),
2118            Field::new("f11", DataType::Boolean, false),
2119            Field::new_dictionary("f12", DataType::Int8, DataType::Utf8, false),
2120            Field::new("f13", DataType::Utf8, false),
2121        ])
2122    }
2123
2124    fn create_test_projection_batch_data(schema: &Schema) -> RecordBatch {
2125        // set test data for each column
2126        let array0 = UInt32Array::from(vec![1, 2, 3]);
2127        let array1 = StringArray::from(vec!["foo", "bar", "baz"]);
2128        let array2 = BooleanArray::from(vec![true, false, true]);
2129
2130        let mut union_builder = UnionBuilder::new_dense();
2131        union_builder.append::<Int32Type>("a", 1).unwrap();
2132        union_builder.append::<Float64Type>("b", 10.1).unwrap();
2133        union_builder.append_null::<Float64Type>("b").unwrap();
2134        let array3 = union_builder.build().unwrap();
2135
2136        let array4 = NullArray::new(3);
2137        let array5 = Float64Array::from(vec![Some(1.1), None, Some(3.3)]);
2138        let array6_values = vec![
2139            Some(vec![Some(10), Some(10), Some(10)]),
2140            Some(vec![Some(20), Some(20), Some(20)]),
2141            Some(vec![Some(30), Some(30)]),
2142        ];
2143        let array6 = ListArray::from_iter_primitive::<Int32Type, _, _>(array6_values);
2144        let array7_values = vec![vec![11, 12, 13], vec![22, 23, 24], vec![33, 34, 35]];
2145        let array7 = FixedSizeBinaryArray::try_from_iter(array7_values.into_iter()).unwrap();
2146
2147        let array8_values = ArrayData::builder(DataType::Int32)
2148            .len(9)
2149            .add_buffer(Buffer::from_slice_ref([40, 41, 42, 43, 44, 45, 46, 47, 48]))
2150            .build()
2151            .unwrap();
2152        let array8_data = ArrayData::builder(schema.field(8).data_type().clone())
2153            .len(3)
2154            .add_child_data(array8_values)
2155            .build()
2156            .unwrap();
2157        let array8 = FixedSizeListArray::from(array8_data);
2158
2159        let array9_id: ArrayRef = Arc::new(Int32Array::from(vec![1001, 1002, 1003]));
2160        let array9_list: ArrayRef =
2161            Arc::new(ListArray::from_iter_primitive::<Int8Type, _, _>(vec![
2162                Some(vec![Some(-10)]),
2163                Some(vec![Some(-20), Some(-20), Some(-20)]),
2164                Some(vec![Some(-30)]),
2165            ]));
2166        let array9 = ArrayDataBuilder::new(schema.field(9).data_type().clone())
2167            .add_child_data(array9_id.into_data())
2168            .add_child_data(array9_list.into_data())
2169            .len(3)
2170            .build()
2171            .unwrap();
2172        let array9 = StructArray::from(array9);
2173
2174        let array10_input = vec![Some(1_i32), None, None];
2175        let mut array10_builder = PrimitiveRunBuilder::<Int16Type, Int32Type>::new();
2176        array10_builder.extend(array10_input);
2177        let array10 = array10_builder.finish();
2178
2179        let array11 = BooleanArray::from(vec![false, false, true]);
2180
2181        let array12_values = StringArray::from(vec!["x", "yy", "zzz"]);
2182        let array12_keys = Int8Array::from_iter_values([1, 1, 2]);
2183        let array12 = DictionaryArray::new(array12_keys, Arc::new(array12_values));
2184
2185        let array13 = StringArray::from(vec!["a", "bb", "ccc"]);
2186
2187        // create record batch
2188        RecordBatch::try_new(
2189            Arc::new(schema.clone()),
2190            vec![
2191                Arc::new(array0),
2192                Arc::new(array1),
2193                Arc::new(array2),
2194                Arc::new(array3),
2195                Arc::new(array4),
2196                Arc::new(array5),
2197                Arc::new(array6),
2198                Arc::new(array7),
2199                Arc::new(array8),
2200                Arc::new(array9),
2201                Arc::new(array10),
2202                Arc::new(array11),
2203                Arc::new(array12),
2204                Arc::new(array13),
2205            ],
2206        )
2207        .unwrap()
2208    }
2209
2210    #[test]
2211    fn test_negative_meta_len_start_stream() {
2212        let bytes = i32::to_le_bytes(-1);
2213        let mut buf = vec![];
2214        buf.extend(CONTINUATION_MARKER);
2215        buf.extend(bytes);
2216
2217        let reader_err = StreamReader::try_new(Cursor::new(buf), None).err();
2218        assert!(reader_err.is_some());
2219        assert_eq!(
2220            reader_err.unwrap().to_string(),
2221            "Parser error: Invalid metadata length: -1"
2222        );
2223    }
2224
2225    #[test]
2226    fn test_negative_meta_len_mid_stream() {
2227        let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
2228        let mut buf = Vec::new();
2229        {
2230            let mut writer = crate::writer::StreamWriter::try_new(&mut buf, &schema).unwrap();
2231            let batch =
2232                RecordBatch::try_new(Arc::new(schema), vec![Arc::new(Int32Array::from(vec![1]))])
2233                    .unwrap();
2234            writer.write(&batch).unwrap();
2235        }
2236
2237        let bytes = i32::to_le_bytes(-1);
2238        buf.extend(CONTINUATION_MARKER);
2239        buf.extend(bytes);
2240
2241        let mut reader = StreamReader::try_new(Cursor::new(buf), None).unwrap();
2242        // Read the valid value
2243        assert!(reader.maybe_next().is_ok());
2244        // Read the invalid meta len
2245        let batch_err = reader.maybe_next().err();
2246        assert!(batch_err.is_some());
2247        assert_eq!(
2248            batch_err.unwrap().to_string(),
2249            "Parser error: Invalid metadata length: -1"
2250        );
2251    }
2252
2253    #[test]
2254    fn test_invalid_dictionary_batch_without_data() {
2255        use crate::r#gen::Message::*;
2256        use flatbuffers::FlatBufferBuilder;
2257
2258        let schema = Schema::new(vec![Field::new(
2259            "col",
2260            DataType::Dictionary(Box::new(DataType::Int8), Box::new(DataType::Utf8)),
2261            true,
2262        )]);
2263
2264        // DictionaryBatch.data is optional in the flatbuffer grammar and
2265        // required by the format
2266        let mut fbb = FlatBufferBuilder::new();
2267        let batch_offset = DictionaryBatch::create(
2268            &mut fbb,
2269            &DictionaryBatchArgs {
2270                id: 0,
2271                data: None,
2272                isDelta: false,
2273            },
2274        );
2275        fbb.finish_minimal(batch_offset);
2276        let batch_bytes = fbb.finished_data().to_vec();
2277        let batch = flatbuffers::root::<DictionaryBatch>(&batch_bytes).unwrap();
2278
2279        let data_buffer = Buffer::from(vec![0u8; 0]);
2280        let mut dictionaries: HashMap<i64, ArrayRef> = HashMap::new();
2281
2282        let err = read_dictionary(
2283            &data_buffer,
2284            batch,
2285            &schema,
2286            &mut dictionaries,
2287            &MetadataVersion::V5,
2288        )
2289        .unwrap_err();
2290
2291        assert_eq!(
2292            err.to_string(),
2293            "Parser error: Dictionary batch is missing its data"
2294        );
2295    }
2296
2297    #[test]
2298    fn test_missing_buffer_metadata_error() {
2299        use crate::r#gen::Message::*;
2300        use flatbuffers::FlatBufferBuilder;
2301
2302        let schema = Arc::new(Schema::new(vec![Field::new("col", DataType::Int32, true)]));
2303
2304        // create RecordBatch buffer metadata with invalid buffer count
2305        // Int32Array needs 2 buffers (validity + data) but we provide only 1
2306        let mut fbb = FlatBufferBuilder::new();
2307        let nodes = fbb.create_vector(&[FieldNode::new(2, 0)]);
2308        let buffers = fbb.create_vector(&[crate::Buffer::new(0, 8)]);
2309        let batch_offset = RecordBatch::create(
2310            &mut fbb,
2311            &RecordBatchArgs {
2312                length: 2,
2313                nodes: Some(nodes),
2314                buffers: Some(buffers),
2315                compression: None,
2316                variadicBufferCounts: None,
2317            },
2318        );
2319        fbb.finish_minimal(batch_offset);
2320        let batch_bytes = fbb.finished_data().to_vec();
2321        let batch = flatbuffers::root::<RecordBatch>(&batch_bytes).unwrap();
2322
2323        let data_buffer = Buffer::from(vec![0u8; 8]);
2324        let dictionaries: HashMap<i64, ArrayRef> = HashMap::new();
2325        let metadata = MetadataVersion::V5;
2326
2327        let decoder = RecordBatchDecoder::try_new(
2328            &data_buffer,
2329            batch,
2330            schema.clone(),
2331            &dictionaries,
2332            &metadata,
2333        )
2334        .unwrap();
2335
2336        let result = decoder.read_record_batch();
2337
2338        match result {
2339            Err(ArrowError::IpcError(msg)) => {
2340                assert_eq!(msg, "Buffer count mismatched with metadata");
2341            }
2342            other => panic!("unexpected error: {other:?}"),
2343        }
2344    }
2345
2346    #[test]
2347    fn test_read_record_batch_buffer_out_of_bounds() {
2348        use crate::r#gen::Message::*;
2349        use flatbuffers::FlatBufferBuilder;
2350
2351        let schema = Arc::new(Schema::new(vec![Field::new("col", DataType::Int32, false)]));
2352        let body = Buffer::from(vec![0u8; 8]);
2353        let mut fbb = FlatBufferBuilder::new();
2354        let nodes = fbb.create_vector(&[FieldNode::new(2, 0)]);
2355        // Empty validity buffer followed by two Int32 values whose range exceeds the body.
2356        let buffers = fbb.create_vector(&[crate::Buffer::new(0, 0), crate::Buffer::new(4, 8)]);
2357        let batch = RecordBatch::create(
2358            &mut fbb,
2359            &RecordBatchArgs {
2360                length: 2,
2361                nodes: Some(nodes),
2362                buffers: Some(buffers),
2363                ..Default::default()
2364            },
2365        );
2366        fbb.finish_minimal(batch);
2367        let batch = flatbuffers::root::<RecordBatch>(fbb.finished_data()).unwrap();
2368
2369        read_record_batch(
2370            &body,
2371            batch,
2372            schema,
2373            &HashMap::new(),
2374            None,
2375            &MetadataVersion::V5,
2376        )
2377        .expect_err("invalid IPC buffer range must return an error");
2378    }
2379
2380    #[test]
2381    fn test_union_short_buffers() {
2382        use crate::r#gen::Message::*;
2383
2384        let fields = UnionFields::from_fields([Field::new("child", DataType::Null, true)]);
2385        // All buffer ranges fit in the body. Only the union length is inconsistent:
2386        // sparse has one type id for two rows, dense has one offset for two rows
2387        let cases = [
2388            (UnionMode::Sparse, vec![crate::Buffer::new(0, 1)]),
2389            (
2390                UnionMode::Dense,
2391                vec![crate::Buffer::new(0, 2), crate::Buffer::new(8, 4)],
2392            ),
2393        ];
2394        for (mode, buffers) in cases {
2395            let schema = Arc::new(Schema::new(vec![Field::new(
2396                "union",
2397                DataType::Union(fields.clone(), mode),
2398                false,
2399            )]));
2400
2401            let mut fbb = flatbuffers::FlatBufferBuilder::new();
2402            let nodes = fbb.create_vector(&[FieldNode::new(2, 0), FieldNode::new(2, 2)]);
2403            let buffers = fbb.create_vector(&buffers);
2404            let batch = RecordBatch::create(
2405                &mut fbb,
2406                &RecordBatchArgs {
2407                    length: 2,
2408                    nodes: Some(nodes),
2409                    buffers: Some(buffers),
2410                    ..Default::default()
2411                },
2412            );
2413            fbb.finish_minimal(batch);
2414            let batch = flatbuffers::root::<RecordBatch>(fbb.finished_data()).unwrap();
2415
2416            read_record_batch(
2417                &Buffer::from(vec![0u8; 16]),
2418                batch,
2419                schema,
2420                &HashMap::new(),
2421                None,
2422                &MetadataVersion::V5,
2423            )
2424            .expect_err("a short union buffer should return an error");
2425        }
2426    }
2427
2428    #[test]
2429    fn test_offsets_buffer_with_trailing_bytes() {
2430        use crate::r#gen::Message::*;
2431        use flatbuffers::FlatBufferBuilder;
2432
2433        let schema = Arc::new(Schema::new(vec![Field::new("col", DataType::Utf8, false)]));
2434
2435        // The offsets buffer is 14 bytes: three i32 offsets and two trailing
2436        // bytes, which is more than the array needs but not a whole number
2437        // of offsets.
2438        let mut fbb = FlatBufferBuilder::new();
2439        let nodes = fbb.create_vector(&[FieldNode::new(2, 0)]);
2440        let buffers = fbb.create_vector(&[
2441            crate::Buffer::new(0, 0),
2442            crate::Buffer::new(0, 14),
2443            crate::Buffer::new(16, 5),
2444        ]);
2445        let batch_offset = RecordBatch::create(
2446            &mut fbb,
2447            &RecordBatchArgs {
2448                length: 2,
2449                nodes: Some(nodes),
2450                buffers: Some(buffers),
2451                compression: None,
2452                variadicBufferCounts: None,
2453            },
2454        );
2455        fbb.finish_minimal(batch_offset);
2456        let batch_bytes = fbb.finished_data().to_vec();
2457        let batch = flatbuffers::root::<RecordBatch>(&batch_bytes).unwrap();
2458
2459        let mut body = Buffer::from_slice_ref([0_i32, 2, 5]).as_slice().to_vec();
2460        body.extend_from_slice(&[0; 4]);
2461        body.extend_from_slice(b"hello\0\0\0");
2462        let data_buffer = Buffer::from_slice_ref(&body);
2463        let dictionaries: HashMap<i64, ArrayRef> = HashMap::new();
2464        let metadata = MetadataVersion::V5;
2465
2466        let decoder =
2467            RecordBatchDecoder::try_new(&data_buffer, batch, schema, &dictionaries, &metadata)
2468                .unwrap();
2469        let batch = decoder.read_record_batch().unwrap();
2470        let col = batch.column(0).as_any().downcast_ref::<StringArray>();
2471        assert_eq!(col.unwrap(), &StringArray::from(vec!["he", "llo"]));
2472    }
2473
2474    /// A `Utf8View` batch whose variadic buffer count is `count`, with two buffers
2475    /// in the message. Returns the error from reading it with the given projection.
2476    fn read_batch_with_variadic_count(count: i64, projection: Option<&[usize]>) -> ArrowError {
2477        use crate::r#gen::Message::*;
2478        use flatbuffers::FlatBufferBuilder;
2479
2480        let schema = Arc::new(Schema::new(vec![Field::new(
2481            "col",
2482            DataType::Utf8View,
2483            true,
2484        )]));
2485
2486        let mut fbb = FlatBufferBuilder::new();
2487        let nodes = fbb.create_vector(&[FieldNode::new(1, 0)]);
2488        let buffers = fbb.create_vector(&[crate::Buffer::new(0, 8), crate::Buffer::new(8, 8)]);
2489        let variadic_buffer_counts = fbb.create_vector(&[count]);
2490        let batch_offset = RecordBatch::create(
2491            &mut fbb,
2492            &RecordBatchArgs {
2493                length: 1,
2494                nodes: Some(nodes),
2495                buffers: Some(buffers),
2496                compression: None,
2497                variadicBufferCounts: Some(variadic_buffer_counts),
2498            },
2499        );
2500        fbb.finish_minimal(batch_offset);
2501        let batch_bytes = fbb.finished_data().to_vec();
2502        let batch = flatbuffers::root::<RecordBatch>(&batch_bytes).unwrap();
2503
2504        let data_buffer = Buffer::from(vec![0u8; 16]);
2505        let dictionaries: HashMap<i64, ArrayRef> = HashMap::new();
2506
2507        RecordBatchDecoder::try_new(
2508            &data_buffer,
2509            batch,
2510            schema,
2511            &dictionaries,
2512            &MetadataVersion::V5,
2513        )
2514        .unwrap()
2515        .with_projection(projection)
2516        .read_record_batch()
2517        .expect_err("should get error")
2518    }
2519
2520    /// A variadic count the message cannot honour used to panic while slicing the
2521    /// buffers it did not read, both when reading the column and when skipping it.
2522    #[test]
2523    fn test_invalid_variadic_buffer_count_error() {
2524        // -2 leaves no buffers at all, -1 leaves too few, and 1 asks for more than the
2525        // message has. The projection selects nothing, so the column is skipped instead.
2526        for count in [-2, -1, 1, i64::MAX] {
2527            for projection in [None, Some([].as_slice())] {
2528                let err = read_batch_with_variadic_count(count, projection);
2529                assert_eq!(
2530                    err.to_string(),
2531                    format!(
2532                        "Ipc error: Invalid variadic count {count} for Utf8View column, \
2533                         with 2 buffer(s) left in the message"
2534                    ),
2535                    "count {count}, projection {projection:?}"
2536                );
2537            }
2538        }
2539    }
2540
2541    /// The valid count for a message with two buffers is zero.
2542    #[test]
2543    fn test_valid_variadic_buffer_count_is_accepted() {
2544        let err = read_batch_with_variadic_count(0, None);
2545        assert!(!err.to_string().contains("Invalid variadic count"), "{err}");
2546    }
2547
2548    #[test]
2549    fn test_missing_footer_schema_error() {
2550        use crate::r#gen::File::{Footer, FooterArgs};
2551        use flatbuffers::FlatBufferBuilder;
2552
2553        // a footer that verifies but has no schema table. record batches present
2554        // (so the earlier ok_or_else passes) but schema absent, which used to panic.
2555        let mut fbb = FlatBufferBuilder::new();
2556        let record_batches = fbb.create_vector::<Block>(&[]);
2557        let footer = Footer::create(
2558            &mut fbb,
2559            &FooterArgs {
2560                version: MetadataVersion::V5,
2561                schema: None,
2562                dictionaries: None,
2563                recordBatches: Some(record_batches),
2564                custom_metadata: None,
2565            },
2566        );
2567        fbb.finish(footer, None);
2568        let footer_data = fbb.finished_data();
2569
2570        // assemble a minimal IPC file: magic header, footer, footer length, magic trailer
2571        let mut buf = Vec::new();
2572        buf.extend_from_slice(&crate::ARROW_MAGIC);
2573        buf.extend_from_slice(footer_data);
2574        buf.extend_from_slice(&(footer_data.len() as i32).to_le_bytes());
2575        buf.extend_from_slice(&crate::ARROW_MAGIC);
2576
2577        let err = FileReader::try_new(Cursor::new(buf), None)
2578            .expect_err("expected an error, not a panic");
2579        assert!(
2580            matches!(err, ArrowError::ParseError(_)),
2581            "expected ParseError, got {err:?}"
2582        );
2583    }
2584
2585    /// Test that the reader can read legacy files where empty list arrays were written with a 0-byte offsets buffer.
2586    #[test]
2587    fn test_read_legacy_empty_list_without_offsets_buffer() {
2588        use crate::r#gen::Message::*;
2589        use flatbuffers::FlatBufferBuilder;
2590
2591        let schema = Arc::new(Schema::new(vec![Field::new_list(
2592            "items",
2593            Field::new_list_field(DataType::Int32, true),
2594            true,
2595        )]));
2596
2597        // Legacy arrow-rs versions wrote empty offsets buffers for empty list arrays.
2598        // Keep reader compatibility with such files by accepting a 0-byte offsets buffer.
2599        let mut fbb = FlatBufferBuilder::new();
2600        let nodes = fbb.create_vector(&[
2601            FieldNode::new(0, 0), // list node
2602            FieldNode::new(0, 0), // child int32 node
2603        ]);
2604        let buffers = fbb.create_vector(&[
2605            crate::Buffer::new(0, 0), // list validity
2606            crate::Buffer::new(0, 0), // list offsets (legacy empty buffer)
2607            crate::Buffer::new(0, 0), // child validity
2608            crate::Buffer::new(0, 0), // child values
2609        ]);
2610        let batch_offset = RecordBatch::create(
2611            &mut fbb,
2612            &RecordBatchArgs {
2613                length: 0,
2614                nodes: Some(nodes),
2615                buffers: Some(buffers),
2616                compression: None,
2617                variadicBufferCounts: None,
2618            },
2619        );
2620        fbb.finish_minimal(batch_offset);
2621        let batch_bytes = fbb.finished_data().to_vec();
2622        let batch = flatbuffers::root::<RecordBatch>(&batch_bytes).unwrap();
2623
2624        let body = Buffer::from(Vec::<u8>::new());
2625        let dictionaries: HashMap<i64, ArrayRef> = HashMap::new();
2626        let metadata = MetadataVersion::V5;
2627
2628        let decoder =
2629            RecordBatchDecoder::try_new(&body, batch, schema.clone(), &dictionaries, &metadata)
2630                .unwrap();
2631
2632        let read_batch = decoder.read_record_batch().unwrap();
2633        assert_eq!(read_batch.num_rows(), 0);
2634
2635        let list = read_batch
2636            .column(0)
2637            .as_any()
2638            .downcast_ref::<ListArray>()
2639            .unwrap();
2640        assert_eq!(list.len(), 0);
2641        assert_eq!(list.values().len(), 0);
2642    }
2643
2644    /// Test that the reader can read legacy files where empty Utf8/Binary arrays were written with a 0-byte offsets buffer.
2645    #[test]
2646    fn test_read_legacy_empty_utf8_and_binary_without_offsets_buffer() {
2647        use crate::r#gen::Message::*;
2648        use flatbuffers::FlatBufferBuilder;
2649
2650        let schema = Arc::new(Schema::new(vec![
2651            Field::new("name", DataType::Utf8, true),
2652            Field::new("payload", DataType::Binary, true),
2653        ]));
2654
2655        // Legacy arrow-rs versions wrote empty offsets buffers for empty Utf8/Binary arrays.
2656        // Keep reader compatibility with such files by accepting 0-byte offsets buffers.
2657        let mut fbb = FlatBufferBuilder::new();
2658        let nodes = fbb.create_vector(&[
2659            FieldNode::new(0, 0), // utf8 node
2660            FieldNode::new(0, 0), // binary node
2661        ]);
2662        let buffers = fbb.create_vector(&[
2663            crate::Buffer::new(0, 0), // utf8 validity
2664            crate::Buffer::new(0, 0), // utf8 offsets (legacy empty buffer)
2665            crate::Buffer::new(0, 0), // utf8 values
2666            crate::Buffer::new(0, 0), // binary validity
2667            crate::Buffer::new(0, 0), // binary offsets (legacy empty buffer)
2668            crate::Buffer::new(0, 0), // binary values
2669        ]);
2670        let batch_offset = RecordBatch::create(
2671            &mut fbb,
2672            &RecordBatchArgs {
2673                length: 0,
2674                nodes: Some(nodes),
2675                buffers: Some(buffers),
2676                compression: None,
2677                variadicBufferCounts: None,
2678            },
2679        );
2680        fbb.finish_minimal(batch_offset);
2681        let batch_bytes = fbb.finished_data().to_vec();
2682        let batch = flatbuffers::root::<RecordBatch>(&batch_bytes).unwrap();
2683
2684        let body = Buffer::from(Vec::<u8>::new());
2685        let dictionaries: HashMap<i64, ArrayRef> = HashMap::new();
2686        let metadata = MetadataVersion::V5;
2687
2688        let decoder =
2689            RecordBatchDecoder::try_new(&body, batch, schema.clone(), &dictionaries, &metadata)
2690                .unwrap();
2691
2692        let read_batch = decoder.read_record_batch().unwrap();
2693        assert_eq!(read_batch.num_rows(), 0);
2694
2695        let utf8 = read_batch
2696            .column(0)
2697            .as_any()
2698            .downcast_ref::<StringArray>()
2699            .unwrap();
2700        assert_eq!(utf8.len(), 0);
2701        assert_eq!(utf8.value_offsets(), [0]);
2702
2703        let binary = read_batch
2704            .column(1)
2705            .as_any()
2706            .downcast_ref::<BinaryArray>()
2707            .unwrap();
2708        assert_eq!(binary.len(), 0);
2709        assert_eq!(binary.value_offsets(), [0]);
2710    }
2711
2712    #[test]
2713    fn test_projection_array_values() {
2714        // define schema
2715        let schema = create_test_projection_schema();
2716
2717        // create record batch with test data
2718        let batch = create_test_projection_batch_data(&schema);
2719
2720        // write record batch in IPC format
2721        let mut buf = Vec::new();
2722        {
2723            let mut writer = crate::writer::FileWriter::try_new(&mut buf, &schema).unwrap();
2724            writer.write(&batch).unwrap();
2725            writer.finish().unwrap();
2726        }
2727
2728        // read record batch with projection
2729        for index in 0..12 {
2730            let projection = vec![index];
2731            let reader = FileReader::try_new(std::io::Cursor::new(buf.clone()), Some(projection));
2732            let read_batch = reader.unwrap().next().unwrap().unwrap();
2733            let projected_column = read_batch.column(0);
2734            let expected_column = batch.column(index);
2735
2736            // check the projected column equals the expected column
2737            assert_eq!(projected_column.as_ref(), expected_column.as_ref());
2738        }
2739
2740        {
2741            // read record batch with reversed projection
2742            let reader =
2743                FileReader::try_new(std::io::Cursor::new(buf.clone()), Some(vec![3, 2, 1]));
2744            let read_batch = reader.unwrap().next().unwrap().unwrap();
2745            let expected_batch = batch.project(&[3, 2, 1]).unwrap();
2746            assert_eq!(read_batch, expected_batch);
2747        }
2748    }
2749
2750    #[test]
2751    fn test_file_reader_projected_schema_matches_batch_schema() {
2752        let schema = create_test_projection_schema();
2753        let batch = create_test_projection_batch_data(&schema);
2754
2755        let mut buf = Vec::new();
2756        {
2757            let mut writer = crate::writer::FileWriter::try_new(&mut buf, &schema).unwrap();
2758            writer.write(&batch).unwrap();
2759            writer.finish().unwrap();
2760        }
2761
2762        let projection = vec![3, 2, 1];
2763        let mut reader = FileReader::try_new(Cursor::new(buf), Some(projection)).unwrap();
2764        let reader_schema = RecordBatchReader::schema(&reader);
2765        let read_batch = reader.next().unwrap().unwrap();
2766
2767        assert_eq!(reader_schema, read_batch.schema());
2768    }
2769
2770    #[test]
2771    fn test_stream_reader_projected_schema_matches_batch_schema() {
2772        let schema = create_test_projection_schema();
2773        let batch = create_test_projection_batch_data(&schema);
2774
2775        let mut buf = Vec::new();
2776        {
2777            let mut writer = crate::writer::StreamWriter::try_new(&mut buf, &schema).unwrap();
2778            writer.write(&batch).unwrap();
2779            writer.finish().unwrap();
2780        }
2781
2782        let projection = vec![3, 2, 1];
2783        let mut reader = StreamReader::try_new(Cursor::new(buf), Some(projection)).unwrap();
2784        let reader_schema = RecordBatchReader::schema(&reader);
2785        let read_batch = reader.next().unwrap().unwrap();
2786
2787        assert_eq!(reader_schema, read_batch.schema());
2788    }
2789
2790    #[test]
2791    fn test_file_reader_rejects_invalid_projection() {
2792        let schema = create_test_projection_schema();
2793        let batch = create_test_projection_batch_data(&schema);
2794
2795        let mut buf = Vec::new();
2796        {
2797            let mut writer = crate::writer::FileWriter::try_new(&mut buf, &schema).unwrap();
2798            writer.write(&batch).unwrap();
2799            writer.finish().unwrap();
2800        }
2801
2802        let result = FileReader::try_new(Cursor::new(buf), Some(vec![schema.fields().len()]));
2803
2804        assert!(matches!(result, Err(ArrowError::SchemaError(_))));
2805    }
2806
2807    #[test]
2808    fn test_projection_duplicate_indices() {
2809        let schema = create_test_projection_schema();
2810        let batch = create_test_projection_batch_data(&schema);
2811
2812        // Write the batch to IPC
2813        let mut buf = Vec::new();
2814        {
2815            let mut writer = crate::writer::FileWriter::try_new(&mut buf, &schema).unwrap();
2816            writer.write(&batch).unwrap();
2817            writer.finish().unwrap();
2818        }
2819
2820        // Verify duplicate([1, 1]) and reordered([2, 0, 2]) projection indices
2821        for projection in [vec![1, 1], vec![2, 0, 2]] {
2822            let reader =
2823                FileReader::try_new(std::io::Cursor::new(buf.clone()), Some(projection.clone()));
2824            let read_batch = reader.unwrap().next().unwrap().unwrap();
2825
2826            let expected_batch = batch.project(&projection).unwrap();
2827            assert_eq!(read_batch, expected_batch);
2828        }
2829    }
2830
2831    #[test]
2832    fn test_arrow_single_float_row() {
2833        let schema = Schema::new(vec![
2834            Field::new("a", DataType::Float32, false),
2835            Field::new("b", DataType::Float32, false),
2836            Field::new("c", DataType::Int32, false),
2837            Field::new("d", DataType::Int32, false),
2838        ]);
2839        let arrays = vec![
2840            Arc::new(Float32Array::from(vec![1.23])) as ArrayRef,
2841            Arc::new(Float32Array::from(vec![-6.50])) as ArrayRef,
2842            Arc::new(Int32Array::from(vec![2])) as ArrayRef,
2843            Arc::new(Int32Array::from(vec![1])) as ArrayRef,
2844        ];
2845        let batch = RecordBatch::try_new(Arc::new(schema.clone()), arrays).unwrap();
2846        // create stream writer
2847        let mut file = tempfile::tempfile().unwrap();
2848        let mut stream_writer = crate::writer::StreamWriter::try_new(&mut file, &schema).unwrap();
2849        stream_writer.write(&batch).unwrap();
2850        stream_writer.finish().unwrap();
2851
2852        drop(stream_writer);
2853
2854        file.rewind().unwrap();
2855
2856        // read stream back
2857        let reader = StreamReader::try_new(&mut file, None).unwrap();
2858
2859        reader.for_each(|batch| {
2860            let batch = batch.unwrap();
2861            assert!(
2862                batch
2863                    .column(0)
2864                    .as_any()
2865                    .downcast_ref::<Float32Array>()
2866                    .unwrap()
2867                    .value(0)
2868                    != 0.0
2869            );
2870            assert!(
2871                batch
2872                    .column(1)
2873                    .as_any()
2874                    .downcast_ref::<Float32Array>()
2875                    .unwrap()
2876                    .value(0)
2877                    != 0.0
2878            );
2879        });
2880
2881        file.rewind().unwrap();
2882
2883        // Read with projection
2884        let reader = StreamReader::try_new(file, Some(vec![0, 3])).unwrap();
2885
2886        reader.for_each(|batch| {
2887            let batch = batch.unwrap();
2888            assert_eq!(batch.schema().fields().len(), 2);
2889            assert_eq!(batch.schema().fields()[0].data_type(), &DataType::Float32);
2890            assert_eq!(batch.schema().fields()[1].data_type(), &DataType::Int32);
2891        });
2892    }
2893
2894    /// Write the record batch to an in-memory buffer in IPC File format
2895    fn write_ipc(rb: &RecordBatch) -> Vec<u8> {
2896        let mut buf = Vec::new();
2897        let mut writer = crate::writer::FileWriter::try_new(&mut buf, rb.schema_ref()).unwrap();
2898        writer.write(rb).unwrap();
2899        writer.finish().unwrap();
2900        buf
2901    }
2902
2903    /// Return the first record batch read from the IPC File buffer
2904    fn read_ipc(buf: &[u8]) -> Result<RecordBatch, ArrowError> {
2905        let mut reader = FileReader::try_new(std::io::Cursor::new(buf), None)?;
2906        reader.next().unwrap()
2907    }
2908
2909    /// Return the first record batch read from the IPC File buffer, disabling
2910    /// validation
2911    fn read_ipc_skip_validation(buf: &[u8]) -> Result<RecordBatch, ArrowError> {
2912        let mut reader = unsafe {
2913            FileReader::try_new(std::io::Cursor::new(buf), None)?.with_skip_validation(true)
2914        };
2915        reader.next().unwrap()
2916    }
2917
2918    fn roundtrip_ipc(rb: &RecordBatch) -> RecordBatch {
2919        let buf = write_ipc(rb);
2920        read_ipc(&buf).unwrap()
2921    }
2922
2923    /// Return the first record batch read from the IPC File buffer
2924    /// using the FileDecoder API
2925    fn read_ipc_with_decoder(buf: Vec<u8>) -> Result<RecordBatch, ArrowError> {
2926        read_ipc_with_decoder_inner(buf, false)
2927    }
2928
2929    /// Return the first record batch read from the IPC File buffer
2930    /// using the FileDecoder API, disabling validation
2931    fn read_ipc_with_decoder_skip_validation(buf: Vec<u8>) -> Result<RecordBatch, ArrowError> {
2932        read_ipc_with_decoder_inner(buf, true)
2933    }
2934
2935    fn read_ipc_with_decoder_inner(
2936        buf: Vec<u8>,
2937        skip_validation: bool,
2938    ) -> Result<RecordBatch, ArrowError> {
2939        let buffer = Buffer::from_vec(buf);
2940        let trailer_start = buffer.len() - 10;
2941        let footer_len = read_footer_length(buffer[trailer_start..].try_into().unwrap())?;
2942        let footer = root_as_footer(&buffer[trailer_start - footer_len..trailer_start])
2943            .map_err(|e| ArrowError::InvalidArgumentError(format!("Invalid footer: {e}")))?;
2944
2945        let schema = try_fb_to_schema(footer.schema().unwrap()).unwrap();
2946
2947        let mut decoder = unsafe {
2948            FileDecoder::new(Arc::new(schema), footer.version())
2949                .with_skip_validation(skip_validation)
2950        };
2951        // Read dictionaries
2952        for block in footer.dictionaries().iter().flatten() {
2953            let block_len = block.bodyLength() as usize + block.metaDataLength() as usize;
2954            let data = buffer.slice_with_length(block.offset() as _, block_len);
2955            decoder.read_dictionary(block, &data)?
2956        }
2957
2958        // Read record batch
2959        let batches = footer.recordBatches().unwrap();
2960        assert_eq!(batches.len(), 1); // Only wrote a single batch
2961
2962        let block = batches.get(0);
2963        let block_len = block.bodyLength() as usize + block.metaDataLength() as usize;
2964        let data = buffer.slice_with_length(block.offset() as _, block_len);
2965        Ok(decoder.read_record_batch(block, &data)?.unwrap())
2966    }
2967
2968    /// Write the record batch to an in-memory buffer in IPC Stream format
2969    fn write_stream(rb: &RecordBatch) -> Vec<u8> {
2970        let mut buf = Vec::new();
2971        let mut writer = crate::writer::StreamWriter::try_new(&mut buf, rb.schema_ref()).unwrap();
2972        writer.write(rb).unwrap();
2973        writer.finish().unwrap();
2974        buf
2975    }
2976
2977    /// Return the first record batch read from the IPC Stream buffer
2978    fn read_stream(buf: &[u8]) -> Result<RecordBatch, ArrowError> {
2979        let mut reader = StreamReader::try_new(std::io::Cursor::new(buf), None)?;
2980        reader.next().unwrap()
2981    }
2982
2983    /// Return the first record batch read from the IPC Stream buffer,
2984    /// disabling validation
2985    fn read_stream_skip_validation(buf: &[u8]) -> Result<RecordBatch, ArrowError> {
2986        let mut reader = unsafe {
2987            StreamReader::try_new(std::io::Cursor::new(buf), None)?.with_skip_validation(true)
2988        };
2989        reader.next().unwrap()
2990    }
2991
2992    /// Return the first record batch read from the IPC Stream buffer
2993    /// using the StreamDecoder API
2994    fn read_stream_with_decoder(
2995        buf: &[u8],
2996        skip_validation: bool,
2997    ) -> Result<RecordBatch, ArrowError> {
2998        let mut decoder = unsafe { StreamDecoder::new().with_skip_validation(skip_validation) };
2999        Ok(decoder.decode(&mut Buffer::from(buf))?.unwrap())
3000    }
3001
3002    fn roundtrip_ipc_stream(rb: &RecordBatch) -> RecordBatch {
3003        let buf = write_stream(rb);
3004        read_stream(&buf).unwrap()
3005    }
3006
3007    #[test]
3008    fn test_roundtrip_with_custom_metadata() {
3009        let schema = Schema::new(vec![Field::new("dummy", DataType::Float64, false)]);
3010        let mut buf = Vec::new();
3011        let mut writer = crate::writer::FileWriter::try_new(&mut buf, &schema).unwrap();
3012        let mut test_metadata = HashMap::new();
3013        test_metadata.insert("abc".to_string(), "abc".to_string());
3014        test_metadata.insert("def".to_string(), "def".to_string());
3015        for (k, v) in &test_metadata {
3016            writer.write_metadata(k, v);
3017        }
3018        writer.finish().unwrap();
3019        drop(writer);
3020
3021        let reader = crate::reader::FileReader::try_new(std::io::Cursor::new(buf), None).unwrap();
3022        assert_eq!(reader.custom_metadata(), &test_metadata);
3023    }
3024
3025    #[test]
3026    fn test_roundtrip_nested_dict() {
3027        let inner: DictionaryArray<Int32Type> = vec!["a", "b", "a"].into_iter().collect();
3028
3029        let array = Arc::new(inner) as ArrayRef;
3030
3031        let dctfield = Arc::new(Field::new("dict", array.data_type().clone(), false));
3032
3033        let s = StructArray::from(vec![(dctfield, array)]);
3034        let struct_array = Arc::new(s) as ArrayRef;
3035
3036        let schema = Arc::new(Schema::new(vec![Field::new(
3037            "struct",
3038            struct_array.data_type().clone(),
3039            false,
3040        )]));
3041
3042        let batch = RecordBatch::try_new(schema, vec![struct_array]).unwrap();
3043
3044        assert_eq!(batch, roundtrip_ipc(&batch));
3045    }
3046
3047    #[test]
3048    fn test_roundtrip_nested_dict_no_preserve_dict_id() {
3049        let inner: DictionaryArray<Int32Type> = vec!["a", "b", "a"].into_iter().collect();
3050
3051        let array = Arc::new(inner) as ArrayRef;
3052
3053        let dctfield = Arc::new(Field::new("dict", array.data_type().clone(), false));
3054
3055        let s = StructArray::from(vec![(dctfield, array)]);
3056        let struct_array = Arc::new(s) as ArrayRef;
3057
3058        let schema = Arc::new(Schema::new(vec![Field::new(
3059            "struct",
3060            struct_array.data_type().clone(),
3061            false,
3062        )]));
3063
3064        let batch = RecordBatch::try_new(schema, vec![struct_array]).unwrap();
3065
3066        let mut buf = Vec::new();
3067        let mut writer = crate::writer::FileWriter::try_new_with_options(
3068            &mut buf,
3069            batch.schema_ref(),
3070            IpcWriteOptions::default(),
3071        )
3072        .unwrap();
3073        writer.write(&batch).unwrap();
3074        writer.finish().unwrap();
3075        drop(writer);
3076
3077        let mut reader = FileReader::try_new(std::io::Cursor::new(buf), None).unwrap();
3078
3079        assert_eq!(batch, reader.next().unwrap().unwrap());
3080    }
3081
3082    fn check_union_with_builder(mut builder: UnionBuilder) {
3083        builder.append::<Int32Type>("a", 1).unwrap();
3084        builder.append_null::<Int32Type>("a").unwrap();
3085        builder.append::<Float64Type>("c", 3.0).unwrap();
3086        builder.append::<Int32Type>("a", 4).unwrap();
3087        builder.append::<Int64Type>("d", 11).unwrap();
3088        let union = builder.build().unwrap();
3089
3090        let schema = Arc::new(Schema::new(vec![Field::new(
3091            "union",
3092            union.data_type().clone(),
3093            false,
3094        )]));
3095
3096        let union_array = Arc::new(union) as ArrayRef;
3097
3098        let rb = RecordBatch::try_new(schema, vec![union_array]).unwrap();
3099        let rb2 = roundtrip_ipc(&rb);
3100        // TODO: equality not yet implemented for union, so we check that the length of the array is
3101        // the same and that all of the buffers are the same instead.
3102        assert_eq!(rb.schema(), rb2.schema());
3103        assert_eq!(rb.num_columns(), rb2.num_columns());
3104        assert_eq!(rb.num_rows(), rb2.num_rows());
3105        let union1 = rb.column(0);
3106        let union2 = rb2.column(0);
3107
3108        assert_eq!(union1, union2);
3109    }
3110
3111    #[test]
3112    fn test_roundtrip_dense_union() {
3113        check_union_with_builder(UnionBuilder::new_dense());
3114    }
3115
3116    #[test]
3117    fn test_roundtrip_sparse_union() {
3118        check_union_with_builder(UnionBuilder::new_sparse());
3119    }
3120
3121    #[test]
3122    fn test_roundtrip_struct_empty_fields() {
3123        let nulls = NullBuffer::from(&[true, true, false]);
3124        let rb = RecordBatch::try_from_iter([(
3125            "",
3126            Arc::new(StructArray::new_empty_fields(nulls.len(), Some(nulls))) as _,
3127        )])
3128        .unwrap();
3129        let rb2 = roundtrip_ipc(&rb);
3130        assert_eq!(rb, rb2);
3131    }
3132
3133    #[test]
3134    fn test_roundtrip_stream_run_array_sliced() {
3135        let run_array_1: Int32RunArray = vec!["a", "a", "a", "b", "b", "c", "c", "c"]
3136            .into_iter()
3137            .collect();
3138        let run_array_1_sliced = run_array_1.slice(2, 5);
3139
3140        let run_array_2_inupt = vec![Some(1_i32), None, None, Some(2), Some(2)];
3141        let mut run_array_2_builder = PrimitiveRunBuilder::<Int16Type, Int32Type>::new();
3142        run_array_2_builder.extend(run_array_2_inupt);
3143        let run_array_2 = run_array_2_builder.finish();
3144
3145        let schema = Arc::new(Schema::new(vec![
3146            Field::new(
3147                "run_array_1_sliced",
3148                run_array_1_sliced.data_type().clone(),
3149                false,
3150            ),
3151            Field::new("run_array_2", run_array_2.data_type().clone(), false),
3152        ]));
3153        let input_batch = RecordBatch::try_new(
3154            schema,
3155            vec![Arc::new(run_array_1_sliced.clone()), Arc::new(run_array_2)],
3156        )
3157        .unwrap();
3158        let output_batch = roundtrip_ipc_stream(&input_batch);
3159
3160        // As partial comparison not yet supported for run arrays, the sliced run array
3161        // has to be unsliced before comparing with the output. the second run array
3162        // can be compared as such.
3163        assert_eq!(input_batch.column(1), output_batch.column(1));
3164
3165        let run_array_1_unsliced = unslice_run_array(run_array_1_sliced.into_data()).unwrap();
3166        assert_eq!(run_array_1_unsliced, output_batch.column(0).into_data());
3167    }
3168
3169    #[test]
3170    fn test_roundtrip_stream_nested_dict() {
3171        let xs = vec!["AA", "BB", "AA", "CC", "BB"];
3172        let dict = Arc::new(
3173            xs.clone()
3174                .into_iter()
3175                .collect::<DictionaryArray<Int8Type>>(),
3176        );
3177        let string_array: ArrayRef = Arc::new(StringArray::from(xs.clone()));
3178        let struct_array = StructArray::from(vec![
3179            (
3180                Arc::new(Field::new("f2.1", DataType::Utf8, false)),
3181                string_array,
3182            ),
3183            (
3184                Arc::new(Field::new("f2.2_struct", dict.data_type().clone(), false)),
3185                dict.clone() as ArrayRef,
3186            ),
3187        ]);
3188        let schema = Arc::new(Schema::new(vec![
3189            Field::new("f1_string", DataType::Utf8, false),
3190            Field::new("f2_struct", struct_array.data_type().clone(), false),
3191        ]));
3192        let input_batch = RecordBatch::try_new(
3193            schema,
3194            vec![
3195                Arc::new(StringArray::from(xs.clone())),
3196                Arc::new(struct_array),
3197            ],
3198        )
3199        .unwrap();
3200        let output_batch = roundtrip_ipc_stream(&input_batch);
3201        assert_eq!(input_batch, output_batch);
3202    }
3203
3204    #[test]
3205    fn test_ipc_writers_reject_dictionary_of_dictionary_schema() {
3206        let values = Arc::new(StringArray::from(vec![Some("a"), Some("b")])) as ArrayRef;
3207        let inner = Arc::new(DictionaryArray::new(
3208            UInt32Array::from_iter_values([0, 1]),
3209            values,
3210        )) as ArrayRef;
3211        let outer = Arc::new(DictionaryArray::new(
3212            UInt32Array::from_iter_values([0, 1, 0]),
3213            inner,
3214        )) as ArrayRef;
3215
3216        let schema = Arc::new(Schema::new(vec![Field::new(
3217            "f1",
3218            outer.data_type().clone(),
3219            false,
3220        )]));
3221        let batch = RecordBatch::try_new(schema, vec![outer]).unwrap();
3222
3223        let mut stream = Vec::new();
3224        let Err(err) = crate::writer::StreamWriter::try_new(&mut stream, batch.schema_ref()) else {
3225            panic!("IPC stream writer should reject dictionary-of-dictionary schemas");
3226        };
3227        assert!(stream.is_empty());
3228
3229        assert!(
3230            err.to_string().contains("dictionary-of-dictionary values"),
3231            "unexpected error: {err}"
3232        );
3233
3234        let mut file = Vec::new();
3235        let Err(err) = crate::writer::FileWriter::try_new(&mut file, batch.schema_ref()) else {
3236            panic!("IPC file writer should reject dictionary-of-dictionary schemas");
3237        };
3238        assert!(file.is_empty());
3239
3240        assert!(
3241            err.to_string().contains("dictionary-of-dictionary values"),
3242            "unexpected error: {err}"
3243        );
3244    }
3245
3246    #[test]
3247    fn test_roundtrip_stream_nested_dict_of_map_of_dict() {
3248        let values = StringArray::from(vec![Some("a"), None, Some("b"), Some("c")]);
3249        let values = Arc::new(values) as ArrayRef;
3250        let value_dict_keys = Int8Array::from_iter_values([0, 1, 1, 2, 3, 1]);
3251        let value_dict_array = DictionaryArray::new(value_dict_keys, values.clone());
3252
3253        let key_dict_keys = Int8Array::from_iter_values([0, 0, 2, 2, 2, 3]);
3254        let key_dict_array = DictionaryArray::new(key_dict_keys, values);
3255
3256        #[expect(deprecated)]
3257        let keys_field = Arc::new(Field::new_dict(
3258            Field::MAP_KEY_FIELD_DEFAULT_NAME,
3259            DataType::Dictionary(Box::new(DataType::Int8), Box::new(DataType::Utf8)),
3260            false,
3261            1,
3262            false,
3263        ));
3264        #[expect(deprecated)]
3265        let values_field = Arc::new(Field::new_dict(
3266            Field::MAP_VALUE_FIELD_DEFAULT_NAME,
3267            DataType::Dictionary(Box::new(DataType::Int8), Box::new(DataType::Utf8)),
3268            true,
3269            2,
3270            false,
3271        ));
3272        let entry_struct = StructArray::from(vec![
3273            (keys_field, make_array(key_dict_array.into_data())),
3274            (values_field, make_array(value_dict_array.into_data())),
3275        ]);
3276        let map_data_type = DataType::Map(
3277            Arc::new(Field::new(
3278                Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
3279                entry_struct.data_type().clone(),
3280                false,
3281            )),
3282            false,
3283        );
3284
3285        let entry_offsets = Buffer::from_slice_ref([0, 2, 4, 6]);
3286        let map_data = ArrayData::builder(map_data_type)
3287            .len(3)
3288            .add_buffer(entry_offsets)
3289            .add_child_data(entry_struct.into_data())
3290            .build()
3291            .unwrap();
3292        let map_array = MapArray::from(map_data);
3293
3294        let dict_keys = Int8Array::from_iter_values([0, 1, 1, 2, 2, 1]);
3295        let dict_dict_array = DictionaryArray::new(dict_keys, Arc::new(map_array));
3296
3297        let schema = Arc::new(Schema::new(vec![Field::new(
3298            "f1",
3299            dict_dict_array.data_type().clone(),
3300            false,
3301        )]));
3302        let input_batch = RecordBatch::try_new(schema, vec![Arc::new(dict_dict_array)]).unwrap();
3303        let output_batch = roundtrip_ipc_stream(&input_batch);
3304        assert_eq!(input_batch, output_batch);
3305    }
3306
3307    fn test_roundtrip_stream_dict_of_list_of_dict_impl<
3308        OffsetSize: OffsetSizeTrait,
3309        U: ArrowNativeType,
3310    >(
3311        list_data_type: DataType,
3312        offsets: &[U; 5],
3313    ) {
3314        let values = StringArray::from(vec![Some("a"), None, Some("c"), None]);
3315        let keys = Int8Array::from_iter_values([0, 0, 1, 2, 0, 1, 3]);
3316        let dict_array = DictionaryArray::new(keys, Arc::new(values));
3317        let dict_data = dict_array.to_data();
3318
3319        let value_offsets = Buffer::from_slice_ref(offsets);
3320
3321        let list_data = ArrayData::builder(list_data_type)
3322            .len(4)
3323            .add_buffer(value_offsets)
3324            .add_child_data(dict_data)
3325            .build()
3326            .unwrap();
3327        let list_array = GenericListArray::<OffsetSize>::from(list_data);
3328
3329        let keys_for_dict = Int8Array::from_iter_values([0, 3, 0, 1, 1, 2, 0, 1, 3]);
3330        let dict_dict_array = DictionaryArray::new(keys_for_dict, Arc::new(list_array));
3331
3332        let schema = Arc::new(Schema::new(vec![Field::new(
3333            "f1",
3334            dict_dict_array.data_type().clone(),
3335            false,
3336        )]));
3337        let input_batch = RecordBatch::try_new(schema, vec![Arc::new(dict_dict_array)]).unwrap();
3338        let output_batch = roundtrip_ipc_stream(&input_batch);
3339        assert_eq!(input_batch, output_batch);
3340    }
3341
3342    #[test]
3343    fn test_roundtrip_stream_dict_of_list_of_dict() {
3344        // list
3345        #[expect(deprecated)]
3346        let list_data_type = DataType::List(Arc::new(Field::new_dict(
3347            "item",
3348            DataType::Dictionary(Box::new(DataType::Int8), Box::new(DataType::Utf8)),
3349            true,
3350            1,
3351            false,
3352        )));
3353        let offsets: &[i32; 5] = &[0, 2, 4, 4, 6];
3354        test_roundtrip_stream_dict_of_list_of_dict_impl::<i32, i32>(list_data_type, offsets);
3355
3356        // large list
3357        #[expect(deprecated)]
3358        let list_data_type = DataType::LargeList(Arc::new(Field::new_dict(
3359            "item",
3360            DataType::Dictionary(Box::new(DataType::Int8), Box::new(DataType::Utf8)),
3361            true,
3362            1,
3363            false,
3364        )));
3365        let offsets: &[i64; 5] = &[0, 2, 4, 4, 7];
3366        test_roundtrip_stream_dict_of_list_of_dict_impl::<i64, i64>(list_data_type, offsets);
3367    }
3368
3369    #[test]
3370    fn test_roundtrip_stream_dict_of_fixed_size_list_of_dict() {
3371        let values = StringArray::from(vec![Some("a"), None, Some("c"), None]);
3372        let keys = Int8Array::from_iter_values([0, 0, 1, 2, 0, 1, 3, 1, 2]);
3373        let dict_array = DictionaryArray::new(keys, Arc::new(values));
3374        let dict_data = dict_array.into_data();
3375
3376        #[expect(deprecated)]
3377        let list_data_type = DataType::FixedSizeList(
3378            Arc::new(Field::new_dict(
3379                "item",
3380                DataType::Dictionary(Box::new(DataType::Int8), Box::new(DataType::Utf8)),
3381                true,
3382                1,
3383                false,
3384            )),
3385            3,
3386        );
3387        let list_data = ArrayData::builder(list_data_type)
3388            .len(3)
3389            .add_child_data(dict_data)
3390            .build()
3391            .unwrap();
3392        let list_array = FixedSizeListArray::from(list_data);
3393
3394        let keys_for_dict = Int8Array::from_iter_values([0, 1, 0, 1, 1, 2, 0, 1, 2]);
3395        let dict_dict_array = DictionaryArray::new(keys_for_dict, Arc::new(list_array));
3396
3397        let schema = Arc::new(Schema::new(vec![Field::new(
3398            "f1",
3399            dict_dict_array.data_type().clone(),
3400            false,
3401        )]));
3402        let input_batch = RecordBatch::try_new(schema, vec![Arc::new(dict_dict_array)]).unwrap();
3403        let output_batch = roundtrip_ipc_stream(&input_batch);
3404        assert_eq!(input_batch, output_batch);
3405    }
3406
3407    const LONG_TEST_STRING: &str =
3408        "This is a long string to make sure binary view array handles it";
3409
3410    #[test]
3411    fn test_roundtrip_view_types() {
3412        let schema = Schema::new(vec![
3413            Field::new("field_1", DataType::BinaryView, true),
3414            Field::new("field_2", DataType::Utf8, true),
3415            Field::new("field_3", DataType::Utf8View, true),
3416        ]);
3417        let bin_values: Vec<Option<&[u8]>> = vec![
3418            Some(b"foo"),
3419            None,
3420            Some(b"bar"),
3421            Some(LONG_TEST_STRING.as_bytes()),
3422        ];
3423        let utf8_values: Vec<Option<&str>> =
3424            vec![Some("foo"), None, Some("bar"), Some(LONG_TEST_STRING)];
3425        let bin_view_array = BinaryViewArray::from_iter(bin_values);
3426        let utf8_array = StringArray::from_iter(utf8_values.iter());
3427        let utf8_view_array = StringViewArray::from_iter(utf8_values);
3428        let record_batch = RecordBatch::try_new(
3429            Arc::new(schema.clone()),
3430            vec![
3431                Arc::new(bin_view_array),
3432                Arc::new(utf8_array),
3433                Arc::new(utf8_view_array),
3434            ],
3435        )
3436        .unwrap();
3437
3438        assert_eq!(record_batch, roundtrip_ipc(&record_batch));
3439        assert_eq!(record_batch, roundtrip_ipc_stream(&record_batch));
3440
3441        let sliced_batch = record_batch.slice(1, 2);
3442        assert_eq!(sliced_batch, roundtrip_ipc(&sliced_batch));
3443        assert_eq!(sliced_batch, roundtrip_ipc_stream(&sliced_batch));
3444    }
3445
3446    #[test]
3447    fn test_roundtrip_view_types_nested_dict() {
3448        let bin_values: Vec<Option<&[u8]>> = vec![
3449            Some(b"foo"),
3450            None,
3451            Some(b"bar"),
3452            Some(LONG_TEST_STRING.as_bytes()),
3453            Some(b"field"),
3454        ];
3455        let utf8_values: Vec<Option<&str>> = vec![
3456            Some("foo"),
3457            None,
3458            Some("bar"),
3459            Some(LONG_TEST_STRING),
3460            Some("field"),
3461        ];
3462        let bin_view_array = Arc::new(BinaryViewArray::from_iter(bin_values));
3463        let utf8_view_array = Arc::new(StringViewArray::from_iter(utf8_values));
3464
3465        let key_dict_keys = Int8Array::from_iter_values([0, 0, 2, 2, 0, 2, 3]);
3466        let key_dict_array = DictionaryArray::new(key_dict_keys, utf8_view_array.clone());
3467        #[expect(deprecated)]
3468        let keys_field = Arc::new(Field::new_dict(
3469            Field::MAP_KEY_FIELD_DEFAULT_NAME,
3470            DataType::Dictionary(Box::new(DataType::Int8), Box::new(DataType::Utf8View)),
3471            false,
3472            1,
3473            false,
3474        ));
3475
3476        let value_dict_keys = Int8Array::from_iter_values([0, 3, 0, 1, 2, 0, 1]);
3477        let value_dict_array = DictionaryArray::new(value_dict_keys, bin_view_array);
3478        #[expect(deprecated)]
3479        let values_field = Arc::new(Field::new_dict(
3480            Field::MAP_VALUE_FIELD_DEFAULT_NAME,
3481            DataType::Dictionary(Box::new(DataType::Int8), Box::new(DataType::BinaryView)),
3482            true,
3483            2,
3484            false,
3485        ));
3486        let entry_struct = StructArray::from(vec![
3487            (keys_field, make_array(key_dict_array.into_data())),
3488            (values_field, make_array(value_dict_array.into_data())),
3489        ]);
3490
3491        let map_data_type = DataType::Map(
3492            Arc::new(Field::new(
3493                Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
3494                entry_struct.data_type().clone(),
3495                false,
3496            )),
3497            false,
3498        );
3499        let entry_offsets = Buffer::from_slice_ref([0, 2, 4, 7]);
3500        let map_data = ArrayData::builder(map_data_type)
3501            .len(3)
3502            .add_buffer(entry_offsets)
3503            .add_child_data(entry_struct.into_data())
3504            .build()
3505            .unwrap();
3506        let map_array = MapArray::from(map_data);
3507
3508        let dict_keys = Int8Array::from_iter_values([0, 1, 0, 1, 1, 2, 0, 1, 2]);
3509        let dict_dict_array = DictionaryArray::new(dict_keys, Arc::new(map_array));
3510        let schema = Arc::new(Schema::new(vec![Field::new(
3511            "f1",
3512            dict_dict_array.data_type().clone(),
3513            false,
3514        )]));
3515        let batch = RecordBatch::try_new(schema, vec![Arc::new(dict_dict_array)]).unwrap();
3516        assert_eq!(batch, roundtrip_ipc(&batch));
3517        assert_eq!(batch, roundtrip_ipc_stream(&batch));
3518
3519        let sliced_batch = batch.slice(1, 2);
3520        assert_eq!(sliced_batch, roundtrip_ipc(&sliced_batch));
3521        assert_eq!(sliced_batch, roundtrip_ipc_stream(&sliced_batch));
3522    }
3523
3524    #[test]
3525    fn test_no_columns_batch() {
3526        let schema = Arc::new(Schema::empty());
3527        let options = RecordBatchOptions::new()
3528            .with_match_field_names(true)
3529            .with_row_count(Some(10));
3530        let input_batch = RecordBatch::try_new_with_options(schema, vec![], &options).unwrap();
3531        let output_batch = roundtrip_ipc_stream(&input_batch);
3532        assert_eq!(input_batch, output_batch);
3533    }
3534
3535    #[test]
3536    fn test_unaligned() {
3537        let batch = RecordBatch::try_from_iter(vec![(
3538            "i32",
3539            Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _,
3540        )])
3541        .unwrap();
3542
3543        let r#gen = IpcDataGenerator {};
3544        let mut dict_tracker = DictionaryTracker::new(false);
3545        let (_, encoded) = r#gen
3546            .encode(
3547                &batch,
3548                &mut dict_tracker,
3549                &Default::default(),
3550                &mut Default::default(),
3551            )
3552            .unwrap();
3553
3554        let message = root_as_message(&encoded.ipc_message).unwrap();
3555
3556        // Construct an unaligned buffer
3557        let mut buffer = MutableBuffer::with_capacity(encoded.arrow_data.len() + 1);
3558        buffer.push(0_u8);
3559        buffer.extend_from_slice(&encoded.arrow_data);
3560        let b = Buffer::from(buffer).slice(1);
3561        assert_ne!(b.as_ptr().align_offset(8), 0);
3562
3563        let ipc_batch = message.header_as_record_batch().unwrap();
3564        let roundtrip = RecordBatchDecoder::try_new(
3565            &b,
3566            ipc_batch,
3567            batch.schema(),
3568            &Default::default(),
3569            &message.version(),
3570        )
3571        .unwrap()
3572        .with_require_alignment(false)
3573        .read_record_batch()
3574        .unwrap();
3575        assert_eq!(batch, roundtrip);
3576    }
3577
3578    #[test]
3579    fn test_unaligned_throws_error_with_require_alignment() {
3580        let batch = RecordBatch::try_from_iter(vec![(
3581            "i32",
3582            Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _,
3583        )])
3584        .unwrap();
3585
3586        let r#gen = IpcDataGenerator {};
3587        let mut dict_tracker = DictionaryTracker::new(false);
3588        let (_, encoded) = r#gen
3589            .encode(
3590                &batch,
3591                &mut dict_tracker,
3592                &Default::default(),
3593                &mut Default::default(),
3594            )
3595            .unwrap();
3596
3597        let message = root_as_message(&encoded.ipc_message).unwrap();
3598
3599        // Construct an unaligned buffer
3600        let mut buffer = MutableBuffer::with_capacity(encoded.arrow_data.len() + 1);
3601        buffer.push(0_u8);
3602        buffer.extend_from_slice(&encoded.arrow_data);
3603        let b = Buffer::from(buffer).slice(1);
3604        assert_ne!(b.as_ptr().align_offset(8), 0);
3605
3606        let ipc_batch = message.header_as_record_batch().unwrap();
3607        let result = RecordBatchDecoder::try_new(
3608            &b,
3609            ipc_batch,
3610            batch.schema(),
3611            &Default::default(),
3612            &message.version(),
3613        )
3614        .unwrap()
3615        .with_require_alignment(true)
3616        .read_record_batch();
3617
3618        let error = result.unwrap_err();
3619        assert_eq!(
3620            error.to_string(),
3621            "Invalid argument error: Misaligned buffers[0] in array of type Int32, \
3622             offset from expected alignment of 4 by 1"
3623        );
3624    }
3625
3626    /// Verify that misaligned IPC buffers are caught by `require_alignment = true`.
3627    ///
3628    /// For each array type we shift the IPC body by every byte offset in 0..OFFSET_RANGE and
3629    /// assert that the decoder errors exactly when the offset violates the type's
3630    /// minimum alignment requirement.  The Arrow columnar spec permits alignment to
3631    /// any multiple of 8 or 64 bytes, so multiples of 8 (which include every multiple
3632    /// of 64) must succeed; everything else must return a "Misaligned buffers" error.
3633    /// See <https://arrow.apache.org/docs/format/Columnar.html#buffer-alignment-and-padding>.
3634    #[test]
3635    fn test_misaligned_buffers_error() {
3636        const OFFSET_RANGE: usize = 128;
3637
3638        // (array, minimum required alignment in bytes)
3639        // Fixed-width: alignment == element byte width.
3640        // Variable-width (e.g. StringArray): the offsets buffer drives alignment (Int32 → 4).
3641        let cases: Vec<(ArrayRef, usize)> = vec![
3642            (Arc::new(Int32Array::from_iter(0i32..100)) as _, 4),
3643            (Arc::new(Int64Array::from_iter(0i64..100)) as _, 8),
3644            (
3645                Arc::new(StringArray::from_iter_values(
3646                    (0..100).map(|i| i.to_string()),
3647                )) as _,
3648                4,
3649            ),
3650            (
3651                Arc::new(LargeStringArray::from_iter_values(
3652                    (0..100).map(|i| i.to_string()),
3653                )) as _,
3654                8,
3655            ),
3656        ];
3657
3658        for (array, alignment) in cases {
3659            let batch = RecordBatch::try_from_iter(vec![("col", Arc::clone(&array))]).unwrap();
3660            let encoder = IpcDataGenerator {};
3661            let mut dict_tracker = DictionaryTracker::new(false);
3662            let (_, encoded) = encoder
3663                .encode(
3664                    &batch,
3665                    &mut dict_tracker,
3666                    &Default::default(),
3667                    &mut Default::default(),
3668                )
3669                .unwrap();
3670            let message = root_as_message(&encoded.ipc_message).unwrap();
3671            let ipc_batch = message.header_as_record_batch().unwrap();
3672
3673            for offset in 0..OFFSET_RANGE {
3674                // MutableBuffer always allocates at a 64-byte aligned base address.
3675                // Slicing by `offset` bytes yields a pointer at `base_ptr + offset`,
3676                // whose alignment is gcd(64, offset).  That makes `offset` the sole
3677                // determinant of the resulting alignment — without this guarantee the
3678                // test would be non-deterministic depending on what the allocator returns.
3679                let mut storage = MutableBuffer::with_capacity(encoded.arrow_data.len() + offset);
3680                for _ in 0..offset {
3681                    storage.push(0_u8);
3682                }
3683                storage.extend_from_slice(&encoded.arrow_data);
3684                let buf = Buffer::from(storage).slice(offset);
3685
3686                let result = RecordBatchDecoder::try_new(
3687                    &buf,
3688                    ipc_batch,
3689                    batch.schema(),
3690                    &Default::default(),
3691                    &message.version(),
3692                )
3693                .unwrap()
3694                .with_require_alignment(true)
3695                .read_record_batch();
3696
3697                if offset % alignment == 0 {
3698                    assert!(
3699                        result.is_ok(),
3700                        "type={} offset={offset}: expected Ok but got {:?}",
3701                        array.data_type(),
3702                        result.unwrap_err(),
3703                    );
3704                } else {
3705                    let err = result
3706                        .expect_err(&format!(
3707                            "type={} offset={offset}: expected Err for misaligned buffer",
3708                            array.data_type()
3709                        ))
3710                        .to_string();
3711                    assert!(
3712                        err.contains("Misaligned buffers"),
3713                        "type={} offset={offset}: unexpected error: {err}",
3714                        array.data_type(),
3715                    );
3716                }
3717            }
3718        }
3719    }
3720
3721    #[test]
3722    #[cfg_attr(miri, ignore)] // Takes too long
3723    fn test_file_with_massive_column_count() {
3724        // 499_999 is upper limit for default settings (1_000_000)
3725        let limit = 600_000;
3726
3727        let fields = (0..limit)
3728            .map(|i| Field::new(format!("{i}"), DataType::Boolean, false))
3729            .collect::<Vec<_>>();
3730        let schema = Arc::new(Schema::new(fields));
3731        let batch = RecordBatch::new_empty(schema);
3732
3733        let mut buf = Vec::new();
3734        let mut writer = crate::writer::FileWriter::try_new(&mut buf, batch.schema_ref()).unwrap();
3735        writer.write(&batch).unwrap();
3736        writer.finish().unwrap();
3737        drop(writer);
3738
3739        let mut reader = FileReaderBuilder::new()
3740            .with_max_footer_fb_tables(1_500_000)
3741            .build(std::io::Cursor::new(buf))
3742            .unwrap();
3743        let roundtrip_batch = reader.next().unwrap().unwrap();
3744
3745        assert_eq!(batch, roundtrip_batch);
3746    }
3747
3748    #[test]
3749    fn test_file_with_deeply_nested_columns() {
3750        // 60 is upper limit for default settings (64)
3751        let limit = 61;
3752
3753        let fields = (0..limit).fold(
3754            vec![Field::new("leaf", DataType::Boolean, false)],
3755            |field, index| vec![Field::new_struct(format!("{index}"), field, false)],
3756        );
3757        let schema = Arc::new(Schema::new(fields));
3758        let batch = RecordBatch::new_empty(schema);
3759
3760        let mut buf = Vec::new();
3761        let mut writer = crate::writer::FileWriter::try_new(&mut buf, batch.schema_ref()).unwrap();
3762        writer.write(&batch).unwrap();
3763        writer.finish().unwrap();
3764        drop(writer);
3765
3766        let mut reader = FileReaderBuilder::new()
3767            .with_max_footer_fb_depth(65)
3768            .build(std::io::Cursor::new(buf))
3769            .unwrap();
3770        let roundtrip_batch = reader.next().unwrap().unwrap();
3771
3772        assert_eq!(batch, roundtrip_batch);
3773    }
3774
3775    #[test]
3776    fn test_stream_reader_rejects_short_validity_buffer() {
3777        // Reproduce #7124: serialize an Int32Array with too few validity bits.
3778        let data = ArrayDataBuilder::new(DataType::Int32)
3779            .len(8000)
3780            .add_buffer(ScalarBuffer::<i32>::from_iter(0..8000).into())
3781            .nulls(Some(NullBuffer::from(&[true, false, true, false])));
3782        let array: ArrayRef = unsafe { Arc::new(Int32Array::from(data.build_unchecked())) };
3783        let batch = RecordBatch::try_from_iter([("a", array)]).unwrap();
3784
3785        let mut stream = Vec::new();
3786        let mut writer =
3787            crate::writer::StreamWriter::try_new(&mut stream, &batch.schema()).unwrap();
3788        writer.write(&batch).unwrap();
3789        writer.finish().unwrap();
3790
3791        let mut reader = StreamReader::try_new(Cursor::new(stream), None).unwrap();
3792        let err = reader.next().unwrap().unwrap_err();
3793        assert_eq!(
3794            err.to_string(),
3795            "Invalid argument error: null_bit_buffer size too small. got 1 needed 1000"
3796        );
3797    }
3798
3799    #[test]
3800    fn test_stream_reader_rejects_short_struct_validity_buffer() {
3801        // Serialize a StructArray with too few validity bits.
3802        let fields = Fields::from(vec![Field::new("b", DataType::Int32, false)]);
3803        let child: ArrayRef = Arc::new(Int32Array::from_iter_values(0..8000));
3804        let nulls = NullBuffer::from(&[true, false, true, false]);
3805        let array: ArrayRef =
3806            unsafe { Arc::new(StructArray::new_unchecked(fields, vec![child], Some(nulls))) };
3807        let batch = RecordBatch::try_from_iter([("a", array)]).unwrap();
3808
3809        let mut stream = Vec::new();
3810        let mut writer =
3811            crate::writer::StreamWriter::try_new(&mut stream, &batch.schema()).unwrap();
3812        writer.write(&batch).unwrap();
3813        writer.finish().unwrap();
3814
3815        let mut reader = StreamReader::try_new(Cursor::new(stream), None).unwrap();
3816        let err = reader.next().unwrap().unwrap_err();
3817        assert_eq!(
3818            err.to_string(),
3819            "Invalid argument error: null_bit_buffer size too small. got 1 needed 1000"
3820        );
3821    }
3822
3823    #[test]
3824    fn test_invalid_struct_array_ipc_read_errors() {
3825        let a_field = Field::new("a", DataType::Int32, false);
3826        let b_field = Field::new("b", DataType::Int32, false);
3827        let struct_fields = Fields::from(vec![a_field.clone(), b_field.clone()]);
3828
3829        let a_array_data = ArrayData::builder(a_field.data_type().clone())
3830            .len(4)
3831            .add_buffer(Buffer::from_slice_ref([1, 2, 3, 4]))
3832            .build()
3833            .unwrap();
3834        let b_array_data = ArrayData::builder(b_field.data_type().clone())
3835            .len(3)
3836            .add_buffer(Buffer::from_slice_ref([5, 6, 7]))
3837            .build()
3838            .unwrap();
3839
3840        let invalid_struct_arr = unsafe {
3841            StructArray::new_unchecked(
3842                struct_fields,
3843                vec![make_array(a_array_data), make_array(b_array_data)],
3844                None,
3845            )
3846        };
3847
3848        expect_ipc_validation_error(
3849            Arc::new(invalid_struct_arr),
3850            "Invalid argument error: Incorrect array length for StructArray field \"b\", expected 4 got 3",
3851        );
3852    }
3853
3854    #[test]
3855    fn test_invalid_nested_array_ipc_read_errors() {
3856        // one of the nested arrays has invalid data
3857        let a_field = Field::new("a", DataType::Int32, false);
3858        let b_field = Field::new("b", DataType::Utf8, false);
3859
3860        let schema = Arc::new(Schema::new(vec![Field::new_struct(
3861            "s",
3862            vec![a_field.clone(), b_field.clone()],
3863            false,
3864        )]));
3865
3866        let a_array_data = ArrayData::builder(a_field.data_type().clone())
3867            .len(4)
3868            .add_buffer(Buffer::from_slice_ref([1, 2, 3, 4]))
3869            .build()
3870            .unwrap();
3871        // invalid nested child array -- length is correct, but has invalid utf8 data
3872        let b_array_data = {
3873            let valid: &[u8] = b"   ";
3874            let mut invalid = vec![];
3875            invalid.extend_from_slice(b"ValidString");
3876            invalid.extend_from_slice(INVALID_UTF8_FIRST_CHAR);
3877            let binary_array =
3878                BinaryArray::from_iter(vec![None, Some(valid), None, Some(&invalid)]);
3879            let array = unsafe {
3880                StringArray::new_unchecked(
3881                    binary_array.offsets().clone(),
3882                    binary_array.values().clone(),
3883                    binary_array.nulls().cloned(),
3884                )
3885            };
3886            array.into_data()
3887        };
3888        let struct_data_type = schema.field(0).data_type();
3889
3890        let invalid_struct_arr = unsafe {
3891            make_array(
3892                ArrayData::builder(struct_data_type.clone())
3893                    .len(4)
3894                    .add_child_data(a_array_data)
3895                    .add_child_data(b_array_data)
3896                    .build_unchecked(),
3897            )
3898        };
3899        expect_ipc_validation_error(
3900            invalid_struct_arr,
3901            "Invalid argument error: Invalid UTF8 sequence at string index 3 (3..18): invalid utf-8 sequence of 1 bytes from index 11",
3902        );
3903    }
3904
3905    #[test]
3906    #[expect(deprecated)]
3907    fn test_first_field_with_dict_id_matches_fields_with_dict_id() {
3908        let dict = |name: &str, id: i64| {
3909            let data_type =
3910                DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8));
3911            Arc::new(Field::new_dict(name, data_type, true, id, false))
3912        };
3913        let dict_of_list_of_dict = |name: &str, outer_id: i64, inner_id: i64| {
3914            let inner = DataType::List(dict("inner", inner_id));
3915            let data_type = DataType::Dictionary(Box::new(DataType::Int32), Box::new(inner));
3916            Field::new_dict(name, data_type, true, outer_id, false)
3917        };
3918        // Duplicate IDs check that the first match in pre-order wins.
3919        let schema = Schema::new(vec![
3920            // dict_id is ignored on non-dictionary fields.
3921            Field::new_dict("not_a_dictionary", DataType::Utf8, true, 3, false),
3922            Field::new_struct(
3923                "struct",
3924                vec![dict("struct.a", 1), dict("struct.b", 2)],
3925                true,
3926            ),
3927            Field::new(
3928                "union",
3929                DataType::Union(
3930                    UnionFields::try_new(vec![0, 1], vec![dict("union.a", 3), dict("union.b", 1)])
3931                        .unwrap(),
3932                    UnionMode::Dense,
3933                ),
3934                true,
3935            ),
3936            Field::new("list", DataType::List(dict("list.item", 4)), true),
3937            Field::new_map(
3938                "map",
3939                "entries",
3940                dict("key", 5),
3941                dict("value", 2),
3942                false,
3943                true,
3944            ),
3945            Field::new(
3946                "ree",
3947                DataType::RunEndEncoded(
3948                    Arc::new(Field::new("run_ends", DataType::Int32, false)),
3949                    dict("ree.values", 6),
3950                ),
3951                true,
3952            ),
3953            dict_of_list_of_dict("dict_of_list_of_dict", 7, 8),
3954            dict_of_list_of_dict("same_outer_and_inner_id", 9, 9),
3955        ]);
3956
3957        for id in -1..=10 {
3958            let expected = schema.fields_with_dict_id(id).first().map(|f| f.name());
3959            let actual = first_field_with_dict_id(schema.fields(), id).map(|f| f.name());
3960            assert_eq!(actual, expected, "dictionary id {id}");
3961        }
3962    }
3963
3964    #[test]
3965    fn test_same_dict_id_without_preserve() {
3966        let batch = RecordBatch::try_new(
3967            Arc::new(Schema::new(
3968                ["a", "b"]
3969                    .iter()
3970                    .map(|name| {
3971                        #[expect(deprecated)]
3972                        Field::new_dict(
3973                            name.to_string(),
3974                            DataType::Dictionary(
3975                                Box::new(DataType::Int32),
3976                                Box::new(DataType::Utf8),
3977                            ),
3978                            true,
3979                            0,
3980                            false,
3981                        )
3982                    })
3983                    .collect::<Vec<Field>>(),
3984            )),
3985            vec![
3986                Arc::new(
3987                    vec![Some("c"), Some("d")]
3988                        .into_iter()
3989                        .collect::<DictionaryArray<Int32Type>>(),
3990                ) as ArrayRef,
3991                Arc::new(
3992                    vec![Some("e"), Some("f")]
3993                        .into_iter()
3994                        .collect::<DictionaryArray<Int32Type>>(),
3995                ) as ArrayRef,
3996            ],
3997        )
3998        .expect("Failed to create RecordBatch");
3999
4000        // serialize the record batch as an IPC stream
4001        let mut buf = vec![];
4002        {
4003            let mut writer = crate::writer::StreamWriter::try_new_with_options(
4004                &mut buf,
4005                batch.schema().as_ref(),
4006                crate::writer::IpcWriteOptions::default(),
4007            )
4008            .expect("Failed to create StreamWriter");
4009            writer.write(&batch).expect("Failed to write RecordBatch");
4010            writer.finish().expect("Failed to finish StreamWriter");
4011        }
4012
4013        StreamReader::try_new(std::io::Cursor::new(buf), None)
4014            .expect("Failed to create StreamReader")
4015            .for_each(|decoded_batch| {
4016                assert_eq!(decoded_batch.expect("Failed to read RecordBatch"), batch);
4017            });
4018    }
4019
4020    #[test]
4021    fn test_validation_of_invalid_list_array() {
4022        // ListArray with invalid offsets
4023        let array = unsafe {
4024            let values = Int32Array::from(vec![1, 2, 3]);
4025            let bad_offsets = ScalarBuffer::<i32>::from(vec![0, 2, 4, 2]); // offsets can't go backwards
4026            let offsets = OffsetBuffer::new_unchecked(bad_offsets); // INVALID array created
4027            let field = Field::new_list_field(DataType::Int32, true);
4028            let nulls = None;
4029            ListArray::new(Arc::new(field), offsets, Arc::new(values), nulls)
4030        };
4031
4032        expect_ipc_validation_error(
4033            Arc::new(array),
4034            "Invalid argument error: Offset invariant failure: offset at position 2 out of bounds: 4 > 2",
4035        );
4036    }
4037
4038    #[test]
4039    fn test_validation_of_invalid_string_array() {
4040        let valid: &[u8] = b"   ";
4041        let mut invalid = vec![];
4042        invalid.extend_from_slice(b"ThisStringIsCertainlyLongerThan12Bytes");
4043        invalid.extend_from_slice(INVALID_UTF8_FIRST_CHAR);
4044        let binary_array = BinaryArray::from_iter(vec![None, Some(valid), None, Some(&invalid)]);
4045        // data is not valid utf8 we can not construct a correct StringArray
4046        // safely, so purposely create an invalid StringArray
4047        let array = unsafe {
4048            StringArray::new_unchecked(
4049                binary_array.offsets().clone(),
4050                binary_array.values().clone(),
4051                binary_array.nulls().cloned(),
4052            )
4053        };
4054        expect_ipc_validation_error(
4055            Arc::new(array),
4056            "Invalid argument error: Invalid UTF8 sequence at string index 3 (3..45): invalid utf-8 sequence of 1 bytes from index 38",
4057        );
4058    }
4059
4060    #[test]
4061    fn test_validation_of_invalid_string_view_array() {
4062        let valid: &[u8] = b"   ";
4063        let mut invalid = vec![];
4064        invalid.extend_from_slice(b"ThisStringIsCertainlyLongerThan12Bytes");
4065        invalid.extend_from_slice(INVALID_UTF8_FIRST_CHAR);
4066        let binary_view_array =
4067            BinaryViewArray::from_iter(vec![None, Some(valid), None, Some(&invalid)]);
4068        // data is not valid utf8 we can not construct a correct StringArray
4069        // safely, so purposely create an invalid StringArray
4070        let array = unsafe {
4071            StringViewArray::new_unchecked(
4072                binary_view_array.views().clone(),
4073                Arc::clone(binary_view_array.data_buffers()),
4074                binary_view_array.nulls().cloned(),
4075            )
4076        };
4077        expect_ipc_validation_error(
4078            Arc::new(array),
4079            "Invalid argument error: Encountered non-UTF-8 data at index 3: invalid utf-8 sequence of 1 bytes from index 38",
4080        );
4081    }
4082
4083    /// return an invalid dictionary array (key is larger than values)
4084    /// ListArray with invalid offsets
4085    #[test]
4086    fn test_validation_of_invalid_dictionary_array() {
4087        let array = unsafe {
4088            let values = StringArray::from_iter_values(["a", "b", "c"]);
4089            let keys = Int32Array::from(vec![1, 200]); // keys are not valid for values
4090            DictionaryArray::new_unchecked(keys, Arc::new(values))
4091        };
4092
4093        expect_ipc_validation_error(
4094            Arc::new(array),
4095            "Invalid argument error: Value at position 1 out of bounds: 200 (should be in [0, 2])",
4096        );
4097    }
4098
4099    #[test]
4100    fn test_validation_of_invalid_union_array() {
4101        let array = unsafe {
4102            let fields = UnionFields::try_new(
4103                vec![1, 3], // typeids : type id 2 is not valid
4104                vec![
4105                    Field::new("a", DataType::Int32, false),
4106                    Field::new("b", DataType::Utf8, false),
4107                ],
4108            )
4109            .unwrap();
4110            let type_ids = ScalarBuffer::from(vec![1i8, 2, 3]); // 2 is invalid
4111            let offsets = None;
4112            let children: Vec<ArrayRef> = vec![
4113                Arc::new(Int32Array::from(vec![10, 20, 30])),
4114                Arc::new(StringArray::from(vec![Some("a"), Some("b"), Some("c")])),
4115            ];
4116
4117            UnionArray::new_unchecked(fields, type_ids, offsets, children)
4118        };
4119
4120        expect_ipc_validation_error(
4121            Arc::new(array),
4122            "Invalid argument error: Type Ids values must match one of the field type ids",
4123        );
4124    }
4125
4126    /// Invalid Utf-8 sequence in the first character
4127    /// <https://stackoverflow.com/questions/1301402/example-invalid-utf8-string>
4128    const INVALID_UTF8_FIRST_CHAR: &[u8] = &[0xa0, 0xa1, 0x20, 0x20];
4129
4130    /// Expect an error when reading the record batch using IPC or IPC Streams
4131    fn expect_ipc_validation_error(array: ArrayRef, expected_err: &str) {
4132        let rb = RecordBatch::try_from_iter([("a", array)]).unwrap();
4133
4134        // IPC Stream format
4135        let buf = write_stream(&rb); // write is ok
4136        read_stream_skip_validation(&buf).unwrap();
4137        let err = read_stream(&buf).unwrap_err();
4138        assert_eq!(err.to_string(), expected_err);
4139
4140        // IPC Stream format with StreamDecoder
4141        read_stream_with_decoder(&buf, true).unwrap();
4142        let err = read_stream_with_decoder(&buf, false).unwrap_err();
4143        assert_eq!(err.to_string(), expected_err);
4144
4145        // IPC File format
4146        let buf = write_ipc(&rb); // write is ok
4147        read_ipc_skip_validation(&buf).unwrap();
4148        let err = read_ipc(&buf).unwrap_err();
4149        assert_eq!(err.to_string(), expected_err);
4150
4151        // IPC Format with FileDecoder
4152        read_ipc_with_decoder_skip_validation(buf.clone()).unwrap();
4153        let err = read_ipc_with_decoder(buf).unwrap_err();
4154        assert_eq!(err.to_string(), expected_err);
4155    }
4156
4157    #[test]
4158    fn test_roundtrip_schema() {
4159        let schema = Schema::new(vec![
4160            Field::new(
4161                "a",
4162                DataType::Dictionary(Box::new(DataType::UInt16), Box::new(DataType::Utf8)),
4163                false,
4164            ),
4165            Field::new(
4166                "b",
4167                DataType::Dictionary(Box::new(DataType::UInt16), Box::new(DataType::Utf8)),
4168                false,
4169            ),
4170        ]);
4171
4172        let options = IpcWriteOptions::default();
4173        let data_gen = IpcDataGenerator::default();
4174        let mut dict_tracker = DictionaryTracker::new(false);
4175        let encoded_data =
4176            data_gen.schema_to_bytes_with_dictionary_tracker(&schema, &mut dict_tracker, &options);
4177        let mut schema_bytes = vec![];
4178        write_message(&mut schema_bytes, encoded_data, &options).expect("write_message");
4179
4180        let begin_offset: usize = if schema_bytes[0..4].eq(&CONTINUATION_MARKER) {
4181            4
4182        } else {
4183            0
4184        };
4185
4186        size_prefixed_root_as_message(&schema_bytes[begin_offset..])
4187            .expect_err("size_prefixed_root_as_message");
4188
4189        let msg = parse_message(&schema_bytes).expect("parse_message");
4190        let ipc_schema = msg.header_as_schema().expect("header_as_schema");
4191        let new_schema = try_fb_to_schema(ipc_schema).unwrap();
4192
4193        assert_eq!(schema, new_schema);
4194    }
4195
4196    #[test]
4197    fn test_negative_meta_len() {
4198        let bytes = i32::to_le_bytes(-1);
4199        let mut buf = vec![];
4200        buf.extend(CONTINUATION_MARKER);
4201        buf.extend(bytes);
4202
4203        let reader = StreamReader::try_new(Cursor::new(buf), None);
4204        assert!(reader.is_err());
4205    }
4206
4207    /// Per the IPC specification, dictionary batches may be omitted for
4208    /// dictionary-encoded columns where all values are null.  The C++
4209    /// implementation relies on this and does not emit a dictionary batch
4210    /// in that case.  Verify that the Rust reader handles such streams
4211    /// by synthesizing an empty dictionary instead of returning an error.
4212    #[test]
4213    fn test_read_null_dict_without_dictionary_batch() {
4214        // Build an all-null dictionary-encoded column.
4215        let keys = Int32Array::new_null(4);
4216        let values: ArrayRef = new_empty_array(&DataType::Utf8);
4217        let dict_array = DictionaryArray::new(keys, values);
4218
4219        let schema = Arc::new(Schema::new(vec![Field::new(
4220            "d",
4221            dict_array.data_type().clone(),
4222            true,
4223        )]));
4224        let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(dict_array)]).unwrap();
4225
4226        // Write a normal IPC stream (which includes the dictionary batch).
4227        let full_stream = write_stream(&batch);
4228
4229        // Parse the stream into individual messages and reconstruct it
4230        // without the DictionaryBatch message, simulating what C++ emits
4231        // for an all-null dictionary column.
4232        let mut stripped = Vec::new();
4233        let mut cursor = Cursor::new(&full_stream);
4234        loop {
4235            // Each message is: [continuation (4 bytes)] [meta_len (4 bytes)]
4236            //                   [metadata (meta_len bytes)] [body (bodyLength bytes)]
4237            let mut header = [0u8; 4];
4238            if cursor.read_exact(&mut header).is_err() {
4239                break;
4240            }
4241            if header == CONTINUATION_MARKER && cursor.read_exact(&mut header).is_err() {
4242                break;
4243            }
4244            let meta_len = u32::from_le_bytes(header) as usize;
4245            if meta_len == 0 {
4246                // EOS marker — write it through.
4247                stripped.extend_from_slice(&CONTINUATION_MARKER);
4248                stripped.extend_from_slice(&0u32.to_le_bytes());
4249                break;
4250            }
4251            let mut meta_buf = vec![0u8; meta_len];
4252            cursor.read_exact(&mut meta_buf).unwrap();
4253
4254            let message = root_as_message(&meta_buf).unwrap();
4255            let body_len = message.bodyLength() as usize;
4256            let mut body_buf = vec![0u8; body_len];
4257            cursor.read_exact(&mut body_buf).unwrap();
4258
4259            if message.header_type() == crate::MessageHeader::DictionaryBatch {
4260                // Skip the dictionary batch — this is what C++ does for
4261                // all-null dictionary columns.
4262                continue;
4263            }
4264            stripped.extend_from_slice(&CONTINUATION_MARKER);
4265            stripped.extend_from_slice(&(meta_len as u32).to_le_bytes());
4266            stripped.extend_from_slice(&meta_buf);
4267            stripped.extend_from_slice(&body_buf);
4268        }
4269
4270        // Reading the stripped stream must succeed.
4271        let result = read_stream(&stripped).unwrap();
4272        assert_eq!(result.num_rows(), 4);
4273        assert_eq!(result.num_columns(), 1);
4274
4275        let col = result.column(0);
4276        assert_eq!(col.null_count(), 4);
4277        assert_eq!(col.len(), 4);
4278        // The result must be a dictionary-typed array.
4279        assert!(matches!(col.data_type(), DataType::Dictionary(_, _)));
4280    }
4281
4282    // Tests projected reads where a ListView column is skipped before another column.
4283    // This catches cases where skipping the ListView consumes the wrong number of buffers.
4284    #[test]
4285    fn test_projection_skip_list_view() {
4286        use crate::reader::FileReader;
4287        use crate::writer::FileWriter;
4288        use arrow_array::{
4289            GenericListViewArray, Int32Array, RecordBatch,
4290            builder::{GenericListViewBuilder, UInt32Builder},
4291        };
4292        use arrow_schema::{DataType, Field, Schema};
4293        use std::sync::Arc;
4294
4295        // Build a small ListView column with a mix of valid and null entries
4296        let mut builder = GenericListViewBuilder::<i32, _>::new(UInt32Builder::new());
4297
4298        builder.values().append_value(1);
4299        builder.values().append_value(2);
4300        builder.append(true);
4301
4302        builder.append(false);
4303
4304        builder.values().append_value(3);
4305        builder.values().append_value(4);
4306        builder.append(true);
4307
4308        let list_view: GenericListViewArray<i32> = builder.finish();
4309
4310        // Second column with simple values
4311        let values = Int32Array::from(vec![10, 20, 30]);
4312
4313        // Schema: first column is ListView, second is Int32
4314        let schema = Arc::new(Schema::new(vec![
4315            Field::new("a", list_view.data_type().clone(), true),
4316            Field::new("b", DataType::Int32, false),
4317        ]));
4318        // Create a batch with both columns
4319        let batch =
4320            RecordBatch::try_new(schema, vec![Arc::new(list_view), Arc::new(values.clone())])
4321                .unwrap();
4322
4323        // Write the batch to IPC
4324        let mut buf = Vec::new();
4325        {
4326            let mut writer = FileWriter::try_new(&mut buf, &batch.schema()).unwrap();
4327            writer.write(&batch).unwrap();
4328            writer.finish().unwrap();
4329        }
4330
4331        // Skip ListView column and Project only column "b"
4332        let mut reader = FileReader::try_new(std::io::Cursor::new(buf), Some(vec![1])).unwrap();
4333        let read_batch = reader.next().unwrap().unwrap();
4334
4335        // Verify that the projected column is read correctly
4336        assert_eq!(read_batch.num_columns(), 1);
4337        assert_eq!(read_batch.column(0).as_ref(), &values);
4338    }
4339
4340    // Tests reading a column when a preceding V4 Union column is skipped.
4341    // V4 Union columns include a null buffer and type ids (and offsets for dense unions).
4342    #[test]
4343    fn test_projection_skip_union_v4() {
4344        use crate::MetadataVersion;
4345        use crate::reader::FileReader;
4346        use crate::writer::{FileWriter, IpcWriteOptions};
4347        use arrow_array::{
4348            ArrayRef, Int32Array, RecordBatch, builder::UnionBuilder, types::Int32Type,
4349        };
4350        use arrow_schema::{DataType, Field, Schema};
4351        use std::sync::Arc;
4352
4353        // Build a dense Union column with simple Int32 values
4354        let mut builder = UnionBuilder::new_dense();
4355        builder.append::<Int32Type>("a", 1).unwrap();
4356        builder.append::<Int32Type>("a", 2).unwrap();
4357        builder.append::<Int32Type>("a", 3).unwrap();
4358        let union = builder.build().unwrap();
4359
4360        // Second column with known values to verify correctness after projection
4361        let values = Int32Array::from(vec![10, 20, 30]);
4362
4363        // Schema: first column is Union (to be skipped), second is Int32 (to be read)
4364        let schema = Arc::new(Schema::new(vec![
4365            Field::new("union", union.data_type().clone(), false),
4366            Field::new("values", DataType::Int32, false),
4367        ]));
4368
4369        // Create a batch containing both columns
4370        let batch = RecordBatch::try_new(
4371            schema,
4372            vec![Arc::new(union) as ArrayRef, Arc::new(values.clone())],
4373        )
4374        .unwrap();
4375
4376        // Write IPC using V4 metadata to trigger Union null buffer behavior
4377        let mut buf = Vec::new();
4378        {
4379            let options = IpcWriteOptions::try_new(8, false, MetadataVersion::V4).unwrap();
4380            let mut writer =
4381                FileWriter::try_new_with_options(&mut buf, &batch.schema(), options).unwrap();
4382            writer.write(&batch).unwrap();
4383            writer.finish().unwrap();
4384        }
4385        // Read only the second column (skip the Union column)
4386        let mut reader = FileReader::try_new(std::io::Cursor::new(buf), Some(vec![1])).unwrap();
4387        let read_batch = reader.next().unwrap().unwrap();
4388
4389        // Verify that the projected column is read correctly after skipping Union
4390        assert_eq!(read_batch.num_columns(), 1);
4391        assert_eq!(read_batch.column(0).as_ref(), &values);
4392    }
4393
4394    // Tests reading a column when preceding fixed-width and boolean columns are skipped.
4395    // Covers all types that use the same two-buffer layout (null + values).
4396    // Verifies that skipping these types does not affect subsequent column decoding.
4397    #[test]
4398    fn test_projection_skip_fixed_width_types() {
4399        use std::sync::Arc;
4400
4401        use arrow_array::{ArrayRef, BooleanArray, Int32Array, RecordBatch, make_array};
4402        use arrow_buffer::{Buffer, MutableBuffer};
4403        use arrow_data::ArrayData;
4404        use arrow_schema::{DataType, Field, IntervalUnit, Schema, TimeUnit};
4405
4406        use crate::reader::FileReader;
4407        use crate::writer::FileWriter;
4408
4409        // Create a minimal array for a given fixed-width or boolean type
4410        fn make_array_for_type(data_type: DataType) -> ArrayRef {
4411            let len = 3;
4412
4413            if matches!(data_type, DataType::Boolean) {
4414                return Arc::new(BooleanArray::from(vec![true, false, true]));
4415            }
4416
4417            let width = data_type.primitive_width().unwrap();
4418            let data = ArrayData::builder(data_type)
4419                .len(len)
4420                .add_buffer(Buffer::from(MutableBuffer::from_len_zeroed(len * width)))
4421                .build()
4422                .unwrap();
4423
4424            make_array(data)
4425        }
4426
4427        // List of types that follow the same two-buffer layout (null + values)
4428        let data_types = vec![
4429            DataType::Boolean,
4430            DataType::Int8,
4431            DataType::Int16,
4432            DataType::Int32,
4433            DataType::Int64,
4434            DataType::UInt8,
4435            DataType::UInt16,
4436            DataType::UInt32,
4437            DataType::UInt64,
4438            DataType::Float16,
4439            DataType::Float32,
4440            DataType::Float64,
4441            DataType::Timestamp(TimeUnit::Second, None),
4442            DataType::Date32,
4443            DataType::Date64,
4444            DataType::Time32(TimeUnit::Second),
4445            DataType::Time64(TimeUnit::Microsecond),
4446            DataType::Duration(TimeUnit::Second),
4447            DataType::Interval(IntervalUnit::YearMonth),
4448            DataType::Interval(IntervalUnit::DayTime),
4449            DataType::Interval(IntervalUnit::MonthDayNano),
4450            DataType::Decimal32(9, 2),
4451            DataType::Decimal64(18, 2),
4452            DataType::Decimal128(38, 2),
4453            DataType::Decimal256(76, 2),
4454        ];
4455
4456        // For each type:
4457        // - write a batch with [skipped_column, values]
4458        // - read only the second column
4459        // - verify the result is correct
4460        for data_type in data_types {
4461            let skipped = make_array_for_type(data_type.clone());
4462            let values = Int32Array::from(vec![10, 20, 30]);
4463
4464            let schema = Arc::new(Schema::new(vec![
4465                Field::new("skipped", data_type, false),
4466                Field::new("values", DataType::Int32, false),
4467            ]));
4468
4469            let batch =
4470                RecordBatch::try_new(schema, vec![skipped, Arc::new(values.clone())]).unwrap();
4471
4472            // Serialize the batch into IPC format
4473            let mut buf = Vec::new();
4474            {
4475                let mut writer = FileWriter::try_new(&mut buf, &batch.schema()).unwrap();
4476                writer.write(&batch).unwrap();
4477                writer.finish().unwrap();
4478            }
4479
4480            // Read back only the second column (skip the first)
4481            let mut reader = FileReader::try_new(std::io::Cursor::new(buf), Some(vec![1])).unwrap();
4482            let read_batch = reader.next().unwrap().unwrap();
4483
4484            // Verify that the returned column matches the original values column
4485            assert_eq!(read_batch.num_columns(), 1);
4486            assert_eq!(read_batch.column(0).as_ref(), &values);
4487        }
4488    }
4489
4490    /// Builds a stream whose single message declares `body_length` but is followed by only
4491    /// `body_bytes` bytes of body. The body length is consumed before the header is
4492    /// interpreted, so the header itself is immaterial.
4493    fn stream_with_declared_body(body_length: i64, body_bytes: usize) -> Vec<u8> {
4494        let mut fbb = flatbuffers::FlatBufferBuilder::new();
4495        let mut message = crate::MessageBuilder::new(&mut fbb);
4496        message.add_version(crate::MetadataVersion::V5);
4497        message.add_header_type(crate::MessageHeader::NONE);
4498        message.add_bodyLength(body_length);
4499        let root = message.finish();
4500        fbb.finish(root, None);
4501        let metadata = fbb.finished_data();
4502
4503        let mut stream = Vec::new();
4504        stream.extend_from_slice(&CONTINUATION_MARKER);
4505        stream.extend_from_slice(&(metadata.len() as i32).to_le_bytes());
4506        stream.extend_from_slice(metadata);
4507        stream.resize(stream.len() + body_bytes, 0xAB);
4508        stream
4509    }
4510
4511    /// A message body length is read from the stream before any of the bytes it describes,
4512    /// so it cannot be trusted. Declaring an implausible one used to reserve it outright,
4513    /// and the resulting allocation failure aborts the process instead of surfacing an error
4514    /// the caller can handle.
4515    #[test]
4516    fn test_stream_reader_rejects_implausible_body_length() {
4517        for body_length in [i64::MAX, 1 << 50, -1] {
4518            let stream = stream_with_declared_body(body_length, 0);
4519            let err = StreamReader::try_new(std::io::Cursor::new(stream), None).expect_err(
4520                &format!("a message declaring {body_length} body bytes must not be accepted"),
4521            );
4522            let err = err.to_string();
4523            assert!(
4524                err.contains("Invalid IPC message body length")
4525                    || err.contains("Unexpected end of stream")
4526                    || err.contains("failed to fill whole buffer"),
4527                "unexpected error for body_length {body_length}: {err}"
4528            );
4529        }
4530    }
4531
4532    /// A plausible body length whose bytes end early must fail as a short read: before the
4533    /// first read for a small body, and after at least one growth step for a body larger
4534    /// than `MAX_PREALLOC_BYTES`.
4535    #[test]
4536    #[cfg_attr(miri, ignore)] // Takes too long
4537    fn test_stream_reader_rejects_truncated_body() {
4538        let over_prealloc = MAX_PREALLOC_BYTES as i64 + 1;
4539        for (body_length, body_bytes) in [(1024, 10), (over_prealloc, MAX_PREALLOC_BYTES)] {
4540            let stream = stream_with_declared_body(body_length, body_bytes);
4541            let err = StreamReader::try_new(std::io::Cursor::new(stream), None)
4542                .expect_err("a body backed by fewer bytes than declared must not be accepted");
4543            assert!(
4544                err.to_string().contains("failed to fill whole buffer"),
4545                "unexpected error for body_length {body_length}: {err}"
4546            );
4547        }
4548    }
4549
4550    /// The metadata length is untrusted for the same reason as the body length.
4551    #[test]
4552    fn test_stream_reader_rejects_unbacked_metadata_length() {
4553        let mut stream = Vec::new();
4554        stream.extend_from_slice(&CONTINUATION_MARKER);
4555        stream.extend_from_slice(&i32::MAX.to_le_bytes());
4556        stream.extend_from_slice(b"not this many bytes");
4557
4558        let err = StreamReader::try_new(std::io::Cursor::new(stream), None)
4559            .expect_err("metadata length must be backed by the stream");
4560        assert!(
4561            err.to_string().contains("Unexpected end of stream"),
4562            "unexpected error: {err}"
4563        );
4564    }
4565}