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