1use std::pin::Pin;
16use std::str::FromStr;
17use std::sync::atomic::{AtomicBool, Ordering};
18use std::sync::{Arc, RwLock};
19use std::task::{Context, Poll};
20
21use api::v1::auth_header::AuthScheme;
22use api::v1::ddl_request::Expr as DdlExpr;
23use api::v1::greptime_database_client::GreptimeDatabaseClient;
24use api::v1::greptime_request::Request;
25use api::v1::query_request::Query;
26use api::v1::{
27 AlterTableExpr, AuthHeader, Basic, CreateTableExpr, DdlRequest, GreptimeRequest,
28 InsertRequests, QueryRequest, RequestHeader, RowInsertRequests,
29};
30use arc_swap::ArcSwapOption;
31use arrow_flight::{FlightData, Ticket};
32use async_stream::stream;
33use base64::Engine;
34use base64::prelude::BASE64_STANDARD;
35use common_catalog::build_db_string;
36use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
37use common_error::ext::BoxedError;
38use common_grpc::flight::do_put::DoPutResponse;
39use common_grpc::flight::{
40 FLOW_EXTENSIONS_METADATA_KEY, FlightDecoder, FlightMessage, SNAPSHOT_SEQS_METADATA_KEY,
41};
42use common_query::Output;
43use common_recordbatch::adapter::RecordBatchMetrics;
44use common_recordbatch::error::ExternalSnafu;
45use common_recordbatch::{OrderOption, RecordBatch, RecordBatchStream, RecordBatchStreamWrapper};
46use common_telemetry::tracing::Span;
47use common_telemetry::tracing_context::W3cTrace;
48use common_telemetry::{error, warn};
49use futures::future;
50use futures_util::{Stream, StreamExt, TryStreamExt};
51use prost::Message;
52use snafu::ResultExt;
53use tonic::metadata::{AsciiMetadataKey, AsciiMetadataValue, MetadataMap, MetadataValue};
54use tonic::transport::Channel;
55
56use crate::error::{
57 ConvertFlightDataSnafu, Error, FlightGetSnafu, IllegalFlightMessagesSnafu,
58 InvalidTonicMetadataValueSnafu,
59};
60use crate::flight::decode_flight_data;
61use crate::{Client, Result, error, from_grpc_response};
62
63type FlightDataStream = Pin<Box<dyn Stream<Item = FlightData> + Send>>;
64
65type DoPutResponseStream = Pin<Box<dyn Stream<Item = Result<DoPutResponse>>>>;
66
67#[derive(Debug, Clone, Default)]
72pub struct OutputMetrics {
73 inner: Arc<OutputMetricsInner>,
74}
75
76#[derive(Debug, Default)]
77struct OutputMetricsInner {
78 metrics: RwLock<Option<RecordBatchMetrics>>,
79 ready: AtomicBool,
80}
81
82impl OutputMetrics {
83 fn new() -> Self {
84 Self::default()
85 }
86
87 pub fn update(&self, metrics: Option<RecordBatchMetrics>) {
89 *self.inner.metrics.write().unwrap() = metrics;
90 }
91
92 pub fn mark_ready(&self) {
94 let _ = self
95 .inner
96 .ready
97 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire);
98 }
99
100 pub fn is_ready(&self) -> bool {
104 self.inner.ready.load(Ordering::Acquire)
105 }
106
107 pub fn get(&self) -> Option<RecordBatchMetrics> {
109 self.inner.metrics.read().unwrap().clone()
110 }
111
112 pub fn region_watermark_map(&self) -> Option<std::collections::HashMap<u64, u64>> {
118 Some(
119 self.get()?
120 .region_watermarks
121 .into_iter()
122 .filter_map(|entry| entry.watermark.map(|seq| (entry.region_id, seq)))
123 .collect::<std::collections::HashMap<_, _>>(),
124 )
125 }
126
127 pub fn participating_regions(&self) -> Option<std::collections::BTreeSet<u64>> {
130 Some(
131 self.get()?
132 .region_watermarks
133 .into_iter()
134 .map(|entry| entry.region_id)
135 .collect::<std::collections::BTreeSet<_>>(),
136 )
137 }
138}
139
140#[derive(Debug)]
146pub struct OutputWithMetrics {
147 pub output: Output,
148 pub metrics: OutputMetrics,
149}
150
151impl OutputWithMetrics {
152 pub fn from_output(output: Output) -> Self {
157 let terminal_metrics = OutputMetrics::new();
158 let output = attach_terminal_metrics(output, &terminal_metrics);
159 Self {
160 output,
161 metrics: terminal_metrics,
162 }
163 }
164
165 pub fn region_watermark_map(&self) -> Option<std::collections::HashMap<u64, u64>> {
167 self.metrics.region_watermark_map()
168 }
169
170 pub fn participating_regions(&self) -> Option<std::collections::BTreeSet<u64>> {
172 self.metrics.participating_regions()
173 }
174
175 pub fn into_output(self) -> Output {
177 self.output
178 }
179}
180
181fn parse_terminal_metrics(metrics_json: &str) -> Result<RecordBatchMetrics> {
182 serde_json::from_str(metrics_json).map_err(|e| {
183 IllegalFlightMessagesSnafu {
184 reason: format!("Invalid terminal metrics message: {e}"),
185 }
186 .build()
187 })
188}
189
190struct StreamWithMetrics {
191 stream: common_recordbatch::SendableRecordBatchStream,
192 metrics: OutputMetrics,
193}
194
195impl StreamWithMetrics {
196 fn new(stream: common_recordbatch::SendableRecordBatchStream, metrics: OutputMetrics) -> Self {
197 Self { stream, metrics }
198 }
199
200 fn sync_terminal_metrics(&self) {
201 self.metrics.update(self.stream.metrics());
202 }
203}
204
205impl RecordBatchStream for StreamWithMetrics {
206 fn name(&self) -> &str {
207 self.stream.name()
208 }
209
210 fn schema(&self) -> datatypes::schema::SchemaRef {
211 self.stream.schema()
212 }
213
214 fn output_ordering(&self) -> Option<&[OrderOption]> {
215 self.stream.output_ordering()
216 }
217
218 fn metrics(&self) -> Option<RecordBatchMetrics> {
219 self.sync_terminal_metrics();
220 self.metrics.get()
221 }
222}
223
224impl Stream for StreamWithMetrics {
225 type Item = common_recordbatch::error::Result<RecordBatch>;
226
227 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
228 let polled = Pin::new(&mut self.stream).poll_next(cx);
229 if let Poll::Ready(None) = &polled {
230 self.sync_terminal_metrics();
231 self.metrics.mark_ready();
232 }
233 polled
234 }
235
236 fn size_hint(&self) -> (usize, Option<usize>) {
237 self.stream.size_hint()
238 }
239}
240
241fn attach_terminal_metrics(output: Output, terminal_metrics: &OutputMetrics) -> Output {
242 let Output { data, meta } = output;
243 let data = match data {
244 common_query::OutputData::Stream(stream) => {
245 terminal_metrics.update(stream.metrics());
246 common_query::OutputData::Stream(Box::pin(StreamWithMetrics::new(
247 stream,
248 terminal_metrics.clone(),
249 )))
250 }
251 other => {
252 terminal_metrics.mark_ready();
253 other
254 }
255 };
256 Output::new(data, meta)
257}
258
259async fn output_from_flight_message_stream<S>(
260 mut flight_message_stream: S,
261) -> Result<OutputWithMetrics>
262where
263 S: Stream<Item = Result<FlightMessage>> + Send + Unpin + 'static,
264{
265 let Some(first_flight_message) = flight_message_stream.next().await else {
266 return IllegalFlightMessagesSnafu {
267 reason: "Expect the response not to be empty",
268 }
269 .fail();
270 };
271
272 let first_flight_message = first_flight_message?;
273
274 match first_flight_message {
275 FlightMessage::AffectedRows { rows, metrics } => {
276 let terminal_metrics = OutputMetrics::new();
277 if let Some(metrics) = metrics {
278 terminal_metrics.update(Some(parse_terminal_metrics(&metrics)?));
279 }
280 let next_message = flight_message_stream.next().await.transpose()?;
281 match next_message {
282 None => terminal_metrics.mark_ready(),
283 Some(FlightMessage::Metrics(s)) if terminal_metrics.get().is_none() => {
284 terminal_metrics.update(Some(parse_terminal_metrics(&s)?));
285 terminal_metrics.mark_ready();
286 }
287 Some(FlightMessage::Metrics(_)) => {
288 return IllegalFlightMessagesSnafu {
289 reason: "'AffectedRows' Flight metadata already carries Metrics and cannot be followed by another Metrics message",
290 }
291 .fail();
292 }
293 Some(other) => {
294 return IllegalFlightMessagesSnafu {
295 reason: format!(
296 "'AffectedRows' Flight message can only be followed by a Metrics message, got {other:?}"
297 ),
298 }
299 .fail();
300 }
301 }
302 Ok(OutputWithMetrics {
303 output: Output::new_with_affected_rows(rows),
304 metrics: terminal_metrics,
305 })
306 }
307 FlightMessage::RecordBatch(_) | FlightMessage::Metrics(_) => IllegalFlightMessagesSnafu {
308 reason: "The first flight message cannot be a RecordBatch or Metrics message",
309 }
310 .fail(),
311 FlightMessage::Schema(schema) => {
312 let metrics = Arc::new(ArcSwapOption::from(None));
313 let metrics_ref = metrics.clone();
314 let schema = Arc::new(
315 datatypes::schema::Schema::try_from(schema).context(error::ConvertSchemaSnafu)?,
316 );
317 let schema_cloned = schema.clone();
318 let stream = Box::pin(stream!({
319 while let Some(flight_message_item) = flight_message_stream.next().await {
320 let flight_message = match flight_message_item {
321 Ok(message) => message,
322 Err(e) => {
323 yield Err(BoxedError::new(e)).context(ExternalSnafu);
324 break;
325 }
326 };
327
328 match flight_message {
329 FlightMessage::RecordBatch(arrow_batch) => {
330 yield Ok(RecordBatch::from_df_record_batch(
331 schema_cloned.clone(),
332 arrow_batch,
333 ))
334 }
335 FlightMessage::Metrics(s) => {
336 match parse_terminal_metrics(&s) {
337 Ok(m) => {
338 metrics_ref.swap(Some(Arc::new(m)));
339 }
340 Err(e) => {
341 yield Err(BoxedError::new(e)).context(ExternalSnafu);
342 }
343 };
344 }
345 FlightMessage::AffectedRows { .. } | FlightMessage::Schema(_) => {
346 yield IllegalFlightMessagesSnafu {
347 reason: format!(
348 "A Schema message must be succeeded exclusively by a set of RecordBatch messages, flight_message: {:?}",
349 flight_message
350 )
351 }
352 .fail()
353 .map_err(BoxedError::new)
354 .context(ExternalSnafu);
355 break;
356 }
357 }
358 }
359 }));
360 let record_batch_stream = RecordBatchStreamWrapper {
361 schema,
362 stream,
363 output_ordering: None,
364 metrics,
365 span: Span::current(),
366 };
367 Ok(OutputWithMetrics::from_output(Output::new_with_stream(
368 Box::pin(record_batch_stream),
369 )))
370 }
371 }
372}
373
374#[derive(Clone, Debug, Default)]
375pub struct Database {
376 catalog: String,
380 schema: String,
381 dbname: String,
384 timezone: String,
387
388 client: Client,
389 ctx: FlightContext,
390}
391
392pub struct DatabaseClient {
393 pub addr: String,
394 pub inner: GreptimeDatabaseClient<Channel>,
395}
396
397impl DatabaseClient {
398 pub fn inspect_err<'a>(&'a self, context: &'a str) -> impl Fn(&tonic::Status) + 'a {
400 let addr = &self.addr;
401 move |status| {
402 error!("Failed to {context} request, peer: {addr}, status: {status:?}");
403 }
404 }
405}
406
407fn make_database_client(client: &Client) -> Result<DatabaseClient> {
408 let (addr, channel) = client.find_channel()?;
409 Ok(DatabaseClient {
410 addr,
411 inner: GreptimeDatabaseClient::new(channel)
412 .max_decoding_message_size(client.max_grpc_recv_message_size())
413 .max_encoding_message_size(client.max_grpc_send_message_size()),
414 })
415}
416
417impl Database {
418 pub fn new(catalog: impl Into<String>, schema: impl Into<String>, client: Client) -> Self {
420 Self {
421 catalog: catalog.into(),
422 schema: schema.into(),
423 dbname: String::default(),
424 timezone: String::default(),
425 client,
426 ctx: FlightContext::default(),
427 }
428 }
429
430 pub fn new_with_dbname(dbname: impl Into<String>, client: Client) -> Self {
438 Self {
439 catalog: String::default(),
440 schema: String::default(),
441 timezone: String::default(),
442 dbname: dbname.into(),
443 client,
444 ctx: FlightContext::default(),
445 }
446 }
447
448 pub fn set_catalog(&mut self, catalog: impl Into<String>) {
450 self.catalog = catalog.into();
451 }
452
453 fn catalog_or_default(&self) -> &str {
454 if self.catalog.is_empty() {
455 DEFAULT_CATALOG_NAME
456 } else {
457 &self.catalog
458 }
459 }
460
461 pub fn set_schema(&mut self, schema: impl Into<String>) {
463 self.schema = schema.into();
464 }
465
466 fn schema_or_default(&self) -> &str {
467 if self.schema.is_empty() {
468 DEFAULT_SCHEMA_NAME
469 } else {
470 &self.schema
471 }
472 }
473
474 pub fn set_timezone(&mut self, timezone: impl Into<String>) {
476 self.timezone = timezone.into();
477 }
478
479 pub fn set_auth(&mut self, auth: AuthScheme) {
481 self.ctx.auth_header = Some(AuthHeader {
482 auth_scheme: Some(auth),
483 });
484 }
485
486 pub async fn insert(&self, requests: InsertRequests) -> Result<u32> {
488 self.handle(Request::Inserts(requests)).await
489 }
490
491 pub async fn insert_with_hints(
493 &self,
494 requests: InsertRequests,
495 hints: &[(&str, &str)],
496 ) -> Result<u32> {
497 let mut client = make_database_client(&self.client)?;
498 let request = self.to_rpc_request(Request::Inserts(requests));
499
500 let mut request = tonic::Request::new(request);
501 let metadata = request.metadata_mut();
502 Self::put_hints(metadata, hints)?;
503
504 let response = client
505 .inner
506 .handle(request)
507 .await
508 .inspect_err(client.inspect_err("insert_with_hints"))?
509 .into_inner();
510 from_grpc_response(response)
511 }
512
513 pub async fn row_inserts(&self, requests: RowInsertRequests) -> Result<u32> {
515 self.handle(Request::RowInserts(requests)).await
516 }
517
518 pub async fn row_inserts_with_hints(
520 &self,
521 requests: RowInsertRequests,
522 hints: &[(&str, &str)],
523 ) -> Result<u32> {
524 let mut client = make_database_client(&self.client)?;
525 let request = self.to_rpc_request(Request::RowInserts(requests));
526
527 let mut request = tonic::Request::new(request);
528 let metadata = request.metadata_mut();
529 Self::put_hints(metadata, hints)?;
530
531 let response = client
532 .inner
533 .handle(request)
534 .await
535 .inspect_err(client.inspect_err("row_inserts_with_hints"))?
536 .into_inner();
537 from_grpc_response(response)
538 }
539
540 fn put_hints(metadata: &mut MetadataMap, hints: &[(&str, &str)]) -> Result<()> {
541 let Some(value) = hints
542 .iter()
543 .map(|(k, v)| format!("{}={}", k, v))
544 .reduce(|a, b| format!("{},{}", a, b))
545 else {
546 return Ok(());
547 };
548
549 let key = AsciiMetadataKey::from_static("x-greptime-hints");
550 let value = AsciiMetadataValue::from_str(&value).context(InvalidTonicMetadataValueSnafu)?;
551 metadata.insert(key, value);
552 Ok(())
553 }
554
555 fn put_flow_extensions(
556 metadata: &mut MetadataMap,
557 flow_extensions: &[(&str, &str)],
558 ) -> Result<()> {
559 if flow_extensions.is_empty() {
560 return Ok(());
561 }
562
563 let value = serde_json::to_string(&flow_extensions.to_vec())
564 .expect("flow extension pairs should serialize");
565 Self::put_metadata_value(metadata, FLOW_EXTENSIONS_METADATA_KEY, value)
566 }
567
568 fn put_snapshot_seqs(
569 metadata: &mut MetadataMap,
570 snapshot_seqs: &std::collections::HashMap<u64, u64>,
571 ) -> Result<()> {
572 if snapshot_seqs.is_empty() {
573 return Ok(());
574 }
575
576 let value =
577 serde_json::to_string(snapshot_seqs).expect("snapshot sequence map should serialize");
578 Self::put_metadata_value(metadata, SNAPSHOT_SEQS_METADATA_KEY, value)
579 }
580
581 fn put_metadata_value(
582 metadata: &mut MetadataMap,
583 key: &'static str,
584 value: String,
585 ) -> Result<()> {
586 let key = AsciiMetadataKey::from_static(key);
587 let value = AsciiMetadataValue::from_str(&value).context(InvalidTonicMetadataValueSnafu)?;
588 metadata.insert(key, value);
589 Ok(())
590 }
591
592 pub async fn handle(&self, request: Request) -> Result<u32> {
594 let mut client = make_database_client(&self.client)?;
595 let request = self.to_rpc_request(request);
596 let response = client
597 .inner
598 .handle(request)
599 .await
600 .inspect_err(client.inspect_err("handle"))?
601 .into_inner();
602 from_grpc_response(response)
603 }
604
605 pub async fn handle_with_retry(
608 &self,
609 request: Request,
610 max_retries: u32,
611 hints: &[(&str, &str)],
612 ) -> Result<u32> {
613 let mut client = make_database_client(&self.client)?;
614 let mut retries = 0;
615
616 let request = self.to_rpc_request(request);
617
618 loop {
619 let mut tonic_request = tonic::Request::new(request.clone());
620 let metadata = tonic_request.metadata_mut();
621 Self::put_hints(metadata, hints)?;
622 let raw_response = client
623 .inner
624 .handle(tonic_request)
625 .await
626 .inspect_err(client.inspect_err("handle"));
627 match (raw_response, retries < max_retries) {
628 (Ok(resp), _) => return from_grpc_response(resp.into_inner()),
629 (Err(err), true) => {
630 if is_grpc_retryable(&err) {
632 retries += 1;
634 warn!("Retrying {} times with error = {:?}", retries, err);
635 continue;
636 } else {
637 error!(
638 err; "Failed to send request to grpc handle, retries = {}, not retryable error, aborting",
639 retries
640 );
641 return Err(err.into());
642 }
643 }
644 (Err(err), false) => {
645 error!(
646 err; "Failed to send request to grpc handle after {} retries",
647 retries,
648 );
649 return Err(err.into());
650 }
651 }
652 }
653 }
654
655 #[inline]
656 fn to_rpc_request(&self, request: Request) -> GreptimeRequest {
657 GreptimeRequest {
658 header: Some(RequestHeader {
659 catalog: self.catalog.clone(),
660 schema: self.schema.clone(),
661 authorization: self.ctx.auth_header.clone(),
662 dbname: self.dbname.clone(),
663 timezone: self.timezone.clone(),
664 tracing_context: W3cTrace::new(),
666 }),
667 request: Some(request),
668 }
669 }
670
671 pub async fn sql<S>(&self, sql: S) -> Result<Output>
673 where
674 S: AsRef<str>,
675 {
676 self.sql_with_hint(sql, &[]).await
677 }
678
679 pub async fn sql_with_hint<S>(&self, sql: S, hints: &[(&str, &str)]) -> Result<Output>
681 where
682 S: AsRef<str>,
683 {
684 let request = Request::Query(QueryRequest {
685 query: Some(Query::Sql(sql.as_ref().to_string())),
686 });
687 self.do_get(request, hints, &[], &Default::default())
688 .await
689 .map(OutputWithMetrics::into_output)
690 }
691
692 pub async fn sql_with_terminal_metrics<S>(
697 &self,
698 sql: S,
699 hints: &[(&str, &str)],
700 ) -> Result<OutputWithMetrics>
701 where
702 S: AsRef<str>,
703 {
704 self.query_with_terminal_metrics_and_flow_extensions(
705 QueryRequest {
706 query: Some(Query::Sql(sql.as_ref().to_string())),
707 },
708 hints,
709 &[],
710 &Default::default(),
711 )
712 .await
713 }
714
715 pub async fn logical_plan(&self, logical_plan: Vec<u8>) -> Result<Output> {
717 self.query_with_terminal_metrics_and_flow_extensions(
718 QueryRequest {
719 query: Some(Query::LogicalPlan(logical_plan)),
720 },
721 &[],
722 &[],
723 &Default::default(),
724 )
725 .await
726 .map(OutputWithMetrics::into_output)
727 }
728
729 pub async fn query_with_terminal_metrics_and_flow_extensions(
734 &self,
735 request: QueryRequest,
736 hints: &[(&str, &str)],
737 flow_extensions: &[(&str, &str)],
738 snapshot_seqs: &std::collections::HashMap<u64, u64>,
739 ) -> Result<OutputWithMetrics> {
740 self.do_get(
741 Request::Query(request),
742 hints,
743 flow_extensions,
744 snapshot_seqs,
745 )
746 .await
747 }
748
749 pub async fn create(&self, expr: CreateTableExpr) -> Result<Output> {
751 let request = Request::Ddl(DdlRequest {
752 expr: Some(DdlExpr::CreateTable(expr)),
753 });
754 self.do_get(request, &[], &[], &Default::default())
755 .await
756 .map(OutputWithMetrics::into_output)
757 }
758
759 pub async fn alter(&self, expr: AlterTableExpr) -> Result<Output> {
761 let request = Request::Ddl(DdlRequest {
762 expr: Some(DdlExpr::AlterTable(expr)),
763 });
764 self.do_get(request, &[], &[], &Default::default())
765 .await
766 .map(OutputWithMetrics::into_output)
767 }
768
769 async fn do_get(
770 &self,
771 request: Request,
772 hints: &[(&str, &str)],
773 flow_extensions: &[(&str, &str)],
774 snapshot_seqs: &std::collections::HashMap<u64, u64>,
775 ) -> Result<OutputWithMetrics> {
776 let request = self.to_rpc_request(request);
777 let request = Ticket {
778 ticket: request.encode_to_vec().into(),
779 };
780
781 let mut request = tonic::Request::new(request);
782 let metadata = request.metadata_mut();
783 Self::put_hints(metadata, hints)?;
784 Self::put_flow_extensions(metadata, flow_extensions)?;
785 Self::put_snapshot_seqs(metadata, snapshot_seqs)?;
786
787 let mut client = self.client.make_flight_client(false, false)?;
788
789 let response = client.mut_inner().do_get(request).await.or_else(|e| {
790 let tonic_code = e.code();
791 let e: Error = e.into();
792 error!(
793 "Failed to do Flight get, addr: {}, code: {}, source: {:?}",
794 client.addr(),
795 tonic_code,
796 e
797 );
798 Err(BoxedError::new(e)).with_context(|_| FlightGetSnafu {
799 addr: client.addr().to_string(),
800 tonic_code,
801 })
802 })?;
803
804 let flight_data_stream = response.into_inner();
805 let mut decoder = FlightDecoder::default();
806
807 let flight_message_stream = flight_data_stream.filter_map(move |flight_data| {
808 future::ready(decode_flight_data(&mut decoder, flight_data))
809 });
810
811 output_from_flight_message_stream(flight_message_stream).await
812 }
813
814 pub async fn do_put(&self, stream: FlightDataStream) -> Result<DoPutResponseStream> {
817 let mut request = tonic::Request::new(stream);
818
819 if let Some(AuthHeader {
820 auth_scheme: Some(AuthScheme::Basic(Basic { username, password })),
821 }) = &self.ctx.auth_header
822 {
823 let encoded = BASE64_STANDARD.encode(format!("{username}:{password}"));
824 let value = MetadataValue::from_str(&format!("Basic {encoded}"))
825 .context(InvalidTonicMetadataValueSnafu)?;
826 request.metadata_mut().insert("x-greptime-auth", value);
827 }
828
829 let db_to_put = if !self.dbname.is_empty() {
830 &self.dbname
831 } else {
832 &build_db_string(self.catalog_or_default(), self.schema_or_default())
833 };
834 request.metadata_mut().insert(
835 "x-greptime-db-name",
836 MetadataValue::from_str(db_to_put).context(InvalidTonicMetadataValueSnafu)?,
837 );
838
839 let mut client = self.client.make_flight_client(false, false)?;
840 let response = client.mut_inner().do_put(request).await?;
841 let response = response
842 .into_inner()
843 .map_err(Into::into)
844 .and_then(|x| future::ready(DoPutResponse::try_from(x).context(ConvertFlightDataSnafu)))
845 .boxed();
846 Ok(response)
847 }
848}
849
850pub fn is_grpc_retryable(err: &tonic::Status) -> bool {
852 matches!(err.code(), tonic::Code::Unavailable)
853}
854
855#[derive(Default, Debug, Clone)]
856struct FlightContext {
857 auth_header: Option<AuthHeader>,
858}
859
860#[cfg(test)]
861mod tests {
862 use std::sync::Arc;
863 use std::task::{Context, Poll};
864
865 use api::v1::auth_header::AuthScheme;
866 use api::v1::{AuthHeader, Basic};
867 use common_error::ext::{ErrorExt, RetryHint};
868 use common_error::status_code::StatusCode;
869 use common_error::{GREPTIME_DB_HEADER_ERROR_CODE, GREPTIME_DB_HEADER_ERROR_RETRY_HINT};
870 use common_query::OutputData;
871 use common_recordbatch::{OrderOption, RecordBatch, RecordBatchStream};
872 use datatypes::prelude::{ConcreteDataType, VectorRef};
873 use datatypes::schema::{ColumnSchema, Schema};
874 use datatypes::vectors::Int32Vector;
875 use futures_util::StreamExt;
876 use tonic::codegen::http::{HeaderMap, HeaderValue};
877 use tonic::metadata::MetadataMap;
878 use tonic::{Code, Status};
879
880 use super::*;
881 use crate::error::TonicSnafu;
882
883 struct MockMetricsStream {
884 schema: datatypes::schema::SchemaRef,
885 batch: Option<RecordBatch>,
886 metrics: RecordBatchMetrics,
887 terminal_metrics_only: bool,
888 }
889
890 impl Stream for MockMetricsStream {
891 type Item = common_recordbatch::error::Result<RecordBatch>;
892
893 fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
894 Poll::Ready(self.batch.take().map(Ok))
895 }
896 }
897
898 impl RecordBatchStream for MockMetricsStream {
899 fn name(&self) -> &str {
900 "MockMetricsStream"
901 }
902
903 fn schema(&self) -> datatypes::schema::SchemaRef {
904 self.schema.clone()
905 }
906
907 fn output_ordering(&self) -> Option<&[OrderOption]> {
908 None
909 }
910
911 fn metrics(&self) -> Option<RecordBatchMetrics> {
912 if self.terminal_metrics_only && self.batch.is_some() {
913 return None;
914 }
915 Some(self.metrics.clone())
916 }
917 }
918
919 fn terminal_metrics_json() -> String {
920 terminal_metrics_json_with_seq(42)
921 }
922
923 fn terminal_metrics_json_with_seq(seq: u64) -> String {
924 serde_json::to_string(&RecordBatchMetrics {
925 region_watermarks: vec![common_recordbatch::adapter::RegionWatermarkEntry {
926 region_id: 7,
927 watermark: Some(seq),
928 }],
929 ..Default::default()
930 })
931 .unwrap()
932 }
933
934 #[test]
935 fn test_put_flow_extensions_preserves_comma_bearing_values() {
936 let mut metadata = MetadataMap::new();
937 Database::put_flow_extensions(
938 &mut metadata,
939 &[
940 ("flow.return_region_seq", "true"),
941 ("flow.incremental_after_seqs", r#"{"1":10,"2":20}"#),
942 ],
943 )
944 .unwrap();
945
946 let value = metadata
947 .get(FLOW_EXTENSIONS_METADATA_KEY)
948 .unwrap()
949 .to_str()
950 .unwrap();
951 let decoded: Vec<(String, String)> = serde_json::from_str(value).unwrap();
952 assert_eq!(
953 decoded,
954 vec![
955 ("flow.return_region_seq".to_string(), "true".to_string()),
956 (
957 "flow.incremental_after_seqs".to_string(),
958 r#"{"1":10,"2":20}"#.to_string()
959 ),
960 ]
961 );
962 }
963
964 #[test]
965 fn test_put_snapshot_seqs_preserves_u64_precision() {
966 let mut metadata = MetadataMap::new();
967 let snapshot_seqs = std::collections::HashMap::from([
968 (u64::MAX, u64::MAX - 1),
969 (9_007_199_254_740_993_u64, 9_007_199_254_740_995_u64),
970 ]);
971
972 Database::put_snapshot_seqs(&mut metadata, &snapshot_seqs).unwrap();
973
974 let value = metadata
975 .get(SNAPSHOT_SEQS_METADATA_KEY)
976 .unwrap()
977 .to_str()
978 .unwrap();
979 let decoded: std::collections::HashMap<u64, u64> = serde_json::from_str(value).unwrap();
980 assert_eq!(decoded, snapshot_seqs);
981 }
982
983 #[test]
984 fn test_flight_ctx() {
985 let mut ctx = FlightContext::default();
986 assert!(ctx.auth_header.is_none());
987
988 let basic = AuthScheme::Basic(Basic {
989 username: "u".to_string(),
990 password: "p".to_string(),
991 });
992
993 ctx.auth_header = Some(AuthHeader {
994 auth_scheme: Some(basic),
995 });
996
997 assert!(matches!(
998 ctx.auth_header,
999 Some(AuthHeader {
1000 auth_scheme: Some(AuthScheme::Basic(_)),
1001 })
1002 ));
1003 }
1004
1005 #[test]
1006 fn test_from_tonic_status() {
1007 let expected = TonicSnafu {
1008 code: StatusCode::Internal,
1009 msg: "blabla".to_string(),
1010 tonic_code: Code::Internal,
1011 retry_hint: RetryHint::NonRetryable,
1012 }
1013 .build();
1014
1015 let status = Status::new(Code::Internal, "blabla");
1016 let actual: Error = status.into();
1017
1018 assert_eq!(expected.to_string(), actual.to_string());
1019 assert_eq!(expected.retry_hint(), actual.retry_hint());
1020 assert_eq!(expected.should_retry(), actual.should_retry());
1021 }
1022
1023 #[test]
1024 fn test_from_tonic_status_with_retry_hint() {
1025 let mut headers = HeaderMap::new();
1026 headers.insert(
1027 GREPTIME_DB_HEADER_ERROR_CODE,
1028 HeaderValue::from(StatusCode::Internal as u32),
1029 );
1030 headers.insert(
1031 GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
1032 HeaderValue::from_static(RetryHint::Retryable.as_str()),
1033 );
1034 let status =
1035 Status::with_metadata(Code::Internal, "blabla", MetadataMap::from_headers(headers));
1036
1037 let actual: Error = status.into();
1038
1039 assert_eq!(actual.retry_hint(), RetryHint::Retryable);
1040 assert!(actual.should_retry());
1041 }
1042
1043 #[test]
1044 fn test_from_tonic_status_fallback() {
1045 let mut headers = HeaderMap::new();
1046 headers.insert(
1047 GREPTIME_DB_HEADER_ERROR_CODE,
1048 HeaderValue::from(StatusCode::InvalidArguments as u32),
1049 );
1050 let status =
1051 Status::with_metadata(Code::Internal, "blabla", MetadataMap::from_headers(headers));
1052
1053 let actual: Error = status.into();
1054
1055 assert_eq!(actual.retry_hint(), RetryHint::NonRetryable);
1056 assert!(!actual.should_retry());
1057 }
1058
1059 #[test]
1060 fn test_should_retry_preserves_transport_retry() {
1061 let status = Status::new(Code::Unavailable, "blabla");
1062 let actual: Error = status.into();
1063
1064 assert!(actual.should_retry());
1065 }
1066
1067 #[tokio::test]
1068 async fn test_query_with_terminal_metrics_tracks_terminal_only_metrics() {
1069 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1070 "v",
1071 ConcreteDataType::int32_datatype(),
1072 false,
1073 )]));
1074 let batch = RecordBatch::new(
1075 schema.clone(),
1076 vec![Arc::new(Int32Vector::from_slice([1, 2])) as VectorRef],
1077 )
1078 .unwrap();
1079 let output = Output::new_with_stream(Box::pin(MockMetricsStream {
1080 schema,
1081 batch: Some(batch),
1082 metrics: RecordBatchMetrics {
1083 region_watermarks: vec![common_recordbatch::adapter::RegionWatermarkEntry {
1084 region_id: 7,
1085 watermark: Some(42),
1086 }],
1087 ..Default::default()
1088 },
1089 terminal_metrics_only: true,
1090 }));
1091
1092 let result = OutputWithMetrics::from_output(output);
1093 let terminal_metrics = result.metrics.clone();
1094 assert!(!terminal_metrics.is_ready());
1095 assert!(terminal_metrics.get().is_none());
1096
1097 let OutputData::Stream(mut stream) = result.output.data else {
1098 panic!("expected stream output");
1099 };
1100 while stream.next().await.is_some() {}
1101
1102 assert!(terminal_metrics.is_ready());
1103 assert_eq!(
1104 terminal_metrics.participating_regions(),
1105 Some(std::collections::BTreeSet::from([7_u64]))
1106 );
1107 assert_eq!(
1108 terminal_metrics.region_watermark_map(),
1109 Some(std::collections::HashMap::from([(7_u64, 42_u64)]))
1110 );
1111 }
1112
1113 #[test]
1114 fn test_parse_terminal_metrics_rejects_invalid_json() {
1115 assert!(parse_terminal_metrics("{not-json}").is_err());
1116 }
1117
1118 #[tokio::test]
1119 async fn test_affected_rows_inline_metrics_are_parsed() {
1120 let output = output_from_flight_message_stream(futures_util::stream::iter(vec![Ok(
1121 FlightMessage::AffectedRows {
1122 rows: 3,
1123 metrics: Some(terminal_metrics_json()),
1124 },
1125 )]
1126 as Vec<Result<FlightMessage>>))
1127 .await
1128 .unwrap();
1129
1130 assert!(matches!(output.output.data, OutputData::AffectedRows(3)));
1131 assert!(output.metrics.is_ready());
1132 assert_eq!(
1133 output.metrics.region_watermark_map(),
1134 Some(std::collections::HashMap::from([(7, 42)]))
1135 );
1136 }
1137
1138 #[tokio::test]
1139 async fn test_affected_rows_inline_metrics_rejects_trailing_metrics() {
1140 let metrics_json = terminal_metrics_json();
1141 let err = output_from_flight_message_stream(futures_util::stream::iter(vec![
1142 Ok(FlightMessage::AffectedRows {
1143 rows: 3,
1144 metrics: Some(metrics_json.clone()),
1145 }),
1146 Ok(FlightMessage::Metrics(metrics_json)),
1147 ]
1148 as Vec<Result<FlightMessage>>))
1149 .await
1150 .unwrap_err();
1151
1152 assert!(
1153 err.to_string().contains("already carries Metrics"),
1154 "unexpected error: {err:?}"
1155 );
1156 }
1157
1158 #[tokio::test]
1159 async fn test_invalid_terminal_metrics_after_record_batch_yields_batch_then_error() {
1160 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1161 "v",
1162 ConcreteDataType::int32_datatype(),
1163 false,
1164 )]));
1165 let batch = RecordBatch::new(
1166 schema.clone(),
1167 vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1168 )
1169 .unwrap();
1170 let output = output_from_flight_message_stream(futures_util::stream::iter(vec![
1171 Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1172 Ok(FlightMessage::RecordBatch(batch.into_df_record_batch())),
1173 Ok(FlightMessage::Metrics("{not-json}".to_string())),
1174 ]
1175 as Vec<Result<FlightMessage>>))
1176 .await
1177 .unwrap();
1178 let terminal_metrics = output.metrics.clone();
1179 let OutputData::Stream(mut record_batch_stream) = output.output.data else {
1180 panic!("expected stream output");
1181 };
1182
1183 let batch = record_batch_stream.next().await.unwrap().unwrap();
1184 assert_eq!(batch.num_rows(), 1);
1185
1186 let err = record_batch_stream.next().await.unwrap().unwrap_err();
1187 assert_eq!("External error", err.to_string());
1188 assert!(
1189 format!("{err:?}").contains("Invalid terminal metrics message"),
1190 "unexpected error: {err:?}"
1191 );
1192 assert!(record_batch_stream.next().await.is_none());
1193 assert!(terminal_metrics.is_ready());
1194 assert!(terminal_metrics.get().is_none());
1195 }
1196
1197 #[tokio::test]
1198 async fn test_record_batch_stream_continues_after_partial_metrics() {
1199 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1200 "v",
1201 ConcreteDataType::int32_datatype(),
1202 false,
1203 )]));
1204 let first_batch = RecordBatch::new(
1205 schema.clone(),
1206 vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1207 )
1208 .unwrap();
1209 let second_batch = RecordBatch::new(
1210 schema.clone(),
1211 vec![Arc::new(Int32Vector::from_slice([2])) as VectorRef],
1212 )
1213 .unwrap();
1214 let output = output_from_flight_message_stream(futures_util::stream::iter(vec![
1215 Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1216 Ok(FlightMessage::RecordBatch(
1217 first_batch.into_df_record_batch(),
1218 )),
1219 Ok(FlightMessage::Metrics(terminal_metrics_json_with_seq(1))),
1220 Ok(FlightMessage::RecordBatch(
1221 second_batch.into_df_record_batch(),
1222 )),
1223 Ok(FlightMessage::Metrics(terminal_metrics_json_with_seq(2))),
1224 ]
1225 as Vec<Result<FlightMessage>>))
1226 .await
1227 .unwrap();
1228 let terminal_metrics = output.metrics.clone();
1229 let OutputData::Stream(mut record_batch_stream) = output.output.data else {
1230 panic!("expected stream output");
1231 };
1232
1233 let first_batch = record_batch_stream.next().await.unwrap().unwrap();
1234 assert_eq!(first_batch.num_rows(), 1);
1235 let second_batch = record_batch_stream.next().await.unwrap().unwrap();
1236 assert_eq!(second_batch.num_rows(), 1);
1237 assert!(record_batch_stream.next().await.is_none());
1238
1239 assert!(terminal_metrics.is_ready());
1240 assert_eq!(
1241 terminal_metrics.region_watermark_map(),
1242 Some(std::collections::HashMap::from([(7, 2)]))
1243 );
1244 }
1245
1246 #[test]
1247 fn test_output_metrics_distinguishes_empty_region_watermarks_from_absence() {
1248 let metrics = OutputMetrics::default();
1249 metrics.update(Some(RecordBatchMetrics::default()));
1250
1251 assert_eq!(
1252 metrics.participating_regions(),
1253 Some(std::collections::BTreeSet::new())
1254 );
1255 assert_eq!(
1256 metrics.region_watermark_map(),
1257 Some(std::collections::HashMap::new())
1258 );
1259 }
1260}