Skip to main content

arrow_ipc/
writer.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Arrow IPC File and Stream Writers
19//!
20//! # Notes
21//!
22//! [`FileWriter`] and [`StreamWriter`] have similar interfaces,
23//! however the [`FileWriter`] expects a reader that supports [`Seek`]ing
24//!
25//! [`Seek`]: std::io::Seek
26
27use std::cmp::min;
28use std::collections::HashMap;
29use std::io::{BufWriter, Write};
30use std::mem::size_of;
31use std::sync::Arc;
32
33use flatbuffers::FlatBufferBuilder;
34
35use arrow_array::cast::*;
36use arrow_array::types::{Int16Type, Int32Type, Int64Type, RunEndIndexType};
37use arrow_array::*;
38use arrow_buffer::{ArrowNativeType, Buffer, MutableBuffer, ToByteSlice};
39use arrow_data::{ArrayData, ArrayDataBuilder, BufferSpec, layout};
40use arrow_schema::*;
41
42use crate::CONTINUATION_MARKER;
43use crate::compression::CompressionCodec;
44#[expect(deprecated)]
45pub use crate::compression::{CompressionContext, IpcWriteContext};
46use crate::convert::IpcSchemaEncoder;
47
48/// IPC write options used to control the behaviour of the [`IpcDataGenerator`]
49#[derive(Debug, Clone)]
50pub struct IpcWriteOptions {
51    /// Write padding after memory buffers to this multiple of bytes.
52    /// Must be 8, 16, 32, or 64 - defaults to 64.
53    alignment: u8,
54    /// The legacy format is for releases before 0.15.0, and uses metadata V4
55    write_legacy_ipc_format: bool,
56    /// The metadata version to write. The Rust IPC writer supports V4+
57    ///
58    /// *Default versions per crate*
59    ///
60    /// When creating the default IpcWriteOptions, the following metadata versions are used:
61    ///
62    /// version 2.0.0: V4, with legacy format enabled
63    /// version 4.0.0: V5
64    metadata_version: crate::MetadataVersion,
65    /// Compression, if desired. Will result in a runtime error
66    /// if the corresponding feature is not enabled
67    batch_compression_type: Option<crate::CompressionType>,
68    // Compression level
69    batch_compression_level: Option<i32>,
70    /// How to handle updating dictionaries in IPC messages
71    dictionary_handling: DictionaryHandling,
72}
73
74/// A single buffer segment ready to be written to the output stream.
75///
76/// For the uncompressed path the original Arc-backed [`Buffer`] is stored
77/// directly (zero copy). For the compressed path the compressed bytes are
78/// owned by a scratch `Vec<u8>`.
79enum EncodedBuffer {
80    /// Uncompressed: Arc-backed reference to the original array buffer.
81    Raw(Buffer),
82    /// Compressed: owned scratch bytes produced by the codec.
83    Compressed(Vec<u8>),
84}
85
86impl EncodedBuffer {
87    fn as_slice(&self) -> &[u8] {
88        match self {
89            EncodedBuffer::Raw(b) => b.as_slice(),
90            EncodedBuffer::Compressed(v) => v.as_slice(),
91        }
92    }
93
94    fn len(&self) -> usize {
95        match self {
96            EncodedBuffer::Raw(b) => b.len(),
97            EncodedBuffer::Compressed(v) => v.len(),
98        }
99    }
100}
101/// Accumulates the IPC metadata produced by [`write_array_data`].
102///
103/// `nodes` and `buffers` are serialised into the flatbuffer `RecordBatch` (or `DictionaryBatch`)
104/// header. The companion [`IpcBodySink`] holds the actual encoded bytes.
105#[derive(Default)]
106struct IpcMetadataBuilder {
107    nodes: Vec<crate::FieldNode>,
108    buffers: Vec<crate::Buffer>,
109}
110
111/// Destination for the raw Arrow data bytes (the IPC message body) produced by [`write_array_data`].
112///
113/// The companion [`IpcMetadataBuilder`] accumulates the flatbuffer metadata
114/// (offset + length of each buffer in the body); together they form a complete IPC message.
115enum IpcBodySink<'a> {
116    /// Serialize buffer bytes (with padding) into a contiguous byte vec.
117    Write(&'a mut Vec<u8>),
118    /// Accumulate pre-encoded buffer segments for deferred zero-copy streaming.
119    Collect(&'a mut Vec<EncodedBuffer>),
120}
121impl IpcBodySink<'_> {
122    /// Writes the encoded buffer to the sink.
123    pub fn write(&mut self, pad_len: usize, buffer: EncodedBuffer) {
124        match self {
125            IpcBodySink::Write(vec) => {
126                vec.extend_from_slice(buffer.as_slice());
127                vec.extend_from_slice(&PADDING[..pad_len]);
128            }
129            IpcBodySink::Collect(vec) => {
130                vec.push(buffer);
131            }
132        }
133    }
134}
135
136struct MetadataLayout {
137    padded_header_len: usize,
138    padded_metadata_len: usize,
139    metadata_padding: usize,
140}
141
142impl MetadataLayout {
143    fn new(metadata_len: usize, write_options: &IpcWriteOptions) -> MetadataLayout {
144        let prefix_size = if write_options.write_legacy_ipc_format {
145            4
146        } else {
147            8
148        };
149        let alignment_mask = usize::from(write_options.alignment - 1);
150        let padded_header_len = (metadata_len + prefix_size + alignment_mask) & !alignment_mask;
151        let padded_metadata_len = padded_header_len - prefix_size;
152        let metadata_padding = padded_metadata_len - metadata_len;
153
154        MetadataLayout {
155            padded_header_len,
156            padded_metadata_len,
157            metadata_padding,
158        }
159    }
160}
161
162/// Destination-specific byte writing for a framed IPC message.
163///
164/// The default owned-buffer methods borrow their bytes and call `write_slice`,
165/// which can copy the data. Sink implementations that can take ownership of
166/// generated bytes should override `write_vec` or `write_encoded_buffer`.
167trait IpcMessageSink {
168    fn write_slice(&mut self, bytes: &[u8]) -> Result<(), ArrowError>;
169
170    fn write_vec(&mut self, bytes: Vec<u8>) -> Result<(), ArrowError> {
171        self.write_slice(&bytes)
172    }
173
174    fn write_encoded_buffer(&mut self, buffer: EncodedBuffer) -> Result<(), ArrowError> {
175        self.write_slice(buffer.as_slice())
176    }
177}
178
179/// Shared IPC framing helpers for [`IpcMessageSink`].
180trait IpcMessageSinkExt: IpcMessageSink {
181    fn write_padding(&mut self, len: usize) -> Result<(), ArrowError> {
182        self.write_slice(&PADDING[..len])
183    }
184
185    /// Writes the IPC continuation marker and metadata length prefix.
186    fn write_continuation(
187        &mut self,
188        write_options: &IpcWriteOptions,
189        metadata_len: i32,
190    ) -> Result<(), ArrowError> {
191        let mut buffer = [0; 8];
192        let len = match write_options.metadata_version {
193            crate::MetadataVersion::V1
194            | crate::MetadataVersion::V2
195            | crate::MetadataVersion::V3 => {
196                unreachable!("Options with the metadata version cannot be created")
197            }
198            crate::MetadataVersion::V4 => {
199                let metadata_len_bytes = metadata_len.to_le_bytes();
200                if !write_options.write_legacy_ipc_format {
201                    // v0.15.0 format
202                    buffer[..4].copy_from_slice(&CONTINUATION_MARKER);
203                    buffer[4..].copy_from_slice(&metadata_len_bytes);
204                    8
205                } else {
206                    buffer[..4].copy_from_slice(&metadata_len_bytes);
207                    4
208                }
209            }
210            crate::MetadataVersion::V5 => {
211                buffer[..4].copy_from_slice(&CONTINUATION_MARKER);
212                buffer[4..].copy_from_slice(&metadata_len.to_le_bytes());
213                8
214            }
215            z => panic!("Unsupported crate::MetadataVersion {z:?}"),
216        };
217        self.write_slice(&buffer[..len])
218    }
219
220    fn write_body_data(&mut self, data: Vec<u8>, alignment: u8) -> Result<usize, ArrowError> {
221        let len = data.len();
222        let pad_len = pad_to_alignment(alignment, len);
223        self.write_vec(data)?;
224        self.write_padding(pad_len)?;
225        Ok(len + pad_len)
226    }
227
228    /// Writes an already encoded IPC message with optional contiguous body data.
229    ///
230    /// This is used for schema and dictionary messages represented by [`EncodedData`].
231    /// Returns the padded metadata length and body length written.
232    fn write_encoded_data(
233        &mut self,
234        encoded: EncodedData,
235        write_options: &IpcWriteOptions,
236    ) -> Result<(usize, usize), ArrowError> {
237        let arrow_data_len = encoded.arrow_data.len();
238        if !arrow_data_len.is_multiple_of(usize::from(write_options.alignment)) {
239            return Err(ArrowError::MemoryError(
240                "Arrow data not aligned".to_string(),
241            ));
242        }
243
244        let metadata = encoded.ipc_message;
245        let metadata_len = metadata.len();
246        let layout = MetadataLayout::new(metadata_len, write_options);
247
248        self.write_continuation(write_options, layout.padded_metadata_len as i32)?;
249        self.write_vec(metadata)?;
250        self.write_padding(layout.metadata_padding)?;
251
252        let body_len = if arrow_data_len > 0 {
253            self.write_body_data(encoded.arrow_data, write_options.alignment)?
254        } else {
255            0
256        };
257
258        Ok((layout.padded_header_len, body_len))
259    }
260
261    /// Writes the IPC end-of-stream marker.
262    fn write_eos(&mut self, write_options: &IpcWriteOptions) -> Result<(), ArrowError> {
263        self.write_continuation(write_options, 0)?;
264        Ok(())
265    }
266}
267
268impl<T: IpcMessageSink + ?Sized> IpcMessageSinkExt for T {}
269
270/// Optional hot-path hook for record batch messages.
271trait IpcRecordBatchSink: IpcMessageSinkExt {
272    /// Writes a record batch (or dictionary batch) message from its encoded
273    /// metadata and body buffers.
274    ///
275    /// `metadata` is the raw flatbuffer [`crate::Message`] (without continuation
276    /// prefix), borrowed from the reused [`FlatBufferBuilder`]. The body buffers are
277    /// already materialized as [`EncodedBuffer`] segments, allowing buffer output to
278    /// preserve uncompressed Arrow buffers. They are drained out of
279    /// `encoded_buffers` so the caller can reuse its allocation.
280    ///
281    /// Each body buffer is padded to the alignment as it is written, so the body
282    /// needs no trailing padding.
283    ///
284    /// Returns the padded metadata length and body length written.
285    fn write_record_batch(
286        &mut self,
287        metadata: &[u8],
288        encoded_buffers: &mut Vec<EncodedBuffer>,
289        body_len: usize,
290        write_options: &IpcWriteOptions,
291    ) -> Result<(usize, usize), ArrowError> {
292        let alignment = write_options.alignment;
293        let layout = MetadataLayout::new(metadata.len(), write_options);
294
295        self.write_continuation(write_options, layout.padded_metadata_len as i32)?;
296        self.write_slice(metadata)?;
297        self.write_padding(layout.metadata_padding)?;
298        for enc in encoded_buffers.drain(..) {
299            let len = enc.len();
300            self.write_encoded_buffer(enc)?;
301            self.write_padding(pad_to_alignment(alignment, len))?;
302        }
303
304        Ok((layout.padded_header_len, body_len))
305    }
306}
307
308impl<W> IpcMessageSink for W
309where
310    W: Write,
311{
312    fn write_slice(&mut self, bytes: &[u8]) -> Result<(), ArrowError> {
313        if !bytes.is_empty() {
314            self.write_all(bytes)?;
315        }
316        Ok(())
317    }
318}
319
320impl<W> IpcRecordBatchSink for W
321where
322    W: Write,
323{
324    fn write_record_batch(
325        &mut self,
326        metadata: &[u8],
327        encoded_buffers: &mut Vec<EncodedBuffer>,
328        body_len: usize,
329        write_options: &IpcWriteOptions,
330    ) -> Result<(usize, usize), ArrowError> {
331        let alignment = write_options.alignment;
332        let layout = MetadataLayout::new(metadata.len(), write_options);
333
334        self.write_continuation(write_options, layout.padded_metadata_len as i32)?;
335        self.write_all(metadata)?;
336        self.write_all(&PADDING[..layout.metadata_padding])?;
337        for enc in encoded_buffers.iter_mut() {
338            self.write_all(enc.as_slice())?;
339            self.write_all(&PADDING[..pad_to_alignment(alignment, enc.len())])?;
340        }
341        // Clearing the buffers after the loop instead of draining the vec is
342        // a deliberate choice. It seems to perform much better on certain
343        // ARM chips.
344        //
345        // See: https://github.com/apache/arrow-rs/pull/10128#issuecomment-5406019272
346        encoded_buffers.clear();
347
348        Ok((layout.padded_header_len, body_len))
349    }
350}
351
352/// Accumulates complete framed IPC messages as ordered buffers.
353struct Buffers<'a> {
354    out: &'a mut Vec<Buffer>,
355}
356
357impl IpcMessageSink for Buffers<'_> {
358    fn write_slice(&mut self, bytes: &[u8]) -> Result<(), ArrowError> {
359        if !bytes.is_empty() {
360            self.out.push(Buffer::from(bytes));
361        }
362        Ok(())
363    }
364
365    fn write_vec(&mut self, bytes: Vec<u8>) -> Result<(), ArrowError> {
366        if !bytes.is_empty() {
367            self.out.push(Buffer::from(bytes));
368        }
369        Ok(())
370    }
371
372    fn write_encoded_buffer(&mut self, buffer: EncodedBuffer) -> Result<(), ArrowError> {
373        match buffer {
374            EncodedBuffer::Raw(buffer) => self.out.push(buffer),
375            EncodedBuffer::Compressed(bytes) => self.out.push(Buffer::from(bytes)),
376        }
377        Ok(())
378    }
379}
380
381impl IpcRecordBatchSink for Buffers<'_> {}
382
383/// Per-message sizes produced by [`IpcDataGenerator::write`].
384///
385/// [`FileWriter`] uses these to build the Block index entries required by the IPC footer for
386/// random-access reads.
387struct IpcWriteMetadata {
388    /// Per-dictionary `(padded_header_len, body_len)` for each dictionary batch written
389    /// before the record batch.
390    dictionary_block_sizes: Vec<(usize, usize)>,
391    /// Flatbuffer header size including continuation prefix and alignment padding.
392    padded_header_len: usize,
393    /// Total length of the record-batch body including alignment padding
394    body_len: usize,
395}
396
397impl IpcWriteOptions {
398    /// Configures compression when writing IPC files.
399    ///
400    /// Will result in a runtime error if the corresponding feature
401    /// is not enabled
402    pub fn try_with_compression(
403        mut self,
404        batch_compression_type: Option<crate::CompressionType>,
405    ) -> Result<Self, ArrowError> {
406        self.batch_compression_type = batch_compression_type;
407
408        if self.batch_compression_type.is_some()
409            && self.metadata_version < crate::MetadataVersion::V5
410        {
411            return Err(ArrowError::InvalidArgumentError(
412                "Compression only supported in metadata v5 and above".to_string(),
413            ));
414        }
415        Ok(self)
416    }
417
418    /// Configures the compression level used when writing compressed IPC batches.
419    ///
420    /// Compression levels require metadata V5 or newer and are currently only
421    /// supported for ZSTD compression.
422    pub fn try_with_compression_level(
423        mut self,
424        batch_compression_level: Option<i32>,
425    ) -> Result<Self, ArrowError> {
426        self.batch_compression_level = batch_compression_level;
427
428        if self.batch_compression_level.is_some()
429            && self.metadata_version < crate::MetadataVersion::V5
430        {
431            return Err(ArrowError::InvalidArgumentError(
432                "Compression only supported in metadata v5 and above".to_string(),
433            ));
434        }
435
436        match (self.batch_compression_type, self.batch_compression_level) {
437            (Some(crate::CompressionType::ZSTD), Some(level)) => {
438                return self.check_zstd_level(level);
439            }
440            (Some(crate::CompressionType::LZ4_FRAME), Some(_)) => {
441                return Err(ArrowError::InvalidArgumentError(
442                    "LZ4 Frame compression does not support configurable compression levels"
443                        .to_string(),
444                ));
445            }
446            _ => {}
447        }
448
449        Ok(self)
450    }
451
452    #[cfg(not(feature = "zstd"))]
453    fn check_zstd_level(self, _level: i32) -> Result<Self, ArrowError> {
454        Err(ArrowError::InvalidArgumentError(
455            "zstd IPC compression requires the zstd feature".to_string(),
456        ))
457    }
458
459    #[cfg(feature = "zstd")]
460    fn check_zstd_level(self, level: i32) -> Result<Self, ArrowError> {
461        let range = zstd::compression_level_range();
462        if !range.contains(&level) {
463            return Err(ArrowError::InvalidArgumentError(format!(
464                "ZSTD compression level must be between {} and {}, got {}",
465                range.start(),
466                range.end(),
467                level,
468            )));
469        }
470
471        Ok(self)
472    }
473
474    /// Try to create IpcWriteOptions, checking for incompatible settings
475    pub fn try_new(
476        alignment: usize,
477        write_legacy_ipc_format: bool,
478        metadata_version: crate::MetadataVersion,
479    ) -> Result<Self, ArrowError> {
480        let alignment: u8 = match alignment {
481            8 => 8,
482            16 => 16,
483            32 => 32,
484            64 => 64,
485            _ => {
486                return Err(ArrowError::InvalidArgumentError(
487                    "Alignment should be 8, 16, 32, or 64.".to_string(),
488                ));
489            }
490        };
491        match metadata_version {
492            crate::MetadataVersion::V1
493            | crate::MetadataVersion::V2
494            | crate::MetadataVersion::V3 => Err(ArrowError::InvalidArgumentError(
495                "Writing IPC metadata version 3 and lower not supported".to_string(),
496            )),
497            crate::MetadataVersion::V4 => Ok(Self {
498                alignment,
499                write_legacy_ipc_format,
500                metadata_version,
501                batch_compression_type: None,
502                batch_compression_level: None,
503                dictionary_handling: DictionaryHandling::default(),
504            }),
505            crate::MetadataVersion::V5 => {
506                if write_legacy_ipc_format {
507                    Err(ArrowError::InvalidArgumentError(
508                        "Legacy IPC format only supported on metadata version 4".to_string(),
509                    ))
510                } else {
511                    Ok(Self {
512                        alignment,
513                        write_legacy_ipc_format,
514                        metadata_version,
515                        batch_compression_type: None,
516                        batch_compression_level: None,
517                        dictionary_handling: DictionaryHandling::default(),
518                    })
519                }
520            }
521            z => Err(ArrowError::InvalidArgumentError(format!(
522                "Unsupported crate::MetadataVersion {z:?}"
523            ))),
524        }
525    }
526
527    /// Configure how dictionaries are handled in IPC messages
528    pub fn with_dictionary_handling(mut self, dictionary_handling: DictionaryHandling) -> Self {
529        self.dictionary_handling = dictionary_handling;
530        self
531    }
532}
533
534impl Default for IpcWriteOptions {
535    fn default() -> Self {
536        Self {
537            alignment: 64,
538            write_legacy_ipc_format: false,
539            metadata_version: crate::MetadataVersion::V5,
540            batch_compression_type: None,
541            batch_compression_level: None,
542            dictionary_handling: DictionaryHandling::default(),
543        }
544    }
545}
546
547#[derive(Debug, Default)]
548/// Handles low level details of encoding [`Array`] and [`Schema`] into the
549/// [Arrow IPC Format].
550///
551/// # Example
552/// ```
553/// # fn run() {
554/// # use std::sync::Arc;
555/// # use arrow_array::UInt64Array;
556/// # use arrow_array::RecordBatch;
557/// # use arrow_ipc::writer::{IpcWriteContext, DictionaryTracker, IpcDataGenerator, IpcWriteOptions};
558///
559/// // Create a record batch
560/// let batch = RecordBatch::try_from_iter(vec![
561///  ("col2", Arc::new(UInt64Array::from_iter([10, 23, 33])) as _)
562/// ]).unwrap();
563///
564/// // Error of dictionary ids are replaced.
565/// let error_on_replacement = true;
566/// let options = IpcWriteOptions::default();
567/// let mut dictionary_tracker = DictionaryTracker::new(error_on_replacement);
568///
569/// let mut ipc_write_context = IpcWriteContext::default();
570///
571/// // encode the batch into zero or more encoded dictionaries
572/// // and the data for the actual array.
573/// let data_gen = IpcDataGenerator::default();
574/// let (encoded_dictionaries, encoded_message) = data_gen
575///   .encode(&batch, &mut dictionary_tracker, &options, &mut ipc_write_context)
576///   .unwrap();
577/// # }
578/// ```
579///
580/// [Arrow IPC Format]: https://arrow.apache.org/docs/format/Columnar.html#serialization-and-interprocess-communication-ipc
581pub struct IpcDataGenerator {}
582
583/// A dictionary determined to need sending as a part of a record batch.
584struct DictionaryToEncode {
585    id: i64,
586    data: ArrayData,
587    is_delta: bool,
588}
589
590/// The result of encoding a record batch's body and flatbuffer `RecordBatch` table
591/// via [`IpcDataGenerator::encode_record_batch_data`].
592///
593/// The `RecordBatch` table is left in-progress in the caller's [`FlatBufferBuilder`];
594/// the caller wraps `record_batch` in a `Message` (directly for a record batch
595/// message, or inside a `DictionaryBatch` for a dictionary message).
596struct EncodedRecordBatchMeta {
597    /// In-progress flatbuffer offset of the `RecordBatch` table.
598    fb_offset: flatbuffers::WIPOffset<crate::RecordBatch<'static>>,
599    /// Total body length written to the sink (the sum of each buffer's encoded
600    /// length and its alignment padding).
601    body_len: usize,
602}
603
604impl IpcDataGenerator {
605    /// Converts a schema to an IPC message along with `dictionary_tracker`
606    /// and returns it encoded inside [EncodedData] as a flatbuffer.
607    pub fn schema_to_bytes_with_dictionary_tracker(
608        &self,
609        schema: &Schema,
610        dictionary_tracker: &mut DictionaryTracker,
611        write_options: &IpcWriteOptions,
612    ) -> EncodedData {
613        let mut fbb = FlatBufferBuilder::new();
614        let schema = {
615            let fb = IpcSchemaEncoder::new()
616                .with_dictionary_tracker(dictionary_tracker)
617                .schema_to_fb_offset(&mut fbb, schema);
618            fb.as_union_value()
619        };
620
621        let mut message = crate::MessageBuilder::new(&mut fbb);
622        message.add_version(write_options.metadata_version);
623        message.add_header_type(crate::MessageHeader::Schema);
624        message.add_bodyLength(0);
625        message.add_header(schema);
626        // TODO: custom metadata
627        let root = message.finish();
628        fbb.finish(root, None);
629
630        let metadata = fbb.finished_data();
631        EncodedData {
632            ipc_message: metadata.to_vec(),
633            arrow_data: vec![],
634        }
635    }
636
637    fn _collect_dict_updates<I: Iterator<Item = i64>>(
638        &self,
639        column: &ArrayRef,
640        dictionaries_to_encode: &mut Vec<DictionaryToEncode>,
641        dictionary_tracker: &mut DictionaryTracker,
642        write_options: &IpcWriteOptions,
643        dict_id: &mut I,
644    ) -> Result<(), ArrowError> {
645        match column.data_type() {
646            DataType::Struct(fields) => {
647                let s = as_struct_array(column);
648                for (field, column) in fields.iter().zip(s.columns()) {
649                    self.collect_dict_updates(
650                        field,
651                        column,
652                        dictionaries_to_encode,
653                        dictionary_tracker,
654                        write_options,
655                        dict_id,
656                    )?;
657                }
658            }
659            DataType::RunEndEncoded(_, values) => {
660                let data = column.to_data();
661                if data.child_data().len() != 2 {
662                    return Err(ArrowError::InvalidArgumentError(format!(
663                        "The run encoded array should have exactly two child arrays. Found {}",
664                        data.child_data().len()
665                    )));
666                }
667                // The run_ends array is not expected to be dictionary encoded. Hence encode dictionaries
668                // only for values array.
669                let values_array = make_array(data.child_data()[1].clone());
670                self.collect_dict_updates(
671                    values,
672                    &values_array,
673                    dictionaries_to_encode,
674                    dictionary_tracker,
675                    write_options,
676                    dict_id,
677                )?;
678            }
679            DataType::List(field) => {
680                let list = as_list_array(column);
681                self.collect_dict_updates(
682                    field,
683                    list.values(),
684                    dictionaries_to_encode,
685                    dictionary_tracker,
686                    write_options,
687                    dict_id,
688                )?;
689            }
690            DataType::LargeList(field) => {
691                let list = as_large_list_array(column);
692                self.collect_dict_updates(
693                    field,
694                    list.values(),
695                    dictionaries_to_encode,
696                    dictionary_tracker,
697                    write_options,
698                    dict_id,
699                )?;
700            }
701            DataType::ListView(field) => {
702                let list = column.as_list_view::<i32>();
703                self.collect_dict_updates(
704                    field,
705                    list.values(),
706                    dictionaries_to_encode,
707                    dictionary_tracker,
708                    write_options,
709                    dict_id,
710                )?;
711            }
712            DataType::LargeListView(field) => {
713                let list = column.as_list_view::<i64>();
714                self.collect_dict_updates(
715                    field,
716                    list.values(),
717                    dictionaries_to_encode,
718                    dictionary_tracker,
719                    write_options,
720                    dict_id,
721                )?;
722            }
723            DataType::FixedSizeList(field, _) => {
724                let list = column
725                    .as_any()
726                    .downcast_ref::<FixedSizeListArray>()
727                    .expect("Unable to downcast to fixed size list array");
728                self.collect_dict_updates(
729                    field,
730                    list.values(),
731                    dictionaries_to_encode,
732                    dictionary_tracker,
733                    write_options,
734                    dict_id,
735                )?;
736            }
737            DataType::Map(field, _) => {
738                let map_array = as_map_array(column);
739
740                let (keys, values) = match field.data_type() {
741                    DataType::Struct(fields) if fields.len() == 2 => (&fields[0], &fields[1]),
742                    _ => panic!("Incorrect field data type {:?}", field.data_type()),
743                };
744
745                // keys
746                self.collect_dict_updates(
747                    keys,
748                    map_array.keys(),
749                    dictionaries_to_encode,
750                    dictionary_tracker,
751                    write_options,
752                    dict_id,
753                )?;
754
755                // values
756                self.collect_dict_updates(
757                    values,
758                    map_array.values(),
759                    dictionaries_to_encode,
760                    dictionary_tracker,
761                    write_options,
762                    dict_id,
763                )?;
764            }
765            DataType::Union(fields, _) => {
766                let union = as_union_array(column);
767                for (type_id, field) in fields.iter() {
768                    let column = union.child(type_id);
769                    self.collect_dict_updates(
770                        field,
771                        column,
772                        dictionaries_to_encode,
773                        dictionary_tracker,
774                        write_options,
775                        dict_id,
776                    )?;
777                }
778            }
779            _ => (),
780        }
781
782        Ok(())
783    }
784
785    // Collect all dicts that need a dictionary message i.e. ones that were
786    // either not in the tracker previously or ones that were but need a
787    // replacement or delta.
788    fn collect_dict_updates<I: Iterator<Item = i64>>(
789        &self,
790        field: &Field,
791        column: &ArrayRef,
792        dictionaries_to_encode: &mut Vec<DictionaryToEncode>,
793        dictionary_tracker: &mut DictionaryTracker,
794        write_options: &IpcWriteOptions,
795        dict_id_seq: &mut I,
796    ) -> Result<(), ArrowError> {
797        match column.data_type() {
798            DataType::Dictionary(_key_type, value_type) => {
799                if matches!(value_type.as_ref(), DataType::Dictionary(_, _)) {
800                    return Err(ArrowError::InvalidArgumentError(format!(
801                        "Arrow IPC field metadata cannot encode direct dictionary-of-dictionary values for field {:?}",
802                        field.name()
803                    )));
804                }
805
806                let dict_data = column.to_data();
807                let dict_values = &dict_data.child_data()[0];
808
809                let values = make_array(dict_data.child_data()[0].clone());
810
811                self._collect_dict_updates(
812                    &values,
813                    dictionaries_to_encode,
814                    dictionary_tracker,
815                    write_options,
816                    dict_id_seq,
817                )?;
818
819                // It's important to only take the dict_id at this point, because the dict ID
820                // sequence is assigned depth-first, so we need to first encode children and have
821                // them take their assigned dict IDs before we take the dict ID for this field.
822                let dict_id = dict_id_seq.next().ok_or_else(|| {
823                    ArrowError::IpcError(format!(
824                        "no dict id for field {:?}: field.data_type={:?}, column.data_type={:?}",
825                        field.name(),
826                        field.data_type(),
827                        column.data_type()
828                    ))
829                })?;
830
831                match dictionary_tracker.insert_column(
832                    dict_id,
833                    column,
834                    write_options.dictionary_handling,
835                )? {
836                    DictionaryUpdate::None => {}
837                    DictionaryUpdate::New | DictionaryUpdate::Replaced => {
838                        dictionaries_to_encode.push(DictionaryToEncode {
839                            id: dict_id,
840                            data: dict_values.clone(),
841                            is_delta: false,
842                        });
843                    }
844                    DictionaryUpdate::Delta(data) => {
845                        dictionaries_to_encode.push(DictionaryToEncode {
846                            id: dict_id,
847                            data,
848                            is_delta: true,
849                        });
850                    }
851                }
852            }
853            _ => self._collect_dict_updates(
854                column,
855                dictionaries_to_encode,
856                dictionary_tracker,
857                write_options,
858                dict_id_seq,
859            )?,
860        }
861
862        Ok(())
863    }
864
865    /// Encodes a batch to a number of [EncodedData] items (dictionary batches + the record batch).
866    /// The [DictionaryTracker] keeps track of dictionaries with new `dict_id`s  (so they are only sent once)
867    /// Make sure the [DictionaryTracker] is initialized at the start of the stream.
868    pub fn encode(
869        &self,
870        batch: &RecordBatch,
871        dictionary_tracker: &mut DictionaryTracker,
872        write_options: &IpcWriteOptions,
873        ipc_write_context: &mut IpcWriteContext,
874    ) -> Result<(Vec<EncodedData>, EncodedData), ArrowError> {
875        let dictionaries = self.collect_all_dicts(batch, dictionary_tracker, write_options)?;
876        let mut encoded_dictionaries = Vec::with_capacity(dictionaries.len());
877        for dict in dictionaries {
878            encoded_dictionaries.push(self.dictionary_batch_to_bytes(
879                dict,
880                write_options,
881                ipc_write_context,
882            )?);
883        }
884        let mut arrow_data = ipc_write_context.scratch();
885        self.record_batch_to_bytes(
886            batch,
887            write_options,
888            ipc_write_context,
889            &mut IpcBodySink::Write(&mut arrow_data),
890        )?;
891        ipc_write_context.reserve_scratch_with_capacity(arrow_data.capacity());
892        Ok((
893            encoded_dictionaries,
894            EncodedData {
895                ipc_message: ipc_write_context.mut_fbb().finished_data().to_vec(),
896                arrow_data,
897            },
898        ))
899    }
900
901    /// Walk the record batch and collect dictionaries for later encoding.
902    fn collect_all_dicts(
903        &self,
904        batch: &RecordBatch,
905        dictionary_tracker: &mut DictionaryTracker,
906        write_options: &IpcWriteOptions,
907    ) -> Result<Vec<DictionaryToEncode>, ArrowError> {
908        let schema = batch.schema();
909        let mut dictionaries = Vec::with_capacity(schema.flattened_fields().len());
910        let mut dict_id = dictionary_tracker.dict_ids.clone().into_iter();
911        for (i, field) in schema.fields().iter().enumerate() {
912            self.collect_dict_updates(
913                field,
914                batch.column(i),
915                &mut dictionaries,
916                dictionary_tracker,
917                write_options,
918                &mut dict_id,
919            )?;
920        }
921        Ok(dictionaries)
922    }
923
924    /// Write dictionary batches and the record batch directly to `writer`, skipping the
925    /// intermediate body `Vec<u8>` allocations
926    /// Returns [`IpcWriteMetadata`] with the sizes needed to build footer blocks.
927    fn write<W: Write>(
928        &self,
929        batch: &RecordBatch,
930        dictionary_tracker: &mut DictionaryTracker,
931        write_options: &IpcWriteOptions,
932        ipc_write_context: &mut IpcWriteContext,
933        writer: &mut W,
934    ) -> Result<IpcWriteMetadata, ArrowError> {
935        self.write_to_sink(
936            batch,
937            dictionary_tracker,
938            write_options,
939            ipc_write_context,
940            writer,
941        )
942    }
943
944    /// Encode dictionary batches and the record batch to output buffers, skipping the
945    /// intermediate body `Vec<u8>` allocations for uncompressed record batch buffers.
946    fn encode_to_buffers(
947        &self,
948        batch: &RecordBatch,
949        dictionary_tracker: &mut DictionaryTracker,
950        write_options: &IpcWriteOptions,
951        ipc_write_context: &mut IpcWriteContext,
952        out: &mut Vec<Buffer>,
953    ) -> Result<IpcWriteMetadata, ArrowError> {
954        let mut sink = Buffers { out };
955        self.write_to_sink(
956            batch,
957            dictionary_tracker,
958            write_options,
959            ipc_write_context,
960            &mut sink,
961        )
962    }
963
964    fn write_to_sink<S: IpcRecordBatchSink>(
965        &self,
966        batch: &RecordBatch,
967        dictionary_tracker: &mut DictionaryTracker,
968        write_options: &IpcWriteOptions,
969        ipc_write_context: &mut IpcWriteContext,
970        sink: &mut S,
971    ) -> Result<IpcWriteMetadata, ArrowError> {
972        let dictionaries = self.collect_all_dicts(batch, dictionary_tracker, write_options)?;
973        let mut dictionary_block_sizes = Vec::with_capacity(dictionaries.len());
974
975        let capacity = batch
976            .columns()
977            .iter()
978            .map(|a| estimate_encoded_buffer_count(a.data_type()))
979            .sum();
980        let mut encoded_buffers: Vec<EncodedBuffer> = Vec::with_capacity(capacity);
981
982        for dict in &dictionaries {
983            encoded_buffers.clear();
984
985            let body_len = self.dictionary_batch_to_sink(
986                dict,
987                write_options,
988                ipc_write_context,
989                &mut IpcBodySink::Collect(&mut encoded_buffers),
990            )?;
991
992            dictionary_block_sizes.push(sink.write_record_batch(
993                ipc_write_context.mut_fbb().finished_data(),
994                &mut encoded_buffers,
995                body_len,
996                write_options,
997            )?);
998        }
999
1000        encoded_buffers.clear();
1001        let body_len = self.record_batch_to_bytes(
1002            batch,
1003            write_options,
1004            ipc_write_context,
1005            &mut IpcBodySink::Collect(&mut encoded_buffers),
1006        )?;
1007
1008        let (padded_header_len, body_len) = sink.write_record_batch(
1009            ipc_write_context.mut_fbb().finished_data(),
1010            &mut encoded_buffers,
1011            body_len,
1012            write_options,
1013        )?;
1014
1015        Ok(IpcWriteMetadata {
1016            dictionary_block_sizes,
1017            padded_header_len,
1018            body_len,
1019        })
1020    }
1021
1022    /// Encodes a batch to a number of [EncodedData] items (dictionary batches + the record batch).
1023    /// The [DictionaryTracker] keeps track of dictionaries with new `dict_id`s  (so they are only sent once)
1024    /// Make sure the [DictionaryTracker] is initialized at the start of the stream.
1025    #[deprecated(since = "57.0.0", note = "Use `encode` instead")]
1026    pub fn encoded_batch(
1027        &self,
1028        batch: &RecordBatch,
1029        dictionary_tracker: &mut DictionaryTracker,
1030        write_options: &IpcWriteOptions,
1031    ) -> Result<(Vec<EncodedData>, EncodedData), ArrowError> {
1032        self.encode(
1033            batch,
1034            dictionary_tracker,
1035            write_options,
1036            &mut Default::default(),
1037        )
1038    }
1039
1040    /// Encodes a `RecordBatch` into a flatbuffer IPC message and fills `sink` with the
1041    /// serialised buffer data.
1042    ///
1043    /// Returns the total body length written to `sink` (including per-buffer alignment
1044    /// padding).
1045    ///
1046    /// The FlatBuffer [`crate::Message`] is located in `ipc_write_context`'s
1047    /// [`FlatBufferBuilder`] finished bytes. A successful Result from this function
1048    /// guarantees the builder is in a finished state to call
1049    /// [`FlatBufferBuilder::finished_data`].
1050    fn record_batch_to_bytes(
1051        &self,
1052        batch: &RecordBatch,
1053        write_options: &IpcWriteOptions,
1054        ipc_write_context: &mut IpcWriteContext,
1055        sink: &mut IpcBodySink<'_>,
1056    ) -> Result<usize, ArrowError> {
1057        // Reset the fbb
1058        ipc_write_context.mut_fbb().reset();
1059
1060        let EncodedRecordBatchMeta {
1061            fb_offset: record_batch,
1062            body_len,
1063        } = self.encode_record_batch_data(
1064            batch.columns().iter().map(|array| array.to_data()),
1065            batch.num_rows() as i64,
1066            write_options,
1067            ipc_write_context,
1068            sink,
1069        )?;
1070
1071        let fbb = ipc_write_context.mut_fbb();
1072        let mut message = crate::MessageBuilder::new(fbb);
1073        message.add_version(write_options.metadata_version);
1074        message.add_header_type(crate::MessageHeader::RecordBatch);
1075        message.add_bodyLength(body_len as i64);
1076        message.add_header(record_batch.as_union_value());
1077        let root = message.finish();
1078        fbb.finish(root, None);
1079
1080        Ok(body_len)
1081    }
1082
1083    /// Write dictionary values into two sets of bytes, one for the header (crate::Message) and the
1084    /// other for the data
1085    fn dictionary_batch_to_bytes(
1086        &self,
1087        dict: DictionaryToEncode,
1088        write_options: &IpcWriteOptions,
1089        ipc_write_context: &mut IpcWriteContext,
1090    ) -> Result<EncodedData, ArrowError> {
1091        let mut arrow_data: Vec<u8> = vec![];
1092        self.dictionary_batch_to_sink(
1093            &dict,
1094            write_options,
1095            ipc_write_context,
1096            &mut IpcBodySink::Write(&mut arrow_data),
1097        )?;
1098
1099        Ok(EncodedData {
1100            ipc_message: ipc_write_context.mut_fbb().finished_data().to_vec(),
1101            arrow_data,
1102        })
1103    }
1104
1105    /// Encodes a dictionary batch's flatbuffer metadata into `ipc_write_context`'s
1106    /// builder (read back via `ipc_write_context.mut_fbb().finished_data()`) and fills
1107    /// `sink` with its body buffers.
1108    ///
1109    /// Returns the total body length written to `sink` (including per-buffer alignment
1110    /// padding).
1111    fn dictionary_batch_to_sink(
1112        &self,
1113        dict: &DictionaryToEncode,
1114        write_options: &IpcWriteOptions,
1115        ipc_write_context: &mut IpcWriteContext,
1116        sink: &mut IpcBodySink<'_>,
1117    ) -> Result<usize, ArrowError> {
1118        // Reset fbb
1119        ipc_write_context.mut_fbb().reset();
1120
1121        // A dictionary batch is a record batch (the single column of dictionary
1122        // values) wrapped in a DictionaryBatch, so we share the record batch body
1123        // and table encoding and only differ in the message framing.
1124        let EncodedRecordBatchMeta {
1125            fb_offset: record_batch,
1126            body_len,
1127        } = self.encode_record_batch_data(
1128            std::iter::once(dict.data.clone()),
1129            dict.data.len() as i64,
1130            write_options,
1131            ipc_write_context,
1132            sink,
1133        )?;
1134
1135        let fbb = ipc_write_context.mut_fbb();
1136        let root = {
1137            let mut batch_builder = crate::DictionaryBatchBuilder::new(fbb);
1138            batch_builder.add_id(dict.id);
1139            batch_builder.add_data(record_batch);
1140            batch_builder.add_isDelta(dict.is_delta);
1141            batch_builder.finish().as_union_value()
1142        };
1143
1144        let root = {
1145            let mut message_builder = crate::MessageBuilder::new(fbb);
1146            message_builder.add_version(write_options.metadata_version);
1147            message_builder.add_header_type(crate::MessageHeader::DictionaryBatch);
1148            message_builder.add_bodyLength(body_len as i64);
1149            message_builder.add_header(root);
1150            message_builder.finish()
1151        };
1152
1153        fbb.finish(root, None);
1154
1155        Ok(body_len)
1156    }
1157
1158    /// Encode the body buffers of a single record batch into `sink` and build the
1159    /// flatbuffer `RecordBatch` table into `ipc_write_context`'s builder.
1160    ///
1161    /// This is the shared core of [`Self::record_batch_to_bytes`] and
1162    /// [`Self::dictionary_batch_to_sink`]: a dictionary message embeds a record
1163    /// batch (its single column of values), so both encode the same `RecordBatch`
1164    /// table and only differ in how they wrap the returned offset in a message.
1165    ///
1166    /// `columns` yields the column data in IPC buffer order and `row_count` is the
1167    /// logical length recorded in the `RecordBatch` header. The caller is responsible
1168    /// for wrapping the returned [`EncodedRecordBatchMeta::fb_offset`] in a `Message`
1169    /// and calling [`FlatBufferBuilder::finish`].
1170    #[inline]
1171    fn encode_record_batch_data(
1172        &self,
1173        columns: impl IntoIterator<Item = ArrayData>,
1174        row_count: i64,
1175        write_options: &IpcWriteOptions,
1176        ipc_write_context: &mut IpcWriteContext,
1177        sink: &mut IpcBodySink<'_>,
1178    ) -> Result<EncodedRecordBatchMeta, ArrowError> {
1179        let batch_compression_type = write_options.batch_compression_type;
1180
1181        let compression = batch_compression_type.map(|batch_compression_type| {
1182            let fbb = ipc_write_context.mut_fbb();
1183            let mut c = crate::BodyCompressionBuilder::new(fbb);
1184            c.add_method(crate::BodyCompressionMethod::BUFFER);
1185            c.add_codec(batch_compression_type);
1186            c.finish()
1187        });
1188
1189        let batch_compression_level = write_options.batch_compression_level;
1190        let compression_codec: Option<CompressionCodec> = batch_compression_type
1191            .map(|compression_type| match batch_compression_level {
1192                Some(level) => {
1193                    CompressionCodec::try_new_with_compression_level(compression_type, level)
1194                }
1195                None => compression_type.try_into(),
1196            })
1197            .transpose()?;
1198
1199        let mut meta = IpcMetadataBuilder::default();
1200        let mut variadic_buffer_counts: Vec<i64> = vec![];
1201        let mut offset = 0i64;
1202
1203        for array_data in columns {
1204            offset = write_array_data(
1205                &array_data,
1206                &mut meta,
1207                sink,
1208                offset,
1209                compression_codec,
1210                ipc_write_context,
1211                write_options,
1212            )?;
1213            append_variadic_buffer_counts(&mut variadic_buffer_counts, &array_data);
1214        }
1215
1216        // Each buffer is padded to the alignment as it is written, so `offset` is
1217        // already a multiple of the alignment -- the body needs no trailing padding.
1218        debug_assert_eq!(offset % write_options.alignment as i64, 0);
1219        let body_len = offset as usize;
1220
1221        let fbb = ipc_write_context.mut_fbb();
1222        let buffers = fbb.create_vector(&meta.buffers);
1223        let nodes = fbb.create_vector(&meta.nodes);
1224        let variadic_buffer = if variadic_buffer_counts.is_empty() {
1225            None
1226        } else {
1227            Some(fbb.create_vector(&variadic_buffer_counts))
1228        };
1229
1230        let mut batch_builder = crate::RecordBatchBuilder::new(fbb);
1231        batch_builder.add_length(row_count);
1232        batch_builder.add_nodes(nodes);
1233        batch_builder.add_buffers(buffers);
1234        if let Some(c) = compression {
1235            batch_builder.add_compression(c);
1236        }
1237        if let Some(v) = variadic_buffer {
1238            batch_builder.add_variadicBufferCounts(v);
1239        }
1240        let record_batch = batch_builder.finish();
1241
1242        Ok(EncodedRecordBatchMeta {
1243            fb_offset: record_batch,
1244            body_len,
1245        })
1246    }
1247}
1248
1249fn ensure_supported_ipc_schema(schema: &Schema) -> Result<(), ArrowError> {
1250    schema
1251        .fields()
1252        .iter()
1253        .try_for_each(|field| ensure_supported_ipc_data_type(field.name(), field.data_type()))
1254}
1255
1256fn ensure_supported_ipc_data_type(
1257    field_name: &str,
1258    data_type: &DataType,
1259) -> Result<(), ArrowError> {
1260    match data_type {
1261        DataType::Dictionary(_, value_type)
1262            if matches!(value_type.as_ref(), DataType::Dictionary(_, _)) =>
1263        {
1264            Err(ArrowError::InvalidArgumentError(format!(
1265                "Arrow IPC field metadata cannot encode direct dictionary-of-dictionary values for field {field_name:?}"
1266            )))
1267        }
1268        DataType::Dictionary(_, value_type) => {
1269            ensure_supported_ipc_data_type(field_name, value_type)
1270        }
1271        DataType::Struct(fields) => fields
1272            .iter()
1273            .try_for_each(|field| ensure_supported_ipc_data_type(field.name(), field.data_type())),
1274        DataType::RunEndEncoded(_, field)
1275        | DataType::List(field)
1276        | DataType::LargeList(field)
1277        | DataType::ListView(field)
1278        | DataType::LargeListView(field)
1279        | DataType::FixedSizeList(field, _)
1280        | DataType::Map(field, _) => {
1281            ensure_supported_ipc_data_type(field.name(), field.data_type())
1282        }
1283        DataType::Union(fields, _) => fields.iter().try_for_each(|(_, field)| {
1284            ensure_supported_ipc_data_type(field.name(), field.data_type())
1285        }),
1286        _ => Ok(()),
1287    }
1288}
1289
1290fn append_variadic_buffer_counts(counts: &mut Vec<i64>, array: &ArrayData) {
1291    match array.data_type() {
1292        DataType::BinaryView | DataType::Utf8View => {
1293            // The spec documents the counts only includes the variadic buffers, not the view/null buffers.
1294            // https://arrow.apache.org/docs/format/Columnar.html#variadic-buffers
1295            counts.push(array.buffers().len() as i64 - 1);
1296        }
1297        DataType::Dictionary(_, _) => {
1298            // Do nothing
1299            // Dictionary values are handled in special dictionary messages
1300        }
1301        _ => {
1302            for child in array.child_data() {
1303                append_variadic_buffer_counts(counts, child)
1304            }
1305        }
1306    }
1307}
1308
1309pub(crate) fn unslice_run_array(arr: ArrayData) -> Result<ArrayData, ArrowError> {
1310    match arr.data_type() {
1311        DataType::RunEndEncoded(k, _) => match k.data_type() {
1312            DataType::Int16 => {
1313                Ok(into_zero_offset_run_array(RunArray::<Int16Type>::from(arr))?.into_data())
1314            }
1315            DataType::Int32 => {
1316                Ok(into_zero_offset_run_array(RunArray::<Int32Type>::from(arr))?.into_data())
1317            }
1318            DataType::Int64 => {
1319                Ok(into_zero_offset_run_array(RunArray::<Int64Type>::from(arr))?.into_data())
1320            }
1321            d => unreachable!("Unexpected data type {d}"),
1322        },
1323        d => Err(ArrowError::InvalidArgumentError(format!(
1324            "The given array is not a run array. Data type of given array: {d}"
1325        ))),
1326    }
1327}
1328
1329// Returns a `RunArray` with zero offset and length matching the last value
1330// in run_ends array.
1331fn into_zero_offset_run_array<R: RunEndIndexType>(
1332    run_array: RunArray<R>,
1333) -> Result<RunArray<R>, ArrowError> {
1334    // Empty slices have no physical runs, regardless of their offset.
1335    if run_array.is_empty() {
1336        return Ok(ArrayData::new_empty(run_array.data_type()).into());
1337    }
1338
1339    let run_ends = run_array.run_ends();
1340    if run_ends.offset() == 0 && run_ends.max_value() == run_ends.len() {
1341        return Ok(run_array);
1342    }
1343
1344    // The physical index of original run_ends array from which the `ArrayData`is sliced.
1345    let start_physical_index = run_ends.get_start_physical_index();
1346
1347    // The physical index of original run_ends array until which the `ArrayData`is sliced.
1348    let end_physical_index = run_ends.get_end_physical_index();
1349
1350    let physical_length = end_physical_index - start_physical_index + 1;
1351
1352    // build new run_ends array by subtracting offset from run ends.
1353    let offset = R::Native::usize_as(run_ends.offset());
1354    let mut run_ends_values = Vec::<R::Native>::with_capacity(physical_length);
1355    for run_end_value in &run_ends.values()[start_physical_index..end_physical_index] {
1356        run_ends_values.push(run_end_value.sub_wrapping(offset));
1357    }
1358    run_ends_values.push(R::Native::from_usize(run_array.len()).unwrap());
1359    let offset_buffer = Buffer::from_vec(run_ends_values);
1360    let new_run_ends = unsafe {
1361        // Safety:
1362        // The function builds a valid run_ends array and hence need not be validated.
1363        ArrayDataBuilder::new(R::DATA_TYPE)
1364            .len(physical_length)
1365            .add_buffer(offset_buffer)
1366            .build_unchecked()
1367    };
1368
1369    // build new values by slicing physical indices.
1370    let new_values = run_array
1371        .values()
1372        .slice(start_physical_index, physical_length)
1373        .into_data();
1374
1375    let builder = ArrayDataBuilder::new(run_array.data_type().clone())
1376        .len(run_array.len())
1377        .add_child_data(new_run_ends)
1378        .add_child_data(new_values);
1379    let array_data = unsafe {
1380        // Safety:
1381        //  This function builds a valid run array and hence can skip validation.
1382        builder.build_unchecked()
1383    };
1384    Ok(array_data.into())
1385}
1386
1387/// Controls how dictionaries are handled in Arrow IPC messages
1388#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1389pub enum DictionaryHandling {
1390    /// Send the entire dictionary every time it is encountered (default)
1391    #[default]
1392    Resend,
1393    /// Send only new dictionary values since the last batch (delta encoding)
1394    ///
1395    /// When a dictionary is first encountered, the entire dictionary is sent.
1396    /// For subsequent batches, only values that are new (not previously sent)
1397    /// are transmitted with the `isDelta` flag set to true.
1398    Delta,
1399}
1400
1401/// Describes what kind of update took place after a call to [`DictionaryTracker::insert`].
1402#[derive(Debug, Clone)]
1403pub enum DictionaryUpdate {
1404    /// No dictionary was written, the dictionary was identical to what was already
1405    /// in the tracker.
1406    None,
1407    /// No dictionary was present in the tracker
1408    New,
1409    /// Dictionary was replaced with the new data
1410    Replaced,
1411    /// Dictionary was updated, ArrayData is the delta between old and new
1412    Delta(ArrayData),
1413}
1414
1415/// Keeps track of dictionaries that have been written, to avoid emitting the same dictionary
1416/// multiple times.
1417///
1418/// Can optionally error if an update to an existing dictionary is attempted, which
1419/// isn't allowed in the `FileWriter`.
1420#[derive(Debug)]
1421pub struct DictionaryTracker {
1422    // NOTE: When adding fields, update the clear() method accordingly.
1423    written: HashMap<i64, ArrayData>,
1424    dict_ids: Vec<i64>,
1425    error_on_replacement: bool,
1426}
1427
1428impl DictionaryTracker {
1429    /// Create a new [`DictionaryTracker`].
1430    ///
1431    /// If `error_on_replacement`
1432    /// is true, an error will be generated if an update to an
1433    /// existing dictionary is attempted.
1434    pub fn new(error_on_replacement: bool) -> Self {
1435        Self {
1436            written: HashMap::new(),
1437            dict_ids: Vec::new(),
1438            error_on_replacement,
1439        }
1440    }
1441
1442    /// Record and return the next dictionary ID.
1443    pub fn next_dict_id(&mut self) -> i64 {
1444        let next = self
1445            .dict_ids
1446            .last()
1447            .copied()
1448            .map(|i| i + 1)
1449            .unwrap_or_default();
1450
1451        self.dict_ids.push(next);
1452        next
1453    }
1454
1455    /// Return the sequence of dictionary IDs in the order they should be observed while
1456    /// traversing the schema
1457    pub fn dict_id(&mut self) -> &[i64] {
1458        &self.dict_ids
1459    }
1460
1461    /// Keep track of the dictionary with the given ID and values. Behavior:
1462    ///
1463    /// * If this ID has been written already and has the same data, return `Ok(false)` to indicate
1464    ///   that the dictionary was not actually inserted (because it's already been seen).
1465    /// * If this ID has been written already but with different data, and this tracker is
1466    ///   configured to return an error, return an error.
1467    /// * If the tracker has not been configured to error on replacement or this dictionary
1468    ///   has never been seen before, return `Ok(true)` to indicate that the dictionary was just
1469    ///   inserted.
1470    #[deprecated(since = "56.1.0", note = "Use `insert_column` instead")]
1471    pub fn insert(&mut self, dict_id: i64, column: &ArrayRef) -> Result<bool, ArrowError> {
1472        let dict_data = column.to_data();
1473        let dict_values = &dict_data.child_data()[0];
1474
1475        // If a dictionary with this id was already emitted, check if it was the same.
1476        if let Some(last) = self.written.get(&dict_id) {
1477            if ArrayData::ptr_eq(&last.child_data()[0], dict_values) {
1478                // Same dictionary values => no need to emit it again
1479                return Ok(false);
1480            }
1481            if self.error_on_replacement {
1482                // If error on replacement perform a logical comparison
1483                if last.child_data()[0] == *dict_values {
1484                    // Same dictionary values => no need to emit it again
1485                    return Ok(false);
1486                }
1487                return Err(ArrowError::InvalidArgumentError(
1488                    "Dictionary replacement detected when writing IPC file format. \
1489                     Arrow IPC files only support a single dictionary for a given field \
1490                     across all batches."
1491                        .to_string(),
1492                ));
1493            }
1494        }
1495
1496        self.written.insert(dict_id, dict_data);
1497        Ok(true)
1498    }
1499
1500    /// Keep track of the dictionary with the given ID and values. The return
1501    /// value indicates what, if any, update to the internal map took place
1502    /// and how it should be interpreted based on the `dict_handling` parameter.
1503    ///
1504    /// # Returns
1505    ///
1506    /// * `Ok(Dictionary::New)` - If the dictionary was not previously written
1507    /// * `Ok(Dictionary::Replaced)` - If the dictionary was previously written
1508    ///   with completely different data, or if the data is a delta of the existing,
1509    ///   but with `dict_handling` set to `DictionaryHandling::Resend`
1510    /// * `Ok(Dictionary::Delta)` - If the dictionary was previously written, but
1511    ///   the new data is a delta of the old and the `dict_handling` is set to
1512    ///   `DictionaryHandling::Delta`
1513    /// * `Err(e)` - If the dictionary was previously written with different data,
1514    ///   and `error_on_replacement` is set to `true`.
1515    pub fn insert_column(
1516        &mut self,
1517        dict_id: i64,
1518        column: &ArrayRef,
1519        dict_handling: DictionaryHandling,
1520    ) -> Result<DictionaryUpdate, ArrowError> {
1521        let new_data = column.to_data();
1522        let new_values = &new_data.child_data()[0];
1523
1524        // If there is no existing dictionary with this ID, we always insert
1525        let Some(old) = self.written.get(&dict_id) else {
1526            self.written.insert(dict_id, new_data);
1527            return Ok(DictionaryUpdate::New);
1528        };
1529
1530        // Fast path - If the array data points to the same buffer as the
1531        // existing then they're the same.
1532        let old_values = &old.child_data()[0];
1533        if ArrayData::ptr_eq(old_values, new_values) {
1534            return Ok(DictionaryUpdate::None);
1535        }
1536
1537        // Slow path - Compare the dictionaries value by value
1538        let comparison = compare_dictionaries(old_values, new_values);
1539        if matches!(comparison, DictionaryComparison::Equal) {
1540            return Ok(DictionaryUpdate::None);
1541        }
1542
1543        const REPLACEMENT_ERROR: &str = "Dictionary replacement detected when writing IPC file format. \
1544                 Arrow IPC files only support a single dictionary for a given field \
1545                 across all batches.";
1546
1547        match comparison {
1548            DictionaryComparison::NotEqual => {
1549                if self.error_on_replacement {
1550                    return Err(ArrowError::InvalidArgumentError(
1551                        REPLACEMENT_ERROR.to_string(),
1552                    ));
1553                }
1554
1555                self.written.insert(dict_id, new_data);
1556                Ok(DictionaryUpdate::Replaced)
1557            }
1558            DictionaryComparison::Delta => match dict_handling {
1559                DictionaryHandling::Resend => {
1560                    if self.error_on_replacement {
1561                        return Err(ArrowError::InvalidArgumentError(
1562                            REPLACEMENT_ERROR.to_string(),
1563                        ));
1564                    }
1565
1566                    self.written.insert(dict_id, new_data);
1567                    Ok(DictionaryUpdate::Replaced)
1568                }
1569                DictionaryHandling::Delta => {
1570                    let delta =
1571                        new_values.slice(old_values.len(), new_values.len() - old_values.len());
1572                    self.written.insert(dict_id, new_data);
1573                    Ok(DictionaryUpdate::Delta(delta))
1574                }
1575            },
1576            DictionaryComparison::Equal => unreachable!("Already checked equal case"),
1577        }
1578    }
1579
1580    /// Clears the state of the dictionary tracker.
1581    ///
1582    /// This allows the dictionary tracker to be reused for a new IPC stream while avoiding the
1583    /// allocation cost of creating a new instance. This method should not be called if
1584    /// the dictionary tracker will be used to continue writing to an existing IPC stream.
1585    pub fn clear(&mut self) {
1586        self.dict_ids.clear();
1587        self.written.clear();
1588    }
1589}
1590
1591/// Describes how two dictionary arrays compare to each other.
1592#[derive(Debug, Clone)]
1593enum DictionaryComparison {
1594    /// Neither a delta, nor an exact match
1595    NotEqual,
1596    /// Exact element-wise match
1597    Equal,
1598    /// The two arrays are dictionary deltas of each other, meaning the first
1599    /// is a prefix of the second.
1600    Delta,
1601}
1602
1603// Compares two dictionaries and returns a [`DictionaryComparison`].
1604fn compare_dictionaries(old: &ArrayData, new: &ArrayData) -> DictionaryComparison {
1605    // Check for exact match
1606    let existing_len = old.len();
1607    let new_len = new.len();
1608    if existing_len == new_len {
1609        return if *old == *new {
1610            DictionaryComparison::Equal
1611        } else {
1612            DictionaryComparison::NotEqual
1613        };
1614    }
1615
1616    // Can't be a delta if the new is shorter than the existing
1617    if new_len < existing_len {
1618        return DictionaryComparison::NotEqual;
1619    }
1620
1621    // Check for delta
1622    if new.slice(0, existing_len) == *old {
1623        return DictionaryComparison::Delta;
1624    }
1625
1626    DictionaryComparison::NotEqual
1627}
1628
1629/// Arrow File Writer
1630///
1631/// Writes Arrow [`RecordBatch`]es in the [IPC File Format].
1632///
1633/// # See Also
1634///
1635/// * [`StreamWriter`] for writing IPC Streams
1636///
1637/// # Example
1638/// ```
1639/// # use arrow_array::record_batch;
1640/// # use arrow_ipc::writer::FileWriter;
1641/// # let mut file = vec![]; // mimic a file for the example
1642/// let batch = record_batch!(("a", Int32, [1, 2, 3])).unwrap();
1643/// // create a new writer, the schema must be known in advance
1644/// let mut writer = FileWriter::try_new(&mut file, &batch.schema()).unwrap();
1645/// // write each batch to the underlying writer
1646/// writer.write(&batch).unwrap();
1647/// // When all batches are written, call finish to flush all buffers
1648/// writer.finish().unwrap();
1649/// ```
1650/// [IPC File Format]: https://arrow.apache.org/docs/format/Columnar.html#ipc-file-format
1651pub struct FileWriter<W> {
1652    /// The object to write to
1653    writer: W,
1654    /// IPC write options
1655    write_options: IpcWriteOptions,
1656    /// A reference to the schema, used in validating record batches
1657    schema: SchemaRef,
1658    /// The number of bytes between each block of bytes, as an offset for random access
1659    block_offsets: usize,
1660    /// Dictionary blocks that will be written as part of the IPC footer
1661    dictionary_blocks: Vec<crate::Block>,
1662    /// Record blocks that will be written as part of the IPC footer
1663    record_blocks: Vec<crate::Block>,
1664    /// Whether the writer footer has been written, and the writer is finished
1665    finished: bool,
1666    /// Keeps track of dictionaries that have been written
1667    dictionary_tracker: DictionaryTracker,
1668    /// User level customized metadata
1669    custom_metadata: Metadata,
1670
1671    data_gen: IpcDataGenerator,
1672
1673    /// Holds reusable scratch state shared across all messages -- including the
1674    /// [`FlatBufferBuilder`] whose internal buffer is reused to avoid reallocating
1675    /// on every batch.
1676    ipc_write_context: IpcWriteContext,
1677}
1678
1679impl<W: Write> FileWriter<BufWriter<W>> {
1680    /// Try to create a new file writer with the writer wrapped in a BufWriter.
1681    ///
1682    /// See [`FileWriter::try_new`] for an unbuffered version.
1683    pub fn try_new_buffered(writer: W, schema: &Schema) -> Result<Self, ArrowError> {
1684        Self::try_new(BufWriter::new(writer), schema)
1685    }
1686}
1687
1688impl<W: Write> FileWriter<W> {
1689    /// Try to create a new writer, with the schema written as part of the header
1690    ///
1691    /// Note the created writer is not buffered. See [`FileWriter::try_new_buffered`] for details.
1692    ///
1693    /// # Errors
1694    ///
1695    /// An [`Err`] may be returned if writing the header to the writer fails.
1696    pub fn try_new(writer: W, schema: &Schema) -> Result<Self, ArrowError> {
1697        let write_options = IpcWriteOptions::default();
1698        Self::try_new_with_options(writer, schema, write_options)
1699    }
1700
1701    /// Try to create a new writer with IpcWriteOptions
1702    ///
1703    /// Note the created writer is not buffered. See [`FileWriter::try_new_buffered`] for details.
1704    ///
1705    /// # Errors
1706    ///
1707    /// An [`Err`] may be returned if writing the header to the writer fails.
1708    pub fn try_new_with_options(
1709        mut writer: W,
1710        schema: &Schema,
1711        write_options: IpcWriteOptions,
1712    ) -> Result<Self, ArrowError> {
1713        ensure_supported_ipc_schema(schema)?;
1714
1715        let data_gen = IpcDataGenerator::default();
1716        // write magic to header aligned on alignment boundary
1717        let pad_len = pad_to_alignment(write_options.alignment, super::ARROW_MAGIC.len());
1718        let header_size = super::ARROW_MAGIC.len() + pad_len;
1719        writer.write_all(&super::ARROW_MAGIC)?;
1720        writer.write_all(&PADDING[..pad_len])?;
1721        // write the schema, set the written bytes to the schema + header
1722        let mut dictionary_tracker = DictionaryTracker::new(true);
1723        let encoded_message = data_gen.schema_to_bytes_with_dictionary_tracker(
1724            schema,
1725            &mut dictionary_tracker,
1726            &write_options,
1727        );
1728        let (meta, data) = write_message(&mut writer, encoded_message, &write_options)?;
1729        Ok(Self {
1730            writer,
1731            write_options,
1732            schema: Arc::new(schema.clone()),
1733            block_offsets: meta + data + header_size,
1734            dictionary_blocks: vec![],
1735            record_blocks: vec![],
1736            finished: false,
1737            dictionary_tracker,
1738            custom_metadata: Default::default(),
1739            data_gen,
1740            ipc_write_context: IpcWriteContext::default(),
1741        })
1742    }
1743
1744    /// Adds a key-value pair to the [FileWriter]'s custom metadata
1745    pub fn write_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
1746        self.custom_metadata.insert(key.into(), value.into());
1747    }
1748
1749    /// Write a record batch to the file
1750    pub fn write(&mut self, batch: &RecordBatch) -> Result<(), ArrowError> {
1751        if self.finished {
1752            return Err(ArrowError::IpcError(
1753                "Cannot write record batch to file writer as it is closed".to_string(),
1754            ));
1755        }
1756
1757        let meta = self.data_gen.write(
1758            batch,
1759            &mut self.dictionary_tracker,
1760            &self.write_options,
1761            &mut self.ipc_write_context,
1762            &mut self.writer,
1763        )?;
1764
1765        for (header_len, body_len) in meta.dictionary_block_sizes {
1766            let block = crate::Block::new(
1767                self.block_offsets as i64,
1768                header_len as i32,
1769                body_len as i64,
1770            );
1771            self.dictionary_blocks.push(block);
1772            self.block_offsets += header_len + body_len;
1773        }
1774
1775        // add a record block for the footer
1776        let block = crate::Block::new(
1777            self.block_offsets as i64,
1778            meta.padded_header_len as i32,
1779            meta.body_len as i64,
1780        );
1781        self.record_blocks.push(block);
1782        self.block_offsets += meta.padded_header_len + meta.body_len;
1783        Ok(())
1784    }
1785
1786    /// Write footer and closing tag, then mark the writer as done
1787    pub fn finish(&mut self) -> Result<(), ArrowError> {
1788        if self.finished {
1789            return Err(ArrowError::IpcError(
1790                "Cannot write footer to file writer as it is closed".to_string(),
1791            ));
1792        }
1793
1794        // write EOS
1795        self.writer.write_eos(&self.write_options)?;
1796
1797        let mut fbb = FlatBufferBuilder::new();
1798        let dictionaries = fbb.create_vector(&self.dictionary_blocks);
1799        let record_batches = fbb.create_vector(&self.record_blocks);
1800
1801        // dictionaries are already written, so we can reset dictionary tracker to reuse for schema
1802        self.dictionary_tracker.clear();
1803        let schema = IpcSchemaEncoder::new()
1804            .with_dictionary_tracker(&mut self.dictionary_tracker)
1805            .schema_to_fb_offset(&mut fbb, &self.schema);
1806        let fb_custom_metadata = (!self.custom_metadata.is_empty())
1807            .then(|| crate::convert::metadata_to_fb(&mut fbb, &self.custom_metadata));
1808
1809        let root = {
1810            let mut footer_builder = crate::FooterBuilder::new(&mut fbb);
1811            footer_builder.add_version(self.write_options.metadata_version);
1812            footer_builder.add_schema(schema);
1813            footer_builder.add_dictionaries(dictionaries);
1814            footer_builder.add_recordBatches(record_batches);
1815            if let Some(fb_custom_metadata) = fb_custom_metadata {
1816                footer_builder.add_custom_metadata(fb_custom_metadata);
1817            }
1818            footer_builder.finish()
1819        };
1820        fbb.finish(root, None);
1821        let footer_data = fbb.finished_data();
1822        self.writer.write_all(footer_data)?;
1823        self.writer
1824            .write_all(&(footer_data.len() as i32).to_le_bytes())?;
1825        self.writer.write_all(&super::ARROW_MAGIC)?;
1826        self.writer.flush()?;
1827        self.finished = true;
1828
1829        Ok(())
1830    }
1831
1832    /// Returns the arrow [`SchemaRef`] for this arrow file.
1833    pub fn schema(&self) -> &SchemaRef {
1834        &self.schema
1835    }
1836
1837    /// Gets a reference to the underlying writer.
1838    pub fn get_ref(&self) -> &W {
1839        &self.writer
1840    }
1841
1842    /// Gets a mutable reference to the underlying writer.
1843    ///
1844    /// It is inadvisable to directly write to the underlying writer.
1845    pub fn get_mut(&mut self) -> &mut W {
1846        &mut self.writer
1847    }
1848
1849    /// Flush the underlying writer.
1850    ///
1851    /// Both the BufWriter and the underlying writer are flushed.
1852    pub fn flush(&mut self) -> Result<(), ArrowError> {
1853        self.writer.flush()?;
1854        Ok(())
1855    }
1856
1857    /// Unwraps the underlying writer.
1858    ///
1859    /// The writer is flushed and the FileWriter is finished before returning.
1860    ///
1861    /// # Errors
1862    ///
1863    /// An [`Err`] may be returned if an error occurs while finishing the StreamWriter
1864    /// or while flushing the writer.
1865    pub fn into_inner(mut self) -> Result<W, ArrowError> {
1866        if !self.finished {
1867            // `finish` flushes the writer.
1868            self.finish()?;
1869        }
1870        Ok(self.writer)
1871    }
1872}
1873
1874impl<W: Write> RecordBatchWriter for FileWriter<W> {
1875    fn write(&mut self, batch: &RecordBatch) -> Result<(), ArrowError> {
1876        self.write(batch)
1877    }
1878
1879    fn close(mut self) -> Result<(), ArrowError> {
1880        self.finish()
1881    }
1882}
1883
1884/// Arrow IPC stream encoder.
1885///
1886/// Encodes Arrow [`RecordBatch`]es to byte buffers using the [IPC Streaming Format],
1887/// without performing any IO.
1888///
1889/// The returned [`Buffer`]s are ordered and should be written to the destination
1890/// stream in order. Uncompressed record batch body buffers can share the original
1891/// Arrow buffers instead of being copied into an intermediate contiguous buffer.
1892///
1893/// # Example
1894/// ```
1895/// # use arrow_array::record_batch;
1896/// # use arrow_ipc::writer::StreamEncoder;
1897/// # use arrow_schema::ArrowError;
1898/// # fn main() -> Result<(), ArrowError> {
1899/// let batch = record_batch!(("a", Int32, [1, 2, 3]))?;
1900///
1901/// let mut encoder = StreamEncoder::try_new(&batch.schema())?;
1902/// let mut stream = vec![];
1903/// for buffer in encoder.encode(&batch)? {
1904///     stream.extend_from_slice(buffer.as_slice());
1905/// }
1906/// for buffer in encoder.finish()? {
1907///     stream.extend_from_slice(buffer.as_slice());
1908/// }
1909/// # Ok(())
1910/// # }
1911/// ```
1912pub struct StreamEncoder {
1913    schema: Schema,
1914    /// IPC write options
1915    write_options: IpcWriteOptions,
1916    /// Whether the stream schema has been encoded
1917    schema_encoded: bool,
1918    /// Keeps track of dictionaries that have been encoded
1919    dictionary_tracker: DictionaryTracker,
1920    data_gen: IpcDataGenerator,
1921    ipc_write_context: IpcWriteContext,
1922}
1923
1924impl StreamEncoder {
1925    /// Try to create a new stream encoder.
1926    pub fn try_new(schema: &Schema) -> Result<Self, ArrowError> {
1927        let write_options = IpcWriteOptions::default();
1928        Self::try_new_with_options(schema, write_options)
1929    }
1930
1931    /// Try to create a new stream encoder with [`IpcWriteOptions`].
1932    pub fn try_new_with_options(
1933        schema: &Schema,
1934        write_options: IpcWriteOptions,
1935    ) -> Result<Self, ArrowError> {
1936        ensure_supported_ipc_schema(schema)?;
1937
1938        Ok(Self {
1939            schema: schema.clone(),
1940            write_options,
1941            schema_encoded: false,
1942            dictionary_tracker: DictionaryTracker::new(false),
1943            data_gen: IpcDataGenerator::default(),
1944            ipc_write_context: IpcWriteContext::default(),
1945        })
1946    }
1947
1948    /// Encode a [`RecordBatch`] into buffers.
1949    ///
1950    /// The first call also includes the IPC stream schema message before the
1951    /// record batch message. Later calls only include dictionary and record
1952    /// batch messages.
1953    ///
1954    /// # Errors
1955    ///
1956    /// Returns an error if encoding fails.
1957    pub fn encode(&mut self, batch: &RecordBatch) -> Result<Vec<Buffer>, ArrowError> {
1958        let mut out = vec![];
1959        self.encode_schema(&mut out)?;
1960        self.data_gen.encode_to_buffers(
1961            batch,
1962            &mut self.dictionary_tracker,
1963            &self.write_options,
1964            &mut self.ipc_write_context,
1965            &mut out,
1966        )?;
1967        Ok(out)
1968    }
1969
1970    /// Encode the end-of-stream marker.
1971    ///
1972    /// If no batches have been encoded, this also emits the IPC stream schema
1973    /// message so the returned buffers form a valid empty IPC stream.
1974    ///
1975    /// # Errors
1976    ///
1977    /// Returns an error if encoding the schema or end-of-stream marker fails.
1978    pub fn finish(mut self) -> Result<Vec<Buffer>, ArrowError> {
1979        let mut out = vec![];
1980        self.encode_schema(&mut out)?;
1981        let mut sink = Buffers { out: &mut out };
1982        sink.write_eos(&self.write_options)?;
1983        Ok(out)
1984    }
1985
1986    fn encode_schema(&mut self, out: &mut Vec<Buffer>) -> Result<(), ArrowError> {
1987        if !self.schema_encoded {
1988            let encoded_message = self.data_gen.schema_to_bytes_with_dictionary_tracker(
1989                &self.schema,
1990                &mut self.dictionary_tracker,
1991                &self.write_options,
1992            );
1993            let mut sink = Buffers { out };
1994            sink.write_encoded_data(encoded_message, &self.write_options)?;
1995            self.schema_encoded = true;
1996        }
1997        Ok(())
1998    }
1999}
2000
2001/// Arrow Stream Writer
2002///
2003/// Writes Arrow [`RecordBatch`]es to bytes using the [IPC Streaming Format].
2004///
2005/// # See Also
2006///
2007/// * [`FileWriter`] for writing IPC Files
2008///
2009/// # Example - Basic usage
2010/// ```
2011/// # use arrow_array::record_batch;
2012/// # use arrow_ipc::writer::StreamWriter;
2013/// # let mut stream = vec![]; // mimic a stream for the example
2014/// let batch = record_batch!(("a", Int32, [1, 2, 3])).unwrap();
2015/// // create a new writer, the schema must be known in advance
2016/// let mut writer = StreamWriter::try_new(&mut stream, &batch.schema()).unwrap();
2017/// // write each batch to the underlying stream
2018/// writer.write(&batch).unwrap();
2019/// // When all batches are written, call finish to flush all buffers
2020/// writer.finish().unwrap();
2021/// ```
2022/// # Example - Efficient delta dictionaries
2023/// ```
2024/// # use arrow_array::record_batch;
2025/// # use arrow_ipc::writer::{StreamWriter, IpcWriteOptions};
2026/// # use arrow_ipc::writer::DictionaryHandling;
2027/// # use arrow_schema::{DataType, Field, Schema, SchemaRef};
2028/// # use arrow_array::{
2029/// #    builder::StringDictionaryBuilder, types::Int32Type, Array, ArrayRef, DictionaryArray,
2030/// #    RecordBatch, StringArray,
2031/// # };
2032/// # use std::sync::Arc;
2033///
2034/// let schema = Arc::new(Schema::new(vec![Field::new(
2035///    "col1",
2036///    DataType::Dictionary(Box::from(DataType::Int32), Box::from(DataType::Utf8)),
2037///    true,
2038/// )]));
2039///
2040/// let mut builder = StringDictionaryBuilder::<arrow_array::types::Int32Type>::new();
2041///
2042/// // `finish_preserve_values` will keep the dictionary values along with their
2043/// // key assignments so that they can be re-used in the next batch.
2044/// builder.append("a").unwrap();
2045/// builder.append("b").unwrap();
2046/// let array1 = builder.finish_preserve_values();
2047/// let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(array1) as ArrayRef]).unwrap();
2048///
2049/// // In this batch, 'a' will have the same dictionary key as 'a' in the previous batch,
2050/// // and 'd' will take the next available key.
2051/// builder.append("a").unwrap();
2052/// builder.append("d").unwrap();
2053/// let array2 = builder.finish_preserve_values();
2054/// let batch2 = RecordBatch::try_new(schema.clone(), vec![Arc::new(array2) as ArrayRef]).unwrap();
2055///
2056/// let mut stream = vec![];
2057/// // You must set `.with_dictionary_handling(DictionaryHandling::Delta)` to
2058/// // enable delta dictionaries in the writer
2059/// let options = IpcWriteOptions::default().with_dictionary_handling(DictionaryHandling::Delta);
2060/// let mut writer = StreamWriter::try_new_with_options(&mut stream, &schema, options).unwrap();
2061///
2062/// // When writing the first batch, a dictionary message with 'a' and 'b' will be written
2063/// // prior to the record batch.
2064/// writer.write(&batch1).unwrap();
2065/// // With the second batch only a delta dictionary with 'd' will be written
2066/// // prior to the record batch. This is only possible with `finish_preserve_values`.
2067/// // Without it, 'a' and 'd' in this batch would have different keys than the
2068/// // first batch and so we'd have to send a replacement dictionary with new keys
2069/// // for both.
2070/// writer.write(&batch2).unwrap();
2071/// writer.finish().unwrap();
2072/// ```
2073/// [IPC Streaming Format]: https://arrow.apache.org/docs/format/Columnar.html#ipc-streaming-format
2074pub struct StreamWriter<W> {
2075    /// The object to write to
2076    writer: W,
2077    /// IPC write options
2078    write_options: IpcWriteOptions,
2079    /// Whether the writer footer has been written, and the writer is finished
2080    finished: bool,
2081    /// Keeps track of dictionaries that have been written
2082    dictionary_tracker: DictionaryTracker,
2083
2084    data_gen: IpcDataGenerator,
2085
2086    /// Reusable scratch state shared across all messages.
2087    ipc_write_context: IpcWriteContext,
2088}
2089
2090impl<W: Write> StreamWriter<BufWriter<W>> {
2091    /// Try to create a new stream writer with the writer wrapped in a BufWriter.
2092    ///
2093    /// See [`StreamWriter::try_new`] for an unbuffered version.
2094    pub fn try_new_buffered(writer: W, schema: &Schema) -> Result<Self, ArrowError> {
2095        Self::try_new(BufWriter::new(writer), schema)
2096    }
2097}
2098
2099impl<W: Write> StreamWriter<W> {
2100    /// Try to create a new writer, with the schema written as part of the header.
2101    ///
2102    /// Note that there is no internal buffering. See also [`StreamWriter::try_new_buffered`].
2103    ///
2104    /// # Errors
2105    ///
2106    /// An [`Err`] may be returned if writing the header to the writer fails.
2107    pub fn try_new(writer: W, schema: &Schema) -> Result<Self, ArrowError> {
2108        let write_options = IpcWriteOptions::default();
2109        Self::try_new_with_options(writer, schema, write_options)
2110    }
2111
2112    /// Try to create a new writer with [`IpcWriteOptions`].
2113    ///
2114    /// # Errors
2115    ///
2116    /// An [`Err`] may be returned if writing the header to the writer fails.
2117    pub fn try_new_with_options(
2118        mut writer: W,
2119        schema: &Schema,
2120        write_options: IpcWriteOptions,
2121    ) -> Result<Self, ArrowError> {
2122        ensure_supported_ipc_schema(schema)?;
2123
2124        let data_gen = IpcDataGenerator::default();
2125        let mut dictionary_tracker = DictionaryTracker::new(false);
2126
2127        // write the schema, set the written bytes to the schema
2128        let encoded_message = data_gen.schema_to_bytes_with_dictionary_tracker(
2129            schema,
2130            &mut dictionary_tracker,
2131            &write_options,
2132        );
2133        write_message(&mut writer, encoded_message, &write_options)?;
2134        Ok(Self {
2135            writer,
2136            write_options,
2137            finished: false,
2138            dictionary_tracker,
2139            data_gen,
2140            ipc_write_context: IpcWriteContext::default(),
2141        })
2142    }
2143
2144    /// Write a record batch to the stream
2145    pub fn write(&mut self, batch: &RecordBatch) -> Result<(), ArrowError> {
2146        if self.finished {
2147            return Err(ArrowError::IpcError(
2148                "Cannot write record batch to stream writer as it is closed".to_string(),
2149            ));
2150        }
2151
2152        self.data_gen.write(
2153            batch,
2154            &mut self.dictionary_tracker,
2155            &self.write_options,
2156            &mut self.ipc_write_context,
2157            &mut self.writer,
2158        )?;
2159        Ok(())
2160    }
2161
2162    /// Write continuation bytes, and mark the stream as done
2163    pub fn finish(&mut self) -> Result<(), ArrowError> {
2164        if self.finished {
2165            return Err(ArrowError::IpcError(
2166                "Cannot write footer to stream writer as it is closed".to_string(),
2167            ));
2168        }
2169
2170        self.writer.write_eos(&self.write_options)?;
2171        self.writer.flush()?;
2172
2173        self.finished = true;
2174
2175        Ok(())
2176    }
2177
2178    /// Gets a reference to the underlying writer.
2179    pub fn get_ref(&self) -> &W {
2180        &self.writer
2181    }
2182
2183    /// Gets a mutable reference to the underlying writer.
2184    ///
2185    /// It is inadvisable to directly write to the underlying writer.
2186    pub fn get_mut(&mut self) -> &mut W {
2187        &mut self.writer
2188    }
2189
2190    /// Flush the underlying writer.
2191    ///
2192    /// Both the BufWriter and the underlying writer are flushed.
2193    pub fn flush(&mut self) -> Result<(), ArrowError> {
2194        self.writer.flush()?;
2195        Ok(())
2196    }
2197
2198    /// Unwraps the the underlying writer.
2199    ///
2200    /// The writer is flushed and the StreamWriter is finished before returning.
2201    ///
2202    /// # Errors
2203    ///
2204    /// An [`Err`] may be returned if an error occurs while finishing the StreamWriter
2205    /// or while flushing the writer.
2206    ///
2207    /// # Example
2208    ///
2209    /// ```
2210    /// # use arrow_ipc::writer::{StreamWriter, IpcWriteOptions};
2211    /// # use arrow_ipc::MetadataVersion;
2212    /// # use arrow_schema::{ArrowError, Schema};
2213    /// # fn main() -> Result<(), ArrowError> {
2214    /// // The result we expect from an empty schema
2215    /// let expected = vec![
2216    ///     255, 255, 255, 255,  48,   0,   0,   0,
2217    ///      16,   0,   0,   0,   0,   0,  10,   0,
2218    ///      12,   0,  10,   0,   9,   0,   4,   0,
2219    ///      10,   0,   0,   0,  16,   0,   0,   0,
2220    ///       0,   1,   4,   0,   8,   0,   8,   0,
2221    ///       0,   0,   4,   0,   8,   0,   0,   0,
2222    ///       4,   0,   0,   0,   0,   0,   0,   0,
2223    ///     255, 255, 255, 255,   0,   0,   0,   0
2224    /// ];
2225    ///
2226    /// let schema = Schema::empty();
2227    /// let buffer: Vec<u8> = Vec::new();
2228    /// let options = IpcWriteOptions::try_new(8, false, MetadataVersion::V5)?;
2229    /// let stream_writer = StreamWriter::try_new_with_options(buffer, &schema, options)?;
2230    ///
2231    /// assert_eq!(stream_writer.into_inner()?, expected);
2232    /// # Ok(())
2233    /// # }
2234    /// ```
2235    pub fn into_inner(mut self) -> Result<W, ArrowError> {
2236        if !self.finished {
2237            // `finish` flushes.
2238            self.finish()?;
2239        }
2240        Ok(self.writer)
2241    }
2242}
2243
2244impl<W: Write> RecordBatchWriter for StreamWriter<W> {
2245    fn write(&mut self, batch: &RecordBatch) -> Result<(), ArrowError> {
2246        self.write(batch)
2247    }
2248
2249    fn close(mut self) -> Result<(), ArrowError> {
2250        self.finish()
2251    }
2252}
2253
2254/// Stores the encoded data, which is an crate::Message, and optional Arrow data
2255pub struct EncodedData {
2256    /// An encoded crate::Message
2257    pub ipc_message: Vec<u8>,
2258    /// Arrow buffers to be written, should be an empty vec for schema messages
2259    pub arrow_data: Vec<u8>,
2260}
2261
2262/// Write a message's IPC data and buffers, returning metadata and buffer data lengths written
2263pub fn write_message<W: Write>(
2264    mut writer: W,
2265    encoded: EncodedData,
2266    write_options: &IpcWriteOptions,
2267) -> Result<(usize, usize), ArrowError> {
2268    writer.write_encoded_data(encoded, write_options)
2269}
2270
2271/// In V4, null types have no validity bitmap
2272/// In V5 and later, null and union types have no validity bitmap
2273/// Run end encoded type has no validity bitmap.
2274fn has_validity_bitmap(data_type: &DataType, write_options: &IpcWriteOptions) -> bool {
2275    if write_options.metadata_version < crate::MetadataVersion::V5 {
2276        !matches!(data_type, DataType::Null)
2277    } else {
2278        !matches!(
2279            data_type,
2280            DataType::Null | DataType::Union(_, _) | DataType::RunEndEncoded(_, _)
2281        )
2282    }
2283}
2284
2285/// Whether to truncate the buffer
2286#[inline]
2287fn buffer_need_truncate(
2288    array_offset: usize,
2289    buffer: &Buffer,
2290    spec: &BufferSpec,
2291    min_length: usize,
2292) -> bool {
2293    spec != &BufferSpec::AlwaysNull && (array_offset != 0 || min_length < buffer.len())
2294}
2295
2296/// Returns byte width for a buffer spec. Only for `BufferSpec::FixedWidth`.
2297#[inline]
2298fn get_buffer_element_width(spec: &BufferSpec) -> usize {
2299    match spec {
2300        BufferSpec::FixedWidth { byte_width, .. } => *byte_width,
2301        _ => 0,
2302    }
2303}
2304
2305/// Common functionality for re-encoding offsets. Returns the new offsets as well as
2306/// original start offset and length for use in slicing child data.
2307fn reencode_offsets<O: OffsetSizeTrait>(
2308    offsets: &Buffer,
2309    data: &ArrayData,
2310) -> (Buffer, usize, usize) {
2311    let offsets_slice: &[O] = offsets.typed_data::<O>();
2312    let offset_slice = &offsets_slice[data.offset()..data.offset() + data.len() + 1];
2313
2314    let start_offset = offset_slice.first().unwrap();
2315    let end_offset = offset_slice.last().unwrap();
2316
2317    let offsets = match start_offset.as_usize() {
2318        0 => {
2319            let size = size_of::<O>();
2320            offsets.slice_with_length(data.offset() * size, (data.len() + 1) * size)
2321        }
2322        _ => offset_slice.iter().map(|x| *x - *start_offset).collect(),
2323    };
2324
2325    let start_offset = start_offset.as_usize();
2326    let end_offset = end_offset.as_usize();
2327
2328    (offsets, start_offset, end_offset - start_offset)
2329}
2330
2331/// Returns the values and offsets [`Buffer`] for a ByteArray with offset type `O`
2332///
2333/// In particular, this handles re-encoding the offsets if they don't start at `0`,
2334/// slicing the values buffer as appropriate. This helps reduce the encoded
2335/// size of sliced arrays, as values that have been sliced away are not encoded
2336/// Returns the offsets and values buffers, in that order.
2337fn get_byte_array_buffers<O: OffsetSizeTrait>(data: &ArrayData) -> [Buffer; 2] {
2338    if data.is_empty() {
2339        // As per specification, offsets buffer has N+1 elements.
2340        // So an empty array should still be encoded with a single 0 offset.
2341        let mut offsets = MutableBuffer::new(size_of::<O>());
2342        offsets.extend_from_slice(O::usize_as(0).to_byte_slice());
2343        return [offsets.into(), MutableBuffer::new(0).into()];
2344    }
2345
2346    let (offsets, original_start_offset, len) = reencode_offsets::<O>(&data.buffers()[0], data);
2347    let values = data.buffers()[1].slice_with_length(original_start_offset, len);
2348    [offsets, values]
2349}
2350
2351/// Similar logic as [`get_byte_array_buffers()`] but slices the child array instead
2352/// of a values buffer.
2353fn get_list_array_buffers<O: OffsetSizeTrait>(data: &ArrayData) -> (Buffer, ArrayData) {
2354    if data.is_empty() {
2355        // As per specification, offsets buffer has N+1 elements.
2356        // So an empty array should still be encoded with a single 0 offset.
2357        let mut offsets = MutableBuffer::new(size_of::<O>());
2358        offsets.extend_from_slice(O::usize_as(0).to_byte_slice());
2359        return (offsets.into(), data.child_data()[0].slice(0, 0));
2360    }
2361
2362    let (offsets, original_start_offset, len) = reencode_offsets::<O>(&data.buffers()[0], data);
2363    let child_data = data.child_data()[0].slice(original_start_offset, len);
2364    (offsets, child_data)
2365}
2366
2367/// Returns the offsets, sizes, and child data buffers for a ListView array.
2368///
2369/// Unlike List arrays, ListView arrays store both offsets and sizes explicitly,
2370/// and offsets can be non-monotonic. When slicing, we simply pass through the
2371/// offsets and sizes without re-encoding, and do not slice the child data.
2372fn get_list_view_array_buffers<O: OffsetSizeTrait>(
2373    data: &ArrayData,
2374) -> (Buffer, Buffer, ArrayData) {
2375    if data.is_empty() {
2376        return (
2377            MutableBuffer::new(0).into(),
2378            MutableBuffer::new(0).into(),
2379            data.child_data()[0].slice(0, 0),
2380        );
2381    }
2382
2383    let offsets = &data.buffers()[0];
2384    let sizes = &data.buffers()[1];
2385
2386    let element_size = std::mem::size_of::<O>();
2387    let offsets_slice =
2388        offsets.slice_with_length(data.offset() * element_size, data.len() * element_size);
2389    let sizes_slice =
2390        sizes.slice_with_length(data.offset() * element_size, data.len() * element_size);
2391
2392    let child_data = data.child_data()[0].clone();
2393
2394    (offsets_slice, sizes_slice, child_data)
2395}
2396
2397/// Returns the sliced views [`Buffer`] for a BinaryView/Utf8View array.
2398///
2399/// The views buffer is sliced to only include views in the valid range based on
2400/// the array's offset and length. This helps reduce the encoded size of sliced
2401/// arrays
2402///
2403fn get_or_truncate_buffer(array_data: &ArrayData) -> Buffer {
2404    let buffer = &array_data.buffers()[0];
2405    let layout = layout(array_data.data_type());
2406    let spec = &layout.buffers[0];
2407
2408    let byte_width = get_buffer_element_width(spec);
2409    let min_length = array_data.len() * byte_width;
2410    if buffer_need_truncate(array_data.offset(), buffer, spec, min_length) {
2411        let byte_offset = array_data.offset() * byte_width;
2412        let buffer_length = min(min_length, buffer.len() - byte_offset);
2413        buffer.slice_with_length(byte_offset, buffer_length)
2414    } else {
2415        buffer.clone()
2416    }
2417}
2418
2419/// Recursively encodes `array_data` into its IPC representation.
2420///
2421/// Output goes to two separate channels:
2422/// - `meta`: accumulates IPC metadata (`nodes` and `buffers`) for the flatbuffer header.
2423/// - `sink`: the raw Arrow data bytes that form the IPC message body.
2424fn write_array_data(
2425    array_data: &ArrayData,
2426    meta: &mut IpcMetadataBuilder,
2427    sink: &mut IpcBodySink<'_>,
2428    offset: i64,
2429    compression_codec: Option<CompressionCodec>,
2430    ipc_write_context: &mut IpcWriteContext,
2431    write_options: &IpcWriteOptions,
2432) -> Result<i64, ArrowError> {
2433    let mut offset = offset;
2434    let num_rows = array_data.len();
2435    let null_count = array_data.null_count();
2436    if !matches!(array_data.data_type(), DataType::Null) {
2437        meta.nodes
2438            .push(crate::FieldNode::new(num_rows as i64, null_count as i64));
2439    } else {
2440        // NullArray's null_count equals to len, but ArrayData null_count is always 0.
2441        meta.nodes
2442            .push(crate::FieldNode::new(num_rows as i64, num_rows as i64));
2443    }
2444    if has_validity_bitmap(array_data.data_type(), write_options) {
2445        // Fields with null_count == 0 may write a 0-length validity buffer
2446        // instead of a materialized bitmap (see FieldNode in Message.fbs)
2447        let null_buffer = match array_data.nulls() {
2448            Some(buffer) if null_count > 0 => buffer.inner().sliced(),
2449            _ => MutableBuffer::new(0).into(),
2450        };
2451
2452        offset = encode_sink_buffer(
2453            null_buffer,
2454            meta,
2455            sink,
2456            offset,
2457            compression_codec,
2458            ipc_write_context,
2459            write_options.alignment,
2460        )?;
2461    }
2462
2463    let data_type = array_data.data_type();
2464    if matches!(data_type, DataType::Binary | DataType::Utf8) {
2465        for buffer in get_byte_array_buffers::<i32>(array_data) {
2466            offset = encode_sink_buffer(
2467                buffer,
2468                meta,
2469                sink,
2470                offset,
2471                compression_codec,
2472                ipc_write_context,
2473                write_options.alignment,
2474            )?;
2475        }
2476    } else if matches!(data_type, DataType::BinaryView | DataType::Utf8View) {
2477        // Slicing the views buffer is safe and easy,
2478        // but pruning unneeded data buffers is much more nuanced since it's complicated to prove that no views reference the pruned buffers
2479        //
2480        // Current implementation just serialize the raw arrays as given and not try to optimize anything.
2481        // If users wants to "compact" the arrays prior to sending them over IPC,
2482        // they should consider the gc API suggested in #5513
2483        let views = get_or_truncate_buffer(array_data);
2484        offset = encode_sink_buffer(
2485            views,
2486            meta,
2487            sink,
2488            offset,
2489            compression_codec,
2490            ipc_write_context,
2491            write_options.alignment,
2492        )?;
2493
2494        for buffer in array_data.buffers().iter().skip(1) {
2495            offset = encode_sink_buffer(
2496                buffer.clone(),
2497                meta,
2498                sink,
2499                offset,
2500                compression_codec,
2501                ipc_write_context,
2502                write_options.alignment,
2503            )?;
2504        }
2505    } else if matches!(data_type, DataType::LargeBinary | DataType::LargeUtf8) {
2506        for buffer in get_byte_array_buffers::<i64>(array_data) {
2507            offset = encode_sink_buffer(
2508                buffer,
2509                meta,
2510                sink,
2511                offset,
2512                compression_codec,
2513                ipc_write_context,
2514                write_options.alignment,
2515            )?;
2516        }
2517    } else if DataType::is_numeric(data_type)
2518        || DataType::is_temporal(data_type)
2519        || matches!(
2520            array_data.data_type(),
2521            DataType::FixedSizeBinary(_) | DataType::Dictionary(_, _)
2522        )
2523    {
2524        // Truncate values
2525        assert_eq!(array_data.buffers().len(), 1);
2526
2527        let buffer = get_or_truncate_buffer(array_data);
2528        offset = encode_sink_buffer(
2529            buffer,
2530            meta,
2531            sink,
2532            offset,
2533            compression_codec,
2534            ipc_write_context,
2535            write_options.alignment,
2536        )?;
2537    } else if matches!(data_type, DataType::Boolean) {
2538        // Bools are special because the payload (= 1 bit) is smaller than the physical container elements (= bytes).
2539        // The array data may not start at the physical boundary of the underlying buffer, so we need to shift bits around.
2540        assert_eq!(array_data.buffers().len(), 1);
2541
2542        let buffer = &array_data.buffers()[0];
2543        let buffer = buffer.bit_slice(array_data.offset(), array_data.len());
2544        offset = encode_sink_buffer(
2545            buffer,
2546            meta,
2547            sink,
2548            offset,
2549            compression_codec,
2550            ipc_write_context,
2551            write_options.alignment,
2552        )?;
2553    } else if matches!(
2554        data_type,
2555        DataType::List(_) | DataType::LargeList(_) | DataType::Map(_, _)
2556    ) {
2557        assert_eq!(array_data.buffers().len(), 1);
2558        assert_eq!(array_data.child_data().len(), 1);
2559
2560        // Truncate offsets and the child data to avoid writing unnecessary data
2561        let (offsets, sliced_child_data) = match data_type {
2562            DataType::List(_) => get_list_array_buffers::<i32>(array_data),
2563            DataType::Map(_, _) => get_list_array_buffers::<i32>(array_data),
2564            DataType::LargeList(_) => get_list_array_buffers::<i64>(array_data),
2565            _ => unreachable!(),
2566        };
2567        offset = encode_sink_buffer(
2568            offsets,
2569            meta,
2570            sink,
2571            offset,
2572            compression_codec,
2573            ipc_write_context,
2574            write_options.alignment,
2575        )?;
2576        offset = write_array_data(
2577            &sliced_child_data,
2578            meta,
2579            sink,
2580            offset,
2581            compression_codec,
2582            ipc_write_context,
2583            write_options,
2584        )?;
2585        return Ok(offset);
2586    } else if matches!(
2587        data_type,
2588        DataType::ListView(_) | DataType::LargeListView(_)
2589    ) {
2590        assert_eq!(array_data.buffers().len(), 2); // offsets + sizes
2591        assert_eq!(array_data.child_data().len(), 1);
2592
2593        let (offsets, sizes, child_data) = match data_type {
2594            DataType::ListView(_) => get_list_view_array_buffers::<i32>(array_data),
2595            DataType::LargeListView(_) => get_list_view_array_buffers::<i64>(array_data),
2596            _ => unreachable!(),
2597        };
2598
2599        offset = encode_sink_buffer(
2600            offsets,
2601            meta,
2602            sink,
2603            offset,
2604            compression_codec,
2605            ipc_write_context,
2606            write_options.alignment,
2607        )?;
2608        offset = encode_sink_buffer(
2609            sizes,
2610            meta,
2611            sink,
2612            offset,
2613            compression_codec,
2614            ipc_write_context,
2615            write_options.alignment,
2616        )?;
2617
2618        offset = write_array_data(
2619            &child_data,
2620            meta,
2621            sink,
2622            offset,
2623            compression_codec,
2624            ipc_write_context,
2625            write_options,
2626        )?;
2627        return Ok(offset);
2628    } else if let DataType::FixedSizeList(_, fixed_size) = data_type {
2629        assert_eq!(array_data.child_data().len(), 1);
2630        let fixed_size = *fixed_size as usize;
2631
2632        let child_offset = array_data.offset() * fixed_size;
2633        let child_length = array_data.len() * fixed_size;
2634        let child_data = array_data.child_data()[0].slice(child_offset, child_length);
2635
2636        offset = write_array_data(
2637            &child_data,
2638            meta,
2639            sink,
2640            offset,
2641            compression_codec,
2642            ipc_write_context,
2643            write_options,
2644        )?;
2645        return Ok(offset);
2646    } else if matches!(data_type, DataType::Union(_, _)) {
2647        for (buffer, byte_width) in array_data
2648            .buffers()
2649            .iter()
2650            .zip([size_of::<i8>(), size_of::<i32>()])
2651        {
2652            let buffer =
2653                buffer.slice_with_length(array_data.offset() * byte_width, num_rows * byte_width);
2654            offset = encode_sink_buffer(
2655                buffer,
2656                meta,
2657                sink,
2658                offset,
2659                compression_codec,
2660                ipc_write_context,
2661                write_options.alignment,
2662            )?;
2663        }
2664    } else {
2665        for buffer in array_data.buffers() {
2666            offset = encode_sink_buffer(
2667                buffer.clone(),
2668                meta,
2669                sink,
2670                offset,
2671                compression_codec,
2672                ipc_write_context,
2673                write_options.alignment,
2674            )?;
2675        }
2676    }
2677
2678    match array_data.data_type() {
2679        DataType::Dictionary(_, _) => {}
2680        DataType::RunEndEncoded(_, _) => {
2681            // unslice the run encoded array.
2682            let arr = unslice_run_array(array_data.clone())?;
2683            // recursively write out nested structures
2684            for data_ref in arr.child_data() {
2685                // write the nested data (e.g list data)
2686                offset = write_array_data(
2687                    data_ref,
2688                    meta,
2689                    sink,
2690                    offset,
2691                    compression_codec,
2692                    ipc_write_context,
2693                    write_options,
2694                )?;
2695            }
2696        }
2697        DataType::Union(_, UnionMode::Sparse) => {
2698            for data_ref in array_data.child_data() {
2699                let child_data = data_ref.slice(array_data.offset(), num_rows);
2700                offset = write_array_data(
2701                    &child_data,
2702                    meta,
2703                    sink,
2704                    offset,
2705                    compression_codec,
2706                    ipc_write_context,
2707                    write_options,
2708                )?;
2709            }
2710        }
2711        _ => {
2712            // recursively write out nested structures
2713            for data_ref in array_data.child_data() {
2714                // write the nested data (e.g list data)
2715                offset = write_array_data(
2716                    data_ref,
2717                    meta,
2718                    sink,
2719                    offset,
2720                    compression_codec,
2721                    ipc_write_context,
2722                    write_options,
2723                )?;
2724            }
2725        }
2726    }
2727    Ok(offset)
2728}
2729
2730/// Encodes a single Arrow [`Buffer`] into the IPC body and records its metadata.
2731///
2732/// - `buffer`: the Arrow data buffer to encode (validity bitmap, offsets, values, etc.)
2733/// - `buffers`: in-progress list of IPC `Buffer` metadata entries (body offset + length) that
2734///   will eventually be serialised into the flatbuffer `RecordBatch` header.
2735/// - `sink`: destination for the actual encoded bytes; either a contiguous `Vec<u8>` for
2736///   in-memory writes, or a list of [`EncodedBuffer`] segments for deferred zero-copy streaming.
2737/// - `offset`: running byte offset into the IPC message body, used to compute the metadata entry.
2738/// - `compression_codec` / `ipc_write_context`: if `Some`, the buffer is compressed before
2739///   writing; `ipc_write_context` provides reusable scratch space across calls.
2740/// - `alignment`: each buffer is padded to this many bytes so the next buffer starts aligned.
2741///
2742/// Returns the updated `offset` (advanced by the encoded length plus any alignment padding).
2743fn encode_sink_buffer(
2744    buffer: Buffer,
2745    ipc_meta_data: &mut IpcMetadataBuilder,
2746    sink: &mut IpcBodySink<'_>,
2747    offset: i64,
2748    compression_codec: Option<CompressionCodec>,
2749    ipc_write_context: &mut IpcWriteContext,
2750    alignment: u8,
2751) -> Result<i64, ArrowError> {
2752    let (encoded, len) = match compression_codec {
2753        None => {
2754            let len = buffer.len() as i64;
2755            (EncodedBuffer::Raw(buffer), len)
2756        }
2757        Some(codec) => {
2758            let mut scratch = Vec::new();
2759            let written =
2760                codec.compress_to_vec(buffer.as_slice(), &mut scratch, ipc_write_context)?;
2761            let len = i64::try_from(written)
2762                .map_err(|e| ArrowError::InvalidArgumentError(format!("{e}")))?;
2763            (EncodedBuffer::Compressed(scratch), len)
2764        }
2765    };
2766
2767    let pad_len = pad_to_alignment(alignment, len as usize);
2768    sink.write(pad_len, encoded);
2769    ipc_meta_data.buffers.push(crate::Buffer::new(offset, len));
2770    Ok(offset + len + pad_len as i64)
2771}
2772
2773const PADDING: [u8; 64] = [0; 64];
2774
2775/// Estimates the number of [`EncodedBuffer`] segments that [`write_array_data`]
2776/// will produce for a column of the given type.
2777///
2778/// Based on the Arrow IPC buffer layout
2779/// (<https://arrow.apache.org/docs/format/Columnar.html#recordbatch-message>):
2780#[inline]
2781fn estimate_encoded_buffer_count(dt: &DataType) -> usize {
2782    match dt {
2783        DataType::Null => 0,
2784
2785        DataType::Binary | DataType::Utf8 | DataType::LargeBinary | DataType::LargeUtf8 => 3,
2786
2787        DataType::BinaryView | DataType::Utf8View => 3,
2788
2789        DataType::List(f) | DataType::LargeList(f) | DataType::Map(f, _) => {
2790            2 + estimate_encoded_buffer_count(f.data_type())
2791        }
2792
2793        DataType::ListView(f) | DataType::LargeListView(f) => {
2794            3 + estimate_encoded_buffer_count(f.data_type())
2795        }
2796
2797        DataType::FixedSizeList(f, _) => 1 + estimate_encoded_buffer_count(f.data_type()),
2798
2799        DataType::Struct(fields) => {
2800            1 + fields
2801                .iter()
2802                .map(|f| estimate_encoded_buffer_count(f.data_type()))
2803                .sum::<usize>()
2804        }
2805
2806        // Dictionary indices only; dictionary body is a separate IPC message.
2807        DataType::Dictionary(_, _) => 2,
2808
2809        DataType::Union(fields, UnionMode::Sparse) => {
2810            1 + fields
2811                .iter()
2812                .map(|(_, f)| estimate_encoded_buffer_count(f.data_type()))
2813                .sum::<usize>()
2814        }
2815        DataType::Union(fields, UnionMode::Dense) => {
2816            2 + fields
2817                .iter()
2818                .map(|(_, f)| estimate_encoded_buffer_count(f.data_type()))
2819                .sum::<usize>()
2820        }
2821
2822        DataType::RunEndEncoded(run_ends, values) => {
2823            estimate_encoded_buffer_count(run_ends.data_type())
2824                + estimate_encoded_buffer_count(values.data_type())
2825        }
2826        // Primitive, Bool, temporal, Decimal*, FixedSizeBinary: validity + values.
2827        _ => 2,
2828    }
2829}
2830
2831/// Calculate an alignment boundary and return the number of bytes needed to pad to the alignment boundary
2832#[inline]
2833fn pad_to_alignment(alignment: u8, len: usize) -> usize {
2834    let a = usize::from(alignment - 1);
2835    ((len + a) & !a) - len
2836}
2837
2838#[cfg(test)]
2839mod tests {
2840    use std::hash::Hasher;
2841    use std::io::Cursor;
2842    use std::io::Seek;
2843
2844    use arrow_array::builder::FixedSizeListBuilder;
2845    use arrow_array::builder::Float32Builder;
2846    use arrow_array::builder::Int64Builder;
2847    use arrow_array::builder::MapBuilder;
2848    use arrow_array::builder::StringViewBuilder;
2849    use arrow_array::builder::UnionBuilder;
2850    use arrow_array::builder::{
2851        GenericListBuilder, GenericListViewBuilder, ListBuilder, StringBuilder,
2852    };
2853    use arrow_array::builder::{PrimitiveRunBuilder, UInt32Builder};
2854    use arrow_array::types::*;
2855    use arrow_buffer::ScalarBuffer;
2856
2857    use crate::MetadataVersion;
2858    use crate::convert::try_fb_to_schema;
2859    use crate::reader::*;
2860    use crate::root_as_footer;
2861
2862    use super::*;
2863
2864    #[test]
2865    fn test_roundtrip_sliced_list_of_union() {
2866        fn decoded(array: &ArrayRef) -> Vec<Vec<Option<i32>>> {
2867            (0..array.len())
2868                .map(|row| {
2869                    let values = array.as_list::<i32>().value(row);
2870                    let union = values.as_union();
2871                    (0..union.len())
2872                        .map(|index| {
2873                            union
2874                                .value(index)
2875                                .as_primitive::<Int32Type>()
2876                                .iter()
2877                                .next()
2878                                .unwrap()
2879                        })
2880                        .collect()
2881                })
2882                .collect()
2883        }
2884
2885        for mode in [UnionMode::Dense, UnionMode::Sparse] {
2886            let fields = UnionFields::try_new(
2887                [3, 7],
2888                [
2889                    Field::new("a", DataType::Int32, true),
2890                    Field::new("b", DataType::Int32, true),
2891                ],
2892            )
2893            .unwrap();
2894            let offsets = (mode == UnionMode::Dense).then(|| vec![2, 0, 1].into());
2895            let union: ArrayRef = Arc::new(
2896                UnionArray::try_new(
2897                    fields,
2898                    vec![3_i8, 7, 3].into(),
2899                    offsets,
2900                    vec![
2901                        Arc::new(Int32Array::from(vec![Some(11), None, Some(33)])),
2902                        Arc::new(Int32Array::from(vec![Some(101), Some(202), None])),
2903                    ],
2904                )
2905                .unwrap(),
2906            );
2907            let item = Arc::new(Field::new("item", union.data_type().clone(), true));
2908            let list: ArrayRef = Arc::new(ListArray::new(
2909                item,
2910                arrow_buffer::OffsetBuffer::from_lengths([1, 2]),
2911                union,
2912                None,
2913            ));
2914            for (offset, len) in [(0, 2), (1, 1), (2, 0)] {
2915                let array = list.slice(offset, len);
2916                array.to_data().validate_full().unwrap();
2917                let expected = decoded(&array);
2918                let schema = Arc::new(Schema::new(vec![Field::new(
2919                    "v",
2920                    array.data_type().clone(),
2921                    true,
2922                )]));
2923                let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
2924                for output in [
2925                    deserialize_file(serialize_file(&batch)),
2926                    deserialize_stream(serialize_stream(&batch)),
2927                ] {
2928                    output.column(0).to_data().validate_full().unwrap();
2929                    assert_eq!(decoded(output.column(0)), expected);
2930                }
2931            }
2932        }
2933    }
2934
2935    fn serialize_file(rb: &RecordBatch) -> Vec<u8> {
2936        let mut writer = FileWriter::try_new(vec![], rb.schema_ref()).unwrap();
2937        writer.write(rb).unwrap();
2938        writer.finish().unwrap();
2939        writer.into_inner().unwrap()
2940    }
2941
2942    fn deserialize_file(bytes: Vec<u8>) -> RecordBatch {
2943        let mut reader = FileReader::try_new(Cursor::new(bytes), None).unwrap();
2944        reader.next().unwrap().unwrap()
2945    }
2946
2947    fn serialize_stream(record: &RecordBatch) -> Vec<u8> {
2948        // Use 8-byte alignment so that the various `truncate_*` tests can be compactly written,
2949        // without needing to construct a giant array to spill over the 64-byte default alignment
2950        // boundary.
2951        const IPC_ALIGNMENT: usize = 8;
2952
2953        let mut stream_writer = StreamWriter::try_new_with_options(
2954            vec![],
2955            record.schema_ref(),
2956            IpcWriteOptions::try_new(IPC_ALIGNMENT, false, MetadataVersion::V5).unwrap(),
2957        )
2958        .unwrap();
2959        stream_writer.write(record).unwrap();
2960        stream_writer.finish().unwrap();
2961        stream_writer.into_inner().unwrap()
2962    }
2963
2964    fn deserialize_stream(bytes: Vec<u8>) -> RecordBatch {
2965        let mut stream_reader = StreamReader::try_new(Cursor::new(bytes), None).unwrap();
2966        stream_reader.next().unwrap().unwrap()
2967    }
2968
2969    /// Encodes record batches with [`StreamEncoder`] into one contiguous byte vector.
2970    ///
2971    /// This still exercises the sans-IO encoder path; `Vec<u8>` is only used
2972    /// as a convenient [`Write`] sink for comparing the resulting byte stream.
2973    fn encode_stream(
2974        schema: &Schema,
2975        batches: &[RecordBatch],
2976        options: IpcWriteOptions,
2977    ) -> Vec<u8> {
2978        let mut encoder = StreamEncoder::try_new_with_options(schema, options).unwrap();
2979        let mut bytes = Vec::new();
2980        for batch in batches {
2981            for buffer in encoder.encode(batch).unwrap() {
2982                bytes.write_all(buffer.as_slice()).unwrap();
2983            }
2984        }
2985        for buffer in encoder.finish().unwrap() {
2986            bytes.write_all(buffer.as_slice()).unwrap();
2987        }
2988        bytes
2989    }
2990
2991    fn write_stream(schema: &Schema, batches: &[RecordBatch], options: IpcWriteOptions) -> Vec<u8> {
2992        let mut bytes = Vec::new();
2993        let mut writer = StreamWriter::try_new_with_options(&mut bytes, schema, options).unwrap();
2994        for batch in batches {
2995            writer.write(batch).unwrap();
2996        }
2997        writer.finish().unwrap();
2998        bytes
2999    }
3000
3001    // StreamEncoder and StreamWriter currently use separate encoding paths, so these tests
3002    // verify the new sans-IO API preserves the existing IPC stream byte layout.
3003    #[tokio::test]
3004    async fn test_stream_encoder_async_writer_matches_stream_writer() {
3005        use tokio::io::{AsyncReadExt, AsyncWriteExt};
3006
3007        let batch = record_batch!(("a", Int32, [1, 2, 3]), ("b", Utf8, ["x", "y", "z"])).unwrap();
3008        let options = IpcWriteOptions::default();
3009        let expected = write_stream(
3010            batch.schema_ref(),
3011            std::slice::from_ref(&batch),
3012            options.clone(),
3013        );
3014
3015        let (mut sink, mut source) = tokio::io::duplex(64);
3016        let read = tokio::spawn(async move {
3017            let mut bytes = Vec::new();
3018            source.read_to_end(&mut bytes).await.unwrap();
3019            bytes
3020        });
3021
3022        let mut encoder = StreamEncoder::try_new_with_options(batch.schema_ref(), options).unwrap();
3023        for buffer in encoder.encode(&batch).unwrap() {
3024            sink.write_all(buffer.as_slice()).await.unwrap();
3025        }
3026        for buffer in encoder.finish().unwrap() {
3027            sink.write_all(buffer.as_slice()).await.unwrap();
3028        }
3029        sink.shutdown().await.unwrap();
3030
3031        let encoded = read.await.unwrap();
3032        assert_eq!(encoded, expected);
3033
3034        let mut reader = StreamReader::try_new(Cursor::new(encoded), None).unwrap();
3035        assert_eq!(reader.next().unwrap().unwrap(), batch);
3036        assert!(reader.next().is_none());
3037    }
3038
3039    #[test]
3040    fn test_stream_encoder_empty_stream_matches_stream_writer() {
3041        let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
3042        let options = IpcWriteOptions::default();
3043        let encoded = encode_stream(&schema, &[], options.clone());
3044        let written = write_stream(&schema, &[], options);
3045
3046        assert_eq!(encoded, written);
3047
3048        let mut reader = StreamReader::try_new(Cursor::new(encoded), None).unwrap();
3049        assert!(reader.next().is_none());
3050    }
3051
3052    #[test]
3053    fn test_stream_encoder_dictionary_batches_match_stream_writer() {
3054        let schema = Arc::new(Schema::new(vec![Field::new(
3055            "a",
3056            DataType::Dictionary(Box::new(DataType::UInt8), Box::new(DataType::Utf8)),
3057            false,
3058        )]));
3059        let batch = RecordBatch::try_new(
3060            schema.clone(),
3061            vec![Arc::new(DictionaryArray::new(
3062                UInt8Array::from_iter_values([0, 1, 0]),
3063                Arc::new(StringArray::from_iter_values(["a", "b"])),
3064            ))],
3065        )
3066        .unwrap();
3067        let options = IpcWriteOptions::default();
3068        let encoded = encode_stream(&schema, std::slice::from_ref(&batch), options.clone());
3069        let written = write_stream(&schema, std::slice::from_ref(&batch), options);
3070
3071        assert_eq!(encoded, written);
3072
3073        let mut reader = StreamReader::try_new(Cursor::new(encoded), None).unwrap();
3074        assert_eq!(reader.next().unwrap().unwrap(), batch);
3075        assert!(reader.next().is_none());
3076    }
3077
3078    #[test]
3079    #[cfg(feature = "lz4")]
3080    fn test_write_empty_record_batch_lz4_compression() {
3081        let schema = Schema::new(vec![Field::new("field1", DataType::Int32, true)]);
3082        let values: Vec<Option<i32>> = vec![];
3083        let array = Int32Array::from(values);
3084        let record_batch =
3085            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(array)]).unwrap();
3086
3087        let mut file = tempfile::tempfile().unwrap();
3088
3089        {
3090            let write_option = IpcWriteOptions::try_new(8, false, crate::MetadataVersion::V5)
3091                .unwrap()
3092                .try_with_compression(Some(crate::CompressionType::LZ4_FRAME))
3093                .unwrap();
3094
3095            let mut writer =
3096                FileWriter::try_new_with_options(&mut file, &schema, write_option).unwrap();
3097            writer.write(&record_batch).unwrap();
3098            writer.finish().unwrap();
3099        }
3100        file.rewind().unwrap();
3101        {
3102            // read file
3103            let reader = FileReader::try_new(file, None).unwrap();
3104            for read_batch in reader {
3105                read_batch
3106                    .unwrap()
3107                    .columns()
3108                    .iter()
3109                    .zip(record_batch.columns())
3110                    .for_each(|(a, b)| {
3111                        assert_eq!(a.data_type(), b.data_type());
3112                        assert_eq!(a.len(), b.len());
3113                        assert_eq!(a.null_count(), b.null_count());
3114                    });
3115            }
3116        }
3117    }
3118
3119    #[test]
3120    #[cfg(feature = "lz4")]
3121    fn test_write_file_with_lz4_compression() {
3122        let schema = Schema::new(vec![Field::new("field1", DataType::Int32, true)]);
3123        let values: Vec<Option<i32>> = vec![Some(12), Some(1)];
3124        let array = Int32Array::from(values);
3125        let record_batch =
3126            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(array)]).unwrap();
3127
3128        let mut file = tempfile::tempfile().unwrap();
3129        {
3130            let write_option = IpcWriteOptions::try_new(8, false, crate::MetadataVersion::V5)
3131                .unwrap()
3132                .try_with_compression(Some(crate::CompressionType::LZ4_FRAME))
3133                .unwrap();
3134
3135            let mut writer =
3136                FileWriter::try_new_with_options(&mut file, &schema, write_option).unwrap();
3137            writer.write(&record_batch).unwrap();
3138            writer.finish().unwrap();
3139        }
3140        file.rewind().unwrap();
3141        {
3142            // read file
3143            let reader = FileReader::try_new(file, None).unwrap();
3144            for read_batch in reader {
3145                read_batch
3146                    .unwrap()
3147                    .columns()
3148                    .iter()
3149                    .zip(record_batch.columns())
3150                    .for_each(|(a, b)| {
3151                        assert_eq!(a.data_type(), b.data_type());
3152                        assert_eq!(a.len(), b.len());
3153                        assert_eq!(a.null_count(), b.null_count());
3154                    });
3155            }
3156        }
3157    }
3158
3159    #[test]
3160    #[cfg(feature = "zstd")]
3161    fn test_write_file_with_zstd_compression() {
3162        let schema = Schema::new(vec![Field::new("field1", DataType::Int32, true)]);
3163        let values: Vec<Option<i32>> = vec![Some(12), Some(1)];
3164        let array = Int32Array::from(values);
3165        let record_batch =
3166            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(array)]).unwrap();
3167        let mut file = tempfile::tempfile().unwrap();
3168        {
3169            let write_option = IpcWriteOptions::try_new(8, false, crate::MetadataVersion::V5)
3170                .unwrap()
3171                .try_with_compression(Some(crate::CompressionType::ZSTD))
3172                .unwrap()
3173                .try_with_compression_level(Some(1))
3174                .unwrap();
3175
3176            let mut writer =
3177                FileWriter::try_new_with_options(&mut file, &schema, write_option).unwrap();
3178            writer.write(&record_batch).unwrap();
3179            writer.finish().unwrap();
3180        }
3181        file.rewind().unwrap();
3182        {
3183            // read file
3184            let reader = FileReader::try_new(file, None).unwrap();
3185            for read_batch in reader {
3186                read_batch
3187                    .unwrap()
3188                    .columns()
3189                    .iter()
3190                    .zip(record_batch.columns())
3191                    .for_each(|(a, b)| {
3192                        assert_eq!(a.data_type(), b.data_type());
3193                        assert_eq!(a.len(), b.len());
3194                        assert_eq!(a.null_count(), b.null_count());
3195                    });
3196            }
3197        }
3198    }
3199
3200    #[test]
3201    fn test_write_file() {
3202        let schema = Schema::new(vec![Field::new("field1", DataType::UInt32, true)]);
3203        let values: Vec<Option<u32>> = vec![
3204            Some(999),
3205            None,
3206            Some(235),
3207            Some(123),
3208            None,
3209            None,
3210            None,
3211            None,
3212            None,
3213        ];
3214        let array1 = UInt32Array::from(values);
3215        let batch =
3216            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(array1) as ArrayRef])
3217                .unwrap();
3218        let mut file = tempfile::tempfile().unwrap();
3219        {
3220            let mut writer = FileWriter::try_new(&mut file, &schema).unwrap();
3221
3222            writer.write(&batch).unwrap();
3223            writer.finish().unwrap();
3224        }
3225        file.rewind().unwrap();
3226
3227        {
3228            let mut reader = FileReader::try_new(file, None).unwrap();
3229            while let Some(Ok(read_batch)) = reader.next() {
3230                read_batch
3231                    .columns()
3232                    .iter()
3233                    .zip(batch.columns())
3234                    .for_each(|(a, b)| {
3235                        assert_eq!(a.data_type(), b.data_type());
3236                        assert_eq!(a.len(), b.len());
3237                        assert_eq!(a.null_count(), b.null_count());
3238                    });
3239            }
3240        }
3241    }
3242
3243    #[test]
3244    fn test_empty_utf8_ipc_writes_nonempty_offsets_buffer() {
3245        let name = StringArray::from(Vec::<String>::new());
3246        let [offsets, values] = get_byte_array_buffers::<i32>(&name.to_data());
3247
3248        assert_eq!(name.len(), 0);
3249        assert_eq!(
3250            offsets.len(),
3251            std::mem::size_of::<i32>(),
3252            "offsets buffer should contain one zero i32 offset"
3253        );
3254        assert_eq!(values.len(), 0, "values buffer should remain empty");
3255    }
3256
3257    #[test]
3258    fn test_empty_large_utf8_ipc_writes_nonempty_offsets_buffer() {
3259        let name = LargeStringArray::from(Vec::<String>::new());
3260        let [offsets, values] = get_byte_array_buffers::<i64>(&name.to_data());
3261
3262        assert_eq!(name.len(), 0);
3263        assert_eq!(
3264            offsets.len(),
3265            std::mem::size_of::<i64>(),
3266            "offsets buffer should contain one zero i64 offset"
3267        );
3268        assert_eq!(values.len(), 0, "values buffer should remain empty");
3269    }
3270
3271    #[test]
3272    fn test_empty_list_ipc_writes_nonempty_offsets_buffer() {
3273        let list = GenericListBuilder::<i32, _>::new(UInt32Builder::new()).finish();
3274        let (offsets, child_data) = get_list_array_buffers::<i32>(&list.to_data());
3275
3276        assert_eq!(list.len(), 0);
3277        assert_eq!(
3278            offsets.len(),
3279            std::mem::size_of::<i32>(),
3280            "offsets buffer should contain one zero i32 offset"
3281        );
3282        assert_eq!(child_data.len(), 0, "child data should remain empty");
3283    }
3284
3285    #[test]
3286    fn test_empty_large_list_ipc_writes_nonempty_offsets_buffer() {
3287        let list = GenericListBuilder::<i64, _>::new(UInt32Builder::new()).finish();
3288        let (offsets, child_data) = get_list_array_buffers::<i64>(&list.to_data());
3289
3290        assert_eq!(list.len(), 0);
3291        assert_eq!(
3292            offsets.len(),
3293            std::mem::size_of::<i64>(),
3294            "offsets buffer should contain one zero i64 offset"
3295        );
3296        assert_eq!(child_data.len(), 0, "child data should remain empty");
3297    }
3298
3299    fn write_null_file(options: IpcWriteOptions) {
3300        let schema = Schema::new(vec![
3301            Field::new("nulls", DataType::Null, true),
3302            Field::new("int32s", DataType::Int32, false),
3303            Field::new("nulls2", DataType::Null, true),
3304            Field::new("f64s", DataType::Float64, false),
3305        ]);
3306        let array1 = NullArray::new(32);
3307        let array2 = Int32Array::from(vec![1; 32]);
3308        let array3 = NullArray::new(32);
3309        let array4 = Float64Array::from(vec![f64::NAN; 32]);
3310        let batch = RecordBatch::try_new(
3311            Arc::new(schema.clone()),
3312            vec![
3313                Arc::new(array1) as ArrayRef,
3314                Arc::new(array2) as ArrayRef,
3315                Arc::new(array3) as ArrayRef,
3316                Arc::new(array4) as ArrayRef,
3317            ],
3318        )
3319        .unwrap();
3320        let mut file = tempfile::tempfile().unwrap();
3321        {
3322            let mut writer = FileWriter::try_new_with_options(&mut file, &schema, options).unwrap();
3323
3324            writer.write(&batch).unwrap();
3325            writer.finish().unwrap();
3326        }
3327
3328        file.rewind().unwrap();
3329
3330        {
3331            let reader = FileReader::try_new(file, None).unwrap();
3332            reader.for_each(|maybe_batch| {
3333                maybe_batch
3334                    .unwrap()
3335                    .columns()
3336                    .iter()
3337                    .zip(batch.columns())
3338                    .for_each(|(a, b)| {
3339                        assert_eq!(a.data_type(), b.data_type());
3340                        assert_eq!(a.len(), b.len());
3341                        assert_eq!(a.null_count(), b.null_count());
3342                    });
3343            });
3344        }
3345    }
3346    #[test]
3347    fn test_write_null_file_v4() {
3348        write_null_file(IpcWriteOptions::try_new(8, false, MetadataVersion::V4).unwrap());
3349        write_null_file(IpcWriteOptions::try_new(8, true, MetadataVersion::V4).unwrap());
3350        write_null_file(IpcWriteOptions::try_new(64, false, MetadataVersion::V4).unwrap());
3351        write_null_file(IpcWriteOptions::try_new(64, true, MetadataVersion::V4).unwrap());
3352    }
3353
3354    #[test]
3355    fn test_write_null_file_v5() {
3356        write_null_file(IpcWriteOptions::try_new(8, false, MetadataVersion::V5).unwrap());
3357        write_null_file(IpcWriteOptions::try_new(64, false, MetadataVersion::V5).unwrap());
3358    }
3359
3360    #[test]
3361    fn track_union_nested_dict() {
3362        let inner: DictionaryArray<Int32Type> = vec!["a", "b", "a"].into_iter().collect();
3363
3364        let array = Arc::new(inner) as ArrayRef;
3365
3366        // Dict field with id 2
3367        #[expect(deprecated)]
3368        let dctfield = Field::new_dict("dict", array.data_type().clone(), false, 0, false);
3369        let union_fields = std::iter::once((0, Arc::new(dctfield))).collect();
3370
3371        let types = [0, 0, 0].into_iter().collect::<ScalarBuffer<i8>>();
3372        let offsets = [0, 1, 2].into_iter().collect::<ScalarBuffer<i32>>();
3373
3374        let union = UnionArray::try_new(union_fields, types, Some(offsets), vec![array]).unwrap();
3375
3376        let schema = Arc::new(Schema::new(vec![Field::new(
3377            "union",
3378            union.data_type().clone(),
3379            false,
3380        )]));
3381
3382        let r#gen = IpcDataGenerator::default();
3383        let mut dict_tracker = DictionaryTracker::new(false);
3384        r#gen.schema_to_bytes_with_dictionary_tracker(
3385            &schema,
3386            &mut dict_tracker,
3387            &IpcWriteOptions::default(),
3388        );
3389
3390        let batch = RecordBatch::try_new(schema, vec![Arc::new(union)]).unwrap();
3391
3392        r#gen
3393            .encode(
3394                &batch,
3395                &mut dict_tracker,
3396                &Default::default(),
3397                &mut Default::default(),
3398            )
3399            .unwrap();
3400
3401        // The encoder will assign dict IDs itself to ensure uniqueness and ignore the dict ID in the schema
3402        // so we expect the dict will be keyed to 0
3403        assert!(dict_tracker.written.contains_key(&0));
3404    }
3405
3406    #[test]
3407    fn track_struct_nested_dict() {
3408        let inner: DictionaryArray<Int32Type> = vec!["a", "b", "a"].into_iter().collect();
3409
3410        let array = Arc::new(inner) as ArrayRef;
3411
3412        // Dict field with id 2
3413        #[expect(deprecated)]
3414        let dctfield = Arc::new(Field::new_dict(
3415            "dict",
3416            array.data_type().clone(),
3417            false,
3418            2,
3419            false,
3420        ));
3421
3422        let s = StructArray::from(vec![(dctfield, array)]);
3423        let struct_array = Arc::new(s) as ArrayRef;
3424
3425        let schema = Arc::new(Schema::new(vec![Field::new(
3426            "struct",
3427            struct_array.data_type().clone(),
3428            false,
3429        )]));
3430
3431        let r#gen = IpcDataGenerator::default();
3432        let mut dict_tracker = DictionaryTracker::new(false);
3433        r#gen.schema_to_bytes_with_dictionary_tracker(
3434            &schema,
3435            &mut dict_tracker,
3436            &IpcWriteOptions::default(),
3437        );
3438
3439        let batch = RecordBatch::try_new(schema, vec![struct_array]).unwrap();
3440
3441        r#gen
3442            .encode(
3443                &batch,
3444                &mut dict_tracker,
3445                &Default::default(),
3446                &mut Default::default(),
3447            )
3448            .unwrap();
3449
3450        assert!(dict_tracker.written.contains_key(&0));
3451    }
3452
3453    fn write_union_file(options: IpcWriteOptions) {
3454        let schema = Schema::new(vec![Field::new_union(
3455            "union",
3456            vec![0, 1],
3457            vec![
3458                Field::new("a", DataType::Int32, false),
3459                Field::new("c", DataType::Float64, false),
3460            ],
3461            UnionMode::Sparse,
3462        )]);
3463        let mut builder = UnionBuilder::with_capacity_sparse(5);
3464        builder.append::<Int32Type>("a", 1).unwrap();
3465        builder.append_null::<Int32Type>("a").unwrap();
3466        builder.append::<Float64Type>("c", 3.0).unwrap();
3467        builder.append_null::<Float64Type>("c").unwrap();
3468        builder.append::<Int32Type>("a", 4).unwrap();
3469        let union = builder.build().unwrap();
3470
3471        let batch =
3472            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(union) as ArrayRef])
3473                .unwrap();
3474
3475        let mut file = tempfile::tempfile().unwrap();
3476        {
3477            let mut writer = FileWriter::try_new_with_options(&mut file, &schema, options).unwrap();
3478
3479            writer.write(&batch).unwrap();
3480            writer.finish().unwrap();
3481        }
3482        file.rewind().unwrap();
3483
3484        {
3485            let reader = FileReader::try_new(file, None).unwrap();
3486            reader.for_each(|maybe_batch| {
3487                maybe_batch
3488                    .unwrap()
3489                    .columns()
3490                    .iter()
3491                    .zip(batch.columns())
3492                    .for_each(|(a, b)| {
3493                        assert_eq!(a.data_type(), b.data_type());
3494                        assert_eq!(a.len(), b.len());
3495                        assert_eq!(a.null_count(), b.null_count());
3496                    });
3497            });
3498        }
3499    }
3500
3501    #[test]
3502    fn test_write_union_file_v4_v5() {
3503        write_union_file(IpcWriteOptions::try_new(8, false, MetadataVersion::V4).unwrap());
3504        write_union_file(IpcWriteOptions::try_new(8, false, MetadataVersion::V5).unwrap());
3505    }
3506
3507    #[test]
3508    fn test_write_view_types() {
3509        const LONG_TEST_STRING: &str =
3510            "This is a long string to make sure binary view array handles it";
3511        let schema = Schema::new(vec![
3512            Field::new("field1", DataType::BinaryView, true),
3513            Field::new("field2", DataType::Utf8View, true),
3514        ]);
3515        let values: Vec<Option<&[u8]>> = vec![
3516            Some(b"foo"),
3517            Some(b"bar"),
3518            Some(LONG_TEST_STRING.as_bytes()),
3519        ];
3520        let binary_array = BinaryViewArray::from_iter(values);
3521        let utf8_array =
3522            StringViewArray::from_iter(vec![Some("foo"), Some("bar"), Some(LONG_TEST_STRING)]);
3523        let record_batch = RecordBatch::try_new(
3524            Arc::new(schema.clone()),
3525            vec![Arc::new(binary_array), Arc::new(utf8_array)],
3526        )
3527        .unwrap();
3528
3529        let mut file = tempfile::tempfile().unwrap();
3530        {
3531            let mut writer = FileWriter::try_new(&mut file, &schema).unwrap();
3532            writer.write(&record_batch).unwrap();
3533            writer.finish().unwrap();
3534        }
3535        file.rewind().unwrap();
3536        {
3537            let mut reader = FileReader::try_new(&file, None).unwrap();
3538            let read_batch = reader.next().unwrap().unwrap();
3539            read_batch
3540                .columns()
3541                .iter()
3542                .zip(record_batch.columns())
3543                .for_each(|(a, b)| {
3544                    assert_eq!(a, b);
3545                });
3546        }
3547        file.rewind().unwrap();
3548        {
3549            let mut reader = FileReader::try_new(&file, Some(vec![0])).unwrap();
3550            let read_batch = reader.next().unwrap().unwrap();
3551            assert_eq!(read_batch.num_columns(), 1);
3552            let read_array = read_batch.column(0);
3553            let write_array = record_batch.column(0);
3554            assert_eq!(read_array, write_array);
3555        }
3556    }
3557
3558    #[test]
3559    #[cfg_attr(miri, ignore)] // Takes too long
3560    fn truncate_ipc_record_batch() {
3561        fn create_batch(rows: usize) -> RecordBatch {
3562            let schema = Schema::new(vec![
3563                Field::new("a", DataType::Int32, false),
3564                Field::new("b", DataType::Utf8, false),
3565            ]);
3566
3567            let a = Int32Array::from_iter_values(0..rows as i32);
3568            let b = StringArray::from_iter_values((0..rows).map(|i| i.to_string()));
3569
3570            RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a), Arc::new(b)]).unwrap()
3571        }
3572
3573        let big_record_batch = create_batch(65536);
3574
3575        let length = 5;
3576        let small_record_batch = create_batch(length);
3577
3578        let offset = 2;
3579        let record_batch_slice = big_record_batch.slice(offset, length);
3580        assert!(
3581            serialize_stream(&big_record_batch).len() > serialize_stream(&small_record_batch).len()
3582        );
3583        assert_eq!(
3584            serialize_stream(&small_record_batch).len(),
3585            serialize_stream(&record_batch_slice).len()
3586        );
3587
3588        assert_eq!(
3589            deserialize_stream(serialize_stream(&record_batch_slice)),
3590            record_batch_slice
3591        );
3592    }
3593
3594    #[test]
3595    fn truncate_ipc_record_batch_with_nulls() {
3596        fn create_batch() -> RecordBatch {
3597            let schema = Schema::new(vec![
3598                Field::new("a", DataType::Int32, true),
3599                Field::new("b", DataType::Utf8, true),
3600            ]);
3601
3602            let a = Int32Array::from(vec![Some(1), None, Some(1), None, Some(1)]);
3603            let b = StringArray::from(vec![None, Some("a"), Some("a"), None, Some("a")]);
3604
3605            RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a), Arc::new(b)]).unwrap()
3606        }
3607
3608        let record_batch = create_batch();
3609        let record_batch_slice = record_batch.slice(1, 2);
3610        let deserialized_batch = deserialize_stream(serialize_stream(&record_batch_slice));
3611
3612        assert!(
3613            serialize_stream(&record_batch).len() > serialize_stream(&record_batch_slice).len()
3614        );
3615
3616        assert!(deserialized_batch.column(0).is_null(0));
3617        assert!(deserialized_batch.column(0).is_valid(1));
3618        assert!(deserialized_batch.column(1).is_valid(0));
3619        assert!(deserialized_batch.column(1).is_valid(1));
3620
3621        assert_eq!(record_batch_slice, deserialized_batch);
3622    }
3623
3624    #[test]
3625    fn test_write_empty_validity_buffer_without_nulls() {
3626        let batch = RecordBatch::try_from_iter([
3627            ("a", Arc::new(Int32Array::from(vec![1, 2])) as _),
3628            // Keeps a null buffer, but the slice has no nulls
3629            (
3630                "b",
3631                Arc::new(Int32Array::from(vec![None, Some(2), Some(3)]).slice(1, 2)) as _,
3632            ),
3633            ("c", Arc::new(Int32Array::from(vec![Some(1), None])) as _),
3634        ])
3635        .unwrap();
3636        assert_eq!(batch.column(1).nulls().map(|n| n.null_count()), Some(0));
3637
3638        let (_, encoded) = IpcDataGenerator::default()
3639            .encode(
3640                &batch,
3641                &mut DictionaryTracker::new(false),
3642                &Default::default(),
3643                &mut Default::default(),
3644            )
3645            .unwrap();
3646        let message = crate::root_as_message(&encoded.ipc_message).unwrap();
3647        let buffers = message.header_as_record_batch().unwrap().buffers().unwrap();
3648        // Each column writes a validity buffer, then a values buffer
3649        let validity_lengths: Vec<_> = buffers.iter().step_by(2).map(|b| b.length()).collect();
3650        assert_eq!(validity_lengths, [0, 0, 1]);
3651
3652        assert_eq!(deserialize_stream(serialize_stream(&batch)), batch);
3653    }
3654
3655    #[test]
3656    fn truncate_ipc_dictionary_array() {
3657        fn create_batch() -> RecordBatch {
3658            let values: StringArray = [Some("foo"), Some("bar"), Some("baz")]
3659                .into_iter()
3660                .collect();
3661            let keys: Int32Array = [Some(0), Some(2), None, Some(1)].into_iter().collect();
3662
3663            let array = DictionaryArray::new(keys, Arc::new(values));
3664
3665            let schema = Schema::new(vec![Field::new("dict", array.data_type().clone(), true)]);
3666
3667            RecordBatch::try_new(Arc::new(schema), vec![Arc::new(array)]).unwrap()
3668        }
3669
3670        let record_batch = create_batch();
3671        let record_batch_slice = record_batch.slice(1, 2);
3672        let deserialized_batch = deserialize_stream(serialize_stream(&record_batch_slice));
3673
3674        assert!(
3675            serialize_stream(&record_batch).len() > serialize_stream(&record_batch_slice).len()
3676        );
3677
3678        assert!(deserialized_batch.column(0).is_valid(0));
3679        assert!(deserialized_batch.column(0).is_null(1));
3680
3681        assert_eq!(record_batch_slice, deserialized_batch);
3682    }
3683
3684    #[test]
3685    fn truncate_ipc_struct_array() {
3686        fn create_batch() -> RecordBatch {
3687            let strings: StringArray = [Some("foo"), None, Some("bar"), Some("baz")]
3688                .into_iter()
3689                .collect();
3690            let ints: Int32Array = [Some(0), Some(2), None, Some(1)].into_iter().collect();
3691
3692            let struct_array = StructArray::from(vec![
3693                (
3694                    Arc::new(Field::new("s", DataType::Utf8, true)),
3695                    Arc::new(strings) as ArrayRef,
3696                ),
3697                (
3698                    Arc::new(Field::new("c", DataType::Int32, true)),
3699                    Arc::new(ints) as ArrayRef,
3700                ),
3701            ]);
3702
3703            let schema = Schema::new(vec![Field::new(
3704                "struct_array",
3705                struct_array.data_type().clone(),
3706                true,
3707            )]);
3708
3709            RecordBatch::try_new(Arc::new(schema), vec![Arc::new(struct_array)]).unwrap()
3710        }
3711
3712        let record_batch = create_batch();
3713        let record_batch_slice = record_batch.slice(1, 2);
3714        let deserialized_batch = deserialize_stream(serialize_stream(&record_batch_slice));
3715
3716        assert!(
3717            serialize_stream(&record_batch).len() > serialize_stream(&record_batch_slice).len()
3718        );
3719
3720        let structs = deserialized_batch
3721            .column(0)
3722            .as_any()
3723            .downcast_ref::<StructArray>()
3724            .unwrap();
3725
3726        assert!(structs.column(0).is_null(0));
3727        assert!(structs.column(0).is_valid(1));
3728        assert!(structs.column(1).is_valid(0));
3729        assert!(structs.column(1).is_null(1));
3730        assert_eq!(record_batch_slice, deserialized_batch);
3731    }
3732
3733    #[test]
3734    fn truncate_ipc_string_array_with_all_empty_string() {
3735        fn create_batch() -> RecordBatch {
3736            let schema = Schema::new(vec![Field::new("a", DataType::Utf8, true)]);
3737            let a = StringArray::from(vec![Some(""), Some(""), Some(""), Some(""), Some("")]);
3738            RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)]).unwrap()
3739        }
3740
3741        let record_batch = create_batch();
3742        let record_batch_slice = record_batch.slice(0, 1);
3743        let deserialized_batch = deserialize_stream(serialize_stream(&record_batch_slice));
3744
3745        assert!(
3746            serialize_stream(&record_batch).len() > serialize_stream(&record_batch_slice).len()
3747        );
3748        assert_eq!(record_batch_slice, deserialized_batch);
3749    }
3750
3751    #[test]
3752    fn test_stream_writer_writes_array_slice() {
3753        let array = UInt32Array::from(vec![Some(1), Some(2), Some(3)]);
3754        assert_eq!(
3755            vec![Some(1), Some(2), Some(3)],
3756            array.iter().collect::<Vec<_>>()
3757        );
3758
3759        let sliced = array.slice(1, 2);
3760        assert_eq!(vec![Some(2), Some(3)], sliced.iter().collect::<Vec<_>>());
3761
3762        let batch = RecordBatch::try_new(
3763            Arc::new(Schema::new(vec![Field::new("a", DataType::UInt32, true)])),
3764            vec![Arc::new(sliced)],
3765        )
3766        .expect("new batch");
3767
3768        let mut writer = StreamWriter::try_new(vec![], batch.schema_ref()).expect("new writer");
3769        writer.write(&batch).expect("write");
3770        let outbuf = writer.into_inner().expect("inner");
3771
3772        let mut reader = StreamReader::try_new(&outbuf[..], None).expect("new reader");
3773        let read_batch = reader.next().unwrap().expect("read batch");
3774
3775        let read_array: &UInt32Array = read_batch.column(0).as_primitive();
3776        assert_eq!(
3777            vec![Some(2), Some(3)],
3778            read_array.iter().collect::<Vec<_>>()
3779        );
3780    }
3781
3782    #[test]
3783    fn test_large_slice_uint32() {
3784        ensure_roundtrip(Arc::new(UInt32Array::from_iter(
3785            (0..8000).map(|i| if i % 2 == 0 { Some(i) } else { None }),
3786        )));
3787    }
3788
3789    #[test]
3790    #[cfg_attr(miri, ignore)] // Takes too long
3791    fn test_large_slice_string() {
3792        let strings: Vec<_> = (0..8000)
3793            .map(|i| {
3794                if i % 2 == 0 {
3795                    Some(format!("value{i}"))
3796                } else {
3797                    None
3798                }
3799            })
3800            .collect();
3801
3802        ensure_roundtrip(Arc::new(StringArray::from(strings)));
3803    }
3804
3805    #[test]
3806    #[cfg_attr(miri, ignore)] // Takes too long
3807    fn test_large_slice_string_list() {
3808        let mut ls = ListBuilder::new(StringBuilder::new());
3809
3810        let mut s = String::new();
3811        for row_number in 0..8000 {
3812            if row_number % 2 == 0 {
3813                for list_element in 0..1000 {
3814                    s.clear();
3815                    use std::fmt::Write;
3816                    write!(&mut s, "value{row_number}-{list_element}").unwrap();
3817                    ls.values().append_value(&s);
3818                }
3819                ls.append(true)
3820            } else {
3821                ls.append(false); // null
3822            }
3823        }
3824
3825        ensure_roundtrip(Arc::new(ls.finish()));
3826    }
3827
3828    #[test]
3829    #[cfg_attr(miri, ignore)] // Takes too long
3830    fn test_large_slice_string_list_of_lists() {
3831        // The reason for the special test is to verify reencode_offsets which looks both at
3832        // the starting offset and the data offset.  So need a dataset where the starting_offset
3833        // is zero but the data offset is not.
3834        let mut ls = ListBuilder::new(ListBuilder::new(StringBuilder::new()));
3835
3836        for _ in 0..4000 {
3837            ls.values().append(true);
3838            ls.append(true)
3839        }
3840
3841        let mut s = String::new();
3842        for row_number in 0..4000 {
3843            if row_number % 2 == 0 {
3844                for list_element in 0..1000 {
3845                    s.clear();
3846                    use std::fmt::Write;
3847                    write!(&mut s, "value{row_number}-{list_element}").unwrap();
3848                    ls.values().values().append_value(&s);
3849                }
3850                ls.values().append(true);
3851                ls.append(true)
3852            } else {
3853                ls.append(false); // null
3854            }
3855        }
3856
3857        ensure_roundtrip(Arc::new(ls.finish()));
3858    }
3859
3860    /// Read/write a record batch to a File and Stream and ensure it is the same at the output
3861    fn ensure_roundtrip(array: ArrayRef) {
3862        let num_rows = array.len();
3863        let orig_batch = RecordBatch::try_from_iter(vec![("a", array)]).unwrap();
3864        // take off the first element
3865        let sliced_batch = orig_batch.slice(1, num_rows - 1);
3866
3867        let schema = orig_batch.schema();
3868        let stream_data = {
3869            let mut writer = StreamWriter::try_new(vec![], &schema).unwrap();
3870            writer.write(&sliced_batch).unwrap();
3871            writer.into_inner().unwrap()
3872        };
3873        let read_batch = {
3874            let projection = None;
3875            let mut reader = StreamReader::try_new(Cursor::new(stream_data), projection).unwrap();
3876            reader
3877                .next()
3878                .expect("expect no errors reading batch")
3879                .expect("expect batch")
3880        };
3881        assert_eq!(sliced_batch, read_batch);
3882
3883        let file_data = {
3884            let mut writer = FileWriter::try_new_buffered(vec![], &schema).unwrap();
3885            writer.write(&sliced_batch).unwrap();
3886            writer.into_inner().unwrap().into_inner().unwrap()
3887        };
3888        let read_batch = {
3889            let projection = None;
3890            let mut reader = FileReader::try_new(Cursor::new(file_data), projection).unwrap();
3891            reader
3892                .next()
3893                .expect("expect no errors reading batch")
3894                .expect("expect batch")
3895        };
3896        assert_eq!(sliced_batch, read_batch);
3897
3898        // TODO test file writer/reader
3899    }
3900
3901    #[test]
3902    fn encode_bools_slice() {
3903        // Test case for https://github.com/apache/arrow-rs/issues/3496
3904        assert_bool_roundtrip([true, false], 1, 1);
3905
3906        // slice somewhere in the middle
3907        assert_bool_roundtrip(
3908            [
3909                true, false, true, true, false, false, true, true, true, false, false, false, true,
3910                true, true, true, false, false, false, false, true, true, true, true, true, false,
3911                false, false, false, false,
3912            ],
3913            13,
3914            17,
3915        );
3916
3917        // start at byte boundary, end in the middle
3918        assert_bool_roundtrip(
3919            [
3920                true, false, true, true, false, false, true, true, true, false, false, false,
3921            ],
3922            8,
3923            2,
3924        );
3925
3926        // start and stop and byte boundary
3927        assert_bool_roundtrip(
3928            [
3929                true, false, true, true, false, false, true, true, true, false, false, false, true,
3930                true, true, true, true, false, false, false, false, false,
3931            ],
3932            8,
3933            8,
3934        );
3935    }
3936
3937    fn assert_bool_roundtrip<const N: usize>(bools: [bool; N], offset: usize, length: usize) {
3938        let val_bool_field = Field::new("val", DataType::Boolean, false);
3939
3940        let schema = Arc::new(Schema::new(vec![val_bool_field]));
3941
3942        let bools = BooleanArray::from(bools.to_vec());
3943
3944        let batch = RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(bools)]).unwrap();
3945        let batch = batch.slice(offset, length);
3946
3947        let data = serialize_stream(&batch);
3948        let batch2 = deserialize_stream(data);
3949        assert_eq!(batch, batch2);
3950    }
3951
3952    fn assert_empty_run_array_roundtrip<R: RunEndIndexType>() {
3953        let run_ends = PrimitiveArray::<R>::from_iter_values(
3954            [2, 5]
3955                .into_iter()
3956                .map(|v| R::Native::from_usize(v).unwrap()),
3957        );
3958        let values = Int32Array::from(vec![10, 20]);
3959        let array = RunArray::<R>::try_new(&run_ends, &values).unwrap();
3960        let empty = RunArray::<R>::from(ArrayData::new_empty(array.data_type()));
3961
3962        for source in [&array, &empty] {
3963            for offset in 0..=source.len() {
3964                let sliced = source.slice(offset, 0);
3965                let batch = RecordBatch::try_from_iter(vec![("run", Arc::new(sliced) as ArrayRef)])
3966                    .unwrap();
3967                for decoded in [
3968                    deserialize_stream(serialize_stream(&batch)),
3969                    deserialize_file(serialize_file(&batch)),
3970                ] {
3971                    assert_eq!(decoded, batch);
3972                    let data = decoded.column(0).to_data();
3973                    data.validate_full().unwrap();
3974                    assert_eq!(data.offset(), 0);
3975                    assert!(data.child_data().iter().all(ArrayData::is_empty));
3976                }
3977
3978                let normalized = into_zero_offset_run_array(source.slice(offset, 0)).unwrap();
3979                normalized.to_data().validate_full().unwrap();
3980                assert_eq!(normalized.offset(), 0);
3981                assert!(normalized.run_ends().values().is_empty());
3982                assert!(normalized.values().is_empty());
3983            }
3984        }
3985    }
3986
3987    #[test]
3988    fn test_empty_run_array_roundtrip_i16() {
3989        assert_empty_run_array_roundtrip::<Int16Type>();
3990    }
3991
3992    #[test]
3993    fn test_empty_run_array_roundtrip_i32() {
3994        assert_empty_run_array_roundtrip::<Int32Type>();
3995    }
3996
3997    #[test]
3998    fn test_empty_run_array_roundtrip_i64() {
3999        assert_empty_run_array_roundtrip::<Int64Type>();
4000    }
4001
4002    #[test]
4003    fn test_run_array_unslice() {
4004        let total_len = 80;
4005        let vals: Vec<Option<i32>> = vec![Some(1), None, Some(2), Some(3), Some(4), None, Some(5)];
4006        let repeats: Vec<usize> = vec![3, 4, 1, 2];
4007        let mut input_array: Vec<Option<i32>> = Vec::with_capacity(total_len);
4008        for ix in 0_usize..32 {
4009            let repeat: usize = repeats[ix % repeats.len()];
4010            let val: Option<i32> = vals[ix % vals.len()];
4011            input_array.resize(input_array.len() + repeat, val);
4012        }
4013
4014        // Encode the input_array to run array
4015        let mut builder =
4016            PrimitiveRunBuilder::<Int16Type, Int32Type>::with_capacity(input_array.len());
4017        builder.extend(input_array.iter().copied());
4018        let run_array = builder.finish();
4019
4020        // test for all slice lengths.
4021        for slice_len in 1..=total_len {
4022            // test for offset = 0, slice length = slice_len
4023            let sliced_run_array: RunArray<Int16Type> =
4024                run_array.slice(0, slice_len).into_data().into();
4025
4026            // Create unsliced run array.
4027            let unsliced_run_array = into_zero_offset_run_array(sliced_run_array).unwrap();
4028            let typed = unsliced_run_array
4029                .downcast::<PrimitiveArray<Int32Type>>()
4030                .unwrap();
4031            let expected: Vec<Option<i32>> = input_array.iter().take(slice_len).copied().collect();
4032            let actual: Vec<Option<i32>> = typed.into_iter().collect();
4033            assert_eq!(expected, actual);
4034
4035            // test for offset = total_len - slice_len, length = slice_len
4036            let sliced_run_array: RunArray<Int16Type> = run_array
4037                .slice(total_len - slice_len, slice_len)
4038                .into_data()
4039                .into();
4040
4041            // Create unsliced run array.
4042            let unsliced_run_array = into_zero_offset_run_array(sliced_run_array).unwrap();
4043            let typed = unsliced_run_array
4044                .downcast::<PrimitiveArray<Int32Type>>()
4045                .unwrap();
4046            let expected: Vec<Option<i32>> = input_array
4047                .iter()
4048                .skip(total_len - slice_len)
4049                .copied()
4050                .collect();
4051            let actual: Vec<Option<i32>> = typed.into_iter().collect();
4052            assert_eq!(expected, actual);
4053        }
4054    }
4055
4056    fn generate_list_data<O: OffsetSizeTrait>() -> GenericListArray<O> {
4057        let mut ls = GenericListBuilder::<O, _>::new(UInt32Builder::new());
4058
4059        for i in 0..100_000 {
4060            for value in [i, i, i] {
4061                ls.values().append_value(value);
4062            }
4063            ls.append(true)
4064        }
4065
4066        ls.finish()
4067    }
4068
4069    fn generate_utf8view_list_data<O: OffsetSizeTrait>() -> GenericListArray<O> {
4070        let mut ls = GenericListBuilder::<O, _>::new(StringViewBuilder::new());
4071
4072        for i in 0..100_000 {
4073            for value in [
4074                format!("value{i}"),
4075                format!("value{i}"),
4076                format!("value{i}"),
4077            ] {
4078                ls.values().append_value(&value);
4079            }
4080            ls.append(true)
4081        }
4082
4083        ls.finish()
4084    }
4085
4086    fn generate_string_list_data<O: OffsetSizeTrait>() -> GenericListArray<O> {
4087        let mut ls = GenericListBuilder::<O, _>::new(StringBuilder::new());
4088
4089        for i in 0..100_000 {
4090            for value in [
4091                format!("value{i}"),
4092                format!("value{i}"),
4093                format!("value{i}"),
4094            ] {
4095                ls.values().append_value(&value);
4096            }
4097            ls.append(true)
4098        }
4099
4100        ls.finish()
4101    }
4102
4103    fn generate_nested_list_data<O: OffsetSizeTrait>() -> GenericListArray<O> {
4104        let mut ls =
4105            GenericListBuilder::<O, _>::new(GenericListBuilder::<O, _>::new(UInt32Builder::new()));
4106
4107        for _i in 0..10_000 {
4108            for j in 0..10 {
4109                for value in [j, j, j, j] {
4110                    ls.values().values().append_value(value);
4111                }
4112                ls.values().append(true)
4113            }
4114            ls.append(true);
4115        }
4116
4117        ls.finish()
4118    }
4119
4120    fn generate_nested_list_data_starting_at_zero<O: OffsetSizeTrait>() -> GenericListArray<O> {
4121        let mut ls =
4122            GenericListBuilder::<O, _>::new(GenericListBuilder::<O, _>::new(UInt32Builder::new()));
4123
4124        for _i in 0..999 {
4125            ls.values().append(true);
4126            ls.append(true);
4127        }
4128
4129        for j in 0..10 {
4130            for value in [j, j, j, j] {
4131                ls.values().values().append_value(value);
4132            }
4133            ls.values().append(true)
4134        }
4135        ls.append(true);
4136
4137        for i in 0..9_000 {
4138            for j in 0..10 {
4139                for value in [i + j, i + j, i + j, i + j] {
4140                    ls.values().values().append_value(value);
4141                }
4142                ls.values().append(true)
4143            }
4144            ls.append(true);
4145        }
4146
4147        ls.finish()
4148    }
4149
4150    fn generate_map_array_data() -> MapArray {
4151        let keys_builder = UInt32Builder::new();
4152        let values_builder = UInt32Builder::new();
4153
4154        let mut builder = MapBuilder::new(None, keys_builder, values_builder);
4155
4156        for i in 0..100_000 {
4157            for _j in 0..3 {
4158                builder.keys().append_value(i);
4159                builder.values().append_value(i * 2);
4160            }
4161            builder.append(true).unwrap();
4162        }
4163
4164        builder.finish()
4165    }
4166
4167    #[test]
4168    #[cfg_attr(miri, ignore)] // Takes too long
4169    fn reencode_offsets_when_first_offset_is_not_zero() {
4170        let original_list = generate_list_data::<i32>();
4171        let original_data = original_list.into_data();
4172        let slice_data = original_data.slice(75, 7);
4173        let (new_offsets, original_start, length) =
4174            reencode_offsets::<i32>(&slice_data.buffers()[0], &slice_data);
4175        assert_eq!(
4176            vec![0, 3, 6, 9, 12, 15, 18, 21],
4177            new_offsets.typed_data::<i32>()
4178        );
4179        assert_eq!(225, original_start);
4180        assert_eq!(21, length);
4181    }
4182
4183    #[test]
4184    fn reencode_offsets_when_first_offset_is_zero() {
4185        let mut ls = GenericListBuilder::<i32, _>::new(UInt32Builder::new());
4186        // ls = [[], [35, 42]
4187        ls.append(true);
4188        ls.values().append_value(35);
4189        ls.values().append_value(42);
4190        ls.append(true);
4191        let original_list = ls.finish();
4192        let original_data = original_list.into_data();
4193
4194        let slice_data = original_data.slice(1, 1);
4195        let (new_offsets, original_start, length) =
4196            reencode_offsets::<i32>(&slice_data.buffers()[0], &slice_data);
4197        assert_eq!(vec![0, 2], new_offsets.typed_data::<i32>());
4198        assert_eq!(0, original_start);
4199        assert_eq!(2, length);
4200    }
4201
4202    /// Ensure when serde full & sliced versions they are equal to original input.
4203    /// Also ensure serialized sliced version is significantly smaller than serialized full.
4204    fn roundtrip_ensure_sliced_smaller(in_batch: RecordBatch, expected_size_factor: usize) {
4205        // test both full and sliced versions
4206        let in_sliced = in_batch.slice(999, 1);
4207
4208        let bytes_batch = serialize_file(&in_batch);
4209        let bytes_sliced = serialize_file(&in_sliced);
4210
4211        // serializing 1 row should be significantly smaller than serializing 100,000
4212        assert!(bytes_sliced.len() < (bytes_batch.len() / expected_size_factor));
4213
4214        // ensure both are still valid and equal to originals
4215        let out_batch = deserialize_file(bytes_batch);
4216        assert_eq!(in_batch, out_batch);
4217
4218        let out_sliced = deserialize_file(bytes_sliced);
4219        assert_eq!(in_sliced, out_sliced);
4220    }
4221
4222    #[test]
4223    #[cfg_attr(miri, ignore)] // Takes too long
4224    fn encode_lists() {
4225        let val_inner = Field::new_list_field(DataType::UInt32, true);
4226        let val_list_field = Field::new("val", DataType::List(Arc::new(val_inner)), false);
4227        let schema = Arc::new(Schema::new(vec![val_list_field]));
4228
4229        let values = Arc::new(generate_list_data::<i32>());
4230
4231        let in_batch = RecordBatch::try_new(schema, vec![values]).unwrap();
4232        roundtrip_ensure_sliced_smaller(in_batch, 1000);
4233    }
4234
4235    #[test]
4236    #[cfg_attr(miri, ignore)] // Takes too long
4237    fn encode_empty_list() {
4238        let val_inner = Field::new_list_field(DataType::UInt32, true);
4239        let val_list_field = Field::new("val", DataType::List(Arc::new(val_inner)), false);
4240        let schema = Arc::new(Schema::new(vec![val_list_field]));
4241
4242        let values = Arc::new(generate_list_data::<i32>());
4243
4244        let in_batch = RecordBatch::try_new(schema, vec![values])
4245            .unwrap()
4246            .slice(999, 0);
4247        let out_batch = deserialize_file(serialize_file(&in_batch));
4248        assert_eq!(in_batch, out_batch);
4249    }
4250
4251    #[test]
4252    #[cfg_attr(miri, ignore)] // Takes too long
4253    fn encode_large_lists() {
4254        let val_inner = Field::new_list_field(DataType::UInt32, true);
4255        let val_list_field = Field::new("val", DataType::LargeList(Arc::new(val_inner)), false);
4256        let schema = Arc::new(Schema::new(vec![val_list_field]));
4257
4258        let values = Arc::new(generate_list_data::<i64>());
4259
4260        // ensure when serde full & sliced versions they are equal to original input
4261        // also ensure serialized sliced version is significantly smaller than serialized full
4262        let in_batch = RecordBatch::try_new(schema, vec![values]).unwrap();
4263        roundtrip_ensure_sliced_smaller(in_batch, 1000);
4264    }
4265
4266    #[test]
4267    #[cfg_attr(miri, ignore)] // Takes too long
4268    fn encode_large_lists_non_zero_offset() {
4269        let val_inner = Field::new_list_field(DataType::UInt32, true);
4270        let val_list_field = Field::new("val", DataType::LargeList(Arc::new(val_inner)), false);
4271        let schema = Arc::new(Schema::new(vec![val_list_field]));
4272
4273        let values = Arc::new(generate_list_data::<i64>());
4274
4275        check_sliced_list_array(schema, values);
4276    }
4277
4278    #[test]
4279    #[cfg_attr(miri, ignore)] // Takes too long
4280    fn encode_large_lists_string_non_zero_offset() {
4281        let val_inner = Field::new_list_field(DataType::Utf8, true);
4282        let val_list_field = Field::new("val", DataType::LargeList(Arc::new(val_inner)), false);
4283        let schema = Arc::new(Schema::new(vec![val_list_field]));
4284
4285        let values = Arc::new(generate_string_list_data::<i64>());
4286
4287        check_sliced_list_array(schema, values);
4288    }
4289
4290    #[test]
4291    #[cfg_attr(miri, ignore)] // Takes too long
4292    fn encode_large_list_string_view_non_zero_offset() {
4293        let val_inner = Field::new_list_field(DataType::Utf8View, true);
4294        let val_list_field = Field::new("val", DataType::LargeList(Arc::new(val_inner)), false);
4295        let schema = Arc::new(Schema::new(vec![val_list_field]));
4296
4297        let values = Arc::new(generate_utf8view_list_data::<i64>());
4298
4299        check_sliced_list_array(schema, values);
4300    }
4301
4302    fn check_sliced_list_array(schema: Arc<Schema>, values: Arc<GenericListArray<i64>>) {
4303        for (offset, len) in [(999, 1), (0, 13), (47, 12), (values.len() - 13, 13)] {
4304            let in_batch = RecordBatch::try_new(schema.clone(), vec![values.clone()])
4305                .unwrap()
4306                .slice(offset, len);
4307            let out_batch = deserialize_file(serialize_file(&in_batch));
4308            assert_eq!(in_batch, out_batch);
4309        }
4310    }
4311
4312    #[test]
4313    #[cfg_attr(miri, ignore)] // Takes too long
4314    fn encode_nested_lists() {
4315        let inner_int = Arc::new(Field::new_list_field(DataType::UInt32, true));
4316        let inner_list_field = Arc::new(Field::new_list_field(DataType::List(inner_int), true));
4317        let list_field = Field::new("val", DataType::List(inner_list_field), true);
4318        let schema = Arc::new(Schema::new(vec![list_field]));
4319
4320        let values = Arc::new(generate_nested_list_data::<i32>());
4321
4322        let in_batch = RecordBatch::try_new(schema, vec![values]).unwrap();
4323        roundtrip_ensure_sliced_smaller(in_batch, 1000);
4324    }
4325
4326    #[test]
4327    #[cfg_attr(miri, ignore)] // Takes too long
4328    fn encode_nested_lists_starting_at_zero() {
4329        let inner_int = Arc::new(Field::new("item", DataType::UInt32, true));
4330        let inner_list_field = Arc::new(Field::new("item", DataType::List(inner_int), true));
4331        let list_field = Field::new("val", DataType::List(inner_list_field), true);
4332        let schema = Arc::new(Schema::new(vec![list_field]));
4333
4334        let values = Arc::new(generate_nested_list_data_starting_at_zero::<i32>());
4335
4336        let in_batch = RecordBatch::try_new(schema, vec![values]).unwrap();
4337        roundtrip_ensure_sliced_smaller(in_batch, 1);
4338    }
4339
4340    #[test]
4341    #[cfg_attr(miri, ignore)] // Takes too long
4342    fn encode_map_array() {
4343        let keys = Arc::new(Field::new(
4344            Field::MAP_KEY_FIELD_DEFAULT_NAME,
4345            DataType::UInt32,
4346            false,
4347        ));
4348        let values = Arc::new(Field::new(
4349            Field::MAP_VALUE_FIELD_DEFAULT_NAME,
4350            DataType::UInt32,
4351            true,
4352        ));
4353        let map_field = Field::new_map(
4354            "map",
4355            Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
4356            keys,
4357            values,
4358            false,
4359            true,
4360        );
4361        let schema = Arc::new(Schema::new(vec![map_field]));
4362
4363        let values = Arc::new(generate_map_array_data());
4364
4365        let in_batch = RecordBatch::try_new(schema, vec![values]).unwrap();
4366        roundtrip_ensure_sliced_smaller(in_batch, 1000);
4367    }
4368
4369    fn generate_list_view_data<O: OffsetSizeTrait>() -> GenericListViewArray<O> {
4370        let mut builder = GenericListViewBuilder::<O, _>::new(UInt32Builder::new());
4371
4372        for i in 0u32..100_000 {
4373            if i.is_multiple_of(10_000) {
4374                builder.append(false);
4375                continue;
4376            }
4377            for value in [i, i, i] {
4378                builder.values().append_value(value);
4379            }
4380            builder.append(true);
4381        }
4382
4383        builder.finish()
4384    }
4385
4386    #[test]
4387    #[cfg_attr(miri, ignore)] // Takes too long
4388    fn encode_list_view_arrays() {
4389        let val_inner = Field::new_list_field(DataType::UInt32, true);
4390        let val_field = Field::new("val", DataType::ListView(Arc::new(val_inner)), true);
4391        let schema = Arc::new(Schema::new(vec![val_field]));
4392
4393        let values = Arc::new(generate_list_view_data::<i32>());
4394
4395        let in_batch = RecordBatch::try_new(schema, vec![values]).unwrap();
4396        let out_batch = deserialize_file(serialize_file(&in_batch));
4397        assert_eq!(in_batch, out_batch);
4398    }
4399
4400    #[test]
4401    #[cfg_attr(miri, ignore)] // Takes too long
4402    fn encode_large_list_view_arrays() {
4403        let val_inner = Field::new_list_field(DataType::UInt32, true);
4404        let val_field = Field::new("val", DataType::LargeListView(Arc::new(val_inner)), true);
4405        let schema = Arc::new(Schema::new(vec![val_field]));
4406
4407        let values = Arc::new(generate_list_view_data::<i64>());
4408
4409        let in_batch = RecordBatch::try_new(schema, vec![values]).unwrap();
4410        let out_batch = deserialize_file(serialize_file(&in_batch));
4411        assert_eq!(in_batch, out_batch);
4412    }
4413
4414    #[test]
4415    #[cfg_attr(miri, ignore)] // Takes too long
4416    fn check_sliced_list_view_array() {
4417        let inner = Field::new_list_field(DataType::UInt32, true);
4418        let field = Field::new("val", DataType::ListView(Arc::new(inner)), true);
4419        let schema = Arc::new(Schema::new(vec![field]));
4420        let values = Arc::new(generate_list_view_data::<i32>());
4421
4422        for (offset, len) in [(999, 1), (0, 13), (47, 12), (values.len() - 13, 13)] {
4423            let in_batch = RecordBatch::try_new(schema.clone(), vec![values.clone()])
4424                .unwrap()
4425                .slice(offset, len);
4426            let out_batch = deserialize_file(serialize_file(&in_batch));
4427            assert_eq!(in_batch, out_batch);
4428        }
4429    }
4430
4431    #[test]
4432    #[cfg_attr(miri, ignore)] // Takes too long
4433    fn check_sliced_large_list_view_array() {
4434        let inner = Field::new_list_field(DataType::UInt32, true);
4435        let field = Field::new("val", DataType::LargeListView(Arc::new(inner)), true);
4436        let schema = Arc::new(Schema::new(vec![field]));
4437        let values = Arc::new(generate_list_view_data::<i64>());
4438
4439        for (offset, len) in [(999, 1), (0, 13), (47, 12), (values.len() - 13, 13)] {
4440            let in_batch = RecordBatch::try_new(schema.clone(), vec![values.clone()])
4441                .unwrap()
4442                .slice(offset, len);
4443            let out_batch = deserialize_file(serialize_file(&in_batch));
4444            assert_eq!(in_batch, out_batch);
4445        }
4446    }
4447
4448    fn generate_nested_list_view_data<O: OffsetSizeTrait>() -> GenericListViewArray<O> {
4449        let inner_builder = UInt32Builder::new();
4450        let middle_builder = GenericListViewBuilder::<O, _>::new(inner_builder);
4451        let mut outer_builder = GenericListViewBuilder::<O, _>::new(middle_builder);
4452
4453        for i in 0u32..10_000 {
4454            if i.is_multiple_of(1_000) {
4455                outer_builder.append(false);
4456                continue;
4457            }
4458
4459            for _ in 0..3 {
4460                for value in [i, i + 1, i + 2] {
4461                    outer_builder.values().values().append_value(value);
4462                }
4463                outer_builder.values().append(true);
4464            }
4465            outer_builder.append(true);
4466        }
4467
4468        outer_builder.finish()
4469    }
4470
4471    #[test]
4472    #[cfg_attr(miri, ignore)] // Takes too long
4473    fn encode_nested_list_views() {
4474        let inner_int = Arc::new(Field::new_list_field(DataType::UInt32, true));
4475        let inner_list_field = Arc::new(Field::new_list_field(DataType::ListView(inner_int), true));
4476        let list_field = Field::new("val", DataType::ListView(inner_list_field), true);
4477        let schema = Arc::new(Schema::new(vec![list_field]));
4478
4479        let values = Arc::new(generate_nested_list_view_data::<i32>());
4480
4481        let in_batch = RecordBatch::try_new(schema, vec![values]).unwrap();
4482        let out_batch = deserialize_file(serialize_file(&in_batch));
4483        assert_eq!(in_batch, out_batch);
4484    }
4485
4486    fn test_roundtrip_list_view_of_dict_impl<OffsetSize: OffsetSizeTrait, U: ArrowNativeType>(
4487        list_data_type: DataType,
4488        offsets: &[U; 5],
4489        sizes: &[U; 4],
4490    ) {
4491        let values = StringArray::from(vec![Some("alpha"), None, Some("beta"), Some("gamma")]);
4492        let keys = Int32Array::from_iter_values([0, 0, 1, 2, 3, 0, 2]);
4493        let dict_array = DictionaryArray::new(keys, Arc::new(values));
4494        let dict_data = dict_array.to_data();
4495
4496        let value_offsets = Buffer::from_slice_ref(offsets);
4497        let value_sizes = Buffer::from_slice_ref(sizes);
4498
4499        let list_data = ArrayData::builder(list_data_type)
4500            .len(4)
4501            .add_buffer(value_offsets)
4502            .add_buffer(value_sizes)
4503            .add_child_data(dict_data)
4504            .build()
4505            .unwrap();
4506        let list_view_array = GenericListViewArray::<OffsetSize>::from(list_data);
4507
4508        let schema = Arc::new(Schema::new(vec![Field::new(
4509            "f1",
4510            list_view_array.data_type().clone(),
4511            false,
4512        )]));
4513        let input_batch = RecordBatch::try_new(schema, vec![Arc::new(list_view_array)]).unwrap();
4514
4515        let output_batch = deserialize_file(serialize_file(&input_batch));
4516        assert_eq!(input_batch, output_batch);
4517
4518        let output_batch = deserialize_stream(serialize_stream(&input_batch));
4519        assert_eq!(input_batch, output_batch);
4520    }
4521
4522    #[test]
4523    fn test_roundtrip_list_view_of_dict() {
4524        #[expect(deprecated)]
4525        let list_data_type = DataType::ListView(Arc::new(Field::new_dict(
4526            "item",
4527            DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
4528            true,
4529            1,
4530            false,
4531        )));
4532        let offsets: &[i32; 5] = &[0, 2, 4, 4, 7];
4533        let sizes: &[i32; 4] = &[2, 2, 0, 3];
4534        test_roundtrip_list_view_of_dict_impl::<i32, i32>(list_data_type, offsets, sizes);
4535    }
4536
4537    #[test]
4538    fn test_roundtrip_large_list_view_of_dict() {
4539        #[expect(deprecated)]
4540        let list_data_type = DataType::LargeListView(Arc::new(Field::new_dict(
4541            "item",
4542            DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
4543            true,
4544            2,
4545            false,
4546        )));
4547        let offsets: &[i64; 5] = &[0, 2, 4, 4, 7];
4548        let sizes: &[i64; 4] = &[2, 2, 0, 3];
4549        test_roundtrip_list_view_of_dict_impl::<i64, i64>(list_data_type, offsets, sizes);
4550    }
4551
4552    #[test]
4553    fn test_roundtrip_sliced_list_view_of_dict() {
4554        #[expect(deprecated)]
4555        let list_data_type = DataType::ListView(Arc::new(Field::new_dict(
4556            "item",
4557            DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
4558            true,
4559            3,
4560            false,
4561        )));
4562
4563        let values = StringArray::from(vec![Some("alpha"), None, Some("beta"), Some("gamma")]);
4564        let keys = Int32Array::from_iter_values([0, 0, 1, 2, 3, 0, 2, 1, 0, 3, 2, 1]);
4565        let dict_array = DictionaryArray::new(keys, Arc::new(values));
4566        let dict_data = dict_array.to_data();
4567
4568        let offsets: &[i32; 7] = &[0, 2, 4, 4, 7, 9, 12];
4569        let sizes: &[i32; 6] = &[2, 2, 0, 3, 2, 3];
4570        let value_offsets = Buffer::from_slice_ref(offsets);
4571        let value_sizes = Buffer::from_slice_ref(sizes);
4572
4573        let list_data = ArrayData::builder(list_data_type)
4574            .len(6)
4575            .add_buffer(value_offsets)
4576            .add_buffer(value_sizes)
4577            .add_child_data(dict_data)
4578            .build()
4579            .unwrap();
4580        let list_view_array = GenericListViewArray::<i32>::from(list_data);
4581
4582        let schema = Arc::new(Schema::new(vec![Field::new(
4583            "f1",
4584            list_view_array.data_type().clone(),
4585            false,
4586        )]));
4587        let input_batch = RecordBatch::try_new(schema, vec![Arc::new(list_view_array)]).unwrap();
4588
4589        let sliced_batch = input_batch.slice(1, 4);
4590
4591        let output_batch = deserialize_file(serialize_file(&sliced_batch));
4592        assert_eq!(sliced_batch, output_batch);
4593
4594        let output_batch = deserialize_stream(serialize_stream(&sliced_batch));
4595        assert_eq!(sliced_batch, output_batch);
4596    }
4597
4598    #[test]
4599    fn test_roundtrip_dense_union_of_dict() {
4600        let values = StringArray::from(vec![Some("alpha"), None, Some("beta"), Some("gamma")]);
4601        let keys = Int32Array::from_iter_values([0, 0, 1, 2, 3, 0, 2]);
4602        let dict_array = DictionaryArray::new(keys, Arc::new(values));
4603
4604        #[expect(deprecated)]
4605        let dict_field = Arc::new(Field::new_dict(
4606            "dict",
4607            DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
4608            true,
4609            1,
4610            false,
4611        ));
4612        let int_field = Arc::new(Field::new("int", DataType::Int32, false));
4613        let union_fields = UnionFields::try_new(vec![0, 1], vec![dict_field, int_field]).unwrap();
4614
4615        let types = ScalarBuffer::from(vec![0i8, 0, 1, 0, 1, 0, 0]);
4616        let offsets = ScalarBuffer::from(vec![0i32, 1, 0, 2, 1, 3, 4]);
4617
4618        let int_array = Int32Array::from(vec![100, 200]);
4619
4620        let union = UnionArray::try_new(
4621            union_fields.clone(),
4622            types,
4623            Some(offsets),
4624            vec![Arc::new(dict_array), Arc::new(int_array)],
4625        )
4626        .unwrap();
4627
4628        let schema = Arc::new(Schema::new(vec![Field::new(
4629            "union",
4630            DataType::Union(union_fields, UnionMode::Dense),
4631            false,
4632        )]));
4633        let input_batch = RecordBatch::try_new(schema, vec![Arc::new(union)]).unwrap();
4634
4635        let output_batch = deserialize_file(serialize_file(&input_batch));
4636        assert_eq!(input_batch, output_batch);
4637
4638        let output_batch = deserialize_stream(serialize_stream(&input_batch));
4639        assert_eq!(input_batch, output_batch);
4640    }
4641
4642    #[test]
4643    fn test_roundtrip_sparse_union_of_dict() {
4644        let values = StringArray::from(vec![Some("alpha"), None, Some("beta"), Some("gamma")]);
4645        let keys = Int32Array::from_iter_values([0, 0, 1, 2, 3, 0, 2]);
4646        let dict_array = DictionaryArray::new(keys, Arc::new(values));
4647
4648        #[expect(deprecated)]
4649        let dict_field = Arc::new(Field::new_dict(
4650            "dict",
4651            DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
4652            true,
4653            2,
4654            false,
4655        ));
4656        let int_field = Arc::new(Field::new("int", DataType::Int32, false));
4657        let union_fields = UnionFields::try_new(vec![0, 1], vec![dict_field, int_field]).unwrap();
4658
4659        let types = ScalarBuffer::from(vec![0i8, 0, 1, 0, 1, 0, 0]);
4660
4661        let int_array = Int32Array::from(vec![0, 0, 100, 0, 200, 0, 0]);
4662
4663        let union = UnionArray::try_new(
4664            union_fields.clone(),
4665            types,
4666            None,
4667            vec![Arc::new(dict_array), Arc::new(int_array)],
4668        )
4669        .unwrap();
4670
4671        let schema = Arc::new(Schema::new(vec![Field::new(
4672            "union",
4673            DataType::Union(union_fields, UnionMode::Sparse),
4674            false,
4675        )]));
4676        let input_batch = RecordBatch::try_new(schema, vec![Arc::new(union)]).unwrap();
4677
4678        let output_batch = deserialize_file(serialize_file(&input_batch));
4679        assert_eq!(input_batch, output_batch);
4680
4681        let output_batch = deserialize_stream(serialize_stream(&input_batch));
4682        assert_eq!(input_batch, output_batch);
4683    }
4684
4685    #[test]
4686    fn test_roundtrip_map_with_dict_keys() {
4687        // Building a map array is a bit involved. We first build a struct arary that has a key and
4688        // value field and then use that to build the actual map array.
4689        let key_values = StringArray::from(vec!["key_a", "key_b", "key_c"]);
4690        let keys = Int32Array::from_iter_values([0, 1, 2, 0, 1, 0]);
4691        let dict_keys = DictionaryArray::new(keys, Arc::new(key_values));
4692
4693        let values = Int32Array::from(vec![1, 2, 3, 4, 5, 6]);
4694
4695        #[expect(deprecated)]
4696        let entries_field = Arc::new(Field::new(
4697            Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
4698            DataType::Struct(
4699                vec![
4700                    Field::new_dict(
4701                        Field::MAP_KEY_FIELD_DEFAULT_NAME,
4702                        DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
4703                        false,
4704                        1,
4705                        false,
4706                    ),
4707                    Field::new(Field::MAP_VALUE_FIELD_DEFAULT_NAME, DataType::Int32, true),
4708                ]
4709                .into(),
4710            ),
4711            false,
4712        ));
4713
4714        let entries = StructArray::from(vec![
4715            (
4716                Arc::new(Field::new(
4717                    Field::MAP_KEY_FIELD_DEFAULT_NAME,
4718                    DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
4719                    false,
4720                )),
4721                Arc::new(dict_keys) as ArrayRef,
4722            ),
4723            (
4724                Arc::new(Field::new(
4725                    Field::MAP_VALUE_FIELD_DEFAULT_NAME,
4726                    DataType::Int32,
4727                    true,
4728                )),
4729                Arc::new(values) as ArrayRef,
4730            ),
4731        ]);
4732
4733        let offsets = Buffer::from_slice_ref([0i32, 2, 4, 6]);
4734
4735        let map_data = ArrayData::builder(DataType::Map(entries_field, false))
4736            .len(3)
4737            .add_buffer(offsets)
4738            .add_child_data(entries.into_data())
4739            .build()
4740            .unwrap();
4741        let map_array = MapArray::from(map_data);
4742
4743        let schema = Arc::new(Schema::new(vec![Field::new(
4744            "map",
4745            map_array.data_type().clone(),
4746            false,
4747        )]));
4748        let input_batch = RecordBatch::try_new(schema, vec![Arc::new(map_array)]).unwrap();
4749
4750        let output_batch = deserialize_file(serialize_file(&input_batch));
4751        assert_eq!(input_batch, output_batch);
4752
4753        let output_batch = deserialize_stream(serialize_stream(&input_batch));
4754        assert_eq!(input_batch, output_batch);
4755    }
4756
4757    #[test]
4758    fn test_roundtrip_map_with_dict_values() {
4759        // Building a map array is a bit involved. We first build a struct arary that has a key and
4760        // value field and then use that to build the actual map array.
4761        let keys = StringArray::from(vec!["a", "b", "c", "d", "e", "f"]);
4762
4763        let value_values = StringArray::from(vec!["val_x", "val_y", "val_z"]);
4764        let value_keys = Int32Array::from_iter_values([0, 1, 2, 0, 1, 0]);
4765        let dict_values = DictionaryArray::new(value_keys, Arc::new(value_values));
4766
4767        #[expect(deprecated)]
4768        let entries_field = Arc::new(Field::new(
4769            Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
4770            DataType::Struct(
4771                vec![
4772                    Field::new(Field::MAP_KEY_FIELD_DEFAULT_NAME, DataType::Utf8, false),
4773                    Field::new_dict(
4774                        Field::MAP_VALUE_FIELD_DEFAULT_NAME,
4775                        DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
4776                        true,
4777                        2,
4778                        false,
4779                    ),
4780                ]
4781                .into(),
4782            ),
4783            false,
4784        ));
4785
4786        let entries = StructArray::from(vec![
4787            (
4788                Arc::new(Field::new(
4789                    Field::MAP_KEY_FIELD_DEFAULT_NAME,
4790                    DataType::Utf8,
4791                    false,
4792                )),
4793                Arc::new(keys) as ArrayRef,
4794            ),
4795            (
4796                Arc::new(Field::new(
4797                    Field::MAP_VALUE_FIELD_DEFAULT_NAME,
4798                    DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
4799                    true,
4800                )),
4801                Arc::new(dict_values) as ArrayRef,
4802            ),
4803        ]);
4804
4805        let offsets = Buffer::from_slice_ref([0i32, 2, 4, 6]);
4806
4807        let map_data = ArrayData::builder(DataType::Map(entries_field, false))
4808            .len(3)
4809            .add_buffer(offsets)
4810            .add_child_data(entries.into_data())
4811            .build()
4812            .unwrap();
4813        let map_array = MapArray::from(map_data);
4814
4815        let schema = Arc::new(Schema::new(vec![Field::new(
4816            "map",
4817            map_array.data_type().clone(),
4818            false,
4819        )]));
4820        let input_batch = RecordBatch::try_new(schema, vec![Arc::new(map_array)]).unwrap();
4821
4822        let output_batch = deserialize_file(serialize_file(&input_batch));
4823        assert_eq!(input_batch, output_batch);
4824
4825        let output_batch = deserialize_stream(serialize_stream(&input_batch));
4826        assert_eq!(input_batch, output_batch);
4827    }
4828
4829    #[test]
4830    fn test_decimal128_alignment16_is_sufficient() {
4831        const IPC_ALIGNMENT: usize = 16;
4832
4833        // Test a bunch of different dimensions to ensure alignment is never an issue.
4834        // For example, if we only test `num_cols = 1` then even with alignment 8 this
4835        // test would _happen_ to pass, even though for different dimensions like
4836        // `num_cols = 2` it would fail.
4837        for num_cols in [1, 2, 3, 17, 50, 73, 99] {
4838            let num_rows = (num_cols * 7 + 11) % 100; // Deterministic swizzle
4839
4840            let mut fields = Vec::new();
4841            let mut arrays = Vec::new();
4842            for i in 0..num_cols {
4843                let field = Field::new(format!("col_{i}"), DataType::Decimal128(38, 10), true);
4844                let array = Decimal128Array::from(vec![num_cols as i128; num_rows]);
4845                fields.push(field);
4846                arrays.push(Arc::new(array) as Arc<dyn Array>);
4847            }
4848            let schema = Schema::new(fields);
4849            let batch = RecordBatch::try_new(Arc::new(schema), arrays).unwrap();
4850
4851            let mut writer = FileWriter::try_new_with_options(
4852                Vec::new(),
4853                batch.schema_ref(),
4854                IpcWriteOptions::try_new(IPC_ALIGNMENT, false, MetadataVersion::V5).unwrap(),
4855            )
4856            .unwrap();
4857            writer.write(&batch).unwrap();
4858            writer.finish().unwrap();
4859
4860            let out: Vec<u8> = writer.into_inner().unwrap();
4861
4862            let buffer = Buffer::from_slice_ref(out);
4863            let trailer_start = buffer.len() - 10;
4864            let footer_len =
4865                read_footer_length(buffer[trailer_start..].try_into().unwrap()).unwrap();
4866            let footer =
4867                root_as_footer(&buffer[trailer_start - footer_len..trailer_start]).unwrap();
4868
4869            let schema = try_fb_to_schema(footer.schema().unwrap()).unwrap();
4870
4871            // Importantly we set `require_alignment`, checking that 16-byte alignment is sufficient
4872            // for `read_record_batch` later on to read the data in a zero-copy manner.
4873            let decoder =
4874                FileDecoder::new(Arc::new(schema), footer.version()).with_require_alignment(true);
4875
4876            let batches = footer.recordBatches().unwrap();
4877
4878            let block = batches.get(0);
4879            let block_len = block.bodyLength() as usize + block.metaDataLength() as usize;
4880            let data = buffer.slice_with_length(block.offset() as _, block_len);
4881
4882            let batch2 = decoder.read_record_batch(block, &data).unwrap().unwrap();
4883
4884            assert_eq!(batch, batch2);
4885        }
4886    }
4887
4888    #[test]
4889    fn test_decimal128_alignment8_is_unaligned() {
4890        const IPC_ALIGNMENT: usize = 8;
4891
4892        let num_cols = 2;
4893
4894        let mut fields = Vec::new();
4895        let mut arrays = Vec::new();
4896        for i in 0..num_cols {
4897            let field = Field::new(format!("col_{i}"), DataType::Decimal128(38, 10), true);
4898            // The null makes the writer emit a 1-byte validity buffer. With IPC_ALIGNMENT = 8,
4899            // the writer pads that buffer to 8 bytes, so the decimal values that follow start
4900            // at offset 8 of the body, which is not a multiple of 16.
4901            let array = Decimal128Array::from(vec![Some(num_cols as i128), None]);
4902            fields.push(field);
4903            arrays.push(Arc::new(array) as Arc<dyn Array>);
4904        }
4905        let schema = Schema::new(fields);
4906        let batch = RecordBatch::try_new(Arc::new(schema), arrays).unwrap();
4907
4908        let mut writer = FileWriter::try_new_with_options(
4909            Vec::new(),
4910            batch.schema_ref(),
4911            IpcWriteOptions::try_new(IPC_ALIGNMENT, false, MetadataVersion::V5).unwrap(),
4912        )
4913        .unwrap();
4914        writer.write(&batch).unwrap();
4915        writer.finish().unwrap();
4916
4917        let out: Vec<u8> = writer.into_inner().unwrap();
4918
4919        let buffer = Buffer::from_slice_ref(out);
4920        let trailer_start = buffer.len() - 10;
4921        let footer_len = read_footer_length(buffer[trailer_start..].try_into().unwrap()).unwrap();
4922        let footer = root_as_footer(&buffer[trailer_start - footer_len..trailer_start]).unwrap();
4923        let schema = try_fb_to_schema(footer.schema().unwrap()).unwrap();
4924
4925        // Importantly we set `require_alignment`, otherwise the error later is suppressed due to copying
4926        // to an aligned buffer in `ArrayDataBuilder.build_aligned`.
4927        let decoder =
4928            FileDecoder::new(Arc::new(schema), footer.version()).with_require_alignment(true);
4929
4930        let batches = footer.recordBatches().unwrap();
4931
4932        let block = batches.get(0);
4933        let block_len = block.bodyLength() as usize + block.metaDataLength() as usize;
4934        let data = buffer.slice_with_length(block.offset() as _, block_len);
4935
4936        let result = decoder.read_record_batch(block, &data);
4937
4938        let error = result.unwrap_err();
4939        assert_eq!(
4940            error.to_string(),
4941            "Invalid argument error: Misaligned buffers[0] in array of type Decimal128(38, 10), \
4942             offset from expected alignment of 16 by 8"
4943        );
4944    }
4945
4946    #[test]
4947    fn test_flush() {
4948        // We write a schema which is small enough to fit into a buffer and not get flushed,
4949        // and then force the write with .flush().
4950        let num_cols = 2;
4951        let mut fields = Vec::new();
4952        let options = IpcWriteOptions::try_new(8, false, MetadataVersion::V5).unwrap();
4953        for i in 0..num_cols {
4954            let field = Field::new(format!("col_{i}"), DataType::Decimal128(38, 10), true);
4955            fields.push(field);
4956        }
4957        let schema = Schema::new(fields);
4958        let inner_stream_writer = BufWriter::with_capacity(1024, Vec::new());
4959        let inner_file_writer = BufWriter::with_capacity(1024, Vec::new());
4960        let mut stream_writer =
4961            StreamWriter::try_new_with_options(inner_stream_writer, &schema, options.clone())
4962                .unwrap();
4963        let mut file_writer =
4964            FileWriter::try_new_with_options(inner_file_writer, &schema, options).unwrap();
4965
4966        let stream_bytes_written_on_new = stream_writer.get_ref().get_ref().len();
4967        let file_bytes_written_on_new = file_writer.get_ref().get_ref().len();
4968        stream_writer.flush().unwrap();
4969        file_writer.flush().unwrap();
4970        let stream_bytes_written_on_flush = stream_writer.get_ref().get_ref().len();
4971        let file_bytes_written_on_flush = file_writer.get_ref().get_ref().len();
4972        let stream_out = stream_writer.into_inner().unwrap().into_inner().unwrap();
4973        // Finishing a stream writes the continuation bytes in MetadataVersion::V5 (4 bytes)
4974        // and then a length of 0 (4 bytes) for a total of 8 bytes.
4975        // Everything before that should have been flushed in the .flush() call.
4976        let expected_stream_flushed_bytes = stream_out.len() - 8;
4977        // A file write is the same as the stream write except for the leading magic string
4978        // ARROW1 plus padding, which is 8 bytes.
4979        let expected_file_flushed_bytes = expected_stream_flushed_bytes + 8;
4980
4981        assert!(
4982            stream_bytes_written_on_new < stream_bytes_written_on_flush,
4983            "this test makes no sense if flush is not actually required"
4984        );
4985        assert!(
4986            file_bytes_written_on_new < file_bytes_written_on_flush,
4987            "this test makes no sense if flush is not actually required"
4988        );
4989        assert_eq!(stream_bytes_written_on_flush, expected_stream_flushed_bytes);
4990        assert_eq!(file_bytes_written_on_flush, expected_file_flushed_bytes);
4991    }
4992
4993    #[test]
4994    fn test_roundtrip_list_of_fixed_list() -> Result<(), ArrowError> {
4995        let l1_type =
4996            DataType::FixedSizeList(Arc::new(Field::new("item", DataType::Float32, false)), 3);
4997        let l2_type = DataType::List(Arc::new(Field::new("item", l1_type.clone(), false)));
4998
4999        let l0_builder = Float32Builder::new();
5000        let l1_builder = FixedSizeListBuilder::new(l0_builder, 3).with_field(Arc::new(Field::new(
5001            "item",
5002            DataType::Float32,
5003            false,
5004        )));
5005        let mut l2_builder =
5006            ListBuilder::new(l1_builder).with_field(Arc::new(Field::new("item", l1_type, false)));
5007
5008        for point in [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]] {
5009            l2_builder.values().values().append_value(point[0]);
5010            l2_builder.values().values().append_value(point[1]);
5011            l2_builder.values().values().append_value(point[2]);
5012
5013            l2_builder.values().append(true);
5014        }
5015        l2_builder.append(true);
5016
5017        let point = [10., 11., 12.];
5018        l2_builder.values().values().append_value(point[0]);
5019        l2_builder.values().values().append_value(point[1]);
5020        l2_builder.values().values().append_value(point[2]);
5021
5022        l2_builder.values().append(true);
5023        l2_builder.append(true);
5024
5025        let array = Arc::new(l2_builder.finish()) as ArrayRef;
5026
5027        let schema = Arc::new(Schema::new_with_metadata(
5028            vec![Field::new("points", l2_type, false)],
5029            HashMap::default(),
5030        ));
5031
5032        // Test a variety of combinations that include 0 and non-zero offsets
5033        // and also portions or the rest of the array
5034        test_slices(&array, &schema, 0, 1)?;
5035        test_slices(&array, &schema, 0, 2)?;
5036        test_slices(&array, &schema, 1, 1)?;
5037
5038        Ok(())
5039    }
5040
5041    #[test]
5042    fn test_roundtrip_list_of_fixed_list_w_nulls() -> Result<(), ArrowError> {
5043        let l0_builder = Float32Builder::new();
5044        let l1_builder = FixedSizeListBuilder::new(l0_builder, 3);
5045        let mut l2_builder = ListBuilder::new(l1_builder);
5046
5047        for point in [
5048            [Some(1.0), Some(2.0), None],
5049            [Some(4.0), Some(5.0), Some(6.0)],
5050            [None, Some(8.0), Some(9.0)],
5051        ] {
5052            for p in point {
5053                match p {
5054                    Some(p) => l2_builder.values().values().append_value(p),
5055                    None => l2_builder.values().values().append_null(),
5056                }
5057            }
5058
5059            l2_builder.values().append(true);
5060        }
5061        l2_builder.append(true);
5062
5063        let point = [Some(10.), None, None];
5064        for p in point {
5065            match p {
5066                Some(p) => l2_builder.values().values().append_value(p),
5067                None => l2_builder.values().values().append_null(),
5068            }
5069        }
5070
5071        l2_builder.values().append(true);
5072        l2_builder.append(true);
5073
5074        let array = Arc::new(l2_builder.finish()) as ArrayRef;
5075
5076        let schema = Arc::new(Schema::new_with_metadata(
5077            vec![Field::new(
5078                "points",
5079                DataType::List(Arc::new(Field::new(
5080                    "item",
5081                    DataType::FixedSizeList(
5082                        Arc::new(Field::new("item", DataType::Float32, true)),
5083                        3,
5084                    ),
5085                    true,
5086                ))),
5087                true,
5088            )],
5089            HashMap::default(),
5090        ));
5091
5092        // Test a variety of combinations that include 0 and non-zero offsets
5093        // and also portions or the rest of the array
5094        test_slices(&array, &schema, 0, 1)?;
5095        test_slices(&array, &schema, 0, 2)?;
5096        test_slices(&array, &schema, 1, 1)?;
5097
5098        Ok(())
5099    }
5100
5101    fn test_slices(
5102        parent_array: &ArrayRef,
5103        schema: &SchemaRef,
5104        offset: usize,
5105        length: usize,
5106    ) -> Result<(), ArrowError> {
5107        let subarray = parent_array.slice(offset, length);
5108        let original_batch = RecordBatch::try_new(schema.clone(), vec![subarray])?;
5109
5110        let mut bytes = Vec::new();
5111        let mut writer = StreamWriter::try_new(&mut bytes, schema)?;
5112        writer.write(&original_batch)?;
5113        writer.finish()?;
5114
5115        let mut cursor = std::io::Cursor::new(bytes);
5116        let mut reader = StreamReader::try_new(&mut cursor, None)?;
5117        let returned_batch = reader.next().unwrap()?;
5118
5119        assert_eq!(original_batch, returned_batch);
5120
5121        Ok(())
5122    }
5123
5124    #[test]
5125    fn test_roundtrip_fixed_list() -> Result<(), ArrowError> {
5126        let int_builder = Int64Builder::new();
5127        let mut fixed_list_builder = FixedSizeListBuilder::new(int_builder, 3)
5128            .with_field(Arc::new(Field::new("item", DataType::Int64, false)));
5129
5130        for point in [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]] {
5131            fixed_list_builder.values().append_value(point[0]);
5132            fixed_list_builder.values().append_value(point[1]);
5133            fixed_list_builder.values().append_value(point[2]);
5134
5135            fixed_list_builder.append(true);
5136        }
5137
5138        let array = Arc::new(fixed_list_builder.finish()) as ArrayRef;
5139
5140        let schema = Arc::new(Schema::new_with_metadata(
5141            vec![Field::new(
5142                "points",
5143                DataType::FixedSizeList(Arc::new(Field::new("item", DataType::Int64, false)), 3),
5144                false,
5145            )],
5146            HashMap::default(),
5147        ));
5148
5149        // Test a variety of combinations that include 0 and non-zero offsets
5150        // and also portions or the rest of the array
5151        test_slices(&array, &schema, 0, 4)?;
5152        test_slices(&array, &schema, 0, 2)?;
5153        test_slices(&array, &schema, 1, 3)?;
5154        test_slices(&array, &schema, 2, 1)?;
5155
5156        Ok(())
5157    }
5158
5159    #[test]
5160    fn test_roundtrip_fixed_list_w_nulls() -> Result<(), ArrowError> {
5161        let int_builder = Int64Builder::new();
5162        let mut fixed_list_builder = FixedSizeListBuilder::new(int_builder, 3);
5163
5164        for point in [
5165            [Some(1), Some(2), None],
5166            [Some(4), Some(5), Some(6)],
5167            [None, Some(8), Some(9)],
5168            [Some(10), None, None],
5169        ] {
5170            for p in point {
5171                match p {
5172                    Some(p) => fixed_list_builder.values().append_value(p),
5173                    None => fixed_list_builder.values().append_null(),
5174                }
5175            }
5176
5177            fixed_list_builder.append(true);
5178        }
5179
5180        let array = Arc::new(fixed_list_builder.finish()) as ArrayRef;
5181
5182        let schema = Arc::new(Schema::new_with_metadata(
5183            vec![Field::new(
5184                "points",
5185                DataType::FixedSizeList(Arc::new(Field::new("item", DataType::Int64, true)), 3),
5186                true,
5187            )],
5188            HashMap::default(),
5189        ));
5190
5191        // Test a variety of combinations that include 0 and non-zero offsets
5192        // and also portions or the rest of the array
5193        test_slices(&array, &schema, 0, 4)?;
5194        test_slices(&array, &schema, 0, 2)?;
5195        test_slices(&array, &schema, 1, 3)?;
5196        test_slices(&array, &schema, 2, 1)?;
5197
5198        Ok(())
5199    }
5200
5201    #[test]
5202    fn test_metadata_encoding_ordering() {
5203        fn create_hash() -> u64 {
5204            let metadata: HashMap<String, String> = [
5205                ("a", "1"), //
5206                ("b", "2"), //
5207                ("c", "3"), //
5208                ("d", "4"), //
5209                ("e", "5"), //
5210            ]
5211            .into_iter()
5212            .map(|(k, v)| (k.to_owned(), v.to_owned()))
5213            .collect();
5214
5215            // Set metadata on both the schema and a field within it.
5216            let schema = Arc::new(
5217                Schema::new(vec![
5218                    Field::new("a", DataType::Int64, true).with_metadata(metadata.clone()),
5219                ])
5220                .with_metadata(metadata)
5221                .clone(),
5222            );
5223            let batch = RecordBatch::new_empty(schema.clone());
5224
5225            let mut bytes = Vec::new();
5226            let mut w = StreamWriter::try_new(&mut bytes, batch.schema_ref()).unwrap();
5227            w.write(&batch).unwrap();
5228            w.finish().unwrap();
5229
5230            let mut h = std::hash::DefaultHasher::new();
5231            h.write(&bytes);
5232            h.finish()
5233        }
5234
5235        let expected = create_hash();
5236
5237        // Since there is randomness in the HashMap and we cannot specify our
5238        // own Hasher for the implementation used for metadata, run the above
5239        // code 20x and verify it does not change. This is not perfect but it
5240        // should be good enough.
5241        let all_passed = (0..20).all(|_| create_hash() == expected);
5242        assert!(all_passed);
5243    }
5244
5245    #[test]
5246    fn test_dictionary_tracker_reset() {
5247        let data_gen = IpcDataGenerator::default();
5248        let mut dictionary_tracker = DictionaryTracker::new(false);
5249        let writer_options = IpcWriteOptions::default();
5250        let mut compression_ctx = IpcWriteContext::default();
5251
5252        let schema = Arc::new(Schema::new(vec![Field::new(
5253            "a",
5254            DataType::Dictionary(Box::new(DataType::UInt8), Box::new(DataType::Utf8)),
5255            false,
5256        )]));
5257
5258        let mut write_single_batch_stream =
5259            |batch: RecordBatch, dict_tracker: &mut DictionaryTracker| -> Vec<u8> {
5260                let mut buffer = Vec::new();
5261
5262                // create a new IPC stream:
5263                let stream_header = data_gen.schema_to_bytes_with_dictionary_tracker(
5264                    &schema,
5265                    dict_tracker,
5266                    &writer_options,
5267                );
5268                _ = write_message(&mut buffer, stream_header, &writer_options).unwrap();
5269
5270                let (encoded_dicts, encoded_batch) = data_gen
5271                    .encode(&batch, dict_tracker, &writer_options, &mut compression_ctx)
5272                    .unwrap();
5273                for encoded_dict in encoded_dicts {
5274                    _ = write_message(&mut buffer, encoded_dict, &writer_options).unwrap();
5275                }
5276                _ = write_message(&mut buffer, encoded_batch, &writer_options).unwrap();
5277
5278                buffer
5279            };
5280
5281        let batch1 = RecordBatch::try_new(
5282            schema.clone(),
5283            vec![Arc::new(DictionaryArray::new(
5284                UInt8Array::from_iter_values([0]),
5285                Arc::new(StringArray::from_iter_values(["a"])),
5286            ))],
5287        )
5288        .unwrap();
5289        let buffer = write_single_batch_stream(batch1.clone(), &mut dictionary_tracker);
5290
5291        // ensure we can read the stream back
5292        let mut reader = StreamReader::try_new(Cursor::new(buffer), None).unwrap();
5293        let read_batch = reader.next().unwrap().unwrap();
5294        assert_eq!(read_batch, batch1);
5295
5296        // reset the dictionary tracker so it can be used for next stream
5297        dictionary_tracker.clear();
5298
5299        // now write a 2nd stream and ensure we can also read it:
5300        let batch2 = RecordBatch::try_new(
5301            schema.clone(),
5302            vec![Arc::new(DictionaryArray::new(
5303                UInt8Array::from_iter_values([0]),
5304                Arc::new(StringArray::from_iter_values(["a"])),
5305            ))],
5306        )
5307        .unwrap();
5308        let buffer = write_single_batch_stream(batch2.clone(), &mut dictionary_tracker);
5309        let mut reader = StreamReader::try_new(Cursor::new(buffer), None).unwrap();
5310        let read_batch = reader.next().unwrap().unwrap();
5311        assert_eq!(read_batch, batch2);
5312    }
5313}