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