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