Skip to main content

frontend/
instance.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15pub 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/// The frontend instance contains necessary components, and implements many
118/// traits, like [`servers::query_handler::grpc::GrpcQueryHandler`],
119/// [`servers::query_handler::sql::SqlQueryHandler`], etc.
120#[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    // cache for otlp metrics
139    // first layer key: db-string
140    // key: direct input metric name
141    // value: if runs in legacy mode
142    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            // Keep explicit FORMAT JSON accepted, but pass JSON through
227            // QueryContext.explain_format instead of the statement to avoid the
228            // planner's current `EXPLAIN VERBOSE with FORMAT` limitation.
229            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                // compute remaining timeout
321                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                // TODO: remove this when format is supported in datafusion
340                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        // fast cache check
413        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        // release cache reference to avoid lock contention
421        drop(cache);
422
423        let catalog = ctx.current_catalog();
424        let schema = ctx.current_schema();
425
426        // query legacy table names
427        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        // means no existing table is found, use new mode
447        if table_ids.is_empty() {
448            return Ok(false);
449        }
450
451        // has existing table, check table options
452        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            // check value consistency
471            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            // no table info, use new mode
479            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        // hit but have true and false, means both legacy and new mode are used
510        // we cannot handle this case, so return error
511        // add doc links in err msg later
512        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
532/// If the relevant variables are set, the timeout is enforced for all PostgreSQL statements.
533/// For MySQL, it applies only to read-only statements.
534fn 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
546/// Derives timeout for plan execution.
547fn 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                    // tokio::time::timeout may not return an error immediately when timeout is 0.
577                    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        // check will be done in prometheus handler's do_query
862        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        // EXPLAIN / EXPLAIN ANALYZE wrap an inner statement; describe them when the
878        // wrapped statement is something we already plan (so that bind parameters
879        // in the inner query get their types inferred). See #8029.
880        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
984/// Expands scan-time dictionaries only when a query result leaves the frontend.
985pub(crate) fn map_query_output(output: Output) -> Result<Output> {
986    output
987        .map_dictionary_to_values()
988        .context(CollectRecordbatchSnafu)
989}
990
991/// Attaches a timer to the output and observes it once the output is exhausted.
992pub 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        // Bound catalog load and inspect results in target order to preserve serial semantics.
1085        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        // Take the EvalStmt from the original QueryStatement and use it to create the CatalogQueryStatement.
1177        let query_statement = if let QueryStatement::Promql(eval_stmt, alias) = stmt {
1178            CatalogQueryStatement::Promql(eval_stmt, alias)
1179        } else {
1180            // It should not happen since the query is already parsed successfully.
1181            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
1368/// Validate `stmt.database` permission if it's presented.
1369macro_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        // Will be checked in execution.
1395        // TODO(dennis): add a hook for admin commands.
1396        Statement::Admin(_) => {}
1397        // These are executed by query engine, and will be checked there.
1398        Statement::Query(_)
1399        | Statement::Explain(_)
1400        | Statement::Tql(_)
1401        | Statement::Delete(_)
1402        | Statement::DeclareCursor(_)
1403        | Statement::Copy(sql::statements::copy::Copy::CopyQueryTo(_)) => {}
1404        // database ops won't be checked
1405        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            // TODO: should also validate source table name here?
1434            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        // set/show variable now only alter/show variable in session
1449        Statement::SetVariables(_) | Statement::ShowVariables(_) => {}
1450        // show charset and show collation won't be checked
1451        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            // The trigger is organized based on the catalog dimension, so there
1505            // is no need to check the permission of the database(schema).
1506        }
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(&copy_table_to.table_name, query_ctx)?,
1514            CopyTable::From(copy_table_from) => {
1515                validate_param(&copy_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        // cursor operations are always allowed once it's created
1528        Statement::FetchCursor(_) | Statement::CloseCursor(_) => {}
1529        // User can only kill process in their own catalog.
1530        Statement::Kill(_) => {}
1531        // SHOW PROCESSLIST
1532        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            // test right
2705            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        // test insert
2740        let sql = "INSERT INTO {catalog}{schema}monitor(host) VALUES ('host1');";
2741        replace_test(sql, plugins.clone(), &query_ctx);
2742
2743        // test create table
2744        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        // test drop table
2753        let sql = "DROP TABLE {catalog}{schema}demo;";
2754        replace_test(sql, plugins.clone(), &query_ctx);
2755
2756        // test undrop table
2757        let sql = "UNDROP TABLE {catalog}{schema}demo;";
2758        replace_test(sql, plugins.clone(), &query_ctx);
2759
2760        // test show tables
2761        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        // test describe table
2771        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}