Skip to main content

arrow_flight/sql/
client.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! A FlightSQL Client [`FlightSqlServiceClient`]
19
20use arrow_buffer::Buffer;
21use arrow_ipc::MessageHeader;
22use arrow_ipc::convert::try_fb_to_schema;
23use arrow_ipc::reader::read_record_batch;
24use arrow_ipc::root_as_message;
25use arrow_schema::SchemaRef;
26use base64::Engine;
27use base64::prelude::BASE64_STANDARD;
28use bytes::Bytes;
29use std::collections::HashMap;
30use std::str::FromStr;
31use tonic::metadata::AsciiMetadataKey;
32
33use crate::decode::FlightRecordBatchStream;
34use crate::encode::FlightDataEncoderBuilder;
35use crate::error::FlightError;
36use crate::error::Result;
37use crate::flight_service_client::FlightServiceClient;
38use crate::sql::r#gen::action_end_transaction_request::EndTransaction;
39use crate::sql::server::{
40    BEGIN_TRANSACTION, CLOSE_PREPARED_STATEMENT, CREATE_PREPARED_STATEMENT, END_TRANSACTION,
41};
42use crate::sql::{
43    ActionBeginTransactionRequest, ActionBeginTransactionResult,
44    ActionClosePreparedStatementRequest, ActionCreatePreparedStatementRequest,
45    ActionCreatePreparedStatementResult, ActionEndTransactionRequest, Any, CommandGetCatalogs,
46    CommandGetCrossReference, CommandGetDbSchemas, CommandGetExportedKeys, CommandGetImportedKeys,
47    CommandGetPrimaryKeys, CommandGetSqlInfo, CommandGetTableTypes, CommandGetTables,
48    CommandGetXdbcTypeInfo, CommandPreparedStatementQuery, CommandPreparedStatementUpdate,
49    CommandStatementIngest, CommandStatementQuery, CommandStatementUpdate,
50    DoPutPreparedStatementResult, DoPutUpdateResult, ProstMessageExt, SqlInfo,
51};
52use crate::streams::FallibleRequestStream;
53use crate::trailers::extract_lazy_trailers;
54use crate::{
55    Action, FlightData, FlightDescriptor, FlightInfo, HandshakeRequest, HandshakeResponse,
56    IpcMessage, PutResult, Ticket,
57};
58use arrow_array::RecordBatch;
59use arrow_schema::{ArrowError, Schema};
60use futures::{Stream, TryStreamExt, stream};
61use prost::Message;
62use tonic::codegen::{Body, StdError};
63use tonic::{IntoRequest, IntoStreamingRequest, Streaming};
64
65/// A FlightSQLServiceClient is an endpoint for retrieving or storing Arrow data
66/// by FlightSQL protocol.
67#[derive(Debug)]
68pub struct FlightSqlServiceClient<T> {
69    token: Option<String>,
70    headers: HashMap<String, String>,
71    flight_client: FlightServiceClient<T>,
72}
73
74/// A FlightSql protocol client that can run queries against FlightSql servers
75/// This client is in the "experimental" stage. It is not guaranteed to follow the spec in all instances.
76/// Github issues are welcomed.
77impl<T> FlightSqlServiceClient<T>
78where
79    T: tonic::client::GrpcService<tonic::body::Body>,
80    T::Error: Into<StdError>,
81    T::ResponseBody: Body<Data = Bytes> + Send + 'static,
82    <T::ResponseBody as Body>::Error: Into<StdError> + Send,
83{
84    /// Creates a new FlightSql client that connects to a server over an arbitrary tonic `Channel`
85    pub fn new(channel: T) -> Self {
86        Self::new_from_inner(FlightServiceClient::new(channel))
87    }
88
89    /// Creates a new higher level client with the provided lower level client
90    pub fn new_from_inner(inner: FlightServiceClient<T>) -> Self {
91        Self {
92            token: None,
93            flight_client: inner,
94            headers: HashMap::default(),
95        }
96    }
97
98    /// Return a reference to the underlying [`FlightServiceClient`]
99    pub fn inner(&self) -> &FlightServiceClient<T> {
100        &self.flight_client
101    }
102
103    /// Return a mutable reference to the underlying [`FlightServiceClient`]
104    pub fn inner_mut(&mut self) -> &mut FlightServiceClient<T> {
105        &mut self.flight_client
106    }
107
108    /// Consume this client and return the underlying [`FlightServiceClient`]
109    pub fn into_inner(self) -> FlightServiceClient<T> {
110        self.flight_client
111    }
112
113    /// Set auth token to the given value.
114    pub fn set_token(&mut self, token: String) {
115        self.token = Some(token);
116    }
117
118    /// Clear the auth token.
119    pub fn clear_token(&mut self) {
120        self.token = None;
121    }
122
123    /// Share the bearer token with potentially different `DoGet` clients
124    pub fn token(&self) -> Option<&String> {
125        self.token.as_ref()
126    }
127
128    /// Set header value.
129    pub fn set_header(&mut self, key: impl Into<String>, value: impl Into<String>) {
130        let key: String = key.into();
131        let value: String = value.into();
132        self.headers.insert(key, value);
133    }
134
135    async fn get_flight_info_for_command<M: ProstMessageExt>(
136        &mut self,
137        cmd: M,
138    ) -> Result<FlightInfo> {
139        let descriptor = FlightDescriptor::new_cmd(cmd.as_any().encode_to_vec());
140        let req = self.set_request_headers(descriptor.into_request())?;
141        let fi = self.flight_client.get_flight_info(req).await?.into_inner();
142        Ok(fi)
143    }
144
145    /// Execute a query on the server.
146    pub async fn execute(
147        &mut self,
148        query: String,
149        transaction_id: Option<Bytes>,
150    ) -> Result<FlightInfo> {
151        let cmd = CommandStatementQuery {
152            query,
153            transaction_id,
154        };
155        self.get_flight_info_for_command(cmd).await
156    }
157
158    /// Perform a `handshake` with the server, passing credentials and establishing a session.
159    ///
160    /// If the server returns an "authorization" header, it is automatically parsed and set as
161    /// a token for future requests. Any other data returned by the server in the handshake
162    /// response is returned as a binary blob.
163    pub async fn handshake(&mut self, username: &str, password: &str) -> Result<Bytes> {
164        let cmd = HandshakeRequest {
165            protocol_version: 0,
166            payload: Default::default(),
167        };
168        let mut req = tonic::Request::new(stream::iter(vec![cmd]));
169        let val = BASE64_STANDARD.encode(format!("{username}:{password}"));
170        let val = format!("Basic {val}")
171            .parse()
172            .map_err(|_| ArrowError::ParseError("Cannot parse header".to_string()))?;
173        req.metadata_mut().insert("authorization", val);
174        let req = self.set_request_headers(req)?;
175        let resp = self
176            .flight_client
177            .handshake(req)
178            .await
179            .map_err(|e| ArrowError::IpcError(format!("Can't handshake {e}")))?;
180        if let Some(auth) = resp.metadata().get("authorization") {
181            let auth = auth
182                .to_str()
183                .map_err(|_| ArrowError::ParseError("Can't read auth header".to_string()))?;
184            let bearer = "Bearer ";
185            if !auth.starts_with(bearer) {
186                Err(ArrowError::ParseError("Invalid auth header!".to_string()))?;
187            }
188            let auth = auth[bearer.len()..].to_string();
189            self.token = Some(auth);
190        }
191        let responses: Vec<HandshakeResponse> = resp
192            .into_inner()
193            .try_collect()
194            .await
195            .map_err(|_| ArrowError::ParseError("Can't collect responses".to_string()))?;
196        let resp = match responses.as_slice() {
197            [resp] => resp.payload.clone(),
198            [] => Bytes::new(),
199            _ => Err(ArrowError::ParseError(
200                "Multiple handshake responses".to_string(),
201            ))?,
202        };
203        Ok(resp)
204    }
205
206    /// Execute a update query on the server, and return the number of records affected
207    pub async fn execute_update(
208        &mut self,
209        query: String,
210        transaction_id: Option<Bytes>,
211    ) -> Result<i64> {
212        let cmd = CommandStatementUpdate {
213            query,
214            transaction_id,
215        };
216        let descriptor = FlightDescriptor::new_cmd(cmd.as_any().encode_to_vec());
217        let req = self.set_request_headers(
218            stream::iter(vec![FlightData {
219                flight_descriptor: Some(descriptor),
220                ..Default::default()
221            }])
222            .into_request(),
223        )?;
224        let mut result = self.flight_client.do_put(req).await?.into_inner();
225        let result = result.message().await?.ok_or_else(|| {
226            FlightError::protocol("Server closed the stream without sending a result")
227        })?;
228        let result: DoPutUpdateResult = Message::decode(&*result.app_metadata)?;
229        Ok(result.record_count)
230    }
231
232    /// Execute a bulk ingest on the server and return the number of records added
233    pub async fn execute_ingest<S>(
234        &mut self,
235        command: CommandStatementIngest,
236        stream: S,
237    ) -> Result<i64>
238    where
239        S: Stream<Item = crate::error::Result<RecordBatch>> + Send + 'static,
240    {
241        let (sender, receiver) = futures::channel::oneshot::channel();
242
243        let descriptor = FlightDescriptor::new_cmd(command.as_any().encode_to_vec());
244        let flight_data = FlightDataEncoderBuilder::new()
245            .with_flight_descriptor(Some(descriptor))
246            .build(stream);
247
248        // Intercept client errors and send them to the one shot channel above
249        let flight_data = Box::pin(flight_data);
250        let flight_data: FallibleRequestStream<FlightData, FlightError> =
251            FallibleRequestStream::new(sender, flight_data);
252
253        let req = self.set_request_headers(flight_data.into_streaming_request())?;
254        let mut result = self.flight_client.do_put(req).await?.into_inner();
255
256        // check if the there were any errors in the input stream provided note
257        // if receiver.await fails, it means the sender was dropped and there is
258        // no message to return.
259        if let Ok(msg) = receiver.await {
260            return Err(FlightError::ExternalError(Box::new(msg)));
261        }
262
263        let result = result.message().await?.ok_or_else(|| {
264            FlightError::protocol("Server closed the stream without sending a result")
265        })?;
266        let result: DoPutUpdateResult = Message::decode(&*result.app_metadata)?;
267        Ok(result.record_count)
268    }
269
270    /// Request a list of catalogs as tabular FlightInfo results
271    pub async fn get_catalogs(&mut self) -> Result<FlightInfo> {
272        self.get_flight_info_for_command(CommandGetCatalogs {})
273            .await
274    }
275
276    /// Request a list of database schemas as tabular FlightInfo results
277    pub async fn get_db_schemas(&mut self, request: CommandGetDbSchemas) -> Result<FlightInfo> {
278        self.get_flight_info_for_command(request).await
279    }
280
281    /// Given a flight ticket, request to be sent the stream. Returns record batch stream reader
282    pub async fn do_get(
283        &mut self,
284        ticket: impl IntoRequest<Ticket>,
285    ) -> Result<FlightRecordBatchStream> {
286        let req = self.set_request_headers(ticket.into_request())?;
287
288        let (md, response_stream, _ext) = self.flight_client.do_get(req).await?.into_parts();
289        let (response_stream, trailers) = extract_lazy_trailers(response_stream);
290
291        Ok(FlightRecordBatchStream::new_from_flight_data(
292            response_stream.map_err(|status| status.into()),
293        )
294        .with_headers(md)
295        .with_trailers(trailers))
296    }
297
298    /// Push a stream to the flight service associated with a particular flight stream.
299    pub async fn do_put(
300        &mut self,
301        request: impl tonic::IntoStreamingRequest<Message = FlightData>,
302    ) -> Result<Streaming<PutResult>> {
303        let req = self.set_request_headers(request.into_streaming_request())?;
304        Ok(self.flight_client.do_put(req).await?.into_inner())
305    }
306
307    /// DoAction allows a flight client to do a specific action against a flight service
308    pub async fn do_action(
309        &mut self,
310        request: impl IntoRequest<Action>,
311    ) -> Result<Streaming<crate::Result>> {
312        let req = self.set_request_headers(request.into_request())?;
313        Ok(self.flight_client.do_action(req).await?.into_inner())
314    }
315
316    /// Request a list of tables.
317    pub async fn get_tables(&mut self, request: CommandGetTables) -> Result<FlightInfo> {
318        self.get_flight_info_for_command(request).await
319    }
320
321    /// Request the primary keys for a table.
322    pub async fn get_primary_keys(&mut self, request: CommandGetPrimaryKeys) -> Result<FlightInfo> {
323        self.get_flight_info_for_command(request).await
324    }
325
326    /// Retrieves a description about the foreign key columns that reference the
327    /// primary key columns of the given table.
328    pub async fn get_exported_keys(
329        &mut self,
330        request: CommandGetExportedKeys,
331    ) -> Result<FlightInfo> {
332        self.get_flight_info_for_command(request).await
333    }
334
335    /// Retrieves the foreign key columns for the given table.
336    pub async fn get_imported_keys(
337        &mut self,
338        request: CommandGetImportedKeys,
339    ) -> Result<FlightInfo> {
340        self.get_flight_info_for_command(request).await
341    }
342
343    /// Retrieves a description of the foreign key columns in the given foreign key
344    /// table that reference the primary key or the columns representing a unique
345    /// constraint of the parent table (could be the same or a different table).
346    pub async fn get_cross_reference(
347        &mut self,
348        request: CommandGetCrossReference,
349    ) -> Result<FlightInfo> {
350        self.get_flight_info_for_command(request).await
351    }
352
353    /// Request a list of table types.
354    pub async fn get_table_types(&mut self) -> Result<FlightInfo> {
355        self.get_flight_info_for_command(CommandGetTableTypes {})
356            .await
357    }
358
359    /// Request a list of SQL information.
360    pub async fn get_sql_info(&mut self, sql_infos: Vec<SqlInfo>) -> Result<FlightInfo> {
361        let request = CommandGetSqlInfo {
362            info: sql_infos.iter().map(|sql_info| *sql_info as u32).collect(),
363        };
364        self.get_flight_info_for_command(request).await
365    }
366
367    /// Request XDBC SQL information.
368    pub async fn get_xdbc_type_info(
369        &mut self,
370        request: CommandGetXdbcTypeInfo,
371    ) -> Result<FlightInfo> {
372        self.get_flight_info_for_command(request).await
373    }
374
375    /// Create a prepared statement object.
376    pub async fn prepare(
377        &mut self,
378        query: String,
379        transaction_id: Option<Bytes>,
380    ) -> Result<PreparedStatement<T>>
381    where
382        T: Clone,
383    {
384        let cmd = ActionCreatePreparedStatementRequest {
385            query,
386            transaction_id,
387        };
388        let action = Action {
389            r#type: CREATE_PREPARED_STATEMENT.to_string(),
390            body: cmd.as_any().encode_to_vec().into(),
391        };
392        let req = self.set_request_headers(action.into_request())?;
393        let mut result = self.flight_client.do_action(req).await?.into_inner();
394        let result = result.message().await?.ok_or_else(|| {
395            FlightError::protocol("Server closed the stream without sending a result")
396        })?;
397        let any = Any::decode(&*result.body)?;
398        let prepared_result: ActionCreatePreparedStatementResult =
399            any.unpack()?.ok_or_else(|| {
400                FlightError::protocol(
401                    "Server did not return an ActionCreatePreparedStatementResult",
402                )
403            })?;
404        let dataset_schema = match prepared_result.dataset_schema.len() {
405            0 => Schema::empty(),
406            _ => Schema::try_from(IpcMessage(prepared_result.dataset_schema))?,
407        };
408        let parameter_schema = match prepared_result.parameter_schema.len() {
409            0 => Schema::empty(),
410            _ => Schema::try_from(IpcMessage(prepared_result.parameter_schema))?,
411        };
412        Ok(PreparedStatement::new(
413            self.clone(),
414            prepared_result.prepared_statement_handle,
415            dataset_schema,
416            parameter_schema,
417        ))
418    }
419
420    /// Request to begin a transaction.
421    pub async fn begin_transaction(&mut self) -> Result<Bytes> {
422        let cmd = ActionBeginTransactionRequest {};
423        let action = Action {
424            r#type: BEGIN_TRANSACTION.to_string(),
425            body: cmd.as_any().encode_to_vec().into(),
426        };
427        let req = self.set_request_headers(action.into_request())?;
428        let mut result = self.flight_client.do_action(req).await?.into_inner();
429        let result = result.message().await?.ok_or_else(|| {
430            FlightError::protocol("Server closed the stream without sending a result")
431        })?;
432        let any = Any::decode(&*result.body)?;
433        let begin_result: ActionBeginTransactionResult = any.unpack()?.ok_or_else(|| {
434            FlightError::protocol("Server did not return an ActionBeginTransactionResult")
435        })?;
436        Ok(begin_result.transaction_id)
437    }
438
439    /// Request to commit/rollback a transaction.
440    pub async fn end_transaction(
441        &mut self,
442        transaction_id: Bytes,
443        action: EndTransaction,
444    ) -> Result<()> {
445        let cmd = ActionEndTransactionRequest {
446            transaction_id,
447            action: action as i32,
448        };
449        let action = Action {
450            r#type: END_TRANSACTION.to_string(),
451            body: cmd.as_any().encode_to_vec().into(),
452        };
453        let req = self.set_request_headers(action.into_request())?;
454        let _ = self.flight_client.do_action(req).await?.into_inner();
455        Ok(())
456    }
457
458    /// Explicitly shut down and clean up the client.
459    #[expect(
460        clippy::unused_async,
461        clippy::unused_async_trait_impl,
462        reason = "public API: dropping `async` would break callers that `.await` it"
463    )]
464    pub async fn close(&mut self) -> Result<()> {
465        // TODO: consume self instead of &mut self to explicitly prevent reuse?
466        Ok(())
467    }
468
469    fn set_request_headers<M>(&self, mut req: tonic::Request<M>) -> Result<tonic::Request<M>> {
470        for (k, v) in &self.headers {
471            let k = AsciiMetadataKey::from_str(k.as_str()).map_err(|e| {
472                ArrowError::ParseError(format!("Cannot convert header key \"{k}\": {e}"))
473            })?;
474            let v = v.parse().map_err(|e| {
475                ArrowError::ParseError(format!("Cannot convert header value \"{v}\": {e}"))
476            })?;
477            req.metadata_mut().insert(k, v);
478        }
479        if let Some(token) = &self.token {
480            let val = format!("Bearer {token}").parse().map_err(|e| {
481                ArrowError::ParseError(format!("Cannot convert token to header value: {e}"))
482            })?;
483            req.metadata_mut().insert("authorization", val);
484        }
485        Ok(req)
486    }
487}
488
489impl<T: Clone> Clone for FlightSqlServiceClient<T> {
490    fn clone(&self) -> Self {
491        Self {
492            headers: self.headers.clone(),
493            token: self.token.clone(),
494            flight_client: self.flight_client.clone(),
495        }
496    }
497}
498
499/// A PreparedStatement
500#[derive(Debug, Clone)]
501pub struct PreparedStatement<T> {
502    flight_sql_client: FlightSqlServiceClient<T>,
503    parameter_binding: Option<RecordBatch>,
504    handle: Bytes,
505    dataset_schema: Schema,
506    parameter_schema: Schema,
507}
508
509impl<T> PreparedStatement<T>
510where
511    T: tonic::client::GrpcService<tonic::body::Body>,
512    T::Error: Into<StdError>,
513    T::ResponseBody: Body<Data = Bytes> + Send + 'static,
514    <T::ResponseBody as Body>::Error: Into<StdError> + Send,
515{
516    pub(crate) fn new(
517        flight_client: FlightSqlServiceClient<T>,
518        handle: impl Into<Bytes>,
519        dataset_schema: Schema,
520        parameter_schema: Schema,
521    ) -> Self {
522        PreparedStatement {
523            flight_sql_client: flight_client,
524            parameter_binding: None,
525            handle: handle.into(),
526            dataset_schema,
527            parameter_schema,
528        }
529    }
530
531    /// Executes the prepared statement query on the server.
532    pub async fn execute(&mut self) -> Result<FlightInfo> {
533        self.write_bind_params().await?;
534
535        let cmd = CommandPreparedStatementQuery {
536            prepared_statement_handle: self.handle.clone(),
537        };
538
539        let result = self
540            .flight_sql_client
541            .get_flight_info_for_command(cmd)
542            .await?;
543        Ok(result)
544    }
545
546    /// Executes the prepared statement update query on the server.
547    pub async fn execute_update(&mut self) -> Result<i64> {
548        self.write_bind_params().await?;
549
550        let cmd = CommandPreparedStatementUpdate {
551            prepared_statement_handle: self.handle.clone(),
552        };
553        let descriptor = FlightDescriptor::new_cmd(cmd.as_any().encode_to_vec());
554        let mut result = self
555            .flight_sql_client
556            .do_put(stream::iter(vec![FlightData {
557                flight_descriptor: Some(descriptor),
558                ..Default::default()
559            }]))
560            .await?;
561        let result = result.message().await?.ok_or_else(|| {
562            FlightError::protocol("Server closed the stream without sending a result")
563        })?;
564        let result: DoPutUpdateResult = Message::decode(&*result.app_metadata)?;
565        Ok(result.record_count)
566    }
567
568    /// Retrieve the parameter schema from the query.
569    pub fn parameter_schema(&self) -> Result<&Schema> {
570        Ok(&self.parameter_schema)
571    }
572
573    /// Retrieve the ResultSet schema from the query.
574    pub fn dataset_schema(&self) -> Result<&Schema> {
575        Ok(&self.dataset_schema)
576    }
577
578    /// Set a RecordBatch that contains the parameters that will be bind.
579    pub fn set_parameters(&mut self, parameter_binding: RecordBatch) -> Result<()> {
580        self.parameter_binding = Some(parameter_binding);
581        Ok(())
582    }
583
584    /// Submit parameters to the server, if any have been set on this prepared statement instance
585    /// Updates our stored prepared statement handle with the handle given by the server response.
586    async fn write_bind_params(&mut self) -> Result<()> {
587        if let Some(ref params_batch) = self.parameter_binding {
588            let cmd = CommandPreparedStatementQuery {
589                prepared_statement_handle: self.handle.clone(),
590            };
591
592            let descriptor = FlightDescriptor::new_cmd(cmd.as_any().encode_to_vec());
593            let flight_stream_builder = FlightDataEncoderBuilder::new()
594                .with_flight_descriptor(Some(descriptor))
595                .with_schema(params_batch.schema());
596            let flight_data = flight_stream_builder
597                .build(futures::stream::iter(
598                    self.parameter_binding.clone().map(Ok),
599                ))
600                .try_collect::<Vec<_>>()
601                .await?;
602
603            // Attempt to update the stored handle with any updated handle in the DoPut result.
604            // Older servers do not respond with a result for DoPut, so skip this step when
605            // the stream closes with no response.
606            if let Some(result) = self
607                .flight_sql_client
608                .do_put(stream::iter(flight_data))
609                .await?
610                .message()
611                .await?
612                && let Some(handle) = self.unpack_prepared_statement_handle(&result)?
613            {
614                self.handle = handle;
615            }
616        }
617        Ok(())
618    }
619
620    /// Decodes the app_metadata stored in a [`PutResult`] as a
621    /// [`DoPutPreparedStatementResult`] and then returns
622    /// the inner prepared statement handle as [`Bytes`]
623    fn unpack_prepared_statement_handle(&self, put_result: &PutResult) -> Result<Option<Bytes>> {
624        let result: DoPutPreparedStatementResult = Message::decode(&*put_result.app_metadata)?;
625        Ok(result.prepared_statement_handle)
626    }
627
628    /// Close the prepared statement, so that this PreparedStatement can not used
629    /// anymore and server can free up any resources.
630    pub async fn close(mut self) -> Result<()> {
631        let cmd = ActionClosePreparedStatementRequest {
632            prepared_statement_handle: self.handle.clone(),
633        };
634        let action = Action {
635            r#type: CLOSE_PREPARED_STATEMENT.to_string(),
636            body: cmd.as_any().encode_to_vec().into(),
637        };
638        let _ = self.flight_sql_client.do_action(action).await?;
639        Ok(())
640    }
641}
642
643/// A polymorphic structure to natively represent different types of data contained in `FlightData`
644pub enum ArrowFlightData {
645    /// A record batch
646    RecordBatch(RecordBatch),
647    /// A schema
648    Schema(Schema),
649}
650
651/// Extract `Schema` or `RecordBatch`es from the `FlightData` wire representation
652pub fn arrow_data_from_flight_data(
653    flight_data: FlightData,
654    arrow_schema_ref: &SchemaRef,
655) -> std::result::Result<ArrowFlightData, ArrowError> {
656    let ipc_message = root_as_message(&flight_data.data_header[..])
657        .map_err(|err| ArrowError::ParseError(format!("Unable to get root as message: {err:?}")))?;
658
659    match ipc_message.header_type() {
660        MessageHeader::RecordBatch => {
661            let ipc_record_batch = ipc_message.header_as_record_batch().ok_or_else(|| {
662                ArrowError::ComputeError(
663                    "Unable to convert flight data header to a record batch".to_string(),
664                )
665            })?;
666
667            let dictionaries_by_field = HashMap::new();
668            let record_batch = read_record_batch(
669                &Buffer::from(flight_data.data_body),
670                ipc_record_batch,
671                arrow_schema_ref.clone(),
672                &dictionaries_by_field,
673                None,
674                &ipc_message.version(),
675            )?;
676            Ok(ArrowFlightData::RecordBatch(record_batch))
677        }
678        MessageHeader::Schema => {
679            let ipc_schema = ipc_message.header_as_schema().ok_or_else(|| {
680                ArrowError::ComputeError(
681                    "Unable to convert flight data header to a schema".to_string(),
682                )
683            })?;
684
685            let arrow_schema = try_fb_to_schema(ipc_schema)?;
686            Ok(ArrowFlightData::Schema(arrow_schema))
687        }
688        MessageHeader::DictionaryBatch => {
689            let _ = ipc_message.header_as_dictionary_batch().ok_or_else(|| {
690                ArrowError::ComputeError(
691                    "Unable to convert flight data header to a dictionary batch".to_string(),
692                )
693            })?;
694            Err(ArrowError::NotYetImplemented(
695                "no idea on how to convert an ipc dictionary batch to an arrow type".to_string(),
696            ))
697        }
698        MessageHeader::Tensor => {
699            let _ = ipc_message.header_as_tensor().ok_or_else(|| {
700                ArrowError::ComputeError(
701                    "Unable to convert flight data header to a tensor".to_string(),
702                )
703            })?;
704            Err(ArrowError::NotYetImplemented(
705                "no idea on how to convert an ipc tensor to an arrow type".to_string(),
706            ))
707        }
708        MessageHeader::SparseTensor => {
709            let _ = ipc_message.header_as_sparse_tensor().ok_or_else(|| {
710                ArrowError::ComputeError(
711                    "Unable to convert flight data header to a sparse tensor".to_string(),
712                )
713            })?;
714            Err(ArrowError::NotYetImplemented(
715                "no idea on how to convert an ipc sparse tensor to an arrow type".to_string(),
716            ))
717        }
718        _ => Err(ArrowError::ComputeError(format!(
719            "Unable to convert message with header_type: '{:?}' to arrow data",
720            ipc_message.header_type()
721        ))),
722    }
723}