1pub mod builder;
16mod dashboard;
17mod grpc;
18mod influxdb;
19mod jaeger;
20mod log_handler;
21mod logs;
22mod opentsdb;
23mod otlp;
24pub mod prom_store;
25mod promql;
26mod region_query;
27pub mod standalone;
28
29use std::pin::Pin;
30use std::sync::atomic::AtomicBool;
31use std::sync::{Arc, atomic};
32use std::time::{Duration, SystemTime};
33
34use async_stream::stream;
35use async_trait::async_trait;
36use auth::{PermissionChecker, PermissionCheckerRef, PermissionReq};
37use catalog::CatalogManagerRef;
38use catalog::process_manager::{
39 ProcessManagerRef, QueryStatement as CatalogQueryStatement, SlowQueryTimer,
40};
41use client::OutputData;
42use common_base::Plugins;
43use common_base::cancellation::CancellableFuture;
44use common_error::ext::{BoxedError, ErrorExt};
45use common_event_recorder::EventRecorderRef;
46use common_meta::cache_invalidator::CacheInvalidatorRef;
47use common_meta::key::TableMetadataManagerRef;
48use common_meta::key::table_name::TableNameKey;
49use common_meta::node_manager::NodeManagerRef;
50use common_meta::procedure_executor::ProcedureExecutorRef;
51use common_query::Output;
52use common_recordbatch::RecordBatchStreamWrapper;
53use common_recordbatch::error::StreamTimeoutSnafu;
54use common_telemetry::logging::SlowQueryOptions;
55use common_telemetry::{debug, error, tracing};
56use dashmap::DashMap;
57use datafusion_expr::LogicalPlan;
58use futures::{Stream, StreamExt};
59use lazy_static::lazy_static;
60use operator::delete::DeleterRef;
61use operator::insert::InserterRef;
62use operator::statement::{StatementExecutor, StatementExecutorRef};
63use partition::manager::PartitionRuleManagerRef;
64use pipeline::pipeline_operator::PipelineOperator;
65use prometheus::HistogramTimer;
66use promql_parser::label::Matcher;
67use query::QueryEngineRef;
68use query::metrics::OnDone;
69use query::parser::{PromQuery, QueryLanguageParser, QueryStatement};
70use query::query_engine::DescribeResult;
71use query::query_engine::options::{QueryOptions, validate_catalog_and_schema};
72use servers::error::{
73 self as server_error, AuthSnafu, CommonMetaSnafu, ExecuteQuerySnafu,
74 OtlpMetricModeIncompatibleSnafu, ParsePromQLSnafu, UnexpectedResultSnafu,
75};
76use servers::interceptor::{
77 PromQueryInterceptor, PromQueryInterceptorRef, SqlQueryInterceptor, SqlQueryInterceptorRef,
78};
79use servers::otlp::metrics::legacy_normalize_otlp_name;
80use servers::prometheus_handler::PrometheusHandler;
81use servers::query_handler::sql::SqlQueryHandler;
82use session::context::{Channel, QueryContextRef};
83use session::table_name::table_idents_to_full_name;
84use snafu::prelude::*;
85use sql::ast::ObjectNamePartExt;
86use sql::dialect::Dialect;
87use sql::parser::{ParseOptions, ParserContext};
88use sql::statements::comment::CommentObject;
89use sql::statements::copy::{CopyDatabase, CopyTable};
90use sql::statements::statement::Statement;
91use sql::statements::tql::Tql;
92use sqlparser::ast::{AnalyzeFormat, ObjectName};
93pub use standalone::StandaloneDatanodeManager;
94use table::requests::{OTLP_METRIC_COMPAT_KEY, OTLP_METRIC_COMPAT_PROM};
95use tracing::Span;
96
97use crate::error::{
98 self, Error, ExecLogicalPlanSnafu, ExecutePromqlSnafu, ExternalSnafu, InvalidSqlSnafu,
99 ParseSqlSnafu, PermissionSnafu, PlanStatementSnafu, Result, SqlExecInterceptedSnafu,
100 StatementTimeoutSnafu, TableOperationSnafu,
101};
102use crate::service_config::InfluxdbMergeMode;
103use crate::stream_wrapper::CancellableStreamWrapper;
104
105lazy_static! {
106 static ref OTLP_LEGACY_DEFAULT_VALUE: String = "legacy".to_string();
107}
108
109#[derive(Clone)]
113pub struct Instance {
114 frontend_peer_addr: String,
115 catalog_manager: CatalogManagerRef,
116 pipeline_operator: Arc<PipelineOperator>,
117 statement_executor: Arc<StatementExecutor>,
118 query_engine: QueryEngineRef,
119 plugins: Plugins,
120 inserter: InserterRef,
121 deleter: DeleterRef,
122 table_metadata_manager: TableMetadataManagerRef,
123 event_recorder: Option<EventRecorderRef>,
124 process_manager: ProcessManagerRef,
125 slow_query_options: SlowQueryOptions,
126 influxdb_default_merge_mode: InfluxdbMergeMode,
127 trace_ingest_chunk_size: usize,
128 suspend: Arc<AtomicBool>,
129
130 otlp_metrics_table_legacy_cache: DashMap<String, DashMap<String, bool>>,
135}
136
137impl Instance {
138 pub fn frontend_peer_addr(&self) -> &str {
139 &self.frontend_peer_addr
140 }
141
142 pub fn catalog_manager(&self) -> &CatalogManagerRef {
143 &self.catalog_manager
144 }
145
146 pub fn query_engine(&self) -> &QueryEngineRef {
147 &self.query_engine
148 }
149
150 pub fn plugins(&self) -> &Plugins {
151 &self.plugins
152 }
153
154 pub fn statement_executor(&self) -> &StatementExecutorRef {
155 &self.statement_executor
156 }
157
158 pub fn table_metadata_manager(&self) -> &TableMetadataManagerRef {
159 &self.table_metadata_manager
160 }
161
162 pub fn inserter(&self) -> &InserterRef {
163 &self.inserter
164 }
165
166 pub fn process_manager(&self) -> &ProcessManagerRef {
167 &self.process_manager
168 }
169
170 pub fn node_manager(&self) -> &NodeManagerRef {
171 self.inserter.node_manager()
172 }
173
174 pub fn partition_manager(&self) -> &PartitionRuleManagerRef {
175 self.inserter.partition_manager()
176 }
177
178 pub fn cache_invalidator(&self) -> &CacheInvalidatorRef {
179 self.statement_executor.cache_invalidator()
180 }
181
182 pub fn procedure_executor(&self) -> &ProcedureExecutorRef {
183 self.statement_executor.procedure_executor()
184 }
185
186 pub fn suspend_state(&self) -> Arc<AtomicBool> {
187 self.suspend.clone()
188 }
189
190 pub(crate) fn is_suspended(&self) -> bool {
191 self.suspend.load(atomic::Ordering::Relaxed)
192 }
193}
194
195fn parse_stmt(sql: &str, dialect: &(dyn Dialect + Send + Sync)) -> Result<Vec<Statement>> {
196 ParserContext::create_with_dialect(sql, dialect, ParseOptions::default()).context(ParseSqlSnafu)
197}
198
199fn validate_analyze_stream_statement(stmt: &mut Statement) -> Result<()> {
200 let Statement::Explain(explain) = stmt else {
201 return InvalidSqlSnafu {
202 err_msg: "only EXPLAIN ANALYZE VERBOSE statement is supported",
203 }
204 .fail();
205 };
206 ensure!(
207 explain.analyze && explain.verbose,
208 InvalidSqlSnafu {
209 err_msg: "statement must be EXPLAIN ANALYZE VERBOSE"
210 }
211 );
212 match explain.format {
213 None | Some(AnalyzeFormat::JSON) => {
214 explain.format = None;
218 Ok(())
219 }
220 Some(_) => InvalidSqlSnafu {
221 err_msg: "only FORMAT JSON is supported for analyze stream",
222 }
223 .fail(),
224 }
225}
226
227impl Instance {
228 fn statement_slow_query_timer(
229 &self,
230 stmt: &Statement,
231 schema_name: String,
232 ) -> Option<SlowQueryTimer> {
233 if !stmt.is_readonly() || !self.slow_query_options.enable {
234 return None;
235 }
236
237 self.event_recorder.clone().map(|event_recorder| {
238 SlowQueryTimer::new(
239 CatalogQueryStatement::Sql(stmt.clone()),
240 schema_name,
241 self.slow_query_options.threshold,
242 self.slow_query_options.sample_ratio,
243 self.slow_query_options.record_type,
244 event_recorder,
245 )
246 })
247 }
248
249 async fn query_statement(&self, stmt: Statement, query_ctx: QueryContextRef) -> Result<Output> {
250 check_permission(self.plugins.clone(), &stmt, &query_ctx)?;
251
252 let query_interceptor = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
253 let query_interceptor = query_interceptor.as_ref();
254
255 if should_track_statement_process(&stmt) {
256 let catalog_name = query_ctx.current_catalog().to_string();
257 let schema_name = query_ctx.current_schema();
258 let slow_query_timer = self.statement_slow_query_timer(&stmt, schema_name.clone());
259
260 let ticket = self.process_manager.register_query(
261 catalog_name,
262 vec![schema_name],
263 stmt.to_string(),
264 query_ctx.conn_info().to_string(),
265 Some(query_ctx.process_id()),
266 slow_query_timer,
267 );
268
269 let query_fut = self.exec_statement_with_timeout(stmt, query_ctx, query_interceptor);
270
271 CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
272 .await
273 .map_err(|_| error::CancelledSnafu.build())?
274 .map(|output| {
275 let Output { meta, data } = output;
276
277 let data = match data {
278 OutputData::Stream(stream) => OutputData::Stream(Box::pin(
279 CancellableStreamWrapper::new(stream, ticket),
280 )),
281 other => other,
282 };
283 Output { data, meta }
284 })
285 } else {
286 self.exec_statement_with_timeout(stmt, query_ctx, query_interceptor)
287 .await
288 }
289 }
290
291 async fn exec_statement_with_timeout(
292 &self,
293 stmt: Statement,
294 query_ctx: QueryContextRef,
295 query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
296 ) -> Result<Output> {
297 let timeout = derive_timeout(&stmt, &query_ctx);
298 match timeout {
299 Some(timeout) => {
300 let start = tokio::time::Instant::now();
301 let output = tokio::time::timeout(
302 timeout,
303 self.exec_statement(stmt, query_ctx, query_interceptor),
304 )
305 .await
306 .map_err(|_| StatementTimeoutSnafu.build())??;
307 let remaining_timeout = timeout.checked_sub(start.elapsed()).unwrap_or_default();
309 attach_timeout(output, remaining_timeout)
310 }
311 None => {
312 self.exec_statement(stmt, query_ctx, query_interceptor)
313 .await
314 }
315 }
316 }
317
318 async fn exec_statement(
319 &self,
320 stmt: Statement,
321 query_ctx: QueryContextRef,
322 query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
323 ) -> Result<Output> {
324 match stmt {
325 Statement::Query(_) | Statement::Explain(_) | Statement::Delete(_) => {
326 if let Statement::Explain(explain) = &stmt
328 && let Some(format) = explain.format()
329 {
330 query_ctx.set_explain_format(format.to_string());
331 }
332
333 self.plan_and_exec_sql(stmt, &query_ctx, query_interceptor)
334 .await
335 }
336 Statement::Tql(tql) => {
337 self.plan_and_exec_tql(&query_ctx, query_interceptor, tql)
338 .await
339 }
340 _ => {
341 query_interceptor.pre_execute(Some(&stmt), None, query_ctx.clone())?;
342 self.statement_executor
343 .execute_sql(stmt, query_ctx)
344 .await
345 .context(TableOperationSnafu)
346 }
347 }
348 }
349
350 async fn plan_and_exec_sql(
351 &self,
352 stmt: Statement,
353 query_ctx: &QueryContextRef,
354 query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
355 ) -> Result<Output> {
356 let stmt = QueryStatement::Sql(stmt);
357 let plan = self
358 .statement_executor
359 .plan(&stmt, query_ctx.clone())
360 .await?;
361 let QueryStatement::Sql(stmt) = stmt else {
362 unreachable!()
363 };
364 query_interceptor.pre_execute(Some(&stmt), Some(&plan), query_ctx.clone())?;
365
366 self.statement_executor
367 .exec_plan(plan, query_ctx.clone())
368 .await
369 .context(TableOperationSnafu)
370 }
371
372 async fn plan_and_exec_tql(
373 &self,
374 query_ctx: &QueryContextRef,
375 query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
376 tql: Tql,
377 ) -> Result<Output> {
378 let plan = self
379 .statement_executor
380 .plan_tql(tql.clone(), query_ctx)
381 .await?;
382 query_interceptor.pre_execute(
383 Some(&Statement::Tql(tql)),
384 Some(&plan),
385 query_ctx.clone(),
386 )?;
387 self.statement_executor
388 .exec_plan(plan, query_ctx.clone())
389 .await
390 .context(TableOperationSnafu)
391 }
392
393 async fn check_otlp_legacy(
394 &self,
395 names: &[&String],
396 ctx: QueryContextRef,
397 ) -> server_error::Result<bool> {
398 let db_string = ctx.get_db_string();
399 let cache = self
401 .otlp_metrics_table_legacy_cache
402 .entry(db_string.clone())
403 .or_default();
404 if let Some(flag) = fast_legacy_check(&cache, names)? {
405 return Ok(flag);
406 }
407 drop(cache);
409
410 let catalog = ctx.current_catalog();
411 let schema = ctx.current_schema();
412
413 let normalized_names = names
415 .iter()
416 .map(|n| legacy_normalize_otlp_name(n))
417 .collect::<Vec<_>>();
418 let table_names = normalized_names
419 .iter()
420 .map(|n| TableNameKey::new(catalog, &schema, n))
421 .collect::<Vec<_>>();
422 let table_values = self
423 .table_metadata_manager()
424 .table_name_manager()
425 .batch_get(table_names)
426 .await
427 .context(CommonMetaSnafu)?;
428 let table_ids = table_values
429 .into_iter()
430 .filter_map(|v| v.map(|vi| vi.table_id()))
431 .collect::<Vec<_>>();
432
433 if table_ids.is_empty() {
435 let cache = self
436 .otlp_metrics_table_legacy_cache
437 .entry(db_string)
438 .or_default();
439 names.iter().for_each(|name| {
440 cache.insert((*name).clone(), false);
441 });
442 return Ok(false);
443 }
444
445 let table_infos = self
447 .table_metadata_manager()
448 .table_info_manager()
449 .batch_get(&table_ids)
450 .await
451 .context(CommonMetaSnafu)?;
452 let options = table_infos
453 .values()
454 .map(|info| {
455 info.table_info
456 .meta
457 .options
458 .extra_options
459 .get(OTLP_METRIC_COMPAT_KEY)
460 .unwrap_or(&OTLP_LEGACY_DEFAULT_VALUE)
461 })
462 .collect::<Vec<_>>();
463 let cache = self
464 .otlp_metrics_table_legacy_cache
465 .entry(db_string)
466 .or_default();
467 if !options.is_empty() {
468 let has_prom = options.iter().any(|opt| *opt == OTLP_METRIC_COMPAT_PROM);
470 let has_legacy = options
471 .iter()
472 .any(|opt| *opt == OTLP_LEGACY_DEFAULT_VALUE.as_str());
473 ensure!(!(has_prom && has_legacy), OtlpMetricModeIncompatibleSnafu);
474 let flag = has_legacy;
475 names.iter().for_each(|name| {
476 cache.insert((*name).clone(), flag);
477 });
478 Ok(flag)
479 } else {
480 names.iter().for_each(|name| {
482 cache.insert((*name).clone(), false);
483 });
484 Ok(false)
485 }
486 }
487}
488
489fn fast_legacy_check(
490 cache: &DashMap<String, bool>,
491 names: &[&String],
492) -> server_error::Result<Option<bool>> {
493 let hit_cache = names
494 .iter()
495 .filter_map(|name| cache.get(*name))
496 .collect::<Vec<_>>();
497 if !hit_cache.is_empty() {
498 let hit_legacy = hit_cache.iter().any(|en| *en.value());
499 let hit_prom = hit_cache.iter().any(|en| !*en.value());
500
501 ensure!(!(hit_legacy && hit_prom), OtlpMetricModeIncompatibleSnafu);
505
506 let flag = hit_legacy;
507 drop(hit_cache);
509
510 names.iter().for_each(|name| {
512 if !cache.contains_key(*name) {
513 cache.insert((*name).clone(), flag);
514 }
515 });
516 Ok(Some(flag))
517 } else {
518 Ok(None)
519 }
520}
521
522fn derive_timeout(stmt: &Statement, query_ctx: &QueryContextRef) -> Option<Duration> {
525 let query_timeout = query_ctx.query_timeout()?;
526 if query_timeout.is_zero() {
527 return None;
528 }
529 match query_ctx.channel() {
530 Channel::Mysql if stmt.is_readonly() => Some(query_timeout),
531 Channel::Postgres => Some(query_timeout),
532 _ => None,
533 }
534}
535
536fn derive_timeout_for_plan(plan: &LogicalPlan, query_ctx: &QueryContextRef) -> Option<Duration> {
538 let query_timeout = query_ctx.query_timeout()?;
539 if query_timeout.is_zero() {
540 return None;
541 }
542 match query_ctx.channel() {
543 Channel::Mysql if is_readonly_plan(plan) => Some(query_timeout),
544 Channel::Postgres => Some(query_timeout),
545 _ => None,
546 }
547}
548
549fn attach_timeout(output: Output, mut timeout: Duration) -> Result<Output> {
550 if timeout.is_zero() {
551 return StatementTimeoutSnafu.fail();
552 }
553
554 let output = match output.data {
555 OutputData::AffectedRows(_) | OutputData::RecordBatches(_) => output,
556 OutputData::Stream(mut stream) => {
557 let schema = stream.schema();
558 let s = Box::pin(stream! {
559 let mut start = tokio::time::Instant::now();
560 while let Some(item) = tokio::time::timeout(timeout, stream.next()).await.map_err(|_| StreamTimeoutSnafu.build())? {
561 yield item;
562
563 let now = tokio::time::Instant::now();
564 timeout = timeout.checked_sub(now - start).unwrap_or(Duration::ZERO);
565 start = now;
566 if timeout.is_zero() {
568 StreamTimeoutSnafu.fail()?;
569 }
570 }
571 }) as Pin<Box<dyn Stream<Item = _> + Send>>;
572 let stream = RecordBatchStreamWrapper {
573 schema,
574 stream: s,
575 output_ordering: None,
576 metrics: Default::default(),
577 span: Span::current(),
578 };
579 Output::new(OutputData::Stream(Box::pin(stream)), output.meta)
580 }
581 };
582
583 Ok(output)
584}
585
586impl Instance {
587 #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_analyze_stream_query")]
588 async fn do_analyze_stream_query_inner(
589 &self,
590 query: &str,
591 query_ctx: QueryContextRef,
592 ) -> Result<Output> {
593 ensure!(!self.is_suspended(), error::SuspendedSnafu);
594
595 let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
596 let query_interceptor = query_interceptor_opt.as_ref();
597 let query = query_interceptor.pre_parsing(query, query_ctx.clone())?;
598 let mut stmts = parse_stmt(query.as_ref(), query_ctx.sql_dialect())
599 .and_then(|stmts| query_interceptor.post_parsing(stmts, query_ctx.clone()))?;
600
601 ensure!(
602 stmts.len() == 1,
603 InvalidSqlSnafu {
604 err_msg: "only single EXPLAIN ANALYZE VERBOSE statement is supported"
605 }
606 );
607 let mut stmt = stmts.remove(0);
608 validate_analyze_stream_statement(&mut stmt)?;
609 query_ctx.set_explain_format(AnalyzeFormat::JSON.to_string());
610
611 let checker_ref = self.plugins.get::<PermissionCheckerRef>();
612 checker_ref
613 .as_ref()
614 .check_permission(query_ctx.current_user(), PermissionReq::SqlStatement(&stmt))
615 .context(PermissionSnafu)?;
616 check_permission(self.plugins.clone(), &stmt, &query_ctx)?;
617 let catalog_name = query_ctx.current_catalog().to_string();
618 let schema_name = query_ctx.current_schema();
619 let slow_query_timer = self.statement_slow_query_timer(&stmt, schema_name.clone());
620 let ticket = self.process_manager.register_query(
621 catalog_name,
622 vec![schema_name],
623 stmt.to_string(),
624 query_ctx.conn_info().to_string(),
625 Some(query_ctx.process_id()),
626 slow_query_timer,
627 );
628 let query_fut =
629 self.exec_statement_with_timeout(stmt, query_ctx.clone(), query_interceptor);
630 let output = CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
631 .await
632 .map_err(|_| error::CancelledSnafu.build())??;
633 let Output { meta, data } = output;
634 let data = match data {
635 OutputData::Stream(stream) => OutputData::Stream(Box::pin(
636 CancellableStreamWrapper::new_cancel_on_drop(stream, ticket),
637 )),
638 other => other,
639 };
640 query_interceptor.post_execute(Output { data, meta }, query_ctx)
641 }
642
643 #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_query")]
644 async fn do_query_inner(&self, query: &str, query_ctx: QueryContextRef) -> Vec<Result<Output>> {
645 if self.is_suspended() {
646 return vec![error::SuspendedSnafu {}.fail()];
647 }
648
649 let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
650 let query_interceptor = query_interceptor_opt.as_ref();
651 let query = match query_interceptor.pre_parsing(query, query_ctx.clone()) {
652 Ok(q) => q,
653 Err(e) => return vec![Err(e)],
654 };
655
656 let checker_ref = self.plugins.get::<PermissionCheckerRef>();
657 let checker = checker_ref.as_ref();
658
659 match parse_stmt(query.as_ref(), query_ctx.sql_dialect())
660 .and_then(|stmts| query_interceptor.post_parsing(stmts, query_ctx.clone()))
661 {
662 Ok(stmts) => {
663 if stmts.is_empty() {
664 return vec![
665 InvalidSqlSnafu {
666 err_msg: "empty statements",
667 }
668 .fail(),
669 ];
670 }
671
672 let mut results = Vec::with_capacity(stmts.len());
673 for stmt in stmts {
674 if let Err(e) = checker
675 .check_permission(
676 query_ctx.current_user(),
677 PermissionReq::SqlStatement(&stmt),
678 )
679 .context(PermissionSnafu)
680 {
681 results.push(Err(e));
682 break;
683 }
684
685 match self.query_statement(stmt.clone(), query_ctx.clone()).await {
686 Ok(output) => {
687 let output_result =
688 query_interceptor.post_execute(output, query_ctx.clone());
689 results.push(output_result);
690 }
691 Err(e) => {
692 if e.status_code().should_log_error() {
693 error!(e; "Failed to execute query: {stmt}");
694 } else {
695 debug!("Failed to execute query: {stmt}, {e}");
696 }
697 results.push(Err(e));
698 break;
699 }
700 }
701 }
702 results
703 }
704 Err(e) => {
705 vec![Err(e)]
706 }
707 }
708 }
709
710 async fn exec_plan(&self, plan: LogicalPlan, query_ctx: QueryContextRef) -> Result<Output> {
711 self.query_engine
712 .execute(plan, query_ctx)
713 .await
714 .context(ExecLogicalPlanSnafu)
715 }
716
717 async fn exec_plan_with_timeout(
718 &self,
719 plan: LogicalPlan,
720 query_ctx: QueryContextRef,
721 ) -> Result<Output> {
722 let timeout = derive_timeout_for_plan(&plan, &query_ctx);
723 match timeout {
724 Some(timeout) => {
725 let start = tokio::time::Instant::now();
726 let output = tokio::time::timeout(timeout, self.exec_plan(plan, query_ctx))
727 .await
728 .map_err(|_| StatementTimeoutSnafu.build())??;
729 let remaining_timeout = timeout.checked_sub(start.elapsed()).unwrap_or_default();
730 attach_timeout(output, remaining_timeout)
731 }
732 None => self.exec_plan(plan, query_ctx).await,
733 }
734 }
735
736 async fn do_exec_plan_inner(
737 &self,
738 plan: LogicalPlan,
739 stmt: Option<Statement>,
740 query_ctx: QueryContextRef,
741 ) -> Result<Output> {
742 ensure!(!self.is_suspended(), error::SuspendedSnafu);
743
744 let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
745 let query_interceptor = query_interceptor_opt.as_ref();
746
747 query_interceptor.pre_execute(stmt.as_ref(), Some(&plan), query_ctx.clone())?;
748
749 let query = stmt
750 .as_ref()
751 .map(|s| s.to_string())
752 .unwrap_or_else(|| plan.display_indent().to_string());
753
754 let plan_is_readonly = is_readonly_plan(&plan);
755 let result = if should_track_plan_process(stmt.as_ref(), &plan) {
756 let catalog_name = query_ctx.current_catalog().to_string();
757 let schema_name = query_ctx.current_schema();
758 let slow_query_timer = if plan_is_readonly {
759 self.slow_query_options
760 .enable
761 .then(|| self.event_recorder.clone())
762 .flatten()
763 .map(|event_recorder| {
764 SlowQueryTimer::new(
765 CatalogQueryStatement::Plan(query.clone()),
766 schema_name.clone(),
767 self.slow_query_options.threshold,
768 self.slow_query_options.sample_ratio,
769 self.slow_query_options.record_type,
770 event_recorder,
771 )
772 })
773 } else {
774 None
775 };
776
777 let ticket = self.process_manager.register_query(
778 catalog_name,
779 vec![schema_name],
780 query,
781 query_ctx.conn_info().to_string(),
782 Some(query_ctx.process_id()),
783 slow_query_timer,
784 );
785
786 let query_fut = self.exec_plan_with_timeout(plan, query_ctx.clone());
787
788 CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
789 .await
790 .map_err(|_| error::CancelledSnafu.build())?
791 .map(|output| {
792 let Output { meta, data } = output;
793
794 let data = match data {
795 OutputData::Stream(stream) => OutputData::Stream(Box::pin(
796 CancellableStreamWrapper::new(stream, ticket),
797 )),
798 other => other,
799 };
800 Output { data, meta }
801 })
802 } else {
803 self.exec_plan_with_timeout(plan, query_ctx.clone()).await
804 };
805
806 result.and_then(|output| query_interceptor.post_execute(output, query_ctx))
807 }
808
809 #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_promql_query")]
810 async fn do_promql_query_inner(
811 &self,
812 query: &PromQuery,
813 query_ctx: QueryContextRef,
814 ) -> Vec<Result<Output>> {
815 if self.is_suspended() {
816 return vec![error::SuspendedSnafu {}.fail()];
817 }
818
819 let result = PrometheusHandler::do_query(self, query, query_ctx)
821 .await
822 .with_context(|_| ExecutePromqlSnafu {
823 query: format!("{query:?}"),
824 });
825 vec![result]
826 }
827
828 async fn do_describe_inner(
829 &self,
830 stmt: Statement,
831 query_ctx: QueryContextRef,
832 ) -> Result<Option<DescribeResult>> {
833 ensure!(!self.is_suspended(), error::SuspendedSnafu);
834
835 let is_inner_plannable = |s: &Statement| {
839 matches!(
840 s,
841 Statement::Insert(_) | Statement::Query(_) | Statement::Delete(_)
842 )
843 };
844 let plannable = is_inner_plannable(&stmt)
845 || matches!(&stmt, Statement::Explain(explain) if is_inner_plannable(explain.statement.as_ref()));
846
847 if plannable {
848 self.plugins
849 .get::<PermissionCheckerRef>()
850 .as_ref()
851 .check_permission(query_ctx.current_user(), PermissionReq::SqlStatement(&stmt))
852 .context(PermissionSnafu)?;
853
854 let plan = self
855 .query_engine
856 .planner()
857 .plan(&QueryStatement::Sql(stmt), query_ctx.clone())
858 .await
859 .context(PlanStatementSnafu)?;
860 self.query_engine
861 .describe(plan, query_ctx)
862 .await
863 .map(Some)
864 .context(error::DescribeStatementSnafu)
865 } else {
866 Ok(None)
867 }
868 }
869
870 async fn is_valid_schema_inner(&self, catalog: &str, schema: &str) -> Result<bool> {
871 self.catalog_manager
872 .schema_exists(catalog, schema, None)
873 .await
874 .context(error::CatalogSnafu)
875 }
876}
877
878#[async_trait]
879impl SqlQueryHandler for Instance {
880 async fn do_query(
881 &self,
882 query: &str,
883 query_ctx: QueryContextRef,
884 ) -> Vec<server_error::Result<Output>> {
885 self.do_query_inner(query, query_ctx)
886 .await
887 .into_iter()
888 .map(|result| result.map_err(BoxedError::new).context(ExecuteQuerySnafu))
889 .collect()
890 }
891
892 async fn do_analyze_stream_query(
893 &self,
894 query: &str,
895 query_ctx: QueryContextRef,
896 ) -> server_error::Result<Output> {
897 self.do_analyze_stream_query_inner(query, query_ctx)
898 .await
899 .map_err(BoxedError::new)
900 .context(ExecuteQuerySnafu)
901 }
902
903 async fn do_exec_plan(
904 &self,
905 plan: LogicalPlan,
906 stmt: Option<Statement>,
907 query_ctx: QueryContextRef,
908 ) -> server_error::Result<Output> {
909 self.do_exec_plan_inner(plan, stmt, query_ctx)
910 .await
911 .map_err(BoxedError::new)
912 .context(server_error::ExecutePlanSnafu)
913 }
914
915 async fn do_promql_query(
916 &self,
917 query: &PromQuery,
918 query_ctx: QueryContextRef,
919 ) -> Vec<server_error::Result<Output>> {
920 self.do_promql_query_inner(query, query_ctx)
921 .await
922 .into_iter()
923 .map(|result| result.map_err(BoxedError::new).context(ExecuteQuerySnafu))
924 .collect()
925 }
926
927 async fn do_describe(
928 &self,
929 stmt: Statement,
930 query_ctx: QueryContextRef,
931 ) -> server_error::Result<Option<DescribeResult>> {
932 self.do_describe_inner(stmt, query_ctx)
933 .await
934 .map_err(BoxedError::new)
935 .context(server_error::DescribeStatementSnafu)
936 }
937
938 async fn is_valid_schema(&self, catalog: &str, schema: &str) -> server_error::Result<bool> {
939 self.is_valid_schema_inner(catalog, schema)
940 .await
941 .map_err(BoxedError::new)
942 .context(server_error::CheckDatabaseValiditySnafu)
943 }
944}
945
946pub fn attach_timer(output: Output, timer: HistogramTimer) -> Output {
948 match output.data {
949 OutputData::AffectedRows(_) | OutputData::RecordBatches(_) => output,
950 OutputData::Stream(stream) => {
951 let stream = OnDone::new(stream, move || {
952 timer.observe_duration();
953 });
954 Output::new(OutputData::Stream(Box::pin(stream)), output.meta)
955 }
956 }
957}
958
959#[async_trait]
960impl PrometheusHandler for Instance {
961 #[tracing::instrument(skip_all)]
962 async fn do_query(
963 &self,
964 query: &PromQuery,
965 query_ctx: QueryContextRef,
966 ) -> server_error::Result<Output> {
967 let interceptor = self
968 .plugins
969 .get::<PromQueryInterceptorRef<server_error::Error>>();
970
971 self.plugins
972 .get::<PermissionCheckerRef>()
973 .as_ref()
974 .check_permission(query_ctx.current_user(), PermissionReq::PromQuery)
975 .context(AuthSnafu)?;
976
977 let stmt = QueryLanguageParser::parse_promql(query, &query_ctx).with_context(|_| {
978 ParsePromQLSnafu {
979 query: query.clone(),
980 }
981 })?;
982
983 let plan = self
984 .statement_executor
985 .plan(&stmt, query_ctx.clone())
986 .await
987 .map_err(BoxedError::new)
988 .context(ExecuteQuerySnafu)?;
989
990 let QueryStatement::Promql(eval_stmt, _) = &stmt else {
991 unreachable!("query is parsed from promql");
992 };
993
994 interceptor.pre_execute(query, &eval_stmt.expr, Some(&plan), query_ctx.clone())?;
995
996 let query_statement = if let QueryStatement::Promql(eval_stmt, alias) = stmt {
998 CatalogQueryStatement::Promql(eval_stmt, alias)
999 } else {
1000 return UnexpectedResultSnafu {
1002 reason: "The query should always be promql.".to_string(),
1003 }
1004 .fail();
1005 };
1006 let raw_query = query_statement.to_string();
1007
1008 let slow_query_timer = self
1009 .slow_query_options
1010 .enable
1011 .then(|| self.event_recorder.clone())
1012 .flatten()
1013 .map(|event_recorder| {
1014 SlowQueryTimer::new(
1015 query_statement,
1016 query_ctx.current_schema(),
1017 self.slow_query_options.threshold,
1018 self.slow_query_options.sample_ratio,
1019 self.slow_query_options.record_type,
1020 event_recorder,
1021 )
1022 });
1023
1024 let ticket = self.process_manager.register_query(
1025 query_ctx.current_catalog().to_string(),
1026 vec![query_ctx.current_schema()],
1027 raw_query,
1028 query_ctx.conn_info().to_string(),
1029 Some(query_ctx.process_id()),
1030 slow_query_timer,
1031 );
1032
1033 let query_fut = self.statement_executor.exec_plan(plan, query_ctx.clone());
1034
1035 let output = CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
1036 .await
1037 .map_err(|_| servers::error::CancelledSnafu.build())?
1038 .map(|output| {
1039 let Output { meta, data } = output;
1040 let data = match data {
1041 OutputData::Stream(stream) => {
1042 OutputData::Stream(Box::pin(CancellableStreamWrapper::new(stream, ticket)))
1043 }
1044 other => other,
1045 };
1046 Output { data, meta }
1047 })
1048 .map_err(BoxedError::new)
1049 .context(ExecuteQuerySnafu)?;
1050
1051 Ok(interceptor.post_execute(output, query_ctx)?)
1052 }
1053
1054 async fn query_metric_names(
1055 &self,
1056 matchers: Vec<Matcher>,
1057 ctx: &QueryContextRef,
1058 ) -> server_error::Result<Vec<String>> {
1059 self.handle_query_metric_names(matchers, ctx)
1060 .await
1061 .map_err(BoxedError::new)
1062 .context(ExecuteQuerySnafu)
1063 }
1064
1065 async fn query_label_values(
1066 &self,
1067 metric: String,
1068 label_name: String,
1069 matchers: Vec<Matcher>,
1070 start: SystemTime,
1071 end: SystemTime,
1072 ctx: &QueryContextRef,
1073 ) -> server_error::Result<Vec<String>> {
1074 self.handle_query_label_values(metric, label_name, matchers, start, end, ctx)
1075 .await
1076 .map_err(BoxedError::new)
1077 .context(ExecuteQuerySnafu)
1078 }
1079
1080 fn catalog_manager(&self) -> CatalogManagerRef {
1081 self.catalog_manager.clone()
1082 }
1083}
1084
1085macro_rules! validate_db_permission {
1087 ($stmt: expr, $query_ctx: expr) => {
1088 if let Some(database) = &$stmt.database {
1089 validate_catalog_and_schema($query_ctx.current_catalog(), database, $query_ctx)
1090 .map_err(BoxedError::new)
1091 .context(SqlExecInterceptedSnafu)?;
1092 }
1093 };
1094}
1095
1096pub fn check_permission(
1097 plugins: Plugins,
1098 stmt: &Statement,
1099 query_ctx: &QueryContextRef,
1100) -> Result<()> {
1101 let need_validate = plugins
1102 .get::<QueryOptions>()
1103 .map(|opts| opts.disallow_cross_catalog_query)
1104 .unwrap_or_default();
1105
1106 if !need_validate {
1107 return Ok(());
1108 }
1109
1110 match stmt {
1111 Statement::Admin(_) => {}
1114 Statement::Query(_)
1116 | Statement::Explain(_)
1117 | Statement::Tql(_)
1118 | Statement::Delete(_)
1119 | Statement::DeclareCursor(_)
1120 | Statement::Copy(sql::statements::copy::Copy::CopyQueryTo(_)) => {}
1121 Statement::CreateDatabase(_)
1123 | Statement::ShowDatabases(_)
1124 | Statement::DropDatabase(_)
1125 | Statement::AlterDatabase(_)
1126 | Statement::DropFlow(_)
1127 | Statement::Use(_) => {}
1128 #[cfg(feature = "enterprise")]
1129 Statement::DropTrigger(_) => {}
1130 Statement::ShowCreateDatabase(stmt) => {
1131 validate_database(&stmt.database_name, query_ctx)?;
1132 }
1133 Statement::ShowCreateTable(stmt) => {
1134 validate_param(&stmt.table_name, query_ctx)?;
1135 }
1136 Statement::ShowCreateFlow(stmt) => {
1137 validate_flow(&stmt.flow_name, query_ctx)?;
1138 }
1139 #[cfg(feature = "enterprise")]
1140 Statement::ShowCreateTrigger(stmt) => {
1141 validate_param(&stmt.trigger_name, query_ctx)?;
1142 }
1143 Statement::ShowCreateView(stmt) => {
1144 validate_param(&stmt.view_name, query_ctx)?;
1145 }
1146 Statement::CreateExternalTable(stmt) => {
1147 validate_param(&stmt.name, query_ctx)?;
1148 }
1149 Statement::CreateFlow(stmt) => {
1150 validate_param(&stmt.sink_table_name, query_ctx)?;
1152 }
1153 #[cfg(feature = "enterprise")]
1154 Statement::CreateTrigger(stmt) => {
1155 validate_param(&stmt.trigger_name, query_ctx)?;
1156 }
1157 Statement::CreateView(stmt) => {
1158 validate_param(&stmt.name, query_ctx)?;
1159 }
1160 Statement::AlterTable(stmt) => {
1161 validate_param(stmt.table_name(), query_ctx)?;
1162 }
1163 #[cfg(feature = "enterprise")]
1164 Statement::AlterTrigger(_) => {}
1165 Statement::SetVariables(_) | Statement::ShowVariables(_) => {}
1167 Statement::ShowCharset(_) | Statement::ShowCollation(_) => {}
1169
1170 Statement::Comment(comment) => match &comment.object {
1171 CommentObject::Table(table) => validate_param(table, query_ctx)?,
1172 CommentObject::Column { table, .. } => validate_param(table, query_ctx)?,
1173 CommentObject::Flow(flow) => validate_flow(flow, query_ctx)?,
1174 },
1175
1176 Statement::Insert(insert) => {
1177 let name = insert.table_name().context(ParseSqlSnafu)?;
1178 validate_param(name, query_ctx)?;
1179 }
1180 Statement::CreateTable(stmt) => {
1181 validate_param(&stmt.name, query_ctx)?;
1182 }
1183 Statement::CreateTableLike(stmt) => {
1184 validate_param(&stmt.table_name, query_ctx)?;
1185 validate_param(&stmt.source_name, query_ctx)?;
1186 }
1187 Statement::DropTable(drop_stmt) => {
1188 for table_name in drop_stmt.table_names() {
1189 validate_param(table_name, query_ctx)?;
1190 }
1191 }
1192 Statement::DropView(stmt) => {
1193 validate_param(&stmt.view_name, query_ctx)?;
1194 }
1195 Statement::ShowTables(stmt) => {
1196 validate_db_permission!(stmt, query_ctx);
1197 }
1198 Statement::ShowTableStatus(stmt) => {
1199 validate_db_permission!(stmt, query_ctx);
1200 }
1201 Statement::ShowColumns(stmt) => {
1202 validate_db_permission!(stmt, query_ctx);
1203 }
1204 Statement::ShowIndex(stmt) => {
1205 validate_db_permission!(stmt, query_ctx);
1206 }
1207 Statement::ShowRegion(stmt) => {
1208 validate_db_permission!(stmt, query_ctx);
1209 }
1210 Statement::ShowViews(stmt) => {
1211 validate_db_permission!(stmt, query_ctx);
1212 }
1213 Statement::ShowFlows(stmt) => {
1214 validate_db_permission!(stmt, query_ctx);
1215 }
1216 #[cfg(feature = "enterprise")]
1217 Statement::ShowTriggers(_stmt) => {
1218 }
1221 Statement::ShowStatus(_stmt) => {}
1222 Statement::ShowSearchPath(_stmt) => {}
1223 Statement::DescribeTable(stmt) => {
1224 validate_param(stmt.name(), query_ctx)?;
1225 }
1226 Statement::Copy(sql::statements::copy::Copy::CopyTable(stmt)) => match stmt {
1227 CopyTable::To(copy_table_to) => validate_param(©_table_to.table_name, query_ctx)?,
1228 CopyTable::From(copy_table_from) => {
1229 validate_param(©_table_from.table_name, query_ctx)?
1230 }
1231 },
1232 Statement::Copy(sql::statements::copy::Copy::CopyDatabase(copy_database)) => {
1233 match copy_database {
1234 CopyDatabase::To(stmt) => validate_database(&stmt.database_name, query_ctx)?,
1235 CopyDatabase::From(stmt) => validate_database(&stmt.database_name, query_ctx)?,
1236 }
1237 }
1238 Statement::TruncateTable(stmt) => {
1239 validate_param(stmt.table_name(), query_ctx)?;
1240 }
1241 Statement::FetchCursor(_) | Statement::CloseCursor(_) => {}
1243 Statement::Kill(_) => {}
1245 Statement::ShowProcesslist(_) => {}
1247 }
1248 Ok(())
1249}
1250
1251fn validate_param(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1252 let (catalog, schema, _) = table_idents_to_full_name(name, query_ctx)
1253 .map_err(BoxedError::new)
1254 .context(ExternalSnafu)?;
1255
1256 validate_catalog_and_schema(&catalog, &schema, query_ctx)
1257 .map_err(BoxedError::new)
1258 .context(SqlExecInterceptedSnafu)
1259}
1260
1261fn validate_flow(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1262 let catalog = match &name.0[..] {
1263 [_flow] => query_ctx.current_catalog().to_string(),
1264 [catalog, _flow] => catalog.to_string_unquoted(),
1265 _ => {
1266 return InvalidSqlSnafu {
1267 err_msg: format!(
1268 "expect flow name to be <catalog>.<flow_name> or <flow_name>, actual: {name}",
1269 ),
1270 }
1271 .fail();
1272 }
1273 };
1274
1275 let schema = query_ctx.current_schema();
1276
1277 validate_catalog_and_schema(&catalog, &schema, query_ctx)
1278 .map_err(BoxedError::new)
1279 .context(SqlExecInterceptedSnafu)
1280}
1281
1282fn validate_database(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1283 let (catalog, schema) = match &name.0[..] {
1284 [schema] => (
1285 query_ctx.current_catalog().to_string(),
1286 schema.to_string_unquoted(),
1287 ),
1288 [catalog, schema] => (catalog.to_string_unquoted(), schema.to_string_unquoted()),
1289 _ => InvalidSqlSnafu {
1290 err_msg: format!(
1291 "expect database name to be <catalog>.<schema> or <schema>, actual: {name}",
1292 ),
1293 }
1294 .fail()?,
1295 };
1296
1297 validate_catalog_and_schema(&catalog, &schema, query_ctx)
1298 .map_err(BoxedError::new)
1299 .context(SqlExecInterceptedSnafu)
1300}
1301
1302fn is_readonly_plan(plan: &LogicalPlan) -> bool {
1303 !matches!(plan, LogicalPlan::Dml(_) | LogicalPlan::Ddl(_))
1304}
1305
1306fn should_track_statement_process(stmt: &Statement) -> bool {
1307 stmt.is_readonly()
1308 || matches!(stmt, Statement::Insert(insert) if insert.has_non_values_query_source())
1309}
1310
1311fn should_track_plan_process(stmt: Option<&Statement>, plan: &LogicalPlan) -> bool {
1312 is_readonly_plan(plan)
1313 || matches!(stmt, Some(Statement::Insert(insert)) if insert.has_non_values_query_source())
1314}
1315
1316#[cfg(test)]
1317mod tests {
1318 use std::collections::HashMap;
1319 use std::future::Future;
1320 use std::pin::Pin;
1321 use std::sync::atomic::{AtomicBool, Ordering};
1322 use std::sync::{Arc, Barrier};
1323 use std::task::{Context, Poll};
1324 use std::thread;
1325 use std::time::{Duration, Instant};
1326
1327 use api::v1::meta::{ProcedureDetailResponse, ReconcileRequest, ReconcileResponse};
1328 use catalog::process_manager::ProcessManager;
1329 use common_base::Plugins;
1330 use common_error::ext::{BoxedError, PlainError};
1331 use common_error::status_code::StatusCode;
1332 use common_meta::cache::LayeredCacheRegistryBuilder;
1333 use common_meta::kv_backend::memory::MemoryKvBackend;
1334 use common_meta::procedure_executor::{ExecutorContext, ProcedureExecutor};
1335 use common_meta::rpc::ddl::{SubmitDdlTaskRequest, SubmitDdlTaskResponse};
1336 use common_meta::rpc::procedure::{
1337 MigrateRegionRequest, MigrateRegionResponse, ProcedureStateResponse,
1338 };
1339 use common_query::Output;
1340 use common_recordbatch::{
1341 OrderOption, RecordBatch, RecordBatchStream, SendableRecordBatchStream,
1342 };
1343 use datafusion::arrow::datatypes::{DataType, Field, Schema, SchemaRef};
1344 use datafusion_expr::dml::InsertOp;
1345 use datafusion_expr::{LogicalPlanBuilder, LogicalTableSource};
1346 use datatypes::prelude::ConcreteDataType;
1347 use datatypes::schema::{ColumnSchema, Schema as GtSchema, SchemaRef as GtSchemaRef};
1348 use query::query_engine::options::QueryOptions;
1349 use session::context::{Channel, ConnInfo, QueryContext, QueryContextBuilder};
1350 use snafu::{Location, Snafu};
1351 use sql::dialect::GreptimeDbDialect;
1352 use store_api::data_source::DataSource;
1353 use store_api::storage::ScanRequest;
1354 use strfmt::Format;
1355 use table::metadata::{FilterPushDownType, TableInfo, TableInfoBuilder, TableMetaBuilder};
1356 use table::test_util::EmptyTable;
1357 use table::{Table, TableRef};
1358 use tokio::sync::{mpsc, oneshot};
1359
1360 use super::*;
1361 use crate::frontend::FrontendOptions;
1362 use crate::instance::builder::FrontendBuilder;
1363
1364 fn parse_test_sql(sql: &str) -> Vec<Statement> {
1365 parse_stmt(sql, &GreptimeDbDialect {}).unwrap()
1366 }
1367
1368 #[test]
1369 fn test_validate_analyze_stream_statement_strictness() {
1370 for sql in [
1371 "select 1",
1372 "explain analyze select 1",
1373 "explain analyze verbose format text select 1",
1374 "explain analyze verbose format graphviz select 1",
1375 ] {
1376 let mut stmts = parse_test_sql(sql);
1377 assert!(
1378 validate_analyze_stream_statement(&mut stmts[0]).is_err(),
1379 "{sql}"
1380 );
1381 }
1382
1383 for sql in [
1384 "explain analyze verbose select 1",
1385 "explain analyze verbose format json select 1",
1386 ] {
1387 let mut stmts = parse_test_sql(sql);
1388 assert!(
1389 validate_analyze_stream_statement(&mut stmts[0]).is_ok(),
1390 "{sql}"
1391 );
1392 let Statement::Explain(explain) = &stmts[0] else {
1393 unreachable!();
1394 };
1395 assert!(explain.format.is_none());
1396 }
1397
1398 assert_eq!(
1399 parse_test_sql("explain analyze verbose select 1; select 2").len(),
1400 2
1401 );
1402 }
1403
1404 #[derive(Debug, Snafu)]
1405 enum TestError {
1406 #[snafu(display("Failed to build test cache registry"))]
1407 BuildCacheRegistry {
1408 source: cache::error::Error,
1409 #[snafu(implicit)]
1410 location: Location,
1411 },
1412
1413 #[snafu(display("Failed to build test table meta for table: {table_name}"))]
1414 BuildTableMeta {
1415 table_name: String,
1416 source: table::metadata::TableMetaBuilderError,
1417 #[snafu(implicit)]
1418 location: Location,
1419 },
1420
1421 #[snafu(display("Failed to build test table info for table: {table_name}"))]
1422 BuildTableInfo {
1423 table_name: String,
1424 source: table::metadata::TableInfoBuilderError,
1425 #[snafu(implicit)]
1426 location: Location,
1427 },
1428
1429 #[snafu(display("Failed to register test table: {table_name}"))]
1430 RegisterTable {
1431 table_name: String,
1432 source: catalog::error::Error,
1433 #[snafu(implicit)]
1434 location: Location,
1435 },
1436
1437 #[snafu(display("Failed to build test frontend instance"))]
1438 BuildFrontend {
1439 source: crate::error::Error,
1440 #[snafu(implicit)]
1441 location: Location,
1442 },
1443
1444 #[snafu(display("Expected exactly one output for SQL `{sql}`, got {actual}"))]
1445 UnexpectedOutputCount {
1446 sql: String,
1447 actual: usize,
1448 #[snafu(implicit)]
1449 location: Location,
1450 },
1451
1452 #[snafu(display("Failed to execute SQL `{sql}`"))]
1453 ExecuteSql {
1454 sql: String,
1455 source: crate::error::Error,
1456 #[snafu(implicit)]
1457 location: Location,
1458 },
1459
1460 #[snafu(display("Timed out waiting for insert-select start notification"))]
1461 InsertStartTimeout {
1462 source: tokio::time::error::Elapsed,
1463 #[snafu(implicit)]
1464 location: Location,
1465 },
1466
1467 #[snafu(display("Insert-select start notification channel closed"))]
1468 InsertStartChannelClosed {
1469 #[snafu(implicit)]
1470 location: Location,
1471 },
1472
1473 #[snafu(display("Failed to release blocking insert-select interceptor"))]
1474 ReleaseBlockedInsert {
1475 #[snafu(implicit)]
1476 location: Location,
1477 },
1478
1479 #[snafu(display("Timed out waiting for insert-select source to be polled"))]
1480 SourcePollTimeout {
1481 source: tokio::time::error::Elapsed,
1482 #[snafu(implicit)]
1483 location: Location,
1484 },
1485
1486 #[snafu(display("Insert-select source poll notification channel closed"))]
1487 SourcePollChannelClosed {
1488 source: oneshot::error::RecvError,
1489 #[snafu(implicit)]
1490 location: Location,
1491 },
1492
1493 #[snafu(display("Timed out waiting for insert task to finish"))]
1494 InsertTaskTimeout {
1495 source: tokio::time::error::Elapsed,
1496 #[snafu(implicit)]
1497 location: Location,
1498 },
1499
1500 #[snafu(display("Insert task panicked"))]
1501 InsertTaskPanic {
1502 source: tokio::task::JoinError,
1503 #[snafu(implicit)]
1504 location: Location,
1505 },
1506
1507 #[snafu(display("Expected insert-select to be cancelled"))]
1508 InsertSelectNotCancelled {
1509 #[snafu(implicit)]
1510 location: Location,
1511 },
1512 }
1513
1514 type TestResult<T> = std::result::Result<T, TestError>;
1515
1516 fn parse_one_sql(sql: &str) -> Statement {
1517 parse_stmt(sql, &GreptimeDbDialect {}).unwrap().remove(0)
1518 }
1519
1520 fn test_query_ctx(process_id: u32) -> QueryContextRef {
1521 Arc::new(
1522 QueryContextBuilder::default()
1523 .channel(Channel::Mysql)
1524 .conn_info(ConnInfo::new(None, Channel::Mysql))
1525 .process_id(process_id)
1526 .build(),
1527 )
1528 }
1529
1530 struct BlockingInsertSelectInterceptor {
1531 started_tx: mpsc::UnboundedSender<()>,
1532 finish_rx: std::sync::Mutex<Option<oneshot::Receiver<()>>>,
1533 }
1534
1535 impl BlockingInsertSelectInterceptor {
1536 fn new(started_tx: mpsc::UnboundedSender<()>, finish_rx: oneshot::Receiver<()>) -> Self {
1537 Self {
1538 started_tx,
1539 finish_rx: std::sync::Mutex::new(Some(finish_rx)),
1540 }
1541 }
1542 }
1543
1544 impl SqlQueryInterceptor for BlockingInsertSelectInterceptor {
1545 type Error = Error;
1546
1547 fn pre_execute(
1548 &self,
1549 statement: Option<&Statement>,
1550 _plan: Option<&LogicalPlan>,
1551 _query_ctx: QueryContextRef,
1552 ) -> Result<()> {
1553 let Some(Statement::Insert(insert)) = statement else {
1554 return Ok(());
1555 };
1556 if !insert.has_non_values_query_source() {
1557 return Ok(());
1558 }
1559
1560 let finish_rx = self.finish_rx.lock().unwrap().take().unwrap();
1561 let _ = self.started_tx.send(());
1562 tokio::task::block_in_place(|| {
1563 tokio::runtime::Handle::current()
1564 .block_on(finish_rx)
1565 .unwrap();
1566 });
1567 Ok(())
1568 }
1569 }
1570
1571 struct PendingRecordBatchStream {
1572 schema: GtSchemaRef,
1573 polled_tx: Option<oneshot::Sender<()>>,
1574 _finish_tx: oneshot::Sender<()>,
1575 finish_rx: Pin<Box<oneshot::Receiver<()>>>,
1576 }
1577
1578 impl RecordBatchStream for PendingRecordBatchStream {
1579 fn schema(&self) -> GtSchemaRef {
1580 self.schema.clone()
1581 }
1582
1583 fn output_ordering(&self) -> Option<&[OrderOption]> {
1584 None
1585 }
1586
1587 fn metrics(&self) -> Option<common_recordbatch::adapter::RecordBatchMetrics> {
1588 None
1589 }
1590 }
1591
1592 impl Stream for PendingRecordBatchStream {
1593 type Item = common_recordbatch::error::Result<RecordBatch>;
1594
1595 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1596 if let Some(polled_tx) = self.polled_tx.take() {
1597 let _ = polled_tx.send(());
1598 }
1599
1600 match self.finish_rx.as_mut().poll(cx) {
1601 Poll::Ready(_) => Poll::Ready(None),
1602 Poll::Pending => Poll::Pending,
1603 }
1604 }
1605 }
1606
1607 impl Unpin for PendingRecordBatchStream {}
1608
1609 struct PendingDataSource {
1610 schema: GtSchemaRef,
1611 polled_tx: std::sync::Mutex<Option<oneshot::Sender<()>>>,
1612 }
1613
1614 impl DataSource for PendingDataSource {
1615 fn get_stream(
1616 &self,
1617 _request: ScanRequest,
1618 ) -> std::result::Result<SendableRecordBatchStream, BoxedError> {
1619 let (finish_tx, finish_rx) = oneshot::channel();
1620 let mut polled_tx = self.polled_tx.lock().map_err(|_| {
1621 BoxedError::new(PlainError::new(
1622 "pending data source lock poisoned".to_string(),
1623 StatusCode::Unexpected,
1624 ))
1625 })?;
1626 Ok(Box::pin(PendingRecordBatchStream {
1627 schema: self.schema.clone(),
1628 polled_tx: polled_tx.take(),
1629 _finish_tx: finish_tx,
1630 finish_rx: Box::pin(finish_rx),
1631 }))
1632 }
1633 }
1634
1635 struct NoopProcedureExecutor;
1636
1637 #[async_trait::async_trait]
1638 impl ProcedureExecutor for NoopProcedureExecutor {
1639 async fn submit_ddl_task(
1640 &self,
1641 _ctx: &ExecutorContext,
1642 _request: SubmitDdlTaskRequest,
1643 ) -> common_meta::error::Result<SubmitDdlTaskResponse> {
1644 common_meta::error::UnsupportedSnafu {
1645 operation: "submit_ddl_task",
1646 }
1647 .fail()
1648 }
1649
1650 async fn migrate_region(
1651 &self,
1652 _ctx: &ExecutorContext,
1653 _request: MigrateRegionRequest,
1654 ) -> common_meta::error::Result<MigrateRegionResponse> {
1655 common_meta::error::UnsupportedSnafu {
1656 operation: "migrate_region",
1657 }
1658 .fail()
1659 }
1660
1661 async fn reconcile(
1662 &self,
1663 _ctx: &ExecutorContext,
1664 _request: ReconcileRequest,
1665 ) -> common_meta::error::Result<ReconcileResponse> {
1666 common_meta::error::UnsupportedSnafu {
1667 operation: "reconcile",
1668 }
1669 .fail()
1670 }
1671
1672 async fn query_procedure_state(
1673 &self,
1674 _ctx: &ExecutorContext,
1675 _pid: &str,
1676 ) -> common_meta::error::Result<ProcedureStateResponse> {
1677 common_meta::error::UnsupportedSnafu {
1678 operation: "query_procedure_state",
1679 }
1680 .fail()
1681 }
1682
1683 async fn list_procedures(
1684 &self,
1685 _ctx: &ExecutorContext,
1686 ) -> common_meta::error::Result<ProcedureDetailResponse> {
1687 common_meta::error::UnsupportedSnafu {
1688 operation: "list_procedures",
1689 }
1690 .fail()
1691 }
1692 }
1693
1694 fn test_cache_registry(
1695 kv_backend: common_meta::kv_backend::KvBackendRef,
1696 ) -> TestResult<common_meta::cache::LayeredCacheRegistryRef> {
1697 Ok(Arc::new(
1698 cache::with_default_composite_cache_registry(
1699 LayeredCacheRegistryBuilder::default()
1700 .add_cache_registry(cache::build_fundamental_cache_registry(kv_backend)),
1701 )
1702 .context(BuildCacheRegistrySnafu)?
1703 .build(),
1704 ))
1705 }
1706
1707 fn test_table_info(table_id: u32, table_name: &str) -> TestResult<TableInfo> {
1708 let schema = Arc::new(GtSchema::new(vec![
1709 ColumnSchema::new("id", ConcreteDataType::int32_datatype(), false),
1710 ColumnSchema::new(
1711 "ts",
1712 ConcreteDataType::timestamp_millisecond_datatype(),
1713 false,
1714 )
1715 .with_time_index(true),
1716 ]));
1717 let table_meta = TableMetaBuilder::empty()
1718 .schema(schema)
1719 .primary_key_indices(vec![0])
1720 .value_indices(vec![1])
1721 .next_column_id(1024)
1722 .build()
1723 .with_context(|_| BuildTableMetaSnafu {
1724 table_name: table_name.to_string(),
1725 })?;
1726
1727 TableInfoBuilder::new(table_name, table_meta)
1728 .table_id(table_id)
1729 .build()
1730 .with_context(|_| BuildTableInfoSnafu {
1731 table_name: table_name.to_string(),
1732 })
1733 }
1734
1735 fn test_table(table_id: u32, table_name: &str) -> TestResult<table::TableRef> {
1736 let table_info = test_table_info(table_id, table_name)?;
1737 Ok(EmptyTable::from_table_info(&table_info))
1738 }
1739
1740 fn pending_table(
1741 table_id: u32,
1742 table_name: &str,
1743 polled_tx: oneshot::Sender<()>,
1744 ) -> TestResult<table::TableRef> {
1745 let table_info = test_table_info(table_id, table_name)?;
1746 let data_source = Arc::new(PendingDataSource {
1747 schema: table_info.meta.schema.clone(),
1748 polled_tx: std::sync::Mutex::new(Some(polled_tx)),
1749 });
1750
1751 Ok(Arc::new(Table::new(
1752 Arc::new(table_info),
1753 FilterPushDownType::Unsupported,
1754 data_source,
1755 )))
1756 }
1757
1758 async fn test_instance_with_tables(
1759 source_table: TableRef,
1760 target_table: TableRef,
1761 ) -> TestResult<Instance> {
1762 test_instance_with_plugins(source_table, target_table, Plugins::new()).await
1763 }
1764
1765 async fn test_instance_with_insert_select_interceptor(
1766 interceptor: SqlQueryInterceptorRef<Error>,
1767 ) -> TestResult<Instance> {
1768 let plugins = Plugins::new();
1769 plugins.insert::<SqlQueryInterceptorRef<Error>>(interceptor);
1770
1771 test_instance_with_plugins(
1772 test_table(1024, "source")?,
1773 test_table(1025, "target")?,
1774 plugins,
1775 )
1776 .await
1777 }
1778
1779 async fn test_instance_with_plugins(
1780 source_table: TableRef,
1781 target_table: TableRef,
1782 plugins: Plugins,
1783 ) -> TestResult<Instance> {
1784 let kv_backend = Arc::new(MemoryKvBackend::new());
1785 let process_manager = Arc::new(ProcessManager::new("test-frontend".to_string(), None));
1786 let catalog_manager = catalog::memory::MemoryCatalogManager::new_with_table(source_table);
1787 let target_table_name = "target";
1788 catalog_manager
1789 .register_table_sync(catalog::RegisterTableRequest {
1790 catalog: "greptime".to_string(),
1791 schema: "public".to_string(),
1792 table_name: target_table_name.to_string(),
1793 table_id: 1025,
1794 table: target_table,
1795 })
1796 .with_context(|_| RegisterTableSnafu {
1797 table_name: target_table_name.to_string(),
1798 })?;
1799 catalog_manager.register_process_list_table(process_manager.clone());
1800
1801 let cache_registry = test_cache_registry(kv_backend.clone())?;
1802
1803 FrontendBuilder::new(
1804 FrontendOptions::default(),
1805 kv_backend,
1806 cache_registry,
1807 catalog_manager,
1808 Arc::new(client::client_manager::NodeClients::default()),
1809 Arc::new(NoopProcedureExecutor),
1810 process_manager,
1811 )
1812 .with_plugin(plugins)
1813 .try_build()
1814 .await
1815 .context(BuildFrontendSnafu)
1816 }
1817
1818 async fn execute_one_sql(
1819 instance: &Instance,
1820 sql: &str,
1821 query_ctx: QueryContextRef,
1822 ) -> TestResult<Output> {
1823 let mut results = instance.do_query_inner(sql, query_ctx).await;
1824 ensure!(
1825 results.len() == 1,
1826 UnexpectedOutputCountSnafu {
1827 sql: sql.to_string(),
1828 actual: results.len(),
1829 }
1830 );
1831 results.remove(0).with_context(|_| ExecuteSqlSnafu {
1832 sql: sql.to_string(),
1833 })
1834 }
1835
1836 #[test]
1837 fn test_fast_legacy_check_deadlock_prevention() {
1838 let cache = DashMap::new();
1840
1841 cache.insert("metric1".to_string(), true); cache.insert("metric2".to_string(), false); cache.insert("metric3".to_string(), true); let metric1 = "metric1".to_string();
1848 let metric4 = "metric4".to_string();
1849 let names1 = vec![&metric1, &metric4];
1850 let result = fast_legacy_check(&cache, &names1);
1851 assert!(result.is_ok());
1852 assert_eq!(result.unwrap(), Some(true)); assert!(cache.contains_key("metric4"));
1856 assert!(*cache.get("metric4").unwrap().value());
1857
1858 let metric5 = "metric5".to_string();
1860 let metric6 = "metric6".to_string();
1861 let names2 = vec![&metric5, &metric6];
1862 let result = fast_legacy_check(&cache, &names2);
1863 assert!(result.is_ok());
1864 assert_eq!(result.unwrap(), None); let cache_incompatible = DashMap::new();
1868 cache_incompatible.insert("metric1".to_string(), true); cache_incompatible.insert("metric2".to_string(), false); let metric1_test = "metric1".to_string();
1871 let metric2_test = "metric2".to_string();
1872 let names3 = vec![&metric1_test, &metric2_test];
1873 let result = fast_legacy_check(&cache_incompatible, &names3);
1874 assert!(result.is_err()); let cache_concurrent = Arc::new(DashMap::new());
1880 cache_concurrent.insert("shared_metric".to_string(), true);
1881
1882 let num_threads = 8;
1883 let operations_per_thread = 100;
1884 let barrier = Arc::new(Barrier::new(num_threads));
1885 let success_flag = Arc::new(AtomicBool::new(true));
1886
1887 let handles: Vec<_> = (0..num_threads)
1888 .map(|thread_id| {
1889 let cache_clone = Arc::clone(&cache_concurrent);
1890 let barrier_clone = Arc::clone(&barrier);
1891 let success_flag_clone = Arc::clone(&success_flag);
1892
1893 thread::spawn(move || {
1894 barrier_clone.wait();
1896
1897 let start_time = Instant::now();
1898 for i in 0..operations_per_thread {
1899 let shared_metric = "shared_metric".to_string();
1901 let new_metric = format!("thread_{}_metric_{}", thread_id, i);
1902 let names = vec![&shared_metric, &new_metric];
1903
1904 match fast_legacy_check(&cache_clone, &names) {
1905 Ok(_) => {}
1906 Err(_) => {
1907 success_flag_clone.store(false, Ordering::Relaxed);
1908 return;
1909 }
1910 }
1911
1912 if start_time.elapsed() > Duration::from_secs(10) {
1914 success_flag_clone.store(false, Ordering::Relaxed);
1915 return;
1916 }
1917 }
1918 })
1919 })
1920 .collect();
1921
1922 let start_time = Instant::now();
1924 for (i, handle) in handles.into_iter().enumerate() {
1925 let join_result = handle.join();
1926
1927 if start_time.elapsed() > Duration::from_secs(30) {
1929 panic!("Test timed out - possible deadlock detected!");
1930 }
1931
1932 if join_result.is_err() {
1933 panic!("Thread {} panicked during execution", i);
1934 }
1935 }
1936
1937 assert!(
1939 success_flag.load(Ordering::Relaxed),
1940 "Some operations failed"
1941 );
1942
1943 let final_count = cache_concurrent.len();
1945 assert!(
1946 final_count > 1 + num_threads * operations_per_thread / 2,
1947 "Expected more cache entries, got {}",
1948 final_count
1949 );
1950 }
1951
1952 #[test]
1953 fn test_should_track_statement_process() {
1954 assert!(should_track_statement_process(&parse_one_sql(
1955 "SELECT * FROM demo"
1956 )));
1957 assert!(should_track_statement_process(&parse_one_sql(
1958 "INSERT INTO demo SELECT * FROM source"
1959 )));
1960 assert!(!should_track_statement_process(&parse_one_sql(
1961 "INSERT INTO demo VALUES (1)"
1962 )));
1963 assert!(!should_track_statement_process(&parse_one_sql(
1964 "INSERT INTO demo VALUES (now())"
1965 )));
1966 }
1967
1968 #[test]
1969 fn test_should_track_plan_process() {
1970 let select_stmt = parse_one_sql("SELECT * FROM demo");
1971 let insert_select_stmt = parse_one_sql("INSERT INTO demo SELECT * FROM source");
1972 let insert_values_stmt = parse_one_sql("INSERT INTO demo VALUES (now())");
1973
1974 let empty_plan = LogicalPlanBuilder::empty(false).build().unwrap();
1975 assert!(should_track_plan_process(Some(&select_stmt), &empty_plan));
1976 assert!(should_track_plan_process(
1977 Some(&insert_select_stmt),
1978 &insert_dml_plan()
1979 ));
1980 assert!(!should_track_plan_process(
1981 Some(&insert_values_stmt),
1982 &insert_dml_plan()
1983 ));
1984 assert!(!should_track_plan_process(None, &insert_dml_plan()));
1985 }
1986
1987 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1988 async fn test_insert_select_is_visible_in_show_processlist() -> TestResult<()> {
1989 let insert_sql = "INSERT INTO target SELECT * FROM source";
1990 let (started_tx, mut started_rx) = mpsc::unbounded_channel();
1991 let (finish_tx, finish_rx) = oneshot::channel();
1992 let interceptor = Arc::new(BlockingInsertSelectInterceptor::new(started_tx, finish_rx));
1993 let instance = Arc::new(test_instance_with_insert_select_interceptor(interceptor).await?);
1994
1995 let insert_task = tokio::spawn({
1996 let instance = instance.clone();
1997 async move { execute_one_sql(&instance, insert_sql, test_query_ctx(4242)).await }
1998 });
1999
2000 tokio::time::timeout(Duration::from_secs(5), started_rx.recv())
2001 .await
2002 .context(InsertStartTimeoutSnafu)?
2003 .context(InsertStartChannelClosedSnafu)?;
2004
2005 let output = execute_one_sql(&instance, "SHOW PROCESSLIST", test_query_ctx(43)).await?;
2006 let process_list = output.data.pretty_print().await;
2007 assert!(
2008 process_list.contains(insert_sql),
2009 "process list did not contain running insert:\n{process_list}"
2010 );
2011
2012 finish_tx
2013 .send(())
2014 .map_err(|_| ReleaseBlockedInsertSnafu.build())?;
2015 insert_task.await.context(InsertTaskPanicSnafu)??;
2016
2017 Ok(())
2018 }
2019
2020 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2021 async fn test_kill_query_cancels_insert_select() -> TestResult<()> {
2022 assert_kill_cancels_insert_select("KILL QUERY 4242").await
2023 }
2024
2025 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2026 async fn test_kill_process_id_cancels_insert_select() -> TestResult<()> {
2027 assert_kill_cancels_insert_select("KILL 'test-frontend/4242'").await
2028 }
2029
2030 async fn assert_kill_cancels_insert_select(kill_sql: &str) -> TestResult<()> {
2031 let insert_sql = "INSERT INTO target SELECT * FROM source";
2032 let (source_polled_tx, source_polled_rx) = oneshot::channel();
2033 let instance = Arc::new(
2034 test_instance_with_tables(
2035 pending_table(1024, "source", source_polled_tx)?,
2036 test_table(1025, "target")?,
2037 )
2038 .await?,
2039 );
2040
2041 let insert_task = tokio::spawn({
2042 let instance = instance.clone();
2043 async move { execute_one_sql(&instance, insert_sql, test_query_ctx(4242)).await }
2044 });
2045
2046 tokio::time::timeout(Duration::from_secs(5), source_polled_rx)
2047 .await
2048 .context(SourcePollTimeoutSnafu)?
2049 .context(SourcePollChannelClosedSnafu)?;
2050
2051 let output = execute_one_sql(&instance, kill_sql, test_query_ctx(43)).await?;
2052 assert!(matches!(output.data, OutputData::AffectedRows(1)));
2053
2054 let insert_result = tokio::time::timeout(Duration::from_secs(5), insert_task)
2055 .await
2056 .context(InsertTaskTimeoutSnafu)?
2057 .context(InsertTaskPanicSnafu)?;
2058 let err = match insert_result {
2059 Ok(_) => return InsertSelectNotCancelledSnafu.fail(),
2060 Err(TestError::ExecuteSql { source, .. }) => source,
2061 Err(err) => return Err(err),
2062 };
2063 assert_eq!(StatusCode::Cancelled, err.status_code());
2064
2065 let output = execute_one_sql(&instance, "SHOW PROCESSLIST", test_query_ctx(43)).await?;
2066 let process_list = output.data.pretty_print().await;
2067 assert!(
2068 !process_list.contains(insert_sql),
2069 "process list still contains killed insert:\n{process_list}"
2070 );
2071
2072 Ok(())
2073 }
2074
2075 fn insert_dml_plan() -> LogicalPlan {
2076 let schema = SchemaRef::new(Schema::new(vec![Field::new(
2077 "value",
2078 DataType::Int64,
2079 true,
2080 )]));
2081 let target = Arc::new(LogicalTableSource::new(schema));
2082 let input = LogicalPlanBuilder::empty(false).build().unwrap();
2083
2084 LogicalPlanBuilder::insert_into(input, "demo", target, InsertOp::Append)
2085 .unwrap()
2086 .build()
2087 .unwrap()
2088 }
2089
2090 #[test]
2091 fn test_exec_validation() {
2092 let query_ctx = QueryContext::arc();
2093 let plugins: Plugins = Plugins::new();
2094 plugins.insert(QueryOptions {
2095 disallow_cross_catalog_query: true,
2096 });
2097
2098 let sql = r#"
2099 SELECT * FROM demo;
2100 EXPLAIN SELECT * FROM demo;
2101 CREATE DATABASE test_database;
2102 SHOW DATABASES;
2103 "#;
2104 let stmts = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2105 assert_eq!(stmts.len(), 4);
2106 for stmt in stmts {
2107 let re = check_permission(plugins.clone(), &stmt, &query_ctx);
2108 re.unwrap();
2109 }
2110
2111 let sql = r#"
2112 SHOW CREATE TABLE demo;
2113 ALTER TABLE demo ADD COLUMN new_col INT;
2114 "#;
2115 let stmts = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2116 assert_eq!(stmts.len(), 2);
2117 for stmt in stmts {
2118 let re = check_permission(plugins.clone(), &stmt, &query_ctx);
2119 re.unwrap();
2120 }
2121
2122 fn replace_test(template_sql: &str, plugins: Plugins, query_ctx: &QueryContextRef) {
2123 let right = vec![("", ""), ("", "public."), ("greptime.", "public.")];
2125 for (catalog, schema) in right {
2126 let sql = do_fmt(template_sql, catalog, schema);
2127 do_test(&sql, plugins.clone(), query_ctx, true);
2128 }
2129
2130 let wrong = vec![
2131 ("wrongcatalog.", "public."),
2132 ("wrongcatalog.", "wrongschema."),
2133 ];
2134 for (catalog, schema) in wrong {
2135 let sql = do_fmt(template_sql, catalog, schema);
2136 do_test(&sql, plugins.clone(), query_ctx, false);
2137 }
2138 }
2139
2140 fn do_fmt(template: &str, catalog: &str, schema: &str) -> String {
2141 let vars = HashMap::from([
2142 ("catalog".to_string(), catalog),
2143 ("schema".to_string(), schema),
2144 ]);
2145 template.format(&vars).unwrap()
2146 }
2147
2148 fn do_test(sql: &str, plugins: Plugins, query_ctx: &QueryContextRef, is_ok: bool) {
2149 let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
2150 let re = check_permission(plugins, stmt, query_ctx);
2151 if is_ok {
2152 re.unwrap();
2153 } else {
2154 assert!(re.is_err());
2155 }
2156 }
2157
2158 let sql = "INSERT INTO {catalog}{schema}monitor(host) VALUES ('host1');";
2160 replace_test(sql, plugins.clone(), &query_ctx);
2161
2162 let sql = r#"CREATE TABLE {catalog}{schema}demo(
2164 host STRING,
2165 ts TIMESTAMP,
2166 TIME INDEX (ts),
2167 PRIMARY KEY(host)
2168 ) engine=mito;"#;
2169 replace_test(sql, plugins.clone(), &query_ctx);
2170
2171 let sql = "DROP TABLE {catalog}{schema}demo;";
2173 replace_test(sql, plugins.clone(), &query_ctx);
2174
2175 let sql = "SHOW TABLES FROM public";
2177 let stmt = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2178 check_permission(plugins.clone(), &stmt[0], &query_ctx).unwrap();
2179
2180 let sql = "SHOW TABLES FROM private";
2181 let stmt = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2182 let re = check_permission(plugins.clone(), &stmt[0], &query_ctx);
2183 assert!(re.is_ok());
2184
2185 let sql = "DESC TABLE {catalog}{schema}demo;";
2187 replace_test(sql, plugins.clone(), &query_ctx);
2188
2189 let comment_flow_cases = [
2190 ("COMMENT ON FLOW my_flow IS 'comment';", true),
2191 ("COMMENT ON FLOW greptime.my_flow IS 'comment';", true),
2192 ("COMMENT ON FLOW wrongcatalog.my_flow IS 'comment';", false),
2193 ];
2194 for (sql, is_ok) in comment_flow_cases {
2195 let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
2196 let result = check_permission(plugins.clone(), stmt, &query_ctx);
2197 assert_eq!(result.is_ok(), is_ok);
2198 }
2199
2200 let show_flow_cases = [
2201 ("SHOW CREATE FLOW my_flow;", true),
2202 ("SHOW CREATE FLOW greptime.my_flow;", true),
2203 ("SHOW CREATE FLOW wrongcatalog.my_flow;", false),
2204 ];
2205 for (sql, is_ok) in show_flow_cases {
2206 let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
2207 let result = check_permission(plugins.clone(), stmt, &query_ctx);
2208 assert_eq!(result.is_ok(), is_ok);
2209 }
2210 }
2211}