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 entity_graph;
18mod grpc;
19mod influxdb;
20mod jaeger;
21mod log_handler;
22mod logs;
23mod opentsdb;
24mod otlp;
25pub mod prom_store;
26mod promql;
27mod region_query;
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::dataframe::DataFrame;
63use datafusion::physical_plan::ExecutionPlan;
64use datafusion_expr::LogicalPlan;
65use futures::{Stream, StreamExt, future};
66use lazy_static::lazy_static;
67use operator::delete::DeleterRef;
68use operator::insert::InserterRef;
69use operator::statement::{StatementExecutor, StatementExecutorRef};
70use partition::manager::PartitionRuleManagerRef;
71use pipeline::pipeline_operator::PipelineOperator;
72use prometheus::HistogramTimer;
73use promql_parser::label::Matcher;
74use query::QueryEngineRef;
75use query::metrics::OnDone;
76use query::parser::{PromQuery, QueryStatement};
77use query::query_engine::DescribeResult;
78use query::query_engine::options::{QueryOptions, validate_catalog_and_schema};
79use servers::error::{
80    self as server_error, AuthSnafu, CommonMetaSnafu, ExecuteQuerySnafu,
81    OtlpMetricModeIncompatibleSnafu, UnexpectedResultSnafu,
82};
83use servers::interceptor::{
84    PromQueryInterceptor, PromQueryInterceptorRef, SqlQueryInterceptor, SqlQueryInterceptorRef,
85};
86use servers::otlp::metrics::legacy_normalize_otlp_name;
87use servers::prometheus_handler::{
88    ParsedPromQuery, PrometheusHandler, resolve_schema_from_matchers,
89};
90use servers::query_handler::sql::SqlQueryHandler;
91use session::context::{Channel, QueryContextRef};
92use session::table_name::table_idents_to_full_name;
93use snafu::prelude::*;
94use sql::ast::ObjectNamePartExt;
95use sql::dialect::Dialect;
96use sql::parser::{ParseOptions, ParserContext};
97use sql::statements::comment::CommentObject;
98use sql::statements::copy::{CopyDatabase, CopyTable};
99use sql::statements::statement::Statement;
100use sql::statements::tql::Tql;
101use sql::util::{extract_tables_from_prom_expr_checked, extract_tables_from_statement_checked};
102use sqlparser::ast::{AnalyzeFormat, ObjectName};
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    otlp_resource_info: bool,
139    suspend: Arc<AtomicBool>,
140
141    // cache for otlp metrics
142    // first layer key: db-string
143    // key: direct input metric name
144    // value: if runs in legacy mode
145    otlp_metrics_table_legacy_cache: DashMap<String, DashMap<String, bool>>,
146}
147
148impl Instance {
149    pub fn frontend_peer_addr(&self) -> &str {
150        &self.frontend_peer_addr
151    }
152
153    pub fn catalog_manager(&self) -> &CatalogManagerRef {
154        &self.catalog_manager
155    }
156
157    pub fn query_engine(&self) -> &QueryEngineRef {
158        &self.query_engine
159    }
160
161    pub fn plugins(&self) -> &Plugins {
162        &self.plugins
163    }
164
165    fn check_permission(
166        &self,
167        ctx: &QueryContextRef,
168        req: PermissionReq<'_>,
169    ) -> server_error::Result<()> {
170        self.plugins
171            .get::<PermissionCheckerRef>()
172            .as_ref()
173            .check_permission(ctx.current_user(), req)
174            .context(AuthSnafu)?;
175        Ok(())
176    }
177
178    pub fn statement_executor(&self) -> &StatementExecutorRef {
179        &self.statement_executor
180    }
181
182    pub fn table_metadata_manager(&self) -> &TableMetadataManagerRef {
183        &self.table_metadata_manager
184    }
185
186    pub fn inserter(&self) -> &InserterRef {
187        &self.inserter
188    }
189
190    pub fn process_manager(&self) -> &ProcessManagerRef {
191        &self.process_manager
192    }
193
194    /// Returns the event recorder configured for this frontend instance.
195    pub fn event_recorder(&self) -> EventRecorderRef {
196        self.event_recorder.clone()
197    }
198
199    pub fn node_manager(&self) -> &NodeManagerRef {
200        self.inserter.node_manager()
201    }
202
203    pub fn partition_manager(&self) -> &PartitionRuleManagerRef {
204        self.inserter.partition_manager()
205    }
206
207    pub fn table_flownode_set_cache(&self) -> &TableFlownodeSetCacheRef {
208        self.inserter.table_flownode_set_cache()
209    }
210
211    pub fn cache_invalidator(&self) -> &CacheInvalidatorRef {
212        self.statement_executor.cache_invalidator()
213    }
214
215    pub fn procedure_executor(&self) -> &ProcedureExecutorRef {
216        self.statement_executor.procedure_executor()
217    }
218
219    pub fn suspend_state(&self) -> Arc<AtomicBool> {
220        self.suspend.clone()
221    }
222
223    pub(crate) fn is_suspended(&self) -> bool {
224        self.suspend.load(atomic::Ordering::Relaxed)
225    }
226}
227
228fn parse_stmt(sql: &str, dialect: &(dyn Dialect + Send + Sync)) -> Result<Vec<Statement>> {
229    ParserContext::create_with_dialect(sql, dialect, ParseOptions::default()).context(ParseSqlSnafu)
230}
231
232fn is_explain_analyze_verbose(stmt: &Statement) -> bool {
233    matches!(stmt, Statement::Explain(explain) if explain.analyze && explain.verbose)
234}
235
236fn validate_analyze_stream_statement(stmt: &mut Statement) -> Result<()> {
237    let Statement::Explain(explain) = stmt else {
238        return InvalidSqlSnafu {
239            err_msg: "only EXPLAIN ANALYZE VERBOSE statement is supported",
240        }
241        .fail();
242    };
243    ensure!(
244        explain.analyze && explain.verbose,
245        InvalidSqlSnafu {
246            err_msg: "statement must be EXPLAIN ANALYZE VERBOSE"
247        }
248    );
249    match explain.format {
250        None | Some(AnalyzeFormat::JSON) => {
251            // Keep explicit FORMAT JSON accepted, but pass JSON through
252            // QueryContext.explain_format instead of the statement to avoid the
253            // planner's current `EXPLAIN VERBOSE with FORMAT` limitation.
254            explain.format = None;
255            Ok(())
256        }
257        Some(_) => InvalidSqlSnafu {
258            err_msg: "only FORMAT JSON is supported for analyze stream",
259        }
260        .fail(),
261    }
262}
263
264impl Instance {
265    fn statement_slow_query_timer(
266        &self,
267        stmt: &Statement,
268        schema_name: String,
269    ) -> Option<SlowQueryTimer> {
270        if !stmt.is_readonly() || !self.slow_query_options.enable {
271            return None;
272        }
273
274        Some(SlowQueryTimer::new(
275            CatalogQueryStatement::Sql(stmt.clone()),
276            schema_name,
277            self.slow_query_options.threshold,
278            self.slow_query_options.sample_ratio,
279            self.slow_query_options.record_type,
280            self.slow_query_recorder.clone(),
281        ))
282    }
283
284    async fn query_statement(&self, stmt: Statement, query_ctx: QueryContextRef) -> Result<Output> {
285        check_permission(self.plugins.clone(), &stmt, &query_ctx)?;
286
287        let query_interceptor = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
288        let query_interceptor = query_interceptor.as_ref();
289
290        if should_track_statement_process(&stmt) {
291            let catalog_name = query_ctx.current_catalog().to_string();
292            let schema_name = query_ctx.current_schema();
293            let slow_query_timer = self.statement_slow_query_timer(&stmt, schema_name.clone());
294            let timeout_recorder = is_explain_analyze_verbose(&stmt)
295                .then(|| slow_query_timer.as_ref().map(SlowQueryTimer::recorder))
296                .flatten();
297
298            let ticket = self.process_manager.register_query(
299                catalog_name,
300                vec![schema_name],
301                stmt.to_string(),
302                query_ctx.conn_info().to_string(),
303                Some(query_ctx.process_id()),
304                slow_query_timer,
305            );
306
307            let query_fut = self.exec_statement_with_timeout(
308                stmt,
309                query_ctx,
310                query_interceptor,
311                timeout_recorder,
312            );
313
314            CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
315                .await
316                .map_err(|_| error::CancelledSnafu.build())?
317                .map(|output| {
318                    let Output { meta, data } = output;
319
320                    let data = match data {
321                        OutputData::Stream(stream) => OutputData::Stream(Box::pin(
322                            CancellableStreamWrapper::new(stream, ticket),
323                        )),
324                        other => other,
325                    };
326                    Output { data, meta }
327                })
328        } else {
329            self.exec_statement_with_timeout(stmt, query_ctx, query_interceptor, None)
330                .await
331        }
332    }
333
334    async fn exec_statement_with_timeout(
335        &self,
336        stmt: Statement,
337        query_ctx: QueryContextRef,
338        query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
339        timeout_recorder: Option<SlowQueryRecorder>,
340    ) -> Result<Output> {
341        let timeout = derive_timeout(&stmt, &query_ctx);
342        match timeout {
343            Some(timeout) => {
344                let start = tokio::time::Instant::now();
345                let output = tokio::time::timeout(
346                    timeout,
347                    self.exec_statement(stmt, query_ctx, query_interceptor),
348                )
349                .await
350                .map_err(|_| StatementTimeoutSnafu.build())??;
351                let output = map_query_output(output)?;
352                // compute remaining timeout
353                let remaining_timeout = timeout.checked_sub(start.elapsed()).unwrap_or_default();
354                attach_timeout(output, remaining_timeout, timeout_recorder)
355            }
356            None => self
357                .exec_statement(stmt, query_ctx, query_interceptor)
358                .await
359                .and_then(map_query_output),
360        }
361    }
362
363    async fn exec_statement(
364        &self,
365        stmt: Statement,
366        query_ctx: QueryContextRef,
367        query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
368    ) -> Result<Output> {
369        match stmt {
370            Statement::Query(_) | Statement::Explain(_) | Statement::Delete(_) => {
371                // TODO: remove this when format is supported in datafusion
372                if let Statement::Explain(explain) = &stmt
373                    && let Some(format) = explain.format()
374                {
375                    query_ctx.set_explain_format(format.to_string());
376                }
377
378                self.plan_and_exec_sql(stmt, &query_ctx, query_interceptor)
379                    .await
380            }
381            Statement::Tql(tql) => {
382                self.plan_and_exec_tql(&query_ctx, query_interceptor, tql)
383                    .await
384            }
385            _ => {
386                query_interceptor.pre_execute(Some(&stmt), None, query_ctx.clone())?;
387                self.statement_executor
388                    .execute_sql(stmt, query_ctx)
389                    .await
390                    .context(TableOperationSnafu)
391            }
392        }
393    }
394
395    async fn plan_and_exec_sql(
396        &self,
397        stmt: Statement,
398        query_ctx: &QueryContextRef,
399        query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
400    ) -> Result<Output> {
401        let stmt = QueryStatement::Sql(stmt);
402        let plan = self
403            .statement_executor
404            .plan(&stmt, query_ctx.clone())
405            .await?;
406        let QueryStatement::Sql(stmt) = stmt else {
407            unreachable!()
408        };
409        query_interceptor.pre_execute(Some(&stmt), Some(&plan), query_ctx.clone())?;
410
411        self.statement_executor
412            .exec_plan(plan, query_ctx.clone())
413            .await
414            .context(TableOperationSnafu)
415    }
416
417    async fn plan_and_exec_tql(
418        &self,
419        query_ctx: &QueryContextRef,
420        query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
421        tql: Tql,
422    ) -> Result<Output> {
423        let plan = self
424            .statement_executor
425            .plan_tql(tql.clone(), query_ctx)
426            .await?;
427        query_interceptor.pre_execute(
428            Some(&Statement::Tql(tql)),
429            Some(&plan),
430            query_ctx.clone(),
431        )?;
432        self.statement_executor
433            .exec_plan(plan, query_ctx.clone())
434            .await
435            .context(TableOperationSnafu)
436    }
437
438    async fn check_otlp_legacy(
439        &self,
440        names: &[String],
441        ctx: &QueryContextRef,
442    ) -> server_error::Result<bool> {
443        let db_string = ctx.get_db_string();
444        // fast cache check
445        let cache = self
446            .otlp_metrics_table_legacy_cache
447            .entry(db_string.clone())
448            .or_default();
449        if let Some(flag) = fast_legacy_check(&cache, names)? {
450            return Ok(flag);
451        }
452        // release cache reference to avoid lock contention
453        drop(cache);
454
455        let catalog = ctx.current_catalog();
456        let schema = ctx.current_schema();
457
458        // query legacy table names
459        let normalized_names = names
460            .iter()
461            .map(|n| legacy_normalize_otlp_name(n))
462            .collect::<Vec<_>>();
463        let table_names = normalized_names
464            .iter()
465            .map(|n| TableNameKey::new(catalog, &schema, n))
466            .collect::<Vec<_>>();
467        let table_values = self
468            .table_metadata_manager()
469            .table_name_manager()
470            .batch_get(table_names)
471            .await
472            .context(CommonMetaSnafu)?;
473        let table_ids = table_values
474            .into_iter()
475            .filter_map(|v| v.map(|vi| vi.table_id()))
476            .collect::<Vec<_>>();
477
478        // means no existing table is found, use new mode
479        if table_ids.is_empty() {
480            return Ok(false);
481        }
482
483        // has existing table, check table options
484        let table_infos = self
485            .table_metadata_manager()
486            .table_info_manager()
487            .batch_get(&table_ids)
488            .await
489            .context(CommonMetaSnafu)?;
490        let options = table_infos
491            .values()
492            .map(|info| {
493                info.table_info
494                    .meta
495                    .options
496                    .extra_options
497                    .get(OTLP_METRIC_COMPAT_KEY)
498                    .unwrap_or(&OTLP_LEGACY_DEFAULT_VALUE)
499            })
500            .collect::<Vec<_>>();
501        if !options.is_empty() {
502            // check value consistency
503            let has_prom = options.iter().any(|opt| *opt == OTLP_METRIC_COMPAT_PROM);
504            let has_legacy = options
505                .iter()
506                .any(|opt| *opt == OTLP_LEGACY_DEFAULT_VALUE.as_str());
507            ensure!(!(has_prom && has_legacy), OtlpMetricModeIncompatibleSnafu);
508            Ok(has_legacy)
509        } else {
510            // no table info, use new mode
511            Ok(false)
512        }
513    }
514
515    fn cache_otlp_legacy(
516        &self,
517        names: &[String],
518        ctx: &QueryContextRef,
519        is_legacy: bool,
520    ) -> server_error::Result<()> {
521        let cache = self
522            .otlp_metrics_table_legacy_cache
523            .entry(ctx.get_db_string())
524            .or_default();
525        cache_legacy_mode(&cache, names, is_legacy)
526    }
527}
528
529fn fast_legacy_check(
530    cache: &DashMap<String, bool>,
531    names: &[String],
532) -> server_error::Result<Option<bool>> {
533    let hit_cache = names
534        .iter()
535        .filter_map(|name| cache.get(name))
536        .collect::<Vec<_>>();
537    if !hit_cache.is_empty() {
538        let hit_legacy = hit_cache.iter().any(|en| *en.value());
539        let hit_prom = hit_cache.iter().any(|en| !*en.value());
540
541        // hit but have true and false, means both legacy and new mode are used
542        // we cannot handle this case, so return error
543        // add doc links in err msg later
544        ensure!(!(hit_legacy && hit_prom), OtlpMetricModeIncompatibleSnafu);
545
546        Ok(Some(hit_legacy))
547    } else {
548        Ok(None)
549    }
550}
551
552fn cache_legacy_mode(
553    cache: &DashMap<String, bool>,
554    names: &[String],
555    is_legacy: bool,
556) -> server_error::Result<()> {
557    for name in names {
558        let cached = cache.entry(name.clone()).or_insert(is_legacy);
559        ensure!(*cached == is_legacy, OtlpMetricModeIncompatibleSnafu);
560    }
561    Ok(())
562}
563
564/// If the relevant variables are set, the timeout is enforced for all PostgreSQL statements.
565/// For MySQL, it applies only to read-only statements.
566fn derive_timeout(stmt: &Statement, query_ctx: &QueryContextRef) -> Option<Duration> {
567    let query_timeout = query_ctx.query_timeout()?;
568    if query_timeout.is_zero() {
569        return None;
570    }
571    match query_ctx.channel() {
572        Channel::Mysql if stmt.is_readonly() => Some(query_timeout),
573        Channel::Postgres => Some(query_timeout),
574        _ => None,
575    }
576}
577
578/// Derives timeout for plan execution.
579fn derive_timeout_for_plan(plan: &LogicalPlan, query_ctx: &QueryContextRef) -> Option<Duration> {
580    let query_timeout = query_ctx.query_timeout()?;
581    if query_timeout.is_zero() {
582        return None;
583    }
584    match query_ctx.channel() {
585        Channel::Mysql if is_readonly_plan(plan) => Some(query_timeout),
586        Channel::Postgres => Some(query_timeout),
587        _ => None,
588    }
589}
590
591fn record_explain_analyze_timeout(
592    recorder: Option<&SlowQueryRecorder>,
593    plan: Option<&Arc<dyn ExecutionPlan>>,
594) {
595    let Some(recorder) = recorder else {
596        return;
597    };
598    let metrics = plan
599        .and_then(|plan| query::analyze_plan_metrics_to_json_value(plan, true).ok())
600        .unwrap_or_else(|| serde_json::json!([]));
601    recorder.force_record_with_payload(serde_json::json!({
602        "timed_out": true,
603        "metrics": metrics,
604    }));
605}
606
607fn attach_timeout(
608    output: Output,
609    mut timeout: Duration,
610    timeout_recorder: Option<SlowQueryRecorder>,
611) -> Result<Output> {
612    if timeout.is_zero() {
613        return StatementTimeoutSnafu.fail();
614    }
615
616    let plan = timeout_recorder
617        .as_ref()
618        .and_then(|_| output.meta.plan.clone());
619    let output = match output.data {
620        OutputData::AffectedRows(_) | OutputData::RecordBatches(_) => output,
621        OutputData::Stream(mut stream) => {
622            let schema = stream.schema();
623            let s = Box::pin(stream! {
624                let mut start = tokio::time::Instant::now();
625                while let Some(item) = tokio::time::timeout(timeout, stream.next()).await.map_err(|_| {
626                    record_explain_analyze_timeout(timeout_recorder.as_ref(), plan.as_ref());
627                    StreamTimeoutSnafu.build()
628                })? {
629                    yield item;
630
631                    let now = tokio::time::Instant::now();
632                    timeout = timeout.checked_sub(now - start).unwrap_or(Duration::ZERO);
633                    start = now;
634                    // tokio::time::timeout may not return an error immediately when timeout is 0.
635                    if timeout.is_zero() {
636                        record_explain_analyze_timeout(timeout_recorder.as_ref(), plan.as_ref());
637                        StreamTimeoutSnafu.fail()?;
638                    }
639                }
640            }) as Pin<Box<dyn Stream<Item = _> + Send>>;
641            let stream = RecordBatchStreamWrapper {
642                schema,
643                stream: s,
644                output_ordering: None,
645                metrics: Default::default(),
646                span: Span::current(),
647            };
648            Output::new(OutputData::Stream(Box::pin(stream)), output.meta)
649        }
650    };
651
652    Ok(output)
653}
654
655impl Instance {
656    async fn check_sql_permission(
657        &self,
658        stmt: &Statement,
659        query_ctx: &QueryContextRef,
660    ) -> Result<()> {
661        self.plugins
662            .get::<PermissionCheckerRef>()
663            .as_ref()
664            .check_permission_with_context(
665                query_ctx.current_user(),
666                PermissionReq::SqlStatement(stmt),
667                Some(&query_ctx.current_schema()),
668            )
669            .context(PermissionSnafu)?;
670
671        let targets = match extract_tables_from_statement_checked(stmt) {
672            Some(tables) => PermissionTableTargets::resolved(
673                tables
674                    .map(|name| {
675                        table_idents_to_full_name(&name, query_ctx).map(
676                            |(catalog, schema, table)| {
677                                PermissionTableTarget::new(catalog, schema, table)
678                            },
679                        )
680                    })
681                    .collect::<std::result::Result<Vec<_>, _>>()
682                    .map_err(BoxedError::new)
683                    .context(ExternalSnafu)?,
684            ),
685            None => PermissionTableTargets::Unresolved,
686        };
687        let targets = self
688            .resolve_query_permission_targets(targets, query_ctx)
689            .await
690            .map_err(BoxedError::new)
691            .context(ExternalSnafu)?;
692        self.check_table_permission(query_ctx, PermissionReq::SqlStatement(stmt), targets)
693            .context(PermissionSnafu)?;
694        Ok(())
695    }
696
697    #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_analyze_stream_query")]
698    async fn do_analyze_stream_query_inner(
699        &self,
700        query: &str,
701        query_ctx: QueryContextRef,
702    ) -> Result<Output> {
703        ensure!(!self.is_suspended(), error::SuspendedSnafu);
704
705        let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
706        let query_interceptor = query_interceptor_opt.as_ref();
707        let query = query_interceptor.pre_parsing(query, query_ctx.clone())?;
708        let mut stmts = parse_stmt(query.as_ref(), query_ctx.sql_dialect())
709            .and_then(|stmts| query_interceptor.post_parsing(stmts, query_ctx.clone()))?;
710
711        ensure!(
712            stmts.len() == 1,
713            InvalidSqlSnafu {
714                err_msg: "only single EXPLAIN ANALYZE VERBOSE statement is supported"
715            }
716        );
717        let mut stmt = stmts.remove(0);
718        validate_analyze_stream_statement(&mut stmt)?;
719        query_ctx.set_explain_format(AnalyzeFormat::JSON.to_string());
720
721        self.check_sql_permission(&stmt, &query_ctx).await?;
722        check_permission(self.plugins.clone(), &stmt, &query_ctx)?;
723        let catalog_name = query_ctx.current_catalog().to_string();
724        let schema_name = query_ctx.current_schema();
725        let slow_query_timer = self.statement_slow_query_timer(&stmt, schema_name.clone());
726        let ticket = self.process_manager.register_query(
727            catalog_name,
728            vec![schema_name],
729            stmt.to_string(),
730            query_ctx.conn_info().to_string(),
731            Some(query_ctx.process_id()),
732            slow_query_timer,
733        );
734        let query_fut =
735            self.exec_statement_with_timeout(stmt, query_ctx.clone(), query_interceptor, None);
736        let output = CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
737            .await
738            .map_err(|_| error::CancelledSnafu.build())??;
739        let Output { meta, data } = output;
740        let data = match data {
741            OutputData::Stream(stream) => OutputData::Stream(Box::pin(
742                CancellableStreamWrapper::new_cancel_on_drop(stream, ticket),
743            )),
744            other => other,
745        };
746        query_interceptor.post_execute(Output { data, meta }, query_ctx)
747    }
748
749    #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_query")]
750    async fn do_query_inner(&self, query: &str, query_ctx: QueryContextRef) -> Vec<Result<Output>> {
751        if self.is_suspended() {
752            return vec![error::SuspendedSnafu {}.fail()];
753        }
754
755        let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
756        let query_interceptor = query_interceptor_opt.as_ref();
757        let query = match query_interceptor.pre_parsing(query, query_ctx.clone()) {
758            Ok(q) => q,
759            Err(e) => return vec![Err(e)],
760        };
761
762        match parse_stmt(query.as_ref(), query_ctx.sql_dialect())
763            .and_then(|stmts| query_interceptor.post_parsing(stmts, query_ctx.clone()))
764        {
765            Ok(stmts) => {
766                if stmts.is_empty() {
767                    return vec![
768                        InvalidSqlSnafu {
769                            err_msg: "empty statements",
770                        }
771                        .fail(),
772                    ];
773                }
774
775                let mut results = Vec::with_capacity(stmts.len());
776                for stmt in stmts {
777                    if let Err(e) = self.check_sql_permission(&stmt, &query_ctx).await {
778                        results.push(Err(e));
779                        break;
780                    }
781
782                    match self.query_statement(stmt.clone(), query_ctx.clone()).await {
783                        Ok(output) => {
784                            let output_result =
785                                query_interceptor.post_execute(output, query_ctx.clone());
786                            results.push(output_result);
787                        }
788                        Err(e) => {
789                            if e.status_code().should_log_error() {
790                                error!(e; "Failed to execute query: {stmt}");
791                            } else {
792                                debug!("Failed to execute query: {stmt}, {e}");
793                            }
794                            results.push(Err(e));
795                            break;
796                        }
797                    }
798                }
799                results
800            }
801            Err(e) => {
802                vec![Err(e)]
803            }
804        }
805    }
806
807    async fn exec_plan(&self, plan: LogicalPlan, query_ctx: QueryContextRef) -> Result<Output> {
808        self.query_engine
809            .execute(plan, query_ctx)
810            .await
811            .context(ExecLogicalPlanSnafu)
812    }
813
814    async fn exec_plan_with_timeout(
815        &self,
816        plan: LogicalPlan,
817        query_ctx: QueryContextRef,
818        timeout_recorder: Option<SlowQueryRecorder>,
819    ) -> Result<Output> {
820        let timeout = derive_timeout_for_plan(&plan, &query_ctx);
821        match timeout {
822            Some(timeout) => {
823                let start = tokio::time::Instant::now();
824                let output = tokio::time::timeout(timeout, self.exec_plan(plan, query_ctx))
825                    .await
826                    .map_err(|_| StatementTimeoutSnafu.build())??;
827                let output = map_query_output(output)?;
828                let remaining_timeout = timeout.checked_sub(start.elapsed()).unwrap_or_default();
829                attach_timeout(output, remaining_timeout, timeout_recorder)
830            }
831            None => self
832                .exec_plan(plan, query_ctx)
833                .await
834                .and_then(map_query_output),
835        }
836    }
837
838    async fn do_exec_plan_inner(
839        &self,
840        plan: LogicalPlan,
841        stmt: Option<Statement>,
842        query_ctx: QueryContextRef,
843    ) -> Result<Output> {
844        ensure!(!self.is_suspended(), error::SuspendedSnafu);
845
846        let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
847        let query_interceptor = query_interceptor_opt.as_ref();
848
849        query_interceptor.pre_execute(stmt.as_ref(), Some(&plan), query_ctx.clone())?;
850
851        // TQL EXPLAIN/ANALYZE formats are consumed from the query context at
852        // execution time (see `optimize_physical_plan`); re-apply the side
853        // effect of `plan_tql` that was lost when the plan was built during
854        // Describe. `explain_format` is per-query state, so this never
855        // overwrites anything.
856        if let Some(Statement::Tql(tql)) = &stmt {
857            let format = match tql {
858                Tql::Explain(explain) => explain.format.as_ref(),
859                Tql::Analyze(analyze) => analyze.format.as_ref(),
860                Tql::Eval(_) => None,
861            };
862            if let Some(format) = format {
863                query_ctx.set_explain_format(format.to_string());
864            }
865        }
866
867        let query = stmt
868            .as_ref()
869            .map(|s| s.to_string())
870            .unwrap_or_else(|| plan.display_indent().to_string());
871
872        let plan_is_readonly = is_readonly_plan(&plan);
873        let result = if should_track_plan_process(stmt.as_ref(), &plan) {
874            let catalog_name = query_ctx.current_catalog().to_string();
875            let schema_name = query_ctx.current_schema();
876            let slow_query_timer = if plan_is_readonly {
877                self.slow_query_options.enable.then(|| {
878                    SlowQueryTimer::new(
879                        CatalogQueryStatement::Plan(query.clone()),
880                        schema_name.clone(),
881                        self.slow_query_options.threshold,
882                        self.slow_query_options.sample_ratio,
883                        self.slow_query_options.record_type,
884                        self.slow_query_recorder.clone(),
885                    )
886                })
887            } else {
888                None
889            };
890
891            let timeout_recorder = stmt
892                .as_ref()
893                .is_some_and(is_explain_analyze_verbose)
894                .then(|| slow_query_timer.as_ref().map(SlowQueryTimer::recorder))
895                .flatten();
896            let ticket = self.process_manager.register_query(
897                catalog_name,
898                vec![schema_name],
899                query,
900                query_ctx.conn_info().to_string(),
901                Some(query_ctx.process_id()),
902                slow_query_timer,
903            );
904
905            let query_fut = self.exec_plan_with_timeout(plan, query_ctx.clone(), timeout_recorder);
906
907            CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
908                .await
909                .map_err(|_| error::CancelledSnafu.build())?
910                .map(|output| {
911                    let Output { meta, data } = output;
912
913                    let data = match data {
914                        OutputData::Stream(stream) => OutputData::Stream(Box::pin(
915                            CancellableStreamWrapper::new(stream, ticket),
916                        )),
917                        other => other,
918                    };
919                    Output { data, meta }
920                })
921        } else {
922            self.exec_plan_with_timeout(plan, query_ctx.clone(), None)
923                .await
924        };
925
926        result.and_then(|output| query_interceptor.post_execute(output, query_ctx))
927    }
928
929    #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_promql_query")]
930    async fn do_promql_query_inner(
931        &self,
932        query: &PromQuery,
933        query_ctx: QueryContextRef,
934    ) -> Vec<Result<Output>> {
935        if self.is_suspended() {
936            return vec![error::SuspendedSnafu {}.fail()];
937        }
938
939        // check will be done in prometheus handler's do_query
940        let result = PrometheusHandler::do_query(self, query, query_ctx)
941            .await
942            .with_context(|_| ExecutePromqlSnafu {
943                query: format!("{query:?}"),
944            });
945        vec![result]
946    }
947
948    /// Builds the [`DataFrame`] for an information-schema-backed `SHOW`
949    /// statement; `None` for other statements. The future is boxed to keep
950    /// `do_describe_inner`'s state machine small.
951    fn show_statement_dataframe<'a>(
952        &'a self,
953        stmt: &'a Statement,
954        query_ctx: &'a QueryContextRef,
955    ) -> Pin<Box<dyn Future<Output = Option<query::error::Result<DataFrame>>> + Send + 'a>> {
956        Box::pin(async move {
957            let engine = &self.query_engine;
958            let catalog_manager = self.catalog_manager();
959            let ctx = query_ctx.clone();
960            let dataframe = match stmt {
961                Statement::ShowDatabases(show) => {
962                    query::sql::show_databases_dataframe(show, engine, catalog_manager, ctx).await
963                }
964                Statement::ShowTables(show) => {
965                    query::sql::show_tables_dataframe(show, engine, catalog_manager, ctx).await
966                }
967                Statement::ShowViews(show) => {
968                    query::sql::show_views_dataframe(show, engine, catalog_manager, ctx).await
969                }
970                Statement::ShowFlows(show) => {
971                    query::sql::show_flows_dataframe(show, engine, catalog_manager, ctx).await
972                }
973                Statement::ShowColumns(show) => {
974                    query::sql::show_columns_dataframe(show, engine, catalog_manager, ctx).await
975                }
976                Statement::ShowTableStatus(show) => {
977                    query::sql::show_table_status_dataframe(show, engine, catalog_manager, ctx)
978                        .await
979                }
980                Statement::ShowCharset(kind) => {
981                    query::sql::show_charsets_dataframe(kind, engine, catalog_manager, ctx).await
982                }
983                Statement::ShowCollation(kind) => {
984                    query::sql::show_collations_dataframe(kind, engine, catalog_manager, ctx).await
985                }
986                Statement::ShowIndex(show) => {
987                    query::sql::show_index_dataframe(show, engine, catalog_manager, ctx).await
988                }
989                Statement::ShowRegion(show) => {
990                    query::sql::show_region_dataframe(show, engine, catalog_manager, ctx).await
991                }
992                Statement::ShowProcesslist(show) => {
993                    query::sql::show_processlist_dataframe(show, engine, catalog_manager, ctx).await
994                }
995                _ => return None,
996            };
997            Some(dataframe)
998        })
999    }
1000
1001    async fn do_describe_inner(
1002        &self,
1003        stmt: Statement,
1004        query_ctx: QueryContextRef,
1005    ) -> Result<Option<DescribeResult>> {
1006        ensure!(!self.is_suspended(), error::SuspendedSnafu);
1007
1008        // EXPLAIN / EXPLAIN ANALYZE wrap an inner statement; describe them when the
1009        // wrapped statement is something we already plan (so that bind parameters
1010        // in the inner query get their types inferred). See #8029.
1011        let is_inner_plannable = |s: &Statement| {
1012            matches!(
1013                s,
1014                Statement::Insert(_) | Statement::Query(_) | Statement::Delete(_)
1015            )
1016        };
1017        let plannable = is_inner_plannable(&stmt)
1018            || matches!(&stmt, Statement::Explain(explain) if is_inner_plannable(explain.statement.as_ref()));
1019
1020        if let Statement::Tql(tql) = stmt {
1021            // TQL produces a logical plan; describe it from the plan so the
1022            // extended-protocol RowDescription matches the executed DataRows.
1023            self.check_sql_permission(&Statement::Tql(tql.clone()), &query_ctx)
1024                .await?;
1025            let plan = self.statement_executor.plan_tql(tql, &query_ctx).await?;
1026            return self
1027                .query_engine
1028                .describe(plan, query_ctx)
1029                .await
1030                .map(Some)
1031                .context(error::DescribeStatementSnafu);
1032        }
1033
1034        // Describe SHOW statements from the same projection the executor builds.
1035        if let Some(dataframe) = self
1036            .show_statement_dataframe(&stmt, &query_ctx)
1037            .await
1038            .transpose()
1039            .context(PlanStatementSnafu)?
1040        {
1041            self.check_sql_permission(&stmt, &query_ctx).await?;
1042            let plan = dataframe.into_unoptimized_plan();
1043            return self
1044                .query_engine
1045                .describe(plan, query_ctx)
1046                .await
1047                .map(Some)
1048                .context(error::DescribeStatementSnafu);
1049        }
1050
1051        if plannable {
1052            self.check_sql_permission(&stmt, &query_ctx).await?;
1053
1054            let plan = self
1055                .query_engine
1056                .planner()
1057                .plan(&QueryStatement::Sql(stmt), query_ctx.clone())
1058                .await
1059                .context(PlanStatementSnafu)?;
1060            self.query_engine
1061                .describe(plan, query_ctx)
1062                .await
1063                .map(Some)
1064                .context(error::DescribeStatementSnafu)
1065        } else {
1066            Ok(None)
1067        }
1068    }
1069
1070    async fn is_valid_schema_inner(&self, catalog: &str, schema: &str) -> Result<bool> {
1071        self.catalog_manager
1072            .schema_exists(catalog, schema, None)
1073            .await
1074            .context(error::CatalogSnafu)
1075    }
1076}
1077
1078#[async_trait]
1079impl SqlQueryHandler for Instance {
1080    async fn do_query(
1081        &self,
1082        query: &str,
1083        query_ctx: QueryContextRef,
1084    ) -> Vec<server_error::Result<Output>> {
1085        self.do_query_inner(query, query_ctx)
1086            .await
1087            .into_iter()
1088            .map(|result| result.map_err(BoxedError::new).context(ExecuteQuerySnafu))
1089            .collect()
1090    }
1091
1092    async fn do_analyze_stream_query(
1093        &self,
1094        query: &str,
1095        query_ctx: QueryContextRef,
1096    ) -> server_error::Result<Output> {
1097        self.do_analyze_stream_query_inner(query, query_ctx)
1098            .await
1099            .map_err(BoxedError::new)
1100            .context(ExecuteQuerySnafu)
1101    }
1102
1103    async fn do_exec_plan(
1104        &self,
1105        plan: LogicalPlan,
1106        stmt: Option<Statement>,
1107        query_ctx: QueryContextRef,
1108    ) -> server_error::Result<Output> {
1109        self.do_exec_plan_inner(plan, stmt, query_ctx)
1110            .await
1111            .map_err(BoxedError::new)
1112            .context(server_error::ExecutePlanSnafu)
1113    }
1114
1115    async fn do_promql_query(
1116        &self,
1117        query: &PromQuery,
1118        query_ctx: QueryContextRef,
1119    ) -> Vec<server_error::Result<Output>> {
1120        self.do_promql_query_inner(query, query_ctx)
1121            .await
1122            .into_iter()
1123            .map(|result| result.map_err(BoxedError::new).context(ExecuteQuerySnafu))
1124            .collect()
1125    }
1126
1127    async fn do_describe(
1128        &self,
1129        stmt: Statement,
1130        query_ctx: QueryContextRef,
1131    ) -> server_error::Result<Option<DescribeResult>> {
1132        self.do_describe_inner(stmt, query_ctx)
1133            .await
1134            .map_err(BoxedError::new)
1135            .context(server_error::DescribeStatementSnafu)
1136    }
1137
1138    async fn is_valid_schema(&self, catalog: &str, schema: &str) -> server_error::Result<bool> {
1139        self.is_valid_schema_inner(catalog, schema)
1140            .await
1141            .map_err(BoxedError::new)
1142            .context(server_error::CheckDatabaseValiditySnafu)
1143    }
1144}
1145
1146/// Expands scan-time dictionaries only when a query result leaves the frontend.
1147pub(crate) fn map_query_output(output: Output) -> Result<Output> {
1148    output
1149        .map_dictionary_to_values()
1150        .context(CollectRecordbatchSnafu)
1151}
1152
1153/// Attaches a timer to the output and observes it once the output is exhausted.
1154pub fn attach_timer(output: Output, timer: HistogramTimer) -> Output {
1155    match output.data {
1156        OutputData::AffectedRows(_) | OutputData::RecordBatches(_) => output,
1157        OutputData::Stream(stream) => {
1158            let stream = OnDone::new(stream, move || {
1159                timer.observe_duration();
1160            });
1161            Output::new(OutputData::Stream(Box::pin(stream)), output.meta)
1162        }
1163    }
1164}
1165
1166impl Instance {
1167    fn check_prom_query_privilege(&self, query_ctx: &QueryContextRef) -> server_error::Result<()> {
1168        self.plugins
1169            .get::<PermissionCheckerRef>()
1170            .as_ref()
1171            .check_permission(
1172                query_ctx.current_user(),
1173                PermissionReq::Action(PROMQL_QUERY),
1174            )
1175            .context(AuthSnafu)?;
1176        Ok(())
1177    }
1178
1179    fn prom_expr_permission_targets(
1180        &self,
1181        expr: &promql_parser::parser::Expr,
1182        query_ctx: &QueryContextRef,
1183    ) -> server_error::Result<Option<Vec<PermissionTableTarget>>> {
1184        extract_tables_from_prom_expr_checked(expr)
1185            .map(|tables| {
1186                tables
1187                    .map(|name| {
1188                        table_idents_to_full_name(&name, query_ctx).map(
1189                            |(catalog, schema, table)| {
1190                                PermissionTableTarget::new(catalog, schema, table)
1191                            },
1192                        )
1193                    })
1194                    .collect::<std::result::Result<Vec<_>, _>>()
1195                    .map_err(BoxedError::new)
1196                    .context(ExecuteQuerySnafu)
1197            })
1198            .transpose()
1199    }
1200
1201    async fn is_physical_query_permission_target(
1202        &self,
1203        target: &PermissionTableTarget,
1204        query_ctx: &QueryContextRef,
1205    ) -> server_error::Result<bool> {
1206        self.catalog_manager
1207            .table(
1208                &target.catalog,
1209                &target.schema,
1210                &target.table,
1211                Some(query_ctx),
1212            )
1213            .await
1214            .map(|table| table.is_some_and(|table| table.table_info().is_physical_table()))
1215            .map_err(BoxedError::new)
1216            .context(ExecuteQuerySnafu)
1217    }
1218
1219    async fn resolve_query_permission_targets(
1220        &self,
1221        targets: PermissionTableTargets,
1222        query_ctx: &QueryContextRef,
1223    ) -> server_error::Result<PermissionTableTargets> {
1224        const CONCURRENCY: usize = 8;
1225
1226        let checker = self.plugins.get::<PermissionCheckerRef>();
1227        if !checker.as_ref().uses_table_targets() {
1228            return Ok(targets);
1229        }
1230
1231        let PermissionTableTargets::Resolved(mut targets) = targets else {
1232            return Ok(PermissionTableTargets::Unresolved);
1233        };
1234        if targets.len() > 1 {
1235            let mut seen = HashSet::with_capacity(targets.len());
1236            targets.retain(|target| seen.insert(target.clone()));
1237        }
1238        if let [target] = targets.as_slice() {
1239            return if self
1240                .is_physical_query_permission_target(target, query_ctx)
1241                .await?
1242            {
1243                Ok(PermissionTableTargets::Unresolved)
1244            } else {
1245                Ok(PermissionTableTargets::resolved(targets))
1246            };
1247        }
1248
1249        // Bound catalog load and inspect results in target order to preserve serial semantics.
1250        for chunk in targets.chunks(CONCURRENCY) {
1251            let results = future::join_all(
1252                chunk
1253                    .iter()
1254                    .map(|target| self.is_physical_query_permission_target(target, query_ctx)),
1255            )
1256            .await;
1257            for result in results {
1258                if result? {
1259                    return Ok(PermissionTableTargets::Unresolved);
1260                }
1261            }
1262        }
1263
1264        Ok(PermissionTableTargets::resolved(targets))
1265    }
1266
1267    fn prom_queries_permission_targets(
1268        &self,
1269        queries: &[ParsedPromQuery],
1270        query_ctx: &QueryContextRef,
1271    ) -> server_error::Result<PermissionTableTargets> {
1272        let mut targets = Vec::new();
1273        let mut resolved = true;
1274
1275        for query in queries {
1276            let QueryStatement::Promql(eval_stmt, _) = query.statement() else {
1277                unreachable!("query is parsed from promql");
1278            };
1279
1280            if let Some(query_targets) =
1281                self.prom_expr_permission_targets(&eval_stmt.expr, query_ctx)?
1282            {
1283                targets.extend(query_targets);
1284            } else {
1285                resolved = false;
1286            }
1287        }
1288
1289        Ok(if resolved {
1290            PermissionTableTargets::resolved(targets)
1291        } else {
1292            PermissionTableTargets::Unresolved
1293        })
1294    }
1295}
1296
1297#[async_trait]
1298impl PrometheusHandler for Instance {
1299    #[tracing::instrument(skip_all)]
1300    async fn do_query(
1301        &self,
1302        query: &PromQuery,
1303        query_ctx: QueryContextRef,
1304    ) -> server_error::Result<Output> {
1305        let query = ParsedPromQuery::parse(query.clone(), &query_ctx)?;
1306        self.do_query_parsed(query, query_ctx).await
1307    }
1308
1309    #[tracing::instrument(skip_all)]
1310    async fn do_query_parsed(
1311        &self,
1312        query: ParsedPromQuery,
1313        query_ctx: QueryContextRef,
1314    ) -> server_error::Result<Output> {
1315        let interceptor = self
1316            .plugins
1317            .get::<PromQueryInterceptorRef<server_error::Error>>();
1318
1319        self.check_prom_query_privilege(&query_ctx)?;
1320
1321        let targets =
1322            self.prom_queries_permission_targets(std::slice::from_ref(&query), &query_ctx)?;
1323        self.check_query_target_permission(targets, &query_ctx)
1324            .await?;
1325
1326        let (query, stmt) = query.into_parts();
1327
1328        let QueryStatement::Promql(eval_stmt, _) = &stmt else {
1329            unreachable!("query is parsed from promql");
1330        };
1331
1332        let plan = self
1333            .statement_executor
1334            .plan(&stmt, query_ctx.clone())
1335            .await
1336            .map_err(BoxedError::new)
1337            .context(ExecuteQuerySnafu)?;
1338
1339        interceptor.pre_execute(&query, &eval_stmt.expr, Some(&plan), query_ctx.clone())?;
1340
1341        // Take the EvalStmt from the original QueryStatement and use it to create the CatalogQueryStatement.
1342        let query_statement = if let QueryStatement::Promql(eval_stmt, alias) = stmt {
1343            CatalogQueryStatement::Promql(eval_stmt, alias)
1344        } else {
1345            // It should not happen since the query is already parsed successfully.
1346            return UnexpectedResultSnafu {
1347                reason: "The query should always be promql.".to_string(),
1348            }
1349            .fail();
1350        };
1351        let raw_query = query_statement.to_string();
1352
1353        let slow_query_timer = self.slow_query_options.enable.then(|| {
1354            SlowQueryTimer::new(
1355                query_statement,
1356                query_ctx.current_schema(),
1357                self.slow_query_options.threshold,
1358                self.slow_query_options.sample_ratio,
1359                self.slow_query_options.record_type,
1360                self.slow_query_recorder.clone(),
1361            )
1362        });
1363
1364        let ticket = self.process_manager.register_query(
1365            query_ctx.current_catalog().to_string(),
1366            vec![query_ctx.current_schema()],
1367            raw_query,
1368            query_ctx.conn_info().to_string(),
1369            Some(query_ctx.process_id()),
1370            slow_query_timer,
1371        );
1372
1373        let query_fut = self.statement_executor.exec_plan(plan, query_ctx.clone());
1374
1375        let output = CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
1376            .await
1377            .map_err(|_| servers::error::CancelledSnafu.build())?
1378            .map_err(BoxedError::new)
1379            .context(ExecuteQuerySnafu)?;
1380        let output = map_query_output(output)
1381            .map_err(BoxedError::new)
1382            .context(ExecuteQuerySnafu)?;
1383        let Output { meta, data } = output;
1384        let data = match data {
1385            OutputData::Stream(stream) => {
1386                OutputData::Stream(Box::pin(CancellableStreamWrapper::new(stream, ticket)))
1387            }
1388            other => other,
1389        };
1390        let output = Output { data, meta };
1391        Ok(interceptor.post_execute(output, query_ctx)?)
1392    }
1393
1394    async fn check_query_permission(
1395        &self,
1396        queries: &[PromQuery],
1397        query_ctx: &QueryContextRef,
1398    ) -> server_error::Result<()> {
1399        let queries = queries
1400            .iter()
1401            .cloned()
1402            .map(|query| ParsedPromQuery::parse(query, query_ctx))
1403            .collect::<server_error::Result<Vec<_>>>()?;
1404        self.check_query_permission_parsed(&queries, query_ctx)
1405            .await
1406    }
1407
1408    async fn check_query_permission_parsed(
1409        &self,
1410        queries: &[ParsedPromQuery],
1411        query_ctx: &QueryContextRef,
1412    ) -> server_error::Result<()> {
1413        self.check_prom_query_privilege(query_ctx)?;
1414        let targets = self.prom_queries_permission_targets(queries, query_ctx)?;
1415        self.check_query_target_permission(targets, query_ctx).await
1416    }
1417
1418    async fn check_query_target_permission(
1419        &self,
1420        targets: PermissionTableTargets,
1421        query_ctx: &QueryContextRef,
1422    ) -> server_error::Result<()> {
1423        let targets = self
1424            .resolve_query_permission_targets(targets, query_ctx)
1425            .await?;
1426        self.check_table_permission(query_ctx, PermissionReq::Action(PROMQL_QUERY), targets)
1427            .context(AuthSnafu)?;
1428        Ok(())
1429    }
1430
1431    async fn filter_metadata_metric_names(
1432        &self,
1433        metric_names: Vec<String>,
1434        schema: &str,
1435        query_ctx: &QueryContextRef,
1436    ) -> server_error::Result<Vec<String>> {
1437        let checker = self.plugins.get::<PermissionCheckerRef>();
1438        if !checker.as_ref().uses_table_targets() {
1439            let Some(metric) = metric_names.first() else {
1440                return Ok(metric_names);
1441            };
1442            let target =
1443                PermissionTableTarget::new(query_ctx.current_catalog(), schema, metric.as_str());
1444            let result = checker
1445                .as_ref()
1446                .check_permission_with_table_targets(
1447                    query_ctx.current_user(),
1448                    PermissionReq::Action(PROMQL_QUERY),
1449                    PermissionTableTargets::resolved(vec![target]),
1450                )
1451                .context(AuthSnafu);
1452            return match result {
1453                Ok(_) => Ok(metric_names),
1454                Err(error)
1455                    if error.status_code()
1456                        == common_error::status_code::StatusCode::PermissionDenied =>
1457                {
1458                    Ok(Vec::new())
1459                }
1460                Err(error) => Err(error),
1461            };
1462        }
1463
1464        let mut allowed = Vec::with_capacity(metric_names.len());
1465        for metric in metric_names {
1466            let target =
1467                PermissionTableTarget::new(query_ctx.current_catalog(), schema, metric.as_str());
1468            match checker
1469                .as_ref()
1470                .check_permission_with_table_targets(
1471                    query_ctx.current_user(),
1472                    PermissionReq::Action(PROMQL_QUERY),
1473                    PermissionTableTargets::resolved(vec![target]),
1474                )
1475                .context(AuthSnafu)
1476            {
1477                Ok(_) => allowed.push(metric),
1478                Err(error)
1479                    if error.status_code()
1480                        == common_error::status_code::StatusCode::PermissionDenied => {}
1481                Err(error) => return Err(error),
1482            }
1483        }
1484        Ok(allowed)
1485    }
1486
1487    async fn query_metric_names(
1488        &self,
1489        matchers: Vec<Matcher>,
1490        schema: &str,
1491        ctx: &QueryContextRef,
1492    ) -> server_error::Result<Vec<String>> {
1493        self.handle_query_metric_names(matchers, schema, ctx)
1494            .await
1495            .map_err(BoxedError::new)
1496            .context(ExecuteQuerySnafu)
1497    }
1498
1499    async fn query_label_values(
1500        &self,
1501        metric: String,
1502        label_name: String,
1503        matchers: Vec<Matcher>,
1504        start: SystemTime,
1505        end: SystemTime,
1506        ctx: &QueryContextRef,
1507    ) -> server_error::Result<Vec<String>> {
1508        let schema =
1509            resolve_schema_from_matchers(&matchers)?.unwrap_or_else(|| ctx.current_schema());
1510        let target = PermissionTableTarget::new(ctx.current_catalog(), schema.as_str(), &metric);
1511        self.check_query_target_permission(
1512            PermissionTableTargets::resolved(vec![target.clone()]),
1513            ctx,
1514        )
1515        .await?;
1516
1517        self.handle_query_label_values(target, label_name, matchers, start, end, ctx)
1518            .await
1519            .map_err(BoxedError::new)
1520            .context(ExecuteQuerySnafu)
1521    }
1522
1523    fn catalog_manager(&self) -> CatalogManagerRef {
1524        self.catalog_manager.clone()
1525    }
1526}
1527
1528/// Validate `stmt.database` permission if it's presented.
1529macro_rules! validate_db_permission {
1530    ($stmt: expr, $query_ctx: expr) => {
1531        if let Some(database) = &$stmt.database {
1532            validate_catalog_and_schema($query_ctx.current_catalog(), database, $query_ctx)
1533                .map_err(BoxedError::new)
1534                .context(SqlExecInterceptedSnafu)?;
1535        }
1536    };
1537}
1538
1539pub fn check_permission(
1540    plugins: Plugins,
1541    stmt: &Statement,
1542    query_ctx: &QueryContextRef,
1543) -> Result<()> {
1544    let need_validate = plugins
1545        .get::<QueryOptions>()
1546        .map(|opts| opts.disallow_cross_catalog_query)
1547        .unwrap_or_default();
1548
1549    if !need_validate {
1550        return Ok(());
1551    }
1552
1553    match stmt {
1554        // Will be checked in execution.
1555        // TODO(dennis): add a hook for admin commands.
1556        Statement::Admin(_) => {}
1557        // These are executed by query engine, and will be checked there.
1558        Statement::Query(_)
1559        | Statement::Explain(_)
1560        | Statement::Tql(_)
1561        | Statement::Delete(_)
1562        | Statement::DeclareCursor(_)
1563        | Statement::Copy(sql::statements::copy::Copy::CopyQueryTo(_)) => {}
1564        // database ops won't be checked
1565        Statement::CreateDatabase(_)
1566        | Statement::ShowDatabases(_)
1567        | Statement::DropDatabase(_)
1568        | Statement::AlterDatabase(_)
1569        | Statement::DropFlow(_)
1570        | Statement::Use(_) => {}
1571        #[cfg(feature = "enterprise")]
1572        Statement::DropTrigger(_) => {}
1573        Statement::ShowCreateDatabase(stmt) => {
1574            validate_database(&stmt.database_name, query_ctx)?;
1575        }
1576        Statement::ShowCreateTable(stmt) => {
1577            validate_param(&stmt.table_name, query_ctx)?;
1578        }
1579        Statement::ShowCreateFlow(stmt) => {
1580            validate_flow(&stmt.flow_name, query_ctx)?;
1581        }
1582        #[cfg(feature = "enterprise")]
1583        Statement::ShowCreateTrigger(stmt) => {
1584            validate_param(&stmt.trigger_name, query_ctx)?;
1585        }
1586        Statement::ShowCreateView(stmt) => {
1587            validate_param(&stmt.view_name, query_ctx)?;
1588        }
1589        Statement::CreateExternalTable(stmt) => {
1590            validate_param(&stmt.name, query_ctx)?;
1591        }
1592        Statement::CreateFlow(stmt) => {
1593            // TODO: should also validate source table name here?
1594            validate_param(&stmt.sink_table_name, query_ctx)?;
1595        }
1596        #[cfg(feature = "enterprise")]
1597        Statement::CreateTrigger(stmt) => {
1598            validate_param(&stmt.trigger_name, query_ctx)?;
1599        }
1600        Statement::CreateView(stmt) => {
1601            validate_param(&stmt.name, query_ctx)?;
1602        }
1603        Statement::AlterTable(stmt) => {
1604            validate_param(stmt.table_name(), query_ctx)?;
1605        }
1606        #[cfg(feature = "enterprise")]
1607        Statement::AlterTrigger(_) => {}
1608        // set/show variable now only alter/show variable in session
1609        Statement::SetVariables(_) | Statement::ShowVariables(_) => {}
1610        // show charset and show collation won't be checked
1611        Statement::ShowCharset(_) | Statement::ShowCollation(_) => {}
1612
1613        Statement::Comment(comment) => match &comment.object {
1614            CommentObject::Table(table) => validate_param(table, query_ctx)?,
1615            CommentObject::Column { table, .. } => validate_param(table, query_ctx)?,
1616            CommentObject::Flow(flow) => validate_flow(flow, query_ctx)?,
1617        },
1618
1619        Statement::Insert(insert) => {
1620            let name = insert.table_name().context(ParseSqlSnafu)?;
1621            validate_param(name, query_ctx)?;
1622        }
1623        Statement::CreateTable(stmt) => {
1624            validate_param(&stmt.name, query_ctx)?;
1625        }
1626        Statement::CreateTableLike(stmt) => {
1627            validate_param(&stmt.table_name, query_ctx)?;
1628            validate_param(&stmt.source_name, query_ctx)?;
1629        }
1630        Statement::DropTable(drop_stmt) => {
1631            for table_name in drop_stmt.table_names() {
1632                validate_param(table_name, query_ctx)?;
1633            }
1634        }
1635        #[cfg(feature = "enterprise")]
1636        Statement::UndropTable(stmt) => {
1637            validate_param(stmt.table_name(), query_ctx)?;
1638        }
1639        Statement::DropView(stmt) => {
1640            validate_param(&stmt.view_name, query_ctx)?;
1641        }
1642        Statement::ShowTables(stmt) => {
1643            validate_db_permission!(stmt, query_ctx);
1644        }
1645        Statement::ShowTableStatus(stmt) => {
1646            validate_db_permission!(stmt, query_ctx);
1647        }
1648        Statement::ShowColumns(stmt) => {
1649            validate_db_permission!(stmt, query_ctx);
1650        }
1651        Statement::ShowIndex(stmt) => {
1652            validate_db_permission!(stmt, query_ctx);
1653        }
1654        Statement::ShowRegion(stmt) => {
1655            validate_db_permission!(stmt, query_ctx);
1656        }
1657        Statement::ShowViews(stmt) => {
1658            validate_db_permission!(stmt, query_ctx);
1659        }
1660        Statement::ShowFlows(stmt) => {
1661            validate_db_permission!(stmt, query_ctx);
1662        }
1663        Statement::ShowFlowStatus(_stmt) => {
1664            // Flow statistics are organized based on the catalog dimension and
1665            // filtered by the current catalog, so there is no need to check the
1666            // permission of the database(schema).
1667        }
1668        #[cfg(feature = "enterprise")]
1669        Statement::ShowTriggers(_stmt) => {
1670            // The trigger is organized based on the catalog dimension, so there
1671            // is no need to check the permission of the database(schema).
1672        }
1673        Statement::ShowStatus(_stmt) => {}
1674        Statement::ShowSearchPath(_stmt) => {}
1675        Statement::DescribeTable(stmt) => {
1676            validate_param(stmt.name(), query_ctx)?;
1677        }
1678        Statement::Copy(sql::statements::copy::Copy::CopyTable(stmt)) => match stmt {
1679            CopyTable::To(copy_table_to) => validate_param(&copy_table_to.table_name, query_ctx)?,
1680            CopyTable::From(copy_table_from) => {
1681                validate_param(&copy_table_from.table_name, query_ctx)?
1682            }
1683        },
1684        Statement::Copy(sql::statements::copy::Copy::CopyDatabase(copy_database)) => {
1685            match copy_database {
1686                CopyDatabase::To(stmt) => validate_database(&stmt.database_name, query_ctx)?,
1687                CopyDatabase::From(stmt) => validate_database(&stmt.database_name, query_ctx)?,
1688            }
1689        }
1690        Statement::TruncateTable(stmt) => {
1691            validate_param(stmt.table_name(), query_ctx)?;
1692        }
1693        // cursor operations are always allowed once it's created
1694        Statement::FetchCursor(_) | Statement::CloseCursor(_) => {}
1695        // User can only kill process in their own catalog.
1696        Statement::Kill(_) => {}
1697        // SHOW PROCESSLIST
1698        Statement::ShowProcesslist(_) => {}
1699    }
1700    Ok(())
1701}
1702
1703fn validate_param(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1704    let (catalog, schema, _) = table_idents_to_full_name(name, query_ctx)
1705        .map_err(BoxedError::new)
1706        .context(ExternalSnafu)?;
1707
1708    validate_catalog_and_schema(&catalog, &schema, query_ctx)
1709        .map_err(BoxedError::new)
1710        .context(SqlExecInterceptedSnafu)
1711}
1712
1713fn validate_flow(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1714    let catalog = match &name.0[..] {
1715        [_flow] => query_ctx.current_catalog().to_string(),
1716        [catalog, _flow] => catalog.to_string_unquoted(),
1717        _ => {
1718            return InvalidSqlSnafu {
1719                err_msg: format!(
1720                    "expect flow name to be <catalog>.<flow_name> or <flow_name>, actual: {name}",
1721                ),
1722            }
1723            .fail();
1724        }
1725    };
1726
1727    let schema = query_ctx.current_schema();
1728
1729    validate_catalog_and_schema(&catalog, &schema, query_ctx)
1730        .map_err(BoxedError::new)
1731        .context(SqlExecInterceptedSnafu)
1732}
1733
1734fn validate_database(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1735    let (catalog, schema) = match &name.0[..] {
1736        [schema] => (
1737            query_ctx.current_catalog().to_string(),
1738            schema.to_string_unquoted(),
1739        ),
1740        [catalog, schema] => (catalog.to_string_unquoted(), schema.to_string_unquoted()),
1741        _ => InvalidSqlSnafu {
1742            err_msg: format!(
1743                "expect database name to be <catalog>.<schema> or <schema>, actual: {name}",
1744            ),
1745        }
1746        .fail()?,
1747    };
1748
1749    validate_catalog_and_schema(&catalog, &schema, query_ctx)
1750        .map_err(BoxedError::new)
1751        .context(SqlExecInterceptedSnafu)
1752}
1753
1754fn is_readonly_plan(plan: &LogicalPlan) -> bool {
1755    !matches!(plan, LogicalPlan::Dml(_) | LogicalPlan::Ddl(_))
1756}
1757
1758fn should_track_statement_process(stmt: &Statement) -> bool {
1759    stmt.is_readonly()
1760        || matches!(stmt, Statement::Insert(insert) if insert.has_non_values_query_source())
1761}
1762
1763fn should_track_plan_process(stmt: Option<&Statement>, plan: &LogicalPlan) -> bool {
1764    is_readonly_plan(plan)
1765        || matches!(stmt, Some(Statement::Insert(insert)) if insert.has_non_values_query_source())
1766}
1767
1768#[cfg(test)]
1769mod tests {
1770    use std::any::Any;
1771    use std::collections::HashMap;
1772    use std::future::Future;
1773    use std::pin::Pin;
1774    use std::sync::Arc;
1775    use std::task::{Context, Poll};
1776    use std::time::Duration;
1777
1778    use api::prom_store::remote::label_matcher::Type as PromMatcherType;
1779    use api::prom_store::remote::{
1780        Label, LabelMatcher, Query as RemoteQuery, ReadRequest, ReadResponse, Sample,
1781    };
1782    use api::v1::greptime_request::Request;
1783    use api::v1::meta::{ProcedureDetailResponse, ReconcileRequest, ReconcileResponse};
1784    use api::v1::query_request::Query;
1785    use auth::{
1786        DASHBOARD_DELETE, DASHBOARD_QUERY, DASHBOARD_SAVE, JAEGER_QUERY, PIPELINE_DELETE,
1787        PIPELINE_INSERT, PIPELINE_QUERY, PermissionAction, PermissionResp, UserInfo, UserInfoRef,
1788    };
1789    use catalog::process_manager::{ProcessManager, QueryStatement, SlowQueryTimer};
1790    use common_base::Plugins;
1791    use common_catalog::consts::DEFAULT_PRIVATE_SCHEMA_NAME;
1792    use common_error::ext::{BoxedError, ErrorExt, PlainError};
1793    use common_error::status_code::StatusCode;
1794    use common_event_recorder::{Event, EventRecorder, EventTypeFilter, EventTypeFilterRef};
1795    use common_frontend::slow_query_event::SlowQueryEvent;
1796    use common_meta::cache::LayeredCacheRegistryBuilder;
1797    use common_meta::kv_backend::memory::MemoryKvBackend;
1798    use common_meta::procedure_executor::{ExecutorContext, ProcedureExecutor};
1799    use common_meta::rpc::ddl::{DdlTask, SubmitDdlTaskRequest, SubmitDdlTaskResponse};
1800    use common_meta::rpc::procedure::{
1801        MigrateRegionRequest, MigrateRegionResponse, ProcedureStateResponse,
1802    };
1803    use common_query::prelude::greptime_value;
1804    use common_query::{Output, OutputMeta};
1805    use common_recordbatch::{
1806        OrderOption, RecordBatch, RecordBatchStream, SendableRecordBatchStream,
1807    };
1808    use common_telemetry::logging::SlowQueriesRecordType;
1809    use datafusion::arrow::datatypes::{DataType, Field, Schema, SchemaRef};
1810    use datafusion::physical_plan::empty::EmptyExec;
1811    use datafusion_expr::dml::InsertOp;
1812    use datafusion_expr::{LogicalPlanBuilder, LogicalTableSource};
1813    use datatypes::prelude::ConcreteDataType;
1814    use datatypes::schema::{ColumnSchema, Schema as GtSchema, SchemaRef as GtSchemaRef};
1815    use datatypes::vectors::{
1816        Float64Vector, StringVector, TimestampMillisecondVector, TimestampNanosecondVector,
1817        VectorRef,
1818    };
1819    use log_query::LogQuery;
1820    use prost::Message;
1821    use query::query_engine::options::QueryOptions;
1822    use servers::query_handler::{
1823        DashboardHandler, JaegerQueryHandler, LogQueryHandler, PipelineHandler, PipelineHandlerRef,
1824        PromStoreProtocolHandler,
1825    };
1826    use session::context::{Channel, ConnInfo, QueryContext, QueryContextBuilder};
1827    use snafu::{Location, Snafu};
1828    use sql::dialect::GreptimeDbDialect;
1829    use store_api::data_source::DataSource;
1830    use store_api::metric_engine_consts::{
1831        LOGICAL_TABLE_METADATA_KEY, METRIC_ENGINE_NAME, PHYSICAL_TABLE_METADATA_KEY,
1832    };
1833    use store_api::storage::ScanRequest;
1834    use strfmt::Format;
1835    use table::metadata::{
1836        FilterPushDownType, TableInfo, TableInfoBuilder, TableMetaBuilder, TableType,
1837    };
1838    use table::table_name::TableName;
1839    use table::test_util::{EmptyTable, MemTable};
1840    use table::{Table, TableRef};
1841    use tokio::sync::{mpsc, oneshot};
1842    use tower::ServiceExt;
1843
1844    use super::*;
1845    use crate::frontend::FrontendOptions;
1846    use crate::instance::builder::FrontendBuilder;
1847
1848    fn parse_test_sql(sql: &str) -> Vec<Statement> {
1849        parse_stmt(sql, &GreptimeDbDialect {}).unwrap()
1850    }
1851
1852    #[derive(Debug, Default)]
1853    struct RecordingSlowQueryEventRecorder {
1854        payloads: std::sync::Mutex<Vec<serde_json::Value>>,
1855    }
1856
1857    impl EventRecorder for RecordingSlowQueryEventRecorder {
1858        fn record(&self, event: Box<dyn Event>) {
1859            let event = event
1860                .as_any()
1861                .downcast_ref::<SlowQueryEvent>()
1862                .expect("expected a slow query event");
1863            self.payloads.lock().unwrap().push(event.payload.clone());
1864        }
1865
1866        fn event_type_filter(&self) -> EventTypeFilterRef {
1867            Arc::new(EventTypeFilter::All)
1868        }
1869
1870        fn close(&self) {}
1871    }
1872
1873    #[test]
1874    fn test_validate_analyze_stream_statement_strictness() {
1875        for sql in [
1876            "select 1",
1877            "explain analyze select 1",
1878            "explain analyze verbose format text select 1",
1879            "explain analyze verbose format graphviz select 1",
1880        ] {
1881            let mut stmts = parse_test_sql(sql);
1882            assert!(
1883                validate_analyze_stream_statement(&mut stmts[0]).is_err(),
1884                "{sql}"
1885            );
1886        }
1887
1888        for sql in [
1889            "explain analyze verbose select 1",
1890            "explain analyze verbose format json select 1",
1891        ] {
1892            let mut stmts = parse_test_sql(sql);
1893            assert!(
1894                validate_analyze_stream_statement(&mut stmts[0]).is_ok(),
1895                "{sql}"
1896            );
1897            let Statement::Explain(explain) = &stmts[0] else {
1898                unreachable!();
1899            };
1900            assert!(explain.format.is_none());
1901        }
1902
1903        assert_eq!(
1904            parse_test_sql("explain analyze verbose select 1; select 2").len(),
1905            2
1906        );
1907
1908        assert!(is_explain_analyze_verbose(
1909            &parse_test_sql("explain analyze verbose select 1")[0]
1910        ));
1911        for sql in [
1912            "select 1",
1913            "explain select 1",
1914            "explain analyze select 1",
1915            "explain verbose select 1",
1916        ] {
1917            assert!(
1918                !is_explain_analyze_verbose(&parse_test_sql(sql)[0]),
1919                "{sql}"
1920            );
1921        }
1922    }
1923
1924    #[derive(Debug, Snafu)]
1925    enum TestError {
1926        #[snafu(display("Failed to build test cache registry"))]
1927        BuildCacheRegistry {
1928            source: cache::error::Error,
1929            #[snafu(implicit)]
1930            location: Location,
1931        },
1932
1933        #[snafu(display("Failed to build test table meta for table: {table_name}"))]
1934        BuildTableMeta {
1935            table_name: String,
1936            source: table::metadata::TableMetaBuilderError,
1937            #[snafu(implicit)]
1938            location: Location,
1939        },
1940
1941        #[snafu(display("Failed to build test table info for table: {table_name}"))]
1942        BuildTableInfo {
1943            table_name: String,
1944            source: table::metadata::TableInfoBuilderError,
1945            #[snafu(implicit)]
1946            location: Location,
1947        },
1948
1949        #[snafu(display("Failed to register test table: {table_name}"))]
1950        RegisterTable {
1951            table_name: String,
1952            source: catalog::error::Error,
1953            #[snafu(implicit)]
1954            location: Location,
1955        },
1956
1957        #[snafu(display("Failed to build test frontend instance"))]
1958        BuildFrontend {
1959            source: crate::error::Error,
1960            #[snafu(implicit)]
1961            location: Location,
1962        },
1963
1964        #[snafu(display("Expected exactly one output for SQL `{sql}`, got {actual}"))]
1965        UnexpectedOutputCount {
1966            sql: String,
1967            actual: usize,
1968            #[snafu(implicit)]
1969            location: Location,
1970        },
1971
1972        #[snafu(display("Failed to execute SQL `{sql}`"))]
1973        ExecuteSql {
1974            sql: String,
1975            source: crate::error::Error,
1976            #[snafu(implicit)]
1977            location: Location,
1978        },
1979
1980        #[snafu(display("Timed out waiting for insert-select start notification"))]
1981        InsertStartTimeout {
1982            source: tokio::time::error::Elapsed,
1983            #[snafu(implicit)]
1984            location: Location,
1985        },
1986
1987        #[snafu(display("Insert-select start notification channel closed"))]
1988        InsertStartChannelClosed {
1989            #[snafu(implicit)]
1990            location: Location,
1991        },
1992
1993        #[snafu(display("Failed to release blocking insert-select interceptor"))]
1994        ReleaseBlockedInsert {
1995            #[snafu(implicit)]
1996            location: Location,
1997        },
1998
1999        #[snafu(display("Timed out waiting for insert-select source to be polled"))]
2000        SourcePollTimeout {
2001            source: tokio::time::error::Elapsed,
2002            #[snafu(implicit)]
2003            location: Location,
2004        },
2005
2006        #[snafu(display("Insert-select source poll notification channel closed"))]
2007        SourcePollChannelClosed {
2008            source: oneshot::error::RecvError,
2009            #[snafu(implicit)]
2010            location: Location,
2011        },
2012
2013        #[snafu(display("Timed out waiting for insert task to finish"))]
2014        InsertTaskTimeout {
2015            source: tokio::time::error::Elapsed,
2016            #[snafu(implicit)]
2017            location: Location,
2018        },
2019
2020        #[snafu(display("Insert task panicked"))]
2021        InsertTaskPanic {
2022            source: tokio::task::JoinError,
2023            #[snafu(implicit)]
2024            location: Location,
2025        },
2026
2027        #[snafu(display("Expected insert-select to be cancelled"))]
2028        InsertSelectNotCancelled {
2029            #[snafu(implicit)]
2030            location: Location,
2031        },
2032    }
2033
2034    type TestResult<T> = std::result::Result<T, TestError>;
2035
2036    fn parse_one_sql(sql: &str) -> Statement {
2037        parse_stmt(sql, &GreptimeDbDialect {}).unwrap().remove(0)
2038    }
2039
2040    fn test_query_ctx(process_id: u32) -> QueryContextRef {
2041        Arc::new(
2042            QueryContextBuilder::default()
2043                .channel(Channel::Mysql)
2044                .conn_info(ConnInfo::new(None, Channel::Mysql))
2045                .process_id(process_id)
2046                .build(),
2047        )
2048    }
2049
2050    #[derive(Debug)]
2051    struct AdminUserInfo;
2052
2053    impl UserInfo for AdminUserInfo {
2054        fn as_any(&self) -> &dyn Any {
2055            self
2056        }
2057
2058        fn username(&self) -> &str {
2059            "admin"
2060        }
2061
2062        fn is_admin(&self) -> bool {
2063            true
2064        }
2065    }
2066
2067    struct RejectUnresolvedPermissionChecker;
2068
2069    impl PermissionChecker for RejectUnresolvedPermissionChecker {
2070        fn check_permission(
2071            &self,
2072            _user_info: UserInfoRef,
2073            _req: PermissionReq,
2074        ) -> auth::error::Result<PermissionResp> {
2075            Ok(PermissionResp::Allow)
2076        }
2077
2078        fn check_permission_with_table_targets(
2079            &self,
2080            _user_info: UserInfoRef,
2081            _req: PermissionReq,
2082            targets: PermissionTableTargets,
2083        ) -> auth::error::Result<PermissionResp> {
2084            let reject = match targets {
2085                PermissionTableTargets::Unresolved => true,
2086                PermissionTableTargets::Resolved(targets) => {
2087                    targets.iter().any(|target| target.table == "denied")
2088                }
2089            };
2090            Ok(if reject {
2091                PermissionResp::Reject
2092            } else {
2093                PermissionResp::Allow
2094            })
2095        }
2096    }
2097
2098    #[derive(Debug, PartialEq, Eq)]
2099    struct CheckedAction {
2100        action: PermissionAction,
2101        targets: Option<PermissionTableTargets>,
2102    }
2103
2104    #[derive(Default)]
2105    struct RejectEndpointPermissionChecker {
2106        checks: std::sync::Mutex<Vec<CheckedAction>>,
2107    }
2108
2109    impl RejectEndpointPermissionChecker {
2110        fn reject(
2111            &self,
2112            action: PermissionAction,
2113            targets: Option<PermissionTableTargets>,
2114        ) -> PermissionResp {
2115            self.checks
2116                .lock()
2117                .unwrap()
2118                .push(CheckedAction { action, targets });
2119            PermissionResp::Reject
2120        }
2121
2122        fn take_check(&self) -> CheckedAction {
2123            let mut checks = self.checks.lock().unwrap();
2124            assert_eq!(1, checks.len());
2125            checks.pop().unwrap()
2126        }
2127    }
2128
2129    impl PermissionChecker for RejectEndpointPermissionChecker {
2130        fn check_permission(
2131            &self,
2132            _user_info: UserInfoRef,
2133            req: PermissionReq,
2134        ) -> auth::error::Result<PermissionResp> {
2135            Ok(match req {
2136                PermissionReq::Action(action) => self.reject(action, None),
2137                _ => PermissionResp::Allow,
2138            })
2139        }
2140
2141        fn check_permission_with_table_targets(
2142            &self,
2143            _user_info: UserInfoRef,
2144            req: PermissionReq,
2145            targets: PermissionTableTargets,
2146        ) -> auth::error::Result<PermissionResp> {
2147            Ok(match req {
2148                PermissionReq::Action(action) => self.reject(action, Some(targets)),
2149                _ => PermissionResp::Allow,
2150            })
2151        }
2152    }
2153
2154    struct WriteOnlyPermissionChecker;
2155
2156    impl PermissionChecker for WriteOnlyPermissionChecker {
2157        fn check_permission(
2158            &self,
2159            _user_info: UserInfoRef,
2160            req: PermissionReq,
2161        ) -> auth::error::Result<PermissionResp> {
2162            Ok(if req.is_readonly() {
2163                PermissionResp::Reject
2164            } else {
2165                PermissionResp::Allow
2166            })
2167        }
2168
2169        fn check_permission_with_table_targets(
2170            &self,
2171            user_info: UserInfoRef,
2172            req: PermissionReq,
2173            _targets: PermissionTableTargets,
2174        ) -> auth::error::Result<PermissionResp> {
2175            self.check_permission(user_info, req)
2176        }
2177    }
2178
2179    #[derive(Default)]
2180    struct TargetIndependentPermissionChecker {
2181        checks: atomic::AtomicUsize,
2182    }
2183
2184    impl PermissionChecker for TargetIndependentPermissionChecker {
2185        fn check_permission(
2186            &self,
2187            _user_info: UserInfoRef,
2188            _req: PermissionReq,
2189        ) -> auth::error::Result<PermissionResp> {
2190            self.checks.fetch_add(1, atomic::Ordering::Relaxed);
2191            Ok(PermissionResp::Allow)
2192        }
2193
2194        fn uses_table_targets(&self) -> bool {
2195            false
2196        }
2197
2198        fn check_permission_with_table_targets(
2199            &self,
2200            user_info: UserInfoRef,
2201            req: PermissionReq,
2202            _targets: PermissionTableTargets,
2203        ) -> auth::error::Result<PermissionResp> {
2204            self.check_permission(user_info, req)
2205        }
2206    }
2207
2208    struct BlockingInsertSelectInterceptor {
2209        started_tx: mpsc::UnboundedSender<()>,
2210        finish_rx: std::sync::Mutex<Option<oneshot::Receiver<()>>>,
2211    }
2212
2213    impl BlockingInsertSelectInterceptor {
2214        fn new(started_tx: mpsc::UnboundedSender<()>, finish_rx: oneshot::Receiver<()>) -> Self {
2215            Self {
2216                started_tx,
2217                finish_rx: std::sync::Mutex::new(Some(finish_rx)),
2218            }
2219        }
2220    }
2221
2222    impl SqlQueryInterceptor for BlockingInsertSelectInterceptor {
2223        type Error = Error;
2224
2225        fn pre_execute(
2226            &self,
2227            statement: Option<&Statement>,
2228            _plan: Option<&LogicalPlan>,
2229            _query_ctx: QueryContextRef,
2230        ) -> Result<()> {
2231            let Some(Statement::Insert(insert)) = statement else {
2232                return Ok(());
2233            };
2234            if !insert.has_non_values_query_source() {
2235                return Ok(());
2236            }
2237
2238            let finish_rx = self.finish_rx.lock().unwrap().take().unwrap();
2239            let _ = self.started_tx.send(());
2240            tokio::task::block_in_place(|| {
2241                tokio::runtime::Handle::current()
2242                    .block_on(finish_rx)
2243                    .unwrap();
2244            });
2245            Ok(())
2246        }
2247    }
2248
2249    struct PendingRecordBatchStream {
2250        schema: GtSchemaRef,
2251        polled_tx: Option<oneshot::Sender<()>>,
2252        _finish_tx: oneshot::Sender<()>,
2253        finish_rx: Pin<Box<oneshot::Receiver<()>>>,
2254    }
2255
2256    impl RecordBatchStream for PendingRecordBatchStream {
2257        fn schema(&self) -> GtSchemaRef {
2258            self.schema.clone()
2259        }
2260
2261        fn output_ordering(&self) -> Option<&[OrderOption]> {
2262            None
2263        }
2264
2265        fn metrics(&self) -> Option<common_recordbatch::adapter::RecordBatchMetrics> {
2266            None
2267        }
2268    }
2269
2270    impl Stream for PendingRecordBatchStream {
2271        type Item = common_recordbatch::error::Result<RecordBatch>;
2272
2273        fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
2274            if let Some(polled_tx) = self.polled_tx.take() {
2275                let _ = polled_tx.send(());
2276            }
2277
2278            match self.finish_rx.as_mut().poll(cx) {
2279                Poll::Ready(_) => Poll::Ready(None),
2280                Poll::Pending => Poll::Pending,
2281            }
2282        }
2283    }
2284
2285    impl Unpin for PendingRecordBatchStream {}
2286
2287    #[test]
2288    fn test_record_explain_analyze_timeout_uses_empty_metrics_without_plan() {
2289        let event_recorder = Arc::new(RecordingSlowQueryEventRecorder::default());
2290        let timer = SlowQueryTimer::new(
2291            QueryStatement::Plan("EXPLAIN ANALYZE VERBOSE SELECT 1".to_string()),
2292            "public".to_string(),
2293            Duration::from_secs(3600),
2294            0.0,
2295            SlowQueriesRecordType::SystemTable,
2296            event_recorder.clone(),
2297        );
2298        let timeout_recorder = timer.recorder();
2299
2300        record_explain_analyze_timeout(Some(&timeout_recorder), None);
2301        drop(timer);
2302
2303        let payloads = event_recorder.payloads.lock().unwrap();
2304        assert_eq!(payloads.len(), 1);
2305        assert_eq!(payloads[0]["timed_out"], true);
2306        assert_eq!(payloads[0]["metrics"], serde_json::json!([]));
2307    }
2308
2309    #[tokio::test]
2310    async fn test_attach_timeout_records_explain_analyze_metrics() {
2311        let event_recorder = Arc::new(RecordingSlowQueryEventRecorder::default());
2312        let timer = SlowQueryTimer::new(
2313            QueryStatement::Plan("EXPLAIN ANALYZE VERBOSE SELECT 1".to_string()),
2314            "public".to_string(),
2315            Duration::from_secs(3600),
2316            0.0,
2317            SlowQueriesRecordType::SystemTable,
2318            event_recorder.clone(),
2319        );
2320        let timeout_recorder = timer.recorder();
2321        let plan: Arc<dyn ExecutionPlan> = Arc::new(EmptyExec::new(Arc::new(Schema::empty())));
2322        let (finish_tx, finish_rx) = oneshot::channel();
2323        let stream = PendingRecordBatchStream {
2324            schema: Arc::new(GtSchema::new(vec![])),
2325            polled_tx: None,
2326            _finish_tx: finish_tx,
2327            finish_rx: Box::pin(finish_rx),
2328        };
2329        let output = Output::new(
2330            OutputData::Stream(Box::pin(stream)),
2331            OutputMeta::new_with_plan(plan),
2332        );
2333        let output =
2334            attach_timeout(output, Duration::from_millis(10), Some(timeout_recorder)).unwrap();
2335        let OutputData::Stream(mut stream) = output.data else {
2336            unreachable!();
2337        };
2338
2339        let err = stream.next().await.unwrap().unwrap_err();
2340        assert_eq!(err.to_string(), "Stream timeout");
2341        drop(stream);
2342        drop(timer);
2343
2344        let payloads = event_recorder.payloads.lock().unwrap();
2345        assert_eq!(payloads.len(), 1);
2346        assert_eq!(payloads[0]["timed_out"], true);
2347        assert!(
2348            payloads[0]["metrics"]
2349                .as_array()
2350                .is_some_and(|metrics| !metrics.is_empty())
2351        );
2352    }
2353
2354    struct PendingDataSource {
2355        schema: GtSchemaRef,
2356        polled_tx: std::sync::Mutex<Option<oneshot::Sender<()>>>,
2357    }
2358
2359    impl DataSource for PendingDataSource {
2360        fn get_stream(
2361            &self,
2362            _request: ScanRequest,
2363        ) -> std::result::Result<SendableRecordBatchStream, BoxedError> {
2364            let (finish_tx, finish_rx) = oneshot::channel();
2365            let mut polled_tx = self.polled_tx.lock().map_err(|_| {
2366                BoxedError::new(PlainError::new(
2367                    "pending data source lock poisoned".to_string(),
2368                    StatusCode::Unexpected,
2369                ))
2370            })?;
2371            Ok(Box::pin(PendingRecordBatchStream {
2372                schema: self.schema.clone(),
2373                polled_tx: polled_tx.take(),
2374                _finish_tx: finish_tx,
2375                finish_rx: Box::pin(finish_rx),
2376            }))
2377        }
2378    }
2379
2380    struct NoopProcedureExecutor;
2381
2382    #[async_trait::async_trait]
2383    impl ProcedureExecutor for NoopProcedureExecutor {
2384        async fn submit_ddl_task(
2385            &self,
2386            _ctx: ExecutorContext,
2387            _request: SubmitDdlTaskRequest,
2388        ) -> common_meta::error::Result<SubmitDdlTaskResponse> {
2389            common_meta::error::UnsupportedSnafu {
2390                operation: "submit_ddl_task",
2391            }
2392            .fail()
2393        }
2394
2395        async fn migrate_region(
2396            &self,
2397            _ctx: &ExecutorContext,
2398            _request: MigrateRegionRequest,
2399        ) -> common_meta::error::Result<MigrateRegionResponse> {
2400            common_meta::error::UnsupportedSnafu {
2401                operation: "migrate_region",
2402            }
2403            .fail()
2404        }
2405
2406        async fn reconcile(
2407            &self,
2408            _ctx: &ExecutorContext,
2409            _request: ReconcileRequest,
2410        ) -> common_meta::error::Result<ReconcileResponse> {
2411            common_meta::error::UnsupportedSnafu {
2412                operation: "reconcile",
2413            }
2414            .fail()
2415        }
2416
2417        async fn query_procedure_state(
2418            &self,
2419            _ctx: &ExecutorContext,
2420            _pid: &str,
2421        ) -> common_meta::error::Result<ProcedureStateResponse> {
2422            common_meta::error::UnsupportedSnafu {
2423                operation: "query_procedure_state",
2424            }
2425            .fail()
2426        }
2427
2428        async fn list_procedures(
2429            &self,
2430            _ctx: &ExecutorContext,
2431        ) -> common_meta::error::Result<ProcedureDetailResponse> {
2432            common_meta::error::UnsupportedSnafu {
2433                operation: "list_procedures",
2434            }
2435            .fail()
2436        }
2437    }
2438
2439    /// A test [`ProcedureExecutor`] that completes create/drop DDL tasks against the
2440    /// in-memory catalog, mimicking what the meta DDL procedures do in production.
2441    /// This allows happy-path DDL requests (create/drop table/view) to be exercised
2442    /// end to end through the gRPC ingress.
2443    struct MockProcedureExecutor {
2444        catalog_manager: Arc<catalog::memory::MemoryCatalogManager>,
2445        next_table_id: std::sync::atomic::AtomicU32,
2446        submitted: std::sync::Mutex<Vec<DdlTask>>,
2447    }
2448
2449    impl MockProcedureExecutor {
2450        fn new(catalog_manager: Arc<catalog::memory::MemoryCatalogManager>) -> Self {
2451            Self {
2452                catalog_manager,
2453                next_table_id: std::sync::atomic::AtomicU32::new(1026),
2454                submitted: std::sync::Mutex::new(Vec::new()),
2455            }
2456        }
2457    }
2458
2459    #[async_trait::async_trait]
2460    impl ProcedureExecutor for MockProcedureExecutor {
2461        async fn submit_ddl_task(
2462            &self,
2463            _ctx: ExecutorContext,
2464            request: SubmitDdlTaskRequest,
2465        ) -> common_meta::error::Result<SubmitDdlTaskResponse> {
2466            self.submitted.lock().unwrap().push(request.task.clone());
2467            match request.task {
2468                DdlTask::CreateTable(task) => {
2469                    let table_id = self
2470                        .next_table_id
2471                        .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2472                    let mut table_info = task.table_info;
2473                    table_info.ident.table_id = table_id;
2474                    self.catalog_manager
2475                        .register_table_sync(catalog::RegisterTableRequest {
2476                            catalog: table_info.catalog_name.clone(),
2477                            schema: table_info.schema_name.clone(),
2478                            table_name: table_info.name.clone(),
2479                            table_id,
2480                            table: table::dist_table::DistTable::table(Arc::new(table_info)),
2481                        })
2482                        .map_err(BoxedError::new)
2483                        .context(common_meta::error::ExternalSnafu)?;
2484                    Ok(SubmitDdlTaskResponse {
2485                        key: Vec::new(),
2486                        table_ids: vec![table_id],
2487                    })
2488                }
2489                DdlTask::CreateView(task) => {
2490                    let view_id = self
2491                        .next_table_id
2492                        .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2493                    let mut view_info = task.view_info;
2494                    view_info.ident.table_id = view_id;
2495                    self.catalog_manager
2496                        .register_table_sync(catalog::RegisterTableRequest {
2497                            catalog: task.create_view.catalog_name.clone(),
2498                            schema: task.create_view.schema_name.clone(),
2499                            table_name: task.create_view.view_name.clone(),
2500                            table_id: view_id,
2501                            table: table::dist_table::DistTable::table(Arc::new(view_info)),
2502                        })
2503                        .map_err(BoxedError::new)
2504                        .context(common_meta::error::ExternalSnafu)?;
2505                    Ok(SubmitDdlTaskResponse {
2506                        key: Vec::new(),
2507                        table_ids: vec![view_id],
2508                    })
2509                }
2510                DdlTask::DropView(task) => {
2511                    self.catalog_manager
2512                        .deregister_table_sync(catalog::DeregisterTableRequest {
2513                            catalog: task.catalog.clone(),
2514                            schema: task.schema.clone(),
2515                            table_name: task.view.clone(),
2516                        })
2517                        .map_err(BoxedError::new)
2518                        .context(common_meta::error::ExternalSnafu)?;
2519                    Ok(SubmitDdlTaskResponse::default())
2520                }
2521                other => common_meta::error::UnsupportedSnafu {
2522                    operation: format!("mock submit_ddl_task: {other:?}"),
2523                }
2524                .fail(),
2525            }
2526        }
2527
2528        async fn migrate_region(
2529            &self,
2530            _ctx: &ExecutorContext,
2531            _request: MigrateRegionRequest,
2532        ) -> common_meta::error::Result<MigrateRegionResponse> {
2533            common_meta::error::UnsupportedSnafu {
2534                operation: "migrate_region",
2535            }
2536            .fail()
2537        }
2538
2539        async fn reconcile(
2540            &self,
2541            _ctx: &ExecutorContext,
2542            _request: ReconcileRequest,
2543        ) -> common_meta::error::Result<ReconcileResponse> {
2544            common_meta::error::UnsupportedSnafu {
2545                operation: "reconcile",
2546            }
2547            .fail()
2548        }
2549
2550        async fn query_procedure_state(
2551            &self,
2552            _ctx: &ExecutorContext,
2553            _pid: &str,
2554        ) -> common_meta::error::Result<ProcedureStateResponse> {
2555            common_meta::error::UnsupportedSnafu {
2556                operation: "query_procedure_state",
2557            }
2558            .fail()
2559        }
2560
2561        async fn list_procedures(
2562            &self,
2563            _ctx: &ExecutorContext,
2564        ) -> common_meta::error::Result<ProcedureDetailResponse> {
2565            common_meta::error::UnsupportedSnafu {
2566                operation: "list_procedures",
2567            }
2568            .fail()
2569        }
2570    }
2571
2572    fn test_cache_registry(
2573        kv_backend: common_meta::kv_backend::KvBackendRef,
2574    ) -> TestResult<common_meta::cache::LayeredCacheRegistryRef> {
2575        Ok(Arc::new(
2576            cache::with_default_composite_cache_registry(
2577                LayeredCacheRegistryBuilder::default()
2578                    .add_cache_registry(cache::build_fundamental_cache_registry(kv_backend)),
2579            )
2580            .context(BuildCacheRegistrySnafu)?
2581            .build(),
2582        ))
2583    }
2584
2585    fn test_table_info(table_id: u32, table_name: &str) -> TestResult<TableInfo> {
2586        let schema = Arc::new(GtSchema::new(vec![
2587            ColumnSchema::new("id", ConcreteDataType::int32_datatype(), false),
2588            ColumnSchema::new(
2589                "ts",
2590                ConcreteDataType::timestamp_millisecond_datatype(),
2591                false,
2592            )
2593            .with_time_index(true),
2594        ]));
2595        let table_meta = TableMetaBuilder::empty()
2596            .schema(schema)
2597            .primary_key_indices(vec![0])
2598            .value_indices(vec![1])
2599            .next_column_id(1024)
2600            .build()
2601            .with_context(|_| BuildTableMetaSnafu {
2602                table_name: table_name.to_string(),
2603            })?;
2604
2605        TableInfoBuilder::new(table_name, table_meta)
2606            .table_id(table_id)
2607            .build()
2608            .with_context(|_| BuildTableInfoSnafu {
2609                table_name: table_name.to_string(),
2610            })
2611    }
2612
2613    fn test_table(table_id: u32, table_name: &str) -> TestResult<table::TableRef> {
2614        let table_info = test_table_info(table_id, table_name)?;
2615        Ok(EmptyTable::from_table_info(&table_info))
2616    }
2617
2618    fn test_physical_table(table_id: u32, table_name: &str) -> TestResult<table::TableRef> {
2619        let mut table_info = test_table_info(table_id, table_name)?;
2620        table_info
2621            .meta
2622            .options
2623            .extra_options
2624            .insert(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new());
2625        Ok(EmptyTable::from_table_info(&table_info))
2626    }
2627
2628    fn test_logical_table(table_id: u32, table_name: &str) -> TestResult<table::TableRef> {
2629        let mut table_info = test_table_info(table_id, table_name)?;
2630        table_info.meta.engine = METRIC_ENGINE_NAME.to_string();
2631        table_info.meta.options.extra_options.insert(
2632            LOGICAL_TABLE_METADATA_KEY.to_string(),
2633            "physical_metric".to_string(),
2634        );
2635        Ok(EmptyTable::from_table_info(&table_info))
2636    }
2637
2638    fn test_metric_names_table() -> TableRef {
2639        let schema = Arc::new(GtSchema::new(vec![
2640            ColumnSchema::new("table_catalog", ConcreteDataType::string_datatype(), false),
2641            ColumnSchema::new("table_schema", ConcreteDataType::string_datatype(), false),
2642            ColumnSchema::new("table_name", ConcreteDataType::string_datatype(), false),
2643            ColumnSchema::new("engine", ConcreteDataType::string_datatype(), false),
2644            ColumnSchema::new("create_options", ConcreteDataType::string_datatype(), false),
2645        ]));
2646        let columns: Vec<VectorRef> = vec![
2647            Arc::new(StringVector::from(vec!["greptime", "greptime"])),
2648            Arc::new(StringVector::from(vec!["public", "public"])),
2649            Arc::new(StringVector::from(vec!["denied", "target"])),
2650            Arc::new(StringVector::from(vec!["metric", "metric"])),
2651            Arc::new(StringVector::from(vec![
2652                "on_physical_table=physical_metric",
2653                "on_physical_table=physical_metric",
2654            ])),
2655        ];
2656        let record_batch = RecordBatch::new(schema, columns).unwrap();
2657        MemTable::new_with_catalog(
2658            "tables",
2659            record_batch,
2660            2048,
2661            "greptime".to_string(),
2662            "information_schema".to_string(),
2663        )
2664    }
2665
2666    fn test_pipeline_table() -> TableRef {
2667        let schema = Arc::new(GtSchema::new(vec![
2668            ColumnSchema::new("name", ConcreteDataType::string_datatype(), false),
2669            ColumnSchema::new("schema", ConcreteDataType::string_datatype(), false),
2670            ColumnSchema::new("content_type", ConcreteDataType::string_datatype(), false),
2671            ColumnSchema::new("pipeline", ConcreteDataType::string_datatype(), false),
2672            ColumnSchema::new(
2673                "created_at",
2674                ConcreteDataType::timestamp_nanosecond_datatype(),
2675                false,
2676            )
2677            .with_time_index(true),
2678        ]));
2679        let columns: Vec<VectorRef> = vec![
2680            Arc::new(StringVector::from(vec!["pipeline"])),
2681            Arc::new(StringVector::from(vec!["public"])),
2682            Arc::new(StringVector::from(vec!["application/yaml"])),
2683            Arc::new(StringVector::from(vec![
2684                "transform:\n- field: ts\n  type: timestamp, ns\n  index: time\n",
2685            ])),
2686            Arc::new(TimestampNanosecondVector::from_values([1])),
2687        ];
2688        let record_batch = RecordBatch::new(schema, columns).unwrap();
2689        MemTable::new_with_catalog(
2690            "pipelines",
2691            record_batch,
2692            2049,
2693            "greptime".to_string(),
2694            DEFAULT_PRIVATE_SCHEMA_NAME.to_string(),
2695        )
2696    }
2697
2698    fn pending_table(
2699        table_id: u32,
2700        table_name: &str,
2701        polled_tx: oneshot::Sender<()>,
2702    ) -> TestResult<table::TableRef> {
2703        let table_info = test_table_info(table_id, table_name)?;
2704        let data_source = Arc::new(PendingDataSource {
2705            schema: table_info.meta.schema.clone(),
2706            polled_tx: std::sync::Mutex::new(Some(polled_tx)),
2707        });
2708
2709        Ok(Arc::new(Table::new(
2710            Arc::new(table_info),
2711            FilterPushDownType::Unsupported,
2712            data_source,
2713        )))
2714    }
2715
2716    async fn test_instance_with_tables(
2717        source_table: TableRef,
2718        target_table: TableRef,
2719    ) -> TestResult<Instance> {
2720        test_instance_with_plugins(source_table, target_table, Plugins::new()).await
2721    }
2722
2723    async fn test_instance_with_insert_select_interceptor(
2724        interceptor: SqlQueryInterceptorRef<Error>,
2725    ) -> TestResult<Instance> {
2726        let plugins = Plugins::new();
2727        plugins.insert::<SqlQueryInterceptorRef<Error>>(interceptor);
2728
2729        test_instance_with_plugins(
2730            test_table(1024, "source")?,
2731            test_table(1025, "target")?,
2732            plugins,
2733        )
2734        .await
2735    }
2736
2737    async fn test_instance_with_plugins(
2738        source_table: TableRef,
2739        target_table: TableRef,
2740        plugins: Plugins,
2741    ) -> TestResult<Instance> {
2742        test_instance_with_plugins_and_metric_names(source_table, target_table, plugins, None).await
2743    }
2744
2745    async fn test_instance_with_plugins_and_metric_names(
2746        source_table: TableRef,
2747        target_table: TableRef,
2748        plugins: Plugins,
2749        metric_names_table: Option<TableRef>,
2750    ) -> TestResult<Instance> {
2751        let catalog_manager = catalog::memory::MemoryCatalogManager::new_with_table(source_table);
2752        test_instance_with_catalog_manager(
2753            catalog_manager,
2754            target_table,
2755            plugins,
2756            metric_names_table,
2757            Arc::new(NoopProcedureExecutor),
2758        )
2759        .await
2760    }
2761
2762    /// Builds a test frontend `Instance` over the given (already source-registered)
2763    /// catalog manager, completing DDL tasks through `procedure_executor`.
2764    async fn test_instance_with_catalog_manager(
2765        catalog_manager: Arc<catalog::memory::MemoryCatalogManager>,
2766        target_table: TableRef,
2767        plugins: Plugins,
2768        metric_names_table: Option<TableRef>,
2769        procedure_executor: ProcedureExecutorRef,
2770    ) -> TestResult<Instance> {
2771        let kv_backend = Arc::new(MemoryKvBackend::new());
2772        let process_manager = Arc::new(ProcessManager::new("test-frontend".to_string(), None));
2773        let target_table_name = "target";
2774        catalog_manager
2775            .register_table_sync(catalog::RegisterTableRequest {
2776                catalog: "greptime".to_string(),
2777                schema: "public".to_string(),
2778                table_name: target_table_name.to_string(),
2779                table_id: 1025,
2780                table: target_table,
2781            })
2782            .with_context(|_| RegisterTableSnafu {
2783                table_name: target_table_name.to_string(),
2784            })?;
2785        if let Some(table) = metric_names_table {
2786            catalog_manager
2787                .deregister_table_sync(catalog::DeregisterTableRequest {
2788                    catalog: "greptime".to_string(),
2789                    schema: "information_schema".to_string(),
2790                    table_name: "tables".to_string(),
2791                })
2792                .unwrap();
2793            catalog_manager
2794                .register_table_sync(catalog::RegisterTableRequest {
2795                    catalog: "greptime".to_string(),
2796                    schema: "information_schema".to_string(),
2797                    table_name: "tables".to_string(),
2798                    table_id: 2048,
2799                    table,
2800                })
2801                .unwrap();
2802        }
2803        catalog_manager.register_process_list_table(process_manager.clone());
2804
2805        let cache_registry = test_cache_registry(kv_backend.clone())?;
2806
2807        FrontendBuilder::new(
2808            FrontendOptions::default(),
2809            kv_backend,
2810            cache_registry,
2811            catalog_manager,
2812            Arc::new(client::client_manager::NodeClients::default()),
2813            procedure_executor,
2814            process_manager,
2815        )
2816        .with_plugin(plugins)
2817        .try_build()
2818        .await
2819        .context(BuildFrontendSnafu)
2820    }
2821
2822    async fn execute_one_sql(
2823        instance: &Instance,
2824        sql: &str,
2825        query_ctx: QueryContextRef,
2826    ) -> TestResult<Output> {
2827        let mut results = instance.do_query_inner(sql, query_ctx).await;
2828        ensure!(
2829            results.len() == 1,
2830            UnexpectedOutputCountSnafu {
2831                sql: sql.to_string(),
2832                actual: results.len(),
2833            }
2834        );
2835        results.remove(0).with_context(|_| ExecuteSqlSnafu {
2836            sql: sql.to_string(),
2837        })
2838    }
2839
2840    fn assert_permission_denied<T>(result: servers::error::Result<T>) {
2841        let err = match result {
2842            Ok(_) => panic!("request should be rejected"),
2843            Err(err) => err,
2844        };
2845        assert_eq!(StatusCode::PermissionDenied, err.status_code());
2846    }
2847
2848    fn assert_action_checked(
2849        checker: &RejectEndpointPermissionChecker,
2850        action: PermissionAction,
2851        targets: Option<PermissionTableTargets>,
2852    ) {
2853        assert_eq!(CheckedAction { action, targets }, checker.take_check());
2854    }
2855
2856    #[tokio::test]
2857    async fn test_prom_remote_read_with_custom_timestamp_and_value_columns() -> TestResult<()> {
2858        let schema = Arc::new(GtSchema::new(vec![
2859            ColumnSchema::new(
2860                "custom_ts",
2861                ConcreteDataType::timestamp_millisecond_datatype(),
2862                false,
2863            )
2864            .with_time_index(true),
2865            ColumnSchema::new("custom_value", ConcreteDataType::float64_datatype(), false),
2866        ]));
2867        let recordbatch = RecordBatch::new(
2868            schema,
2869            vec![
2870                Arc::new(TimestampMillisecondVector::from_vec(vec![1000, 2000, 3000])) as VectorRef,
2871                Arc::new(Float64Vector::from_vec(vec![1.0, 2.0, 3.0])) as VectorRef,
2872            ],
2873        )
2874        .unwrap();
2875        let instance = test_instance_with_tables(
2876            MemTable::table("custom_metric", recordbatch),
2877            test_table(1025, "target")?,
2878        )
2879        .await?;
2880
2881        let response = PromStoreProtocolHandler::read(
2882            &instance,
2883            ReadRequest {
2884                queries: vec![RemoteQuery {
2885                    start_timestamp_ms: 1500,
2886                    end_timestamp_ms: 2500,
2887                    matchers: vec![LabelMatcher {
2888                        r#type: PromMatcherType::Eq as i32,
2889                        name: servers::prom_store::METRIC_NAME_LABEL.to_string(),
2890                        value: "custom_metric".to_string(),
2891                    }],
2892                    ..Default::default()
2893                }],
2894                ..Default::default()
2895            },
2896            test_query_ctx(1),
2897        )
2898        .await
2899        .unwrap();
2900        let body = servers::prom_store::snappy_decompress(&response.body).unwrap();
2901        let response = ReadResponse::decode(body.as_slice()).unwrap();
2902
2903        assert_eq!(1, response.results.len());
2904        assert_eq!(1, response.results[0].timeseries.len());
2905        let timeseries = &response.results[0].timeseries[0];
2906        assert_eq!(
2907            vec![Label {
2908                name: servers::prom_store::METRIC_NAME_LABEL.to_string(),
2909                value: "custom_metric".to_string(),
2910            }],
2911            timeseries.labels
2912        );
2913        assert_eq!(
2914            vec![Sample {
2915                value: 2.0,
2916                timestamp: 2000,
2917            }],
2918            timeseries.samples
2919        );
2920
2921        Ok(())
2922    }
2923
2924    #[tokio::test]
2925    async fn test_prom_remote_read_prefers_default_value_column() -> TestResult<()> {
2926        let schema = Arc::new(GtSchema::new(vec![
2927            ColumnSchema::new(
2928                "custom_ts",
2929                ConcreteDataType::timestamp_millisecond_datatype(),
2930                false,
2931            )
2932            .with_time_index(true),
2933            ColumnSchema::new("extra_field", ConcreteDataType::float64_datatype(), false),
2934            ColumnSchema::new(
2935                greptime_value(),
2936                ConcreteDataType::float64_datatype(),
2937                false,
2938            ),
2939        ]));
2940        let recordbatch = RecordBatch::new(
2941            schema,
2942            vec![
2943                Arc::new(TimestampMillisecondVector::from_vec(vec![1000, 2000, 3000])) as VectorRef,
2944                Arc::new(Float64Vector::from_vec(vec![99.0, 99.0, 99.0])) as VectorRef,
2945                Arc::new(Float64Vector::from_vec(vec![1.0, 2.0, 3.0])) as VectorRef,
2946            ],
2947        )
2948        .unwrap();
2949        let instance = test_instance_with_tables(
2950            MemTable::table("multi_field_metric", recordbatch),
2951            test_table(1025, "target")?,
2952        )
2953        .await?;
2954
2955        let response = PromStoreProtocolHandler::read(
2956            &instance,
2957            ReadRequest {
2958                queries: vec![RemoteQuery {
2959                    start_timestamp_ms: 1500,
2960                    end_timestamp_ms: 2500,
2961                    matchers: vec![LabelMatcher {
2962                        r#type: PromMatcherType::Eq as i32,
2963                        name: servers::prom_store::METRIC_NAME_LABEL.to_string(),
2964                        value: "multi_field_metric".to_string(),
2965                    }],
2966                    ..Default::default()
2967                }],
2968                ..Default::default()
2969            },
2970            test_query_ctx(1),
2971        )
2972        .await
2973        .unwrap();
2974        let body = servers::prom_store::snappy_decompress(&response.body).unwrap();
2975        let response = ReadResponse::decode(body.as_slice()).unwrap();
2976
2977        assert_eq!(1, response.results.len());
2978        assert_eq!(1, response.results[0].timeseries.len());
2979        let timeseries = &response.results[0].timeseries[0];
2980        assert_eq!(
2981            vec![
2982                Label {
2983                    name: servers::prom_store::METRIC_NAME_LABEL.to_string(),
2984                    value: "multi_field_metric".to_string(),
2985                },
2986                Label {
2987                    name: "extra_field".to_string(),
2988                    value: "99".to_string(),
2989                },
2990            ],
2991            timeseries.labels
2992        );
2993        assert_eq!(
2994            vec![Sample {
2995                value: 2.0,
2996                timestamp: 2000,
2997            }],
2998            timeseries.samples
2999        );
3000
3001        Ok(())
3002    }
3003
3004    #[tokio::test]
3005    async fn test_prom_remote_read_rejects_ambiguous_value_columns() -> TestResult<()> {
3006        let schema = Arc::new(GtSchema::new(vec![
3007            ColumnSchema::new(
3008                "custom_ts",
3009                ConcreteDataType::timestamp_millisecond_datatype(),
3010                false,
3011            )
3012            .with_time_index(true),
3013            ColumnSchema::new("field_a", ConcreteDataType::float64_datatype(), false),
3014            ColumnSchema::new("field_b", ConcreteDataType::float64_datatype(), false),
3015        ]));
3016        let recordbatch = RecordBatch::new(
3017            schema,
3018            vec![
3019                Arc::new(TimestampMillisecondVector::from_vec(vec![1000])) as VectorRef,
3020                Arc::new(Float64Vector::from_vec(vec![1.0])) as VectorRef,
3021                Arc::new(Float64Vector::from_vec(vec![2.0])) as VectorRef,
3022            ],
3023        )
3024        .unwrap();
3025        let instance = test_instance_with_tables(
3026            MemTable::table("ambiguous_metric", recordbatch),
3027            test_table(1025, "target")?,
3028        )
3029        .await?;
3030
3031        let err = PromStoreProtocolHandler::read(
3032            &instance,
3033            ReadRequest {
3034                queries: vec![RemoteQuery {
3035                    matchers: vec![LabelMatcher {
3036                        r#type: PromMatcherType::Eq as i32,
3037                        name: servers::prom_store::METRIC_NAME_LABEL.to_string(),
3038                        value: "ambiguous_metric".to_string(),
3039                    }],
3040                    ..Default::default()
3041                }],
3042                ..Default::default()
3043            },
3044            test_query_ctx(1),
3045        )
3046        .await
3047        .err()
3048        .expect("ambiguous value columns should fail remote read");
3049
3050        assert_eq!(StatusCode::InvalidArguments, err.status_code());
3051        assert!(format!("{err:?}").contains("Ambiguous value column"));
3052
3053        Ok(())
3054    }
3055
3056    #[tokio::test]
3057    async fn test_event_recorder_is_exposed() -> TestResult<()> {
3058        let instance =
3059            test_instance_with_tables(test_table(1024, "source")?, test_table(1025, "target")?)
3060                .await?;
3061
3062        let _event_recorder = instance.event_recorder();
3063
3064        Ok(())
3065    }
3066
3067    #[tokio::test]
3068    async fn test_restricted_endpoint_handlers_check_permissions() -> TestResult<()> {
3069        let checker = Arc::new(RejectEndpointPermissionChecker::default());
3070        let plugins = Plugins::new();
3071        plugins.insert::<PermissionCheckerRef>(checker.clone());
3072        let instance = test_instance_with_plugins(
3073            test_table(1024, "denied")?,
3074            test_table(1025, "target")?,
3075            plugins,
3076        )
3077        .await?;
3078        let mut ctx = test_query_ctx(1);
3079        Arc::get_mut(&mut ctx).unwrap().set_extension(
3080            servers::http::jaeger::JAEGER_QUERY_TABLE_NAME_KEY,
3081            "denied".to_string(),
3082        );
3083        let jaeger_targets = Some(PermissionTableTargets::resolved(vec![
3084            PermissionTableTarget::new("greptime", "public", "denied"),
3085        ]));
3086
3087        assert_permission_denied(JaegerQueryHandler::get_services(&instance, ctx.clone()).await);
3088        assert_action_checked(&checker, JAEGER_QUERY, jaeger_targets.clone());
3089        assert_permission_denied(
3090            JaegerQueryHandler::get_operations(&instance, ctx.clone(), "service", None).await,
3091        );
3092        assert_action_checked(&checker, JAEGER_QUERY, jaeger_targets.clone());
3093        assert_permission_denied(
3094            JaegerQueryHandler::get_trace(&instance, ctx.clone(), "trace", None, None, None).await,
3095        );
3096        assert_action_checked(&checker, JAEGER_QUERY, jaeger_targets.clone());
3097        assert_permission_denied(
3098            JaegerQueryHandler::find_traces(
3099                &instance,
3100                ctx.clone(),
3101                servers::http::jaeger::QueryTraceParams {
3102                    service_name: "service".to_string(),
3103                    ..Default::default()
3104                },
3105            )
3106            .await,
3107        );
3108        assert_action_checked(&checker, JAEGER_QUERY, jaeger_targets);
3109
3110        assert_permission_denied(
3111            PipelineHandler::get_pipeline_str(&instance, "pipeline", None, ctx.clone()).await,
3112        );
3113        assert_action_checked(&checker, PIPELINE_QUERY, None);
3114        assert_permission_denied(
3115            PipelineHandler::insert_pipeline(
3116                &instance,
3117                "pipeline",
3118                "application/yaml",
3119                "",
3120                ctx.clone(),
3121            )
3122            .await,
3123        );
3124        assert_action_checked(&checker, PIPELINE_INSERT, None);
3125        assert_permission_denied(
3126            PipelineHandler::delete_pipeline(&instance, "pipeline", None, ctx.clone()).await,
3127        );
3128        assert_action_checked(&checker, PIPELINE_DELETE, None);
3129        let app = axum::Router::new()
3130            .route(
3131                "/pipelines/_dryrun",
3132                axum::routing::post(servers::http::event::pipeline_dryrun),
3133            )
3134            .with_state(servers::http::event::LogState {
3135                log_handler: Arc::new(instance.clone()),
3136                log_validator: None,
3137                ingest_interceptor: None,
3138            })
3139            .layer(axum::Extension((*ctx).clone()));
3140        let response = app
3141            .oneshot(
3142                axum::http::Request::post("/pipelines/_dryrun")
3143                    .header("content-type", "application/json")
3144                    .body(axum::body::Body::from("{}"))
3145                    .unwrap(),
3146            )
3147            .await
3148            .unwrap();
3149        assert_eq!(axum::http::StatusCode::FORBIDDEN, response.status());
3150        assert_action_checked(&checker, PIPELINE_QUERY, None);
3151
3152        assert_permission_denied(
3153            DashboardHandler::save(&instance, "dashboard", "{}", ctx.clone()).await,
3154        );
3155        assert_action_checked(&checker, DASHBOARD_SAVE, None);
3156        assert_permission_denied(DashboardHandler::list(&instance, ctx.clone()).await);
3157        assert_action_checked(&checker, DASHBOARD_QUERY, None);
3158        assert_permission_denied(
3159            DashboardHandler::delete(&instance, "dashboard", ctx.clone()).await,
3160        );
3161        assert_action_checked(&checker, DASHBOARD_DELETE, None);
3162
3163        Ok(())
3164    }
3165
3166    #[tokio::test]
3167    async fn test_write_only_ingestion_loads_named_pipeline() -> TestResult<()> {
3168        let plugins = Plugins::new();
3169        plugins.insert::<PermissionCheckerRef>(Arc::new(WriteOnlyPermissionChecker));
3170        let instance = test_instance_with_plugins(
3171            test_table(1024, "source")?,
3172            test_table(1025, "target")?,
3173            plugins,
3174        )
3175        .await?;
3176        instance
3177            .catalog_manager()
3178            .as_any()
3179            .downcast_ref::<catalog::memory::MemoryCatalogManager>()
3180            .unwrap()
3181            .register_table_sync(catalog::RegisterTableRequest {
3182                catalog: "greptime".to_string(),
3183                schema: DEFAULT_PRIVATE_SCHEMA_NAME.to_string(),
3184                table_name: "pipelines".to_string(),
3185                table_id: 2049,
3186                table: test_pipeline_table(),
3187            })
3188            .with_context(|_| RegisterTableSnafu {
3189                table_name: "pipelines".to_string(),
3190            })?;
3191        let ctx = test_query_ctx(1);
3192        let handler: PipelineHandlerRef = Arc::new(instance.clone());
3193
3194        handler
3195            .get_pipeline("pipeline", None, ctx.clone())
3196            .await
3197            .unwrap();
3198        assert_permission_denied(
3199            PipelineHandler::get_pipeline_str(&instance, "pipeline", None, ctx.clone()).await,
3200        );
3201
3202        let app = axum::Router::new()
3203            .route(
3204                "/pipelines/_dryrun",
3205                axum::routing::post(servers::http::event::pipeline_dryrun),
3206            )
3207            .with_state(servers::http::event::LogState {
3208                log_handler: handler,
3209                log_validator: None,
3210                ingest_interceptor: None,
3211            })
3212            .layer(axum::Extension((*ctx).clone()));
3213        let response = app
3214            .oneshot(
3215                axum::http::Request::post("/pipelines/_dryrun")
3216                    .header("content-type", "application/json")
3217                    .body(axum::body::Body::from("{}"))
3218                    .unwrap(),
3219            )
3220            .await
3221            .unwrap();
3222        assert_eq!(axum::http::StatusCode::FORBIDDEN, response.status());
3223
3224        Ok(())
3225    }
3226
3227    #[tokio::test]
3228    async fn test_write_only_grpc_sql_is_checked_after_parsing() -> TestResult<()> {
3229        let plugins = Plugins::new();
3230        plugins.insert::<PermissionCheckerRef>(Arc::new(WriteOnlyPermissionChecker));
3231        let instance = test_instance_with_plugins(
3232            test_table(1024, "source")?,
3233            test_table(1025, "target")?,
3234            plugins,
3235        )
3236        .await?;
3237
3238        let insert = Request::Query(api::v1::QueryRequest {
3239            query: Some(Query::Sql(
3240                "INSERT INTO target SELECT * FROM source".to_string(),
3241            )),
3242        });
3243        servers::query_handler::grpc::GrpcQueryHandler::do_query(
3244            &instance,
3245            insert,
3246            QueryContext::arc(),
3247        )
3248        .await
3249        .unwrap();
3250
3251        let select = Request::Query(api::v1::QueryRequest {
3252            query: Some(Query::Sql("SELECT * FROM source".to_string())),
3253        });
3254        assert_permission_denied(
3255            servers::query_handler::grpc::GrpcQueryHandler::do_query(
3256                &instance,
3257                select,
3258                QueryContext::arc(),
3259            )
3260            .await,
3261        );
3262
3263        Ok(())
3264    }
3265
3266    #[tokio::test]
3267    async fn test_target_independent_checker_skips_target_resolution() -> TestResult<()> {
3268        let physical_table = "physical_metric";
3269        let checker = Arc::new(TargetIndependentPermissionChecker::default());
3270        let plugins = Plugins::new();
3271        plugins.insert::<PermissionCheckerRef>(checker.clone());
3272        let instance = test_instance_with_plugins(
3273            test_physical_table(1024, physical_table)?,
3274            test_table(1025, "target")?,
3275            plugins,
3276        )
3277        .await?;
3278
3279        let ctx = test_query_ctx(1);
3280        let physical_target = PermissionTableTarget::new("greptime", "public", physical_table);
3281        assert_eq!(
3282            PermissionTableTargets::Resolved(vec![physical_target.clone()]),
3283            instance
3284                .resolve_query_permission_targets(
3285                    PermissionTableTargets::resolved(vec![physical_target]),
3286                    &ctx,
3287                )
3288                .await
3289                .unwrap()
3290        );
3291        assert_eq!(
3292            vec![physical_table.to_string(), "target".to_string()],
3293            PrometheusHandler::filter_metadata_metric_names(
3294                &instance,
3295                vec![physical_table.to_string(), "target".to_string()],
3296                "public",
3297                &ctx,
3298            )
3299            .await
3300            .unwrap()
3301        );
3302        assert_eq!(1, checker.checks.load(atomic::Ordering::Relaxed));
3303
3304        Ok(())
3305    }
3306
3307    #[tokio::test]
3308    async fn test_query_permission_targets_are_deduplicated() -> TestResult<()> {
3309        let plugins = Plugins::new();
3310        plugins.insert::<PermissionCheckerRef>(Arc::new(RejectUnresolvedPermissionChecker));
3311        let instance = test_instance_with_plugins(
3312            test_table(1024, "source")?,
3313            test_table(1025, "target")?,
3314            plugins,
3315        )
3316        .await?;
3317        let ctx = test_query_ctx(1);
3318        let target = PermissionTableTarget::new("greptime", "public", "target");
3319
3320        assert_eq!(
3321            PermissionTableTargets::Resolved(vec![target.clone()]),
3322            instance
3323                .resolve_query_permission_targets(
3324                    PermissionTableTargets::resolved(vec![target.clone(), target]),
3325                    &ctx,
3326                )
3327                .await
3328                .unwrap()
3329        );
3330
3331        Ok(())
3332    }
3333
3334    #[tokio::test]
3335    async fn test_physical_query_targets_fail_closed() -> TestResult<()> {
3336        let physical_table = "physical_metric";
3337        let plugins = Plugins::new();
3338        plugins.insert::<PermissionCheckerRef>(Arc::new(RejectUnresolvedPermissionChecker));
3339        let instance = test_instance_with_plugins(
3340            test_physical_table(1024, physical_table)?,
3341            test_table(1025, "target")?,
3342            plugins,
3343        )
3344        .await?;
3345
3346        let ctx = test_query_ctx(1);
3347        let logical_target = PermissionTableTarget::new("greptime", "public", "target");
3348        assert_eq!(
3349            PermissionTableTargets::Resolved(vec![logical_target.clone()]),
3350            instance
3351                .resolve_query_permission_targets(
3352                    PermissionTableTargets::resolved(vec![logical_target.clone()]),
3353                    &ctx,
3354                )
3355                .await
3356                .unwrap()
3357        );
3358        let physical_target = PermissionTableTarget::new("greptime", "public", physical_table);
3359        assert_eq!(
3360            PermissionTableTargets::Unresolved,
3361            instance
3362                .resolve_query_permission_targets(
3363                    PermissionTableTargets::resolved(
3364                        vec![logical_target, physical_target.clone(),]
3365                    ),
3366                    &ctx,
3367                )
3368                .await
3369                .unwrap()
3370        );
3371        assert_eq!(
3372            vec!["target".to_string()],
3373            PrometheusHandler::filter_metadata_metric_names(
3374                &instance,
3375                vec!["target".to_string(), "denied".to_string()],
3376                "public",
3377                &ctx,
3378            )
3379            .await
3380            .unwrap()
3381        );
3382
3383        let query = PromQuery {
3384            query: physical_table.to_string(),
3385            ..Default::default()
3386        };
3387        let err = PrometheusHandler::check_query_target_permission(
3388            &instance,
3389            PermissionTableTargets::resolved(vec![physical_target]),
3390            &ctx,
3391        )
3392        .await
3393        .unwrap_err();
3394        assert_eq!(StatusCode::PermissionDenied, err.status_code());
3395        let err = PrometheusHandler::check_query_permission(
3396            &instance,
3397            std::slice::from_ref(&query),
3398            &ctx,
3399        )
3400        .await
3401        .unwrap_err();
3402        assert_eq!(StatusCode::PermissionDenied, err.status_code());
3403        let err = PrometheusHandler::do_query(&instance, &query, ctx.clone())
3404            .await
3405            .unwrap_err();
3406        assert_eq!(StatusCode::PermissionDenied, err.status_code());
3407
3408        for sql in [
3409            "SELECT * FROM physical_metric",
3410            "TQL EVAL (0, 10, '5s') physical_metric",
3411            "INSERT INTO target SELECT * FROM physical_metric",
3412        ] {
3413            let mut results = instance.do_query_inner(sql, ctx.clone()).await;
3414            assert_eq!(1, results.len(), "{sql}");
3415            let err = results.remove(0).unwrap_err();
3416            assert_eq!(StatusCode::PermissionDenied, err.status_code(), "{sql}");
3417        }
3418        let err = LogQueryHandler::query(
3419            &instance,
3420            LogQuery {
3421                table: TableName::new("greptime", "public", physical_table),
3422                ..Default::default()
3423            },
3424            ctx.clone(),
3425        )
3426        .await
3427        .unwrap_err();
3428        assert_eq!(StatusCode::PermissionDenied, err.status_code());
3429        let err = instance
3430            .do_describe_inner(parse_one_sql("SELECT * FROM physical_metric"), ctx.clone())
3431            .await
3432            .unwrap_err();
3433        assert_eq!(StatusCode::PermissionDenied, err.status_code());
3434
3435        let request = ReadRequest {
3436            queries: vec![RemoteQuery {
3437                matchers: vec![LabelMatcher {
3438                    r#type: PromMatcherType::Eq as i32,
3439                    name: servers::prom_store::METRIC_NAME_LABEL.to_string(),
3440                    value: physical_table.to_string(),
3441                }],
3442                ..Default::default()
3443            }],
3444            ..Default::default()
3445        };
3446        let Err(err) = PromStoreProtocolHandler::read(&instance, request, ctx.clone()).await else {
3447            panic!("physical remote-read target must be rejected");
3448        };
3449        assert_eq!(StatusCode::PermissionDenied, err.status_code());
3450
3451        let err = PrometheusHandler::query_label_values(
3452            &instance,
3453            physical_table.to_string(),
3454            "host".to_string(),
3455            vec![],
3456            SystemTime::UNIX_EPOCH,
3457            SystemTime::UNIX_EPOCH,
3458            &ctx,
3459        )
3460        .await
3461        .unwrap_err();
3462        assert_eq!(StatusCode::PermissionDenied, err.status_code());
3463
3464        Ok(())
3465    }
3466
3467    #[tokio::test]
3468    async fn test_non_exact_query_discovery_keeps_denied_targets_for_batch_check() -> TestResult<()>
3469    {
3470        let plugins = Plugins::new();
3471        plugins.insert::<PermissionCheckerRef>(Arc::new(RejectUnresolvedPermissionChecker));
3472        let instance = test_instance_with_plugins_and_metric_names(
3473            test_logical_table(1024, "denied")?,
3474            test_logical_table(1025, "target")?,
3475            plugins,
3476            Some(test_metric_names_table()),
3477        )
3478        .await?;
3479        let ctx = test_query_ctx(1);
3480
3481        let mut metric_names = PrometheusHandler::query_metric_names(
3482            &instance,
3483            vec![Matcher::new(
3484                promql_parser::label::MatchOp::NotEqual,
3485                "__name__",
3486                "",
3487            )],
3488            "public",
3489            &ctx,
3490        )
3491        .await
3492        .unwrap();
3493        metric_names.sort_unstable();
3494        assert_eq!(
3495            vec!["denied".to_string(), "target".to_string()],
3496            metric_names
3497        );
3498
3499        let queries = metric_names
3500            .into_iter()
3501            .map(|query| PromQuery {
3502                query,
3503                ..Default::default()
3504            })
3505            .collect::<Vec<_>>();
3506        let err = PrometheusHandler::check_query_permission(&instance, &queries, &ctx)
3507            .await
3508            .unwrap_err();
3509        assert_eq!(StatusCode::PermissionDenied, err.status_code());
3510
3511        Ok(())
3512    }
3513
3514    #[test]
3515    fn test_fast_legacy_check_is_read_only() {
3516        let cache = DashMap::new();
3517        cache.insert("metric1".to_string(), true);
3518
3519        let names = vec!["metric1".to_string(), "metric2".to_string()];
3520        assert_eq!(Some(true), fast_legacy_check(&cache, &names).unwrap());
3521        assert!(!cache.contains_key("metric2"));
3522
3523        cache_legacy_mode(&cache, &names, true).unwrap();
3524        assert!(*cache.get("metric2").unwrap().value());
3525        assert!(cache_legacy_mode(&cache, &names, false).is_err());
3526        assert!(*cache.get("metric2").unwrap().value());
3527
3528        let cache_incompatible = DashMap::new();
3529        cache_incompatible.insert("metric1".to_string(), true);
3530        cache_incompatible.insert("metric2".to_string(), false);
3531        assert!(fast_legacy_check(&cache_incompatible, &names).is_err());
3532    }
3533
3534    #[test]
3535    fn test_should_track_statement_process() {
3536        assert!(should_track_statement_process(&parse_one_sql(
3537            "SELECT * FROM demo"
3538        )));
3539        assert!(should_track_statement_process(&parse_one_sql(
3540            "INSERT INTO demo SELECT * FROM source"
3541        )));
3542        assert!(!should_track_statement_process(&parse_one_sql(
3543            "INSERT INTO demo VALUES (1)"
3544        )));
3545        assert!(!should_track_statement_process(&parse_one_sql(
3546            "INSERT INTO demo VALUES (now())"
3547        )));
3548    }
3549
3550    #[test]
3551    fn test_should_track_plan_process() {
3552        let select_stmt = parse_one_sql("SELECT * FROM demo");
3553        let insert_select_stmt = parse_one_sql("INSERT INTO demo SELECT * FROM source");
3554        let insert_values_stmt = parse_one_sql("INSERT INTO demo VALUES (now())");
3555
3556        let empty_plan = LogicalPlanBuilder::empty(false).build().unwrap();
3557        assert!(should_track_plan_process(Some(&select_stmt), &empty_plan));
3558        assert!(should_track_plan_process(
3559            Some(&insert_select_stmt),
3560            &insert_dml_plan()
3561        ));
3562        assert!(!should_track_plan_process(
3563            Some(&insert_values_stmt),
3564            &insert_dml_plan()
3565        ));
3566        assert!(!should_track_plan_process(None, &insert_dml_plan()));
3567    }
3568
3569    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3570    async fn test_insert_select_is_visible_in_show_processlist() -> TestResult<()> {
3571        let insert_sql = "INSERT INTO target SELECT * FROM source";
3572        let (started_tx, mut started_rx) = mpsc::unbounded_channel();
3573        let (finish_tx, finish_rx) = oneshot::channel();
3574        let interceptor = Arc::new(BlockingInsertSelectInterceptor::new(started_tx, finish_rx));
3575        let instance = Arc::new(test_instance_with_insert_select_interceptor(interceptor).await?);
3576
3577        let insert_task = tokio::spawn({
3578            let instance = instance.clone();
3579            async move { execute_one_sql(&instance, insert_sql, test_query_ctx(4242)).await }
3580        });
3581
3582        tokio::time::timeout(Duration::from_secs(5), started_rx.recv())
3583            .await
3584            .context(InsertStartTimeoutSnafu)?
3585            .context(InsertStartChannelClosedSnafu)?;
3586
3587        let output = execute_one_sql(&instance, "SHOW PROCESSLIST", test_query_ctx(43)).await?;
3588        let process_list = output.data.pretty_print().await;
3589        assert!(
3590            process_list.contains(insert_sql),
3591            "process list did not contain running insert:\n{process_list}"
3592        );
3593
3594        finish_tx
3595            .send(())
3596            .map_err(|_| ReleaseBlockedInsertSnafu.build())?;
3597        insert_task.await.context(InsertTaskPanicSnafu)??;
3598
3599        Ok(())
3600    }
3601
3602    #[tokio::test]
3603    async fn test_show_processlist_catalog_scope() -> TestResult<()> {
3604        let instance =
3605            test_instance_with_tables(test_table(1024, "source")?, test_table(1025, "target")?)
3606                .await?;
3607        let _current_catalog = instance.process_manager().register_query(
3608            "greptime".to_string(),
3609            vec!["public".to_string()],
3610            "current_catalog_query".to_string(),
3611            String::new(),
3612            None,
3613            None,
3614        );
3615        let _other_catalog = instance.process_manager().register_query(
3616            "other".to_string(),
3617            vec!["public".to_string()],
3618            "other_catalog_query".to_string(),
3619            String::new(),
3620            None,
3621            None,
3622        );
3623
3624        for sql in ["SHOW PROCESSLIST", "SHOW FULL PROCESSLIST"] {
3625            let output = execute_one_sql(&instance, sql, test_query_ctx(43)).await?;
3626            let process_list = output.data.pretty_print().await;
3627            assert!(
3628                process_list.contains("current_catalog_query"),
3629                "{process_list}"
3630            );
3631            assert!(
3632                !process_list.contains("other_catalog_query"),
3633                "{process_list}"
3634            );
3635
3636            let admin_ctx = test_query_ctx(44);
3637            admin_ctx.set_current_user(Arc::new(AdminUserInfo));
3638            let output = execute_one_sql(&instance, sql, admin_ctx).await?;
3639            let process_list = output.data.pretty_print().await;
3640            assert!(
3641                process_list.contains("current_catalog_query"),
3642                "{process_list}"
3643            );
3644            assert!(
3645                process_list.contains("other_catalog_query"),
3646                "{process_list}"
3647            );
3648        }
3649
3650        Ok(())
3651    }
3652
3653    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3654    async fn test_kill_query_cancels_insert_select() -> TestResult<()> {
3655        assert_kill_cancels_insert_select("KILL QUERY 4242").await
3656    }
3657
3658    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3659    async fn test_kill_process_id_cancels_insert_select() -> TestResult<()> {
3660        assert_kill_cancels_insert_select("KILL 'test-frontend/4242'").await
3661    }
3662
3663    async fn assert_kill_cancels_insert_select(kill_sql: &str) -> TestResult<()> {
3664        let insert_sql = "INSERT INTO target SELECT * FROM source";
3665        let (source_polled_tx, source_polled_rx) = oneshot::channel();
3666        let instance = Arc::new(
3667            test_instance_with_tables(
3668                pending_table(1024, "source", source_polled_tx)?,
3669                test_table(1025, "target")?,
3670            )
3671            .await?,
3672        );
3673
3674        let insert_task = tokio::spawn({
3675            let instance = instance.clone();
3676            async move { execute_one_sql(&instance, insert_sql, test_query_ctx(4242)).await }
3677        });
3678
3679        tokio::time::timeout(Duration::from_secs(5), source_polled_rx)
3680            .await
3681            .context(SourcePollTimeoutSnafu)?
3682            .context(SourcePollChannelClosedSnafu)?;
3683
3684        let output = execute_one_sql(&instance, kill_sql, test_query_ctx(43)).await?;
3685        assert!(matches!(output.data, OutputData::AffectedRows(1)));
3686
3687        let insert_result = tokio::time::timeout(Duration::from_secs(5), insert_task)
3688            .await
3689            .context(InsertTaskTimeoutSnafu)?
3690            .context(InsertTaskPanicSnafu)?;
3691        let err = match insert_result {
3692            Ok(_) => return InsertSelectNotCancelledSnafu.fail(),
3693            Err(TestError::ExecuteSql { source, .. }) => source,
3694            Err(err) => return Err(err),
3695        };
3696        assert_eq!(StatusCode::Cancelled, err.status_code());
3697
3698        let output = execute_one_sql(&instance, "SHOW PROCESSLIST", test_query_ctx(43)).await?;
3699        let process_list = output.data.pretty_print().await;
3700        assert!(
3701            !process_list.contains(insert_sql),
3702            "process list still contains killed insert:\n{process_list}"
3703        );
3704
3705        Ok(())
3706    }
3707
3708    fn insert_dml_plan() -> LogicalPlan {
3709        let schema = SchemaRef::new(Schema::new(vec![Field::new(
3710            "value",
3711            DataType::Int64,
3712            true,
3713        )]));
3714        let target = Arc::new(LogicalTableSource::new(schema));
3715        let input = LogicalPlanBuilder::empty(false).build().unwrap();
3716
3717        LogicalPlanBuilder::insert_into(input, "demo", target, InsertOp::Append)
3718            .unwrap()
3719            .build()
3720            .unwrap()
3721    }
3722
3723    #[test]
3724    fn test_exec_validation() {
3725        let query_ctx = QueryContext::arc();
3726        let plugins: Plugins = Plugins::new();
3727        plugins.insert(QueryOptions {
3728            disallow_cross_catalog_query: true,
3729        });
3730
3731        let sql = r#"
3732        SELECT * FROM demo;
3733        EXPLAIN SELECT * FROM demo;
3734        CREATE DATABASE test_database;
3735        SHOW DATABASES;
3736        "#;
3737        let stmts = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
3738        assert_eq!(stmts.len(), 4);
3739        for stmt in stmts {
3740            let re = check_permission(plugins.clone(), &stmt, &query_ctx);
3741            re.unwrap();
3742        }
3743
3744        let sql = r#"
3745        SHOW CREATE TABLE demo;
3746        ALTER TABLE demo ADD COLUMN new_col INT;
3747        "#;
3748        let stmts = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
3749        assert_eq!(stmts.len(), 2);
3750        for stmt in stmts {
3751            let re = check_permission(plugins.clone(), &stmt, &query_ctx);
3752            re.unwrap();
3753        }
3754
3755        fn replace_test(template_sql: &str, plugins: Plugins, query_ctx: &QueryContextRef) {
3756            // test right
3757            let right = vec![("", ""), ("", "public."), ("greptime.", "public.")];
3758            for (catalog, schema) in right {
3759                let sql = do_fmt(template_sql, catalog, schema);
3760                do_test(&sql, plugins.clone(), query_ctx, true);
3761            }
3762
3763            let wrong = vec![
3764                ("wrongcatalog.", "public."),
3765                ("wrongcatalog.", "wrongschema."),
3766            ];
3767            for (catalog, schema) in wrong {
3768                let sql = do_fmt(template_sql, catalog, schema);
3769                do_test(&sql, plugins.clone(), query_ctx, false);
3770            }
3771        }
3772
3773        fn do_fmt(template: &str, catalog: &str, schema: &str) -> String {
3774            let vars = HashMap::from([
3775                ("catalog".to_string(), catalog),
3776                ("schema".to_string(), schema),
3777            ]);
3778            template.format(&vars).unwrap()
3779        }
3780
3781        fn do_test(sql: &str, plugins: Plugins, query_ctx: &QueryContextRef, is_ok: bool) {
3782            let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
3783            let re = check_permission(plugins, stmt, query_ctx);
3784            if is_ok {
3785                re.unwrap();
3786            } else {
3787                assert!(re.is_err());
3788            }
3789        }
3790
3791        // test insert
3792        let sql = "INSERT INTO {catalog}{schema}monitor(host) VALUES ('host1');";
3793        replace_test(sql, plugins.clone(), &query_ctx);
3794
3795        // test create table
3796        let sql = r#"CREATE TABLE {catalog}{schema}demo(
3797                            host STRING,
3798                            ts TIMESTAMP,
3799                            TIME INDEX (ts),
3800                            PRIMARY KEY(host)
3801                        ) engine=mito;"#;
3802        replace_test(sql, plugins.clone(), &query_ctx);
3803
3804        // test drop table
3805        let sql = "DROP TABLE {catalog}{schema}demo;";
3806        replace_test(sql, plugins.clone(), &query_ctx);
3807
3808        // test undrop table
3809        #[cfg(feature = "enterprise")]
3810        {
3811            let sql = "UNDROP TABLE {catalog}{schema}demo;";
3812            replace_test(sql, plugins.clone(), &query_ctx);
3813        }
3814
3815        // test show tables
3816        let sql = "SHOW TABLES FROM public";
3817        let stmt = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
3818        check_permission(plugins.clone(), &stmt[0], &query_ctx).unwrap();
3819
3820        let sql = "SHOW TABLES FROM private";
3821        let stmt = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
3822        let re = check_permission(plugins.clone(), &stmt[0], &query_ctx);
3823        assert!(re.is_ok());
3824
3825        // test describe table
3826        let sql = "DESC TABLE {catalog}{schema}demo;";
3827        replace_test(sql, plugins.clone(), &query_ctx);
3828
3829        let comment_flow_cases = [
3830            ("COMMENT ON FLOW my_flow IS 'comment';", true),
3831            ("COMMENT ON FLOW greptime.my_flow IS 'comment';", true),
3832            ("COMMENT ON FLOW wrongcatalog.my_flow IS 'comment';", false),
3833        ];
3834        for (sql, is_ok) in comment_flow_cases {
3835            let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
3836            let result = check_permission(plugins.clone(), stmt, &query_ctx);
3837            assert_eq!(result.is_ok(), is_ok);
3838        }
3839
3840        let show_flow_cases = [
3841            ("SHOW CREATE FLOW my_flow;", true),
3842            ("SHOW CREATE FLOW greptime.my_flow;", true),
3843            ("SHOW CREATE FLOW wrongcatalog.my_flow;", false),
3844        ];
3845        for (sql, is_ok) in show_flow_cases {
3846            let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
3847            let result = check_permission(plugins.clone(), stmt, &query_ctx);
3848            assert_eq!(result.is_ok(), is_ok);
3849        }
3850    }
3851
3852    /// A `DropView` DDL sent through the direct gRPC ingress must return an error
3853    /// (e.g. table not found) instead of panicking on `todo!()`.
3854    #[tokio::test]
3855    async fn qx_152_drop_view_via_grpc_ddl_returns_error_not_panic() -> TestResult<()> {
3856        let instance =
3857            test_instance_with_tables(test_table(1024, "source")?, test_table(1025, "target")?)
3858                .await?;
3859
3860        let request = api::v1::greptime_request::Request::Ddl(api::v1::DdlRequest {
3861            expr: Some(api::v1::ddl_request::Expr::DropView(
3862                api::v1::DropViewExpr {
3863                    catalog_name: String::new(),
3864                    schema_name: String::new(),
3865                    view_name: "non_existent_view".to_string(),
3866                    view_id: None,
3867                    drop_if_exists: false,
3868                },
3869            )),
3870        });
3871
3872        let result = servers::query_handler::grpc::GrpcQueryHandler::do_query(
3873            &instance,
3874            request,
3875            QueryContext::arc(),
3876        )
3877        .await;
3878
3879        let err = match result {
3880            Ok(_) => panic!("DropView DDL request must return an error instead of panicking"),
3881            Err(err) => err,
3882        };
3883        assert_eq!(
3884            err.status_code(),
3885            StatusCode::TableNotFound,
3886            "dropping a non-existent view without IF EXISTS must report TableNotFound, got {err}"
3887        );
3888        Ok(())
3889    }
3890
3891    /// `DROP VIEW IF EXISTS` on a missing view through the direct gRPC ingress must
3892    /// succeed with 0 affected rows (no error, no DDL task submitted), instead of
3893    /// returning `TableNotFound`.
3894    #[tokio::test]
3895    async fn qx_152_drop_view_if_exists_missing_view_via_grpc_ddl_succeeds() -> TestResult<()> {
3896        let catalog_manager =
3897            catalog::memory::MemoryCatalogManager::new_with_table(test_table(1024, "source")?);
3898        let procedure_executor = Arc::new(MockProcedureExecutor::new(catalog_manager.clone()));
3899        let instance = test_instance_with_catalog_manager(
3900            catalog_manager,
3901            test_table(1025, "target")?,
3902            Plugins::new(),
3903            None,
3904            procedure_executor.clone() as ProcedureExecutorRef,
3905        )
3906        .await?;
3907
3908        let request = api::v1::greptime_request::Request::Ddl(api::v1::DdlRequest {
3909            expr: Some(api::v1::ddl_request::Expr::DropView(
3910                api::v1::DropViewExpr {
3911                    catalog_name: String::new(),
3912                    schema_name: String::new(),
3913                    view_name: "non_existent_view".to_string(),
3914                    view_id: None,
3915                    drop_if_exists: true,
3916                },
3917            )),
3918        });
3919
3920        let result = servers::query_handler::grpc::GrpcQueryHandler::do_query(
3921            &instance,
3922            request,
3923            QueryContext::arc(),
3924        )
3925        .await;
3926
3927        let output = match result {
3928            Ok(output) => output,
3929            Err(err) => {
3930                panic!("DROP VIEW IF EXISTS on a missing view must succeed, got error: {err}")
3931            }
3932        };
3933        assert!(
3934            matches!(output.data, OutputData::AffectedRows(0)),
3935            "DROP VIEW IF EXISTS on a missing view must report 0 affected rows"
3936        );
3937        assert!(
3938            procedure_executor.submitted.lock().unwrap().is_empty(),
3939            "DROP VIEW IF EXISTS on a missing view must not submit a DDL task"
3940        );
3941        Ok(())
3942    }
3943
3944    /// A `CREATE VIEW` followed by `DROP VIEW` through the direct gRPC ingress must
3945    /// succeed end to end: the view is registered in the catalog and then removed.
3946    #[tokio::test]
3947    async fn qx_152_drop_existing_view_via_grpc_ddl_succeeds() -> TestResult<()> {
3948        let catalog_manager =
3949            catalog::memory::MemoryCatalogManager::new_with_table(test_table(1024, "source")?);
3950        let procedure_executor = Arc::new(MockProcedureExecutor::new(catalog_manager.clone()));
3951        let instance = test_instance_with_catalog_manager(
3952            catalog_manager,
3953            test_table(1025, "target")?,
3954            Plugins::new(),
3955            None,
3956            procedure_executor.clone() as ProcedureExecutorRef,
3957        )
3958        .await?;
3959
3960        // The default "greptime.public" schema must be visible to the kv-backed table
3961        // metadata manager for `CREATE VIEW`/`CREATE TABLE` to pass validation.
3962        instance
3963            .table_metadata_manager()
3964            .schema_manager()
3965            .create(
3966                common_meta::key::schema_name::SchemaNameKey::new("greptime", "public"),
3967                None,
3968                true,
3969            )
3970            .await
3971            .unwrap();
3972
3973        let create_view_request = api::v1::greptime_request::Request::Ddl(api::v1::DdlRequest {
3974            expr: Some(api::v1::ddl_request::Expr::CreateView(
3975                api::v1::CreateViewExpr {
3976                    catalog_name: String::new(),
3977                    schema_name: String::new(),
3978                    view_name: "my_view".to_string(),
3979                    logical_plan: vec![1, 2, 3],
3980                    create_if_not_exists: false,
3981                    or_replace: false,
3982                    table_names: vec![],
3983                    columns: vec![],
3984                    plan_columns: vec![],
3985                    definition: "CREATE VIEW my_view AS SELECT * FROM source".to_string(),
3986                },
3987            )),
3988        });
3989
3990        let output = match servers::query_handler::grpc::GrpcQueryHandler::do_query(
3991            &instance,
3992            create_view_request,
3993            QueryContext::arc(),
3994        )
3995        .await
3996        {
3997            Ok(output) => output,
3998            Err(err) => panic!("CREATE VIEW via gRPC DDL must succeed, got error: {err}"),
3999        };
4000        assert!(
4001            matches!(output.data, OutputData::AffectedRows(0)),
4002            "CREATE VIEW via gRPC DDL must report 0 affected rows"
4003        );
4004
4005        // The view is registered in the catalog as a view.
4006        let view = instance
4007            .catalog_manager()
4008            .table("greptime", "public", "my_view", None)
4009            .await
4010            .unwrap()
4011            .expect("view should exist after CREATE VIEW");
4012        assert_eq!(view.table_info().table_type, TableType::View);
4013
4014        let drop_view_request = api::v1::greptime_request::Request::Ddl(api::v1::DdlRequest {
4015            expr: Some(api::v1::ddl_request::Expr::DropView(
4016                api::v1::DropViewExpr {
4017                    catalog_name: String::new(),
4018                    schema_name: String::new(),
4019                    view_name: "my_view".to_string(),
4020                    view_id: None,
4021                    drop_if_exists: false,
4022                },
4023            )),
4024        });
4025
4026        let output = match servers::query_handler::grpc::GrpcQueryHandler::do_query(
4027            &instance,
4028            drop_view_request,
4029            QueryContext::arc(),
4030        )
4031        .await
4032        {
4033            Ok(output) => output,
4034            Err(err) => panic!("DROP VIEW via gRPC DDL must succeed, got error: {err}"),
4035        };
4036        assert!(
4037            matches!(output.data, OutputData::AffectedRows(0)),
4038            "DROP VIEW via gRPC DDL must report 0 affected rows"
4039        );
4040
4041        // The view is gone after the drop.
4042        assert!(
4043            instance
4044                .catalog_manager()
4045                .table("greptime", "public", "my_view", None)
4046                .await
4047                .unwrap()
4048                .is_none(),
4049            "view should be removed after DROP VIEW"
4050        );
4051
4052        let submitted = procedure_executor.submitted.lock().unwrap();
4053        assert_eq!(
4054            submitted.len(),
4055            2,
4056            "expected create and drop view tasks, got {submitted:?}"
4057        );
4058        assert!(matches!(&submitted[0], DdlTask::CreateView(_)));
4059        assert!(matches!(&submitted[1], DdlTask::DropView(_)));
4060        Ok(())
4061    }
4062
4063    /// A direct gRPC `CreateTable` whose time index column is not a timestamp must
4064    /// be rejected with `InvalidArguments` instead of panicking while building the schema.
4065    #[tokio::test]
4066    async fn qx_153_create_table_with_non_timestamp_time_index_via_grpc_returns_error()
4067    -> TestResult<()> {
4068        let instance =
4069            test_instance_with_tables(test_table(1024, "source")?, test_table(1025, "target")?)
4070                .await?;
4071
4072        let request = api::v1::greptime_request::Request::Ddl(api::v1::DdlRequest {
4073            expr: Some(api::v1::ddl_request::Expr::CreateTable(
4074                api::v1::CreateTableExpr {
4075                    catalog_name: String::new(),
4076                    schema_name: String::new(),
4077                    table_name: "demo".to_string(),
4078                    desc: String::new(),
4079                    column_defs: vec![api::v1::ColumnDef {
4080                        name: "host".to_string(),
4081                        data_type: api::v1::ColumnDataType::String as i32,
4082                        is_nullable: true,
4083                        default_constraint: vec![],
4084                        semantic_type: 0,
4085                        comment: String::new(),
4086                        datatype_extension: None,
4087                        options: None,
4088                    }],
4089                    time_index: "host".to_string(),
4090                    primary_keys: vec![],
4091                    create_if_not_exists: false,
4092                    table_options: HashMap::new(),
4093                    table_id: None,
4094                    engine: "mito".to_string(),
4095                },
4096            )),
4097        });
4098
4099        let result = servers::query_handler::grpc::GrpcQueryHandler::do_query(
4100            &instance,
4101            request,
4102            QueryContext::arc(),
4103        )
4104        .await;
4105
4106        let err = match result {
4107            Ok(_) => panic!("CreateTable with a non-timestamp time index must be rejected"),
4108            Err(err) => err,
4109        };
4110        assert_eq!(err.status_code(), StatusCode::InvalidArguments, "{err}");
4111        Ok(())
4112    }
4113
4114    /// A valid `CREATE TABLE` (timestamp time index) through the direct gRPC ingress
4115    /// must succeed, guarding that the `validate_create_expr` ingress check doesn't
4116    /// accidentally reject good requests.
4117    #[tokio::test]
4118    async fn qx_153_create_table_with_timestamp_time_index_via_grpc_succeeds() -> TestResult<()> {
4119        let catalog_manager =
4120            catalog::memory::MemoryCatalogManager::new_with_table(test_table(1024, "source")?);
4121        let procedure_executor = Arc::new(MockProcedureExecutor::new(catalog_manager.clone()));
4122        let instance = test_instance_with_catalog_manager(
4123            catalog_manager,
4124            test_table(1025, "target")?,
4125            Plugins::new(),
4126            None,
4127            procedure_executor.clone() as ProcedureExecutorRef,
4128        )
4129        .await?;
4130
4131        // The default "greptime.public" schema must be visible to the kv-backed table
4132        // metadata manager for `CREATE TABLE` to pass validation.
4133        instance
4134            .table_metadata_manager()
4135            .schema_manager()
4136            .create(
4137                common_meta::key::schema_name::SchemaNameKey::new("greptime", "public"),
4138                None,
4139                true,
4140            )
4141            .await
4142            .unwrap();
4143
4144        let request = api::v1::greptime_request::Request::Ddl(api::v1::DdlRequest {
4145            expr: Some(api::v1::ddl_request::Expr::CreateTable(
4146                api::v1::CreateTableExpr {
4147                    catalog_name: String::new(),
4148                    schema_name: String::new(),
4149                    table_name: "demo".to_string(),
4150                    desc: String::new(),
4151                    column_defs: vec![
4152                        api::v1::ColumnDef {
4153                            name: "host".to_string(),
4154                            data_type: api::v1::ColumnDataType::String as i32,
4155                            is_nullable: true,
4156                            default_constraint: vec![],
4157                            semantic_type: 0,
4158                            comment: String::new(),
4159                            datatype_extension: None,
4160                            options: None,
4161                        },
4162                        api::v1::ColumnDef {
4163                            name: "ts".to_string(),
4164                            data_type: api::v1::ColumnDataType::TimestampMillisecond as i32,
4165                            is_nullable: true,
4166                            default_constraint: vec![],
4167                            semantic_type: 0,
4168                            comment: String::new(),
4169                            datatype_extension: None,
4170                            options: None,
4171                        },
4172                    ],
4173                    time_index: "ts".to_string(),
4174                    primary_keys: vec![],
4175                    create_if_not_exists: false,
4176                    table_options: HashMap::new(),
4177                    table_id: None,
4178                    engine: "mito".to_string(),
4179                },
4180            )),
4181        });
4182
4183        let output = match servers::query_handler::grpc::GrpcQueryHandler::do_query(
4184            &instance,
4185            request,
4186            QueryContext::arc(),
4187        )
4188        .await
4189        {
4190            Ok(output) => output,
4191            Err(err) => panic!("CREATE TABLE via gRPC DDL must succeed, got error: {err}"),
4192        };
4193        assert!(
4194            matches!(output.data, OutputData::AffectedRows(0)),
4195            "CREATE TABLE via gRPC DDL must report 0 affected rows"
4196        );
4197
4198        // The table is registered in the catalog.
4199        let table = instance
4200            .catalog_manager()
4201            .table("greptime", "public", "demo", None)
4202            .await
4203            .unwrap()
4204            .expect("table should exist after CREATE TABLE");
4205        assert_eq!(table.table_info().table_type, TableType::Base);
4206
4207        let submitted = procedure_executor.submitted.lock().unwrap();
4208        assert_eq!(
4209            submitted.len(),
4210            1,
4211            "expected one create table task, got {submitted:?}"
4212        );
4213        assert!(matches!(&submitted[0], DdlTask::CreateTable(_)));
4214        Ok(())
4215    }
4216}