1use 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#[derive(Debug)]
68pub struct FlightSqlServiceClient<T> {
69 token: Option<String>,
70 headers: HashMap<String, String>,
71 flight_client: FlightServiceClient<T>,
72}
73
74impl<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 pub fn new(channel: T) -> Self {
86 Self::new_from_inner(FlightServiceClient::new(channel))
87 }
88
89 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 pub fn inner(&self) -> &FlightServiceClient<T> {
100 &self.flight_client
101 }
102
103 pub fn inner_mut(&mut self) -> &mut FlightServiceClient<T> {
105 &mut self.flight_client
106 }
107
108 pub fn into_inner(self) -> FlightServiceClient<T> {
110 self.flight_client
111 }
112
113 pub fn set_token(&mut self, token: String) {
115 self.token = Some(token);
116 }
117
118 pub fn clear_token(&mut self) {
120 self.token = None;
121 }
122
123 pub fn token(&self) -> Option<&String> {
125 self.token.as_ref()
126 }
127
128 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 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 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 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 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 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 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 pub async fn get_catalogs(&mut self) -> Result<FlightInfo> {
272 self.get_flight_info_for_command(CommandGetCatalogs {})
273 .await
274 }
275
276 pub async fn get_db_schemas(&mut self, request: CommandGetDbSchemas) -> Result<FlightInfo> {
278 self.get_flight_info_for_command(request).await
279 }
280
281 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 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 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 pub async fn get_tables(&mut self, request: CommandGetTables) -> Result<FlightInfo> {
318 self.get_flight_info_for_command(request).await
319 }
320
321 pub async fn get_primary_keys(&mut self, request: CommandGetPrimaryKeys) -> Result<FlightInfo> {
323 self.get_flight_info_for_command(request).await
324 }
325
326 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 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 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 pub async fn get_table_types(&mut self) -> Result<FlightInfo> {
355 self.get_flight_info_for_command(CommandGetTableTypes {})
356 .await
357 }
358
359 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 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 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 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 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 #[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 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#[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 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 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 pub fn parameter_schema(&self) -> Result<&Schema> {
570 Ok(&self.parameter_schema)
571 }
572
573 pub fn dataset_schema(&self) -> Result<&Schema> {
575 Ok(&self.dataset_schema)
576 }
577
578 pub fn set_parameters(&mut self, parameter_binding: RecordBatch) -> Result<()> {
580 self.parameter_binding = Some(parameter_binding);
581 Ok(())
582 }
583
584 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 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 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 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
643pub enum ArrowFlightData {
645 RecordBatch(RecordBatch),
647 Schema(Schema),
649}
650
651pub 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}