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::collections::HashSet;
30use std::pin::Pin;
31use std::sync::atomic::AtomicBool;
32use std::sync::{Arc, atomic};
33use std::time::{Duration, SystemTime};
34
35use async_stream::stream;
36use async_trait::async_trait;
37use auth::{
38 PermissionChecker, PermissionCheckerRef, PermissionReq, PermissionTableTarget,
39 PermissionTableTargets,
40};
41use catalog::CatalogManagerRef;
42use catalog::process_manager::{
43 ProcessManagerRef, QueryStatement as CatalogQueryStatement, SlowQueryTimer,
44};
45use client::OutputData;
46use common_base::Plugins;
47use common_base::cancellation::CancellableFuture;
48use common_error::ext::{BoxedError, ErrorExt};
49use common_event_recorder::EventRecorderRef;
50use common_meta::cache::TableFlownodeSetCacheRef;
51use common_meta::cache_invalidator::CacheInvalidatorRef;
52use common_meta::key::TableMetadataManagerRef;
53use common_meta::key::table_name::TableNameKey;
54use common_meta::node_manager::NodeManagerRef;
55use common_meta::procedure_executor::ProcedureExecutorRef;
56use common_query::Output;
57use common_recordbatch::RecordBatchStreamWrapper;
58use common_recordbatch::error::StreamTimeoutSnafu;
59use common_telemetry::logging::SlowQueryOptions;
60use common_telemetry::{debug, error, tracing};
61use dashmap::DashMap;
62use datafusion_expr::LogicalPlan;
63use futures::{Stream, StreamExt, future};
64use lazy_static::lazy_static;
65use operator::delete::DeleterRef;
66use operator::insert::InserterRef;
67use operator::statement::{StatementExecutor, StatementExecutorRef};
68use partition::manager::PartitionRuleManagerRef;
69use pipeline::pipeline_operator::PipelineOperator;
70use prometheus::HistogramTimer;
71use promql_parser::label::Matcher;
72use query::QueryEngineRef;
73use query::metrics::OnDone;
74use query::parser::{PromQuery, QueryStatement};
75use query::query_engine::DescribeResult;
76use query::query_engine::options::{QueryOptions, validate_catalog_and_schema};
77use servers::error::{
78 self as server_error, AuthSnafu, CommonMetaSnafu, ExecuteQuerySnafu,
79 OtlpMetricModeIncompatibleSnafu, UnexpectedResultSnafu,
80};
81use servers::interceptor::{
82 PromQueryInterceptor, PromQueryInterceptorRef, SqlQueryInterceptor, SqlQueryInterceptorRef,
83};
84use servers::otlp::metrics::legacy_normalize_otlp_name;
85use servers::prometheus_handler::{
86 ParsedPromQuery, PrometheusHandler, resolve_schema_from_matchers,
87};
88use servers::query_handler::sql::SqlQueryHandler;
89use session::context::{Channel, QueryContextRef};
90use session::table_name::table_idents_to_full_name;
91use snafu::prelude::*;
92use sql::ast::ObjectNamePartExt;
93use sql::dialect::Dialect;
94use sql::parser::{ParseOptions, ParserContext};
95use sql::statements::comment::CommentObject;
96use sql::statements::copy::{CopyDatabase, CopyTable};
97use sql::statements::statement::Statement;
98use sql::statements::tql::Tql;
99use sql::util::{extract_tables_from_prom_expr_checked, extract_tables_from_statement_checked};
100use sqlparser::ast::{AnalyzeFormat, ObjectName};
101pub use standalone::StandaloneDatanodeManager;
102use table::requests::{OTLP_METRIC_COMPAT_KEY, OTLP_METRIC_COMPAT_PROM};
103use tracing::Span;
104
105use crate::error::{
106 self, CollectRecordbatchSnafu, Error, ExecLogicalPlanSnafu, ExecutePromqlSnafu, ExternalSnafu,
107 InvalidSqlSnafu, ParseSqlSnafu, PermissionSnafu, PlanStatementSnafu, Result,
108 SqlExecInterceptedSnafu, StatementTimeoutSnafu, TableOperationSnafu,
109};
110use crate::service_config::InfluxdbMergeMode;
111use crate::stream_wrapper::CancellableStreamWrapper;
112
113lazy_static! {
114 static ref OTLP_LEGACY_DEFAULT_VALUE: String = "legacy".to_string();
115}
116
117#[derive(Clone)]
121pub struct Instance {
122 frontend_peer_addr: String,
123 catalog_manager: CatalogManagerRef,
124 pipeline_operator: Arc<PipelineOperator>,
125 statement_executor: Arc<StatementExecutor>,
126 query_engine: QueryEngineRef,
127 plugins: Plugins,
128 inserter: InserterRef,
129 deleter: DeleterRef,
130 table_metadata_manager: TableMetadataManagerRef,
131 event_recorder: Option<EventRecorderRef>,
132 process_manager: ProcessManagerRef,
133 slow_query_options: SlowQueryOptions,
134 influxdb_default_merge_mode: InfluxdbMergeMode,
135 trace_ingest_chunk_size: usize,
136 suspend: Arc<AtomicBool>,
137
138 otlp_metrics_table_legacy_cache: DashMap<String, DashMap<String, bool>>,
143}
144
145impl Instance {
146 pub fn frontend_peer_addr(&self) -> &str {
147 &self.frontend_peer_addr
148 }
149
150 pub fn catalog_manager(&self) -> &CatalogManagerRef {
151 &self.catalog_manager
152 }
153
154 pub fn query_engine(&self) -> &QueryEngineRef {
155 &self.query_engine
156 }
157
158 pub fn plugins(&self) -> &Plugins {
159 &self.plugins
160 }
161
162 pub fn statement_executor(&self) -> &StatementExecutorRef {
163 &self.statement_executor
164 }
165
166 pub fn table_metadata_manager(&self) -> &TableMetadataManagerRef {
167 &self.table_metadata_manager
168 }
169
170 pub fn inserter(&self) -> &InserterRef {
171 &self.inserter
172 }
173
174 pub fn process_manager(&self) -> &ProcessManagerRef {
175 &self.process_manager
176 }
177
178 pub fn node_manager(&self) -> &NodeManagerRef {
179 self.inserter.node_manager()
180 }
181
182 pub fn partition_manager(&self) -> &PartitionRuleManagerRef {
183 self.inserter.partition_manager()
184 }
185
186 pub fn table_flownode_set_cache(&self) -> &TableFlownodeSetCacheRef {
187 self.inserter.table_flownode_set_cache()
188 }
189
190 pub fn cache_invalidator(&self) -> &CacheInvalidatorRef {
191 self.statement_executor.cache_invalidator()
192 }
193
194 pub fn procedure_executor(&self) -> &ProcedureExecutorRef {
195 self.statement_executor.procedure_executor()
196 }
197
198 pub fn suspend_state(&self) -> Arc<AtomicBool> {
199 self.suspend.clone()
200 }
201
202 pub(crate) fn is_suspended(&self) -> bool {
203 self.suspend.load(atomic::Ordering::Relaxed)
204 }
205}
206
207fn parse_stmt(sql: &str, dialect: &(dyn Dialect + Send + Sync)) -> Result<Vec<Statement>> {
208 ParserContext::create_with_dialect(sql, dialect, ParseOptions::default()).context(ParseSqlSnafu)
209}
210
211fn validate_analyze_stream_statement(stmt: &mut Statement) -> Result<()> {
212 let Statement::Explain(explain) = stmt else {
213 return InvalidSqlSnafu {
214 err_msg: "only EXPLAIN ANALYZE VERBOSE statement is supported",
215 }
216 .fail();
217 };
218 ensure!(
219 explain.analyze && explain.verbose,
220 InvalidSqlSnafu {
221 err_msg: "statement must be EXPLAIN ANALYZE VERBOSE"
222 }
223 );
224 match explain.format {
225 None | Some(AnalyzeFormat::JSON) => {
226 explain.format = None;
230 Ok(())
231 }
232 Some(_) => InvalidSqlSnafu {
233 err_msg: "only FORMAT JSON is supported for analyze stream",
234 }
235 .fail(),
236 }
237}
238
239impl Instance {
240 fn statement_slow_query_timer(
241 &self,
242 stmt: &Statement,
243 schema_name: String,
244 ) -> Option<SlowQueryTimer> {
245 if !stmt.is_readonly() || !self.slow_query_options.enable {
246 return None;
247 }
248
249 self.event_recorder.clone().map(|event_recorder| {
250 SlowQueryTimer::new(
251 CatalogQueryStatement::Sql(stmt.clone()),
252 schema_name,
253 self.slow_query_options.threshold,
254 self.slow_query_options.sample_ratio,
255 self.slow_query_options.record_type,
256 event_recorder,
257 )
258 })
259 }
260
261 async fn query_statement(&self, stmt: Statement, query_ctx: QueryContextRef) -> Result<Output> {
262 check_permission(self.plugins.clone(), &stmt, &query_ctx)?;
263
264 let query_interceptor = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
265 let query_interceptor = query_interceptor.as_ref();
266
267 if should_track_statement_process(&stmt) {
268 let catalog_name = query_ctx.current_catalog().to_string();
269 let schema_name = query_ctx.current_schema();
270 let slow_query_timer = self.statement_slow_query_timer(&stmt, schema_name.clone());
271
272 let ticket = self.process_manager.register_query(
273 catalog_name,
274 vec![schema_name],
275 stmt.to_string(),
276 query_ctx.conn_info().to_string(),
277 Some(query_ctx.process_id()),
278 slow_query_timer,
279 );
280
281 let query_fut = self.exec_statement_with_timeout(stmt, query_ctx, query_interceptor);
282
283 CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
284 .await
285 .map_err(|_| error::CancelledSnafu.build())?
286 .map(|output| {
287 let Output { meta, data } = output;
288
289 let data = match data {
290 OutputData::Stream(stream) => OutputData::Stream(Box::pin(
291 CancellableStreamWrapper::new(stream, ticket),
292 )),
293 other => other,
294 };
295 Output { data, meta }
296 })
297 } else {
298 self.exec_statement_with_timeout(stmt, query_ctx, query_interceptor)
299 .await
300 }
301 }
302
303 async fn exec_statement_with_timeout(
304 &self,
305 stmt: Statement,
306 query_ctx: QueryContextRef,
307 query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
308 ) -> Result<Output> {
309 let timeout = derive_timeout(&stmt, &query_ctx);
310 match timeout {
311 Some(timeout) => {
312 let start = tokio::time::Instant::now();
313 let output = tokio::time::timeout(
314 timeout,
315 self.exec_statement(stmt, query_ctx, query_interceptor),
316 )
317 .await
318 .map_err(|_| StatementTimeoutSnafu.build())??;
319 let output = map_query_output(output)?;
320 let remaining_timeout = timeout.checked_sub(start.elapsed()).unwrap_or_default();
322 attach_timeout(output, remaining_timeout)
323 }
324 None => self
325 .exec_statement(stmt, query_ctx, query_interceptor)
326 .await
327 .and_then(map_query_output),
328 }
329 }
330
331 async fn exec_statement(
332 &self,
333 stmt: Statement,
334 query_ctx: QueryContextRef,
335 query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
336 ) -> Result<Output> {
337 match stmt {
338 Statement::Query(_) | Statement::Explain(_) | Statement::Delete(_) => {
339 if let Statement::Explain(explain) = &stmt
341 && let Some(format) = explain.format()
342 {
343 query_ctx.set_explain_format(format.to_string());
344 }
345
346 self.plan_and_exec_sql(stmt, &query_ctx, query_interceptor)
347 .await
348 }
349 Statement::Tql(tql) => {
350 self.plan_and_exec_tql(&query_ctx, query_interceptor, tql)
351 .await
352 }
353 _ => {
354 query_interceptor.pre_execute(Some(&stmt), None, query_ctx.clone())?;
355 self.statement_executor
356 .execute_sql(stmt, query_ctx)
357 .await
358 .context(TableOperationSnafu)
359 }
360 }
361 }
362
363 async fn plan_and_exec_sql(
364 &self,
365 stmt: Statement,
366 query_ctx: &QueryContextRef,
367 query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
368 ) -> Result<Output> {
369 let stmt = QueryStatement::Sql(stmt);
370 let plan = self
371 .statement_executor
372 .plan(&stmt, query_ctx.clone())
373 .await?;
374 let QueryStatement::Sql(stmt) = stmt else {
375 unreachable!()
376 };
377 query_interceptor.pre_execute(Some(&stmt), Some(&plan), query_ctx.clone())?;
378
379 self.statement_executor
380 .exec_plan(plan, query_ctx.clone())
381 .await
382 .context(TableOperationSnafu)
383 }
384
385 async fn plan_and_exec_tql(
386 &self,
387 query_ctx: &QueryContextRef,
388 query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
389 tql: Tql,
390 ) -> Result<Output> {
391 let plan = self
392 .statement_executor
393 .plan_tql(tql.clone(), query_ctx)
394 .await?;
395 query_interceptor.pre_execute(
396 Some(&Statement::Tql(tql)),
397 Some(&plan),
398 query_ctx.clone(),
399 )?;
400 self.statement_executor
401 .exec_plan(plan, query_ctx.clone())
402 .await
403 .context(TableOperationSnafu)
404 }
405
406 async fn check_otlp_legacy(
407 &self,
408 names: &[String],
409 ctx: &QueryContextRef,
410 ) -> server_error::Result<bool> {
411 let db_string = ctx.get_db_string();
412 let cache = self
414 .otlp_metrics_table_legacy_cache
415 .entry(db_string.clone())
416 .or_default();
417 if let Some(flag) = fast_legacy_check(&cache, names)? {
418 return Ok(flag);
419 }
420 drop(cache);
422
423 let catalog = ctx.current_catalog();
424 let schema = ctx.current_schema();
425
426 let normalized_names = names
428 .iter()
429 .map(|n| legacy_normalize_otlp_name(n))
430 .collect::<Vec<_>>();
431 let table_names = normalized_names
432 .iter()
433 .map(|n| TableNameKey::new(catalog, &schema, n))
434 .collect::<Vec<_>>();
435 let table_values = self
436 .table_metadata_manager()
437 .table_name_manager()
438 .batch_get(table_names)
439 .await
440 .context(CommonMetaSnafu)?;
441 let table_ids = table_values
442 .into_iter()
443 .filter_map(|v| v.map(|vi| vi.table_id()))
444 .collect::<Vec<_>>();
445
446 if table_ids.is_empty() {
448 return Ok(false);
449 }
450
451 let table_infos = self
453 .table_metadata_manager()
454 .table_info_manager()
455 .batch_get(&table_ids)
456 .await
457 .context(CommonMetaSnafu)?;
458 let options = table_infos
459 .values()
460 .map(|info| {
461 info.table_info
462 .meta
463 .options
464 .extra_options
465 .get(OTLP_METRIC_COMPAT_KEY)
466 .unwrap_or(&OTLP_LEGACY_DEFAULT_VALUE)
467 })
468 .collect::<Vec<_>>();
469 if !options.is_empty() {
470 let has_prom = options.iter().any(|opt| *opt == OTLP_METRIC_COMPAT_PROM);
472 let has_legacy = options
473 .iter()
474 .any(|opt| *opt == OTLP_LEGACY_DEFAULT_VALUE.as_str());
475 ensure!(!(has_prom && has_legacy), OtlpMetricModeIncompatibleSnafu);
476 Ok(has_legacy)
477 } else {
478 Ok(false)
480 }
481 }
482
483 fn cache_otlp_legacy(
484 &self,
485 names: &[String],
486 ctx: &QueryContextRef,
487 is_legacy: bool,
488 ) -> server_error::Result<()> {
489 let cache = self
490 .otlp_metrics_table_legacy_cache
491 .entry(ctx.get_db_string())
492 .or_default();
493 cache_legacy_mode(&cache, names, is_legacy)
494 }
495}
496
497fn fast_legacy_check(
498 cache: &DashMap<String, bool>,
499 names: &[String],
500) -> server_error::Result<Option<bool>> {
501 let hit_cache = names
502 .iter()
503 .filter_map(|name| cache.get(name))
504 .collect::<Vec<_>>();
505 if !hit_cache.is_empty() {
506 let hit_legacy = hit_cache.iter().any(|en| *en.value());
507 let hit_prom = hit_cache.iter().any(|en| !*en.value());
508
509 ensure!(!(hit_legacy && hit_prom), OtlpMetricModeIncompatibleSnafu);
513
514 Ok(Some(hit_legacy))
515 } else {
516 Ok(None)
517 }
518}
519
520fn cache_legacy_mode(
521 cache: &DashMap<String, bool>,
522 names: &[String],
523 is_legacy: bool,
524) -> server_error::Result<()> {
525 for name in names {
526 let cached = cache.entry(name.clone()).or_insert(is_legacy);
527 ensure!(*cached == is_legacy, OtlpMetricModeIncompatibleSnafu);
528 }
529 Ok(())
530}
531
532fn derive_timeout(stmt: &Statement, query_ctx: &QueryContextRef) -> Option<Duration> {
535 let query_timeout = query_ctx.query_timeout()?;
536 if query_timeout.is_zero() {
537 return None;
538 }
539 match query_ctx.channel() {
540 Channel::Mysql if stmt.is_readonly() => Some(query_timeout),
541 Channel::Postgres => Some(query_timeout),
542 _ => None,
543 }
544}
545
546fn derive_timeout_for_plan(plan: &LogicalPlan, query_ctx: &QueryContextRef) -> Option<Duration> {
548 let query_timeout = query_ctx.query_timeout()?;
549 if query_timeout.is_zero() {
550 return None;
551 }
552 match query_ctx.channel() {
553 Channel::Mysql if is_readonly_plan(plan) => Some(query_timeout),
554 Channel::Postgres => Some(query_timeout),
555 _ => None,
556 }
557}
558
559fn attach_timeout(output: Output, mut timeout: Duration) -> Result<Output> {
560 if timeout.is_zero() {
561 return StatementTimeoutSnafu.fail();
562 }
563
564 let output = match output.data {
565 OutputData::AffectedRows(_) | OutputData::RecordBatches(_) => output,
566 OutputData::Stream(mut stream) => {
567 let schema = stream.schema();
568 let s = Box::pin(stream! {
569 let mut start = tokio::time::Instant::now();
570 while let Some(item) = tokio::time::timeout(timeout, stream.next()).await.map_err(|_| StreamTimeoutSnafu.build())? {
571 yield item;
572
573 let now = tokio::time::Instant::now();
574 timeout = timeout.checked_sub(now - start).unwrap_or(Duration::ZERO);
575 start = now;
576 if timeout.is_zero() {
578 StreamTimeoutSnafu.fail()?;
579 }
580 }
581 }) as Pin<Box<dyn Stream<Item = _> + Send>>;
582 let stream = RecordBatchStreamWrapper {
583 schema,
584 stream: s,
585 output_ordering: None,
586 metrics: Default::default(),
587 span: Span::current(),
588 };
589 Output::new(OutputData::Stream(Box::pin(stream)), output.meta)
590 }
591 };
592
593 Ok(output)
594}
595
596impl Instance {
597 async fn check_sql_permission(
598 &self,
599 stmt: &Statement,
600 query_ctx: &QueryContextRef,
601 ) -> Result<()> {
602 self.plugins
603 .get::<PermissionCheckerRef>()
604 .as_ref()
605 .check_permission_with_context(
606 query_ctx.current_user(),
607 PermissionReq::SqlStatement(stmt),
608 Some(&query_ctx.current_schema()),
609 )
610 .context(PermissionSnafu)?;
611
612 let targets = match extract_tables_from_statement_checked(stmt) {
613 Some(tables) => PermissionTableTargets::resolved(
614 tables
615 .map(|name| {
616 table_idents_to_full_name(&name, query_ctx).map(
617 |(catalog, schema, table)| {
618 PermissionTableTarget::new(catalog, schema, table)
619 },
620 )
621 })
622 .collect::<std::result::Result<Vec<_>, _>>()
623 .map_err(BoxedError::new)
624 .context(ExternalSnafu)?,
625 ),
626 None => PermissionTableTargets::Unresolved,
627 };
628 let targets = self
629 .resolve_query_permission_targets(targets, query_ctx)
630 .await
631 .map_err(BoxedError::new)
632 .context(ExternalSnafu)?;
633 self.check_table_permission(query_ctx, PermissionReq::SqlStatement(stmt), targets)
634 .context(PermissionSnafu)?;
635 Ok(())
636 }
637
638 #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_analyze_stream_query")]
639 async fn do_analyze_stream_query_inner(
640 &self,
641 query: &str,
642 query_ctx: QueryContextRef,
643 ) -> Result<Output> {
644 ensure!(!self.is_suspended(), error::SuspendedSnafu);
645
646 let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
647 let query_interceptor = query_interceptor_opt.as_ref();
648 let query = query_interceptor.pre_parsing(query, query_ctx.clone())?;
649 let mut stmts = parse_stmt(query.as_ref(), query_ctx.sql_dialect())
650 .and_then(|stmts| query_interceptor.post_parsing(stmts, query_ctx.clone()))?;
651
652 ensure!(
653 stmts.len() == 1,
654 InvalidSqlSnafu {
655 err_msg: "only single EXPLAIN ANALYZE VERBOSE statement is supported"
656 }
657 );
658 let mut stmt = stmts.remove(0);
659 validate_analyze_stream_statement(&mut stmt)?;
660 query_ctx.set_explain_format(AnalyzeFormat::JSON.to_string());
661
662 self.check_sql_permission(&stmt, &query_ctx).await?;
663 check_permission(self.plugins.clone(), &stmt, &query_ctx)?;
664 let catalog_name = query_ctx.current_catalog().to_string();
665 let schema_name = query_ctx.current_schema();
666 let slow_query_timer = self.statement_slow_query_timer(&stmt, schema_name.clone());
667 let ticket = self.process_manager.register_query(
668 catalog_name,
669 vec![schema_name],
670 stmt.to_string(),
671 query_ctx.conn_info().to_string(),
672 Some(query_ctx.process_id()),
673 slow_query_timer,
674 );
675 let query_fut =
676 self.exec_statement_with_timeout(stmt, query_ctx.clone(), query_interceptor);
677 let output = CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
678 .await
679 .map_err(|_| error::CancelledSnafu.build())??;
680 let Output { meta, data } = output;
681 let data = match data {
682 OutputData::Stream(stream) => OutputData::Stream(Box::pin(
683 CancellableStreamWrapper::new_cancel_on_drop(stream, ticket),
684 )),
685 other => other,
686 };
687 query_interceptor.post_execute(Output { data, meta }, query_ctx)
688 }
689
690 #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_query")]
691 async fn do_query_inner(&self, query: &str, query_ctx: QueryContextRef) -> Vec<Result<Output>> {
692 if self.is_suspended() {
693 return vec![error::SuspendedSnafu {}.fail()];
694 }
695
696 let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
697 let query_interceptor = query_interceptor_opt.as_ref();
698 let query = match query_interceptor.pre_parsing(query, query_ctx.clone()) {
699 Ok(q) => q,
700 Err(e) => return vec![Err(e)],
701 };
702
703 match parse_stmt(query.as_ref(), query_ctx.sql_dialect())
704 .and_then(|stmts| query_interceptor.post_parsing(stmts, query_ctx.clone()))
705 {
706 Ok(stmts) => {
707 if stmts.is_empty() {
708 return vec![
709 InvalidSqlSnafu {
710 err_msg: "empty statements",
711 }
712 .fail(),
713 ];
714 }
715
716 let mut results = Vec::with_capacity(stmts.len());
717 for stmt in stmts {
718 if let Err(e) = self.check_sql_permission(&stmt, &query_ctx).await {
719 results.push(Err(e));
720 break;
721 }
722
723 match self.query_statement(stmt.clone(), query_ctx.clone()).await {
724 Ok(output) => {
725 let output_result =
726 query_interceptor.post_execute(output, query_ctx.clone());
727 results.push(output_result);
728 }
729 Err(e) => {
730 if e.status_code().should_log_error() {
731 error!(e; "Failed to execute query: {stmt}");
732 } else {
733 debug!("Failed to execute query: {stmt}, {e}");
734 }
735 results.push(Err(e));
736 break;
737 }
738 }
739 }
740 results
741 }
742 Err(e) => {
743 vec![Err(e)]
744 }
745 }
746 }
747
748 async fn exec_plan(&self, plan: LogicalPlan, query_ctx: QueryContextRef) -> Result<Output> {
749 self.query_engine
750 .execute(plan, query_ctx)
751 .await
752 .context(ExecLogicalPlanSnafu)
753 }
754
755 async fn exec_plan_with_timeout(
756 &self,
757 plan: LogicalPlan,
758 query_ctx: QueryContextRef,
759 ) -> Result<Output> {
760 let timeout = derive_timeout_for_plan(&plan, &query_ctx);
761 match timeout {
762 Some(timeout) => {
763 let start = tokio::time::Instant::now();
764 let output = tokio::time::timeout(timeout, self.exec_plan(plan, query_ctx))
765 .await
766 .map_err(|_| StatementTimeoutSnafu.build())??;
767 let output = map_query_output(output)?;
768 let remaining_timeout = timeout.checked_sub(start.elapsed()).unwrap_or_default();
769 attach_timeout(output, remaining_timeout)
770 }
771 None => self
772 .exec_plan(plan, query_ctx)
773 .await
774 .and_then(map_query_output),
775 }
776 }
777
778 async fn do_exec_plan_inner(
779 &self,
780 plan: LogicalPlan,
781 stmt: Option<Statement>,
782 query_ctx: QueryContextRef,
783 ) -> Result<Output> {
784 ensure!(!self.is_suspended(), error::SuspendedSnafu);
785
786 let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
787 let query_interceptor = query_interceptor_opt.as_ref();
788
789 query_interceptor.pre_execute(stmt.as_ref(), Some(&plan), query_ctx.clone())?;
790
791 let query = stmt
792 .as_ref()
793 .map(|s| s.to_string())
794 .unwrap_or_else(|| plan.display_indent().to_string());
795
796 let plan_is_readonly = is_readonly_plan(&plan);
797 let result = if should_track_plan_process(stmt.as_ref(), &plan) {
798 let catalog_name = query_ctx.current_catalog().to_string();
799 let schema_name = query_ctx.current_schema();
800 let slow_query_timer = if plan_is_readonly {
801 self.slow_query_options
802 .enable
803 .then(|| self.event_recorder.clone())
804 .flatten()
805 .map(|event_recorder| {
806 SlowQueryTimer::new(
807 CatalogQueryStatement::Plan(query.clone()),
808 schema_name.clone(),
809 self.slow_query_options.threshold,
810 self.slow_query_options.sample_ratio,
811 self.slow_query_options.record_type,
812 event_recorder,
813 )
814 })
815 } else {
816 None
817 };
818
819 let ticket = self.process_manager.register_query(
820 catalog_name,
821 vec![schema_name],
822 query,
823 query_ctx.conn_info().to_string(),
824 Some(query_ctx.process_id()),
825 slow_query_timer,
826 );
827
828 let query_fut = self.exec_plan_with_timeout(plan, query_ctx.clone());
829
830 CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
831 .await
832 .map_err(|_| error::CancelledSnafu.build())?
833 .map(|output| {
834 let Output { meta, data } = output;
835
836 let data = match data {
837 OutputData::Stream(stream) => OutputData::Stream(Box::pin(
838 CancellableStreamWrapper::new(stream, ticket),
839 )),
840 other => other,
841 };
842 Output { data, meta }
843 })
844 } else {
845 self.exec_plan_with_timeout(plan, query_ctx.clone()).await
846 };
847
848 result.and_then(|output| query_interceptor.post_execute(output, query_ctx))
849 }
850
851 #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_promql_query")]
852 async fn do_promql_query_inner(
853 &self,
854 query: &PromQuery,
855 query_ctx: QueryContextRef,
856 ) -> Vec<Result<Output>> {
857 if self.is_suspended() {
858 return vec![error::SuspendedSnafu {}.fail()];
859 }
860
861 let result = PrometheusHandler::do_query(self, query, query_ctx)
863 .await
864 .with_context(|_| ExecutePromqlSnafu {
865 query: format!("{query:?}"),
866 });
867 vec![result]
868 }
869
870 async fn do_describe_inner(
871 &self,
872 stmt: Statement,
873 query_ctx: QueryContextRef,
874 ) -> Result<Option<DescribeResult>> {
875 ensure!(!self.is_suspended(), error::SuspendedSnafu);
876
877 let is_inner_plannable = |s: &Statement| {
881 matches!(
882 s,
883 Statement::Insert(_) | Statement::Query(_) | Statement::Delete(_)
884 )
885 };
886 let plannable = is_inner_plannable(&stmt)
887 || matches!(&stmt, Statement::Explain(explain) if is_inner_plannable(explain.statement.as_ref()));
888
889 if plannable {
890 self.check_sql_permission(&stmt, &query_ctx).await?;
891
892 let plan = self
893 .query_engine
894 .planner()
895 .plan(&QueryStatement::Sql(stmt), query_ctx.clone())
896 .await
897 .context(PlanStatementSnafu)?;
898 self.query_engine
899 .describe(plan, query_ctx)
900 .await
901 .map(Some)
902 .context(error::DescribeStatementSnafu)
903 } else {
904 Ok(None)
905 }
906 }
907
908 async fn is_valid_schema_inner(&self, catalog: &str, schema: &str) -> Result<bool> {
909 self.catalog_manager
910 .schema_exists(catalog, schema, None)
911 .await
912 .context(error::CatalogSnafu)
913 }
914}
915
916#[async_trait]
917impl SqlQueryHandler for Instance {
918 async fn do_query(
919 &self,
920 query: &str,
921 query_ctx: QueryContextRef,
922 ) -> Vec<server_error::Result<Output>> {
923 self.do_query_inner(query, query_ctx)
924 .await
925 .into_iter()
926 .map(|result| result.map_err(BoxedError::new).context(ExecuteQuerySnafu))
927 .collect()
928 }
929
930 async fn do_analyze_stream_query(
931 &self,
932 query: &str,
933 query_ctx: QueryContextRef,
934 ) -> server_error::Result<Output> {
935 self.do_analyze_stream_query_inner(query, query_ctx)
936 .await
937 .map_err(BoxedError::new)
938 .context(ExecuteQuerySnafu)
939 }
940
941 async fn do_exec_plan(
942 &self,
943 plan: LogicalPlan,
944 stmt: Option<Statement>,
945 query_ctx: QueryContextRef,
946 ) -> server_error::Result<Output> {
947 self.do_exec_plan_inner(plan, stmt, query_ctx)
948 .await
949 .map_err(BoxedError::new)
950 .context(server_error::ExecutePlanSnafu)
951 }
952
953 async fn do_promql_query(
954 &self,
955 query: &PromQuery,
956 query_ctx: QueryContextRef,
957 ) -> Vec<server_error::Result<Output>> {
958 self.do_promql_query_inner(query, query_ctx)
959 .await
960 .into_iter()
961 .map(|result| result.map_err(BoxedError::new).context(ExecuteQuerySnafu))
962 .collect()
963 }
964
965 async fn do_describe(
966 &self,
967 stmt: Statement,
968 query_ctx: QueryContextRef,
969 ) -> server_error::Result<Option<DescribeResult>> {
970 self.do_describe_inner(stmt, query_ctx)
971 .await
972 .map_err(BoxedError::new)
973 .context(server_error::DescribeStatementSnafu)
974 }
975
976 async fn is_valid_schema(&self, catalog: &str, schema: &str) -> server_error::Result<bool> {
977 self.is_valid_schema_inner(catalog, schema)
978 .await
979 .map_err(BoxedError::new)
980 .context(server_error::CheckDatabaseValiditySnafu)
981 }
982}
983
984pub(crate) fn map_query_output(output: Output) -> Result<Output> {
986 output
987 .map_dictionary_to_values()
988 .context(CollectRecordbatchSnafu)
989}
990
991pub fn attach_timer(output: Output, timer: HistogramTimer) -> Output {
993 match output.data {
994 OutputData::AffectedRows(_) | OutputData::RecordBatches(_) => output,
995 OutputData::Stream(stream) => {
996 let stream = OnDone::new(stream, move || {
997 timer.observe_duration();
998 });
999 Output::new(OutputData::Stream(Box::pin(stream)), output.meta)
1000 }
1001 }
1002}
1003
1004impl Instance {
1005 fn check_prom_query_privilege(&self, query_ctx: &QueryContextRef) -> server_error::Result<()> {
1006 self.plugins
1007 .get::<PermissionCheckerRef>()
1008 .as_ref()
1009 .check_permission(query_ctx.current_user(), PermissionReq::PromQuery)
1010 .context(AuthSnafu)?;
1011 Ok(())
1012 }
1013
1014 fn prom_expr_permission_targets(
1015 &self,
1016 expr: &promql_parser::parser::Expr,
1017 query_ctx: &QueryContextRef,
1018 ) -> server_error::Result<Option<Vec<PermissionTableTarget>>> {
1019 extract_tables_from_prom_expr_checked(expr)
1020 .map(|tables| {
1021 tables
1022 .map(|name| {
1023 table_idents_to_full_name(&name, query_ctx).map(
1024 |(catalog, schema, table)| {
1025 PermissionTableTarget::new(catalog, schema, table)
1026 },
1027 )
1028 })
1029 .collect::<std::result::Result<Vec<_>, _>>()
1030 .map_err(BoxedError::new)
1031 .context(ExecuteQuerySnafu)
1032 })
1033 .transpose()
1034 }
1035
1036 async fn is_physical_query_permission_target(
1037 &self,
1038 target: &PermissionTableTarget,
1039 query_ctx: &QueryContextRef,
1040 ) -> server_error::Result<bool> {
1041 self.catalog_manager
1042 .table(
1043 &target.catalog,
1044 &target.schema,
1045 &target.table,
1046 Some(query_ctx),
1047 )
1048 .await
1049 .map(|table| table.is_some_and(|table| table.table_info().is_physical_table()))
1050 .map_err(BoxedError::new)
1051 .context(ExecuteQuerySnafu)
1052 }
1053
1054 async fn resolve_query_permission_targets(
1055 &self,
1056 targets: PermissionTableTargets,
1057 query_ctx: &QueryContextRef,
1058 ) -> server_error::Result<PermissionTableTargets> {
1059 const CONCURRENCY: usize = 8;
1060
1061 let checker = self.plugins.get::<PermissionCheckerRef>();
1062 if !checker.as_ref().uses_table_targets() {
1063 return Ok(targets);
1064 }
1065
1066 let PermissionTableTargets::Resolved(mut targets) = targets else {
1067 return Ok(PermissionTableTargets::Unresolved);
1068 };
1069 if targets.len() > 1 {
1070 let mut seen = HashSet::with_capacity(targets.len());
1071 targets.retain(|target| seen.insert(target.clone()));
1072 }
1073 if let [target] = targets.as_slice() {
1074 return if self
1075 .is_physical_query_permission_target(target, query_ctx)
1076 .await?
1077 {
1078 Ok(PermissionTableTargets::Unresolved)
1079 } else {
1080 Ok(PermissionTableTargets::resolved(targets))
1081 };
1082 }
1083
1084 for chunk in targets.chunks(CONCURRENCY) {
1086 let results = future::join_all(
1087 chunk
1088 .iter()
1089 .map(|target| self.is_physical_query_permission_target(target, query_ctx)),
1090 )
1091 .await;
1092 for result in results {
1093 if result? {
1094 return Ok(PermissionTableTargets::Unresolved);
1095 }
1096 }
1097 }
1098
1099 Ok(PermissionTableTargets::resolved(targets))
1100 }
1101
1102 fn prom_queries_permission_targets(
1103 &self,
1104 queries: &[ParsedPromQuery],
1105 query_ctx: &QueryContextRef,
1106 ) -> server_error::Result<PermissionTableTargets> {
1107 let mut targets = Vec::new();
1108 let mut resolved = true;
1109
1110 for query in queries {
1111 let QueryStatement::Promql(eval_stmt, _) = query.statement() else {
1112 unreachable!("query is parsed from promql");
1113 };
1114
1115 if let Some(query_targets) =
1116 self.prom_expr_permission_targets(&eval_stmt.expr, query_ctx)?
1117 {
1118 targets.extend(query_targets);
1119 } else {
1120 resolved = false;
1121 }
1122 }
1123
1124 Ok(if resolved {
1125 PermissionTableTargets::resolved(targets)
1126 } else {
1127 PermissionTableTargets::Unresolved
1128 })
1129 }
1130}
1131
1132#[async_trait]
1133impl PrometheusHandler for Instance {
1134 #[tracing::instrument(skip_all)]
1135 async fn do_query(
1136 &self,
1137 query: &PromQuery,
1138 query_ctx: QueryContextRef,
1139 ) -> server_error::Result<Output> {
1140 let query = ParsedPromQuery::parse(query.clone(), &query_ctx)?;
1141 self.do_query_parsed(query, query_ctx).await
1142 }
1143
1144 #[tracing::instrument(skip_all)]
1145 async fn do_query_parsed(
1146 &self,
1147 query: ParsedPromQuery,
1148 query_ctx: QueryContextRef,
1149 ) -> server_error::Result<Output> {
1150 let interceptor = self
1151 .plugins
1152 .get::<PromQueryInterceptorRef<server_error::Error>>();
1153
1154 self.check_prom_query_privilege(&query_ctx)?;
1155
1156 let targets =
1157 self.prom_queries_permission_targets(std::slice::from_ref(&query), &query_ctx)?;
1158 self.check_query_target_permission(targets, &query_ctx)
1159 .await?;
1160
1161 let (query, stmt) = query.into_parts();
1162
1163 let QueryStatement::Promql(eval_stmt, _) = &stmt else {
1164 unreachable!("query is parsed from promql");
1165 };
1166
1167 let plan = self
1168 .statement_executor
1169 .plan(&stmt, query_ctx.clone())
1170 .await
1171 .map_err(BoxedError::new)
1172 .context(ExecuteQuerySnafu)?;
1173
1174 interceptor.pre_execute(&query, &eval_stmt.expr, Some(&plan), query_ctx.clone())?;
1175
1176 let query_statement = if let QueryStatement::Promql(eval_stmt, alias) = stmt {
1178 CatalogQueryStatement::Promql(eval_stmt, alias)
1179 } else {
1180 return UnexpectedResultSnafu {
1182 reason: "The query should always be promql.".to_string(),
1183 }
1184 .fail();
1185 };
1186 let raw_query = query_statement.to_string();
1187
1188 let slow_query_timer = self
1189 .slow_query_options
1190 .enable
1191 .then(|| self.event_recorder.clone())
1192 .flatten()
1193 .map(|event_recorder| {
1194 SlowQueryTimer::new(
1195 query_statement,
1196 query_ctx.current_schema(),
1197 self.slow_query_options.threshold,
1198 self.slow_query_options.sample_ratio,
1199 self.slow_query_options.record_type,
1200 event_recorder,
1201 )
1202 });
1203
1204 let ticket = self.process_manager.register_query(
1205 query_ctx.current_catalog().to_string(),
1206 vec![query_ctx.current_schema()],
1207 raw_query,
1208 query_ctx.conn_info().to_string(),
1209 Some(query_ctx.process_id()),
1210 slow_query_timer,
1211 );
1212
1213 let query_fut = self.statement_executor.exec_plan(plan, query_ctx.clone());
1214
1215 let output = CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
1216 .await
1217 .map_err(|_| servers::error::CancelledSnafu.build())?
1218 .map_err(BoxedError::new)
1219 .context(ExecuteQuerySnafu)?;
1220 let output = map_query_output(output)
1221 .map_err(BoxedError::new)
1222 .context(ExecuteQuerySnafu)?;
1223 let Output { meta, data } = output;
1224 let data = match data {
1225 OutputData::Stream(stream) => {
1226 OutputData::Stream(Box::pin(CancellableStreamWrapper::new(stream, ticket)))
1227 }
1228 other => other,
1229 };
1230 let output = Output { data, meta };
1231 Ok(interceptor.post_execute(output, query_ctx)?)
1232 }
1233
1234 async fn check_query_permission(
1235 &self,
1236 queries: &[PromQuery],
1237 query_ctx: &QueryContextRef,
1238 ) -> server_error::Result<()> {
1239 let queries = queries
1240 .iter()
1241 .cloned()
1242 .map(|query| ParsedPromQuery::parse(query, query_ctx))
1243 .collect::<server_error::Result<Vec<_>>>()?;
1244 self.check_query_permission_parsed(&queries, query_ctx)
1245 .await
1246 }
1247
1248 async fn check_query_permission_parsed(
1249 &self,
1250 queries: &[ParsedPromQuery],
1251 query_ctx: &QueryContextRef,
1252 ) -> server_error::Result<()> {
1253 self.check_prom_query_privilege(query_ctx)?;
1254 let targets = self.prom_queries_permission_targets(queries, query_ctx)?;
1255 self.check_query_target_permission(targets, query_ctx).await
1256 }
1257
1258 async fn check_query_target_permission(
1259 &self,
1260 targets: PermissionTableTargets,
1261 query_ctx: &QueryContextRef,
1262 ) -> server_error::Result<()> {
1263 let targets = self
1264 .resolve_query_permission_targets(targets, query_ctx)
1265 .await?;
1266 self.check_table_permission(query_ctx, PermissionReq::PromQuery, targets)
1267 .context(AuthSnafu)?;
1268 Ok(())
1269 }
1270
1271 async fn filter_metadata_metric_names(
1272 &self,
1273 metric_names: Vec<String>,
1274 schema: &str,
1275 query_ctx: &QueryContextRef,
1276 ) -> server_error::Result<Vec<String>> {
1277 let checker = self.plugins.get::<PermissionCheckerRef>();
1278 if !checker.as_ref().uses_table_targets() {
1279 let Some(metric) = metric_names.first() else {
1280 return Ok(metric_names);
1281 };
1282 let target =
1283 PermissionTableTarget::new(query_ctx.current_catalog(), schema, metric.as_str());
1284 let result = checker
1285 .as_ref()
1286 .check_permission_with_table_targets(
1287 query_ctx.current_user(),
1288 PermissionReq::PromQuery,
1289 PermissionTableTargets::resolved(vec![target]),
1290 )
1291 .context(AuthSnafu);
1292 return match result {
1293 Ok(_) => Ok(metric_names),
1294 Err(error)
1295 if error.status_code()
1296 == common_error::status_code::StatusCode::PermissionDenied =>
1297 {
1298 Ok(Vec::new())
1299 }
1300 Err(error) => Err(error),
1301 };
1302 }
1303
1304 let mut allowed = Vec::with_capacity(metric_names.len());
1305 for metric in metric_names {
1306 let target =
1307 PermissionTableTarget::new(query_ctx.current_catalog(), schema, metric.as_str());
1308 match checker
1309 .as_ref()
1310 .check_permission_with_table_targets(
1311 query_ctx.current_user(),
1312 PermissionReq::PromQuery,
1313 PermissionTableTargets::resolved(vec![target]),
1314 )
1315 .context(AuthSnafu)
1316 {
1317 Ok(_) => allowed.push(metric),
1318 Err(error)
1319 if error.status_code()
1320 == common_error::status_code::StatusCode::PermissionDenied => {}
1321 Err(error) => return Err(error),
1322 }
1323 }
1324 Ok(allowed)
1325 }
1326
1327 async fn query_metric_names(
1328 &self,
1329 matchers: Vec<Matcher>,
1330 schema: &str,
1331 ctx: &QueryContextRef,
1332 ) -> server_error::Result<Vec<String>> {
1333 self.handle_query_metric_names(matchers, schema, ctx)
1334 .await
1335 .map_err(BoxedError::new)
1336 .context(ExecuteQuerySnafu)
1337 }
1338
1339 async fn query_label_values(
1340 &self,
1341 metric: String,
1342 label_name: String,
1343 matchers: Vec<Matcher>,
1344 start: SystemTime,
1345 end: SystemTime,
1346 ctx: &QueryContextRef,
1347 ) -> server_error::Result<Vec<String>> {
1348 let schema =
1349 resolve_schema_from_matchers(&matchers)?.unwrap_or_else(|| ctx.current_schema());
1350 let target = PermissionTableTarget::new(ctx.current_catalog(), schema.as_str(), &metric);
1351 self.check_query_target_permission(
1352 PermissionTableTargets::resolved(vec![target.clone()]),
1353 ctx,
1354 )
1355 .await?;
1356
1357 self.handle_query_label_values(target, label_name, matchers, start, end, ctx)
1358 .await
1359 .map_err(BoxedError::new)
1360 .context(ExecuteQuerySnafu)
1361 }
1362
1363 fn catalog_manager(&self) -> CatalogManagerRef {
1364 self.catalog_manager.clone()
1365 }
1366}
1367
1368macro_rules! validate_db_permission {
1370 ($stmt: expr, $query_ctx: expr) => {
1371 if let Some(database) = &$stmt.database {
1372 validate_catalog_and_schema($query_ctx.current_catalog(), database, $query_ctx)
1373 .map_err(BoxedError::new)
1374 .context(SqlExecInterceptedSnafu)?;
1375 }
1376 };
1377}
1378
1379pub fn check_permission(
1380 plugins: Plugins,
1381 stmt: &Statement,
1382 query_ctx: &QueryContextRef,
1383) -> Result<()> {
1384 let need_validate = plugins
1385 .get::<QueryOptions>()
1386 .map(|opts| opts.disallow_cross_catalog_query)
1387 .unwrap_or_default();
1388
1389 if !need_validate {
1390 return Ok(());
1391 }
1392
1393 match stmt {
1394 Statement::Admin(_) => {}
1397 Statement::Query(_)
1399 | Statement::Explain(_)
1400 | Statement::Tql(_)
1401 | Statement::Delete(_)
1402 | Statement::DeclareCursor(_)
1403 | Statement::Copy(sql::statements::copy::Copy::CopyQueryTo(_)) => {}
1404 Statement::CreateDatabase(_)
1406 | Statement::ShowDatabases(_)
1407 | Statement::DropDatabase(_)
1408 | Statement::AlterDatabase(_)
1409 | Statement::DropFlow(_)
1410 | Statement::Use(_) => {}
1411 #[cfg(feature = "enterprise")]
1412 Statement::DropTrigger(_) => {}
1413 Statement::ShowCreateDatabase(stmt) => {
1414 validate_database(&stmt.database_name, query_ctx)?;
1415 }
1416 Statement::ShowCreateTable(stmt) => {
1417 validate_param(&stmt.table_name, query_ctx)?;
1418 }
1419 Statement::ShowCreateFlow(stmt) => {
1420 validate_flow(&stmt.flow_name, query_ctx)?;
1421 }
1422 #[cfg(feature = "enterprise")]
1423 Statement::ShowCreateTrigger(stmt) => {
1424 validate_param(&stmt.trigger_name, query_ctx)?;
1425 }
1426 Statement::ShowCreateView(stmt) => {
1427 validate_param(&stmt.view_name, query_ctx)?;
1428 }
1429 Statement::CreateExternalTable(stmt) => {
1430 validate_param(&stmt.name, query_ctx)?;
1431 }
1432 Statement::CreateFlow(stmt) => {
1433 validate_param(&stmt.sink_table_name, query_ctx)?;
1435 }
1436 #[cfg(feature = "enterprise")]
1437 Statement::CreateTrigger(stmt) => {
1438 validate_param(&stmt.trigger_name, query_ctx)?;
1439 }
1440 Statement::CreateView(stmt) => {
1441 validate_param(&stmt.name, query_ctx)?;
1442 }
1443 Statement::AlterTable(stmt) => {
1444 validate_param(stmt.table_name(), query_ctx)?;
1445 }
1446 #[cfg(feature = "enterprise")]
1447 Statement::AlterTrigger(_) => {}
1448 Statement::SetVariables(_) | Statement::ShowVariables(_) => {}
1450 Statement::ShowCharset(_) | Statement::ShowCollation(_) => {}
1452
1453 Statement::Comment(comment) => match &comment.object {
1454 CommentObject::Table(table) => validate_param(table, query_ctx)?,
1455 CommentObject::Column { table, .. } => validate_param(table, query_ctx)?,
1456 CommentObject::Flow(flow) => validate_flow(flow, query_ctx)?,
1457 },
1458
1459 Statement::Insert(insert) => {
1460 let name = insert.table_name().context(ParseSqlSnafu)?;
1461 validate_param(name, query_ctx)?;
1462 }
1463 Statement::CreateTable(stmt) => {
1464 validate_param(&stmt.name, query_ctx)?;
1465 }
1466 Statement::CreateTableLike(stmt) => {
1467 validate_param(&stmt.table_name, query_ctx)?;
1468 validate_param(&stmt.source_name, query_ctx)?;
1469 }
1470 Statement::DropTable(drop_stmt) => {
1471 for table_name in drop_stmt.table_names() {
1472 validate_param(table_name, query_ctx)?;
1473 }
1474 }
1475 Statement::UndropTable(stmt) => {
1476 validate_param(stmt.table_name(), query_ctx)?;
1477 }
1478 Statement::DropView(stmt) => {
1479 validate_param(&stmt.view_name, query_ctx)?;
1480 }
1481 Statement::ShowTables(stmt) => {
1482 validate_db_permission!(stmt, query_ctx);
1483 }
1484 Statement::ShowTableStatus(stmt) => {
1485 validate_db_permission!(stmt, query_ctx);
1486 }
1487 Statement::ShowColumns(stmt) => {
1488 validate_db_permission!(stmt, query_ctx);
1489 }
1490 Statement::ShowIndex(stmt) => {
1491 validate_db_permission!(stmt, query_ctx);
1492 }
1493 Statement::ShowRegion(stmt) => {
1494 validate_db_permission!(stmt, query_ctx);
1495 }
1496 Statement::ShowViews(stmt) => {
1497 validate_db_permission!(stmt, query_ctx);
1498 }
1499 Statement::ShowFlows(stmt) => {
1500 validate_db_permission!(stmt, query_ctx);
1501 }
1502 #[cfg(feature = "enterprise")]
1503 Statement::ShowTriggers(_stmt) => {
1504 }
1507 Statement::ShowStatus(_stmt) => {}
1508 Statement::ShowSearchPath(_stmt) => {}
1509 Statement::DescribeTable(stmt) => {
1510 validate_param(stmt.name(), query_ctx)?;
1511 }
1512 Statement::Copy(sql::statements::copy::Copy::CopyTable(stmt)) => match stmt {
1513 CopyTable::To(copy_table_to) => validate_param(©_table_to.table_name, query_ctx)?,
1514 CopyTable::From(copy_table_from) => {
1515 validate_param(©_table_from.table_name, query_ctx)?
1516 }
1517 },
1518 Statement::Copy(sql::statements::copy::Copy::CopyDatabase(copy_database)) => {
1519 match copy_database {
1520 CopyDatabase::To(stmt) => validate_database(&stmt.database_name, query_ctx)?,
1521 CopyDatabase::From(stmt) => validate_database(&stmt.database_name, query_ctx)?,
1522 }
1523 }
1524 Statement::TruncateTable(stmt) => {
1525 validate_param(stmt.table_name(), query_ctx)?;
1526 }
1527 Statement::FetchCursor(_) | Statement::CloseCursor(_) => {}
1529 Statement::Kill(_) => {}
1531 Statement::ShowProcesslist(_) => {}
1533 }
1534 Ok(())
1535}
1536
1537fn validate_param(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1538 let (catalog, schema, _) = table_idents_to_full_name(name, query_ctx)
1539 .map_err(BoxedError::new)
1540 .context(ExternalSnafu)?;
1541
1542 validate_catalog_and_schema(&catalog, &schema, query_ctx)
1543 .map_err(BoxedError::new)
1544 .context(SqlExecInterceptedSnafu)
1545}
1546
1547fn validate_flow(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1548 let catalog = match &name.0[..] {
1549 [_flow] => query_ctx.current_catalog().to_string(),
1550 [catalog, _flow] => catalog.to_string_unquoted(),
1551 _ => {
1552 return InvalidSqlSnafu {
1553 err_msg: format!(
1554 "expect flow name to be <catalog>.<flow_name> or <flow_name>, actual: {name}",
1555 ),
1556 }
1557 .fail();
1558 }
1559 };
1560
1561 let schema = query_ctx.current_schema();
1562
1563 validate_catalog_and_schema(&catalog, &schema, query_ctx)
1564 .map_err(BoxedError::new)
1565 .context(SqlExecInterceptedSnafu)
1566}
1567
1568fn validate_database(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1569 let (catalog, schema) = match &name.0[..] {
1570 [schema] => (
1571 query_ctx.current_catalog().to_string(),
1572 schema.to_string_unquoted(),
1573 ),
1574 [catalog, schema] => (catalog.to_string_unquoted(), schema.to_string_unquoted()),
1575 _ => InvalidSqlSnafu {
1576 err_msg: format!(
1577 "expect database name to be <catalog>.<schema> or <schema>, actual: {name}",
1578 ),
1579 }
1580 .fail()?,
1581 };
1582
1583 validate_catalog_and_schema(&catalog, &schema, query_ctx)
1584 .map_err(BoxedError::new)
1585 .context(SqlExecInterceptedSnafu)
1586}
1587
1588fn is_readonly_plan(plan: &LogicalPlan) -> bool {
1589 !matches!(plan, LogicalPlan::Dml(_) | LogicalPlan::Ddl(_))
1590}
1591
1592fn should_track_statement_process(stmt: &Statement) -> bool {
1593 stmt.is_readonly()
1594 || matches!(stmt, Statement::Insert(insert) if insert.has_non_values_query_source())
1595}
1596
1597fn should_track_plan_process(stmt: Option<&Statement>, plan: &LogicalPlan) -> bool {
1598 is_readonly_plan(plan)
1599 || matches!(stmt, Some(Statement::Insert(insert)) if insert.has_non_values_query_source())
1600}
1601
1602#[cfg(test)]
1603mod tests {
1604 use std::collections::HashMap;
1605 use std::future::Future;
1606 use std::pin::Pin;
1607 use std::sync::Arc;
1608 use std::task::{Context, Poll};
1609 use std::time::Duration;
1610
1611 use api::prom_store::remote::label_matcher::Type as PromMatcherType;
1612 use api::prom_store::remote::{LabelMatcher, Query as RemoteQuery, ReadRequest};
1613 use api::v1::meta::{ProcedureDetailResponse, ReconcileRequest, ReconcileResponse};
1614 use auth::{PermissionResp, UserInfoRef};
1615 use catalog::process_manager::ProcessManager;
1616 use common_base::Plugins;
1617 use common_error::ext::{BoxedError, PlainError};
1618 use common_error::status_code::StatusCode;
1619 use common_meta::cache::LayeredCacheRegistryBuilder;
1620 use common_meta::kv_backend::memory::MemoryKvBackend;
1621 use common_meta::procedure_executor::{ExecutorContext, ProcedureExecutor};
1622 use common_meta::rpc::ddl::{SubmitDdlTaskRequest, SubmitDdlTaskResponse};
1623 use common_meta::rpc::procedure::{
1624 MigrateRegionRequest, MigrateRegionResponse, ProcedureStateResponse,
1625 };
1626 use common_query::Output;
1627 use common_recordbatch::{
1628 OrderOption, RecordBatch, RecordBatchStream, SendableRecordBatchStream,
1629 };
1630 use datafusion::arrow::datatypes::{DataType, Field, Schema, SchemaRef};
1631 use datafusion_expr::dml::InsertOp;
1632 use datafusion_expr::{LogicalPlanBuilder, LogicalTableSource};
1633 use datatypes::prelude::ConcreteDataType;
1634 use datatypes::schema::{ColumnSchema, Schema as GtSchema, SchemaRef as GtSchemaRef};
1635 use datatypes::vectors::{StringVector, VectorRef};
1636 use log_query::LogQuery;
1637 use query::query_engine::options::QueryOptions;
1638 use servers::query_handler::{LogQueryHandler, PromStoreProtocolHandler};
1639 use session::context::{Channel, ConnInfo, QueryContext, QueryContextBuilder};
1640 use snafu::{Location, Snafu};
1641 use sql::dialect::GreptimeDbDialect;
1642 use store_api::data_source::DataSource;
1643 use store_api::metric_engine_consts::{
1644 LOGICAL_TABLE_METADATA_KEY, METRIC_ENGINE_NAME, PHYSICAL_TABLE_METADATA_KEY,
1645 };
1646 use store_api::storage::ScanRequest;
1647 use strfmt::Format;
1648 use table::metadata::{FilterPushDownType, TableInfo, TableInfoBuilder, TableMetaBuilder};
1649 use table::table_name::TableName;
1650 use table::test_util::{EmptyTable, MemTable};
1651 use table::{Table, TableRef};
1652 use tokio::sync::{mpsc, oneshot};
1653
1654 use super::*;
1655 use crate::frontend::FrontendOptions;
1656 use crate::instance::builder::FrontendBuilder;
1657
1658 fn parse_test_sql(sql: &str) -> Vec<Statement> {
1659 parse_stmt(sql, &GreptimeDbDialect {}).unwrap()
1660 }
1661
1662 #[test]
1663 fn test_validate_analyze_stream_statement_strictness() {
1664 for sql in [
1665 "select 1",
1666 "explain analyze select 1",
1667 "explain analyze verbose format text select 1",
1668 "explain analyze verbose format graphviz select 1",
1669 ] {
1670 let mut stmts = parse_test_sql(sql);
1671 assert!(
1672 validate_analyze_stream_statement(&mut stmts[0]).is_err(),
1673 "{sql}"
1674 );
1675 }
1676
1677 for sql in [
1678 "explain analyze verbose select 1",
1679 "explain analyze verbose format json select 1",
1680 ] {
1681 let mut stmts = parse_test_sql(sql);
1682 assert!(
1683 validate_analyze_stream_statement(&mut stmts[0]).is_ok(),
1684 "{sql}"
1685 );
1686 let Statement::Explain(explain) = &stmts[0] else {
1687 unreachable!();
1688 };
1689 assert!(explain.format.is_none());
1690 }
1691
1692 assert_eq!(
1693 parse_test_sql("explain analyze verbose select 1; select 2").len(),
1694 2
1695 );
1696 }
1697
1698 #[derive(Debug, Snafu)]
1699 enum TestError {
1700 #[snafu(display("Failed to build test cache registry"))]
1701 BuildCacheRegistry {
1702 source: cache::error::Error,
1703 #[snafu(implicit)]
1704 location: Location,
1705 },
1706
1707 #[snafu(display("Failed to build test table meta for table: {table_name}"))]
1708 BuildTableMeta {
1709 table_name: String,
1710 source: table::metadata::TableMetaBuilderError,
1711 #[snafu(implicit)]
1712 location: Location,
1713 },
1714
1715 #[snafu(display("Failed to build test table info for table: {table_name}"))]
1716 BuildTableInfo {
1717 table_name: String,
1718 source: table::metadata::TableInfoBuilderError,
1719 #[snafu(implicit)]
1720 location: Location,
1721 },
1722
1723 #[snafu(display("Failed to register test table: {table_name}"))]
1724 RegisterTable {
1725 table_name: String,
1726 source: catalog::error::Error,
1727 #[snafu(implicit)]
1728 location: Location,
1729 },
1730
1731 #[snafu(display("Failed to build test frontend instance"))]
1732 BuildFrontend {
1733 source: crate::error::Error,
1734 #[snafu(implicit)]
1735 location: Location,
1736 },
1737
1738 #[snafu(display("Expected exactly one output for SQL `{sql}`, got {actual}"))]
1739 UnexpectedOutputCount {
1740 sql: String,
1741 actual: usize,
1742 #[snafu(implicit)]
1743 location: Location,
1744 },
1745
1746 #[snafu(display("Failed to execute SQL `{sql}`"))]
1747 ExecuteSql {
1748 sql: String,
1749 source: crate::error::Error,
1750 #[snafu(implicit)]
1751 location: Location,
1752 },
1753
1754 #[snafu(display("Timed out waiting for insert-select start notification"))]
1755 InsertStartTimeout {
1756 source: tokio::time::error::Elapsed,
1757 #[snafu(implicit)]
1758 location: Location,
1759 },
1760
1761 #[snafu(display("Insert-select start notification channel closed"))]
1762 InsertStartChannelClosed {
1763 #[snafu(implicit)]
1764 location: Location,
1765 },
1766
1767 #[snafu(display("Failed to release blocking insert-select interceptor"))]
1768 ReleaseBlockedInsert {
1769 #[snafu(implicit)]
1770 location: Location,
1771 },
1772
1773 #[snafu(display("Timed out waiting for insert-select source to be polled"))]
1774 SourcePollTimeout {
1775 source: tokio::time::error::Elapsed,
1776 #[snafu(implicit)]
1777 location: Location,
1778 },
1779
1780 #[snafu(display("Insert-select source poll notification channel closed"))]
1781 SourcePollChannelClosed {
1782 source: oneshot::error::RecvError,
1783 #[snafu(implicit)]
1784 location: Location,
1785 },
1786
1787 #[snafu(display("Timed out waiting for insert task to finish"))]
1788 InsertTaskTimeout {
1789 source: tokio::time::error::Elapsed,
1790 #[snafu(implicit)]
1791 location: Location,
1792 },
1793
1794 #[snafu(display("Insert task panicked"))]
1795 InsertTaskPanic {
1796 source: tokio::task::JoinError,
1797 #[snafu(implicit)]
1798 location: Location,
1799 },
1800
1801 #[snafu(display("Expected insert-select to be cancelled"))]
1802 InsertSelectNotCancelled {
1803 #[snafu(implicit)]
1804 location: Location,
1805 },
1806 }
1807
1808 type TestResult<T> = std::result::Result<T, TestError>;
1809
1810 fn parse_one_sql(sql: &str) -> Statement {
1811 parse_stmt(sql, &GreptimeDbDialect {}).unwrap().remove(0)
1812 }
1813
1814 fn test_query_ctx(process_id: u32) -> QueryContextRef {
1815 Arc::new(
1816 QueryContextBuilder::default()
1817 .channel(Channel::Mysql)
1818 .conn_info(ConnInfo::new(None, Channel::Mysql))
1819 .process_id(process_id)
1820 .build(),
1821 )
1822 }
1823
1824 struct RejectUnresolvedPermissionChecker;
1825
1826 impl PermissionChecker for RejectUnresolvedPermissionChecker {
1827 fn check_permission(
1828 &self,
1829 _user_info: UserInfoRef,
1830 _req: PermissionReq,
1831 ) -> auth::error::Result<PermissionResp> {
1832 Ok(PermissionResp::Allow)
1833 }
1834
1835 fn check_permission_with_table_targets(
1836 &self,
1837 _user_info: UserInfoRef,
1838 _req: PermissionReq,
1839 targets: PermissionTableTargets,
1840 ) -> auth::error::Result<PermissionResp> {
1841 let reject = match targets {
1842 PermissionTableTargets::Unresolved => true,
1843 PermissionTableTargets::Resolved(targets) => {
1844 targets.iter().any(|target| target.table == "denied")
1845 }
1846 };
1847 Ok(if reject {
1848 PermissionResp::Reject
1849 } else {
1850 PermissionResp::Allow
1851 })
1852 }
1853 }
1854
1855 #[derive(Default)]
1856 struct TargetIndependentPermissionChecker {
1857 checks: atomic::AtomicUsize,
1858 }
1859
1860 impl PermissionChecker for TargetIndependentPermissionChecker {
1861 fn check_permission(
1862 &self,
1863 _user_info: UserInfoRef,
1864 _req: PermissionReq,
1865 ) -> auth::error::Result<PermissionResp> {
1866 self.checks.fetch_add(1, atomic::Ordering::Relaxed);
1867 Ok(PermissionResp::Allow)
1868 }
1869
1870 fn uses_table_targets(&self) -> bool {
1871 false
1872 }
1873
1874 fn check_permission_with_table_targets(
1875 &self,
1876 user_info: UserInfoRef,
1877 req: PermissionReq,
1878 _targets: PermissionTableTargets,
1879 ) -> auth::error::Result<PermissionResp> {
1880 self.check_permission(user_info, req)
1881 }
1882 }
1883
1884 struct BlockingInsertSelectInterceptor {
1885 started_tx: mpsc::UnboundedSender<()>,
1886 finish_rx: std::sync::Mutex<Option<oneshot::Receiver<()>>>,
1887 }
1888
1889 impl BlockingInsertSelectInterceptor {
1890 fn new(started_tx: mpsc::UnboundedSender<()>, finish_rx: oneshot::Receiver<()>) -> Self {
1891 Self {
1892 started_tx,
1893 finish_rx: std::sync::Mutex::new(Some(finish_rx)),
1894 }
1895 }
1896 }
1897
1898 impl SqlQueryInterceptor for BlockingInsertSelectInterceptor {
1899 type Error = Error;
1900
1901 fn pre_execute(
1902 &self,
1903 statement: Option<&Statement>,
1904 _plan: Option<&LogicalPlan>,
1905 _query_ctx: QueryContextRef,
1906 ) -> Result<()> {
1907 let Some(Statement::Insert(insert)) = statement else {
1908 return Ok(());
1909 };
1910 if !insert.has_non_values_query_source() {
1911 return Ok(());
1912 }
1913
1914 let finish_rx = self.finish_rx.lock().unwrap().take().unwrap();
1915 let _ = self.started_tx.send(());
1916 tokio::task::block_in_place(|| {
1917 tokio::runtime::Handle::current()
1918 .block_on(finish_rx)
1919 .unwrap();
1920 });
1921 Ok(())
1922 }
1923 }
1924
1925 struct PendingRecordBatchStream {
1926 schema: GtSchemaRef,
1927 polled_tx: Option<oneshot::Sender<()>>,
1928 _finish_tx: oneshot::Sender<()>,
1929 finish_rx: Pin<Box<oneshot::Receiver<()>>>,
1930 }
1931
1932 impl RecordBatchStream for PendingRecordBatchStream {
1933 fn schema(&self) -> GtSchemaRef {
1934 self.schema.clone()
1935 }
1936
1937 fn output_ordering(&self) -> Option<&[OrderOption]> {
1938 None
1939 }
1940
1941 fn metrics(&self) -> Option<common_recordbatch::adapter::RecordBatchMetrics> {
1942 None
1943 }
1944 }
1945
1946 impl Stream for PendingRecordBatchStream {
1947 type Item = common_recordbatch::error::Result<RecordBatch>;
1948
1949 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1950 if let Some(polled_tx) = self.polled_tx.take() {
1951 let _ = polled_tx.send(());
1952 }
1953
1954 match self.finish_rx.as_mut().poll(cx) {
1955 Poll::Ready(_) => Poll::Ready(None),
1956 Poll::Pending => Poll::Pending,
1957 }
1958 }
1959 }
1960
1961 impl Unpin for PendingRecordBatchStream {}
1962
1963 struct PendingDataSource {
1964 schema: GtSchemaRef,
1965 polled_tx: std::sync::Mutex<Option<oneshot::Sender<()>>>,
1966 }
1967
1968 impl DataSource for PendingDataSource {
1969 fn get_stream(
1970 &self,
1971 _request: ScanRequest,
1972 ) -> std::result::Result<SendableRecordBatchStream, BoxedError> {
1973 let (finish_tx, finish_rx) = oneshot::channel();
1974 let mut polled_tx = self.polled_tx.lock().map_err(|_| {
1975 BoxedError::new(PlainError::new(
1976 "pending data source lock poisoned".to_string(),
1977 StatusCode::Unexpected,
1978 ))
1979 })?;
1980 Ok(Box::pin(PendingRecordBatchStream {
1981 schema: self.schema.clone(),
1982 polled_tx: polled_tx.take(),
1983 _finish_tx: finish_tx,
1984 finish_rx: Box::pin(finish_rx),
1985 }))
1986 }
1987 }
1988
1989 struct NoopProcedureExecutor;
1990
1991 #[async_trait::async_trait]
1992 impl ProcedureExecutor for NoopProcedureExecutor {
1993 async fn submit_ddl_task(
1994 &self,
1995 _ctx: &ExecutorContext,
1996 _request: SubmitDdlTaskRequest,
1997 ) -> common_meta::error::Result<SubmitDdlTaskResponse> {
1998 common_meta::error::UnsupportedSnafu {
1999 operation: "submit_ddl_task",
2000 }
2001 .fail()
2002 }
2003
2004 async fn migrate_region(
2005 &self,
2006 _ctx: &ExecutorContext,
2007 _request: MigrateRegionRequest,
2008 ) -> common_meta::error::Result<MigrateRegionResponse> {
2009 common_meta::error::UnsupportedSnafu {
2010 operation: "migrate_region",
2011 }
2012 .fail()
2013 }
2014
2015 async fn reconcile(
2016 &self,
2017 _ctx: &ExecutorContext,
2018 _request: ReconcileRequest,
2019 ) -> common_meta::error::Result<ReconcileResponse> {
2020 common_meta::error::UnsupportedSnafu {
2021 operation: "reconcile",
2022 }
2023 .fail()
2024 }
2025
2026 async fn query_procedure_state(
2027 &self,
2028 _ctx: &ExecutorContext,
2029 _pid: &str,
2030 ) -> common_meta::error::Result<ProcedureStateResponse> {
2031 common_meta::error::UnsupportedSnafu {
2032 operation: "query_procedure_state",
2033 }
2034 .fail()
2035 }
2036
2037 async fn list_procedures(
2038 &self,
2039 _ctx: &ExecutorContext,
2040 ) -> common_meta::error::Result<ProcedureDetailResponse> {
2041 common_meta::error::UnsupportedSnafu {
2042 operation: "list_procedures",
2043 }
2044 .fail()
2045 }
2046 }
2047
2048 fn test_cache_registry(
2049 kv_backend: common_meta::kv_backend::KvBackendRef,
2050 ) -> TestResult<common_meta::cache::LayeredCacheRegistryRef> {
2051 Ok(Arc::new(
2052 cache::with_default_composite_cache_registry(
2053 LayeredCacheRegistryBuilder::default()
2054 .add_cache_registry(cache::build_fundamental_cache_registry(kv_backend)),
2055 )
2056 .context(BuildCacheRegistrySnafu)?
2057 .build(),
2058 ))
2059 }
2060
2061 fn test_table_info(table_id: u32, table_name: &str) -> TestResult<TableInfo> {
2062 let schema = Arc::new(GtSchema::new(vec![
2063 ColumnSchema::new("id", ConcreteDataType::int32_datatype(), false),
2064 ColumnSchema::new(
2065 "ts",
2066 ConcreteDataType::timestamp_millisecond_datatype(),
2067 false,
2068 )
2069 .with_time_index(true),
2070 ]));
2071 let table_meta = TableMetaBuilder::empty()
2072 .schema(schema)
2073 .primary_key_indices(vec![0])
2074 .value_indices(vec![1])
2075 .next_column_id(1024)
2076 .build()
2077 .with_context(|_| BuildTableMetaSnafu {
2078 table_name: table_name.to_string(),
2079 })?;
2080
2081 TableInfoBuilder::new(table_name, table_meta)
2082 .table_id(table_id)
2083 .build()
2084 .with_context(|_| BuildTableInfoSnafu {
2085 table_name: table_name.to_string(),
2086 })
2087 }
2088
2089 fn test_table(table_id: u32, table_name: &str) -> TestResult<table::TableRef> {
2090 let table_info = test_table_info(table_id, table_name)?;
2091 Ok(EmptyTable::from_table_info(&table_info))
2092 }
2093
2094 fn test_physical_table(table_id: u32, table_name: &str) -> TestResult<table::TableRef> {
2095 let mut table_info = test_table_info(table_id, table_name)?;
2096 table_info
2097 .meta
2098 .options
2099 .extra_options
2100 .insert(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new());
2101 Ok(EmptyTable::from_table_info(&table_info))
2102 }
2103
2104 fn test_logical_table(table_id: u32, table_name: &str) -> TestResult<table::TableRef> {
2105 let mut table_info = test_table_info(table_id, table_name)?;
2106 table_info.meta.engine = METRIC_ENGINE_NAME.to_string();
2107 table_info.meta.options.extra_options.insert(
2108 LOGICAL_TABLE_METADATA_KEY.to_string(),
2109 "physical_metric".to_string(),
2110 );
2111 Ok(EmptyTable::from_table_info(&table_info))
2112 }
2113
2114 fn test_metric_names_table() -> TableRef {
2115 let schema = Arc::new(GtSchema::new(vec![
2116 ColumnSchema::new("table_catalog", ConcreteDataType::string_datatype(), false),
2117 ColumnSchema::new("table_schema", ConcreteDataType::string_datatype(), false),
2118 ColumnSchema::new("table_name", ConcreteDataType::string_datatype(), false),
2119 ColumnSchema::new("engine", ConcreteDataType::string_datatype(), false),
2120 ColumnSchema::new("create_options", ConcreteDataType::string_datatype(), false),
2121 ]));
2122 let columns: Vec<VectorRef> = vec![
2123 Arc::new(StringVector::from(vec!["greptime", "greptime"])),
2124 Arc::new(StringVector::from(vec!["public", "public"])),
2125 Arc::new(StringVector::from(vec!["denied", "target"])),
2126 Arc::new(StringVector::from(vec!["metric", "metric"])),
2127 Arc::new(StringVector::from(vec![
2128 "on_physical_table=physical_metric",
2129 "on_physical_table=physical_metric",
2130 ])),
2131 ];
2132 let record_batch = RecordBatch::new(schema, columns).unwrap();
2133 MemTable::new_with_catalog(
2134 "tables",
2135 record_batch,
2136 2048,
2137 "greptime".to_string(),
2138 "information_schema".to_string(),
2139 )
2140 }
2141
2142 fn pending_table(
2143 table_id: u32,
2144 table_name: &str,
2145 polled_tx: oneshot::Sender<()>,
2146 ) -> TestResult<table::TableRef> {
2147 let table_info = test_table_info(table_id, table_name)?;
2148 let data_source = Arc::new(PendingDataSource {
2149 schema: table_info.meta.schema.clone(),
2150 polled_tx: std::sync::Mutex::new(Some(polled_tx)),
2151 });
2152
2153 Ok(Arc::new(Table::new(
2154 Arc::new(table_info),
2155 FilterPushDownType::Unsupported,
2156 data_source,
2157 )))
2158 }
2159
2160 async fn test_instance_with_tables(
2161 source_table: TableRef,
2162 target_table: TableRef,
2163 ) -> TestResult<Instance> {
2164 test_instance_with_plugins(source_table, target_table, Plugins::new()).await
2165 }
2166
2167 async fn test_instance_with_insert_select_interceptor(
2168 interceptor: SqlQueryInterceptorRef<Error>,
2169 ) -> TestResult<Instance> {
2170 let plugins = Plugins::new();
2171 plugins.insert::<SqlQueryInterceptorRef<Error>>(interceptor);
2172
2173 test_instance_with_plugins(
2174 test_table(1024, "source")?,
2175 test_table(1025, "target")?,
2176 plugins,
2177 )
2178 .await
2179 }
2180
2181 async fn test_instance_with_plugins(
2182 source_table: TableRef,
2183 target_table: TableRef,
2184 plugins: Plugins,
2185 ) -> TestResult<Instance> {
2186 test_instance_with_plugins_and_metric_names(source_table, target_table, plugins, None).await
2187 }
2188
2189 async fn test_instance_with_plugins_and_metric_names(
2190 source_table: TableRef,
2191 target_table: TableRef,
2192 plugins: Plugins,
2193 metric_names_table: Option<TableRef>,
2194 ) -> TestResult<Instance> {
2195 let kv_backend = Arc::new(MemoryKvBackend::new());
2196 let process_manager = Arc::new(ProcessManager::new("test-frontend".to_string(), None));
2197 let catalog_manager = catalog::memory::MemoryCatalogManager::new_with_table(source_table);
2198 let target_table_name = "target";
2199 catalog_manager
2200 .register_table_sync(catalog::RegisterTableRequest {
2201 catalog: "greptime".to_string(),
2202 schema: "public".to_string(),
2203 table_name: target_table_name.to_string(),
2204 table_id: 1025,
2205 table: target_table,
2206 })
2207 .with_context(|_| RegisterTableSnafu {
2208 table_name: target_table_name.to_string(),
2209 })?;
2210 if let Some(table) = metric_names_table {
2211 catalog_manager
2212 .deregister_table_sync(catalog::DeregisterTableRequest {
2213 catalog: "greptime".to_string(),
2214 schema: "information_schema".to_string(),
2215 table_name: "tables".to_string(),
2216 })
2217 .unwrap();
2218 catalog_manager
2219 .register_table_sync(catalog::RegisterTableRequest {
2220 catalog: "greptime".to_string(),
2221 schema: "information_schema".to_string(),
2222 table_name: "tables".to_string(),
2223 table_id: 2048,
2224 table,
2225 })
2226 .unwrap();
2227 }
2228 catalog_manager.register_process_list_table(process_manager.clone());
2229
2230 let cache_registry = test_cache_registry(kv_backend.clone())?;
2231
2232 FrontendBuilder::new(
2233 FrontendOptions::default(),
2234 kv_backend,
2235 cache_registry,
2236 catalog_manager,
2237 Arc::new(client::client_manager::NodeClients::default()),
2238 Arc::new(NoopProcedureExecutor),
2239 process_manager,
2240 )
2241 .with_plugin(plugins)
2242 .try_build()
2243 .await
2244 .context(BuildFrontendSnafu)
2245 }
2246
2247 async fn execute_one_sql(
2248 instance: &Instance,
2249 sql: &str,
2250 query_ctx: QueryContextRef,
2251 ) -> TestResult<Output> {
2252 let mut results = instance.do_query_inner(sql, query_ctx).await;
2253 ensure!(
2254 results.len() == 1,
2255 UnexpectedOutputCountSnafu {
2256 sql: sql.to_string(),
2257 actual: results.len(),
2258 }
2259 );
2260 results.remove(0).with_context(|_| ExecuteSqlSnafu {
2261 sql: sql.to_string(),
2262 })
2263 }
2264
2265 #[tokio::test]
2266 async fn test_target_independent_checker_skips_target_resolution() -> TestResult<()> {
2267 let physical_table = "physical_metric";
2268 let checker = Arc::new(TargetIndependentPermissionChecker::default());
2269 let plugins = Plugins::new();
2270 plugins.insert::<PermissionCheckerRef>(checker.clone());
2271 let instance = test_instance_with_plugins(
2272 test_physical_table(1024, physical_table)?,
2273 test_table(1025, "target")?,
2274 plugins,
2275 )
2276 .await?;
2277
2278 let ctx = test_query_ctx(1);
2279 let physical_target = PermissionTableTarget::new("greptime", "public", physical_table);
2280 assert_eq!(
2281 PermissionTableTargets::Resolved(vec![physical_target.clone()]),
2282 instance
2283 .resolve_query_permission_targets(
2284 PermissionTableTargets::resolved(vec![physical_target]),
2285 &ctx,
2286 )
2287 .await
2288 .unwrap()
2289 );
2290 assert_eq!(
2291 vec![physical_table.to_string(), "target".to_string()],
2292 PrometheusHandler::filter_metadata_metric_names(
2293 &instance,
2294 vec![physical_table.to_string(), "target".to_string()],
2295 "public",
2296 &ctx,
2297 )
2298 .await
2299 .unwrap()
2300 );
2301 assert_eq!(1, checker.checks.load(atomic::Ordering::Relaxed));
2302
2303 Ok(())
2304 }
2305
2306 #[tokio::test]
2307 async fn test_query_permission_targets_are_deduplicated() -> TestResult<()> {
2308 let plugins = Plugins::new();
2309 plugins.insert::<PermissionCheckerRef>(Arc::new(RejectUnresolvedPermissionChecker));
2310 let instance = test_instance_with_plugins(
2311 test_table(1024, "source")?,
2312 test_table(1025, "target")?,
2313 plugins,
2314 )
2315 .await?;
2316 let ctx = test_query_ctx(1);
2317 let target = PermissionTableTarget::new("greptime", "public", "target");
2318
2319 assert_eq!(
2320 PermissionTableTargets::Resolved(vec![target.clone()]),
2321 instance
2322 .resolve_query_permission_targets(
2323 PermissionTableTargets::resolved(vec![target.clone(), target]),
2324 &ctx,
2325 )
2326 .await
2327 .unwrap()
2328 );
2329
2330 Ok(())
2331 }
2332
2333 #[tokio::test]
2334 async fn test_physical_query_targets_fail_closed() -> TestResult<()> {
2335 let physical_table = "physical_metric";
2336 let plugins = Plugins::new();
2337 plugins.insert::<PermissionCheckerRef>(Arc::new(RejectUnresolvedPermissionChecker));
2338 let instance = test_instance_with_plugins(
2339 test_physical_table(1024, physical_table)?,
2340 test_table(1025, "target")?,
2341 plugins,
2342 )
2343 .await?;
2344
2345 let ctx = test_query_ctx(1);
2346 let logical_target = PermissionTableTarget::new("greptime", "public", "target");
2347 assert_eq!(
2348 PermissionTableTargets::Resolved(vec![logical_target.clone()]),
2349 instance
2350 .resolve_query_permission_targets(
2351 PermissionTableTargets::resolved(vec![logical_target.clone()]),
2352 &ctx,
2353 )
2354 .await
2355 .unwrap()
2356 );
2357 let physical_target = PermissionTableTarget::new("greptime", "public", physical_table);
2358 assert_eq!(
2359 PermissionTableTargets::Unresolved,
2360 instance
2361 .resolve_query_permission_targets(
2362 PermissionTableTargets::resolved(
2363 vec![logical_target, physical_target.clone(),]
2364 ),
2365 &ctx,
2366 )
2367 .await
2368 .unwrap()
2369 );
2370 assert_eq!(
2371 vec!["target".to_string()],
2372 PrometheusHandler::filter_metadata_metric_names(
2373 &instance,
2374 vec!["target".to_string(), "denied".to_string()],
2375 "public",
2376 &ctx,
2377 )
2378 .await
2379 .unwrap()
2380 );
2381
2382 let query = PromQuery {
2383 query: physical_table.to_string(),
2384 ..Default::default()
2385 };
2386 let err = PrometheusHandler::check_query_target_permission(
2387 &instance,
2388 PermissionTableTargets::resolved(vec![physical_target]),
2389 &ctx,
2390 )
2391 .await
2392 .unwrap_err();
2393 assert_eq!(StatusCode::PermissionDenied, err.status_code());
2394 let err = PrometheusHandler::check_query_permission(
2395 &instance,
2396 std::slice::from_ref(&query),
2397 &ctx,
2398 )
2399 .await
2400 .unwrap_err();
2401 assert_eq!(StatusCode::PermissionDenied, err.status_code());
2402 let err = PrometheusHandler::do_query(&instance, &query, ctx.clone())
2403 .await
2404 .unwrap_err();
2405 assert_eq!(StatusCode::PermissionDenied, err.status_code());
2406
2407 for sql in [
2408 "SELECT * FROM physical_metric",
2409 "TQL EVAL (0, 10, '5s') physical_metric",
2410 "INSERT INTO target SELECT * FROM physical_metric",
2411 ] {
2412 let mut results = instance.do_query_inner(sql, ctx.clone()).await;
2413 assert_eq!(1, results.len(), "{sql}");
2414 let err = results.remove(0).unwrap_err();
2415 assert_eq!(StatusCode::PermissionDenied, err.status_code(), "{sql}");
2416 }
2417 let err = LogQueryHandler::query(
2418 &instance,
2419 LogQuery {
2420 table: TableName::new("greptime", "public", physical_table),
2421 ..Default::default()
2422 },
2423 ctx.clone(),
2424 )
2425 .await
2426 .unwrap_err();
2427 assert_eq!(StatusCode::PermissionDenied, err.status_code());
2428 let err = instance
2429 .do_describe_inner(parse_one_sql("SELECT * FROM physical_metric"), ctx.clone())
2430 .await
2431 .unwrap_err();
2432 assert_eq!(StatusCode::PermissionDenied, err.status_code());
2433
2434 let request = ReadRequest {
2435 queries: vec![RemoteQuery {
2436 matchers: vec![LabelMatcher {
2437 r#type: PromMatcherType::Eq as i32,
2438 name: servers::prom_store::METRIC_NAME_LABEL.to_string(),
2439 value: physical_table.to_string(),
2440 }],
2441 ..Default::default()
2442 }],
2443 ..Default::default()
2444 };
2445 let Err(err) = PromStoreProtocolHandler::read(&instance, request, ctx.clone()).await else {
2446 panic!("physical remote-read target must be rejected");
2447 };
2448 assert_eq!(StatusCode::PermissionDenied, err.status_code());
2449
2450 let err = PrometheusHandler::query_label_values(
2451 &instance,
2452 physical_table.to_string(),
2453 "host".to_string(),
2454 vec![],
2455 SystemTime::UNIX_EPOCH,
2456 SystemTime::UNIX_EPOCH,
2457 &ctx,
2458 )
2459 .await
2460 .unwrap_err();
2461 assert_eq!(StatusCode::PermissionDenied, err.status_code());
2462
2463 Ok(())
2464 }
2465
2466 #[tokio::test]
2467 async fn test_non_exact_query_discovery_keeps_denied_targets_for_batch_check() -> TestResult<()>
2468 {
2469 let plugins = Plugins::new();
2470 plugins.insert::<PermissionCheckerRef>(Arc::new(RejectUnresolvedPermissionChecker));
2471 let instance = test_instance_with_plugins_and_metric_names(
2472 test_logical_table(1024, "denied")?,
2473 test_logical_table(1025, "target")?,
2474 plugins,
2475 Some(test_metric_names_table()),
2476 )
2477 .await?;
2478 let ctx = test_query_ctx(1);
2479
2480 let mut metric_names = PrometheusHandler::query_metric_names(
2481 &instance,
2482 vec![Matcher::new(
2483 promql_parser::label::MatchOp::NotEqual,
2484 "__name__",
2485 "",
2486 )],
2487 "public",
2488 &ctx,
2489 )
2490 .await
2491 .unwrap();
2492 metric_names.sort_unstable();
2493 assert_eq!(
2494 vec!["denied".to_string(), "target".to_string()],
2495 metric_names
2496 );
2497
2498 let queries = metric_names
2499 .into_iter()
2500 .map(|query| PromQuery {
2501 query,
2502 ..Default::default()
2503 })
2504 .collect::<Vec<_>>();
2505 let err = PrometheusHandler::check_query_permission(&instance, &queries, &ctx)
2506 .await
2507 .unwrap_err();
2508 assert_eq!(StatusCode::PermissionDenied, err.status_code());
2509
2510 Ok(())
2511 }
2512
2513 #[test]
2514 fn test_fast_legacy_check_is_read_only() {
2515 let cache = DashMap::new();
2516 cache.insert("metric1".to_string(), true);
2517
2518 let names = vec!["metric1".to_string(), "metric2".to_string()];
2519 assert_eq!(Some(true), fast_legacy_check(&cache, &names).unwrap());
2520 assert!(!cache.contains_key("metric2"));
2521
2522 cache_legacy_mode(&cache, &names, true).unwrap();
2523 assert!(*cache.get("metric2").unwrap().value());
2524 assert!(cache_legacy_mode(&cache, &names, false).is_err());
2525 assert!(*cache.get("metric2").unwrap().value());
2526
2527 let cache_incompatible = DashMap::new();
2528 cache_incompatible.insert("metric1".to_string(), true);
2529 cache_incompatible.insert("metric2".to_string(), false);
2530 assert!(fast_legacy_check(&cache_incompatible, &names).is_err());
2531 }
2532
2533 #[test]
2534 fn test_should_track_statement_process() {
2535 assert!(should_track_statement_process(&parse_one_sql(
2536 "SELECT * FROM demo"
2537 )));
2538 assert!(should_track_statement_process(&parse_one_sql(
2539 "INSERT INTO demo SELECT * FROM source"
2540 )));
2541 assert!(!should_track_statement_process(&parse_one_sql(
2542 "INSERT INTO demo VALUES (1)"
2543 )));
2544 assert!(!should_track_statement_process(&parse_one_sql(
2545 "INSERT INTO demo VALUES (now())"
2546 )));
2547 }
2548
2549 #[test]
2550 fn test_should_track_plan_process() {
2551 let select_stmt = parse_one_sql("SELECT * FROM demo");
2552 let insert_select_stmt = parse_one_sql("INSERT INTO demo SELECT * FROM source");
2553 let insert_values_stmt = parse_one_sql("INSERT INTO demo VALUES (now())");
2554
2555 let empty_plan = LogicalPlanBuilder::empty(false).build().unwrap();
2556 assert!(should_track_plan_process(Some(&select_stmt), &empty_plan));
2557 assert!(should_track_plan_process(
2558 Some(&insert_select_stmt),
2559 &insert_dml_plan()
2560 ));
2561 assert!(!should_track_plan_process(
2562 Some(&insert_values_stmt),
2563 &insert_dml_plan()
2564 ));
2565 assert!(!should_track_plan_process(None, &insert_dml_plan()));
2566 }
2567
2568 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2569 async fn test_insert_select_is_visible_in_show_processlist() -> TestResult<()> {
2570 let insert_sql = "INSERT INTO target SELECT * FROM source";
2571 let (started_tx, mut started_rx) = mpsc::unbounded_channel();
2572 let (finish_tx, finish_rx) = oneshot::channel();
2573 let interceptor = Arc::new(BlockingInsertSelectInterceptor::new(started_tx, finish_rx));
2574 let instance = Arc::new(test_instance_with_insert_select_interceptor(interceptor).await?);
2575
2576 let insert_task = tokio::spawn({
2577 let instance = instance.clone();
2578 async move { execute_one_sql(&instance, insert_sql, test_query_ctx(4242)).await }
2579 });
2580
2581 tokio::time::timeout(Duration::from_secs(5), started_rx.recv())
2582 .await
2583 .context(InsertStartTimeoutSnafu)?
2584 .context(InsertStartChannelClosedSnafu)?;
2585
2586 let output = execute_one_sql(&instance, "SHOW PROCESSLIST", test_query_ctx(43)).await?;
2587 let process_list = output.data.pretty_print().await;
2588 assert!(
2589 process_list.contains(insert_sql),
2590 "process list did not contain running insert:\n{process_list}"
2591 );
2592
2593 finish_tx
2594 .send(())
2595 .map_err(|_| ReleaseBlockedInsertSnafu.build())?;
2596 insert_task.await.context(InsertTaskPanicSnafu)??;
2597
2598 Ok(())
2599 }
2600
2601 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2602 async fn test_kill_query_cancels_insert_select() -> TestResult<()> {
2603 assert_kill_cancels_insert_select("KILL QUERY 4242").await
2604 }
2605
2606 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2607 async fn test_kill_process_id_cancels_insert_select() -> TestResult<()> {
2608 assert_kill_cancels_insert_select("KILL 'test-frontend/4242'").await
2609 }
2610
2611 async fn assert_kill_cancels_insert_select(kill_sql: &str) -> TestResult<()> {
2612 let insert_sql = "INSERT INTO target SELECT * FROM source";
2613 let (source_polled_tx, source_polled_rx) = oneshot::channel();
2614 let instance = Arc::new(
2615 test_instance_with_tables(
2616 pending_table(1024, "source", source_polled_tx)?,
2617 test_table(1025, "target")?,
2618 )
2619 .await?,
2620 );
2621
2622 let insert_task = tokio::spawn({
2623 let instance = instance.clone();
2624 async move { execute_one_sql(&instance, insert_sql, test_query_ctx(4242)).await }
2625 });
2626
2627 tokio::time::timeout(Duration::from_secs(5), source_polled_rx)
2628 .await
2629 .context(SourcePollTimeoutSnafu)?
2630 .context(SourcePollChannelClosedSnafu)?;
2631
2632 let output = execute_one_sql(&instance, kill_sql, test_query_ctx(43)).await?;
2633 assert!(matches!(output.data, OutputData::AffectedRows(1)));
2634
2635 let insert_result = tokio::time::timeout(Duration::from_secs(5), insert_task)
2636 .await
2637 .context(InsertTaskTimeoutSnafu)?
2638 .context(InsertTaskPanicSnafu)?;
2639 let err = match insert_result {
2640 Ok(_) => return InsertSelectNotCancelledSnafu.fail(),
2641 Err(TestError::ExecuteSql { source, .. }) => source,
2642 Err(err) => return Err(err),
2643 };
2644 assert_eq!(StatusCode::Cancelled, err.status_code());
2645
2646 let output = execute_one_sql(&instance, "SHOW PROCESSLIST", test_query_ctx(43)).await?;
2647 let process_list = output.data.pretty_print().await;
2648 assert!(
2649 !process_list.contains(insert_sql),
2650 "process list still contains killed insert:\n{process_list}"
2651 );
2652
2653 Ok(())
2654 }
2655
2656 fn insert_dml_plan() -> LogicalPlan {
2657 let schema = SchemaRef::new(Schema::new(vec![Field::new(
2658 "value",
2659 DataType::Int64,
2660 true,
2661 )]));
2662 let target = Arc::new(LogicalTableSource::new(schema));
2663 let input = LogicalPlanBuilder::empty(false).build().unwrap();
2664
2665 LogicalPlanBuilder::insert_into(input, "demo", target, InsertOp::Append)
2666 .unwrap()
2667 .build()
2668 .unwrap()
2669 }
2670
2671 #[test]
2672 fn test_exec_validation() {
2673 let query_ctx = QueryContext::arc();
2674 let plugins: Plugins = Plugins::new();
2675 plugins.insert(QueryOptions {
2676 disallow_cross_catalog_query: true,
2677 });
2678
2679 let sql = r#"
2680 SELECT * FROM demo;
2681 EXPLAIN SELECT * FROM demo;
2682 CREATE DATABASE test_database;
2683 SHOW DATABASES;
2684 "#;
2685 let stmts = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2686 assert_eq!(stmts.len(), 4);
2687 for stmt in stmts {
2688 let re = check_permission(plugins.clone(), &stmt, &query_ctx);
2689 re.unwrap();
2690 }
2691
2692 let sql = r#"
2693 SHOW CREATE TABLE demo;
2694 ALTER TABLE demo ADD COLUMN new_col INT;
2695 "#;
2696 let stmts = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2697 assert_eq!(stmts.len(), 2);
2698 for stmt in stmts {
2699 let re = check_permission(plugins.clone(), &stmt, &query_ctx);
2700 re.unwrap();
2701 }
2702
2703 fn replace_test(template_sql: &str, plugins: Plugins, query_ctx: &QueryContextRef) {
2704 let right = vec![("", ""), ("", "public."), ("greptime.", "public.")];
2706 for (catalog, schema) in right {
2707 let sql = do_fmt(template_sql, catalog, schema);
2708 do_test(&sql, plugins.clone(), query_ctx, true);
2709 }
2710
2711 let wrong = vec![
2712 ("wrongcatalog.", "public."),
2713 ("wrongcatalog.", "wrongschema."),
2714 ];
2715 for (catalog, schema) in wrong {
2716 let sql = do_fmt(template_sql, catalog, schema);
2717 do_test(&sql, plugins.clone(), query_ctx, false);
2718 }
2719 }
2720
2721 fn do_fmt(template: &str, catalog: &str, schema: &str) -> String {
2722 let vars = HashMap::from([
2723 ("catalog".to_string(), catalog),
2724 ("schema".to_string(), schema),
2725 ]);
2726 template.format(&vars).unwrap()
2727 }
2728
2729 fn do_test(sql: &str, plugins: Plugins, query_ctx: &QueryContextRef, is_ok: bool) {
2730 let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
2731 let re = check_permission(plugins, stmt, query_ctx);
2732 if is_ok {
2733 re.unwrap();
2734 } else {
2735 assert!(re.is_err());
2736 }
2737 }
2738
2739 let sql = "INSERT INTO {catalog}{schema}monitor(host) VALUES ('host1');";
2741 replace_test(sql, plugins.clone(), &query_ctx);
2742
2743 let sql = r#"CREATE TABLE {catalog}{schema}demo(
2745 host STRING,
2746 ts TIMESTAMP,
2747 TIME INDEX (ts),
2748 PRIMARY KEY(host)
2749 ) engine=mito;"#;
2750 replace_test(sql, plugins.clone(), &query_ctx);
2751
2752 let sql = "DROP TABLE {catalog}{schema}demo;";
2754 replace_test(sql, plugins.clone(), &query_ctx);
2755
2756 let sql = "UNDROP TABLE {catalog}{schema}demo;";
2758 replace_test(sql, plugins.clone(), &query_ctx);
2759
2760 let sql = "SHOW TABLES FROM public";
2762 let stmt = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2763 check_permission(plugins.clone(), &stmt[0], &query_ctx).unwrap();
2764
2765 let sql = "SHOW TABLES FROM private";
2766 let stmt = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2767 let re = check_permission(plugins.clone(), &stmt[0], &query_ctx);
2768 assert!(re.is_ok());
2769
2770 let sql = "DESC TABLE {catalog}{schema}demo;";
2772 replace_test(sql, plugins.clone(), &query_ctx);
2773
2774 let comment_flow_cases = [
2775 ("COMMENT ON FLOW my_flow IS 'comment';", true),
2776 ("COMMENT ON FLOW greptime.my_flow IS 'comment';", true),
2777 ("COMMENT ON FLOW wrongcatalog.my_flow IS 'comment';", false),
2778 ];
2779 for (sql, is_ok) in comment_flow_cases {
2780 let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
2781 let result = check_permission(plugins.clone(), stmt, &query_ctx);
2782 assert_eq!(result.is_ok(), is_ok);
2783 }
2784
2785 let show_flow_cases = [
2786 ("SHOW CREATE FLOW my_flow;", true),
2787 ("SHOW CREATE FLOW greptime.my_flow;", true),
2788 ("SHOW CREATE FLOW wrongcatalog.my_flow;", false),
2789 ];
2790 for (sql, is_ok) in show_flow_cases {
2791 let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
2792 let result = check_permission(plugins.clone(), stmt, &query_ctx);
2793 assert_eq!(result.is_ok(), is_ok);
2794 }
2795 }
2796}