1use std::collections::HashMap;
18use std::sync::{Arc, Mutex, RwLock, Weak};
19
20use api::v1::greptime_request::Request;
21use api::v1::query_request::Query;
22use api::v1::{CreateTableExpr, QueryRequest, RowInsertRequests};
23use client::{Client, DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, Database, OutputWithMetrics};
24use common_error::ext::BoxedError;
25use common_grpc::channel_manager::{ChannelConfig, ChannelManager, load_client_tls_config};
26use common_meta::peer::{Peer, PeerDiscovery};
27use common_query::{Output, OutputData};
28use common_telemetry::warn;
29use futures::stream::{FuturesUnordered, StreamExt};
30use meta_client::client::MetaClient;
31use query::datafusion::QUERY_PARALLELISM_HINT;
32use query::metrics::terminal_recordbatch_metrics_from_plan;
33use query::options::{FlowQueryExtensions, QueryOptions};
34use rand::rng;
35use rand::seq::SliceRandom;
36use servers::query_handler::grpc::GrpcQueryHandler;
37use session::context::{Channel, QueryContextBuilder, QueryContextRef};
38use session::hints::READ_PREFERENCE_HINT;
39use snafu::{OptionExt, ResultExt};
40use tokio::sync::SetOnce;
41
42use crate::Error;
43use crate::batching_mode::BatchingModeOptions;
44use crate::error::{
45 CreateSinkTableSnafu, ExternalSnafu, InvalidClientConfigSnafu, InvalidRequestSnafu,
46 NoAvailableFrontendSnafu, UnexpectedSnafu,
47};
48
49#[async_trait::async_trait]
53pub trait GrpcQueryHandlerWithBoxedError: Send + Sync + 'static {
54 async fn do_query(
55 &self,
56 query: Request,
57 ctx: QueryContextRef,
58 ) -> std::result::Result<Output, BoxedError>;
59}
60
61#[async_trait::async_trait]
63impl<T: GrpcQueryHandler + Send + Sync + 'static> GrpcQueryHandlerWithBoxedError for T {
64 async fn do_query(
65 &self,
66 query: Request,
67 ctx: QueryContextRef,
68 ) -> std::result::Result<Output, BoxedError> {
69 self.do_query(query, ctx).await.map_err(BoxedError::new)
70 }
71}
72
73#[derive(Debug, Clone)]
74pub struct HandlerMutable {
75 handler: Arc<Mutex<Option<Weak<dyn GrpcQueryHandlerWithBoxedError>>>>,
76 is_initialized: Arc<SetOnce<()>>,
77}
78
79impl HandlerMutable {
80 pub async fn set_handler(&self, handler: Weak<dyn GrpcQueryHandlerWithBoxedError>) {
81 *self.handler.lock().unwrap() = Some(handler);
82 let _ = self.is_initialized.set(());
84 }
85}
86
87#[derive(Debug, Clone)]
91pub enum FrontendClient {
92 Distributed {
93 meta_client: Arc<MetaClient>,
94 chnl_mgr: ChannelManager,
95 query: QueryOptions,
96 batch_opts: BatchingModeOptions,
97 },
98 Standalone {
99 database_client: HandlerMutable,
102 query: QueryOptions,
103 },
104}
105
106impl FrontendClient {
107 pub fn from_empty_grpc_handler(query: QueryOptions) -> (Self, HandlerMutable) {
109 let is_initialized = Arc::new(SetOnce::new());
110 let handler = HandlerMutable {
111 handler: Arc::new(Mutex::new(None)),
112 is_initialized,
113 };
114 (
115 Self::Standalone {
116 database_client: handler.clone(),
117 query,
118 },
119 handler,
120 )
121 }
122
123 pub async fn wait_initialized(&self) {
125 if let FrontendClient::Standalone {
126 database_client, ..
127 } = self
128 {
129 database_client.is_initialized.wait().await;
130 }
131 }
132
133 pub fn from_meta_client(
134 meta_client: Arc<MetaClient>,
135 query: QueryOptions,
136 batch_opts: BatchingModeOptions,
137 ) -> Result<Self, Error> {
138 common_telemetry::info!("Frontend client build without auth");
139 Ok(Self::Distributed {
140 meta_client,
141 chnl_mgr: {
142 let cfg = ChannelConfig::new()
143 .connect_timeout(batch_opts.grpc_conn_timeout)
144 .timeout(Some(batch_opts.query_timeout));
145
146 let tls_config = load_client_tls_config(batch_opts.frontend_tls.clone())
147 .context(InvalidClientConfigSnafu)?;
148 ChannelManager::with_config(cfg, tls_config)
149 },
150 query,
151 batch_opts,
152 })
153 }
154
155 pub fn from_grpc_handler(
156 grpc_handler: Weak<dyn GrpcQueryHandlerWithBoxedError>,
157 query: QueryOptions,
158 ) -> Self {
159 let is_initialized = Arc::new(SetOnce::new_with(Some(())));
160 let handler = HandlerMutable {
161 handler: Arc::new(Mutex::new(Some(grpc_handler))),
162 is_initialized: is_initialized.clone(),
163 };
164
165 Self::Standalone {
166 database_client: handler,
167 query,
168 }
169 }
170}
171
172#[derive(Debug, Clone)]
173pub struct DatabaseWithPeer {
174 pub database: Database,
175 pub peer: Peer,
176}
177
178impl DatabaseWithPeer {
179 fn new(database: Database, peer: Peer) -> Self {
180 Self { database, peer }
181 }
182
183 async fn try_select_one(&self) -> Result<(), Error> {
185 let output = self
187 .database
188 .sql("SELECT 1")
189 .await
190 .with_context(|_| InvalidRequestSnafu {
191 context: format!("Failed to handle `SELECT 1` request at {:?}", self.peer),
192 })?;
193
194 if let OutputData::Stream(stream) = output.data {
195 common_recordbatch::util::collect(stream)
196 .await
197 .map_err(BoxedError::new)
198 .context(ExternalSnafu)?;
199 }
200
201 Ok(())
202 }
203}
204
205impl FrontendClient {
206 pub(crate) async fn scan_for_frontend(&self) -> Result<Vec<Peer>, Error> {
208 let Self::Distributed { meta_client, .. } = self else {
209 return Ok(vec![]);
210 };
211
212 meta_client
213 .active_frontends()
214 .await
215 .map(|nodes| nodes.into_iter().map(|node| node.peer).collect())
216 .map_err(BoxedError::new)
217 .context(ExternalSnafu)
218 }
219
220 pub(crate) async fn check_all_frontends_without_auth(
226 &self,
227 frontends: &[Peer],
228 ) -> Result<Vec<String>, Error> {
229 let Self::Distributed {
230 chnl_mgr,
231 batch_opts,
232 ..
233 } = self
234 else {
235 return Ok(vec![]);
236 };
237
238 let probe_timeout = batch_opts.grpc_conn_timeout;
239 let mut probes = frontends
240 .iter()
241 .map(|peer| {
242 let addr = peer.addr.clone();
243 let chnl_mgr = chnl_mgr.clone();
244
245 async move {
246 let client = Client::with_manager_and_urls(chnl_mgr, vec![addr.clone()]);
247 let database = Database::new(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, client);
248
249 match tokio::time::timeout(probe_timeout, database.sql("SELECT 1")).await {
250 Ok(Ok(_)) => Ok(None),
251 Ok(Err(err)) if err.tonic_code() == Some(tonic::Code::Unauthenticated) => {
252 Err(err).context(InvalidRequestSnafu {
253 context: format!(
254 "Frontend {addr} rejected unauthenticated flownode probe; ensure frontend internal_grpc is advertised to metasrv"
255 ),
256 })
257 }
258 Ok(Err(err)) => Ok(Some(format!("{addr}: {err}"))),
259 Err(_) => Ok(Some(format!(
260 "{addr}: health check timed out after {probe_timeout:?}"
261 ))),
262 }
263 }
264 })
265 .collect::<FuturesUnordered<_>>();
266
267 let mut failures = Vec::new();
268 while let Some(probe_result) = probes.next().await {
269 if let Some(failure) = probe_result? {
270 failures.push(failure);
271 }
272 }
273
274 Ok(failures)
275 }
276
277 async fn get_random_active_frontend(
279 &self,
280 catalog: &str,
281 schema: &str,
282 ) -> Result<DatabaseWithPeer, Error> {
283 let Self::Distributed {
284 meta_client: _,
285 chnl_mgr,
286 query: _,
287 batch_opts,
288 } = self
289 else {
290 return UnexpectedSnafu {
291 reason: "Expect distributed mode",
292 }
293 .fail();
294 };
295
296 let mut interval = tokio::time::interval(batch_opts.grpc_conn_timeout);
297 interval.tick().await;
298 for retry in 0..batch_opts.experimental_grpc_max_retries {
299 let mut frontends = self.scan_for_frontend().await?;
300 frontends.shuffle(&mut rng());
302
303 for peer in frontends {
304 let addr = peer.addr.clone();
305 let client = Client::with_manager_and_urls(chnl_mgr.clone(), vec![addr.clone()]);
306 let database = Database::new(catalog, schema, client);
307 let db = DatabaseWithPeer::new(database, peer);
308 match db.try_select_one().await {
309 Ok(_) => return Ok(db),
310 Err(e) => {
311 warn!(
312 "Failed to connect to frontend {} on retry={}: \n{e:?}",
313 addr, retry
314 );
315 }
316 }
317 }
318 interval.tick().await;
321 }
322
323 NoAvailableFrontendSnafu {
324 timeout: batch_opts.grpc_conn_timeout,
325 context: "No available frontend found that is able to process query",
326 }
327 .fail()
328 }
329
330 pub async fn create(
331 &self,
332 create: CreateTableExpr,
333 catalog: &str,
334 schema: &str,
335 ) -> Result<u32, Error> {
336 self.handle(
337 Request::Ddl(api::v1::DdlRequest {
338 expr: Some(api::v1::ddl_request::Expr::CreateTable(create.clone())),
339 }),
340 catalog,
341 schema,
342 &mut None,
343 )
344 .await
345 .map_err(BoxedError::new)
346 .with_context(|_| CreateSinkTableSnafu {
347 create: create.clone(),
348 })
349 }
350
351 pub async fn sql(&self, catalog: &str, schema: &str, sql: &str) -> Result<Output, Error> {
353 match self {
354 FrontendClient::Distributed { .. } => {
355 let db = self.get_random_active_frontend(catalog, schema).await?;
356 db.database
357 .sql(sql)
358 .await
359 .map_err(BoxedError::new)
360 .context(ExternalSnafu)
361 }
362 FrontendClient::Standalone {
363 database_client, ..
364 } => {
365 let ctx = QueryContextBuilder::default()
366 .current_catalog(catalog.to_string())
367 .current_schema(schema.to_string())
368 .build();
369 let ctx = Arc::new(ctx);
370 {
371 let database_client = {
372 database_client
373 .handler
374 .lock()
375 .map_err(|e| {
376 UnexpectedSnafu {
377 reason: format!("Failed to lock database client: {e}"),
378 }
379 .build()
380 })?
381 .as_ref()
382 .context(UnexpectedSnafu {
383 reason: "Standalone's frontend instance is not set",
384 })?
385 .upgrade()
386 .context(UnexpectedSnafu {
387 reason: "Failed to upgrade database client",
388 })?
389 };
390 let req = Request::Query(QueryRequest {
391 query: Some(Query::Sql(sql.to_string())),
392 });
393 database_client
394 .do_query(req, ctx)
395 .await
396 .map_err(BoxedError::new)
397 .context(ExternalSnafu)
398 }
399 }
400 }
401 }
402
403 pub async fn row_inserts(
405 &self,
406 catalog: &str,
407 schema: &str,
408 requests: RowInsertRequests,
409 hints: &[(&str, &str)],
410 ) -> Result<u32, Error> {
411 match self {
412 FrontendClient::Distributed { .. } => {
413 let db = self.get_random_active_frontend(catalog, schema).await?;
414 db.database
415 .row_inserts_with_hints(requests, hints)
416 .await
417 .with_context(|_| InvalidRequestSnafu {
418 context: format!("Failed to handle row inserts at {:?}", db.peer),
419 })
420 }
421 FrontendClient::Standalone {
422 database_client, ..
423 } => {
424 let extensions = HashMap::from_iter(
425 hints
426 .iter()
427 .map(|(key, value)| ((*key).to_string(), (*value).to_string())),
428 );
429 let ctx = QueryContextBuilder::default()
430 .current_catalog(catalog.to_string())
431 .current_schema(schema.to_string())
432 .extensions(extensions)
433 .build();
434 let ctx = Arc::new(ctx);
435 {
436 let database_client = {
437 database_client
438 .handler
439 .lock()
440 .unwrap()
441 .as_ref()
442 .context(UnexpectedSnafu {
443 reason: "Standalone's frontend instance is not set",
444 })?
445 .upgrade()
446 .context(UnexpectedSnafu {
447 reason: "Failed to upgrade database client",
448 })?
449 };
450 let resp: common_query::Output = database_client
451 .do_query(Request::RowInserts(requests), ctx)
452 .await
453 .map_err(BoxedError::new)
454 .context(ExternalSnafu)?;
455 match resp.data {
456 OutputData::AffectedRows(rows) => Ok(rows.try_into().map_err(|_| {
457 UnexpectedSnafu {
458 reason: format!("Failed to convert rows to u32: {}", rows),
459 }
460 .build()
461 })?),
462 _ => UnexpectedSnafu {
463 reason: "Unexpected output data",
464 }
465 .fail(),
466 }
467 }
468 }
469 }
470 }
471
472 pub(crate) async fn query_with_terminal_metrics(
475 &self,
476 catalog: &str,
477 schema: &str,
478 request: QueryRequest,
479 extensions: &[(&str, &str)],
480 snapshot_seqs: &HashMap<u64, u64>,
481 peer_desc: &mut Option<PeerDesc>,
482 ) -> Result<OutputWithMetrics, Error> {
483 let flow_extensions = build_flow_extensions(extensions)?;
484 match self {
485 FrontendClient::Distributed {
486 query, batch_opts, ..
487 } => {
488 let query_parallelism = query.parallelism.to_string();
489 let hints = vec![
490 (QUERY_PARALLELISM_HINT, query_parallelism.as_str()),
491 (READ_PREFERENCE_HINT, batch_opts.read_preference.as_ref()),
492 ];
493 let db = self.get_random_active_frontend(catalog, schema).await?;
494 *peer_desc = Some(PeerDesc::Dist {
495 peer: db.peer.clone(),
496 });
497 db.database
498 .flight_request()
499 .with_hints(&hints)
500 .with_flow_extensions(extensions)
501 .with_snapshot_seqs(snapshot_seqs)
502 .with_timeout(batch_opts.experimental_flight_do_get_timeout)
503 .query_with_terminal_metrics(request)
504 .await
505 .map_err(BoxedError::new)
506 .context(ExternalSnafu)
507 }
508 FrontendClient::Standalone {
509 database_client,
510 query,
511 } => {
512 *peer_desc = Some(PeerDesc::Standalone);
513 let mut extensions_map = HashMap::from([(
514 QUERY_PARALLELISM_HINT.to_string(),
515 query.parallelism.to_string(),
516 )]);
517 for (key, value) in extensions {
518 extensions_map.insert((*key).to_string(), (*value).to_string());
519 }
520 let ctx = QueryContextBuilder::default()
521 .current_catalog(catalog.to_string())
522 .current_schema(schema.to_string())
523 .extensions(extensions_map)
524 .channel(Channel::Internal)
525 .snapshot_seqs(Arc::new(RwLock::new(snapshot_seqs.clone())))
526 .build();
527 let ctx = Arc::new(ctx);
528 let database_client = {
529 database_client
530 .handler
531 .lock()
532 .map_err(|e| {
533 UnexpectedSnafu {
534 reason: format!("Failed to lock database client: {e}"),
535 }
536 .build()
537 })?
538 .as_ref()
539 .context(UnexpectedSnafu {
540 reason: "Standalone's frontend instance is not set",
541 })?
542 .upgrade()
543 .context(UnexpectedSnafu {
544 reason: "Failed to upgrade database client",
545 })?
546 };
547 database_client
548 .do_query(Request::Query(request), ctx.clone())
549 .await
550 .map(|output| {
551 wrap_standalone_output_with_terminal_metrics(output, &flow_extensions)
552 })
553 .map_err(BoxedError::new)
554 .context(ExternalSnafu)
555 }
556 }
557 }
558
559 pub(crate) async fn handle_insert_once(
565 &self,
566 req: api::v1::greptime_request::Request,
567 catalog: &str,
568 schema: &str,
569 peer_desc: &mut Option<PeerDesc>,
570 ) -> Result<u32, Error> {
571 match self {
572 FrontendClient::Distributed { .. } => {
573 let db = self.get_random_active_frontend(catalog, schema).await?;
574
575 *peer_desc = Some(PeerDesc::Dist {
576 peer: db.peer.clone(),
577 });
578
579 db.database
580 .handle(req.clone())
581 .await
582 .with_context(|_| InvalidRequestSnafu {
583 context: format!("Failed to handle request at {:?}: {:?}", db.peer, req),
584 })
585 }
586 FrontendClient::Standalone {
587 database_client,
588 query,
589 } => {
590 let ctx = QueryContextBuilder::default()
591 .current_catalog(catalog.to_string())
592 .current_schema(schema.to_string())
593 .channel(Channel::Internal)
594 .extensions(HashMap::from([(
595 QUERY_PARALLELISM_HINT.to_string(),
596 query.parallelism.to_string(),
597 )]))
598 .build();
599 let ctx = Arc::new(ctx);
600 let database_client = {
601 database_client
602 .handler
603 .lock()
604 .unwrap()
605 .as_ref()
606 .context(UnexpectedSnafu {
607 reason: "Standalone's frontend instance is not set",
608 })?
609 .upgrade()
610 .context(UnexpectedSnafu {
611 reason: "Failed to upgrade database client",
612 })?
613 };
614 let resp: common_query::Output = database_client
615 .do_query(req, ctx)
616 .await
617 .map_err(BoxedError::new)
618 .context(ExternalSnafu)?;
619 match resp.data {
620 common_query::OutputData::AffectedRows(rows) => rows.try_into().map_err(|_| {
621 UnexpectedSnafu {
622 reason: format!("Failed to convert rows to u32: {}", rows),
623 }
624 .build()
625 }),
626 _ => UnexpectedSnafu {
627 reason: "Unexpected output data",
628 }
629 .fail(),
630 }
631 }
632 }
633 }
634
635 pub(crate) async fn handle(
637 &self,
638 req: api::v1::greptime_request::Request,
639 catalog: &str,
640 schema: &str,
641 peer_desc: &mut Option<PeerDesc>,
642 ) -> Result<u32, Error> {
643 match self {
644 FrontendClient::Distributed {
645 query, batch_opts, ..
646 } => {
647 let db = self.get_random_active_frontend(catalog, schema).await?;
648
649 *peer_desc = Some(PeerDesc::Dist {
650 peer: db.peer.clone(),
651 });
652
653 db.database
654 .handle_with_retry(
655 req.clone(),
656 batch_opts.experimental_grpc_max_retries,
657 &[
658 (QUERY_PARALLELISM_HINT, &query.parallelism.to_string()),
659 (READ_PREFERENCE_HINT, batch_opts.read_preference.as_ref()),
660 ],
661 )
662 .await
663 .with_context(|_| InvalidRequestSnafu {
664 context: format!("Failed to handle request at {:?}: {:?}", db.peer, req),
665 })
666 }
667 FrontendClient::Standalone {
668 database_client,
669 query,
670 } => {
671 let ctx = QueryContextBuilder::default()
672 .current_catalog(catalog.to_string())
673 .current_schema(schema.to_string())
674 .extensions(HashMap::from([(
675 QUERY_PARALLELISM_HINT.to_string(),
676 query.parallelism.to_string(),
677 )]))
678 .build();
679 let ctx = Arc::new(ctx);
680 {
681 let database_client = {
682 database_client
683 .handler
684 .lock()
685 .unwrap()
686 .as_ref()
687 .context(UnexpectedSnafu {
688 reason: "Standalone's frontend instance is not set",
689 })?
690 .upgrade()
691 .context(UnexpectedSnafu {
692 reason: "Failed to upgrade database client",
693 })?
694 };
695 let resp: common_query::Output = database_client
696 .do_query(req, ctx)
697 .await
698 .map_err(BoxedError::new)
699 .context(ExternalSnafu)?;
700 match resp.data {
701 OutputData::AffectedRows(rows) => Ok(rows.try_into().map_err(|_| {
702 UnexpectedSnafu {
703 reason: format!("Failed to convert rows to u32: {}", rows),
704 }
705 .build()
706 })?),
707 _ => UnexpectedSnafu {
708 reason: "Unexpected output data",
709 }
710 .fail(),
711 }
712 }
713 }
714 }
715 }
716}
717
718fn build_flow_extensions(extensions: &[(&str, &str)]) -> Result<FlowQueryExtensions, Error> {
719 let flow_extensions = HashMap::from_iter(
720 extensions
721 .iter()
722 .map(|(key, value)| ((*key).to_string(), (*value).to_string())),
723 );
724 FlowQueryExtensions::parse_flow_extensions(&flow_extensions)
725 .map_err(BoxedError::new)
726 .context(ExternalSnafu)
727 .map(|extensions| extensions.unwrap_or_default())
728}
729
730fn wrap_standalone_output_with_terminal_metrics(
731 output: Output,
732 flow_extensions: &FlowQueryExtensions,
733) -> OutputWithMetrics {
734 let should_collect_region_watermark = flow_extensions.should_collect_region_watermark();
735 let terminal_metrics =
736 if should_collect_region_watermark && !matches!(&output.data, OutputData::Stream(_)) {
737 output
738 .meta
739 .plan
740 .clone()
741 .and_then(terminal_recordbatch_metrics_from_plan)
742 } else {
743 None
744 };
745 let result = OutputWithMetrics::from_output(output);
746 if let Some(metrics) = terminal_metrics {
747 result.metrics.update(Some(metrics));
748 }
749 result
750}
751
752#[derive(Debug, Default, Clone)]
754pub enum PeerDesc {
755 #[default]
757 Unknown,
758 Dist {
760 peer: Peer,
762 },
763 Standalone,
765}
766
767impl std::fmt::Display for PeerDesc {
768 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
769 match self {
770 PeerDesc::Unknown => write!(f, "unknown"),
771 PeerDesc::Dist { peer } => write!(f, "{}", peer.addr),
772 PeerDesc::Standalone => write!(f, "standalone"),
773 }
774 }
775}
776
777#[cfg(test)]
778mod tests {
779 use std::pin::Pin;
780 use std::sync::atomic::{AtomicUsize, Ordering};
781 use std::task::{Context, Poll};
782 use std::time::Duration;
783
784 use api::v1::query_request::Query;
785 use arrow_flight::flight_service_server::FlightServiceServer;
786 use arrow_flight::{FlightData, Ticket};
787 use common_grpc::flight::FlightEncoder;
788 use common_query::{Output, OutputData};
789 use common_recordbatch::adapter::RecordBatchMetrics;
790 use common_recordbatch::{OrderOption, RecordBatch, RecordBatchStream};
791 use datatypes::arrow::datatypes::Schema as ArrowSchema;
792 use datatypes::prelude::{ConcreteDataType, VectorRef};
793 use datatypes::schema::{ColumnSchema, Schema};
794 use datatypes::vectors::Int32Vector;
795 use futures::StreamExt;
796 use servers::grpc::flight::{FlightCraft, FlightCraftWrapper, TonicStream};
797 use tokio::net::TcpListener;
798 use tokio::task::JoinHandle;
799 use tokio::time::timeout;
800 use tokio_stream::wrappers::TcpListenerStream;
801 use tonic::{Request as TonicRequest, Response as TonicResponse, Status};
802
803 use super::*;
804
805 #[derive(Debug)]
806 struct NoopHandler;
807
808 struct MockMetricsStream {
809 schema: datatypes::schema::SchemaRef,
810 batch: Option<RecordBatch>,
811 metrics: RecordBatchMetrics,
812 terminal_metrics_only: bool,
813 }
814
815 impl futures::Stream for MockMetricsStream {
816 type Item = common_recordbatch::error::Result<RecordBatch>;
817
818 fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
819 Poll::Ready(self.batch.take().map(Ok))
820 }
821
822 fn size_hint(&self) -> (usize, Option<usize>) {
823 (
824 usize::from(self.batch.is_some()),
825 Some(usize::from(self.batch.is_some())),
826 )
827 }
828 }
829
830 impl RecordBatchStream for MockMetricsStream {
831 fn name(&self) -> &str {
832 "MockMetricsStream"
833 }
834
835 fn schema(&self) -> datatypes::schema::SchemaRef {
836 self.schema.clone()
837 }
838
839 fn output_ordering(&self) -> Option<&[OrderOption]> {
840 None
841 }
842
843 fn metrics(&self) -> Option<RecordBatchMetrics> {
844 if self.terminal_metrics_only && self.batch.is_some() {
845 return None;
846 }
847 Some(self.metrics.clone())
848 }
849 }
850
851 #[derive(Debug)]
852 struct MetricsHandler;
853
854 #[derive(Debug)]
855 struct InsertOnceHandler {
856 calls: Arc<AtomicUsize>,
857 }
858
859 #[derive(Debug)]
860 struct ExtensionAwareHandler;
861
862 #[derive(Debug)]
863 struct SnapshotBindingHandler;
864
865 #[derive(Debug)]
866 struct RejectUnauthenticatedFlight;
867
868 #[derive(Debug)]
869 struct SlowFlight;
870
871 struct DelayedEofFlight {
872 schema_sent: Mutex<Option<tokio::sync::oneshot::Sender<()>>>,
873 release: Arc<tokio::sync::Notify>,
874 }
875
876 #[derive(Debug)]
877 struct LateStreamErrorFlight;
878
879 struct WaitForConcurrentFlight {
880 barrier: Arc<tokio::sync::Barrier>,
881 }
882
883 #[async_trait::async_trait]
884 impl GrpcQueryHandlerWithBoxedError for NoopHandler {
885 async fn do_query(
886 &self,
887 _query: Request,
888 _ctx: QueryContextRef,
889 ) -> std::result::Result<Output, BoxedError> {
890 Ok(Output::new_with_affected_rows(0))
891 }
892 }
893
894 #[async_trait::async_trait]
895 impl GrpcQueryHandlerWithBoxedError for InsertOnceHandler {
896 async fn do_query(
897 &self,
898 _query: Request,
899 ctx: QueryContextRef,
900 ) -> std::result::Result<Output, BoxedError> {
901 assert_eq!(ctx.channel(), Channel::Internal);
902 self.calls.fetch_add(1, Ordering::SeqCst);
903 Ok(Output::new_with_affected_rows(1))
904 }
905 }
906
907 #[async_trait::async_trait]
908 impl GrpcQueryHandlerWithBoxedError for MetricsHandler {
909 async fn do_query(
910 &self,
911 _query: Request,
912 _ctx: QueryContextRef,
913 ) -> std::result::Result<Output, BoxedError> {
914 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
915 "v",
916 ConcreteDataType::int32_datatype(),
917 false,
918 )]));
919 let batch = RecordBatch::new(
920 schema.clone(),
921 vec![Arc::new(Int32Vector::from_slice([1, 2])) as VectorRef],
922 )
923 .unwrap();
924 Ok(Output::new_with_stream(Box::pin(MockMetricsStream {
925 schema,
926 batch: Some(batch),
927 metrics: RecordBatchMetrics {
928 region_watermarks: vec![common_recordbatch::adapter::RegionWatermarkEntry {
929 region_id: 42,
930 watermark: Some(99),
931 }],
932 ..Default::default()
933 },
934 terminal_metrics_only: true,
935 })))
936 }
937 }
938
939 #[async_trait::async_trait]
940 impl GrpcQueryHandlerWithBoxedError for ExtensionAwareHandler {
941 async fn do_query(
942 &self,
943 _query: Request,
944 ctx: QueryContextRef,
945 ) -> std::result::Result<Output, BoxedError> {
946 assert_eq!(ctx.extension("flow.return_region_seq"), Some("true"));
947 assert_eq!(ctx.channel(), Channel::Internal);
948 Ok(Output::new_with_affected_rows(1))
949 }
950 }
951
952 #[async_trait::async_trait]
953 impl GrpcQueryHandlerWithBoxedError for SnapshotBindingHandler {
954 async fn do_query(
955 &self,
956 _query: Request,
957 ctx: QueryContextRef,
958 ) -> std::result::Result<Output, BoxedError> {
959 assert_eq!(ctx.extension("flow.return_region_seq"), Some("true"));
960 assert_eq!(ctx.get_snapshot(1), Some(10));
961 assert_eq!(ctx.get_snapshot(2), Some(20));
962 ctx.set_snapshot(42, 99);
963 Ok(Output::new_with_affected_rows(1))
964 }
965 }
966
967 #[async_trait::async_trait]
968 impl FlightCraft for RejectUnauthenticatedFlight {
969 async fn do_get(
970 &self,
971 _request: TonicRequest<Ticket>,
972 ) -> std::result::Result<TonicResponse<TonicStream<FlightData>>, Status> {
973 Err(Status::unauthenticated("auth failed"))
974 }
975 }
976
977 #[async_trait::async_trait]
978 impl FlightCraft for SlowFlight {
979 async fn do_get(
980 &self,
981 _request: TonicRequest<Ticket>,
982 ) -> std::result::Result<TonicResponse<TonicStream<FlightData>>, Status> {
983 tokio::time::sleep(Duration::from_secs(60)).await;
984 Err(Status::unavailable("slow response"))
985 }
986 }
987
988 #[async_trait::async_trait]
989 impl FlightCraft for DelayedEofFlight {
990 async fn do_get(
991 &self,
992 _request: TonicRequest<Ticket>,
993 ) -> std::result::Result<TonicResponse<TonicStream<FlightData>>, Status> {
994 let schema = FlightEncoder::default().encode_schema(&ArrowSchema::empty());
995 let schema_sent = self.schema_sent.lock().unwrap().take();
996 let schema_stream = futures::stream::once(async move {
997 if let Some(schema_sent) = schema_sent {
998 let _ = schema_sent.send(());
999 }
1000 Ok(schema)
1001 });
1002 let release = self.release.clone();
1003 let delayed_eof = futures::stream::unfold(release, |release| async move {
1004 release.notified().await;
1005 None::<(std::result::Result<FlightData, Status>, _)>
1006 });
1007
1008 Ok(TonicResponse::new(Box::pin(
1009 schema_stream.chain(delayed_eof),
1010 )))
1011 }
1012 }
1013
1014 #[async_trait::async_trait]
1015 impl FlightCraft for LateStreamErrorFlight {
1016 async fn do_get(
1017 &self,
1018 _request: TonicRequest<Ticket>,
1019 ) -> std::result::Result<TonicResponse<TonicStream<FlightData>>, Status> {
1020 let schema = FlightEncoder::default().encode_schema(&ArrowSchema::empty());
1021 let stream =
1022 futures::stream::iter([Ok(schema), Err(Status::unavailable("late stream error"))]);
1023 Ok(TonicResponse::new(Box::pin(stream)))
1024 }
1025 }
1026
1027 #[async_trait::async_trait]
1028 impl FlightCraft for WaitForConcurrentFlight {
1029 async fn do_get(
1030 &self,
1031 _request: TonicRequest<Ticket>,
1032 ) -> std::result::Result<TonicResponse<TonicStream<FlightData>>, Status> {
1033 self.barrier.wait().await;
1034 Err(Status::unavailable("probe started concurrently"))
1035 }
1036 }
1037
1038 async fn start_flight_server<T: FlightCraft>(handler: T) -> (String, JoinHandle<()>) {
1039 let listener = TcpListener::bind("127.0.0.1:0")
1040 .await
1041 .expect("bind test flight server");
1042 let addr = listener.local_addr().expect("local addr").to_string();
1043 let server = tokio::spawn(async move {
1044 tonic::transport::Server::builder()
1045 .add_service(FlightServiceServer::new(FlightCraftWrapper(handler)))
1046 .serve_with_incoming(TcpListenerStream::new(listener))
1047 .await
1048 .expect("serve test flight server");
1049 });
1050
1051 (addr, server)
1052 }
1053
1054 #[tokio::test]
1055 async fn wait_initialized() {
1056 let (client, handler_mut) =
1057 FrontendClient::from_empty_grpc_handler(QueryOptions::default());
1058
1059 assert!(
1060 timeout(Duration::from_millis(50), client.wait_initialized())
1061 .await
1062 .is_err()
1063 );
1064
1065 let handler: Arc<dyn GrpcQueryHandlerWithBoxedError> = Arc::new(NoopHandler);
1066 handler_mut.set_handler(Arc::downgrade(&handler)).await;
1067
1068 timeout(Duration::from_secs(1), client.wait_initialized())
1069 .await
1070 .expect("wait_initialized should complete after handler is set");
1071
1072 timeout(Duration::from_millis(10), client.wait_initialized())
1073 .await
1074 .expect("wait_initialized should be a no-op once initialized");
1075
1076 let handler: Arc<dyn GrpcQueryHandlerWithBoxedError> = Arc::new(NoopHandler);
1077 let client =
1078 FrontendClient::from_grpc_handler(Arc::downgrade(&handler), QueryOptions::default());
1079 assert!(
1080 timeout(Duration::from_millis(10), client.wait_initialized())
1081 .await
1082 .is_ok()
1083 );
1084
1085 let meta_client = Arc::new(MetaClient::new(0, api::v1::meta::Role::Frontend));
1086 let client = FrontendClient::from_meta_client(
1087 meta_client,
1088 QueryOptions::default(),
1089 BatchingModeOptions::default(),
1090 )
1091 .unwrap();
1092 assert!(
1093 timeout(Duration::from_millis(10), client.wait_initialized())
1094 .await
1095 .is_ok()
1096 );
1097 }
1098
1099 #[tokio::test]
1100 async fn test_handle_insert_once_calls_standalone_handler_once() {
1101 let calls = Arc::new(AtomicUsize::new(0));
1102 let handler: Arc<dyn GrpcQueryHandlerWithBoxedError> = Arc::new(InsertOnceHandler {
1103 calls: calls.clone(),
1104 });
1105 let client =
1106 FrontendClient::from_grpc_handler(Arc::downgrade(&handler), QueryOptions::default());
1107 let mut peer_desc = None;
1108
1109 let affected_rows = client
1110 .handle_insert_once(
1111 Request::RowInserts(api::v1::RowInsertRequests { inserts: vec![] }),
1112 "greptime",
1113 "public",
1114 &mut peer_desc,
1115 )
1116 .await
1117 .unwrap();
1118
1119 assert_eq!(affected_rows, 1);
1120 assert_eq!(calls.load(Ordering::SeqCst), 1);
1121 assert!(peer_desc.is_none());
1122 }
1123
1124 #[tokio::test]
1125 async fn test_query_with_terminal_metrics_tracks_watermark_in_standalone_mode() {
1126 let handler: Arc<dyn GrpcQueryHandlerWithBoxedError> = Arc::new(MetricsHandler);
1127 let client =
1128 FrontendClient::from_grpc_handler(Arc::downgrade(&handler), QueryOptions::default());
1129 let mut peer_desc = None;
1130
1131 let result = client
1132 .query_with_terminal_metrics(
1133 "greptime",
1134 "public",
1135 QueryRequest {
1136 query: Some(Query::Sql("select 1".to_string())),
1137 },
1138 &[],
1139 &HashMap::new(),
1140 &mut peer_desc,
1141 )
1142 .await
1143 .unwrap();
1144 assert!(matches!(peer_desc, Some(PeerDesc::Standalone)));
1145
1146 let terminal_metrics = result.metrics.clone();
1147 assert!(!result.metrics.is_ready());
1148 assert!(terminal_metrics.get().is_none());
1149
1150 let OutputData::Stream(mut stream) = result.output.data else {
1151 panic!("expected stream output");
1152 };
1153 while stream.next().await.is_some() {}
1154
1155 assert!(terminal_metrics.is_ready());
1156 assert_eq!(
1157 terminal_metrics.region_watermark_map(),
1158 Some(HashMap::from([(42_u64, 99_u64)]))
1159 );
1160 }
1161
1162 #[tokio::test]
1163 async fn test_query_with_terminal_metrics_forwards_flow_extensions_in_standalone_mode() {
1164 let handler: Arc<dyn GrpcQueryHandlerWithBoxedError> = Arc::new(ExtensionAwareHandler);
1165 let client =
1166 FrontendClient::from_grpc_handler(Arc::downgrade(&handler), QueryOptions::default());
1167 let mut peer_desc = None;
1168
1169 let result = client
1170 .query_with_terminal_metrics(
1171 "greptime",
1172 "public",
1173 QueryRequest {
1174 query: Some(Query::Sql("insert into t select 1".to_string())),
1175 },
1176 &[("flow.return_region_seq", "true")],
1177 &HashMap::new(),
1178 &mut peer_desc,
1179 )
1180 .await
1181 .unwrap();
1182 assert!(matches!(peer_desc, Some(PeerDesc::Standalone)));
1183
1184 assert!(result.metrics.is_ready());
1185 assert!(result.region_watermark_map().is_none());
1186 }
1187
1188 #[tokio::test]
1189 async fn test_query_with_terminal_metrics_uses_standalone_snapshot_bounds() {
1190 let handler: Arc<dyn GrpcQueryHandlerWithBoxedError> = Arc::new(SnapshotBindingHandler);
1191 let client =
1192 FrontendClient::from_grpc_handler(Arc::downgrade(&handler), QueryOptions::default());
1193 let mut peer_desc = None;
1194
1195 let result = client
1196 .query_with_terminal_metrics(
1197 "greptime",
1198 "public",
1199 QueryRequest {
1200 query: Some(Query::Sql("insert into t select * from src".to_string())),
1201 },
1202 &[("flow.return_region_seq", "true")],
1203 &HashMap::from([(1, 10), (2, 20)]),
1204 &mut peer_desc,
1205 )
1206 .await
1207 .unwrap();
1208 assert!(matches!(peer_desc, Some(PeerDesc::Standalone)));
1209
1210 assert!(result.metrics.is_ready());
1211 assert_eq!(result.region_watermark_map(), None);
1212 }
1213
1214 #[tokio::test]
1215 async fn test_query_with_terminal_metrics_rejects_invalid_flow_extensions() {
1216 let handler: Arc<dyn GrpcQueryHandlerWithBoxedError> = Arc::new(NoopHandler);
1217 let client =
1218 FrontendClient::from_grpc_handler(Arc::downgrade(&handler), QueryOptions::default());
1219 let mut peer_desc = None;
1220
1221 let err = client
1222 .query_with_terminal_metrics(
1223 "greptime",
1224 "public",
1225 QueryRequest {
1226 query: Some(Query::Sql("select 1".to_string())),
1227 },
1228 &[("flow.return_region_seq", "not-a-bool")],
1229 &HashMap::new(),
1230 &mut peer_desc,
1231 )
1232 .await
1233 .unwrap_err();
1234
1235 assert!(format!("{err:?}").contains("Invalid value for flow.return_region_seq"));
1236 }
1237
1238 #[tokio::test]
1239 async fn test_try_select_one_waits_for_stream_eof() {
1240 let (schema_sent, schema_sent_rx) = tokio::sync::oneshot::channel();
1241 let release = Arc::new(tokio::sync::Notify::new());
1242 let (addr, server) = start_flight_server(DelayedEofFlight {
1243 schema_sent: Mutex::new(Some(schema_sent)),
1244 release: release.clone(),
1245 })
1246 .await;
1247 let database = Database::new(
1248 DEFAULT_CATALOG_NAME,
1249 DEFAULT_SCHEMA_NAME,
1250 Client::with_urls([addr.as_str()]),
1251 );
1252 let db = DatabaseWithPeer::new(
1253 database,
1254 Peer {
1255 id: 1,
1256 addr: addr.clone(),
1257 },
1258 );
1259 let mut probe = tokio::spawn(async move { db.try_select_one().await });
1260
1261 timeout(Duration::from_secs(1), schema_sent_rx)
1262 .await
1263 .expect("server should send the schema")
1264 .expect("schema signal should be sent");
1265 assert!(
1266 timeout(Duration::from_millis(100), &mut probe)
1267 .await
1268 .is_err(),
1269 "SELECT 1 must wait for the delayed stream tail and EOF"
1270 );
1271
1272 release.notify_one();
1273 timeout(Duration::from_secs(1), &mut probe)
1274 .await
1275 .expect("SELECT 1 should complete after EOF")
1276 .expect("probe task should not panic")
1277 .expect("SELECT 1 should succeed after EOF");
1278 server.abort();
1279 }
1280
1281 #[tokio::test]
1282 async fn test_try_select_one_propagates_late_stream_error() {
1283 let (addr, server) = start_flight_server(LateStreamErrorFlight).await;
1284 let database = Database::new(
1285 DEFAULT_CATALOG_NAME,
1286 DEFAULT_SCHEMA_NAME,
1287 Client::with_urls([addr.as_str()]),
1288 );
1289 let db = DatabaseWithPeer::new(
1290 database,
1291 Peer {
1292 id: 1,
1293 addr: addr.clone(),
1294 },
1295 );
1296
1297 let err = db.try_select_one().await.unwrap_err();
1298 server.abort();
1299
1300 assert!(format!("{err:?}").contains("late stream error"));
1301 }
1302
1303 #[tokio::test]
1304 async fn test_check_all_frontends_without_auth_fails_fast_on_unauthenticated_frontend() {
1305 let (addr, server) = start_flight_server(RejectUnauthenticatedFlight).await;
1306 let client = FrontendClient::from_meta_client(
1307 Arc::new(MetaClient::new(0, api::v1::meta::Role::Frontend)),
1308 QueryOptions::default(),
1309 BatchingModeOptions::default(),
1310 )
1311 .unwrap();
1312
1313 let err = client
1314 .check_all_frontends_without_auth(&[Peer {
1315 id: 1,
1316 addr: addr.clone(),
1317 }])
1318 .await
1319 .unwrap_err();
1320 server.abort();
1321
1322 let Error::InvalidRequest {
1323 context, source, ..
1324 } = err
1325 else {
1326 panic!("expected InvalidRequest, got {err:?}");
1327 };
1328 assert!(context.contains(&addr));
1329 assert!(context.contains("rejected unauthenticated flownode probe"));
1330 assert_eq!(source.tonic_code(), Some(tonic::Code::Unauthenticated));
1331 }
1332
1333 #[tokio::test]
1334 async fn test_check_all_frontends_without_auth_uses_grpc_connection_timeout() {
1335 let (addr, server) = start_flight_server(SlowFlight).await;
1336 let client = FrontendClient::from_meta_client(
1337 Arc::new(MetaClient::new(0, api::v1::meta::Role::Frontend)),
1338 QueryOptions::default(),
1339 BatchingModeOptions {
1340 grpc_conn_timeout: Duration::from_millis(50),
1341 ..Default::default()
1342 },
1343 )
1344 .unwrap();
1345
1346 let failures = client
1347 .check_all_frontends_without_auth(&[Peer {
1348 id: 1,
1349 addr: addr.clone(),
1350 }])
1351 .await
1352 .unwrap();
1353 server.abort();
1354
1355 assert_eq!(failures.len(), 1);
1356 assert!(failures[0].contains(&addr));
1357 assert!(failures[0].contains("health check timed out"));
1358 }
1359
1360 #[tokio::test]
1361 async fn test_check_all_frontends_without_auth_checks_frontends_concurrently() {
1362 let barrier = Arc::new(tokio::sync::Barrier::new(2));
1363 let (addr1, server1) = start_flight_server(WaitForConcurrentFlight {
1364 barrier: barrier.clone(),
1365 })
1366 .await;
1367 let (addr2, server2) = start_flight_server(WaitForConcurrentFlight { barrier }).await;
1368 let client = FrontendClient::from_meta_client(
1369 Arc::new(MetaClient::new(0, api::v1::meta::Role::Frontend)),
1370 QueryOptions::default(),
1371 BatchingModeOptions {
1372 grpc_conn_timeout: Duration::from_millis(500),
1373 ..Default::default()
1374 },
1375 )
1376 .unwrap();
1377
1378 let failures = timeout(
1379 Duration::from_secs(2),
1380 client.check_all_frontends_without_auth(&[
1381 Peer {
1382 id: 1,
1383 addr: addr1.clone(),
1384 },
1385 Peer {
1386 id: 2,
1387 addr: addr2.clone(),
1388 },
1389 ]),
1390 )
1391 .await
1392 .expect("concurrent probes should complete before per-peer timeouts")
1393 .unwrap();
1394 server1.abort();
1395 server2.abort();
1396
1397 assert_eq!(failures.len(), 2);
1398 assert!(failures.iter().any(|failure| failure.contains(&addr1)));
1399 assert!(failures.iter().any(|failure| failure.contains(&addr2)));
1400 assert!(
1401 failures
1402 .iter()
1403 .all(|failure| !failure.contains("health check timed out")),
1404 "sequential probes would time out before both requests reach the barrier: {failures:?}"
1405 );
1406 }
1407}