Skip to main content

frontend/
instance.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15pub mod builder;
16mod dashboard;
17mod grpc;
18mod influxdb;
19mod jaeger;
20mod log_handler;
21mod logs;
22mod opentsdb;
23mod otlp;
24pub mod prom_store;
25mod promql;
26mod region_query;
27pub mod standalone;
28
29use std::pin::Pin;
30use std::sync::atomic::AtomicBool;
31use std::sync::{Arc, atomic};
32use std::time::{Duration, SystemTime};
33
34use async_stream::stream;
35use async_trait::async_trait;
36use auth::{PermissionChecker, PermissionCheckerRef, PermissionReq};
37use catalog::CatalogManagerRef;
38use catalog::process_manager::{
39    ProcessManagerRef, QueryStatement as CatalogQueryStatement, SlowQueryTimer,
40};
41use client::OutputData;
42use common_base::Plugins;
43use common_base::cancellation::CancellableFuture;
44use common_error::ext::{BoxedError, ErrorExt};
45use common_event_recorder::EventRecorderRef;
46use common_meta::cache_invalidator::CacheInvalidatorRef;
47use common_meta::key::TableMetadataManagerRef;
48use common_meta::key::table_name::TableNameKey;
49use common_meta::node_manager::NodeManagerRef;
50use common_meta::procedure_executor::ProcedureExecutorRef;
51use common_query::Output;
52use common_recordbatch::RecordBatchStreamWrapper;
53use common_recordbatch::error::StreamTimeoutSnafu;
54use common_telemetry::logging::SlowQueryOptions;
55use common_telemetry::{debug, error, tracing};
56use dashmap::DashMap;
57use datafusion_expr::LogicalPlan;
58use futures::{Stream, StreamExt};
59use lazy_static::lazy_static;
60use operator::delete::DeleterRef;
61use operator::insert::InserterRef;
62use operator::statement::{StatementExecutor, StatementExecutorRef};
63use partition::manager::PartitionRuleManagerRef;
64use pipeline::pipeline_operator::PipelineOperator;
65use prometheus::HistogramTimer;
66use promql_parser::label::Matcher;
67use query::QueryEngineRef;
68use query::metrics::OnDone;
69use query::parser::{PromQuery, QueryLanguageParser, QueryStatement};
70use query::query_engine::DescribeResult;
71use query::query_engine::options::{QueryOptions, validate_catalog_and_schema};
72use servers::error::{
73    self as server_error, AuthSnafu, CommonMetaSnafu, ExecuteQuerySnafu,
74    OtlpMetricModeIncompatibleSnafu, ParsePromQLSnafu, UnexpectedResultSnafu,
75};
76use servers::interceptor::{
77    PromQueryInterceptor, PromQueryInterceptorRef, SqlQueryInterceptor, SqlQueryInterceptorRef,
78};
79use servers::otlp::metrics::legacy_normalize_otlp_name;
80use servers::prometheus_handler::PrometheusHandler;
81use servers::query_handler::sql::SqlQueryHandler;
82use session::context::{Channel, QueryContextRef};
83use session::table_name::table_idents_to_full_name;
84use snafu::prelude::*;
85use sql::ast::ObjectNamePartExt;
86use sql::dialect::Dialect;
87use sql::parser::{ParseOptions, ParserContext};
88use sql::statements::comment::CommentObject;
89use sql::statements::copy::{CopyDatabase, CopyTable};
90use sql::statements::statement::Statement;
91use sql::statements::tql::Tql;
92use sqlparser::ast::{AnalyzeFormat, ObjectName};
93pub use standalone::StandaloneDatanodeManager;
94use table::requests::{OTLP_METRIC_COMPAT_KEY, OTLP_METRIC_COMPAT_PROM};
95use tracing::Span;
96
97use crate::error::{
98    self, Error, ExecLogicalPlanSnafu, ExecutePromqlSnafu, ExternalSnafu, InvalidSqlSnafu,
99    ParseSqlSnafu, PermissionSnafu, PlanStatementSnafu, Result, SqlExecInterceptedSnafu,
100    StatementTimeoutSnafu, TableOperationSnafu,
101};
102use crate::service_config::InfluxdbMergeMode;
103use crate::stream_wrapper::CancellableStreamWrapper;
104
105lazy_static! {
106    static ref OTLP_LEGACY_DEFAULT_VALUE: String = "legacy".to_string();
107}
108
109/// The frontend instance contains necessary components, and implements many
110/// traits, like [`servers::query_handler::grpc::GrpcQueryHandler`],
111/// [`servers::query_handler::sql::SqlQueryHandler`], etc.
112#[derive(Clone)]
113pub struct Instance {
114    frontend_peer_addr: String,
115    catalog_manager: CatalogManagerRef,
116    pipeline_operator: Arc<PipelineOperator>,
117    statement_executor: Arc<StatementExecutor>,
118    query_engine: QueryEngineRef,
119    plugins: Plugins,
120    inserter: InserterRef,
121    deleter: DeleterRef,
122    table_metadata_manager: TableMetadataManagerRef,
123    event_recorder: Option<EventRecorderRef>,
124    process_manager: ProcessManagerRef,
125    slow_query_options: SlowQueryOptions,
126    influxdb_default_merge_mode: InfluxdbMergeMode,
127    trace_ingest_chunk_size: usize,
128    suspend: Arc<AtomicBool>,
129
130    // cache for otlp metrics
131    // first layer key: db-string
132    // key: direct input metric name
133    // value: if runs in legacy mode
134    otlp_metrics_table_legacy_cache: DashMap<String, DashMap<String, bool>>,
135}
136
137impl Instance {
138    pub fn frontend_peer_addr(&self) -> &str {
139        &self.frontend_peer_addr
140    }
141
142    pub fn catalog_manager(&self) -> &CatalogManagerRef {
143        &self.catalog_manager
144    }
145
146    pub fn query_engine(&self) -> &QueryEngineRef {
147        &self.query_engine
148    }
149
150    pub fn plugins(&self) -> &Plugins {
151        &self.plugins
152    }
153
154    pub fn statement_executor(&self) -> &StatementExecutorRef {
155        &self.statement_executor
156    }
157
158    pub fn table_metadata_manager(&self) -> &TableMetadataManagerRef {
159        &self.table_metadata_manager
160    }
161
162    pub fn inserter(&self) -> &InserterRef {
163        &self.inserter
164    }
165
166    pub fn process_manager(&self) -> &ProcessManagerRef {
167        &self.process_manager
168    }
169
170    pub fn node_manager(&self) -> &NodeManagerRef {
171        self.inserter.node_manager()
172    }
173
174    pub fn partition_manager(&self) -> &PartitionRuleManagerRef {
175        self.inserter.partition_manager()
176    }
177
178    pub fn cache_invalidator(&self) -> &CacheInvalidatorRef {
179        self.statement_executor.cache_invalidator()
180    }
181
182    pub fn procedure_executor(&self) -> &ProcedureExecutorRef {
183        self.statement_executor.procedure_executor()
184    }
185
186    pub fn suspend_state(&self) -> Arc<AtomicBool> {
187        self.suspend.clone()
188    }
189
190    pub(crate) fn is_suspended(&self) -> bool {
191        self.suspend.load(atomic::Ordering::Relaxed)
192    }
193}
194
195fn parse_stmt(sql: &str, dialect: &(dyn Dialect + Send + Sync)) -> Result<Vec<Statement>> {
196    ParserContext::create_with_dialect(sql, dialect, ParseOptions::default()).context(ParseSqlSnafu)
197}
198
199fn validate_analyze_stream_statement(stmt: &mut Statement) -> Result<()> {
200    let Statement::Explain(explain) = stmt else {
201        return InvalidSqlSnafu {
202            err_msg: "only EXPLAIN ANALYZE VERBOSE statement is supported",
203        }
204        .fail();
205    };
206    ensure!(
207        explain.analyze && explain.verbose,
208        InvalidSqlSnafu {
209            err_msg: "statement must be EXPLAIN ANALYZE VERBOSE"
210        }
211    );
212    match explain.format {
213        None | Some(AnalyzeFormat::JSON) => {
214            // Keep explicit FORMAT JSON accepted, but pass JSON through
215            // QueryContext.explain_format instead of the statement to avoid the
216            // planner's current `EXPLAIN VERBOSE with FORMAT` limitation.
217            explain.format = None;
218            Ok(())
219        }
220        Some(_) => InvalidSqlSnafu {
221            err_msg: "only FORMAT JSON is supported for analyze stream",
222        }
223        .fail(),
224    }
225}
226
227impl Instance {
228    fn statement_slow_query_timer(
229        &self,
230        stmt: &Statement,
231        schema_name: String,
232    ) -> Option<SlowQueryTimer> {
233        if !stmt.is_readonly() || !self.slow_query_options.enable {
234            return None;
235        }
236
237        self.event_recorder.clone().map(|event_recorder| {
238            SlowQueryTimer::new(
239                CatalogQueryStatement::Sql(stmt.clone()),
240                schema_name,
241                self.slow_query_options.threshold,
242                self.slow_query_options.sample_ratio,
243                self.slow_query_options.record_type,
244                event_recorder,
245            )
246        })
247    }
248
249    async fn query_statement(&self, stmt: Statement, query_ctx: QueryContextRef) -> Result<Output> {
250        check_permission(self.plugins.clone(), &stmt, &query_ctx)?;
251
252        let query_interceptor = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
253        let query_interceptor = query_interceptor.as_ref();
254
255        if should_track_statement_process(&stmt) {
256            let catalog_name = query_ctx.current_catalog().to_string();
257            let schema_name = query_ctx.current_schema();
258            let slow_query_timer = self.statement_slow_query_timer(&stmt, schema_name.clone());
259
260            let ticket = self.process_manager.register_query(
261                catalog_name,
262                vec![schema_name],
263                stmt.to_string(),
264                query_ctx.conn_info().to_string(),
265                Some(query_ctx.process_id()),
266                slow_query_timer,
267            );
268
269            let query_fut = self.exec_statement_with_timeout(stmt, query_ctx, query_interceptor);
270
271            CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
272                .await
273                .map_err(|_| error::CancelledSnafu.build())?
274                .map(|output| {
275                    let Output { meta, data } = output;
276
277                    let data = match data {
278                        OutputData::Stream(stream) => OutputData::Stream(Box::pin(
279                            CancellableStreamWrapper::new(stream, ticket),
280                        )),
281                        other => other,
282                    };
283                    Output { data, meta }
284                })
285        } else {
286            self.exec_statement_with_timeout(stmt, query_ctx, query_interceptor)
287                .await
288        }
289    }
290
291    async fn exec_statement_with_timeout(
292        &self,
293        stmt: Statement,
294        query_ctx: QueryContextRef,
295        query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
296    ) -> Result<Output> {
297        let timeout = derive_timeout(&stmt, &query_ctx);
298        match timeout {
299            Some(timeout) => {
300                let start = tokio::time::Instant::now();
301                let output = tokio::time::timeout(
302                    timeout,
303                    self.exec_statement(stmt, query_ctx, query_interceptor),
304                )
305                .await
306                .map_err(|_| StatementTimeoutSnafu.build())??;
307                // compute remaining timeout
308                let remaining_timeout = timeout.checked_sub(start.elapsed()).unwrap_or_default();
309                attach_timeout(output, remaining_timeout)
310            }
311            None => {
312                self.exec_statement(stmt, query_ctx, query_interceptor)
313                    .await
314            }
315        }
316    }
317
318    async fn exec_statement(
319        &self,
320        stmt: Statement,
321        query_ctx: QueryContextRef,
322        query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
323    ) -> Result<Output> {
324        match stmt {
325            Statement::Query(_) | Statement::Explain(_) | Statement::Delete(_) => {
326                // TODO: remove this when format is supported in datafusion
327                if let Statement::Explain(explain) = &stmt
328                    && let Some(format) = explain.format()
329                {
330                    query_ctx.set_explain_format(format.to_string());
331                }
332
333                self.plan_and_exec_sql(stmt, &query_ctx, query_interceptor)
334                    .await
335            }
336            Statement::Tql(tql) => {
337                self.plan_and_exec_tql(&query_ctx, query_interceptor, tql)
338                    .await
339            }
340            _ => {
341                query_interceptor.pre_execute(Some(&stmt), None, query_ctx.clone())?;
342                self.statement_executor
343                    .execute_sql(stmt, query_ctx)
344                    .await
345                    .context(TableOperationSnafu)
346            }
347        }
348    }
349
350    async fn plan_and_exec_sql(
351        &self,
352        stmt: Statement,
353        query_ctx: &QueryContextRef,
354        query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
355    ) -> Result<Output> {
356        let stmt = QueryStatement::Sql(stmt);
357        let plan = self
358            .statement_executor
359            .plan(&stmt, query_ctx.clone())
360            .await?;
361        let QueryStatement::Sql(stmt) = stmt else {
362            unreachable!()
363        };
364        query_interceptor.pre_execute(Some(&stmt), Some(&plan), query_ctx.clone())?;
365
366        self.statement_executor
367            .exec_plan(plan, query_ctx.clone())
368            .await
369            .context(TableOperationSnafu)
370    }
371
372    async fn plan_and_exec_tql(
373        &self,
374        query_ctx: &QueryContextRef,
375        query_interceptor: Option<&SqlQueryInterceptorRef<Error>>,
376        tql: Tql,
377    ) -> Result<Output> {
378        let plan = self
379            .statement_executor
380            .plan_tql(tql.clone(), query_ctx)
381            .await?;
382        query_interceptor.pre_execute(
383            Some(&Statement::Tql(tql)),
384            Some(&plan),
385            query_ctx.clone(),
386        )?;
387        self.statement_executor
388            .exec_plan(plan, query_ctx.clone())
389            .await
390            .context(TableOperationSnafu)
391    }
392
393    async fn check_otlp_legacy(
394        &self,
395        names: &[&String],
396        ctx: QueryContextRef,
397    ) -> server_error::Result<bool> {
398        let db_string = ctx.get_db_string();
399        // fast cache check
400        let cache = self
401            .otlp_metrics_table_legacy_cache
402            .entry(db_string.clone())
403            .or_default();
404        if let Some(flag) = fast_legacy_check(&cache, names)? {
405            return Ok(flag);
406        }
407        // release cache reference to avoid lock contention
408        drop(cache);
409
410        let catalog = ctx.current_catalog();
411        let schema = ctx.current_schema();
412
413        // query legacy table names
414        let normalized_names = names
415            .iter()
416            .map(|n| legacy_normalize_otlp_name(n))
417            .collect::<Vec<_>>();
418        let table_names = normalized_names
419            .iter()
420            .map(|n| TableNameKey::new(catalog, &schema, n))
421            .collect::<Vec<_>>();
422        let table_values = self
423            .table_metadata_manager()
424            .table_name_manager()
425            .batch_get(table_names)
426            .await
427            .context(CommonMetaSnafu)?;
428        let table_ids = table_values
429            .into_iter()
430            .filter_map(|v| v.map(|vi| vi.table_id()))
431            .collect::<Vec<_>>();
432
433        // means no existing table is found, use new mode
434        if table_ids.is_empty() {
435            let cache = self
436                .otlp_metrics_table_legacy_cache
437                .entry(db_string)
438                .or_default();
439            names.iter().for_each(|name| {
440                cache.insert((*name).clone(), false);
441            });
442            return Ok(false);
443        }
444
445        // has existing table, check table options
446        let table_infos = self
447            .table_metadata_manager()
448            .table_info_manager()
449            .batch_get(&table_ids)
450            .await
451            .context(CommonMetaSnafu)?;
452        let options = table_infos
453            .values()
454            .map(|info| {
455                info.table_info
456                    .meta
457                    .options
458                    .extra_options
459                    .get(OTLP_METRIC_COMPAT_KEY)
460                    .unwrap_or(&OTLP_LEGACY_DEFAULT_VALUE)
461            })
462            .collect::<Vec<_>>();
463        let cache = self
464            .otlp_metrics_table_legacy_cache
465            .entry(db_string)
466            .or_default();
467        if !options.is_empty() {
468            // check value consistency
469            let has_prom = options.iter().any(|opt| *opt == OTLP_METRIC_COMPAT_PROM);
470            let has_legacy = options
471                .iter()
472                .any(|opt| *opt == OTLP_LEGACY_DEFAULT_VALUE.as_str());
473            ensure!(!(has_prom && has_legacy), OtlpMetricModeIncompatibleSnafu);
474            let flag = has_legacy;
475            names.iter().for_each(|name| {
476                cache.insert((*name).clone(), flag);
477            });
478            Ok(flag)
479        } else {
480            // no table info, use new mode
481            names.iter().for_each(|name| {
482                cache.insert((*name).clone(), false);
483            });
484            Ok(false)
485        }
486    }
487}
488
489fn fast_legacy_check(
490    cache: &DashMap<String, bool>,
491    names: &[&String],
492) -> server_error::Result<Option<bool>> {
493    let hit_cache = names
494        .iter()
495        .filter_map(|name| cache.get(*name))
496        .collect::<Vec<_>>();
497    if !hit_cache.is_empty() {
498        let hit_legacy = hit_cache.iter().any(|en| *en.value());
499        let hit_prom = hit_cache.iter().any(|en| !*en.value());
500
501        // hit but have true and false, means both legacy and new mode are used
502        // we cannot handle this case, so return error
503        // add doc links in err msg later
504        ensure!(!(hit_legacy && hit_prom), OtlpMetricModeIncompatibleSnafu);
505
506        let flag = hit_legacy;
507        // drop hit_cache to release references before inserting to avoid deadlock
508        drop(hit_cache);
509
510        // set cache for all names
511        names.iter().for_each(|name| {
512            if !cache.contains_key(*name) {
513                cache.insert((*name).clone(), flag);
514            }
515        });
516        Ok(Some(flag))
517    } else {
518        Ok(None)
519    }
520}
521
522/// If the relevant variables are set, the timeout is enforced for all PostgreSQL statements.
523/// For MySQL, it applies only to read-only statements.
524fn derive_timeout(stmt: &Statement, query_ctx: &QueryContextRef) -> Option<Duration> {
525    let query_timeout = query_ctx.query_timeout()?;
526    if query_timeout.is_zero() {
527        return None;
528    }
529    match query_ctx.channel() {
530        Channel::Mysql if stmt.is_readonly() => Some(query_timeout),
531        Channel::Postgres => Some(query_timeout),
532        _ => None,
533    }
534}
535
536/// Derives timeout for plan execution.
537fn derive_timeout_for_plan(plan: &LogicalPlan, query_ctx: &QueryContextRef) -> Option<Duration> {
538    let query_timeout = query_ctx.query_timeout()?;
539    if query_timeout.is_zero() {
540        return None;
541    }
542    match query_ctx.channel() {
543        Channel::Mysql if is_readonly_plan(plan) => Some(query_timeout),
544        Channel::Postgres => Some(query_timeout),
545        _ => None,
546    }
547}
548
549fn attach_timeout(output: Output, mut timeout: Duration) -> Result<Output> {
550    if timeout.is_zero() {
551        return StatementTimeoutSnafu.fail();
552    }
553
554    let output = match output.data {
555        OutputData::AffectedRows(_) | OutputData::RecordBatches(_) => output,
556        OutputData::Stream(mut stream) => {
557            let schema = stream.schema();
558            let s = Box::pin(stream! {
559                let mut start = tokio::time::Instant::now();
560                while let Some(item) = tokio::time::timeout(timeout, stream.next()).await.map_err(|_| StreamTimeoutSnafu.build())? {
561                    yield item;
562
563                    let now = tokio::time::Instant::now();
564                    timeout = timeout.checked_sub(now - start).unwrap_or(Duration::ZERO);
565                    start = now;
566                    // tokio::time::timeout may not return an error immediately when timeout is 0.
567                    if timeout.is_zero() {
568                        StreamTimeoutSnafu.fail()?;
569                    }
570                }
571            }) as Pin<Box<dyn Stream<Item = _> + Send>>;
572            let stream = RecordBatchStreamWrapper {
573                schema,
574                stream: s,
575                output_ordering: None,
576                metrics: Default::default(),
577                span: Span::current(),
578            };
579            Output::new(OutputData::Stream(Box::pin(stream)), output.meta)
580        }
581    };
582
583    Ok(output)
584}
585
586impl Instance {
587    #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_analyze_stream_query")]
588    async fn do_analyze_stream_query_inner(
589        &self,
590        query: &str,
591        query_ctx: QueryContextRef,
592    ) -> Result<Output> {
593        ensure!(!self.is_suspended(), error::SuspendedSnafu);
594
595        let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
596        let query_interceptor = query_interceptor_opt.as_ref();
597        let query = query_interceptor.pre_parsing(query, query_ctx.clone())?;
598        let mut stmts = parse_stmt(query.as_ref(), query_ctx.sql_dialect())
599            .and_then(|stmts| query_interceptor.post_parsing(stmts, query_ctx.clone()))?;
600
601        ensure!(
602            stmts.len() == 1,
603            InvalidSqlSnafu {
604                err_msg: "only single EXPLAIN ANALYZE VERBOSE statement is supported"
605            }
606        );
607        let mut stmt = stmts.remove(0);
608        validate_analyze_stream_statement(&mut stmt)?;
609        query_ctx.set_explain_format(AnalyzeFormat::JSON.to_string());
610
611        let checker_ref = self.plugins.get::<PermissionCheckerRef>();
612        checker_ref
613            .as_ref()
614            .check_permission_with_context(
615                query_ctx.current_user(),
616                PermissionReq::SqlStatement(&stmt),
617                Some(&query_ctx.current_schema()),
618            )
619            .context(PermissionSnafu)?;
620        check_permission(self.plugins.clone(), &stmt, &query_ctx)?;
621        let catalog_name = query_ctx.current_catalog().to_string();
622        let schema_name = query_ctx.current_schema();
623        let slow_query_timer = self.statement_slow_query_timer(&stmt, schema_name.clone());
624        let ticket = self.process_manager.register_query(
625            catalog_name,
626            vec![schema_name],
627            stmt.to_string(),
628            query_ctx.conn_info().to_string(),
629            Some(query_ctx.process_id()),
630            slow_query_timer,
631        );
632        let query_fut =
633            self.exec_statement_with_timeout(stmt, query_ctx.clone(), query_interceptor);
634        let output = CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
635            .await
636            .map_err(|_| error::CancelledSnafu.build())??;
637        let Output { meta, data } = output;
638        let data = match data {
639            OutputData::Stream(stream) => OutputData::Stream(Box::pin(
640                CancellableStreamWrapper::new_cancel_on_drop(stream, ticket),
641            )),
642            other => other,
643        };
644        query_interceptor.post_execute(Output { data, meta }, query_ctx)
645    }
646
647    #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_query")]
648    async fn do_query_inner(&self, query: &str, query_ctx: QueryContextRef) -> Vec<Result<Output>> {
649        if self.is_suspended() {
650            return vec![error::SuspendedSnafu {}.fail()];
651        }
652
653        let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
654        let query_interceptor = query_interceptor_opt.as_ref();
655        let query = match query_interceptor.pre_parsing(query, query_ctx.clone()) {
656            Ok(q) => q,
657            Err(e) => return vec![Err(e)],
658        };
659
660        let checker_ref = self.plugins.get::<PermissionCheckerRef>();
661        let checker = checker_ref.as_ref();
662
663        match parse_stmt(query.as_ref(), query_ctx.sql_dialect())
664            .and_then(|stmts| query_interceptor.post_parsing(stmts, query_ctx.clone()))
665        {
666            Ok(stmts) => {
667                if stmts.is_empty() {
668                    return vec![
669                        InvalidSqlSnafu {
670                            err_msg: "empty statements",
671                        }
672                        .fail(),
673                    ];
674                }
675
676                let mut results = Vec::with_capacity(stmts.len());
677                for stmt in stmts {
678                    if let Err(e) = checker
679                        .check_permission_with_context(
680                            query_ctx.current_user(),
681                            PermissionReq::SqlStatement(&stmt),
682                            Some(&query_ctx.current_schema()),
683                        )
684                        .context(PermissionSnafu)
685                    {
686                        results.push(Err(e));
687                        break;
688                    }
689
690                    match self.query_statement(stmt.clone(), query_ctx.clone()).await {
691                        Ok(output) => {
692                            let output_result =
693                                query_interceptor.post_execute(output, query_ctx.clone());
694                            results.push(output_result);
695                        }
696                        Err(e) => {
697                            if e.status_code().should_log_error() {
698                                error!(e; "Failed to execute query: {stmt}");
699                            } else {
700                                debug!("Failed to execute query: {stmt}, {e}");
701                            }
702                            results.push(Err(e));
703                            break;
704                        }
705                    }
706                }
707                results
708            }
709            Err(e) => {
710                vec![Err(e)]
711            }
712        }
713    }
714
715    async fn exec_plan(&self, plan: LogicalPlan, query_ctx: QueryContextRef) -> Result<Output> {
716        self.query_engine
717            .execute(plan, query_ctx)
718            .await
719            .context(ExecLogicalPlanSnafu)
720    }
721
722    async fn exec_plan_with_timeout(
723        &self,
724        plan: LogicalPlan,
725        query_ctx: QueryContextRef,
726    ) -> Result<Output> {
727        let timeout = derive_timeout_for_plan(&plan, &query_ctx);
728        match timeout {
729            Some(timeout) => {
730                let start = tokio::time::Instant::now();
731                let output = tokio::time::timeout(timeout, self.exec_plan(plan, query_ctx))
732                    .await
733                    .map_err(|_| StatementTimeoutSnafu.build())??;
734                let remaining_timeout = timeout.checked_sub(start.elapsed()).unwrap_or_default();
735                attach_timeout(output, remaining_timeout)
736            }
737            None => self.exec_plan(plan, query_ctx).await,
738        }
739    }
740
741    async fn do_exec_plan_inner(
742        &self,
743        plan: LogicalPlan,
744        stmt: Option<Statement>,
745        query_ctx: QueryContextRef,
746    ) -> Result<Output> {
747        ensure!(!self.is_suspended(), error::SuspendedSnafu);
748
749        let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
750        let query_interceptor = query_interceptor_opt.as_ref();
751
752        query_interceptor.pre_execute(stmt.as_ref(), Some(&plan), query_ctx.clone())?;
753
754        let query = stmt
755            .as_ref()
756            .map(|s| s.to_string())
757            .unwrap_or_else(|| plan.display_indent().to_string());
758
759        let plan_is_readonly = is_readonly_plan(&plan);
760        let result = if should_track_plan_process(stmt.as_ref(), &plan) {
761            let catalog_name = query_ctx.current_catalog().to_string();
762            let schema_name = query_ctx.current_schema();
763            let slow_query_timer = if plan_is_readonly {
764                self.slow_query_options
765                    .enable
766                    .then(|| self.event_recorder.clone())
767                    .flatten()
768                    .map(|event_recorder| {
769                        SlowQueryTimer::new(
770                            CatalogQueryStatement::Plan(query.clone()),
771                            schema_name.clone(),
772                            self.slow_query_options.threshold,
773                            self.slow_query_options.sample_ratio,
774                            self.slow_query_options.record_type,
775                            event_recorder,
776                        )
777                    })
778            } else {
779                None
780            };
781
782            let ticket = self.process_manager.register_query(
783                catalog_name,
784                vec![schema_name],
785                query,
786                query_ctx.conn_info().to_string(),
787                Some(query_ctx.process_id()),
788                slow_query_timer,
789            );
790
791            let query_fut = self.exec_plan_with_timeout(plan, query_ctx.clone());
792
793            CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
794                .await
795                .map_err(|_| error::CancelledSnafu.build())?
796                .map(|output| {
797                    let Output { meta, data } = output;
798
799                    let data = match data {
800                        OutputData::Stream(stream) => OutputData::Stream(Box::pin(
801                            CancellableStreamWrapper::new(stream, ticket),
802                        )),
803                        other => other,
804                    };
805                    Output { data, meta }
806                })
807        } else {
808            self.exec_plan_with_timeout(plan, query_ctx.clone()).await
809        };
810
811        result.and_then(|output| query_interceptor.post_execute(output, query_ctx))
812    }
813
814    #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_promql_query")]
815    async fn do_promql_query_inner(
816        &self,
817        query: &PromQuery,
818        query_ctx: QueryContextRef,
819    ) -> Vec<Result<Output>> {
820        if self.is_suspended() {
821            return vec![error::SuspendedSnafu {}.fail()];
822        }
823
824        // check will be done in prometheus handler's do_query
825        let result = PrometheusHandler::do_query(self, query, query_ctx)
826            .await
827            .with_context(|_| ExecutePromqlSnafu {
828                query: format!("{query:?}"),
829            });
830        vec![result]
831    }
832
833    async fn do_describe_inner(
834        &self,
835        stmt: Statement,
836        query_ctx: QueryContextRef,
837    ) -> Result<Option<DescribeResult>> {
838        ensure!(!self.is_suspended(), error::SuspendedSnafu);
839
840        // EXPLAIN / EXPLAIN ANALYZE wrap an inner statement; describe them when the
841        // wrapped statement is something we already plan (so that bind parameters
842        // in the inner query get their types inferred). See #8029.
843        let is_inner_plannable = |s: &Statement| {
844            matches!(
845                s,
846                Statement::Insert(_) | Statement::Query(_) | Statement::Delete(_)
847            )
848        };
849        let plannable = is_inner_plannable(&stmt)
850            || matches!(&stmt, Statement::Explain(explain) if is_inner_plannable(explain.statement.as_ref()));
851
852        if plannable {
853            self.plugins
854                .get::<PermissionCheckerRef>()
855                .as_ref()
856                .check_permission_with_context(
857                    query_ctx.current_user(),
858                    PermissionReq::SqlStatement(&stmt),
859                    Some(&query_ctx.current_schema()),
860                )
861                .context(PermissionSnafu)?;
862
863            let plan = self
864                .query_engine
865                .planner()
866                .plan(&QueryStatement::Sql(stmt), query_ctx.clone())
867                .await
868                .context(PlanStatementSnafu)?;
869            self.query_engine
870                .describe(plan, query_ctx)
871                .await
872                .map(Some)
873                .context(error::DescribeStatementSnafu)
874        } else {
875            Ok(None)
876        }
877    }
878
879    async fn is_valid_schema_inner(&self, catalog: &str, schema: &str) -> Result<bool> {
880        self.catalog_manager
881            .schema_exists(catalog, schema, None)
882            .await
883            .context(error::CatalogSnafu)
884    }
885}
886
887#[async_trait]
888impl SqlQueryHandler for Instance {
889    async fn do_query(
890        &self,
891        query: &str,
892        query_ctx: QueryContextRef,
893    ) -> Vec<server_error::Result<Output>> {
894        self.do_query_inner(query, query_ctx)
895            .await
896            .into_iter()
897            .map(|result| result.map_err(BoxedError::new).context(ExecuteQuerySnafu))
898            .collect()
899    }
900
901    async fn do_analyze_stream_query(
902        &self,
903        query: &str,
904        query_ctx: QueryContextRef,
905    ) -> server_error::Result<Output> {
906        self.do_analyze_stream_query_inner(query, query_ctx)
907            .await
908            .map_err(BoxedError::new)
909            .context(ExecuteQuerySnafu)
910    }
911
912    async fn do_exec_plan(
913        &self,
914        plan: LogicalPlan,
915        stmt: Option<Statement>,
916        query_ctx: QueryContextRef,
917    ) -> server_error::Result<Output> {
918        self.do_exec_plan_inner(plan, stmt, query_ctx)
919            .await
920            .map_err(BoxedError::new)
921            .context(server_error::ExecutePlanSnafu)
922    }
923
924    async fn do_promql_query(
925        &self,
926        query: &PromQuery,
927        query_ctx: QueryContextRef,
928    ) -> Vec<server_error::Result<Output>> {
929        self.do_promql_query_inner(query, query_ctx)
930            .await
931            .into_iter()
932            .map(|result| result.map_err(BoxedError::new).context(ExecuteQuerySnafu))
933            .collect()
934    }
935
936    async fn do_describe(
937        &self,
938        stmt: Statement,
939        query_ctx: QueryContextRef,
940    ) -> server_error::Result<Option<DescribeResult>> {
941        self.do_describe_inner(stmt, query_ctx)
942            .await
943            .map_err(BoxedError::new)
944            .context(server_error::DescribeStatementSnafu)
945    }
946
947    async fn is_valid_schema(&self, catalog: &str, schema: &str) -> server_error::Result<bool> {
948        self.is_valid_schema_inner(catalog, schema)
949            .await
950            .map_err(BoxedError::new)
951            .context(server_error::CheckDatabaseValiditySnafu)
952    }
953}
954
955/// Attaches a timer to the output and observes it once the output is exhausted.
956pub fn attach_timer(output: Output, timer: HistogramTimer) -> Output {
957    match output.data {
958        OutputData::AffectedRows(_) | OutputData::RecordBatches(_) => output,
959        OutputData::Stream(stream) => {
960            let stream = OnDone::new(stream, move || {
961                timer.observe_duration();
962            });
963            Output::new(OutputData::Stream(Box::pin(stream)), output.meta)
964        }
965    }
966}
967
968#[async_trait]
969impl PrometheusHandler for Instance {
970    #[tracing::instrument(skip_all)]
971    async fn do_query(
972        &self,
973        query: &PromQuery,
974        query_ctx: QueryContextRef,
975    ) -> server_error::Result<Output> {
976        let interceptor = self
977            .plugins
978            .get::<PromQueryInterceptorRef<server_error::Error>>();
979
980        self.plugins
981            .get::<PermissionCheckerRef>()
982            .as_ref()
983            .check_permission(query_ctx.current_user(), PermissionReq::PromQuery)
984            .context(AuthSnafu)?;
985
986        let stmt = QueryLanguageParser::parse_promql(query, &query_ctx).with_context(|_| {
987            ParsePromQLSnafu {
988                query: query.clone(),
989            }
990        })?;
991
992        let plan = self
993            .statement_executor
994            .plan(&stmt, query_ctx.clone())
995            .await
996            .map_err(BoxedError::new)
997            .context(ExecuteQuerySnafu)?;
998
999        let QueryStatement::Promql(eval_stmt, _) = &stmt else {
1000            unreachable!("query is parsed from promql");
1001        };
1002
1003        interceptor.pre_execute(query, &eval_stmt.expr, Some(&plan), query_ctx.clone())?;
1004
1005        // Take the EvalStmt from the original QueryStatement and use it to create the CatalogQueryStatement.
1006        let query_statement = if let QueryStatement::Promql(eval_stmt, alias) = stmt {
1007            CatalogQueryStatement::Promql(eval_stmt, alias)
1008        } else {
1009            // It should not happen since the query is already parsed successfully.
1010            return UnexpectedResultSnafu {
1011                reason: "The query should always be promql.".to_string(),
1012            }
1013            .fail();
1014        };
1015        let raw_query = query_statement.to_string();
1016
1017        let slow_query_timer = self
1018            .slow_query_options
1019            .enable
1020            .then(|| self.event_recorder.clone())
1021            .flatten()
1022            .map(|event_recorder| {
1023                SlowQueryTimer::new(
1024                    query_statement,
1025                    query_ctx.current_schema(),
1026                    self.slow_query_options.threshold,
1027                    self.slow_query_options.sample_ratio,
1028                    self.slow_query_options.record_type,
1029                    event_recorder,
1030                )
1031            });
1032
1033        let ticket = self.process_manager.register_query(
1034            query_ctx.current_catalog().to_string(),
1035            vec![query_ctx.current_schema()],
1036            raw_query,
1037            query_ctx.conn_info().to_string(),
1038            Some(query_ctx.process_id()),
1039            slow_query_timer,
1040        );
1041
1042        let query_fut = self.statement_executor.exec_plan(plan, query_ctx.clone());
1043
1044        let output = CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
1045            .await
1046            .map_err(|_| servers::error::CancelledSnafu.build())?
1047            .map(|output| {
1048                let Output { meta, data } = output;
1049                let data = match data {
1050                    OutputData::Stream(stream) => {
1051                        OutputData::Stream(Box::pin(CancellableStreamWrapper::new(stream, ticket)))
1052                    }
1053                    other => other,
1054                };
1055                Output { data, meta }
1056            })
1057            .map_err(BoxedError::new)
1058            .context(ExecuteQuerySnafu)?;
1059
1060        Ok(interceptor.post_execute(output, query_ctx)?)
1061    }
1062
1063    async fn query_metric_names(
1064        &self,
1065        matchers: Vec<Matcher>,
1066        ctx: &QueryContextRef,
1067    ) -> server_error::Result<Vec<String>> {
1068        self.handle_query_metric_names(matchers, ctx)
1069            .await
1070            .map_err(BoxedError::new)
1071            .context(ExecuteQuerySnafu)
1072    }
1073
1074    async fn query_label_values(
1075        &self,
1076        metric: String,
1077        label_name: String,
1078        matchers: Vec<Matcher>,
1079        start: SystemTime,
1080        end: SystemTime,
1081        ctx: &QueryContextRef,
1082    ) -> server_error::Result<Vec<String>> {
1083        self.handle_query_label_values(metric, label_name, matchers, start, end, ctx)
1084            .await
1085            .map_err(BoxedError::new)
1086            .context(ExecuteQuerySnafu)
1087    }
1088
1089    fn catalog_manager(&self) -> CatalogManagerRef {
1090        self.catalog_manager.clone()
1091    }
1092}
1093
1094/// Validate `stmt.database` permission if it's presented.
1095macro_rules! validate_db_permission {
1096    ($stmt: expr, $query_ctx: expr) => {
1097        if let Some(database) = &$stmt.database {
1098            validate_catalog_and_schema($query_ctx.current_catalog(), database, $query_ctx)
1099                .map_err(BoxedError::new)
1100                .context(SqlExecInterceptedSnafu)?;
1101        }
1102    };
1103}
1104
1105pub fn check_permission(
1106    plugins: Plugins,
1107    stmt: &Statement,
1108    query_ctx: &QueryContextRef,
1109) -> Result<()> {
1110    let need_validate = plugins
1111        .get::<QueryOptions>()
1112        .map(|opts| opts.disallow_cross_catalog_query)
1113        .unwrap_or_default();
1114
1115    if !need_validate {
1116        return Ok(());
1117    }
1118
1119    match stmt {
1120        // Will be checked in execution.
1121        // TODO(dennis): add a hook for admin commands.
1122        Statement::Admin(_) => {}
1123        // These are executed by query engine, and will be checked there.
1124        Statement::Query(_)
1125        | Statement::Explain(_)
1126        | Statement::Tql(_)
1127        | Statement::Delete(_)
1128        | Statement::DeclareCursor(_)
1129        | Statement::Copy(sql::statements::copy::Copy::CopyQueryTo(_)) => {}
1130        // database ops won't be checked
1131        Statement::CreateDatabase(_)
1132        | Statement::ShowDatabases(_)
1133        | Statement::DropDatabase(_)
1134        | Statement::AlterDatabase(_)
1135        | Statement::DropFlow(_)
1136        | Statement::Use(_) => {}
1137        #[cfg(feature = "enterprise")]
1138        Statement::DropTrigger(_) => {}
1139        Statement::ShowCreateDatabase(stmt) => {
1140            validate_database(&stmt.database_name, query_ctx)?;
1141        }
1142        Statement::ShowCreateTable(stmt) => {
1143            validate_param(&stmt.table_name, query_ctx)?;
1144        }
1145        Statement::ShowCreateFlow(stmt) => {
1146            validate_flow(&stmt.flow_name, query_ctx)?;
1147        }
1148        #[cfg(feature = "enterprise")]
1149        Statement::ShowCreateTrigger(stmt) => {
1150            validate_param(&stmt.trigger_name, query_ctx)?;
1151        }
1152        Statement::ShowCreateView(stmt) => {
1153            validate_param(&stmt.view_name, query_ctx)?;
1154        }
1155        Statement::CreateExternalTable(stmt) => {
1156            validate_param(&stmt.name, query_ctx)?;
1157        }
1158        Statement::CreateFlow(stmt) => {
1159            // TODO: should also validate source table name here?
1160            validate_param(&stmt.sink_table_name, query_ctx)?;
1161        }
1162        #[cfg(feature = "enterprise")]
1163        Statement::CreateTrigger(stmt) => {
1164            validate_param(&stmt.trigger_name, query_ctx)?;
1165        }
1166        Statement::CreateView(stmt) => {
1167            validate_param(&stmt.name, query_ctx)?;
1168        }
1169        Statement::AlterTable(stmt) => {
1170            validate_param(stmt.table_name(), query_ctx)?;
1171        }
1172        #[cfg(feature = "enterprise")]
1173        Statement::AlterTrigger(_) => {}
1174        // set/show variable now only alter/show variable in session
1175        Statement::SetVariables(_) | Statement::ShowVariables(_) => {}
1176        // show charset and show collation won't be checked
1177        Statement::ShowCharset(_) | Statement::ShowCollation(_) => {}
1178
1179        Statement::Comment(comment) => match &comment.object {
1180            CommentObject::Table(table) => validate_param(table, query_ctx)?,
1181            CommentObject::Column { table, .. } => validate_param(table, query_ctx)?,
1182            CommentObject::Flow(flow) => validate_flow(flow, query_ctx)?,
1183        },
1184
1185        Statement::Insert(insert) => {
1186            let name = insert.table_name().context(ParseSqlSnafu)?;
1187            validate_param(name, query_ctx)?;
1188        }
1189        Statement::CreateTable(stmt) => {
1190            validate_param(&stmt.name, query_ctx)?;
1191        }
1192        Statement::CreateTableLike(stmt) => {
1193            validate_param(&stmt.table_name, query_ctx)?;
1194            validate_param(&stmt.source_name, query_ctx)?;
1195        }
1196        Statement::DropTable(drop_stmt) => {
1197            for table_name in drop_stmt.table_names() {
1198                validate_param(table_name, query_ctx)?;
1199            }
1200        }
1201        Statement::DropView(stmt) => {
1202            validate_param(&stmt.view_name, query_ctx)?;
1203        }
1204        Statement::ShowTables(stmt) => {
1205            validate_db_permission!(stmt, query_ctx);
1206        }
1207        Statement::ShowTableStatus(stmt) => {
1208            validate_db_permission!(stmt, query_ctx);
1209        }
1210        Statement::ShowColumns(stmt) => {
1211            validate_db_permission!(stmt, query_ctx);
1212        }
1213        Statement::ShowIndex(stmt) => {
1214            validate_db_permission!(stmt, query_ctx);
1215        }
1216        Statement::ShowRegion(stmt) => {
1217            validate_db_permission!(stmt, query_ctx);
1218        }
1219        Statement::ShowViews(stmt) => {
1220            validate_db_permission!(stmt, query_ctx);
1221        }
1222        Statement::ShowFlows(stmt) => {
1223            validate_db_permission!(stmt, query_ctx);
1224        }
1225        #[cfg(feature = "enterprise")]
1226        Statement::ShowTriggers(_stmt) => {
1227            // The trigger is organized based on the catalog dimension, so there
1228            // is no need to check the permission of the database(schema).
1229        }
1230        Statement::ShowStatus(_stmt) => {}
1231        Statement::ShowSearchPath(_stmt) => {}
1232        Statement::DescribeTable(stmt) => {
1233            validate_param(stmt.name(), query_ctx)?;
1234        }
1235        Statement::Copy(sql::statements::copy::Copy::CopyTable(stmt)) => match stmt {
1236            CopyTable::To(copy_table_to) => validate_param(&copy_table_to.table_name, query_ctx)?,
1237            CopyTable::From(copy_table_from) => {
1238                validate_param(&copy_table_from.table_name, query_ctx)?
1239            }
1240        },
1241        Statement::Copy(sql::statements::copy::Copy::CopyDatabase(copy_database)) => {
1242            match copy_database {
1243                CopyDatabase::To(stmt) => validate_database(&stmt.database_name, query_ctx)?,
1244                CopyDatabase::From(stmt) => validate_database(&stmt.database_name, query_ctx)?,
1245            }
1246        }
1247        Statement::TruncateTable(stmt) => {
1248            validate_param(stmt.table_name(), query_ctx)?;
1249        }
1250        // cursor operations are always allowed once it's created
1251        Statement::FetchCursor(_) | Statement::CloseCursor(_) => {}
1252        // User can only kill process in their own catalog.
1253        Statement::Kill(_) => {}
1254        // SHOW PROCESSLIST
1255        Statement::ShowProcesslist(_) => {}
1256    }
1257    Ok(())
1258}
1259
1260fn validate_param(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1261    let (catalog, schema, _) = table_idents_to_full_name(name, query_ctx)
1262        .map_err(BoxedError::new)
1263        .context(ExternalSnafu)?;
1264
1265    validate_catalog_and_schema(&catalog, &schema, query_ctx)
1266        .map_err(BoxedError::new)
1267        .context(SqlExecInterceptedSnafu)
1268}
1269
1270fn validate_flow(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1271    let catalog = match &name.0[..] {
1272        [_flow] => query_ctx.current_catalog().to_string(),
1273        [catalog, _flow] => catalog.to_string_unquoted(),
1274        _ => {
1275            return InvalidSqlSnafu {
1276                err_msg: format!(
1277                    "expect flow name to be <catalog>.<flow_name> or <flow_name>, actual: {name}",
1278                ),
1279            }
1280            .fail();
1281        }
1282    };
1283
1284    let schema = query_ctx.current_schema();
1285
1286    validate_catalog_and_schema(&catalog, &schema, query_ctx)
1287        .map_err(BoxedError::new)
1288        .context(SqlExecInterceptedSnafu)
1289}
1290
1291fn validate_database(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1292    let (catalog, schema) = match &name.0[..] {
1293        [schema] => (
1294            query_ctx.current_catalog().to_string(),
1295            schema.to_string_unquoted(),
1296        ),
1297        [catalog, schema] => (catalog.to_string_unquoted(), schema.to_string_unquoted()),
1298        _ => InvalidSqlSnafu {
1299            err_msg: format!(
1300                "expect database name to be <catalog>.<schema> or <schema>, actual: {name}",
1301            ),
1302        }
1303        .fail()?,
1304    };
1305
1306    validate_catalog_and_schema(&catalog, &schema, query_ctx)
1307        .map_err(BoxedError::new)
1308        .context(SqlExecInterceptedSnafu)
1309}
1310
1311fn is_readonly_plan(plan: &LogicalPlan) -> bool {
1312    !matches!(plan, LogicalPlan::Dml(_) | LogicalPlan::Ddl(_))
1313}
1314
1315fn should_track_statement_process(stmt: &Statement) -> bool {
1316    stmt.is_readonly()
1317        || matches!(stmt, Statement::Insert(insert) if insert.has_non_values_query_source())
1318}
1319
1320fn should_track_plan_process(stmt: Option<&Statement>, plan: &LogicalPlan) -> bool {
1321    is_readonly_plan(plan)
1322        || matches!(stmt, Some(Statement::Insert(insert)) if insert.has_non_values_query_source())
1323}
1324
1325#[cfg(test)]
1326mod tests {
1327    use std::collections::HashMap;
1328    use std::future::Future;
1329    use std::pin::Pin;
1330    use std::sync::atomic::{AtomicBool, Ordering};
1331    use std::sync::{Arc, Barrier};
1332    use std::task::{Context, Poll};
1333    use std::thread;
1334    use std::time::{Duration, Instant};
1335
1336    use api::v1::meta::{ProcedureDetailResponse, ReconcileRequest, ReconcileResponse};
1337    use catalog::process_manager::ProcessManager;
1338    use common_base::Plugins;
1339    use common_error::ext::{BoxedError, PlainError};
1340    use common_error::status_code::StatusCode;
1341    use common_meta::cache::LayeredCacheRegistryBuilder;
1342    use common_meta::kv_backend::memory::MemoryKvBackend;
1343    use common_meta::procedure_executor::{ExecutorContext, ProcedureExecutor};
1344    use common_meta::rpc::ddl::{SubmitDdlTaskRequest, SubmitDdlTaskResponse};
1345    use common_meta::rpc::procedure::{
1346        MigrateRegionRequest, MigrateRegionResponse, ProcedureStateResponse,
1347    };
1348    use common_query::Output;
1349    use common_recordbatch::{
1350        OrderOption, RecordBatch, RecordBatchStream, SendableRecordBatchStream,
1351    };
1352    use datafusion::arrow::datatypes::{DataType, Field, Schema, SchemaRef};
1353    use datafusion_expr::dml::InsertOp;
1354    use datafusion_expr::{LogicalPlanBuilder, LogicalTableSource};
1355    use datatypes::prelude::ConcreteDataType;
1356    use datatypes::schema::{ColumnSchema, Schema as GtSchema, SchemaRef as GtSchemaRef};
1357    use query::query_engine::options::QueryOptions;
1358    use session::context::{Channel, ConnInfo, QueryContext, QueryContextBuilder};
1359    use snafu::{Location, Snafu};
1360    use sql::dialect::GreptimeDbDialect;
1361    use store_api::data_source::DataSource;
1362    use store_api::storage::ScanRequest;
1363    use strfmt::Format;
1364    use table::metadata::{FilterPushDownType, TableInfo, TableInfoBuilder, TableMetaBuilder};
1365    use table::test_util::EmptyTable;
1366    use table::{Table, TableRef};
1367    use tokio::sync::{mpsc, oneshot};
1368
1369    use super::*;
1370    use crate::frontend::FrontendOptions;
1371    use crate::instance::builder::FrontendBuilder;
1372
1373    fn parse_test_sql(sql: &str) -> Vec<Statement> {
1374        parse_stmt(sql, &GreptimeDbDialect {}).unwrap()
1375    }
1376
1377    #[test]
1378    fn test_validate_analyze_stream_statement_strictness() {
1379        for sql in [
1380            "select 1",
1381            "explain analyze select 1",
1382            "explain analyze verbose format text select 1",
1383            "explain analyze verbose format graphviz select 1",
1384        ] {
1385            let mut stmts = parse_test_sql(sql);
1386            assert!(
1387                validate_analyze_stream_statement(&mut stmts[0]).is_err(),
1388                "{sql}"
1389            );
1390        }
1391
1392        for sql in [
1393            "explain analyze verbose select 1",
1394            "explain analyze verbose format json select 1",
1395        ] {
1396            let mut stmts = parse_test_sql(sql);
1397            assert!(
1398                validate_analyze_stream_statement(&mut stmts[0]).is_ok(),
1399                "{sql}"
1400            );
1401            let Statement::Explain(explain) = &stmts[0] else {
1402                unreachable!();
1403            };
1404            assert!(explain.format.is_none());
1405        }
1406
1407        assert_eq!(
1408            parse_test_sql("explain analyze verbose select 1; select 2").len(),
1409            2
1410        );
1411    }
1412
1413    #[derive(Debug, Snafu)]
1414    enum TestError {
1415        #[snafu(display("Failed to build test cache registry"))]
1416        BuildCacheRegistry {
1417            source: cache::error::Error,
1418            #[snafu(implicit)]
1419            location: Location,
1420        },
1421
1422        #[snafu(display("Failed to build test table meta for table: {table_name}"))]
1423        BuildTableMeta {
1424            table_name: String,
1425            source: table::metadata::TableMetaBuilderError,
1426            #[snafu(implicit)]
1427            location: Location,
1428        },
1429
1430        #[snafu(display("Failed to build test table info for table: {table_name}"))]
1431        BuildTableInfo {
1432            table_name: String,
1433            source: table::metadata::TableInfoBuilderError,
1434            #[snafu(implicit)]
1435            location: Location,
1436        },
1437
1438        #[snafu(display("Failed to register test table: {table_name}"))]
1439        RegisterTable {
1440            table_name: String,
1441            source: catalog::error::Error,
1442            #[snafu(implicit)]
1443            location: Location,
1444        },
1445
1446        #[snafu(display("Failed to build test frontend instance"))]
1447        BuildFrontend {
1448            source: crate::error::Error,
1449            #[snafu(implicit)]
1450            location: Location,
1451        },
1452
1453        #[snafu(display("Expected exactly one output for SQL `{sql}`, got {actual}"))]
1454        UnexpectedOutputCount {
1455            sql: String,
1456            actual: usize,
1457            #[snafu(implicit)]
1458            location: Location,
1459        },
1460
1461        #[snafu(display("Failed to execute SQL `{sql}`"))]
1462        ExecuteSql {
1463            sql: String,
1464            source: crate::error::Error,
1465            #[snafu(implicit)]
1466            location: Location,
1467        },
1468
1469        #[snafu(display("Timed out waiting for insert-select start notification"))]
1470        InsertStartTimeout {
1471            source: tokio::time::error::Elapsed,
1472            #[snafu(implicit)]
1473            location: Location,
1474        },
1475
1476        #[snafu(display("Insert-select start notification channel closed"))]
1477        InsertStartChannelClosed {
1478            #[snafu(implicit)]
1479            location: Location,
1480        },
1481
1482        #[snafu(display("Failed to release blocking insert-select interceptor"))]
1483        ReleaseBlockedInsert {
1484            #[snafu(implicit)]
1485            location: Location,
1486        },
1487
1488        #[snafu(display("Timed out waiting for insert-select source to be polled"))]
1489        SourcePollTimeout {
1490            source: tokio::time::error::Elapsed,
1491            #[snafu(implicit)]
1492            location: Location,
1493        },
1494
1495        #[snafu(display("Insert-select source poll notification channel closed"))]
1496        SourcePollChannelClosed {
1497            source: oneshot::error::RecvError,
1498            #[snafu(implicit)]
1499            location: Location,
1500        },
1501
1502        #[snafu(display("Timed out waiting for insert task to finish"))]
1503        InsertTaskTimeout {
1504            source: tokio::time::error::Elapsed,
1505            #[snafu(implicit)]
1506            location: Location,
1507        },
1508
1509        #[snafu(display("Insert task panicked"))]
1510        InsertTaskPanic {
1511            source: tokio::task::JoinError,
1512            #[snafu(implicit)]
1513            location: Location,
1514        },
1515
1516        #[snafu(display("Expected insert-select to be cancelled"))]
1517        InsertSelectNotCancelled {
1518            #[snafu(implicit)]
1519            location: Location,
1520        },
1521    }
1522
1523    type TestResult<T> = std::result::Result<T, TestError>;
1524
1525    fn parse_one_sql(sql: &str) -> Statement {
1526        parse_stmt(sql, &GreptimeDbDialect {}).unwrap().remove(0)
1527    }
1528
1529    fn test_query_ctx(process_id: u32) -> QueryContextRef {
1530        Arc::new(
1531            QueryContextBuilder::default()
1532                .channel(Channel::Mysql)
1533                .conn_info(ConnInfo::new(None, Channel::Mysql))
1534                .process_id(process_id)
1535                .build(),
1536        )
1537    }
1538
1539    struct BlockingInsertSelectInterceptor {
1540        started_tx: mpsc::UnboundedSender<()>,
1541        finish_rx: std::sync::Mutex<Option<oneshot::Receiver<()>>>,
1542    }
1543
1544    impl BlockingInsertSelectInterceptor {
1545        fn new(started_tx: mpsc::UnboundedSender<()>, finish_rx: oneshot::Receiver<()>) -> Self {
1546            Self {
1547                started_tx,
1548                finish_rx: std::sync::Mutex::new(Some(finish_rx)),
1549            }
1550        }
1551    }
1552
1553    impl SqlQueryInterceptor for BlockingInsertSelectInterceptor {
1554        type Error = Error;
1555
1556        fn pre_execute(
1557            &self,
1558            statement: Option<&Statement>,
1559            _plan: Option<&LogicalPlan>,
1560            _query_ctx: QueryContextRef,
1561        ) -> Result<()> {
1562            let Some(Statement::Insert(insert)) = statement else {
1563                return Ok(());
1564            };
1565            if !insert.has_non_values_query_source() {
1566                return Ok(());
1567            }
1568
1569            let finish_rx = self.finish_rx.lock().unwrap().take().unwrap();
1570            let _ = self.started_tx.send(());
1571            tokio::task::block_in_place(|| {
1572                tokio::runtime::Handle::current()
1573                    .block_on(finish_rx)
1574                    .unwrap();
1575            });
1576            Ok(())
1577        }
1578    }
1579
1580    struct PendingRecordBatchStream {
1581        schema: GtSchemaRef,
1582        polled_tx: Option<oneshot::Sender<()>>,
1583        _finish_tx: oneshot::Sender<()>,
1584        finish_rx: Pin<Box<oneshot::Receiver<()>>>,
1585    }
1586
1587    impl RecordBatchStream for PendingRecordBatchStream {
1588        fn schema(&self) -> GtSchemaRef {
1589            self.schema.clone()
1590        }
1591
1592        fn output_ordering(&self) -> Option<&[OrderOption]> {
1593            None
1594        }
1595
1596        fn metrics(&self) -> Option<common_recordbatch::adapter::RecordBatchMetrics> {
1597            None
1598        }
1599    }
1600
1601    impl Stream for PendingRecordBatchStream {
1602        type Item = common_recordbatch::error::Result<RecordBatch>;
1603
1604        fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1605            if let Some(polled_tx) = self.polled_tx.take() {
1606                let _ = polled_tx.send(());
1607            }
1608
1609            match self.finish_rx.as_mut().poll(cx) {
1610                Poll::Ready(_) => Poll::Ready(None),
1611                Poll::Pending => Poll::Pending,
1612            }
1613        }
1614    }
1615
1616    impl Unpin for PendingRecordBatchStream {}
1617
1618    struct PendingDataSource {
1619        schema: GtSchemaRef,
1620        polled_tx: std::sync::Mutex<Option<oneshot::Sender<()>>>,
1621    }
1622
1623    impl DataSource for PendingDataSource {
1624        fn get_stream(
1625            &self,
1626            _request: ScanRequest,
1627        ) -> std::result::Result<SendableRecordBatchStream, BoxedError> {
1628            let (finish_tx, finish_rx) = oneshot::channel();
1629            let mut polled_tx = self.polled_tx.lock().map_err(|_| {
1630                BoxedError::new(PlainError::new(
1631                    "pending data source lock poisoned".to_string(),
1632                    StatusCode::Unexpected,
1633                ))
1634            })?;
1635            Ok(Box::pin(PendingRecordBatchStream {
1636                schema: self.schema.clone(),
1637                polled_tx: polled_tx.take(),
1638                _finish_tx: finish_tx,
1639                finish_rx: Box::pin(finish_rx),
1640            }))
1641        }
1642    }
1643
1644    struct NoopProcedureExecutor;
1645
1646    #[async_trait::async_trait]
1647    impl ProcedureExecutor for NoopProcedureExecutor {
1648        async fn submit_ddl_task(
1649            &self,
1650            _ctx: &ExecutorContext,
1651            _request: SubmitDdlTaskRequest,
1652        ) -> common_meta::error::Result<SubmitDdlTaskResponse> {
1653            common_meta::error::UnsupportedSnafu {
1654                operation: "submit_ddl_task",
1655            }
1656            .fail()
1657        }
1658
1659        async fn migrate_region(
1660            &self,
1661            _ctx: &ExecutorContext,
1662            _request: MigrateRegionRequest,
1663        ) -> common_meta::error::Result<MigrateRegionResponse> {
1664            common_meta::error::UnsupportedSnafu {
1665                operation: "migrate_region",
1666            }
1667            .fail()
1668        }
1669
1670        async fn reconcile(
1671            &self,
1672            _ctx: &ExecutorContext,
1673            _request: ReconcileRequest,
1674        ) -> common_meta::error::Result<ReconcileResponse> {
1675            common_meta::error::UnsupportedSnafu {
1676                operation: "reconcile",
1677            }
1678            .fail()
1679        }
1680
1681        async fn query_procedure_state(
1682            &self,
1683            _ctx: &ExecutorContext,
1684            _pid: &str,
1685        ) -> common_meta::error::Result<ProcedureStateResponse> {
1686            common_meta::error::UnsupportedSnafu {
1687                operation: "query_procedure_state",
1688            }
1689            .fail()
1690        }
1691
1692        async fn list_procedures(
1693            &self,
1694            _ctx: &ExecutorContext,
1695        ) -> common_meta::error::Result<ProcedureDetailResponse> {
1696            common_meta::error::UnsupportedSnafu {
1697                operation: "list_procedures",
1698            }
1699            .fail()
1700        }
1701    }
1702
1703    fn test_cache_registry(
1704        kv_backend: common_meta::kv_backend::KvBackendRef,
1705    ) -> TestResult<common_meta::cache::LayeredCacheRegistryRef> {
1706        Ok(Arc::new(
1707            cache::with_default_composite_cache_registry(
1708                LayeredCacheRegistryBuilder::default()
1709                    .add_cache_registry(cache::build_fundamental_cache_registry(kv_backend)),
1710            )
1711            .context(BuildCacheRegistrySnafu)?
1712            .build(),
1713        ))
1714    }
1715
1716    fn test_table_info(table_id: u32, table_name: &str) -> TestResult<TableInfo> {
1717        let schema = Arc::new(GtSchema::new(vec![
1718            ColumnSchema::new("id", ConcreteDataType::int32_datatype(), false),
1719            ColumnSchema::new(
1720                "ts",
1721                ConcreteDataType::timestamp_millisecond_datatype(),
1722                false,
1723            )
1724            .with_time_index(true),
1725        ]));
1726        let table_meta = TableMetaBuilder::empty()
1727            .schema(schema)
1728            .primary_key_indices(vec![0])
1729            .value_indices(vec![1])
1730            .next_column_id(1024)
1731            .build()
1732            .with_context(|_| BuildTableMetaSnafu {
1733                table_name: table_name.to_string(),
1734            })?;
1735
1736        TableInfoBuilder::new(table_name, table_meta)
1737            .table_id(table_id)
1738            .build()
1739            .with_context(|_| BuildTableInfoSnafu {
1740                table_name: table_name.to_string(),
1741            })
1742    }
1743
1744    fn test_table(table_id: u32, table_name: &str) -> TestResult<table::TableRef> {
1745        let table_info = test_table_info(table_id, table_name)?;
1746        Ok(EmptyTable::from_table_info(&table_info))
1747    }
1748
1749    fn pending_table(
1750        table_id: u32,
1751        table_name: &str,
1752        polled_tx: oneshot::Sender<()>,
1753    ) -> TestResult<table::TableRef> {
1754        let table_info = test_table_info(table_id, table_name)?;
1755        let data_source = Arc::new(PendingDataSource {
1756            schema: table_info.meta.schema.clone(),
1757            polled_tx: std::sync::Mutex::new(Some(polled_tx)),
1758        });
1759
1760        Ok(Arc::new(Table::new(
1761            Arc::new(table_info),
1762            FilterPushDownType::Unsupported,
1763            data_source,
1764        )))
1765    }
1766
1767    async fn test_instance_with_tables(
1768        source_table: TableRef,
1769        target_table: TableRef,
1770    ) -> TestResult<Instance> {
1771        test_instance_with_plugins(source_table, target_table, Plugins::new()).await
1772    }
1773
1774    async fn test_instance_with_insert_select_interceptor(
1775        interceptor: SqlQueryInterceptorRef<Error>,
1776    ) -> TestResult<Instance> {
1777        let plugins = Plugins::new();
1778        plugins.insert::<SqlQueryInterceptorRef<Error>>(interceptor);
1779
1780        test_instance_with_plugins(
1781            test_table(1024, "source")?,
1782            test_table(1025, "target")?,
1783            plugins,
1784        )
1785        .await
1786    }
1787
1788    async fn test_instance_with_plugins(
1789        source_table: TableRef,
1790        target_table: TableRef,
1791        plugins: Plugins,
1792    ) -> TestResult<Instance> {
1793        let kv_backend = Arc::new(MemoryKvBackend::new());
1794        let process_manager = Arc::new(ProcessManager::new("test-frontend".to_string(), None));
1795        let catalog_manager = catalog::memory::MemoryCatalogManager::new_with_table(source_table);
1796        let target_table_name = "target";
1797        catalog_manager
1798            .register_table_sync(catalog::RegisterTableRequest {
1799                catalog: "greptime".to_string(),
1800                schema: "public".to_string(),
1801                table_name: target_table_name.to_string(),
1802                table_id: 1025,
1803                table: target_table,
1804            })
1805            .with_context(|_| RegisterTableSnafu {
1806                table_name: target_table_name.to_string(),
1807            })?;
1808        catalog_manager.register_process_list_table(process_manager.clone());
1809
1810        let cache_registry = test_cache_registry(kv_backend.clone())?;
1811
1812        FrontendBuilder::new(
1813            FrontendOptions::default(),
1814            kv_backend,
1815            cache_registry,
1816            catalog_manager,
1817            Arc::new(client::client_manager::NodeClients::default()),
1818            Arc::new(NoopProcedureExecutor),
1819            process_manager,
1820        )
1821        .with_plugin(plugins)
1822        .try_build()
1823        .await
1824        .context(BuildFrontendSnafu)
1825    }
1826
1827    async fn execute_one_sql(
1828        instance: &Instance,
1829        sql: &str,
1830        query_ctx: QueryContextRef,
1831    ) -> TestResult<Output> {
1832        let mut results = instance.do_query_inner(sql, query_ctx).await;
1833        ensure!(
1834            results.len() == 1,
1835            UnexpectedOutputCountSnafu {
1836                sql: sql.to_string(),
1837                actual: results.len(),
1838            }
1839        );
1840        results.remove(0).with_context(|_| ExecuteSqlSnafu {
1841            sql: sql.to_string(),
1842        })
1843    }
1844
1845    #[test]
1846    fn test_fast_legacy_check_deadlock_prevention() {
1847        // Create a DashMap to simulate the cache
1848        let cache = DashMap::new();
1849
1850        // Pre-populate cache with some entries
1851        cache.insert("metric1".to_string(), true); // legacy mode
1852        cache.insert("metric2".to_string(), false); // prom mode
1853        cache.insert("metric3".to_string(), true); // legacy mode
1854
1855        // Test case 1: Normal operation with cache hits
1856        let metric1 = "metric1".to_string();
1857        let metric4 = "metric4".to_string();
1858        let names1 = vec![&metric1, &metric4];
1859        let result = fast_legacy_check(&cache, &names1);
1860        assert!(result.is_ok());
1861        assert_eq!(result.unwrap(), Some(true)); // should return legacy mode
1862
1863        // Verify that metric4 was added to cache
1864        assert!(cache.contains_key("metric4"));
1865        assert!(*cache.get("metric4").unwrap().value());
1866
1867        // Test case 2: No cache hits
1868        let metric5 = "metric5".to_string();
1869        let metric6 = "metric6".to_string();
1870        let names2 = vec![&metric5, &metric6];
1871        let result = fast_legacy_check(&cache, &names2);
1872        assert!(result.is_ok());
1873        assert_eq!(result.unwrap(), None); // should return None as no cache hits
1874
1875        // Test case 3: Incompatible modes should return error
1876        let cache_incompatible = DashMap::new();
1877        cache_incompatible.insert("metric1".to_string(), true); // legacy
1878        cache_incompatible.insert("metric2".to_string(), false); // prom
1879        let metric1_test = "metric1".to_string();
1880        let metric2_test = "metric2".to_string();
1881        let names3 = vec![&metric1_test, &metric2_test];
1882        let result = fast_legacy_check(&cache_incompatible, &names3);
1883        assert!(result.is_err()); // should error due to incompatible modes
1884
1885        // Test case 4: Intensive concurrent access to test deadlock prevention
1886        // This test specifically targets the scenario where multiple threads
1887        // access the same cache entries simultaneously
1888        let cache_concurrent = Arc::new(DashMap::new());
1889        cache_concurrent.insert("shared_metric".to_string(), true);
1890
1891        let num_threads = 8;
1892        let operations_per_thread = 100;
1893        let barrier = Arc::new(Barrier::new(num_threads));
1894        let success_flag = Arc::new(AtomicBool::new(true));
1895
1896        let handles: Vec<_> = (0..num_threads)
1897            .map(|thread_id| {
1898                let cache_clone = Arc::clone(&cache_concurrent);
1899                let barrier_clone = Arc::clone(&barrier);
1900                let success_flag_clone = Arc::clone(&success_flag);
1901
1902                thread::spawn(move || {
1903                    // Wait for all threads to be ready
1904                    barrier_clone.wait();
1905
1906                    let start_time = Instant::now();
1907                    for i in 0..operations_per_thread {
1908                        // Each operation references existing cache entry and adds new ones
1909                        let shared_metric = "shared_metric".to_string();
1910                        let new_metric = format!("thread_{}_metric_{}", thread_id, i);
1911                        let names = vec![&shared_metric, &new_metric];
1912
1913                        match fast_legacy_check(&cache_clone, &names) {
1914                            Ok(_) => {}
1915                            Err(_) => {
1916                                success_flag_clone.store(false, Ordering::Relaxed);
1917                                return;
1918                            }
1919                        }
1920
1921                        // If the test takes too long, it likely means deadlock
1922                        if start_time.elapsed() > Duration::from_secs(10) {
1923                            success_flag_clone.store(false, Ordering::Relaxed);
1924                            return;
1925                        }
1926                    }
1927                })
1928            })
1929            .collect();
1930
1931        // Join all threads with timeout
1932        let start_time = Instant::now();
1933        for (i, handle) in handles.into_iter().enumerate() {
1934            let join_result = handle.join();
1935
1936            // Check if we're taking too long (potential deadlock)
1937            if start_time.elapsed() > Duration::from_secs(30) {
1938                panic!("Test timed out - possible deadlock detected!");
1939            }
1940
1941            if join_result.is_err() {
1942                panic!("Thread {} panicked during execution", i);
1943            }
1944        }
1945
1946        // Verify all operations completed successfully
1947        assert!(
1948            success_flag.load(Ordering::Relaxed),
1949            "Some operations failed"
1950        );
1951
1952        // Verify that many new entries were added (proving operations completed)
1953        let final_count = cache_concurrent.len();
1954        assert!(
1955            final_count > 1 + num_threads * operations_per_thread / 2,
1956            "Expected more cache entries, got {}",
1957            final_count
1958        );
1959    }
1960
1961    #[test]
1962    fn test_should_track_statement_process() {
1963        assert!(should_track_statement_process(&parse_one_sql(
1964            "SELECT * FROM demo"
1965        )));
1966        assert!(should_track_statement_process(&parse_one_sql(
1967            "INSERT INTO demo SELECT * FROM source"
1968        )));
1969        assert!(!should_track_statement_process(&parse_one_sql(
1970            "INSERT INTO demo VALUES (1)"
1971        )));
1972        assert!(!should_track_statement_process(&parse_one_sql(
1973            "INSERT INTO demo VALUES (now())"
1974        )));
1975    }
1976
1977    #[test]
1978    fn test_should_track_plan_process() {
1979        let select_stmt = parse_one_sql("SELECT * FROM demo");
1980        let insert_select_stmt = parse_one_sql("INSERT INTO demo SELECT * FROM source");
1981        let insert_values_stmt = parse_one_sql("INSERT INTO demo VALUES (now())");
1982
1983        let empty_plan = LogicalPlanBuilder::empty(false).build().unwrap();
1984        assert!(should_track_plan_process(Some(&select_stmt), &empty_plan));
1985        assert!(should_track_plan_process(
1986            Some(&insert_select_stmt),
1987            &insert_dml_plan()
1988        ));
1989        assert!(!should_track_plan_process(
1990            Some(&insert_values_stmt),
1991            &insert_dml_plan()
1992        ));
1993        assert!(!should_track_plan_process(None, &insert_dml_plan()));
1994    }
1995
1996    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1997    async fn test_insert_select_is_visible_in_show_processlist() -> TestResult<()> {
1998        let insert_sql = "INSERT INTO target SELECT * FROM source";
1999        let (started_tx, mut started_rx) = mpsc::unbounded_channel();
2000        let (finish_tx, finish_rx) = oneshot::channel();
2001        let interceptor = Arc::new(BlockingInsertSelectInterceptor::new(started_tx, finish_rx));
2002        let instance = Arc::new(test_instance_with_insert_select_interceptor(interceptor).await?);
2003
2004        let insert_task = tokio::spawn({
2005            let instance = instance.clone();
2006            async move { execute_one_sql(&instance, insert_sql, test_query_ctx(4242)).await }
2007        });
2008
2009        tokio::time::timeout(Duration::from_secs(5), started_rx.recv())
2010            .await
2011            .context(InsertStartTimeoutSnafu)?
2012            .context(InsertStartChannelClosedSnafu)?;
2013
2014        let output = execute_one_sql(&instance, "SHOW PROCESSLIST", test_query_ctx(43)).await?;
2015        let process_list = output.data.pretty_print().await;
2016        assert!(
2017            process_list.contains(insert_sql),
2018            "process list did not contain running insert:\n{process_list}"
2019        );
2020
2021        finish_tx
2022            .send(())
2023            .map_err(|_| ReleaseBlockedInsertSnafu.build())?;
2024        insert_task.await.context(InsertTaskPanicSnafu)??;
2025
2026        Ok(())
2027    }
2028
2029    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2030    async fn test_kill_query_cancels_insert_select() -> TestResult<()> {
2031        assert_kill_cancels_insert_select("KILL QUERY 4242").await
2032    }
2033
2034    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2035    async fn test_kill_process_id_cancels_insert_select() -> TestResult<()> {
2036        assert_kill_cancels_insert_select("KILL 'test-frontend/4242'").await
2037    }
2038
2039    async fn assert_kill_cancels_insert_select(kill_sql: &str) -> TestResult<()> {
2040        let insert_sql = "INSERT INTO target SELECT * FROM source";
2041        let (source_polled_tx, source_polled_rx) = oneshot::channel();
2042        let instance = Arc::new(
2043            test_instance_with_tables(
2044                pending_table(1024, "source", source_polled_tx)?,
2045                test_table(1025, "target")?,
2046            )
2047            .await?,
2048        );
2049
2050        let insert_task = tokio::spawn({
2051            let instance = instance.clone();
2052            async move { execute_one_sql(&instance, insert_sql, test_query_ctx(4242)).await }
2053        });
2054
2055        tokio::time::timeout(Duration::from_secs(5), source_polled_rx)
2056            .await
2057            .context(SourcePollTimeoutSnafu)?
2058            .context(SourcePollChannelClosedSnafu)?;
2059
2060        let output = execute_one_sql(&instance, kill_sql, test_query_ctx(43)).await?;
2061        assert!(matches!(output.data, OutputData::AffectedRows(1)));
2062
2063        let insert_result = tokio::time::timeout(Duration::from_secs(5), insert_task)
2064            .await
2065            .context(InsertTaskTimeoutSnafu)?
2066            .context(InsertTaskPanicSnafu)?;
2067        let err = match insert_result {
2068            Ok(_) => return InsertSelectNotCancelledSnafu.fail(),
2069            Err(TestError::ExecuteSql { source, .. }) => source,
2070            Err(err) => return Err(err),
2071        };
2072        assert_eq!(StatusCode::Cancelled, err.status_code());
2073
2074        let output = execute_one_sql(&instance, "SHOW PROCESSLIST", test_query_ctx(43)).await?;
2075        let process_list = output.data.pretty_print().await;
2076        assert!(
2077            !process_list.contains(insert_sql),
2078            "process list still contains killed insert:\n{process_list}"
2079        );
2080
2081        Ok(())
2082    }
2083
2084    fn insert_dml_plan() -> LogicalPlan {
2085        let schema = SchemaRef::new(Schema::new(vec![Field::new(
2086            "value",
2087            DataType::Int64,
2088            true,
2089        )]));
2090        let target = Arc::new(LogicalTableSource::new(schema));
2091        let input = LogicalPlanBuilder::empty(false).build().unwrap();
2092
2093        LogicalPlanBuilder::insert_into(input, "demo", target, InsertOp::Append)
2094            .unwrap()
2095            .build()
2096            .unwrap()
2097    }
2098
2099    #[test]
2100    fn test_exec_validation() {
2101        let query_ctx = QueryContext::arc();
2102        let plugins: Plugins = Plugins::new();
2103        plugins.insert(QueryOptions {
2104            disallow_cross_catalog_query: true,
2105        });
2106
2107        let sql = r#"
2108        SELECT * FROM demo;
2109        EXPLAIN SELECT * FROM demo;
2110        CREATE DATABASE test_database;
2111        SHOW DATABASES;
2112        "#;
2113        let stmts = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2114        assert_eq!(stmts.len(), 4);
2115        for stmt in stmts {
2116            let re = check_permission(plugins.clone(), &stmt, &query_ctx);
2117            re.unwrap();
2118        }
2119
2120        let sql = r#"
2121        SHOW CREATE TABLE demo;
2122        ALTER TABLE demo ADD COLUMN new_col INT;
2123        "#;
2124        let stmts = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2125        assert_eq!(stmts.len(), 2);
2126        for stmt in stmts {
2127            let re = check_permission(plugins.clone(), &stmt, &query_ctx);
2128            re.unwrap();
2129        }
2130
2131        fn replace_test(template_sql: &str, plugins: Plugins, query_ctx: &QueryContextRef) {
2132            // test right
2133            let right = vec![("", ""), ("", "public."), ("greptime.", "public.")];
2134            for (catalog, schema) in right {
2135                let sql = do_fmt(template_sql, catalog, schema);
2136                do_test(&sql, plugins.clone(), query_ctx, true);
2137            }
2138
2139            let wrong = vec![
2140                ("wrongcatalog.", "public."),
2141                ("wrongcatalog.", "wrongschema."),
2142            ];
2143            for (catalog, schema) in wrong {
2144                let sql = do_fmt(template_sql, catalog, schema);
2145                do_test(&sql, plugins.clone(), query_ctx, false);
2146            }
2147        }
2148
2149        fn do_fmt(template: &str, catalog: &str, schema: &str) -> String {
2150            let vars = HashMap::from([
2151                ("catalog".to_string(), catalog),
2152                ("schema".to_string(), schema),
2153            ]);
2154            template.format(&vars).unwrap()
2155        }
2156
2157        fn do_test(sql: &str, plugins: Plugins, query_ctx: &QueryContextRef, is_ok: bool) {
2158            let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
2159            let re = check_permission(plugins, stmt, query_ctx);
2160            if is_ok {
2161                re.unwrap();
2162            } else {
2163                assert!(re.is_err());
2164            }
2165        }
2166
2167        // test insert
2168        let sql = "INSERT INTO {catalog}{schema}monitor(host) VALUES ('host1');";
2169        replace_test(sql, plugins.clone(), &query_ctx);
2170
2171        // test create table
2172        let sql = r#"CREATE TABLE {catalog}{schema}demo(
2173                            host STRING,
2174                            ts TIMESTAMP,
2175                            TIME INDEX (ts),
2176                            PRIMARY KEY(host)
2177                        ) engine=mito;"#;
2178        replace_test(sql, plugins.clone(), &query_ctx);
2179
2180        // test drop table
2181        let sql = "DROP TABLE {catalog}{schema}demo;";
2182        replace_test(sql, plugins.clone(), &query_ctx);
2183
2184        // test show tables
2185        let sql = "SHOW TABLES FROM public";
2186        let stmt = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2187        check_permission(plugins.clone(), &stmt[0], &query_ctx).unwrap();
2188
2189        let sql = "SHOW TABLES FROM private";
2190        let stmt = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2191        let re = check_permission(plugins.clone(), &stmt[0], &query_ctx);
2192        assert!(re.is_ok());
2193
2194        // test describe table
2195        let sql = "DESC TABLE {catalog}{schema}demo;";
2196        replace_test(sql, plugins.clone(), &query_ctx);
2197
2198        let comment_flow_cases = [
2199            ("COMMENT ON FLOW my_flow IS 'comment';", true),
2200            ("COMMENT ON FLOW greptime.my_flow IS 'comment';", true),
2201            ("COMMENT ON FLOW wrongcatalog.my_flow IS 'comment';", false),
2202        ];
2203        for (sql, is_ok) in comment_flow_cases {
2204            let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
2205            let result = check_permission(plugins.clone(), stmt, &query_ctx);
2206            assert_eq!(result.is_ok(), is_ok);
2207        }
2208
2209        let show_flow_cases = [
2210            ("SHOW CREATE FLOW my_flow;", true),
2211            ("SHOW CREATE FLOW greptime.my_flow;", true),
2212            ("SHOW CREATE FLOW wrongcatalog.my_flow;", false),
2213        ];
2214        for (sql, is_ok) in show_flow_cases {
2215            let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
2216            let result = check_permission(plugins.clone(), stmt, &query_ctx);
2217            assert_eq!(result.is_ok(), is_ok);
2218        }
2219    }
2220}