Skip to main content

arrow_flight/
encode.rs

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