1use std::collections::HashMap;
16use std::pin::Pin;
17use std::str::FromStr;
18use std::sync::atomic::{AtomicBool, Ordering};
19use std::sync::{Arc, RwLock};
20use std::task::{Context, Poll};
21use std::time::Duration;
22
23use api::v1::auth_header::AuthScheme;
24#[cfg(feature = "testing")]
25use api::v1::ddl_request::Expr as DdlExpr;
26use api::v1::greptime_database_client::GreptimeDatabaseClient;
27use api::v1::greptime_request::Request;
28use api::v1::query_request::Query;
29#[cfg(feature = "testing")]
30use api::v1::{AlterTableExpr, CreateTableExpr, DdlRequest};
31use api::v1::{
32 AuthHeader, Basic, GreptimeRequest, InsertRequests, QueryRequest, RequestHeader,
33 RowInsertRequests,
34};
35use arc_swap::ArcSwapOption;
36use arrow_flight::{FlightData, Ticket};
37use async_stream::stream;
38use base64::Engine;
39use base64::prelude::BASE64_STANDARD;
40use common_catalog::build_db_string;
41use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
42use common_error::ext::{BoxedError, ErrorExt};
43use common_grpc::flight::do_put::DoPutResponse;
44use common_grpc::flight::{
45 FLOW_EXTENSIONS_METADATA_KEY, FlightDecoder, FlightMessage, SNAPSHOT_SEQS_METADATA_KEY,
46};
47use common_query::Output;
48use common_recordbatch::adapter::RecordBatchMetrics;
49use common_recordbatch::error::ExternalSnafu;
50use common_recordbatch::{OrderOption, RecordBatch, RecordBatchStream, RecordBatchStreamWrapper};
51use common_telemetry::tracing::Span;
52use common_telemetry::tracing_context::W3cTrace;
53use common_telemetry::{error, warn};
54use futures::future;
55use futures_util::{Stream, StreamExt, TryStreamExt};
56use prost::Message;
57use snafu::{IntoError, ResultExt};
58use tonic::metadata::{AsciiMetadataKey, AsciiMetadataValue, MetadataMap, MetadataValue};
59use tonic::transport::Channel;
60
61use crate::error::{
62 ConvertFlightDataSnafu, Error, FlightGetSnafu, FlightStreamSnafu, IllegalFlightMessagesSnafu,
63 InvalidTonicMetadataValueSnafu,
64};
65use crate::flight::{FlightMessageReader, decode_flight_data};
66use crate::{Client, Result, error, from_grpc_response};
67
68type FlightDataStream = Pin<Box<dyn Stream<Item = FlightData> + Send>>;
69
70type DoPutResponseStream = Pin<Box<dyn Stream<Item = Result<DoPutResponse>>>>;
71
72const HINTS_METADATA_KEY: &str = "x-greptime-hints";
73
74#[derive(Debug, Clone, Default)]
79pub struct OutputMetrics {
80 inner: Arc<OutputMetricsInner>,
81}
82
83#[derive(Debug, Default)]
84struct OutputMetricsInner {
85 metrics: RwLock<Option<RecordBatchMetrics>>,
86 ready: AtomicBool,
87}
88
89impl OutputMetrics {
90 fn new() -> Self {
91 Self::default()
92 }
93
94 pub fn update(&self, metrics: Option<RecordBatchMetrics>) {
96 *self.inner.metrics.write().unwrap() = metrics;
97 }
98
99 pub fn mark_ready(&self) {
101 let _ = self
102 .inner
103 .ready
104 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire);
105 }
106
107 pub fn is_ready(&self) -> bool {
111 self.inner.ready.load(Ordering::Acquire)
112 }
113
114 pub fn get(&self) -> Option<RecordBatchMetrics> {
116 self.inner.metrics.read().unwrap().clone()
117 }
118
119 pub fn region_watermark_map(&self) -> Option<std::collections::HashMap<u64, u64>> {
125 Some(
126 self.get()?
127 .region_watermarks
128 .into_iter()
129 .filter_map(|entry| entry.watermark.map(|seq| (entry.region_id, seq)))
130 .collect::<std::collections::HashMap<_, _>>(),
131 )
132 }
133
134 pub fn participating_regions(&self) -> Option<std::collections::BTreeSet<u64>> {
137 Some(
138 self.get()?
139 .region_watermarks
140 .into_iter()
141 .map(|entry| entry.region_id)
142 .collect::<std::collections::BTreeSet<_>>(),
143 )
144 }
145}
146
147#[derive(Debug)]
153pub struct OutputWithMetrics {
154 pub output: Output,
155 pub metrics: OutputMetrics,
156}
157
158impl OutputWithMetrics {
159 pub fn from_output(output: Output) -> Self {
164 let terminal_metrics = OutputMetrics::new();
165 let output = attach_terminal_metrics(output, &terminal_metrics);
166 Self {
167 output,
168 metrics: terminal_metrics,
169 }
170 }
171
172 pub fn region_watermark_map(&self) -> Option<std::collections::HashMap<u64, u64>> {
174 self.metrics.region_watermark_map()
175 }
176
177 pub fn participating_regions(&self) -> Option<std::collections::BTreeSet<u64>> {
179 self.metrics.participating_regions()
180 }
181
182 pub fn into_output(self) -> Output {
184 self.output
185 }
186}
187
188fn parse_terminal_metrics(metrics_json: &str) -> Result<RecordBatchMetrics> {
189 serde_json::from_str(metrics_json).map_err(|e| {
190 IllegalFlightMessagesSnafu {
191 reason: format!("Invalid terminal metrics message: {e}"),
192 }
193 .build()
194 })
195}
196
197struct StreamWithMetrics {
198 stream: common_recordbatch::SendableRecordBatchStream,
199 metrics: OutputMetrics,
200}
201
202impl StreamWithMetrics {
203 fn new(stream: common_recordbatch::SendableRecordBatchStream, metrics: OutputMetrics) -> Self {
204 Self { stream, metrics }
205 }
206
207 fn sync_terminal_metrics(&self) {
208 self.metrics.update(self.stream.metrics());
209 }
210}
211
212impl RecordBatchStream for StreamWithMetrics {
213 fn name(&self) -> &str {
214 self.stream.name()
215 }
216
217 fn schema(&self) -> datatypes::schema::SchemaRef {
218 self.stream.schema()
219 }
220
221 fn output_ordering(&self) -> Option<&[OrderOption]> {
222 self.stream.output_ordering()
223 }
224
225 fn metrics(&self) -> Option<RecordBatchMetrics> {
226 self.sync_terminal_metrics();
227 self.metrics.get()
228 }
229}
230
231impl Stream for StreamWithMetrics {
232 type Item = common_recordbatch::error::Result<RecordBatch>;
233
234 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
235 let polled = Pin::new(&mut self.stream).poll_next(cx);
236 if let Poll::Ready(None) = &polled {
237 self.sync_terminal_metrics();
238 self.metrics.mark_ready();
239 }
240 polled
241 }
242
243 fn size_hint(&self) -> (usize, Option<usize>) {
244 self.stream.size_hint()
245 }
246}
247
248fn attach_terminal_metrics(output: Output, terminal_metrics: &OutputMetrics) -> Output {
249 let Output { data, meta } = output;
250 let data = match data {
251 common_query::OutputData::Stream(stream) => {
252 terminal_metrics.update(stream.metrics());
253 common_query::OutputData::Stream(Box::pin(StreamWithMetrics::new(
254 stream,
255 terminal_metrics.clone(),
256 )))
257 }
258 other => {
259 terminal_metrics.mark_ready();
260 other
261 }
262 };
263 Output::new(data, meta)
264}
265
266async fn output_from_flight_message_stream<S>(
267 remote_addr: String,
268 flight_message_stream: S,
269) -> Result<OutputWithMetrics>
270where
271 S: Stream<Item = Result<FlightMessage>> + Send + Unpin + 'static,
272{
273 let mut reader = FlightMessageReader::new(remote_addr, flight_message_stream);
274 let first_flight_message = reader
275 .read_first()
276 .await
277 .map_err(|error| flight_stream_error(reader.remote_addr(), error))?;
278
279 match first_flight_message {
280 FlightMessage::AffectedRows { rows, metrics } => {
281 let terminal_metrics = OutputMetrics::new();
282 if let Some(metrics) = metrics {
283 terminal_metrics.update(Some(parse_terminal_metrics(&metrics)?));
284 }
285 let next_message = reader
286 .read_next()
287 .await
288 .map_err(|error| flight_stream_error(reader.remote_addr(), error))?;
289 match next_message {
290 None => terminal_metrics.mark_ready(),
291 Some(FlightMessage::Metrics(s)) if terminal_metrics.get().is_none() => {
292 terminal_metrics.update(Some(parse_terminal_metrics(&s)?));
293 terminal_metrics.mark_ready();
294 }
295 Some(FlightMessage::Metrics(_)) => {
296 return IllegalFlightMessagesSnafu {
297 reason: "'AffectedRows' Flight metadata already carries Metrics and cannot be followed by another Metrics message",
298 }
299 .fail();
300 }
301 Some(other) => {
302 return IllegalFlightMessagesSnafu {
303 reason: format!(
304 "'AffectedRows' Flight message can only be followed by a Metrics message, got {other:?}"
305 ),
306 }
307 .fail();
308 }
309 }
310 Ok(OutputWithMetrics {
311 output: Output::new_with_affected_rows(rows),
312 metrics: terminal_metrics,
313 })
314 }
315 FlightMessage::RecordBatch(_) | FlightMessage::Metrics(_) => IllegalFlightMessagesSnafu {
316 reason: "The first flight message cannot be a RecordBatch or Metrics message",
317 }
318 .fail(),
319 FlightMessage::Schema(schema) => {
320 let metrics = Arc::new(ArcSwapOption::from(None));
321 let metrics_ref = metrics.clone();
322 let schema = Arc::new(
323 datatypes::schema::Schema::try_from(schema).context(error::ConvertSchemaSnafu)?,
324 );
325 let schema_cloned = schema.clone();
326 let stream = Box::pin(stream!({
327 loop {
328 let flight_message = match reader.read_next().await {
329 Ok(Some(message)) => message,
330 Ok(None) => break,
331 Err(error) => {
332 yield Err(BoxedError::new(flight_stream_error(
333 reader.remote_addr(),
334 error,
335 )))
336 .context(ExternalSnafu);
337 break;
338 }
339 };
340 match flight_message {
341 FlightMessage::RecordBatch(arrow_batch) => {
342 yield Ok(RecordBatch::from_df_record_batch(
343 schema_cloned.clone(),
344 arrow_batch,
345 ))
346 }
347 FlightMessage::Metrics(s) => {
348 match parse_terminal_metrics(&s) {
349 Ok(m) => {
350 metrics_ref.swap(Some(Arc::new(m)));
351 }
352 Err(e) => {
353 yield Err(BoxedError::new(e)).context(ExternalSnafu);
354 }
355 };
356 }
357 FlightMessage::AffectedRows { .. } | FlightMessage::Schema(_) => {
358 yield IllegalFlightMessagesSnafu {
359 reason: format!(
360 "A Schema message must be succeeded exclusively by a set of RecordBatch messages, flight_message: {:?}",
361 flight_message
362 )
363 }
364 .fail()
365 .map_err(BoxedError::new)
366 .context(ExternalSnafu);
367 break;
368 }
369 }
370 }
371 }));
372 let record_batch_stream = RecordBatchStreamWrapper {
373 schema,
374 stream,
375 output_ordering: None,
376 metrics,
377 span: Span::current(),
378 };
379 Ok(OutputWithMetrics::from_output(Output::new_with_stream(
380 Box::pin(record_batch_stream),
381 )))
382 }
383 }
384}
385
386fn flight_stream_error(addr: &str, error: Error) -> Error {
387 let tonic_code = error.tonic_code().unwrap_or(tonic::Code::Unknown);
388 let message = error.to_string();
389 if error.status_code().should_log_error() {
390 error!(
391 error; "Failed to receive Flight data, addr: {}, code: {}",
392 addr,
393 tonic_code
394 );
395 }
396
397 FlightStreamSnafu {
398 addr: addr.to_string(),
399 tonic_code,
400 message,
401 }
402 .into_error(BoxedError::new(error))
403}
404
405#[derive(Clone, Debug, Default)]
406pub struct Database {
407 catalog: String,
411 schema: String,
412 dbname: String,
415 timezone: String,
418
419 client: Client,
420 ctx: FlightContext,
421}
422
423#[derive(Default)]
424struct FlightRequestOptions {
425 hints: Option<String>,
426 flow_extensions: Option<String>,
427 snapshot_seqs: Option<String>,
428 timeout: Option<Duration>,
429}
430
431impl FlightRequestOptions {
432 fn apply_to<T>(self, request: &mut tonic::Request<T>) -> Result<()> {
433 let metadata = request.metadata_mut();
434 if let Some(hints) = self.hints {
435 Database::put_metadata_value(metadata, HINTS_METADATA_KEY, hints)?;
436 }
437 if let Some(flow_extensions) = self.flow_extensions {
438 Database::put_metadata_value(metadata, FLOW_EXTENSIONS_METADATA_KEY, flow_extensions)?;
439 }
440 if let Some(snapshot_seqs) = self.snapshot_seqs {
441 Database::put_metadata_value(metadata, SNAPSHOT_SEQS_METADATA_KEY, snapshot_seqs)?;
442 }
443 if let Some(timeout) = self.timeout {
444 request.set_timeout(timeout);
445 }
446 Ok(())
447 }
448}
449
450pub struct DatabaseFlightRequest<'a> {
455 database: &'a Database,
456 options: FlightRequestOptions,
457}
458
459pub struct DatabaseClient {
460 pub addr: String,
461 pub inner: GreptimeDatabaseClient<Channel>,
462}
463
464impl DatabaseClient {
465 pub fn inspect_err<'a>(&'a self, context: &'a str) -> impl Fn(&tonic::Status) + 'a {
467 let addr = &self.addr;
468 move |status| {
469 error!("Failed to {context} request, peer: {addr}, status: {status:?}");
470 }
471 }
472}
473
474fn make_database_client(client: &Client) -> Result<DatabaseClient> {
475 let (addr, channel) = client.find_channel()?;
476 Ok(DatabaseClient {
477 addr,
478 inner: GreptimeDatabaseClient::new(channel)
479 .max_decoding_message_size(client.max_grpc_recv_message_size())
480 .max_encoding_message_size(client.max_grpc_send_message_size()),
481 })
482}
483
484impl Database {
485 pub fn new(catalog: impl Into<String>, schema: impl Into<String>, client: Client) -> Self {
487 Self {
488 catalog: catalog.into(),
489 schema: schema.into(),
490 dbname: String::default(),
491 timezone: String::default(),
492 client,
493 ctx: FlightContext::default(),
494 }
495 }
496
497 pub fn new_with_dbname(dbname: impl Into<String>, client: Client) -> Self {
505 Self {
506 catalog: String::default(),
507 schema: String::default(),
508 timezone: String::default(),
509 dbname: dbname.into(),
510 client,
511 ctx: FlightContext::default(),
512 }
513 }
514
515 pub fn set_catalog(&mut self, catalog: impl Into<String>) {
517 self.catalog = catalog.into();
518 }
519
520 fn catalog_or_default(&self) -> &str {
521 if self.catalog.is_empty() {
522 DEFAULT_CATALOG_NAME
523 } else {
524 &self.catalog
525 }
526 }
527
528 pub fn set_schema(&mut self, schema: impl Into<String>) {
530 self.schema = schema.into();
531 }
532
533 fn schema_or_default(&self) -> &str {
534 if self.schema.is_empty() {
535 DEFAULT_SCHEMA_NAME
536 } else {
537 &self.schema
538 }
539 }
540
541 pub fn set_timezone(&mut self, timezone: impl Into<String>) {
543 self.timezone = timezone.into();
544 }
545
546 pub fn set_auth(&mut self, auth: AuthScheme) {
548 self.ctx.auth_header = Some(AuthHeader {
549 auth_scheme: Some(auth),
550 });
551 }
552
553 pub fn flight_request(&self) -> DatabaseFlightRequest<'_> {
555 DatabaseFlightRequest {
556 database: self,
557 options: FlightRequestOptions::default(),
558 }
559 }
560
561 pub async fn insert(&self, requests: InsertRequests) -> Result<u32> {
563 self.handle(Request::Inserts(requests)).await
564 }
565
566 pub async fn insert_with_hints(
568 &self,
569 requests: InsertRequests,
570 hints: &[(&str, &str)],
571 ) -> Result<u32> {
572 let mut client = make_database_client(&self.client)?;
573 let request = self.to_rpc_request(Request::Inserts(requests));
574
575 let mut request = tonic::Request::new(request);
576 let metadata = request.metadata_mut();
577 Self::put_hints(metadata, hints)?;
578
579 let response = client
580 .inner
581 .handle(request)
582 .await
583 .inspect_err(client.inspect_err("insert_with_hints"))?
584 .into_inner();
585 from_grpc_response(response)
586 }
587
588 pub async fn row_inserts(&self, requests: RowInsertRequests) -> Result<u32> {
590 self.handle(Request::RowInserts(requests)).await
591 }
592
593 pub async fn row_inserts_with_hints(
595 &self,
596 requests: RowInsertRequests,
597 hints: &[(&str, &str)],
598 ) -> Result<u32> {
599 let mut client = make_database_client(&self.client)?;
600 let request = self.to_rpc_request(Request::RowInserts(requests));
601
602 let mut request = tonic::Request::new(request);
603 let metadata = request.metadata_mut();
604 Self::put_hints(metadata, hints)?;
605
606 let response = client
607 .inner
608 .handle(request)
609 .await
610 .inspect_err(client.inspect_err("row_inserts_with_hints"))?
611 .into_inner();
612 from_grpc_response(response)
613 }
614
615 fn put_hints(metadata: &mut MetadataMap, hints: &[(&str, &str)]) -> Result<()> {
616 let Some(value) = Self::encode_hints(hints) else {
617 return Ok(());
618 };
619
620 Self::put_metadata_value(metadata, HINTS_METADATA_KEY, value)
621 }
622
623 fn encode_hints(hints: &[(&str, &str)]) -> Option<String> {
624 hints
625 .iter()
626 .map(|(k, v)| format!("{}={}", k, v))
627 .reduce(|a, b| format!("{},{}", a, b))
628 }
629
630 fn encode_flow_extensions(flow_extensions: &[(&str, &str)]) -> Option<String> {
631 (!flow_extensions.is_empty()).then(|| {
632 serde_json::to_string(&flow_extensions.to_vec())
633 .expect("flow extension pairs should serialize")
634 })
635 }
636
637 fn encode_snapshot_seqs(snapshot_seqs: &HashMap<u64, u64>) -> Option<String> {
638 (!snapshot_seqs.is_empty()).then(|| {
639 serde_json::to_string(snapshot_seqs).expect("snapshot sequence map should serialize")
640 })
641 }
642
643 fn put_metadata_value(
644 metadata: &mut MetadataMap,
645 key: &'static str,
646 value: String,
647 ) -> Result<()> {
648 let key = AsciiMetadataKey::from_static(key);
649 let value = AsciiMetadataValue::from_str(&value).context(InvalidTonicMetadataValueSnafu)?;
650 metadata.insert(key, value);
651 Ok(())
652 }
653
654 pub async fn handle(&self, request: Request) -> Result<u32> {
656 let mut client = make_database_client(&self.client)?;
657 let request = self.to_rpc_request(request);
658 let response = client
659 .inner
660 .handle(request)
661 .await
662 .inspect_err(client.inspect_err("handle"))?
663 .into_inner();
664 from_grpc_response(response)
665 }
666
667 pub async fn handle_with_retry(
670 &self,
671 request: Request,
672 max_retries: u32,
673 hints: &[(&str, &str)],
674 ) -> Result<u32> {
675 let mut client = make_database_client(&self.client)?;
676 let mut retries = 0;
677
678 let request = self.to_rpc_request(request);
679
680 loop {
681 let mut tonic_request = tonic::Request::new(request.clone());
682 let metadata = tonic_request.metadata_mut();
683 Self::put_hints(metadata, hints)?;
684 let raw_response = client
685 .inner
686 .handle(tonic_request)
687 .await
688 .inspect_err(client.inspect_err("handle"));
689 match (raw_response, retries < max_retries) {
690 (Ok(resp), _) => return from_grpc_response(resp.into_inner()),
691 (Err(err), true) => {
692 if is_grpc_retryable(&err) {
694 retries += 1;
696 warn!("Retrying {} times with error = {:?}", retries, err);
697 continue;
698 } else {
699 error!(
700 err; "Failed to send request to grpc handle, retries = {}, not retryable error, aborting",
701 retries
702 );
703 return Err(err.into());
704 }
705 }
706 (Err(err), false) => {
707 error!(
708 err; "Failed to send request to grpc handle after {} retries",
709 retries,
710 );
711 return Err(err.into());
712 }
713 }
714 }
715 }
716
717 #[inline]
718 fn to_rpc_request(&self, request: Request) -> GreptimeRequest {
719 GreptimeRequest {
720 header: Some(RequestHeader {
721 catalog: self.catalog.clone(),
722 schema: self.schema.clone(),
723 authorization: self.ctx.auth_header.clone(),
724 dbname: self.dbname.clone(),
725 timezone: self.timezone.clone(),
726 tracing_context: W3cTrace::new(),
728 }),
729 request: Some(request),
730 }
731 }
732
733 pub async fn sql<S>(&self, sql: S) -> Result<Output>
735 where
736 S: AsRef<str>,
737 {
738 self.flight_request().sql(sql).await
739 }
740
741 pub async fn sql_with_hint<S>(&self, sql: S, hints: &[(&str, &str)]) -> Result<Output>
743 where
744 S: AsRef<str>,
745 {
746 self.flight_request().with_hints(hints).sql(sql).await
747 }
748
749 pub async fn sql_with_terminal_metrics<S>(
754 &self,
755 sql: S,
756 hints: &[(&str, &str)],
757 ) -> Result<OutputWithMetrics>
758 where
759 S: AsRef<str>,
760 {
761 self.flight_request()
762 .with_hints(hints)
763 .sql_with_terminal_metrics(sql)
764 .await
765 }
766
767 pub async fn logical_plan(&self, logical_plan: Vec<u8>) -> Result<Output> {
769 self.flight_request().logical_plan(logical_plan).await
770 }
771
772 #[cfg(feature = "testing")]
774 pub async fn create(&self, expr: CreateTableExpr) -> Result<Output> {
775 self.flight_request().create(expr).await
776 }
777
778 #[cfg(feature = "testing")]
780 pub async fn alter(&self, expr: AlterTableExpr) -> Result<Output> {
781 self.flight_request().alter(expr).await
782 }
783
784 async fn do_get(
785 &self,
786 request: Request,
787 options: FlightRequestOptions,
788 ) -> Result<OutputWithMetrics> {
789 let request = self.to_rpc_request(request);
790 let request = Ticket {
791 ticket: request.encode_to_vec().into(),
792 };
793
794 let mut request = tonic::Request::new(request);
795 options.apply_to(&mut request)?;
796
797 let mut client = self.client.make_flight_client(false, false)?;
798 let remote_addr = client.addr().to_string();
799
800 let response = client.mut_inner().do_get(request).await.or_else(|e| {
801 let tonic_code = e.code();
802 let e: Error = e.into();
803 error!(
804 "Failed to do Flight get, addr: {}, code: {}, source: {:?}",
805 client.addr(),
806 tonic_code,
807 e
808 );
809 Err(BoxedError::new(e)).with_context(|_| FlightGetSnafu {
810 addr: remote_addr.clone(),
811 tonic_code,
812 })
813 })?;
814
815 let flight_data_stream = response.into_inner();
816 let mut decoder = FlightDecoder::default();
817
818 let flight_message_stream = flight_data_stream.filter_map(move |flight_data| {
819 future::ready(decode_flight_data(&mut decoder, flight_data))
820 });
821
822 output_from_flight_message_stream(remote_addr, flight_message_stream).await
823 }
824
825 pub async fn do_put(&self, stream: FlightDataStream) -> Result<DoPutResponseStream> {
828 self.do_put_with_hints(stream, &[]).await
829 }
830
831 pub async fn do_put_with_hints(
833 &self,
834 stream: FlightDataStream,
835 hints: &[(&str, &str)],
836 ) -> Result<DoPutResponseStream> {
837 let mut request = tonic::Request::new(stream);
838 Self::put_hints(request.metadata_mut(), hints)?;
839
840 if let Some(AuthHeader {
841 auth_scheme: Some(AuthScheme::Basic(Basic { username, password })),
842 }) = &self.ctx.auth_header
843 {
844 let encoded = BASE64_STANDARD.encode(format!("{username}:{password}"));
845 let value = MetadataValue::from_str(&format!("Basic {encoded}"))
846 .context(InvalidTonicMetadataValueSnafu)?;
847 request.metadata_mut().insert("x-greptime-auth", value);
848 }
849
850 let db_to_put = if !self.dbname.is_empty() {
851 &self.dbname
852 } else {
853 &build_db_string(self.catalog_or_default(), self.schema_or_default())
854 };
855 request.metadata_mut().insert(
856 "x-greptime-db-name",
857 MetadataValue::from_str(db_to_put).context(InvalidTonicMetadataValueSnafu)?,
858 );
859
860 let mut client = self.client.make_control_flight_client(false, false)?;
861 let response = client.mut_inner().do_put(request).await?;
862 let response = response
863 .into_inner()
864 .map_err(Into::into)
865 .and_then(|x| future::ready(DoPutResponse::try_from(x).context(ConvertFlightDataSnafu)))
866 .boxed();
867 Ok(response)
868 }
869}
870
871impl<'a> DatabaseFlightRequest<'a> {
872 pub fn with_hints(mut self, hints: &[(&str, &str)]) -> Self {
874 self.options.hints = Database::encode_hints(hints);
875 self
876 }
877
878 pub fn with_flow_extensions(mut self, flow_extensions: &[(&str, &str)]) -> Self {
880 self.options.flow_extensions = Database::encode_flow_extensions(flow_extensions);
881 self
882 }
883
884 pub fn with_snapshot_seqs(mut self, snapshot_seqs: &HashMap<u64, u64>) -> Self {
886 self.options.snapshot_seqs = Database::encode_snapshot_seqs(snapshot_seqs);
887 self
888 }
889
890 pub fn with_timeout(mut self, timeout: Duration) -> Self {
892 self.options.timeout = Some(timeout);
893 self
894 }
895
896 pub async fn sql<S>(self, sql: S) -> Result<Output>
898 where
899 S: AsRef<str>,
900 {
901 let request = Request::Query(QueryRequest {
902 query: Some(Query::Sql(sql.as_ref().to_string())),
903 });
904 self.do_get(request)
905 .await
906 .map(OutputWithMetrics::into_output)
907 }
908
909 pub async fn sql_with_terminal_metrics<S>(self, sql: S) -> Result<OutputWithMetrics>
911 where
912 S: AsRef<str>,
913 {
914 self.query_with_terminal_metrics(QueryRequest {
915 query: Some(Query::Sql(sql.as_ref().to_string())),
916 })
917 .await
918 }
919
920 pub async fn logical_plan(self, logical_plan: Vec<u8>) -> Result<Output> {
922 self.query_with_terminal_metrics(QueryRequest {
923 query: Some(Query::LogicalPlan(logical_plan)),
924 })
925 .await
926 .map(OutputWithMetrics::into_output)
927 }
928
929 pub async fn query_with_terminal_metrics(
931 self,
932 request: QueryRequest,
933 ) -> Result<OutputWithMetrics> {
934 self.do_get(Request::Query(request)).await
935 }
936
937 #[cfg(feature = "testing")]
939 pub async fn create(self, expr: CreateTableExpr) -> Result<Output> {
940 self.do_get(Request::Ddl(DdlRequest {
941 expr: Some(DdlExpr::CreateTable(expr)),
942 }))
943 .await
944 .map(OutputWithMetrics::into_output)
945 }
946
947 #[cfg(feature = "testing")]
949 pub async fn alter(self, expr: AlterTableExpr) -> Result<Output> {
950 self.do_get(Request::Ddl(DdlRequest {
951 expr: Some(DdlExpr::AlterTable(expr)),
952 }))
953 .await
954 .map(OutputWithMetrics::into_output)
955 }
956
957 async fn do_get(self, request: Request) -> Result<OutputWithMetrics> {
958 let Self { database, options } = self;
959 database.do_get(request, options).await
960 }
961}
962
963pub fn is_grpc_retryable(err: &tonic::Status) -> bool {
965 matches!(err.code(), tonic::Code::Unavailable)
966}
967
968#[derive(Default, Debug, Clone)]
969struct FlightContext {
970 auth_header: Option<AuthHeader>,
971}
972
973#[cfg(test)]
974mod tests {
975 use std::sync::Arc;
976 use std::task::{Context, Poll};
977
978 use api::v1::auth_header::AuthScheme;
979 use api::v1::{AuthHeader, Basic};
980 use common_error::ext::{ErrorExt, RetryHint};
981 use common_error::status_code::StatusCode;
982 use common_error::{GREPTIME_DB_HEADER_ERROR_CODE, GREPTIME_DB_HEADER_ERROR_RETRY_HINT};
983 use common_query::OutputData;
984 use common_recordbatch::{OrderOption, RecordBatch, RecordBatchStream};
985 use datatypes::prelude::{ConcreteDataType, VectorRef};
986 use datatypes::schema::{ColumnSchema, Schema};
987 use datatypes::vectors::Int32Vector;
988 use futures_util::StreamExt;
989 use tonic::codegen::http::{HeaderMap, HeaderValue};
990 use tonic::metadata::MetadataMap;
991 use tonic::{Code, Status};
992
993 use super::*;
994 use crate::error::TonicSnafu;
995
996 struct MockMetricsStream {
997 schema: datatypes::schema::SchemaRef,
998 batch: Option<RecordBatch>,
999 metrics: RecordBatchMetrics,
1000 terminal_metrics_only: bool,
1001 }
1002
1003 impl Stream for MockMetricsStream {
1004 type Item = common_recordbatch::error::Result<RecordBatch>;
1005
1006 fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1007 Poll::Ready(self.batch.take().map(Ok))
1008 }
1009 }
1010
1011 impl RecordBatchStream for MockMetricsStream {
1012 fn name(&self) -> &str {
1013 "MockMetricsStream"
1014 }
1015
1016 fn schema(&self) -> datatypes::schema::SchemaRef {
1017 self.schema.clone()
1018 }
1019
1020 fn output_ordering(&self) -> Option<&[OrderOption]> {
1021 None
1022 }
1023
1024 fn metrics(&self) -> Option<RecordBatchMetrics> {
1025 if self.terminal_metrics_only && self.batch.is_some() {
1026 return None;
1027 }
1028 Some(self.metrics.clone())
1029 }
1030 }
1031
1032 fn terminal_metrics_json() -> String {
1033 terminal_metrics_json_with_seq(42)
1034 }
1035
1036 fn terminal_metrics_json_with_seq(seq: u64) -> String {
1037 serde_json::to_string(&RecordBatchMetrics {
1038 region_watermarks: vec![common_recordbatch::adapter::RegionWatermarkEntry {
1039 region_id: 7,
1040 watermark: Some(seq),
1041 }],
1042 ..Default::default()
1043 })
1044 .unwrap()
1045 }
1046
1047 #[test]
1048 fn test_put_flow_extensions_preserves_comma_bearing_values() {
1049 let mut metadata = MetadataMap::new();
1050 Database::put_metadata_value(
1051 &mut metadata,
1052 FLOW_EXTENSIONS_METADATA_KEY,
1053 Database::encode_flow_extensions(&[
1054 ("flow.return_region_seq", "true"),
1055 ("flow.incremental_after_seqs", r#"{"1":10,"2":20}"#),
1056 ])
1057 .unwrap(),
1058 )
1059 .unwrap();
1060
1061 let value = metadata
1062 .get(FLOW_EXTENSIONS_METADATA_KEY)
1063 .unwrap()
1064 .to_str()
1065 .unwrap();
1066 let decoded: Vec<(String, String)> = serde_json::from_str(value).unwrap();
1067 assert_eq!(
1068 decoded,
1069 vec![
1070 ("flow.return_region_seq".to_string(), "true".to_string()),
1071 (
1072 "flow.incremental_after_seqs".to_string(),
1073 r#"{"1":10,"2":20}"#.to_string()
1074 ),
1075 ]
1076 );
1077 }
1078
1079 #[test]
1080 fn test_put_snapshot_seqs_preserves_u64_precision() {
1081 let mut metadata = MetadataMap::new();
1082 let snapshot_seqs = std::collections::HashMap::from([
1083 (u64::MAX, u64::MAX - 1),
1084 (9_007_199_254_740_993_u64, 9_007_199_254_740_995_u64),
1085 ]);
1086
1087 Database::put_metadata_value(
1088 &mut metadata,
1089 SNAPSHOT_SEQS_METADATA_KEY,
1090 Database::encode_snapshot_seqs(&snapshot_seqs).unwrap(),
1091 )
1092 .unwrap();
1093
1094 let value = metadata
1095 .get(SNAPSHOT_SEQS_METADATA_KEY)
1096 .unwrap()
1097 .to_str()
1098 .unwrap();
1099 let decoded: std::collections::HashMap<u64, u64> = serde_json::from_str(value).unwrap();
1100 assert_eq!(decoded, snapshot_seqs);
1101 }
1102
1103 #[test]
1104 fn test_flight_request_builder_applies_request_options() {
1105 let database = Database::new("greptime", "public", Client::default());
1106 let snapshot_seqs = HashMap::from([(42, 99)]);
1107 let request = database
1108 .flight_request()
1109 .with_hints(&[("query_parallelism", "1")])
1110 .with_flow_extensions(&[("flow.return_region_seq", "true")])
1111 .with_snapshot_seqs(&snapshot_seqs)
1112 .with_timeout(Duration::from_millis(50));
1113 let mut tonic_request = tonic::Request::new(());
1114
1115 request.options.apply_to(&mut tonic_request).unwrap();
1116
1117 let metadata = tonic_request.metadata();
1118 assert_eq!(
1119 metadata.get(HINTS_METADATA_KEY).unwrap(),
1120 "query_parallelism=1"
1121 );
1122 assert_eq!(
1123 serde_json::from_str::<Vec<(String, String)>>(
1124 metadata
1125 .get(FLOW_EXTENSIONS_METADATA_KEY)
1126 .unwrap()
1127 .to_str()
1128 .unwrap(),
1129 )
1130 .unwrap(),
1131 vec![("flow.return_region_seq".to_string(), "true".to_string())]
1132 );
1133 assert_eq!(
1134 serde_json::from_str::<HashMap<u64, u64>>(
1135 metadata
1136 .get(SNAPSHOT_SEQS_METADATA_KEY)
1137 .unwrap()
1138 .to_str()
1139 .unwrap(),
1140 )
1141 .unwrap(),
1142 snapshot_seqs
1143 );
1144 assert!(metadata.get("grpc-timeout").is_some());
1145 }
1146
1147 #[test]
1148 fn test_flight_ctx() {
1149 let mut ctx = FlightContext::default();
1150 assert!(ctx.auth_header.is_none());
1151
1152 let basic = AuthScheme::Basic(Basic {
1153 username: "u".to_string(),
1154 password: "p".to_string(),
1155 });
1156
1157 ctx.auth_header = Some(AuthHeader {
1158 auth_scheme: Some(basic),
1159 });
1160
1161 assert!(matches!(
1162 ctx.auth_header,
1163 Some(AuthHeader {
1164 auth_scheme: Some(AuthScheme::Basic(_)),
1165 })
1166 ));
1167 }
1168
1169 #[test]
1170 fn test_from_tonic_status() {
1171 let expected = TonicSnafu {
1172 code: StatusCode::Internal,
1173 msg: "blabla".to_string(),
1174 tonic_code: Code::Internal,
1175 retry_hint: RetryHint::NonRetryable,
1176 }
1177 .build();
1178
1179 let status = Status::new(Code::Internal, "blabla");
1180 let actual: Error = status.into();
1181
1182 assert_eq!(expected.to_string(), actual.to_string());
1183 assert_eq!(expected.retry_hint(), actual.retry_hint());
1184 assert_eq!(expected.should_retry(), actual.should_retry());
1185 }
1186
1187 #[test]
1188 fn test_flight_stream_error_preserves_addr_and_message() {
1189 let error = flight_stream_error(
1190 "127.0.0.1:4001",
1191 Status::out_of_range("message length too large").into(),
1192 );
1193
1194 assert!(matches!(
1195 &error,
1196 Error::FlightStream {
1197 addr,
1198 tonic_code: Code::OutOfRange,
1199 message,
1200 ..
1201 } if addr == "127.0.0.1:4001" && message == "message length too large"
1202 ));
1203 assert_eq!(
1204 "Failed to receive Flight data from 127.0.0.1:4001, code: Operation was attempted past the valid range: message length too large",
1205 error.to_string(),
1206 );
1207 }
1208
1209 #[test]
1210 fn test_from_tonic_status_with_retry_hint() {
1211 let mut headers = HeaderMap::new();
1212 headers.insert(
1213 GREPTIME_DB_HEADER_ERROR_CODE,
1214 HeaderValue::from(StatusCode::Internal as u32),
1215 );
1216 headers.insert(
1217 GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
1218 HeaderValue::from_static(RetryHint::Retryable.as_str()),
1219 );
1220 let status =
1221 Status::with_metadata(Code::Internal, "blabla", MetadataMap::from_headers(headers));
1222
1223 let actual: Error = status.into();
1224
1225 assert_eq!(actual.retry_hint(), RetryHint::Retryable);
1226 assert!(actual.should_retry());
1227 }
1228
1229 #[test]
1230 fn test_from_tonic_status_fallback() {
1231 let mut headers = HeaderMap::new();
1232 headers.insert(
1233 GREPTIME_DB_HEADER_ERROR_CODE,
1234 HeaderValue::from(StatusCode::InvalidArguments as u32),
1235 );
1236 let status =
1237 Status::with_metadata(Code::Internal, "blabla", MetadataMap::from_headers(headers));
1238
1239 let actual: Error = status.into();
1240
1241 assert_eq!(actual.retry_hint(), RetryHint::NonRetryable);
1242 assert!(!actual.should_retry());
1243 }
1244
1245 #[test]
1246 fn test_should_retry_preserves_transport_retry() {
1247 let status = Status::new(Code::Unavailable, "blabla");
1248 let actual: Error = status.into();
1249
1250 assert!(actual.should_retry());
1251 }
1252
1253 #[tokio::test]
1254 async fn test_query_with_terminal_metrics_tracks_terminal_only_metrics() {
1255 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1256 "v",
1257 ConcreteDataType::int32_datatype(),
1258 false,
1259 )]));
1260 let batch = RecordBatch::new(
1261 schema.clone(),
1262 vec![Arc::new(Int32Vector::from_slice([1, 2])) as VectorRef],
1263 )
1264 .unwrap();
1265 let output = Output::new_with_stream(Box::pin(MockMetricsStream {
1266 schema,
1267 batch: Some(batch),
1268 metrics: RecordBatchMetrics {
1269 region_watermarks: vec![common_recordbatch::adapter::RegionWatermarkEntry {
1270 region_id: 7,
1271 watermark: Some(42),
1272 }],
1273 ..Default::default()
1274 },
1275 terminal_metrics_only: true,
1276 }));
1277
1278 let result = OutputWithMetrics::from_output(output);
1279 let terminal_metrics = result.metrics.clone();
1280 assert!(!terminal_metrics.is_ready());
1281 assert!(terminal_metrics.get().is_none());
1282
1283 let OutputData::Stream(mut stream) = result.output.data else {
1284 panic!("expected stream output");
1285 };
1286 while stream.next().await.is_some() {}
1287
1288 assert!(terminal_metrics.is_ready());
1289 assert_eq!(
1290 terminal_metrics.participating_regions(),
1291 Some(std::collections::BTreeSet::from([7_u64]))
1292 );
1293 assert_eq!(
1294 terminal_metrics.region_watermark_map(),
1295 Some(std::collections::HashMap::from([(7_u64, 42_u64)]))
1296 );
1297 }
1298
1299 #[test]
1300 fn test_parse_terminal_metrics_rejects_invalid_json() {
1301 assert!(parse_terminal_metrics("{not-json}").is_err());
1302 }
1303
1304 #[tokio::test]
1305 async fn test_affected_rows_inline_metrics_are_parsed() {
1306 let output = output_from_flight_message_stream(
1307 "test-peer".to_string(),
1308 futures_util::stream::iter(vec![Ok(FlightMessage::AffectedRows {
1309 rows: 3,
1310 metrics: Some(terminal_metrics_json()),
1311 })] as Vec<Result<FlightMessage>>),
1312 )
1313 .await
1314 .unwrap();
1315
1316 assert!(matches!(output.output.data, OutputData::AffectedRows(3)));
1317 assert!(output.metrics.is_ready());
1318 assert_eq!(
1319 output.metrics.region_watermark_map(),
1320 Some(std::collections::HashMap::from([(7, 42)]))
1321 );
1322 }
1323
1324 #[tokio::test]
1325 async fn test_affected_rows_inline_metrics_rejects_trailing_metrics() {
1326 let metrics_json = terminal_metrics_json();
1327 let err = output_from_flight_message_stream(
1328 "test-peer".to_string(),
1329 futures_util::stream::iter(vec![
1330 Ok(FlightMessage::AffectedRows {
1331 rows: 3,
1332 metrics: Some(metrics_json.clone()),
1333 }),
1334 Ok(FlightMessage::Metrics(metrics_json)),
1335 ] as Vec<Result<FlightMessage>>),
1336 )
1337 .await
1338 .unwrap_err();
1339
1340 assert!(
1341 err.to_string().contains("already carries Metrics"),
1342 "unexpected error: {err:?}"
1343 );
1344 }
1345
1346 #[tokio::test]
1347 async fn test_invalid_terminal_metrics_after_record_batch_yields_batch_then_error() {
1348 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1349 "v",
1350 ConcreteDataType::int32_datatype(),
1351 false,
1352 )]));
1353 let batch = RecordBatch::new(
1354 schema.clone(),
1355 vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1356 )
1357 .unwrap();
1358 let output = output_from_flight_message_stream(
1359 "test-peer".to_string(),
1360 futures_util::stream::iter(vec![
1361 Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1362 Ok(FlightMessage::RecordBatch(batch.into_df_record_batch())),
1363 Ok(FlightMessage::Metrics("{not-json}".to_string())),
1364 ] as Vec<Result<FlightMessage>>),
1365 )
1366 .await
1367 .unwrap();
1368 let terminal_metrics = output.metrics.clone();
1369 let OutputData::Stream(mut record_batch_stream) = output.output.data else {
1370 panic!("expected stream output");
1371 };
1372
1373 let batch = record_batch_stream.next().await.unwrap().unwrap();
1374 assert_eq!(batch.num_rows(), 1);
1375
1376 let err = record_batch_stream.next().await.unwrap().unwrap_err();
1377 assert_eq!("External error", err.to_string());
1378 assert!(
1379 format!("{err:?}").contains("Invalid terminal metrics message"),
1380 "unexpected error: {err:?}"
1381 );
1382 assert!(record_batch_stream.next().await.is_none());
1383 assert!(terminal_metrics.is_ready());
1384 assert!(terminal_metrics.get().is_none());
1385 }
1386
1387 #[tokio::test]
1388 async fn test_record_batch_stream_continues_after_partial_metrics() {
1389 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1390 "v",
1391 ConcreteDataType::int32_datatype(),
1392 false,
1393 )]));
1394 let first_batch = RecordBatch::new(
1395 schema.clone(),
1396 vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1397 )
1398 .unwrap();
1399 let second_batch = RecordBatch::new(
1400 schema.clone(),
1401 vec![Arc::new(Int32Vector::from_slice([2])) as VectorRef],
1402 )
1403 .unwrap();
1404 let output = output_from_flight_message_stream(
1405 "test-peer".to_string(),
1406 futures_util::stream::iter(vec![
1407 Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1408 Ok(FlightMessage::RecordBatch(
1409 first_batch.into_df_record_batch(),
1410 )),
1411 Ok(FlightMessage::Metrics(terminal_metrics_json_with_seq(1))),
1412 Ok(FlightMessage::RecordBatch(
1413 second_batch.into_df_record_batch(),
1414 )),
1415 Ok(FlightMessage::Metrics(terminal_metrics_json_with_seq(2))),
1416 ] as Vec<Result<FlightMessage>>),
1417 )
1418 .await
1419 .unwrap();
1420 let terminal_metrics = output.metrics.clone();
1421 let OutputData::Stream(mut record_batch_stream) = output.output.data else {
1422 panic!("expected stream output");
1423 };
1424
1425 let first_batch = record_batch_stream.next().await.unwrap().unwrap();
1426 assert_eq!(first_batch.num_rows(), 1);
1427 let second_batch = record_batch_stream.next().await.unwrap().unwrap();
1428 assert_eq!(second_batch.num_rows(), 1);
1429 assert!(record_batch_stream.next().await.is_none());
1430
1431 assert!(terminal_metrics.is_ready());
1432 assert_eq!(
1433 terminal_metrics.region_watermark_map(),
1434 Some(std::collections::HashMap::from([(7, 2)]))
1435 );
1436 }
1437
1438 #[test]
1439 fn test_output_metrics_distinguishes_empty_region_watermarks_from_absence() {
1440 let metrics = OutputMetrics::default();
1441 metrics.update(Some(RecordBatchMetrics::default()));
1442
1443 assert_eq!(
1444 metrics.participating_regions(),
1445 Some(std::collections::BTreeSet::new())
1446 );
1447 assert_eq!(
1448 metrics.region_watermark_map(),
1449 Some(std::collections::HashMap::new())
1450 );
1451 }
1452}