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