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