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