Skip to main content

arrow_flight/
encode.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership
3// SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements
4
5use std::{collections::VecDeque, fmt::Debug, pin::Pin, sync::Arc, task::Poll};
6
7use crate::{FlightData, FlightDescriptor, SchemaAsIpc, error::Result};
8
9use arrow_array::{Array, ArrayRef, RecordBatch, RecordBatchOptions, UnionArray};
10use arrow_ipc::writer::{DictionaryTracker, IpcDataGenerator, IpcWriteContext, IpcWriteOptions};
11
12use arrow_schema::{ArrowError, DataType, Field, FieldRef, Fields, Schema, SchemaRef, UnionMode};
13use bytes::Bytes;
14use futures::{Stream, StreamExt, ready, stream::BoxStream};
15
16/// Creates a [`Stream`] of [`FlightData`]s from a
17/// `Stream` of [`Result`]<[`RecordBatch`], [`FlightError`]>.
18///
19/// This can be used to implement [`FlightService::do_get`] in an
20/// Arrow Flight implementation;
21///
22/// This structure encodes a stream of `Result`s rather than `RecordBatch`es  to
23/// propagate errors from streaming execution, where the generation of the
24/// `RecordBatch`es is incremental, and an error may occur even after
25/// several have already been successfully produced.
26///
27/// # Caveats
28///
29/// 1. All input record batches must have the same schema. The first batch sets the
30///    expected schema unless [`FlightDataEncoderBuilder::with_schema`] supplies it.
31///    A batch with a different schema produces an error and ends the stream.
32/// 2. When [`DictionaryHandling`] is [`DictionaryHandling::Hydrate`],
33///    [`DictionaryArray`]s are converted to their underlying types prior to
34///    transport.
35///    When [`DictionaryHandling`] is [`DictionaryHandling::Resend`], Dictionary [`FlightData`] is sent with every
36///    [`RecordBatch`] that contains a [`DictionaryArray`](arrow_array::array::DictionaryArray).
37///    See <https://github.com/apache/arrow-rs/issues/3389>.
38///
39/// [`DictionaryArray`]: arrow_array::array::DictionaryArray
40///
41/// # Example
42/// ```no_run
43/// # use std::sync::Arc;
44/// # use arrow_array::{ArrayRef, RecordBatch, UInt32Array};
45/// # async fn f() {
46/// # let c1 = UInt32Array::from(vec![1, 2, 3, 4, 5, 6]);
47/// # let batch = RecordBatch::try_from_iter(vec![
48/// #      ("a", Arc::new(c1) as ArrayRef)
49/// #   ])
50/// #   .expect("cannot create record batch");
51/// use arrow_flight::encode::FlightDataEncoderBuilder;
52///
53/// // Get an input stream of Result<RecordBatch, FlightError>
54/// let input_stream = futures::stream::iter(vec![Ok(batch)]);
55///
56/// // Build a stream of `Result<FlightData>` (e.g. to return for do_get)
57/// let flight_data_stream = FlightDataEncoderBuilder::new()
58///  .build(input_stream);
59///
60/// // Create a tonic `Response` that can be returned from a Flight server
61/// let response = tonic::Response::new(flight_data_stream);
62/// # }
63/// ```
64///
65/// # Example: Sending `Vec<RecordBatch>`
66///
67/// You can create a [`Stream`] to pass to [`Self::build`] from an existing
68/// `Vec` of `RecordBatch`es like this:
69///
70/// ```
71/// # use std::sync::Arc;
72/// # use arrow_array::{ArrayRef, RecordBatch, UInt32Array};
73/// # async fn f() {
74/// # fn make_batches() -> Vec<RecordBatch> {
75/// #   let c1 = UInt32Array::from(vec![1, 2, 3, 4, 5, 6]);
76/// #   let batch = RecordBatch::try_from_iter(vec![
77/// #      ("a", Arc::new(c1) as ArrayRef)
78/// #   ])
79/// #   .expect("cannot create record batch");
80/// #   vec![batch.clone(), batch.clone()]
81/// # }
82/// use arrow_flight::encode::FlightDataEncoderBuilder;
83///
84/// // Get batches that you want to send via Flight
85/// let batches: Vec<RecordBatch> = make_batches();
86///
87/// // Create an input stream of Result<RecordBatch, FlightError>
88/// let input_stream = futures::stream::iter(
89///   batches.into_iter().map(Ok)
90/// );
91///
92/// // Build a stream of `Result<FlightData>` (e.g. to return for do_get)
93/// let flight_data_stream = FlightDataEncoderBuilder::new()
94///  .build(input_stream);
95/// # }
96/// ```
97///
98/// # Example: Determining schema of encoded data
99///
100/// Encoding flight data may hydrate dictionaries, see [`DictionaryHandling`] for more information,
101/// which changes the schema of the encoded data compared to the input record batches.
102/// The fully hydrated schema can be accessed using the [`FlightDataEncoder::known_schema`] method
103/// and explicitly informing the builder of the schema using [`FlightDataEncoderBuilder::with_schema`].
104///
105/// ```
106/// # use std::sync::Arc;
107/// # use arrow_array::{ArrayRef, RecordBatch, UInt32Array};
108/// # async fn f() {
109/// # let c1 = UInt32Array::from(vec![1, 2, 3, 4, 5, 6]);
110/// # let batch = RecordBatch::try_from_iter(vec![
111/// #      ("a", Arc::new(c1) as ArrayRef)
112/// #   ])
113/// #   .expect("cannot create record batch");
114/// use arrow_flight::encode::FlightDataEncoderBuilder;
115///
116/// // Get the schema of the input stream
117/// let schema = batch.schema();
118///
119/// // Get an input stream of Result<RecordBatch, FlightError>
120/// let input_stream = futures::stream::iter(vec![Ok(batch)]);
121///
122/// // Build a stream of `Result<FlightData>` (e.g. to return for do_get)
123/// let flight_data_stream = FlightDataEncoderBuilder::new()
124///  // Inform the builder of the input stream schema
125///  .with_schema(schema)
126///  .build(input_stream);
127///
128/// // Retrieve the schema of the encoded data
129/// let encoded_schema = flight_data_stream.known_schema();
130/// # }
131/// ```
132///
133/// [`FlightService::do_get`]: crate::flight_service_server::FlightService::do_get
134/// [`FlightError`]: crate::error::FlightError
135#[derive(Debug)]
136pub struct FlightDataEncoderBuilder {
137    /// The maximum approximate target message size in bytes
138    /// (see details on [`Self::with_max_flight_data_size`]).
139    max_flight_data_size: usize,
140    /// Ipc writer options
141    options: IpcWriteOptions,
142    /// Metadata to add to the schema message
143    app_metadata: Bytes,
144    /// Optional schema, if known before data.
145    schema: Option<SchemaRef>,
146    /// Optional flight descriptor, if known before data.
147    descriptor: Option<FlightDescriptor>,
148    /// Deterimines how `DictionaryArray`s are encoded for transport.
149    /// See [`DictionaryHandling`] for more information.
150    dictionary_handling: DictionaryHandling,
151}
152
153/// Default target size for encoded [`FlightData`].
154///
155/// Note this value would normally be 4MB, but the size calculation is
156/// somewhat inexact, so we set it to 2MB.
157pub const GRPC_TARGET_MAX_FLIGHT_SIZE_BYTES: usize = 2097152;
158
159impl Default for FlightDataEncoderBuilder {
160    fn default() -> Self {
161        Self {
162            max_flight_data_size: GRPC_TARGET_MAX_FLIGHT_SIZE_BYTES,
163            options: IpcWriteOptions::default(),
164            app_metadata: Bytes::new(),
165            schema: None,
166            descriptor: None,
167            dictionary_handling: DictionaryHandling::Hydrate,
168        }
169    }
170}
171
172impl FlightDataEncoderBuilder {
173    /// Create a new [`FlightDataEncoderBuilder`].
174    pub fn new() -> Self {
175        Self::default()
176    }
177
178    /// Set the (approximate) maximum size, in bytes, of the
179    /// [`FlightData`] produced by this encoder. Defaults to 2MB.
180    ///
181    /// Since there is often a maximum message size for gRPC messages
182    /// (typically around 4MB), this encoder splits up [`RecordBatch`]s
183    /// (preserving order) into multiple [`FlightData`] objects to
184    /// limit the size individual messages sent via gRPC.
185    ///
186    /// The size is approximate because of the additional encoding
187    /// overhead on top of the underlying data buffers themselves.
188    pub fn with_max_flight_data_size(mut self, max_flight_data_size: usize) -> Self {
189        self.max_flight_data_size = max_flight_data_size;
190        self
191    }
192
193    /// Set [`DictionaryHandling`] for encoder
194    pub fn with_dictionary_handling(mut self, dictionary_handling: DictionaryHandling) -> Self {
195        self.dictionary_handling = dictionary_handling;
196        self
197    }
198
199    /// Specify application specific metadata included in the
200    /// [`FlightData::app_metadata`] field of the the first Schema
201    /// message
202    pub fn with_metadata(mut self, app_metadata: Bytes) -> Self {
203        self.app_metadata = app_metadata;
204        self
205    }
206
207    /// Set the [`IpcWriteOptions`] used to encode the [`RecordBatch`]es for transport.
208    pub fn with_options(mut self, options: IpcWriteOptions) -> Self {
209        self.options = options;
210        self
211    }
212
213    /// Specify a schema for the RecordBatches being sent. If a schema
214    /// is not specified, an encoded Schema message will be sent when
215    /// the first [`RecordBatch`], if any, is encoded. Some clients
216    /// expect a Schema message even if there is no data sent.
217    /// Every input batch must match this schema.
218    pub fn with_schema(mut self, schema: SchemaRef) -> Self {
219        self.schema = Some(schema);
220        self
221    }
222
223    /// Specify a flight descriptor in the first FlightData message.
224    pub fn with_flight_descriptor(mut self, descriptor: Option<FlightDescriptor>) -> Self {
225        self.descriptor = descriptor;
226        self
227    }
228
229    /// Takes a [`Stream`] of [`Result<RecordBatch>`] and returns a [`Stream`]
230    /// of [`FlightData`], consuming self.
231    ///
232    /// See example on [`Self`] and [`FlightDataEncoder`] for more details
233    pub fn build<S>(self, input: S) -> FlightDataEncoder
234    where
235        S: Stream<Item = Result<RecordBatch>> + Send + 'static,
236    {
237        let Self {
238            max_flight_data_size,
239            options,
240            app_metadata,
241            schema,
242            descriptor,
243            dictionary_handling,
244        } = self;
245
246        FlightDataEncoder::new(
247            input.boxed(),
248            schema,
249            max_flight_data_size,
250            options,
251            app_metadata,
252            descriptor,
253            dictionary_handling,
254        )
255    }
256}
257
258/// Stream that encodes a stream of record batches to flight data.
259///
260/// See [`FlightDataEncoderBuilder`] for details and example.
261pub struct FlightDataEncoder {
262    /// Input stream
263    inner: BoxStream<'static, Result<RecordBatch>>,
264    /// Schema of input batches before dictionary handling
265    input_schema: Option<SchemaRef>,
266    /// schema, set after the first batch
267    schema: Option<SchemaRef>,
268    /// Target maximum size of flight data
269    /// (see details on [`FlightDataEncoderBuilder::with_max_flight_data_size`]).
270    max_flight_data_size: usize,
271    /// do the encoding / tracking of dictionaries
272    encoder: FlightIpcEncoder,
273    /// optional metadata to add to schema FlightData
274    app_metadata: Option<Bytes>,
275    /// data queued up to send but not yet sent
276    queue: VecDeque<FlightData>,
277    /// Is this stream done (inner is empty or errored)
278    done: bool,
279    /// cleared after the first FlightData message is sent
280    descriptor: Option<FlightDescriptor>,
281    /// Deterimines how `DictionaryArray`s are encoded for transport.
282    /// See [`DictionaryHandling`] for more information.
283    dictionary_handling: DictionaryHandling,
284}
285
286impl FlightDataEncoder {
287    fn new(
288        inner: BoxStream<'static, Result<RecordBatch>>,
289        schema: Option<SchemaRef>,
290        max_flight_data_size: usize,
291        options: IpcWriteOptions,
292        app_metadata: Bytes,
293        descriptor: Option<FlightDescriptor>,
294        dictionary_handling: DictionaryHandling,
295    ) -> Self {
296        let mut encoder = Self {
297            inner,
298            input_schema: schema.clone(),
299            schema: None,
300            max_flight_data_size,
301            encoder: FlightIpcEncoder::new(
302                options,
303                dictionary_handling != DictionaryHandling::Resend,
304            ),
305            app_metadata: Some(app_metadata),
306            queue: VecDeque::new(),
307            done: false,
308            descriptor,
309            dictionary_handling,
310        };
311
312        // If schema is known up front, enqueue it immediately
313        if let Some(schema) = schema {
314            encoder.encode_schema(&schema);
315        }
316
317        encoder
318    }
319
320    /// Report the schema of the encoded data when known.
321    /// A schema is known when provided via the [`FlightDataEncoderBuilder::with_schema`] method.
322    pub fn known_schema(&self) -> Option<SchemaRef> {
323        self.schema.clone()
324    }
325
326    /// Place the `FlightData` in the queue to send
327    #[inline]
328    fn queue_message(&mut self, mut data: FlightData) {
329        if let Some(descriptor) = self.descriptor.take() {
330            data.flight_descriptor = Some(descriptor);
331        }
332        self.queue.push_back(data);
333    }
334
335    /// Encodes schema as a [`FlightData`] in self.queue.
336    /// Updates `self.schema` and returns the new schema
337    fn encode_schema(&mut self, schema: &SchemaRef) -> SchemaRef {
338        // The first message is the schema message, and all
339        // batches have the same schema
340        let send_dictionaries = self.dictionary_handling == DictionaryHandling::Resend;
341        let schema = Arc::new(prepare_schema_for_flight(
342            schema,
343            &mut self.encoder.dictionary_tracker,
344            send_dictionaries,
345        ));
346        let mut schema_flight_data = self.encoder.encode_schema(&schema);
347
348        // attach any metadata requested
349        if let Some(app_metadata) = self.app_metadata.take() {
350            schema_flight_data.app_metadata = app_metadata;
351        }
352        self.queue_message(schema_flight_data);
353        // remember schema
354        self.schema = Some(schema.clone());
355        schema
356    }
357
358    /// Encodes batch into one or more `FlightData` messages in self.queue
359    fn encode_batch(&mut self, batch: RecordBatch) -> Result<()> {
360        match &self.input_schema {
361            Some(expected) if expected.as_ref() != batch.schema_ref().as_ref() => {
362                return Err(ArrowError::SchemaError(
363                    "Record batch schema does not match input schema".to_string(),
364                )
365                .into());
366            }
367            None => self.input_schema = Some(batch.schema()),
368            _ => {}
369        }
370
371        let schema = match &self.schema {
372            Some(schema) => schema.clone(),
373            // encode the schema if this is the first time we have seen it
374            None => self.encode_schema(batch.schema_ref()),
375        };
376
377        let batch = match self.dictionary_handling {
378            DictionaryHandling::Resend => batch,
379            DictionaryHandling::Hydrate => hydrate_dictionaries(&batch, schema)?,
380        };
381
382        let batches = split_batch_for_grpc_response(batch, self.max_flight_data_size);
383        let last = batches.len().saturating_sub(1); // handle empty batches  
384        for (i, batch) in batches.into_iter().enumerate() {
385            self.encoder
386                .ipc_write_context
387                .set_reserve_scratch(i != last);
388            let (flight_dictionaries, flight_batch) = self.encoder.encode_batch(&batch)?;
389            for dict in flight_dictionaries {
390                self.queue_message(dict);
391            }
392            self.queue_message(flight_batch);
393        }
394
395        Ok(())
396    }
397}
398
399impl Stream for FlightDataEncoder {
400    type Item = Result<FlightData>;
401
402    fn poll_next(
403        mut self: Pin<&mut Self>,
404        cx: &mut std::task::Context<'_>,
405    ) -> Poll<Option<Self::Item>> {
406        loop {
407            if self.done && self.queue.is_empty() {
408                return Poll::Ready(None);
409            }
410
411            // Any messages queued to send?
412            if let Some(data) = self.queue.pop_front() {
413                return Poll::Ready(Some(Ok(data)));
414            }
415
416            // Get next batch
417            let batch = ready!(self.inner.poll_next_unpin(cx));
418
419            match batch {
420                None => {
421                    // inner is done
422                    self.done = true;
423                    // queue must also be empty so we are done
424                    assert!(self.queue.is_empty());
425                    return Poll::Ready(None);
426                }
427                Some(Err(e)) => {
428                    // error from inner
429                    self.done = true;
430                    self.queue.clear();
431                    return Poll::Ready(Some(Err(e)));
432                }
433                Some(Ok(batch)) => {
434                    // had data, encode into the queue
435                    if let Err(e) = self.encode_batch(batch) {
436                        self.done = true;
437                        self.queue.clear();
438                        return Poll::Ready(Some(Err(e)));
439                    }
440                }
441            }
442        }
443    }
444}
445
446/// Defines how a [`FlightDataEncoder`] encodes [`DictionaryArray`]s
447///
448/// [`DictionaryArray`]: arrow_array::DictionaryArray
449///
450/// In the arrow flight protocol dictionary values and keys are sent as two separate messages.
451/// When a sender is encoding a [`RecordBatch`] containing [`DictionaryArray`] columns, it will
452/// first send a dictionary batch (a batch with header `MessageHeader::DictionaryBatch`) containing
453/// the dictionary values. The receiver is responsible for reading this batch and maintaining state that associates
454/// those dictionary values with the corresponding array using the `dict_id` as a key.
455///
456/// After sending the dictionary batch the sender will send the array data in a batch with header `MessageHeader::RecordBatch`.
457/// For any dictionary array batches in this message, the encoded flight message will only contain the dictionary keys. The receiver
458/// is then responsible for rebuilding the `DictionaryArray` on the client side using the dictionary values from the DictionaryBatch message
459/// and the keys from the RecordBatch message.
460///
461/// For example, if we have a batch with a `TypedDictionaryArray<'_, UInt32Type, Utf8Type>` (a dictionary array where they keys are `u32` and the
462/// values are `String`), then the DictionaryBatch will contain a `StringArray` and the RecordBatch will contain a `UInt32Array`.
463///
464/// Note that since `dict_id` defined in the `Schema` is used as a key to associate dictionary values to their arrays it is required that each
465/// `DictionaryArray` in a `RecordBatch` have a unique `dict_id`.
466///
467/// The current implementation does not support "delta" dictionaries so a new dictionary batch will be sent each time the encoder sees a
468/// dictionary which is not pointer-equal to the previously observed dictionary for a given `dict_id`.
469///
470/// For clients which may not support `DictionaryEncoding`, the `DictionaryHandling::Hydrate` method will bypass the process defined above
471/// and "hydrate" any `DictionaryArray` in the batch to their underlying value type (e.g. `TypedDictionaryArray<'_, UInt32Type, Utf8Type>` will
472/// be sent as a `StringArray`). With this method all data will be sent in `MessageHeader::RecordBatch` messages and the batch schema
473/// will be adjusted so that all dictionary encoded fields are changed to fields of the dictionary value type.
474#[derive(Debug, PartialEq)]
475pub enum DictionaryHandling {
476    /// Expands to the underlying type (default). This likely sends more data
477    /// over the network but requires less memory (dictionaries are not tracked)
478    /// and is more compatible with other arrow flight client implementations
479    /// that may not support `DictionaryEncoding`
480    ///
481    /// See also:
482    /// * <https://github.com/apache/arrow-rs/issues/1206>
483    Hydrate,
484    /// Send dictionary FlightData with every RecordBatch that contains a
485    /// [`DictionaryArray`]. See [`Self::Hydrate`] for more tradeoffs. No
486    /// attempt is made to skip sending the same (logical) dictionary values
487    /// twice.
488    ///
489    /// [`DictionaryArray`]: arrow_array::DictionaryArray
490    ///
491    /// This requires identifying the different dictionaries in use and assigning
492    //  them unique IDs
493    Resend,
494}
495
496fn prepare_field_for_flight(
497    field: &FieldRef,
498    dictionary_tracker: &mut DictionaryTracker,
499    send_dictionaries: bool,
500) -> Field {
501    match field.data_type() {
502        DataType::List(inner) => Field::new_list(
503            field.name(),
504            prepare_field_for_flight(inner, dictionary_tracker, send_dictionaries),
505            field.is_nullable(),
506        )
507        .with_metadata(field.metadata().clone()),
508        DataType::LargeList(inner) => Field::new_large_list(
509            field.name(),
510            prepare_field_for_flight(inner, dictionary_tracker, send_dictionaries),
511            field.is_nullable(),
512        )
513        .with_metadata(field.metadata().clone()),
514        DataType::Struct(fields) => {
515            let new_fields: Vec<Field> = fields
516                .iter()
517                .map(|f| prepare_field_for_flight(f, dictionary_tracker, send_dictionaries))
518                .collect();
519            Field::new_struct(field.name(), new_fields, field.is_nullable())
520                .with_metadata(field.metadata().clone())
521        }
522        DataType::Union(fields, mode) => {
523            let (type_ids, new_fields): (Vec<i8>, Vec<Field>) = fields
524                .iter()
525                .map(|(type_id, f)| {
526                    (
527                        type_id,
528                        prepare_field_for_flight(f, dictionary_tracker, send_dictionaries),
529                    )
530                })
531                .unzip();
532
533            Field::new_union(field.name(), type_ids, new_fields, *mode)
534        }
535        DataType::Dictionary(_, value_type) => {
536            if !send_dictionaries {
537                // Recurse into value type to handle nested dicts being stripped
538                let value_field = Field::new(
539                    field.name(),
540                    value_type.as_ref().clone(),
541                    field.is_nullable(),
542                );
543                prepare_field_for_flight(
544                    &Arc::new(value_field),
545                    dictionary_tracker,
546                    send_dictionaries,
547                )
548                .with_metadata(field.metadata().clone())
549            } else {
550                // Recurse into value type BEFORE registering this dict's id,
551                // matching the depth-first order of encode_dictionaries in the
552                // IPC writer which processes nested dicts before the parent.
553                let value_field = Field::new("values", value_type.as_ref().clone(), true);
554                prepare_field_for_flight(
555                    &Arc::new(value_field),
556                    dictionary_tracker,
557                    send_dictionaries,
558                );
559                dictionary_tracker.next_dict_id();
560                #[expect(deprecated)]
561                Field::new_dict(
562                    field.name(),
563                    field.data_type().clone(),
564                    field.is_nullable(),
565                    0,
566                    field.dict_is_ordered().unwrap_or_default(),
567                )
568                .with_metadata(field.metadata().clone())
569            }
570        }
571        DataType::ListView(inner) | DataType::LargeListView(inner) => {
572            let prepared = prepare_field_for_flight(inner, dictionary_tracker, send_dictionaries);
573            Field::new(
574                field.name(),
575                match field.data_type() {
576                    DataType::ListView(_) => DataType::ListView(Arc::new(prepared)),
577                    _ => DataType::LargeListView(Arc::new(prepared)),
578                },
579                field.is_nullable(),
580            )
581            .with_metadata(field.metadata().clone())
582        }
583        DataType::FixedSizeList(inner, size) => Field::new(
584            field.name(),
585            DataType::FixedSizeList(
586                Arc::new(prepare_field_for_flight(
587                    inner,
588                    dictionary_tracker,
589                    send_dictionaries,
590                )),
591                *size,
592            ),
593            field.is_nullable(),
594        )
595        .with_metadata(field.metadata().clone()),
596        DataType::RunEndEncoded(run_ends, values) => Field::new(
597            field.name(),
598            DataType::RunEndEncoded(
599                run_ends.clone(),
600                Arc::new(prepare_field_for_flight(
601                    values,
602                    dictionary_tracker,
603                    send_dictionaries,
604                )),
605            ),
606            field.is_nullable(),
607        )
608        .with_metadata(field.metadata().clone()),
609        DataType::Map(inner, sorted) => Field::new(
610            field.name(),
611            DataType::Map(
612                prepare_field_for_flight(inner, dictionary_tracker, send_dictionaries).into(),
613                *sorted,
614            ),
615            field.is_nullable(),
616        )
617        .with_metadata(field.metadata().clone()),
618        DataType::Null
619        | DataType::Boolean
620        | DataType::Int8
621        | DataType::Int16
622        | DataType::Int32
623        | DataType::Int64
624        | DataType::UInt8
625        | DataType::UInt16
626        | DataType::UInt32
627        | DataType::UInt64
628        | DataType::Float16
629        | DataType::Float32
630        | DataType::Float64
631        | DataType::Timestamp(_, _)
632        | DataType::Date32
633        | DataType::Date64
634        | DataType::Time32(_)
635        | DataType::Time64(_)
636        | DataType::Duration(_)
637        | DataType::Interval(_)
638        | DataType::Binary
639        | DataType::FixedSizeBinary(_)
640        | DataType::LargeBinary
641        | DataType::BinaryView
642        | DataType::Utf8
643        | DataType::LargeUtf8
644        | DataType::Utf8View
645        | DataType::Decimal32(_, _)
646        | DataType::Decimal64(_, _)
647        | DataType::Decimal128(_, _)
648        | DataType::Decimal256(_, _) => field.as_ref().clone(),
649    }
650}
651
652/// Prepare an arrow Schema for transport over the Arrow Flight protocol
653///
654/// Convert dictionary types to underlying types
655///
656/// See hydrate_dictionary for more information
657fn prepare_schema_for_flight(
658    schema: &Schema,
659    dictionary_tracker: &mut DictionaryTracker,
660    send_dictionaries: bool,
661) -> Schema {
662    let fields: Fields = schema
663        .fields()
664        .iter()
665        .map(|field| prepare_field_for_flight(field, dictionary_tracker, send_dictionaries))
666        .collect();
667
668    Schema::new(fields).with_metadata(schema.metadata().clone())
669}
670
671/// Split [`RecordBatch`] so it hopefully fits into a gRPC response.
672///
673/// Data is zero-copy sliced into batches.
674///
675/// Note: this method does not take into account already sliced
676/// arrays: <https://github.com/apache/arrow-rs/issues/3407>
677fn split_batch_for_grpc_response(
678    batch: RecordBatch,
679    max_flight_data_size: usize,
680) -> Vec<RecordBatch> {
681    let size = batch
682        .columns()
683        .iter()
684        .map(|col| col.get_buffer_memory_size())
685        .sum::<usize>();
686
687    let n_batches =
688        (size / max_flight_data_size + usize::from(size % max_flight_data_size != 0)).max(1);
689    let num_rows = batch.num_rows();
690    let rows_per_batch = (num_rows / n_batches).max(1);
691    let mut offset = 0;
692    let mut batches = Vec::with_capacity(n_batches);
693
694    while offset < num_rows {
695        let length = rows_per_batch.min(num_rows - offset);
696        batches.push(batch.slice(offset, length));
697        offset += length;
698    }
699
700    batches
701}
702
703/// The data needed to encode a stream of flight data, holding on to
704/// shared Dictionaries.
705///
706/// TODO: at allow dictionaries to be flushed / avoid building them
707///
708/// TODO limit on the number of dictionaries???
709struct FlightIpcEncoder {
710    options: IpcWriteOptions,
711    data_gen: IpcDataGenerator,
712    dictionary_tracker: DictionaryTracker,
713    ipc_write_context: IpcWriteContext,
714}
715
716impl FlightIpcEncoder {
717    fn new(options: IpcWriteOptions, error_on_replacement: bool) -> Self {
718        Self {
719            options,
720            data_gen: IpcDataGenerator::default(),
721            dictionary_tracker: DictionaryTracker::new(error_on_replacement),
722            ipc_write_context: IpcWriteContext::default(),
723        }
724    }
725
726    /// Encode a schema as a FlightData
727    fn encode_schema(&self, schema: &Schema) -> FlightData {
728        SchemaAsIpc::new(schema, &self.options).into()
729    }
730
731    /// Convert a `RecordBatch` to a Vec of `FlightData` representing
732    /// dictionaries and a `FlightData` representing the batch
733    fn encode_batch(
734        &mut self,
735        batch: &RecordBatch,
736    ) -> Result<(impl Iterator<Item = FlightData> + use<>, FlightData)> {
737        let (encoded_dictionaries, encoded_batch) = self.data_gen.encode(
738            batch,
739            &mut self.dictionary_tracker,
740            &self.options,
741            &mut self.ipc_write_context,
742        )?;
743
744        let flight_dictionaries = encoded_dictionaries.into_iter().map(|e| e.into());
745        let flight_batch = encoded_batch.into();
746
747        Ok((flight_dictionaries, flight_batch))
748    }
749}
750
751/// Hydrates any dictionaries arrays in `batch` to its underlying type. See
752/// hydrate_dictionary for more information.
753fn hydrate_dictionaries(batch: &RecordBatch, schema: SchemaRef) -> Result<RecordBatch> {
754    let columns = schema
755        .fields()
756        .iter()
757        .zip(batch.columns())
758        .map(|(field, c)| hydrate_dictionary(c, field.data_type()))
759        .collect::<Result<Vec<_>>>()?;
760
761    let options = RecordBatchOptions::new().with_row_count(Some(batch.num_rows()));
762
763    Ok(RecordBatch::try_new_with_options(
764        schema, columns, &options,
765    )?)
766}
767
768/// Hydrates a dictionary to its underlying type.
769fn hydrate_dictionary(array: &ArrayRef, data_type: &DataType) -> Result<ArrayRef> {
770    let arr = match (array.data_type(), data_type) {
771        (DataType::Union(_, UnionMode::Sparse), DataType::Union(fields, UnionMode::Sparse)) => {
772            let union_arr = array.as_any().downcast_ref::<UnionArray>().unwrap();
773
774            Arc::new(UnionArray::try_new(
775                fields.clone(),
776                union_arr.type_ids().clone(),
777                None,
778                fields
779                    .iter()
780                    .map(|(type_id, field)| {
781                        Ok(arrow_cast::cast(
782                            union_arr.child(type_id),
783                            field.data_type(),
784                        )?)
785                    })
786                    .collect::<Result<Vec<_>>>()?,
787            )?)
788        }
789        (_, data_type) => arrow_cast::cast(array, data_type)?,
790    };
791    Ok(arr)
792}
793
794#[cfg(test)]
795mod tests {
796    use crate::decode::{DecodedPayload, FlightDataDecoder};
797    use arrow_array::builder::{
798        FixedSizeListBuilder, GenericByteDictionaryBuilder, GenericListViewBuilder, Int32Builder,
799        LargeListBuilder, ListBuilder, StringDictionaryBuilder, StructBuilder,
800    };
801    use arrow_array::*;
802    use arrow_array::{cast::downcast_array, types::*};
803    use arrow_buffer::ScalarBuffer;
804    use arrow_cast::pretty::pretty_format_batches;
805    use arrow_ipc::{CompressionType, MetadataVersion};
806    use arrow_schema::{UnionFields, UnionMode};
807    use builder::MapBuilder;
808    use std::collections::HashMap;
809
810    use super::*;
811
812    #[test]
813    /// ensure only the batch's used data (not the allocated data) is sent
814    /// <https://github.com/apache/arrow-rs/issues/208>
815    fn test_encode_flight_data() {
816        // use 8-byte alignment - default alignment is 64 which produces bigger ipc data
817        let options = IpcWriteOptions::try_new(8, false, MetadataVersion::V5).unwrap();
818        let c1 = UInt32Array::from(vec![1, 2, 3, 4, 5, 6]);
819
820        let batch = RecordBatch::try_from_iter(vec![("a", Arc::new(c1) as ArrayRef)])
821            .expect("cannot create record batch");
822        let schema = batch.schema_ref();
823
824        let (_, baseline_flight_batch) = make_flight_data(&batch, &options);
825
826        let big_batch = batch.slice(0, batch.num_rows() - 1);
827        let optimized_big_batch =
828            hydrate_dictionaries(&big_batch, Arc::clone(schema)).expect("failed to optimize");
829        let (_, optimized_big_flight_batch) = make_flight_data(&optimized_big_batch, &options);
830
831        assert_eq!(
832            baseline_flight_batch.data_body.len(),
833            optimized_big_flight_batch.data_body.len()
834        );
835
836        let small_batch = batch.slice(0, 1);
837        let optimized_small_batch =
838            hydrate_dictionaries(&small_batch, Arc::clone(schema)).expect("failed to optimize");
839        let (_, optimized_small_flight_batch) = make_flight_data(&optimized_small_batch, &options);
840
841        assert!(
842            baseline_flight_batch.data_body.len() > optimized_small_flight_batch.data_body.len()
843        );
844    }
845
846    #[tokio::test]
847    async fn test_dictionary_hydration() {
848        let arr1: DictionaryArray<UInt16Type> = vec!["a", "a", "b"].into_iter().collect();
849        let arr2: DictionaryArray<UInt16Type> = vec!["c", "c", "d"].into_iter().collect();
850
851        let schema = Arc::new(Schema::new(vec![Field::new_dictionary(
852            "dict",
853            DataType::UInt16,
854            DataType::Utf8,
855            false,
856        )]));
857        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
858        let batch2 = RecordBatch::try_new(schema, vec![Arc::new(arr2)]).unwrap();
859
860        let stream = futures::stream::iter(vec![Ok(batch1), Ok(batch2)]);
861
862        let encoder = FlightDataEncoderBuilder::default().build(stream);
863        let mut decoder = FlightDataDecoder::new(encoder);
864        let expected_schema = Schema::new(vec![Field::new("dict", DataType::Utf8, false)]);
865        let expected_schema = Arc::new(expected_schema);
866        let mut expected_arrays = vec![
867            StringArray::from(vec!["a", "a", "b"]),
868            StringArray::from(vec!["c", "c", "d"]),
869        ]
870        .into_iter();
871        while let Some(decoded) = decoder.next().await {
872            let decoded = decoded.unwrap();
873            match decoded.payload {
874                DecodedPayload::None => {}
875                DecodedPayload::Schema(s) => assert_eq!(s, expected_schema),
876                DecodedPayload::RecordBatch(b) => {
877                    assert_eq!(b.schema(), expected_schema);
878                    let expected_array = expected_arrays.next().unwrap();
879                    let actual_array = b.column_by_name("dict").unwrap();
880                    let actual_array = downcast_array::<StringArray>(actual_array);
881
882                    assert_eq!(actual_array, expected_array);
883                }
884            }
885        }
886    }
887
888    #[tokio::test]
889    async fn test_dictionary_resend() {
890        let arr1: DictionaryArray<UInt16Type> = vec!["a", "a", "b"].into_iter().collect();
891        let arr2: DictionaryArray<UInt16Type> = vec!["c", "c", "d"].into_iter().collect();
892
893        let schema = Arc::new(Schema::new(vec![Field::new_dictionary(
894            "dict",
895            DataType::UInt16,
896            DataType::Utf8,
897            false,
898        )]));
899        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
900        let batch2 = RecordBatch::try_new(schema, vec![Arc::new(arr2)]).unwrap();
901
902        verify_flight_round_trip(vec![batch1, batch2]).await;
903    }
904
905    #[tokio::test]
906    async fn test_compression_round_trip() {
907        // Round trip a batch through Flight with IPC body compression enabled. This exercises
908        // the compressed `IpcDataGenerator::encode` path (per-buffer codec output), which the
909        // uncompressed Flight tests and the writer-based compression tests do not cover.
910        let ints = Int32Array::from_iter_values((0..1024).map(|i| i % 8));
911        let strings = StringArray::from_iter_values((0..1024).map(|i| format!("value-{}", i % 8)));
912        let batch = RecordBatch::try_from_iter(vec![
913            ("ints", Arc::new(ints) as ArrayRef),
914            ("strings", Arc::new(strings) as ArrayRef),
915        ])
916        .unwrap();
917
918        for compression in [CompressionType::LZ4_FRAME, CompressionType::ZSTD] {
919            let options = IpcWriteOptions::default()
920                .try_with_compression(Some(compression))
921                .unwrap();
922            verify_flight_round_trip_with_options(vec![batch.clone()], options).await;
923        }
924    }
925
926    #[tokio::test]
927    async fn test_dictionary_hydration_known_schema() {
928        let arr1: DictionaryArray<UInt16Type> = vec!["a", "a", "b"].into_iter().collect();
929        let arr2: DictionaryArray<UInt16Type> = vec!["c", "c", "d"].into_iter().collect();
930
931        let schema = Arc::new(Schema::new(vec![Field::new_dictionary(
932            "dict",
933            DataType::UInt16,
934            DataType::Utf8,
935            false,
936        )]));
937        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
938        let batch2 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr2)]).unwrap();
939
940        let stream = futures::stream::iter(vec![Ok(batch1), Ok(batch2)]);
941
942        let encoder = FlightDataEncoderBuilder::default()
943            .with_schema(schema)
944            .build(stream);
945        let expected_schema =
946            Arc::new(Schema::new(vec![Field::new("dict", DataType::Utf8, false)]));
947        assert_eq!(Some(expected_schema), encoder.known_schema())
948    }
949
950    #[tokio::test]
951    async fn test_dictionary_resend_known_schema() {
952        let arr1: DictionaryArray<UInt16Type> = vec!["a", "a", "b"].into_iter().collect();
953        let arr2: DictionaryArray<UInt16Type> = vec!["c", "c", "d"].into_iter().collect();
954
955        let schema = Arc::new(Schema::new(vec![Field::new_dictionary(
956            "dict",
957            DataType::UInt16,
958            DataType::Utf8,
959            false,
960        )]));
961        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
962        let batch2 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr2)]).unwrap();
963
964        let stream = futures::stream::iter(vec![Ok(batch1), Ok(batch2)]);
965
966        let encoder = FlightDataEncoderBuilder::default()
967            .with_dictionary_handling(DictionaryHandling::Resend)
968            .with_schema(schema.clone())
969            .build(stream);
970        assert_eq!(Some(schema), encoder.known_schema())
971    }
972
973    #[tokio::test]
974    async fn test_multiple_dictionaries_resend() {
975        // Create a schema with two dictionary fields that have the same dict ID
976        let schema = Arc::new(Schema::new(vec![
977            Field::new_dictionary("dict_1", DataType::UInt16, DataType::Utf8, false),
978            Field::new_dictionary("dict_2", DataType::UInt16, DataType::Utf8, false),
979        ]));
980
981        let arr_one_1: Arc<DictionaryArray<UInt16Type>> =
982            Arc::new(vec!["a", "a", "b"].into_iter().collect());
983        let arr_one_2: Arc<DictionaryArray<UInt16Type>> =
984            Arc::new(vec!["c", "c", "d"].into_iter().collect());
985        let arr_two_1: Arc<DictionaryArray<UInt16Type>> =
986            Arc::new(vec!["b", "a", "c"].into_iter().collect());
987        let arr_two_2: Arc<DictionaryArray<UInt16Type>> =
988            Arc::new(vec!["k", "d", "e"].into_iter().collect());
989        let batch1 =
990            RecordBatch::try_new(schema.clone(), vec![arr_one_1.clone(), arr_one_2.clone()])
991                .unwrap();
992        let batch2 =
993            RecordBatch::try_new(schema.clone(), vec![arr_two_1.clone(), arr_two_2.clone()])
994                .unwrap();
995
996        verify_flight_round_trip(vec![batch1, batch2]).await;
997    }
998
999    #[tokio::test]
1000    async fn test_dictionary_list_hydration() {
1001        let mut builder = ListBuilder::new(StringDictionaryBuilder::<UInt16Type>::new());
1002
1003        builder.append_value(vec![Some("a"), None, Some("b")]);
1004
1005        let arr1 = builder.finish();
1006
1007        builder.append_value(vec![Some("c"), None, Some("d")]);
1008
1009        let arr2 = builder.finish();
1010
1011        let schema = Arc::new(Schema::new(vec![Field::new_list(
1012            "dict_list",
1013            Field::new_dictionary("item", DataType::UInt16, DataType::Utf8, true),
1014            true,
1015        )]));
1016
1017        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
1018        let batch2 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr2)]).unwrap();
1019
1020        let stream = futures::stream::iter(vec![Ok(batch1), Ok(batch2)]);
1021
1022        let encoder = FlightDataEncoderBuilder::default().build(stream);
1023
1024        let mut decoder = FlightDataDecoder::new(encoder);
1025        let expected_schema = Schema::new(vec![Field::new_list(
1026            "dict_list",
1027            Field::new_list_field(DataType::Utf8, true),
1028            true,
1029        )]);
1030
1031        let expected_schema = Arc::new(expected_schema);
1032
1033        let mut expected_arrays = vec![
1034            StringArray::from_iter(vec![Some("a"), None, Some("b")]),
1035            StringArray::from_iter(vec![Some("c"), None, Some("d")]),
1036        ]
1037        .into_iter();
1038
1039        while let Some(decoded) = decoder.next().await {
1040            let decoded = decoded.unwrap();
1041            match decoded.payload {
1042                DecodedPayload::None => {}
1043                DecodedPayload::Schema(s) => assert_eq!(s, expected_schema),
1044                DecodedPayload::RecordBatch(b) => {
1045                    assert_eq!(b.schema(), expected_schema);
1046                    let expected_array = expected_arrays.next().unwrap();
1047                    let list_array =
1048                        downcast_array::<ListArray>(b.column_by_name("dict_list").unwrap());
1049                    let elem_array = downcast_array::<StringArray>(list_array.value(0).as_ref());
1050
1051                    assert_eq!(elem_array, expected_array);
1052                }
1053            }
1054        }
1055    }
1056
1057    #[tokio::test]
1058    async fn test_dictionary_list_resend() {
1059        let mut builder = ListBuilder::new(StringDictionaryBuilder::<UInt16Type>::new());
1060
1061        builder.append_value(vec![Some("a"), None, Some("b")]);
1062
1063        let arr1 = builder.finish();
1064
1065        builder.append_value(vec![Some("c"), None, Some("d")]);
1066
1067        let arr2 = builder.finish();
1068
1069        let schema = Arc::new(Schema::new(vec![Field::new_list(
1070            "dict_list",
1071            Field::new_dictionary("item", DataType::UInt16, DataType::Utf8, true),
1072            true,
1073        )]));
1074
1075        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
1076        let batch2 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr2)]).unwrap();
1077
1078        verify_flight_round_trip(vec![batch1, batch2]).await;
1079    }
1080
1081    #[tokio::test]
1082    async fn test_dictionary_struct_hydration() {
1083        let struct_fields = vec![Field::new_list(
1084            "dict_list",
1085            Field::new_dictionary("item", DataType::UInt16, DataType::Utf8, true),
1086            true,
1087        )];
1088
1089        let mut struct_builder = StructBuilder::new(
1090            struct_fields.clone(),
1091            vec![Box::new(builder::ListBuilder::new(
1092                StringDictionaryBuilder::<UInt16Type>::new(),
1093            ))],
1094        );
1095
1096        struct_builder
1097            .field_builder::<ListBuilder<GenericByteDictionaryBuilder<UInt16Type,GenericStringType<i32>>>>(0)
1098            .unwrap()
1099            .append_value(vec![Some("a"), None, Some("b")]);
1100
1101        struct_builder.append(true);
1102
1103        let arr1 = struct_builder.finish();
1104
1105        struct_builder
1106            .field_builder::<ListBuilder<GenericByteDictionaryBuilder<UInt16Type,GenericStringType<i32>>>>(0)
1107            .unwrap()
1108            .append_value(vec![Some("c"), None, Some("d")]);
1109        struct_builder.append(true);
1110
1111        let arr2 = struct_builder.finish();
1112
1113        let schema = Arc::new(Schema::new(vec![Field::new_struct(
1114            "struct",
1115            struct_fields,
1116            true,
1117        )]));
1118
1119        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
1120        let batch2 = RecordBatch::try_new(schema, vec![Arc::new(arr2)]).unwrap();
1121
1122        let stream = futures::stream::iter(vec![Ok(batch1), Ok(batch2)]);
1123
1124        let encoder = FlightDataEncoderBuilder::default().build(stream);
1125
1126        let mut decoder = FlightDataDecoder::new(encoder);
1127        let expected_schema = Schema::new(vec![Field::new_struct(
1128            "struct",
1129            vec![Field::new_list(
1130                "dict_list",
1131                Field::new_list_field(DataType::Utf8, true),
1132                true,
1133            )],
1134            true,
1135        )]);
1136
1137        let expected_schema = Arc::new(expected_schema);
1138
1139        let mut expected_arrays = vec![
1140            StringArray::from_iter(vec![Some("a"), None, Some("b")]),
1141            StringArray::from_iter(vec![Some("c"), None, Some("d")]),
1142        ]
1143        .into_iter();
1144
1145        while let Some(decoded) = decoder.next().await {
1146            let decoded = decoded.unwrap();
1147            match decoded.payload {
1148                DecodedPayload::None => {}
1149                DecodedPayload::Schema(s) => assert_eq!(s, expected_schema),
1150                DecodedPayload::RecordBatch(b) => {
1151                    assert_eq!(b.schema(), expected_schema);
1152                    let expected_array = expected_arrays.next().unwrap();
1153                    let struct_array =
1154                        downcast_array::<StructArray>(b.column_by_name("struct").unwrap());
1155                    let list_array = downcast_array::<ListArray>(struct_array.column(0));
1156
1157                    let elem_array = downcast_array::<StringArray>(list_array.value(0).as_ref());
1158
1159                    assert_eq!(elem_array, expected_array);
1160                }
1161            }
1162        }
1163    }
1164
1165    #[tokio::test]
1166    async fn test_dictionary_struct_resend() {
1167        let struct_fields = vec![Field::new_list(
1168            "dict_list",
1169            Field::new_dictionary("item", DataType::UInt16, DataType::Utf8, true),
1170            true,
1171        )];
1172
1173        let mut struct_builder = StructBuilder::new(
1174            struct_fields.clone(),
1175            vec![Box::new(builder::ListBuilder::new(
1176                StringDictionaryBuilder::<UInt16Type>::new(),
1177            ))],
1178        );
1179
1180        struct_builder.field_builder::<ListBuilder<GenericByteDictionaryBuilder<UInt16Type,GenericStringType<i32>>>>(0)
1181            .unwrap()
1182            .append_value(vec![Some("a"), None, Some("b")]);
1183        struct_builder.append(true);
1184
1185        let arr1 = struct_builder.finish();
1186
1187        struct_builder.field_builder::<ListBuilder<GenericByteDictionaryBuilder<UInt16Type,GenericStringType<i32>>>>(0)
1188            .unwrap()
1189            .append_value(vec![Some("c"), None, Some("d")]);
1190        struct_builder.append(true);
1191
1192        let arr2 = struct_builder.finish();
1193
1194        let schema = Arc::new(Schema::new(vec![Field::new_struct(
1195            "struct",
1196            struct_fields,
1197            true,
1198        )]));
1199
1200        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
1201        let batch2 = RecordBatch::try_new(schema, vec![Arc::new(arr2)]).unwrap();
1202
1203        verify_flight_round_trip(vec![batch1, batch2]).await;
1204    }
1205
1206    #[tokio::test]
1207    async fn test_dictionary_union_hydration() {
1208        let struct_fields = vec![Field::new_list(
1209            "dict_list",
1210            Field::new_dictionary("item", DataType::UInt16, DataType::Utf8, true),
1211            true,
1212        )];
1213
1214        let union_fields = [
1215            (
1216                0,
1217                Arc::new(Field::new_list(
1218                    "dict_list",
1219                    Field::new_dictionary("item", DataType::UInt16, DataType::Utf8, true),
1220                    true,
1221                )),
1222            ),
1223            (
1224                1,
1225                Arc::new(Field::new_struct("struct", struct_fields.clone(), true)),
1226            ),
1227            (2, Arc::new(Field::new("string", DataType::Utf8, true))),
1228        ]
1229        .into_iter()
1230        .collect::<UnionFields>();
1231
1232        let struct_fields = vec![Field::new_list(
1233            "dict_list",
1234            Field::new_dictionary("item", DataType::UInt16, DataType::Utf8, true),
1235            true,
1236        )];
1237
1238        let mut builder = builder::ListBuilder::new(StringDictionaryBuilder::<UInt16Type>::new());
1239
1240        builder.append_value(vec![Some("a"), None, Some("b")]);
1241
1242        let arr1 = builder.finish();
1243
1244        let type_id_buffer = std::iter::once(0).collect::<ScalarBuffer<i8>>();
1245        let arr1 = UnionArray::try_new(
1246            union_fields.clone(),
1247            type_id_buffer,
1248            None,
1249            vec![
1250                Arc::new(arr1) as Arc<dyn Array>,
1251                new_null_array(union_fields.iter().nth(1).unwrap().1.data_type(), 1),
1252                new_null_array(union_fields.iter().nth(2).unwrap().1.data_type(), 1),
1253            ],
1254        )
1255        .unwrap();
1256
1257        builder.append_value(vec![Some("c"), None, Some("d")]);
1258
1259        let arr2 = Arc::new(builder.finish());
1260        let arr2 = StructArray::new(struct_fields.clone().into(), vec![arr2], None);
1261
1262        let type_id_buffer = std::iter::once(1).collect::<ScalarBuffer<i8>>();
1263        let arr2 = UnionArray::try_new(
1264            union_fields.clone(),
1265            type_id_buffer,
1266            None,
1267            vec![
1268                new_null_array(union_fields.iter().next().unwrap().1.data_type(), 1),
1269                Arc::new(arr2),
1270                new_null_array(union_fields.iter().nth(2).unwrap().1.data_type(), 1),
1271            ],
1272        )
1273        .unwrap();
1274
1275        let type_id_buffer = std::iter::once(2).collect::<ScalarBuffer<i8>>();
1276        let arr3 = UnionArray::try_new(
1277            union_fields.clone(),
1278            type_id_buffer,
1279            None,
1280            vec![
1281                new_null_array(union_fields.iter().next().unwrap().1.data_type(), 1),
1282                new_null_array(union_fields.iter().nth(1).unwrap().1.data_type(), 1),
1283                Arc::new(StringArray::from(vec!["e"])),
1284            ],
1285        )
1286        .unwrap();
1287
1288        let (type_ids, union_fields): (Vec<_>, Vec<_>) = union_fields
1289            .iter()
1290            .map(|(type_id, field_ref)| (type_id, (*Arc::clone(field_ref)).clone()))
1291            .unzip();
1292        let schema = Arc::new(Schema::new(vec![Field::new_union(
1293            "union",
1294            type_ids.clone(),
1295            union_fields.clone(),
1296            UnionMode::Sparse,
1297        )]));
1298
1299        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
1300        let batch2 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr2)]).unwrap();
1301        let batch3 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr3)]).unwrap();
1302
1303        let stream = futures::stream::iter(vec![Ok(batch1), Ok(batch2), Ok(batch3)]);
1304
1305        let encoder = FlightDataEncoderBuilder::default().build(stream);
1306
1307        let mut decoder = FlightDataDecoder::new(encoder);
1308
1309        let hydrated_struct_fields = vec![Field::new_list(
1310            "dict_list",
1311            Field::new_list_field(DataType::Utf8, true),
1312            true,
1313        )];
1314
1315        let hydrated_union_fields = vec![
1316            Field::new_list(
1317                "dict_list",
1318                Field::new_list_field(DataType::Utf8, true),
1319                true,
1320            ),
1321            Field::new_struct("struct", hydrated_struct_fields.clone(), true),
1322            Field::new("string", DataType::Utf8, true),
1323        ];
1324
1325        let expected_schema = Schema::new(vec![Field::new_union(
1326            "union",
1327            type_ids.clone(),
1328            hydrated_union_fields,
1329            UnionMode::Sparse,
1330        )]);
1331
1332        let expected_schema = Arc::new(expected_schema);
1333
1334        let mut expected_arrays = vec![
1335            StringArray::from_iter(vec![Some("a"), None, Some("b")]),
1336            StringArray::from_iter(vec![Some("c"), None, Some("d")]),
1337            StringArray::from(vec!["e"]),
1338        ]
1339        .into_iter();
1340
1341        let mut batch = 0;
1342        while let Some(decoded) = decoder.next().await {
1343            let decoded = decoded.unwrap();
1344            match decoded.payload {
1345                DecodedPayload::None => {}
1346                DecodedPayload::Schema(s) => assert_eq!(s, expected_schema),
1347                DecodedPayload::RecordBatch(b) => {
1348                    assert_eq!(b.schema(), expected_schema);
1349                    let expected_array = expected_arrays.next().unwrap();
1350                    let union_arr =
1351                        downcast_array::<UnionArray>(b.column_by_name("union").unwrap());
1352
1353                    let elem_array = match batch {
1354                        0 => {
1355                            let list_array = downcast_array::<ListArray>(union_arr.child(0));
1356                            downcast_array::<StringArray>(list_array.value(0).as_ref())
1357                        }
1358                        1 => {
1359                            let struct_array = downcast_array::<StructArray>(union_arr.child(1));
1360                            let list_array = downcast_array::<ListArray>(struct_array.column(0));
1361
1362                            downcast_array::<StringArray>(list_array.value(0).as_ref())
1363                        }
1364                        _ => downcast_array::<StringArray>(union_arr.child(2)),
1365                    };
1366
1367                    batch += 1;
1368
1369                    assert_eq!(elem_array, expected_array);
1370                }
1371            }
1372        }
1373    }
1374
1375    #[tokio::test]
1376    async fn test_dictionary_union_resend() {
1377        let struct_fields = vec![Field::new_list(
1378            "dict_list",
1379            Field::new_dictionary("item", DataType::UInt16, DataType::Utf8, true),
1380            true,
1381        )];
1382
1383        let union_fields = [
1384            (
1385                0,
1386                Arc::new(Field::new_list(
1387                    "dict_list",
1388                    Field::new_dictionary("item", DataType::UInt16, DataType::Utf8, true),
1389                    true,
1390                )),
1391            ),
1392            (
1393                1,
1394                Arc::new(Field::new_struct("struct", struct_fields.clone(), true)),
1395            ),
1396            (2, Arc::new(Field::new("string", DataType::Utf8, true))),
1397        ]
1398        .into_iter()
1399        .collect::<UnionFields>();
1400
1401        let mut field_types = union_fields.iter().map(|(_, field)| field.data_type());
1402        let dict_list_ty = field_types.next().unwrap();
1403        let struct_ty = field_types.next().unwrap();
1404        let string_ty = field_types.next().unwrap();
1405
1406        let struct_fields = vec![Field::new_list(
1407            "dict_list",
1408            Field::new_dictionary("item", DataType::UInt16, DataType::Utf8, true),
1409            true,
1410        )];
1411
1412        let mut builder = builder::ListBuilder::new(StringDictionaryBuilder::<UInt16Type>::new());
1413
1414        builder.append_value(vec![Some("a"), None, Some("b")]);
1415
1416        let arr1 = builder.finish();
1417
1418        let type_id_buffer = std::iter::once(0).collect::<ScalarBuffer<i8>>();
1419        let arr1 = UnionArray::try_new(
1420            union_fields.clone(),
1421            type_id_buffer,
1422            None,
1423            vec![
1424                Arc::new(arr1),
1425                new_null_array(struct_ty, 1),
1426                new_null_array(string_ty, 1),
1427            ],
1428        )
1429        .unwrap();
1430
1431        builder.append_value(vec![Some("c"), None, Some("d")]);
1432
1433        let arr2 = Arc::new(builder.finish());
1434        let arr2 = StructArray::new(struct_fields.clone().into(), vec![arr2], None);
1435
1436        let type_id_buffer = std::iter::once(1).collect::<ScalarBuffer<i8>>();
1437        let arr2 = UnionArray::try_new(
1438            union_fields.clone(),
1439            type_id_buffer,
1440            None,
1441            vec![
1442                new_null_array(dict_list_ty, 1),
1443                Arc::new(arr2),
1444                new_null_array(string_ty, 1),
1445            ],
1446        )
1447        .unwrap();
1448
1449        let type_id_buffer = std::iter::once(2).collect::<ScalarBuffer<i8>>();
1450        let arr3 = UnionArray::try_new(
1451            union_fields.clone(),
1452            type_id_buffer,
1453            None,
1454            vec![
1455                new_null_array(dict_list_ty, 1),
1456                new_null_array(struct_ty, 1),
1457                Arc::new(StringArray::from(vec!["e"])),
1458            ],
1459        )
1460        .unwrap();
1461
1462        let (type_ids, union_fields): (Vec<_>, Vec<_>) = union_fields
1463            .iter()
1464            .map(|(type_id, field_ref)| (type_id, (*Arc::clone(field_ref)).clone()))
1465            .unzip();
1466        let schema = Arc::new(Schema::new(vec![Field::new_union(
1467            "union",
1468            type_ids.clone(),
1469            union_fields.clone(),
1470            UnionMode::Sparse,
1471        )]));
1472
1473        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
1474        let batch2 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr2)]).unwrap();
1475        let batch3 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr3)]).unwrap();
1476
1477        verify_flight_round_trip(vec![batch1, batch2, batch3]).await;
1478    }
1479
1480    #[tokio::test]
1481    async fn test_dictionary_map_hydration() {
1482        let mut builder = MapBuilder::new(
1483            None,
1484            StringDictionaryBuilder::<UInt16Type>::new(),
1485            StringDictionaryBuilder::<UInt16Type>::new(),
1486        );
1487
1488        // {"k1":"a","k2":null,"k3":"b"}
1489        builder.keys().append_value("k1");
1490        builder.values().append_value("a");
1491        builder.keys().append_value("k2");
1492        builder.values().append_null();
1493        builder.keys().append_value("k3");
1494        builder.values().append_value("b");
1495        builder.append(true).unwrap();
1496
1497        let arr1 = builder.finish();
1498
1499        // {"k1":"c","k2":null,"k3":"d"}
1500        builder.keys().append_value("k1");
1501        builder.values().append_value("c");
1502        builder.keys().append_value("k2");
1503        builder.values().append_null();
1504        builder.keys().append_value("k3");
1505        builder.values().append_value("d");
1506        builder.append(true).unwrap();
1507
1508        let arr2 = builder.finish();
1509
1510        let schema = Arc::new(Schema::new(vec![Field::new_map(
1511            "dict_map",
1512            Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
1513            Field::new_dictionary(
1514                Field::MAP_KEY_FIELD_DEFAULT_NAME,
1515                DataType::UInt16,
1516                DataType::Utf8,
1517                false,
1518            ),
1519            Field::new_dictionary(
1520                Field::MAP_VALUE_FIELD_DEFAULT_NAME,
1521                DataType::UInt16,
1522                DataType::Utf8,
1523                true,
1524            ),
1525            false,
1526            false,
1527        )]));
1528
1529        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
1530        let batch2 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr2)]).unwrap();
1531
1532        let stream = futures::stream::iter(vec![Ok(batch1), Ok(batch2)]);
1533
1534        let encoder = FlightDataEncoderBuilder::default().build(stream);
1535
1536        let mut decoder = FlightDataDecoder::new(encoder);
1537        let expected_schema = Schema::new(vec![Field::new_map(
1538            "dict_map",
1539            Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
1540            Field::new(Field::MAP_KEY_FIELD_DEFAULT_NAME, DataType::Utf8, false),
1541            Field::new(Field::MAP_VALUE_FIELD_DEFAULT_NAME, DataType::Utf8, true),
1542            false,
1543            false,
1544        )]);
1545
1546        let expected_schema = Arc::new(expected_schema);
1547
1548        // array without dictionary fields
1549        let arr1 = MapArray::from_vec_of_maps::<StringArray, StringArray, _, _>(
1550            vec![Some(vec![
1551                ("k1", Some("a")),
1552                ("k2", None),
1553                ("k3", Some("b")),
1554            ])],
1555            false,
1556        );
1557
1558        let arr2 = MapArray::from_vec_of_maps::<StringArray, StringArray, _, _>(
1559            vec![Some(vec![
1560                ("k1", Some("c")),
1561                ("k2", None),
1562                ("k3", Some("d")),
1563            ])],
1564            false,
1565        );
1566
1567        let mut expected_arrays = vec![arr1, arr2].into_iter();
1568
1569        while let Some(decoded) = decoder.next().await {
1570            let decoded = decoded.unwrap();
1571            match decoded.payload {
1572                DecodedPayload::None => {}
1573                DecodedPayload::Schema(s) => assert_eq!(s, expected_schema),
1574                DecodedPayload::RecordBatch(b) => {
1575                    assert_eq!(b.schema(), expected_schema);
1576                    let expected_array = expected_arrays.next().unwrap();
1577                    let map_array =
1578                        downcast_array::<MapArray>(b.column_by_name("dict_map").unwrap());
1579
1580                    assert_eq!(map_array, expected_array);
1581                }
1582            }
1583        }
1584    }
1585
1586    #[tokio::test]
1587    async fn test_dictionary_map_resend() {
1588        let mut builder = MapBuilder::new(
1589            None,
1590            StringDictionaryBuilder::<UInt16Type>::new(),
1591            StringDictionaryBuilder::<UInt16Type>::new(),
1592        );
1593
1594        // {"k1":"a","k2":null,"k3":"b"}
1595        builder.keys().append_value("k1");
1596        builder.values().append_value("a");
1597        builder.keys().append_value("k2");
1598        builder.values().append_null();
1599        builder.keys().append_value("k3");
1600        builder.values().append_value("b");
1601        builder.append(true).unwrap();
1602
1603        let arr1 = builder.finish();
1604
1605        // {"k1":"c","k2":null,"k3":"d"}
1606        builder.keys().append_value("k1");
1607        builder.values().append_value("c");
1608        builder.keys().append_value("k2");
1609        builder.values().append_null();
1610        builder.keys().append_value("k3");
1611        builder.values().append_value("d");
1612        builder.append(true).unwrap();
1613
1614        let arr2 = builder.finish();
1615
1616        let schema = Arc::new(Schema::new(vec![Field::new_map(
1617            "dict_map",
1618            Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
1619            Field::new_dictionary(
1620                Field::MAP_KEY_FIELD_DEFAULT_NAME,
1621                DataType::UInt16,
1622                DataType::Utf8,
1623                false,
1624            ),
1625            Field::new_dictionary(
1626                Field::MAP_VALUE_FIELD_DEFAULT_NAME,
1627                DataType::UInt16,
1628                DataType::Utf8,
1629                true,
1630            ),
1631            false,
1632            false,
1633        )]));
1634
1635        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
1636        let batch2 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr2)]).unwrap();
1637
1638        verify_flight_round_trip(vec![batch1, batch2]).await;
1639    }
1640
1641    #[tokio::test]
1642    async fn test_dictionary_ree_resend() {
1643        let dict_values1 = vec![Some("a"), None, Some("b")]
1644            .into_iter()
1645            .collect::<DictionaryArray<Int32Type>>();
1646        let run_ends1 = Int32Array::from(vec![1, 2, 3]);
1647        let arr1 = RunArray::try_new(&run_ends1, &dict_values1).unwrap();
1648
1649        let dict_values2 = vec![Some("c"), Some("a")]
1650            .into_iter()
1651            .collect::<DictionaryArray<Int32Type>>();
1652        let run_ends2 = Int32Array::from(vec![1, 2]);
1653        let arr2 = RunArray::try_new(&run_ends2, &dict_values2).unwrap();
1654
1655        let schema = Arc::new(Schema::new(vec![Field::new(
1656            "ree",
1657            arr1.data_type().clone(),
1658            true,
1659        )]));
1660
1661        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
1662        let batch2 = RecordBatch::try_new(schema, vec![Arc::new(arr2)]).unwrap();
1663
1664        verify_flight_round_trip(vec![batch1, batch2]).await;
1665    }
1666
1667    #[tokio::test]
1668    async fn test_dictionary_of_struct_of_dict_resend() {
1669        // Dict(Int8, Struct { dict: Dict(Int32, Utf8), int: Int32 })
1670        // This exercises the Dictionary branch recursing into its value type
1671        // before assigning its own dict_id (depth-first ordering).
1672        let struct_fields: Vec<Field> = vec![
1673            Field::new_dictionary("dict", DataType::Int32, DataType::Utf8, true),
1674            Field::new("int", DataType::Int32, false),
1675        ];
1676
1677        let inner_values =
1678            StringArray::from(vec![Some("alpha"), None, Some("beta"), Some("gamma")]);
1679        let inner_keys = Int32Array::from_iter_values([0, 1, 2, 3, 0]);
1680        let inner_dict = DictionaryArray::new(inner_keys, Arc::new(inner_values));
1681        let int_array = Int32Array::from(vec![10, 20, 30, 40, 50]);
1682
1683        let struct_array = StructArray::from(vec![
1684            (
1685                Arc::new(struct_fields[0].clone()),
1686                Arc::new(inner_dict) as ArrayRef,
1687            ),
1688            (
1689                Arc::new(struct_fields[1].clone()),
1690                Arc::new(int_array) as ArrayRef,
1691            ),
1692        ]);
1693
1694        let outer_keys = Int8Array::from_iter_values([0, 0, 1, 2]);
1695        let arr1 = DictionaryArray::new(outer_keys, Arc::new(struct_array));
1696
1697        let inner_values2 = StringArray::from(vec![Some("x"), Some("y")]);
1698        let inner_keys2 = Int32Array::from_iter_values([0, 1, 0]);
1699        let inner_dict2 = DictionaryArray::new(inner_keys2, Arc::new(inner_values2));
1700        let int_array2 = Int32Array::from(vec![100, 200, 300]);
1701
1702        let struct_array2 = StructArray::from(vec![
1703            (
1704                Arc::new(struct_fields[0].clone()),
1705                Arc::new(inner_dict2) as ArrayRef,
1706            ),
1707            (
1708                Arc::new(struct_fields[1].clone()),
1709                Arc::new(int_array2) as ArrayRef,
1710            ),
1711        ]);
1712
1713        let outer_keys2 = Int8Array::from_iter_values([0, 1]);
1714        let arr2 = DictionaryArray::new(outer_keys2, Arc::new(struct_array2));
1715
1716        let schema = Arc::new(Schema::new(vec![Field::new(
1717            "dict_struct",
1718            arr1.data_type().clone(),
1719            false,
1720        )]));
1721
1722        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
1723        let batch2 = RecordBatch::try_new(schema, vec![Arc::new(arr2)]).unwrap();
1724
1725        verify_flight_round_trip(vec![batch1, batch2]).await;
1726    }
1727
1728    async fn verify_dictionary_list_view_resend<O: OffsetSizeTrait>() {
1729        let mut builder =
1730            GenericListViewBuilder::<O, _>::new(StringDictionaryBuilder::<UInt16Type>::new());
1731
1732        builder.append_value(vec![Some("a"), None, Some("b")]);
1733        let arr1 = builder.finish();
1734
1735        builder.append_value(vec![Some("c"), None, Some("d")]);
1736        let arr2 = builder.finish();
1737
1738        let inner = Arc::new(Field::new_dictionary(
1739            "item",
1740            DataType::UInt16,
1741            DataType::Utf8,
1742            true,
1743        ));
1744        let dt = if O::IS_LARGE {
1745            DataType::LargeListView(inner)
1746        } else {
1747            DataType::ListView(inner)
1748        };
1749        let schema = Arc::new(Schema::new(vec![Field::new("dict_list_view", dt, true)]));
1750
1751        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
1752        let batch2 = RecordBatch::try_new(schema, vec![Arc::new(arr2)]).unwrap();
1753
1754        verify_flight_round_trip(vec![batch1, batch2]).await;
1755    }
1756
1757    #[tokio::test]
1758    async fn test_dictionary_list_view_resend() {
1759        verify_dictionary_list_view_resend::<i32>().await;
1760    }
1761
1762    #[tokio::test]
1763    async fn test_dictionary_large_list_view_resend() {
1764        verify_dictionary_list_view_resend::<i64>().await;
1765    }
1766
1767    #[tokio::test]
1768    async fn test_dictionary_fixed_size_list_resend() {
1769        let mut builder =
1770            FixedSizeListBuilder::new(StringDictionaryBuilder::<UInt16Type>::new(), 2);
1771
1772        builder.values().append_value("a");
1773        builder.values().append_value("b");
1774        builder.append(true);
1775        let arr1 = builder.finish();
1776
1777        builder.values().append_value("c");
1778        builder.values().append_value("d");
1779        builder.append(true);
1780        let arr2 = builder.finish();
1781
1782        let schema = Arc::new(Schema::new(vec![Field::new_fixed_size_list(
1783            "dict_fsl",
1784            Field::new_dictionary("item", DataType::UInt16, DataType::Utf8, true),
1785            2,
1786            true,
1787        )]));
1788
1789        let batch1 = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr1)]).unwrap();
1790        let batch2 = RecordBatch::try_new(schema, vec![Arc::new(arr2)]).unwrap();
1791
1792        verify_flight_round_trip(vec![batch1, batch2]).await;
1793    }
1794
1795    async fn verify_flight_round_trip(batches: Vec<RecordBatch>) {
1796        verify_flight_round_trip_with_options(batches, IpcWriteOptions::default()).await;
1797    }
1798
1799    /// Encode `batches` through a [`FlightDataEncoderBuilder`] using `options`, decode them
1800    /// again, and assert the decoded batches match the originals.
1801    async fn verify_flight_round_trip_with_options(
1802        batches: Vec<RecordBatch>,
1803        options: IpcWriteOptions,
1804    ) {
1805        let expected_schema = batches.first().unwrap().schema();
1806
1807        let encoder = FlightDataEncoderBuilder::default()
1808            .with_options(options)
1809            .with_dictionary_handling(DictionaryHandling::Resend)
1810            .build(futures::stream::iter(batches.clone().into_iter().map(Ok)));
1811
1812        let mut expected_batches = batches.into_iter();
1813
1814        let mut decoder = FlightDataDecoder::new(encoder);
1815        while let Some(decoded) = decoder.next().await {
1816            let decoded = decoded.unwrap();
1817            match decoded.payload {
1818                DecodedPayload::None => {}
1819                DecodedPayload::Schema(s) => assert_eq!(s, expected_schema),
1820                DecodedPayload::RecordBatch(b) => {
1821                    let expected_batch = expected_batches.next().unwrap();
1822                    assert_eq!(b, expected_batch);
1823                }
1824            }
1825        }
1826    }
1827
1828    #[test]
1829    fn test_schema_metadata_encoded() {
1830        let schema = Schema::new(vec![Field::new("data", DataType::Int32, false)]).with_metadata(
1831            HashMap::from([("some_key".to_owned(), "some_value".to_owned())]),
1832        );
1833
1834        let mut dictionary_tracker = DictionaryTracker::new(false);
1835
1836        let got = prepare_schema_for_flight(&schema, &mut dictionary_tracker, false);
1837        assert!(got.metadata().contains_key("some_key"));
1838    }
1839
1840    #[tokio::test]
1841    async fn test_list_schema_round_trip() {
1842        let list_item = Field::new("list_item", DataType::Int32, false).with_metadata(
1843            HashMap::from([("level".to_owned(), "list_item".to_owned())]),
1844        );
1845        let mut list_builder = ListBuilder::new(Int32Builder::new()).with_field(list_item);
1846        list_builder.append_value([Some(1), Some(2)]);
1847        list_builder.append_value([Some(3)]);
1848        let list = Arc::new(list_builder.finish()) as ArrayRef;
1849        let list_field = Field::new("list", list.data_type().clone(), false)
1850            .with_metadata(HashMap::from([("level".to_owned(), "list".to_owned())]));
1851
1852        let nested_item = Field::new("nested_item", DataType::Int32, true).with_metadata(
1853            HashMap::from([("level".to_owned(), "nested_item".to_owned())]),
1854        );
1855        let nested_list_builder =
1856            ListBuilder::new(Int32Builder::new()).with_field(nested_item.clone());
1857        let nested_list = Field::new_list("nested_list", nested_item, false).with_metadata(
1858            HashMap::from([("level".to_owned(), "nested_list".to_owned())]),
1859        );
1860        let mut large_list_builder =
1861            LargeListBuilder::new(nested_list_builder).with_field(nested_list);
1862        large_list_builder.values().append_value([Some(4), None]);
1863        large_list_builder.values().append_value([Some(5)]);
1864        large_list_builder.append(true);
1865        large_list_builder.append(false);
1866        let large_list = Arc::new(large_list_builder.finish()) as ArrayRef;
1867        let large_list_field =
1868            Field::new("large_list", large_list.data_type().clone(), true).with_metadata(
1869                HashMap::from([("level".to_owned(), "large_list".to_owned())]),
1870            );
1871
1872        let fixed_size_item = Field::new("fixed_size_item", DataType::Int32, true).with_metadata(
1873            HashMap::from([("level".to_owned(), "fixed_size_item".to_owned())]),
1874        );
1875        let mut fixed_size_list_builder =
1876            FixedSizeListBuilder::new(Int32Builder::new(), 2).with_field(fixed_size_item);
1877        fixed_size_list_builder.values().append_value(6);
1878        fixed_size_list_builder.values().append_null();
1879        fixed_size_list_builder.append(true);
1880        fixed_size_list_builder.values().append_value(7);
1881        fixed_size_list_builder.values().append_value(8);
1882        fixed_size_list_builder.append(true);
1883        let fixed_size_list = Arc::new(fixed_size_list_builder.finish()) as ArrayRef;
1884        let fixed_size_list_field = Field::new(
1885            "fixed_size_list",
1886            fixed_size_list.data_type().clone(),
1887            false,
1888        )
1889        .with_metadata(HashMap::from([(
1890            "level".to_owned(),
1891            "fixed_size_list".to_owned(),
1892        )]));
1893
1894        let schema = Arc::new(
1895            Schema::new(vec![list_field, large_list_field, fixed_size_list_field])
1896                .with_metadata(HashMap::from([("level".to_owned(), "schema".to_owned())])),
1897        );
1898        let batch = RecordBatch::try_new(schema, vec![list, large_list, fixed_size_list]).unwrap();
1899
1900        verify_flight_round_trip(vec![batch]).await;
1901    }
1902
1903    #[test]
1904    fn test_encode_no_column_batch() {
1905        let batch = RecordBatch::try_new_with_options(
1906            Arc::new(Schema::empty()),
1907            vec![],
1908            &RecordBatchOptions::new().with_row_count(Some(10)),
1909        )
1910        .expect("cannot create record batch");
1911
1912        hydrate_dictionaries(&batch, batch.schema()).expect("failed to optimize");
1913    }
1914
1915    fn make_flight_data(
1916        batch: &RecordBatch,
1917        options: &IpcWriteOptions,
1918    ) -> (Vec<FlightData>, FlightData) {
1919        flight_data_from_arrow_batch(batch, options)
1920    }
1921
1922    fn flight_data_from_arrow_batch(
1923        batch: &RecordBatch,
1924        options: &IpcWriteOptions,
1925    ) -> (Vec<FlightData>, FlightData) {
1926        let data_gen = IpcDataGenerator::default();
1927        let mut dictionary_tracker = DictionaryTracker::new(false);
1928        let mut ipc_write_context = IpcWriteContext::default();
1929
1930        let (encoded_dictionaries, encoded_batch) = data_gen
1931            .encode(
1932                batch,
1933                &mut dictionary_tracker,
1934                options,
1935                &mut ipc_write_context,
1936            )
1937            .expect("DictionaryTracker configured above to not error on replacement");
1938
1939        let flight_dictionaries = encoded_dictionaries.into_iter().map(Into::into).collect();
1940        let flight_batch = encoded_batch.into();
1941
1942        (flight_dictionaries, flight_batch)
1943    }
1944
1945    #[test]
1946    fn test_split_batch_for_grpc_response() {
1947        let max_flight_data_size = 1024;
1948
1949        // no split
1950        let c = UInt32Array::from(vec![1, 2, 3, 4, 5, 6]);
1951        let batch = RecordBatch::try_from_iter(vec![("a", Arc::new(c) as ArrayRef)])
1952            .expect("cannot create record batch");
1953        let split: Vec<_> = split_batch_for_grpc_response(batch.clone(), max_flight_data_size);
1954        assert_eq!(split.len(), 1);
1955        assert_eq!(batch, split[0]);
1956
1957        // split once
1958        let n_rows = max_flight_data_size + 1;
1959        assert!(n_rows % 2 == 1, "should be an odd number");
1960        let c = UInt8Array::from((0..n_rows).map(|i| (i % 256) as u8).collect::<Vec<_>>());
1961        let batch = RecordBatch::try_from_iter(vec![("a", Arc::new(c) as ArrayRef)])
1962            .expect("cannot create record batch");
1963        let split: Vec<_> = split_batch_for_grpc_response(batch.clone(), max_flight_data_size);
1964        assert_eq!(split.len(), 3);
1965        assert_eq!(
1966            split.iter().map(|batch| batch.num_rows()).sum::<usize>(),
1967            n_rows
1968        );
1969        let a = pretty_format_batches(&split).unwrap().to_string();
1970        let b = pretty_format_batches(&[batch]).unwrap().to_string();
1971        assert_eq!(a, b);
1972    }
1973
1974    #[test]
1975    fn test_split_batch_for_grpc_response_sizes() {
1976        // 2000 8 byte entries into 2k pieces: 8 chunks of 250 rows
1977        verify_split(2000, 2 * 1024, vec![250, 250, 250, 250, 250, 250, 250, 250]);
1978
1979        // 2000 8 byte entries into 4k pieces: 4 chunks of 500 rows
1980        verify_split(2000, 4 * 1024, vec![500, 500, 500, 500]);
1981
1982        // 2023 8 byte entries into 3k pieces does not divide evenly
1983        verify_split(2023, 3 * 1024, vec![337, 337, 337, 337, 337, 337, 1]);
1984
1985        // 10 8 byte entries into 1 byte pieces means each rows gets its own
1986        verify_split(10, 1, vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
1987
1988        // 10 8 byte entries into 1k byte pieces means one piece
1989        verify_split(10, 1024, vec![10]);
1990    }
1991
1992    /// Creates a UInt64Array of 8 byte integers with input_rows rows
1993    /// `max_flight_data_size_bytes` pieces and verifies the row counts in
1994    /// those pieces
1995    fn verify_split(
1996        num_input_rows: u64,
1997        max_flight_data_size_bytes: usize,
1998        expected_sizes: Vec<usize>,
1999    ) {
2000        let array: UInt64Array = (0..num_input_rows).collect();
2001
2002        let batch = RecordBatch::try_from_iter(vec![("a", Arc::new(array) as ArrayRef)])
2003            .expect("cannot create record batch");
2004
2005        let input_rows = batch.num_rows();
2006
2007        let split: Vec<_> =
2008            split_batch_for_grpc_response(batch.clone(), max_flight_data_size_bytes);
2009        let sizes: Vec<_> = split.iter().map(RecordBatch::num_rows).collect();
2010        let output_rows: usize = sizes.iter().sum();
2011
2012        assert_eq!(sizes, expected_sizes, "mismatch for {batch:?}");
2013        assert_eq!(input_rows, output_rows, "mismatch for {batch:?}");
2014    }
2015
2016    // test sending record batches
2017    // test sending record batches with multiple different dictionaries
2018
2019    #[tokio::test]
2020    async fn flight_data_size_even() {
2021        let s1 = StringArray::from_iter_values(std::iter::repeat_n(".10 bytes.", 1024));
2022        let i1 = Int16Array::from_iter_values(0..1024);
2023        let s2 = StringArray::from_iter_values(std::iter::repeat_n("6bytes", 1024));
2024        let i2 = Int64Array::from_iter_values(0..1024);
2025
2026        let batch = RecordBatch::try_from_iter(vec![
2027            ("s1", Arc::new(s1) as _),
2028            ("i1", Arc::new(i1) as _),
2029            ("s2", Arc::new(s2) as _),
2030            ("i2", Arc::new(i2) as _),
2031        ])
2032        .unwrap();
2033
2034        verify_encoded_split(batch, 88).await;
2035    }
2036
2037    #[tokio::test]
2038    async fn flight_data_size_uneven_variable_lengths() {
2039        // each row has a longer string than the last with increasing lengths 0 --> 1024
2040        let array = StringArray::from_iter_values((0..1024).map(|i| "*".repeat(i)));
2041        let batch = RecordBatch::try_from_iter(vec![("data", Arc::new(array) as _)]).unwrap();
2042
2043        // overage is much higher than ideal
2044        // https://github.com/apache/arrow-rs/issues/3478
2045        verify_encoded_split(batch, 4304).await;
2046    }
2047
2048    #[tokio::test]
2049    async fn flight_data_size_large_row() {
2050        // batch with individual that can each exceed the batch size
2051        let array1 = StringArray::from_iter_values(vec![
2052            "*".repeat(500),
2053            "*".repeat(500),
2054            "*".repeat(500),
2055            "*".repeat(500),
2056        ]);
2057        let array2 = StringArray::from_iter_values(vec![
2058            "*".to_string(),
2059            "*".repeat(1000),
2060            "*".repeat(2000),
2061            "*".repeat(4000),
2062        ]);
2063
2064        let array3 = StringArray::from_iter_values(vec![
2065            "*".to_string(),
2066            "*".to_string(),
2067            "*".repeat(1000),
2068            "*".repeat(2000),
2069        ]);
2070
2071        let batch = RecordBatch::try_from_iter(vec![
2072            ("a1", Arc::new(array1) as _),
2073            ("a2", Arc::new(array2) as _),
2074            ("a3", Arc::new(array3) as _),
2075        ])
2076        .unwrap();
2077
2078        // 5k over limit (which is 2x larger than limit of 5k)
2079        // overage is much higher than ideal
2080        // https://github.com/apache/arrow-rs/issues/3478
2081        verify_encoded_split(batch, 5784).await;
2082    }
2083
2084    #[tokio::test]
2085    async fn flight_data_size_string_dictionary() {
2086        // Small dictionary (only 2 distinct values ==> 2 entries in dictionary)
2087        let array: DictionaryArray<Int32Type> = (1..1024)
2088            .map(|i| match i % 3 {
2089                0 => Some("value0"),
2090                1 => Some("value1"),
2091                _ => None,
2092            })
2093            .collect();
2094
2095        let batch = RecordBatch::try_from_iter(vec![("a1", Arc::new(array) as _)]).unwrap();
2096
2097        verify_encoded_split(batch, 56).await;
2098    }
2099
2100    #[tokio::test]
2101    async fn flight_data_size_large_dictionary() {
2102        // large dictionary (all distinct values ==> 1024 entries in dictionary)
2103        let values: Vec<_> = (1..1024).map(|i| "**".repeat(i)).collect();
2104
2105        let array: DictionaryArray<Int32Type> = values.iter().map(|s| Some(s.as_str())).collect();
2106
2107        let batch = RecordBatch::try_from_iter(vec![("a1", Arc::new(array) as _)]).unwrap();
2108
2109        // overage is much higher than ideal
2110        // https://github.com/apache/arrow-rs/issues/3478
2111        verify_encoded_split(batch, 3328).await;
2112    }
2113
2114    #[tokio::test]
2115    async fn flight_data_size_large_dictionary_repeated_non_uniform() {
2116        // large dictionary (1024 distinct values) that are used throughout the array
2117        let values = StringArray::from_iter_values((0..1024).map(|i| "******".repeat(i)));
2118        let keys = Int32Array::from_iter_values((0..3000).map(|i| (3000 - i) % 1024));
2119        let array = DictionaryArray::new(keys, Arc::new(values));
2120
2121        let batch = RecordBatch::try_from_iter(vec![("a1", Arc::new(array) as _)]).unwrap();
2122
2123        // overage is much higher than ideal
2124        // https://github.com/apache/arrow-rs/issues/3478
2125        verify_encoded_split(batch, 5280).await;
2126    }
2127
2128    #[tokio::test]
2129    async fn flight_data_size_multiple_dictionaries() {
2130        // high cardinality
2131        let values1: Vec<_> = (1..1024).map(|i| "**".repeat(i)).collect();
2132        // highish cardinality
2133        let values2: Vec<_> = (1..1024).map(|i| "**".repeat(i % 10)).collect();
2134        // medium cardinality
2135        let values3: Vec<_> = (1..1024).map(|i| "**".repeat(i % 100)).collect();
2136
2137        let array1: DictionaryArray<Int32Type> = values1.iter().map(|s| Some(s.as_str())).collect();
2138        let array2: DictionaryArray<Int32Type> = values2.iter().map(|s| Some(s.as_str())).collect();
2139        let array3: DictionaryArray<Int32Type> = values3.iter().map(|s| Some(s.as_str())).collect();
2140
2141        let batch = RecordBatch::try_from_iter(vec![
2142            ("a1", Arc::new(array1) as _),
2143            ("a2", Arc::new(array2) as _),
2144            ("a3", Arc::new(array3) as _),
2145        ])
2146        .unwrap();
2147
2148        // overage is much higher than ideal
2149        // https://github.com/apache/arrow-rs/issues/3478
2150        verify_encoded_split(batch, 4112).await;
2151    }
2152
2153    /// Return size, in memory of flight data
2154    fn flight_data_size(d: &FlightData) -> usize {
2155        let flight_descriptor_size = d
2156            .flight_descriptor
2157            .as_ref()
2158            .map(|descriptor| {
2159                let path_len: usize = descriptor.path.iter().map(|p| p.len()).sum();
2160
2161                std::mem::size_of_val(descriptor) + descriptor.cmd.len() + path_len
2162            })
2163            .unwrap_or(0);
2164
2165        flight_descriptor_size + d.app_metadata.len() + d.data_body.len() + d.data_header.len()
2166    }
2167
2168    /// Coverage for <https://github.com/apache/arrow-rs/issues/3478>
2169    ///
2170    /// Encodes the specified batch using several values of
2171    /// `max_flight_data_size` between 1K to 5K and ensures that the
2172    /// resulting size of the flight data stays within the limit
2173    /// + `allowed_overage`
2174    ///
2175    /// `allowed_overage` is how far off the actual data encoding is
2176    /// from the target limit that was set. It is an improvement when
2177    /// the allowed_overage decreses.
2178    ///
2179    /// Note this overhead will likely always be greater than zero to
2180    /// account for encoding overhead such as IPC headers and padding.
2181    ///
2182    ///
2183    async fn verify_encoded_split(batch: RecordBatch, allowed_overage: usize) {
2184        let num_rows = batch.num_rows();
2185
2186        // Track the overall required maximum overage
2187        let mut max_overage_seen = 0;
2188
2189        for max_flight_data_size in [1024, 2021, 5000] {
2190            println!("Encoding {num_rows} with a maximum size of {max_flight_data_size}");
2191
2192            let mut stream = FlightDataEncoderBuilder::new()
2193                .with_max_flight_data_size(max_flight_data_size)
2194                // use 8-byte alignment - default alignment is 64 which produces bigger ipc data
2195                .with_options(IpcWriteOptions::try_new(8, false, MetadataVersion::V5).unwrap())
2196                .build(futures::stream::iter([Ok(batch.clone())]));
2197
2198            let mut i = 0;
2199            while let Some(data) = stream.next().await.transpose().unwrap() {
2200                let actual_data_size = flight_data_size(&data);
2201
2202                let actual_overage = actual_data_size.saturating_sub(max_flight_data_size);
2203
2204                assert!(
2205                    actual_overage <= allowed_overage,
2206                    "encoded data[{i}]: actual size {actual_data_size}, \
2207                         actual_overage: {actual_overage} \
2208                         allowed_overage: {allowed_overage}"
2209                );
2210
2211                i += 1;
2212
2213                max_overage_seen = max_overage_seen.max(actual_overage)
2214            }
2215        }
2216
2217        // ensure that the specified overage is exactly the maximum so
2218        // that when the splitting logic improves, the tests must be
2219        // updated to reflect the better logic
2220        assert_eq!(
2221            allowed_overage, max_overage_seen,
2222            "Specified overage was too high"
2223        );
2224    }
2225}