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