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 let mut request = tonic::Request::new(stream);
829
830 if let Some(AuthHeader {
831 auth_scheme: Some(AuthScheme::Basic(Basic { username, password })),
832 }) = &self.ctx.auth_header
833 {
834 let encoded = BASE64_STANDARD.encode(format!("{username}:{password}"));
835 let value = MetadataValue::from_str(&format!("Basic {encoded}"))
836 .context(InvalidTonicMetadataValueSnafu)?;
837 request.metadata_mut().insert("x-greptime-auth", value);
838 }
839
840 let db_to_put = if !self.dbname.is_empty() {
841 &self.dbname
842 } else {
843 &build_db_string(self.catalog_or_default(), self.schema_or_default())
844 };
845 request.metadata_mut().insert(
846 "x-greptime-db-name",
847 MetadataValue::from_str(db_to_put).context(InvalidTonicMetadataValueSnafu)?,
848 );
849
850 let mut client = self.client.make_control_flight_client(false, false)?;
851 let response = client.mut_inner().do_put(request).await?;
852 let response = response
853 .into_inner()
854 .map_err(Into::into)
855 .and_then(|x| future::ready(DoPutResponse::try_from(x).context(ConvertFlightDataSnafu)))
856 .boxed();
857 Ok(response)
858 }
859}
860
861impl<'a> DatabaseFlightRequest<'a> {
862 pub fn with_hints(mut self, hints: &[(&str, &str)]) -> Self {
864 self.options.hints = Database::encode_hints(hints);
865 self
866 }
867
868 pub fn with_flow_extensions(mut self, flow_extensions: &[(&str, &str)]) -> Self {
870 self.options.flow_extensions = Database::encode_flow_extensions(flow_extensions);
871 self
872 }
873
874 pub fn with_snapshot_seqs(mut self, snapshot_seqs: &HashMap<u64, u64>) -> Self {
876 self.options.snapshot_seqs = Database::encode_snapshot_seqs(snapshot_seqs);
877 self
878 }
879
880 pub fn with_timeout(mut self, timeout: Duration) -> Self {
882 self.options.timeout = Some(timeout);
883 self
884 }
885
886 pub async fn sql<S>(self, sql: S) -> Result<Output>
888 where
889 S: AsRef<str>,
890 {
891 let request = Request::Query(QueryRequest {
892 query: Some(Query::Sql(sql.as_ref().to_string())),
893 });
894 self.do_get(request)
895 .await
896 .map(OutputWithMetrics::into_output)
897 }
898
899 pub async fn sql_with_terminal_metrics<S>(self, sql: S) -> Result<OutputWithMetrics>
901 where
902 S: AsRef<str>,
903 {
904 self.query_with_terminal_metrics(QueryRequest {
905 query: Some(Query::Sql(sql.as_ref().to_string())),
906 })
907 .await
908 }
909
910 pub async fn logical_plan(self, logical_plan: Vec<u8>) -> Result<Output> {
912 self.query_with_terminal_metrics(QueryRequest {
913 query: Some(Query::LogicalPlan(logical_plan)),
914 })
915 .await
916 .map(OutputWithMetrics::into_output)
917 }
918
919 pub async fn query_with_terminal_metrics(
921 self,
922 request: QueryRequest,
923 ) -> Result<OutputWithMetrics> {
924 self.do_get(Request::Query(request)).await
925 }
926
927 #[cfg(feature = "testing")]
929 pub async fn create(self, expr: CreateTableExpr) -> Result<Output> {
930 self.do_get(Request::Ddl(DdlRequest {
931 expr: Some(DdlExpr::CreateTable(expr)),
932 }))
933 .await
934 .map(OutputWithMetrics::into_output)
935 }
936
937 #[cfg(feature = "testing")]
939 pub async fn alter(self, expr: AlterTableExpr) -> Result<Output> {
940 self.do_get(Request::Ddl(DdlRequest {
941 expr: Some(DdlExpr::AlterTable(expr)),
942 }))
943 .await
944 .map(OutputWithMetrics::into_output)
945 }
946
947 async fn do_get(self, request: Request) -> Result<OutputWithMetrics> {
948 let Self { database, options } = self;
949 database.do_get(request, options).await
950 }
951}
952
953pub fn is_grpc_retryable(err: &tonic::Status) -> bool {
955 matches!(err.code(), tonic::Code::Unavailable)
956}
957
958#[derive(Default, Debug, Clone)]
959struct FlightContext {
960 auth_header: Option<AuthHeader>,
961}
962
963#[cfg(test)]
964mod tests {
965 use std::sync::Arc;
966 use std::task::{Context, Poll};
967
968 use api::v1::auth_header::AuthScheme;
969 use api::v1::{AuthHeader, Basic};
970 use common_error::ext::{ErrorExt, RetryHint};
971 use common_error::status_code::StatusCode;
972 use common_error::{GREPTIME_DB_HEADER_ERROR_CODE, GREPTIME_DB_HEADER_ERROR_RETRY_HINT};
973 use common_query::OutputData;
974 use common_recordbatch::{OrderOption, RecordBatch, RecordBatchStream};
975 use datatypes::prelude::{ConcreteDataType, VectorRef};
976 use datatypes::schema::{ColumnSchema, Schema};
977 use datatypes::vectors::Int32Vector;
978 use futures_util::StreamExt;
979 use tonic::codegen::http::{HeaderMap, HeaderValue};
980 use tonic::metadata::MetadataMap;
981 use tonic::{Code, Status};
982
983 use super::*;
984 use crate::error::TonicSnafu;
985
986 struct MockMetricsStream {
987 schema: datatypes::schema::SchemaRef,
988 batch: Option<RecordBatch>,
989 metrics: RecordBatchMetrics,
990 terminal_metrics_only: bool,
991 }
992
993 impl Stream for MockMetricsStream {
994 type Item = common_recordbatch::error::Result<RecordBatch>;
995
996 fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
997 Poll::Ready(self.batch.take().map(Ok))
998 }
999 }
1000
1001 impl RecordBatchStream for MockMetricsStream {
1002 fn name(&self) -> &str {
1003 "MockMetricsStream"
1004 }
1005
1006 fn schema(&self) -> datatypes::schema::SchemaRef {
1007 self.schema.clone()
1008 }
1009
1010 fn output_ordering(&self) -> Option<&[OrderOption]> {
1011 None
1012 }
1013
1014 fn metrics(&self) -> Option<RecordBatchMetrics> {
1015 if self.terminal_metrics_only && self.batch.is_some() {
1016 return None;
1017 }
1018 Some(self.metrics.clone())
1019 }
1020 }
1021
1022 fn terminal_metrics_json() -> String {
1023 terminal_metrics_json_with_seq(42)
1024 }
1025
1026 fn terminal_metrics_json_with_seq(seq: u64) -> String {
1027 serde_json::to_string(&RecordBatchMetrics {
1028 region_watermarks: vec![common_recordbatch::adapter::RegionWatermarkEntry {
1029 region_id: 7,
1030 watermark: Some(seq),
1031 }],
1032 ..Default::default()
1033 })
1034 .unwrap()
1035 }
1036
1037 #[test]
1038 fn test_put_flow_extensions_preserves_comma_bearing_values() {
1039 let mut metadata = MetadataMap::new();
1040 Database::put_metadata_value(
1041 &mut metadata,
1042 FLOW_EXTENSIONS_METADATA_KEY,
1043 Database::encode_flow_extensions(&[
1044 ("flow.return_region_seq", "true"),
1045 ("flow.incremental_after_seqs", r#"{"1":10,"2":20}"#),
1046 ])
1047 .unwrap(),
1048 )
1049 .unwrap();
1050
1051 let value = metadata
1052 .get(FLOW_EXTENSIONS_METADATA_KEY)
1053 .unwrap()
1054 .to_str()
1055 .unwrap();
1056 let decoded: Vec<(String, String)> = serde_json::from_str(value).unwrap();
1057 assert_eq!(
1058 decoded,
1059 vec![
1060 ("flow.return_region_seq".to_string(), "true".to_string()),
1061 (
1062 "flow.incremental_after_seqs".to_string(),
1063 r#"{"1":10,"2":20}"#.to_string()
1064 ),
1065 ]
1066 );
1067 }
1068
1069 #[test]
1070 fn test_put_snapshot_seqs_preserves_u64_precision() {
1071 let mut metadata = MetadataMap::new();
1072 let snapshot_seqs = std::collections::HashMap::from([
1073 (u64::MAX, u64::MAX - 1),
1074 (9_007_199_254_740_993_u64, 9_007_199_254_740_995_u64),
1075 ]);
1076
1077 Database::put_metadata_value(
1078 &mut metadata,
1079 SNAPSHOT_SEQS_METADATA_KEY,
1080 Database::encode_snapshot_seqs(&snapshot_seqs).unwrap(),
1081 )
1082 .unwrap();
1083
1084 let value = metadata
1085 .get(SNAPSHOT_SEQS_METADATA_KEY)
1086 .unwrap()
1087 .to_str()
1088 .unwrap();
1089 let decoded: std::collections::HashMap<u64, u64> = serde_json::from_str(value).unwrap();
1090 assert_eq!(decoded, snapshot_seqs);
1091 }
1092
1093 #[test]
1094 fn test_flight_request_builder_applies_request_options() {
1095 let database = Database::new("greptime", "public", Client::default());
1096 let snapshot_seqs = HashMap::from([(42, 99)]);
1097 let request = database
1098 .flight_request()
1099 .with_hints(&[("query_parallelism", "1")])
1100 .with_flow_extensions(&[("flow.return_region_seq", "true")])
1101 .with_snapshot_seqs(&snapshot_seqs)
1102 .with_timeout(Duration::from_millis(50));
1103 let mut tonic_request = tonic::Request::new(());
1104
1105 request.options.apply_to(&mut tonic_request).unwrap();
1106
1107 let metadata = tonic_request.metadata();
1108 assert_eq!(
1109 metadata.get(HINTS_METADATA_KEY).unwrap(),
1110 "query_parallelism=1"
1111 );
1112 assert_eq!(
1113 serde_json::from_str::<Vec<(String, String)>>(
1114 metadata
1115 .get(FLOW_EXTENSIONS_METADATA_KEY)
1116 .unwrap()
1117 .to_str()
1118 .unwrap(),
1119 )
1120 .unwrap(),
1121 vec![("flow.return_region_seq".to_string(), "true".to_string())]
1122 );
1123 assert_eq!(
1124 serde_json::from_str::<HashMap<u64, u64>>(
1125 metadata
1126 .get(SNAPSHOT_SEQS_METADATA_KEY)
1127 .unwrap()
1128 .to_str()
1129 .unwrap(),
1130 )
1131 .unwrap(),
1132 snapshot_seqs
1133 );
1134 assert!(metadata.get("grpc-timeout").is_some());
1135 }
1136
1137 #[test]
1138 fn test_flight_ctx() {
1139 let mut ctx = FlightContext::default();
1140 assert!(ctx.auth_header.is_none());
1141
1142 let basic = AuthScheme::Basic(Basic {
1143 username: "u".to_string(),
1144 password: "p".to_string(),
1145 });
1146
1147 ctx.auth_header = Some(AuthHeader {
1148 auth_scheme: Some(basic),
1149 });
1150
1151 assert!(matches!(
1152 ctx.auth_header,
1153 Some(AuthHeader {
1154 auth_scheme: Some(AuthScheme::Basic(_)),
1155 })
1156 ));
1157 }
1158
1159 #[test]
1160 fn test_from_tonic_status() {
1161 let expected = TonicSnafu {
1162 code: StatusCode::Internal,
1163 msg: "blabla".to_string(),
1164 tonic_code: Code::Internal,
1165 retry_hint: RetryHint::NonRetryable,
1166 }
1167 .build();
1168
1169 let status = Status::new(Code::Internal, "blabla");
1170 let actual: Error = status.into();
1171
1172 assert_eq!(expected.to_string(), actual.to_string());
1173 assert_eq!(expected.retry_hint(), actual.retry_hint());
1174 assert_eq!(expected.should_retry(), actual.should_retry());
1175 }
1176
1177 #[test]
1178 fn test_flight_stream_error_preserves_addr_and_message() {
1179 let error = flight_stream_error(
1180 "127.0.0.1:4001",
1181 Status::out_of_range("message length too large").into(),
1182 );
1183
1184 assert!(matches!(
1185 &error,
1186 Error::FlightStream {
1187 addr,
1188 tonic_code: Code::OutOfRange,
1189 message,
1190 ..
1191 } if addr == "127.0.0.1:4001" && message == "message length too large"
1192 ));
1193 assert_eq!(
1194 "Failed to receive Flight data from 127.0.0.1:4001, code: Operation was attempted past the valid range: message length too large",
1195 error.to_string(),
1196 );
1197 }
1198
1199 #[test]
1200 fn test_from_tonic_status_with_retry_hint() {
1201 let mut headers = HeaderMap::new();
1202 headers.insert(
1203 GREPTIME_DB_HEADER_ERROR_CODE,
1204 HeaderValue::from(StatusCode::Internal as u32),
1205 );
1206 headers.insert(
1207 GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
1208 HeaderValue::from_static(RetryHint::Retryable.as_str()),
1209 );
1210 let status =
1211 Status::with_metadata(Code::Internal, "blabla", MetadataMap::from_headers(headers));
1212
1213 let actual: Error = status.into();
1214
1215 assert_eq!(actual.retry_hint(), RetryHint::Retryable);
1216 assert!(actual.should_retry());
1217 }
1218
1219 #[test]
1220 fn test_from_tonic_status_fallback() {
1221 let mut headers = HeaderMap::new();
1222 headers.insert(
1223 GREPTIME_DB_HEADER_ERROR_CODE,
1224 HeaderValue::from(StatusCode::InvalidArguments as u32),
1225 );
1226 let status =
1227 Status::with_metadata(Code::Internal, "blabla", MetadataMap::from_headers(headers));
1228
1229 let actual: Error = status.into();
1230
1231 assert_eq!(actual.retry_hint(), RetryHint::NonRetryable);
1232 assert!(!actual.should_retry());
1233 }
1234
1235 #[test]
1236 fn test_should_retry_preserves_transport_retry() {
1237 let status = Status::new(Code::Unavailable, "blabla");
1238 let actual: Error = status.into();
1239
1240 assert!(actual.should_retry());
1241 }
1242
1243 #[tokio::test]
1244 async fn test_query_with_terminal_metrics_tracks_terminal_only_metrics() {
1245 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1246 "v",
1247 ConcreteDataType::int32_datatype(),
1248 false,
1249 )]));
1250 let batch = RecordBatch::new(
1251 schema.clone(),
1252 vec![Arc::new(Int32Vector::from_slice([1, 2])) as VectorRef],
1253 )
1254 .unwrap();
1255 let output = Output::new_with_stream(Box::pin(MockMetricsStream {
1256 schema,
1257 batch: Some(batch),
1258 metrics: RecordBatchMetrics {
1259 region_watermarks: vec![common_recordbatch::adapter::RegionWatermarkEntry {
1260 region_id: 7,
1261 watermark: Some(42),
1262 }],
1263 ..Default::default()
1264 },
1265 terminal_metrics_only: true,
1266 }));
1267
1268 let result = OutputWithMetrics::from_output(output);
1269 let terminal_metrics = result.metrics.clone();
1270 assert!(!terminal_metrics.is_ready());
1271 assert!(terminal_metrics.get().is_none());
1272
1273 let OutputData::Stream(mut stream) = result.output.data else {
1274 panic!("expected stream output");
1275 };
1276 while stream.next().await.is_some() {}
1277
1278 assert!(terminal_metrics.is_ready());
1279 assert_eq!(
1280 terminal_metrics.participating_regions(),
1281 Some(std::collections::BTreeSet::from([7_u64]))
1282 );
1283 assert_eq!(
1284 terminal_metrics.region_watermark_map(),
1285 Some(std::collections::HashMap::from([(7_u64, 42_u64)]))
1286 );
1287 }
1288
1289 #[test]
1290 fn test_parse_terminal_metrics_rejects_invalid_json() {
1291 assert!(parse_terminal_metrics("{not-json}").is_err());
1292 }
1293
1294 #[tokio::test]
1295 async fn test_affected_rows_inline_metrics_are_parsed() {
1296 let output = output_from_flight_message_stream(
1297 "test-peer".to_string(),
1298 futures_util::stream::iter(vec![Ok(FlightMessage::AffectedRows {
1299 rows: 3,
1300 metrics: Some(terminal_metrics_json()),
1301 })] as Vec<Result<FlightMessage>>),
1302 )
1303 .await
1304 .unwrap();
1305
1306 assert!(matches!(output.output.data, OutputData::AffectedRows(3)));
1307 assert!(output.metrics.is_ready());
1308 assert_eq!(
1309 output.metrics.region_watermark_map(),
1310 Some(std::collections::HashMap::from([(7, 42)]))
1311 );
1312 }
1313
1314 #[tokio::test]
1315 async fn test_affected_rows_inline_metrics_rejects_trailing_metrics() {
1316 let metrics_json = terminal_metrics_json();
1317 let err = output_from_flight_message_stream(
1318 "test-peer".to_string(),
1319 futures_util::stream::iter(vec![
1320 Ok(FlightMessage::AffectedRows {
1321 rows: 3,
1322 metrics: Some(metrics_json.clone()),
1323 }),
1324 Ok(FlightMessage::Metrics(metrics_json)),
1325 ] as Vec<Result<FlightMessage>>),
1326 )
1327 .await
1328 .unwrap_err();
1329
1330 assert!(
1331 err.to_string().contains("already carries Metrics"),
1332 "unexpected error: {err:?}"
1333 );
1334 }
1335
1336 #[tokio::test]
1337 async fn test_invalid_terminal_metrics_after_record_batch_yields_batch_then_error() {
1338 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1339 "v",
1340 ConcreteDataType::int32_datatype(),
1341 false,
1342 )]));
1343 let batch = RecordBatch::new(
1344 schema.clone(),
1345 vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1346 )
1347 .unwrap();
1348 let output = output_from_flight_message_stream(
1349 "test-peer".to_string(),
1350 futures_util::stream::iter(vec![
1351 Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1352 Ok(FlightMessage::RecordBatch(batch.into_df_record_batch())),
1353 Ok(FlightMessage::Metrics("{not-json}".to_string())),
1354 ] as Vec<Result<FlightMessage>>),
1355 )
1356 .await
1357 .unwrap();
1358 let terminal_metrics = output.metrics.clone();
1359 let OutputData::Stream(mut record_batch_stream) = output.output.data else {
1360 panic!("expected stream output");
1361 };
1362
1363 let batch = record_batch_stream.next().await.unwrap().unwrap();
1364 assert_eq!(batch.num_rows(), 1);
1365
1366 let err = record_batch_stream.next().await.unwrap().unwrap_err();
1367 assert_eq!("External error", err.to_string());
1368 assert!(
1369 format!("{err:?}").contains("Invalid terminal metrics message"),
1370 "unexpected error: {err:?}"
1371 );
1372 assert!(record_batch_stream.next().await.is_none());
1373 assert!(terminal_metrics.is_ready());
1374 assert!(terminal_metrics.get().is_none());
1375 }
1376
1377 #[tokio::test]
1378 async fn test_record_batch_stream_continues_after_partial_metrics() {
1379 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1380 "v",
1381 ConcreteDataType::int32_datatype(),
1382 false,
1383 )]));
1384 let first_batch = RecordBatch::new(
1385 schema.clone(),
1386 vec![Arc::new(Int32Vector::from_slice([1])) as VectorRef],
1387 )
1388 .unwrap();
1389 let second_batch = RecordBatch::new(
1390 schema.clone(),
1391 vec![Arc::new(Int32Vector::from_slice([2])) as VectorRef],
1392 )
1393 .unwrap();
1394 let output = output_from_flight_message_stream(
1395 "test-peer".to_string(),
1396 futures_util::stream::iter(vec![
1397 Ok(FlightMessage::Schema(schema.arrow_schema().clone())),
1398 Ok(FlightMessage::RecordBatch(
1399 first_batch.into_df_record_batch(),
1400 )),
1401 Ok(FlightMessage::Metrics(terminal_metrics_json_with_seq(1))),
1402 Ok(FlightMessage::RecordBatch(
1403 second_batch.into_df_record_batch(),
1404 )),
1405 Ok(FlightMessage::Metrics(terminal_metrics_json_with_seq(2))),
1406 ] as Vec<Result<FlightMessage>>),
1407 )
1408 .await
1409 .unwrap();
1410 let terminal_metrics = output.metrics.clone();
1411 let OutputData::Stream(mut record_batch_stream) = output.output.data else {
1412 panic!("expected stream output");
1413 };
1414
1415 let first_batch = record_batch_stream.next().await.unwrap().unwrap();
1416 assert_eq!(first_batch.num_rows(), 1);
1417 let second_batch = record_batch_stream.next().await.unwrap().unwrap();
1418 assert_eq!(second_batch.num_rows(), 1);
1419 assert!(record_batch_stream.next().await.is_none());
1420
1421 assert!(terminal_metrics.is_ready());
1422 assert_eq!(
1423 terminal_metrics.region_watermark_map(),
1424 Some(std::collections::HashMap::from([(7, 2)]))
1425 );
1426 }
1427
1428 #[test]
1429 fn test_output_metrics_distinguishes_empty_region_watermarks_from_absence() {
1430 let metrics = OutputMetrics::default();
1431 metrics.update(Some(RecordBatchMetrics::default()));
1432
1433 assert_eq!(
1434 metrics.participating_regions(),
1435 Some(std::collections::BTreeSet::new())
1436 );
1437 assert_eq!(
1438 metrics.region_watermark_map(),
1439 Some(std::collections::HashMap::new())
1440 );
1441 }
1442}