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(query_ctx.current_user(), PermissionReq::SqlStatement(&stmt))
615            .context(PermissionSnafu)?;
616        check_permission(self.plugins.clone(), &stmt, &query_ctx)?;
617        let catalog_name = query_ctx.current_catalog().to_string();
618        let schema_name = query_ctx.current_schema();
619        let slow_query_timer = self.statement_slow_query_timer(&stmt, schema_name.clone());
620        let ticket = self.process_manager.register_query(
621            catalog_name,
622            vec![schema_name],
623            stmt.to_string(),
624            query_ctx.conn_info().to_string(),
625            Some(query_ctx.process_id()),
626            slow_query_timer,
627        );
628        let query_fut =
629            self.exec_statement_with_timeout(stmt, query_ctx.clone(), query_interceptor);
630        let output = CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
631            .await
632            .map_err(|_| error::CancelledSnafu.build())??;
633        let Output { meta, data } = output;
634        let data = match data {
635            OutputData::Stream(stream) => OutputData::Stream(Box::pin(
636                CancellableStreamWrapper::new_cancel_on_drop(stream, ticket),
637            )),
638            other => other,
639        };
640        query_interceptor.post_execute(Output { data, meta }, query_ctx)
641    }
642
643    #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_query")]
644    async fn do_query_inner(&self, query: &str, query_ctx: QueryContextRef) -> Vec<Result<Output>> {
645        if self.is_suspended() {
646            return vec![error::SuspendedSnafu {}.fail()];
647        }
648
649        let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
650        let query_interceptor = query_interceptor_opt.as_ref();
651        let query = match query_interceptor.pre_parsing(query, query_ctx.clone()) {
652            Ok(q) => q,
653            Err(e) => return vec![Err(e)],
654        };
655
656        let checker_ref = self.plugins.get::<PermissionCheckerRef>();
657        let checker = checker_ref.as_ref();
658
659        match parse_stmt(query.as_ref(), query_ctx.sql_dialect())
660            .and_then(|stmts| query_interceptor.post_parsing(stmts, query_ctx.clone()))
661        {
662            Ok(stmts) => {
663                if stmts.is_empty() {
664                    return vec![
665                        InvalidSqlSnafu {
666                            err_msg: "empty statements",
667                        }
668                        .fail(),
669                    ];
670                }
671
672                let mut results = Vec::with_capacity(stmts.len());
673                for stmt in stmts {
674                    if let Err(e) = checker
675                        .check_permission(
676                            query_ctx.current_user(),
677                            PermissionReq::SqlStatement(&stmt),
678                        )
679                        .context(PermissionSnafu)
680                    {
681                        results.push(Err(e));
682                        break;
683                    }
684
685                    match self.query_statement(stmt.clone(), query_ctx.clone()).await {
686                        Ok(output) => {
687                            let output_result =
688                                query_interceptor.post_execute(output, query_ctx.clone());
689                            results.push(output_result);
690                        }
691                        Err(e) => {
692                            if e.status_code().should_log_error() {
693                                error!(e; "Failed to execute query: {stmt}");
694                            } else {
695                                debug!("Failed to execute query: {stmt}, {e}");
696                            }
697                            results.push(Err(e));
698                            break;
699                        }
700                    }
701                }
702                results
703            }
704            Err(e) => {
705                vec![Err(e)]
706            }
707        }
708    }
709
710    async fn exec_plan(&self, plan: LogicalPlan, query_ctx: QueryContextRef) -> Result<Output> {
711        self.query_engine
712            .execute(plan, query_ctx)
713            .await
714            .context(ExecLogicalPlanSnafu)
715    }
716
717    async fn exec_plan_with_timeout(
718        &self,
719        plan: LogicalPlan,
720        query_ctx: QueryContextRef,
721    ) -> Result<Output> {
722        let timeout = derive_timeout_for_plan(&plan, &query_ctx);
723        match timeout {
724            Some(timeout) => {
725                let start = tokio::time::Instant::now();
726                let output = tokio::time::timeout(timeout, self.exec_plan(plan, query_ctx))
727                    .await
728                    .map_err(|_| StatementTimeoutSnafu.build())??;
729                let remaining_timeout = timeout.checked_sub(start.elapsed()).unwrap_or_default();
730                attach_timeout(output, remaining_timeout)
731            }
732            None => self.exec_plan(plan, query_ctx).await,
733        }
734    }
735
736    async fn do_exec_plan_inner(
737        &self,
738        plan: LogicalPlan,
739        stmt: Option<Statement>,
740        query_ctx: QueryContextRef,
741    ) -> Result<Output> {
742        ensure!(!self.is_suspended(), error::SuspendedSnafu);
743
744        let query_interceptor_opt = self.plugins.get::<SqlQueryInterceptorRef<Error>>();
745        let query_interceptor = query_interceptor_opt.as_ref();
746
747        query_interceptor.pre_execute(stmt.as_ref(), Some(&plan), query_ctx.clone())?;
748
749        let query = stmt
750            .as_ref()
751            .map(|s| s.to_string())
752            .unwrap_or_else(|| plan.display_indent().to_string());
753
754        let plan_is_readonly = is_readonly_plan(&plan);
755        let result = if should_track_plan_process(stmt.as_ref(), &plan) {
756            let catalog_name = query_ctx.current_catalog().to_string();
757            let schema_name = query_ctx.current_schema();
758            let slow_query_timer = if plan_is_readonly {
759                self.slow_query_options
760                    .enable
761                    .then(|| self.event_recorder.clone())
762                    .flatten()
763                    .map(|event_recorder| {
764                        SlowQueryTimer::new(
765                            CatalogQueryStatement::Plan(query.clone()),
766                            schema_name.clone(),
767                            self.slow_query_options.threshold,
768                            self.slow_query_options.sample_ratio,
769                            self.slow_query_options.record_type,
770                            event_recorder,
771                        )
772                    })
773            } else {
774                None
775            };
776
777            let ticket = self.process_manager.register_query(
778                catalog_name,
779                vec![schema_name],
780                query,
781                query_ctx.conn_info().to_string(),
782                Some(query_ctx.process_id()),
783                slow_query_timer,
784            );
785
786            let query_fut = self.exec_plan_with_timeout(plan, query_ctx.clone());
787
788            CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
789                .await
790                .map_err(|_| error::CancelledSnafu.build())?
791                .map(|output| {
792                    let Output { meta, data } = output;
793
794                    let data = match data {
795                        OutputData::Stream(stream) => OutputData::Stream(Box::pin(
796                            CancellableStreamWrapper::new(stream, ticket),
797                        )),
798                        other => other,
799                    };
800                    Output { data, meta }
801                })
802        } else {
803            self.exec_plan_with_timeout(plan, query_ctx.clone()).await
804        };
805
806        result.and_then(|output| query_interceptor.post_execute(output, query_ctx))
807    }
808
809    #[tracing::instrument(skip_all, name = "SqlQueryHandler::do_promql_query")]
810    async fn do_promql_query_inner(
811        &self,
812        query: &PromQuery,
813        query_ctx: QueryContextRef,
814    ) -> Vec<Result<Output>> {
815        if self.is_suspended() {
816            return vec![error::SuspendedSnafu {}.fail()];
817        }
818
819        // check will be done in prometheus handler's do_query
820        let result = PrometheusHandler::do_query(self, query, query_ctx)
821            .await
822            .with_context(|_| ExecutePromqlSnafu {
823                query: format!("{query:?}"),
824            });
825        vec![result]
826    }
827
828    async fn do_describe_inner(
829        &self,
830        stmt: Statement,
831        query_ctx: QueryContextRef,
832    ) -> Result<Option<DescribeResult>> {
833        ensure!(!self.is_suspended(), error::SuspendedSnafu);
834
835        // EXPLAIN / EXPLAIN ANALYZE wrap an inner statement; describe them when the
836        // wrapped statement is something we already plan (so that bind parameters
837        // in the inner query get their types inferred). See #8029.
838        let is_inner_plannable = |s: &Statement| {
839            matches!(
840                s,
841                Statement::Insert(_) | Statement::Query(_) | Statement::Delete(_)
842            )
843        };
844        let plannable = is_inner_plannable(&stmt)
845            || matches!(&stmt, Statement::Explain(explain) if is_inner_plannable(explain.statement.as_ref()));
846
847        if plannable {
848            self.plugins
849                .get::<PermissionCheckerRef>()
850                .as_ref()
851                .check_permission(query_ctx.current_user(), PermissionReq::SqlStatement(&stmt))
852                .context(PermissionSnafu)?;
853
854            let plan = self
855                .query_engine
856                .planner()
857                .plan(&QueryStatement::Sql(stmt), query_ctx.clone())
858                .await
859                .context(PlanStatementSnafu)?;
860            self.query_engine
861                .describe(plan, query_ctx)
862                .await
863                .map(Some)
864                .context(error::DescribeStatementSnafu)
865        } else {
866            Ok(None)
867        }
868    }
869
870    async fn is_valid_schema_inner(&self, catalog: &str, schema: &str) -> Result<bool> {
871        self.catalog_manager
872            .schema_exists(catalog, schema, None)
873            .await
874            .context(error::CatalogSnafu)
875    }
876}
877
878#[async_trait]
879impl SqlQueryHandler for Instance {
880    async fn do_query(
881        &self,
882        query: &str,
883        query_ctx: QueryContextRef,
884    ) -> Vec<server_error::Result<Output>> {
885        self.do_query_inner(query, query_ctx)
886            .await
887            .into_iter()
888            .map(|result| result.map_err(BoxedError::new).context(ExecuteQuerySnafu))
889            .collect()
890    }
891
892    async fn do_analyze_stream_query(
893        &self,
894        query: &str,
895        query_ctx: QueryContextRef,
896    ) -> server_error::Result<Output> {
897        self.do_analyze_stream_query_inner(query, query_ctx)
898            .await
899            .map_err(BoxedError::new)
900            .context(ExecuteQuerySnafu)
901    }
902
903    async fn do_exec_plan(
904        &self,
905        plan: LogicalPlan,
906        stmt: Option<Statement>,
907        query_ctx: QueryContextRef,
908    ) -> server_error::Result<Output> {
909        self.do_exec_plan_inner(plan, stmt, query_ctx)
910            .await
911            .map_err(BoxedError::new)
912            .context(server_error::ExecutePlanSnafu)
913    }
914
915    async fn do_promql_query(
916        &self,
917        query: &PromQuery,
918        query_ctx: QueryContextRef,
919    ) -> Vec<server_error::Result<Output>> {
920        self.do_promql_query_inner(query, query_ctx)
921            .await
922            .into_iter()
923            .map(|result| result.map_err(BoxedError::new).context(ExecuteQuerySnafu))
924            .collect()
925    }
926
927    async fn do_describe(
928        &self,
929        stmt: Statement,
930        query_ctx: QueryContextRef,
931    ) -> server_error::Result<Option<DescribeResult>> {
932        self.do_describe_inner(stmt, query_ctx)
933            .await
934            .map_err(BoxedError::new)
935            .context(server_error::DescribeStatementSnafu)
936    }
937
938    async fn is_valid_schema(&self, catalog: &str, schema: &str) -> server_error::Result<bool> {
939        self.is_valid_schema_inner(catalog, schema)
940            .await
941            .map_err(BoxedError::new)
942            .context(server_error::CheckDatabaseValiditySnafu)
943    }
944}
945
946/// Attaches a timer to the output and observes it once the output is exhausted.
947pub fn attach_timer(output: Output, timer: HistogramTimer) -> Output {
948    match output.data {
949        OutputData::AffectedRows(_) | OutputData::RecordBatches(_) => output,
950        OutputData::Stream(stream) => {
951            let stream = OnDone::new(stream, move || {
952                timer.observe_duration();
953            });
954            Output::new(OutputData::Stream(Box::pin(stream)), output.meta)
955        }
956    }
957}
958
959#[async_trait]
960impl PrometheusHandler for Instance {
961    #[tracing::instrument(skip_all)]
962    async fn do_query(
963        &self,
964        query: &PromQuery,
965        query_ctx: QueryContextRef,
966    ) -> server_error::Result<Output> {
967        let interceptor = self
968            .plugins
969            .get::<PromQueryInterceptorRef<server_error::Error>>();
970
971        self.plugins
972            .get::<PermissionCheckerRef>()
973            .as_ref()
974            .check_permission(query_ctx.current_user(), PermissionReq::PromQuery)
975            .context(AuthSnafu)?;
976
977        let stmt = QueryLanguageParser::parse_promql(query, &query_ctx).with_context(|_| {
978            ParsePromQLSnafu {
979                query: query.clone(),
980            }
981        })?;
982
983        let plan = self
984            .statement_executor
985            .plan(&stmt, query_ctx.clone())
986            .await
987            .map_err(BoxedError::new)
988            .context(ExecuteQuerySnafu)?;
989
990        let QueryStatement::Promql(eval_stmt, _) = &stmt else {
991            unreachable!("query is parsed from promql");
992        };
993
994        interceptor.pre_execute(query, &eval_stmt.expr, Some(&plan), query_ctx.clone())?;
995
996        // Take the EvalStmt from the original QueryStatement and use it to create the CatalogQueryStatement.
997        let query_statement = if let QueryStatement::Promql(eval_stmt, alias) = stmt {
998            CatalogQueryStatement::Promql(eval_stmt, alias)
999        } else {
1000            // It should not happen since the query is already parsed successfully.
1001            return UnexpectedResultSnafu {
1002                reason: "The query should always be promql.".to_string(),
1003            }
1004            .fail();
1005        };
1006        let raw_query = query_statement.to_string();
1007
1008        let slow_query_timer = self
1009            .slow_query_options
1010            .enable
1011            .then(|| self.event_recorder.clone())
1012            .flatten()
1013            .map(|event_recorder| {
1014                SlowQueryTimer::new(
1015                    query_statement,
1016                    query_ctx.current_schema(),
1017                    self.slow_query_options.threshold,
1018                    self.slow_query_options.sample_ratio,
1019                    self.slow_query_options.record_type,
1020                    event_recorder,
1021                )
1022            });
1023
1024        let ticket = self.process_manager.register_query(
1025            query_ctx.current_catalog().to_string(),
1026            vec![query_ctx.current_schema()],
1027            raw_query,
1028            query_ctx.conn_info().to_string(),
1029            Some(query_ctx.process_id()),
1030            slow_query_timer,
1031        );
1032
1033        let query_fut = self.statement_executor.exec_plan(plan, query_ctx.clone());
1034
1035        let output = CancellableFuture::new(query_fut, ticket.cancellation_handle.clone())
1036            .await
1037            .map_err(|_| servers::error::CancelledSnafu.build())?
1038            .map(|output| {
1039                let Output { meta, data } = output;
1040                let data = match data {
1041                    OutputData::Stream(stream) => {
1042                        OutputData::Stream(Box::pin(CancellableStreamWrapper::new(stream, ticket)))
1043                    }
1044                    other => other,
1045                };
1046                Output { data, meta }
1047            })
1048            .map_err(BoxedError::new)
1049            .context(ExecuteQuerySnafu)?;
1050
1051        Ok(interceptor.post_execute(output, query_ctx)?)
1052    }
1053
1054    async fn query_metric_names(
1055        &self,
1056        matchers: Vec<Matcher>,
1057        ctx: &QueryContextRef,
1058    ) -> server_error::Result<Vec<String>> {
1059        self.handle_query_metric_names(matchers, ctx)
1060            .await
1061            .map_err(BoxedError::new)
1062            .context(ExecuteQuerySnafu)
1063    }
1064
1065    async fn query_label_values(
1066        &self,
1067        metric: String,
1068        label_name: String,
1069        matchers: Vec<Matcher>,
1070        start: SystemTime,
1071        end: SystemTime,
1072        ctx: &QueryContextRef,
1073    ) -> server_error::Result<Vec<String>> {
1074        self.handle_query_label_values(metric, label_name, matchers, start, end, ctx)
1075            .await
1076            .map_err(BoxedError::new)
1077            .context(ExecuteQuerySnafu)
1078    }
1079
1080    fn catalog_manager(&self) -> CatalogManagerRef {
1081        self.catalog_manager.clone()
1082    }
1083}
1084
1085/// Validate `stmt.database` permission if it's presented.
1086macro_rules! validate_db_permission {
1087    ($stmt: expr, $query_ctx: expr) => {
1088        if let Some(database) = &$stmt.database {
1089            validate_catalog_and_schema($query_ctx.current_catalog(), database, $query_ctx)
1090                .map_err(BoxedError::new)
1091                .context(SqlExecInterceptedSnafu)?;
1092        }
1093    };
1094}
1095
1096pub fn check_permission(
1097    plugins: Plugins,
1098    stmt: &Statement,
1099    query_ctx: &QueryContextRef,
1100) -> Result<()> {
1101    let need_validate = plugins
1102        .get::<QueryOptions>()
1103        .map(|opts| opts.disallow_cross_catalog_query)
1104        .unwrap_or_default();
1105
1106    if !need_validate {
1107        return Ok(());
1108    }
1109
1110    match stmt {
1111        // Will be checked in execution.
1112        // TODO(dennis): add a hook for admin commands.
1113        Statement::Admin(_) => {}
1114        // These are executed by query engine, and will be checked there.
1115        Statement::Query(_)
1116        | Statement::Explain(_)
1117        | Statement::Tql(_)
1118        | Statement::Delete(_)
1119        | Statement::DeclareCursor(_)
1120        | Statement::Copy(sql::statements::copy::Copy::CopyQueryTo(_)) => {}
1121        // database ops won't be checked
1122        Statement::CreateDatabase(_)
1123        | Statement::ShowDatabases(_)
1124        | Statement::DropDatabase(_)
1125        | Statement::AlterDatabase(_)
1126        | Statement::DropFlow(_)
1127        | Statement::Use(_) => {}
1128        #[cfg(feature = "enterprise")]
1129        Statement::DropTrigger(_) => {}
1130        Statement::ShowCreateDatabase(stmt) => {
1131            validate_database(&stmt.database_name, query_ctx)?;
1132        }
1133        Statement::ShowCreateTable(stmt) => {
1134            validate_param(&stmt.table_name, query_ctx)?;
1135        }
1136        Statement::ShowCreateFlow(stmt) => {
1137            validate_flow(&stmt.flow_name, query_ctx)?;
1138        }
1139        #[cfg(feature = "enterprise")]
1140        Statement::ShowCreateTrigger(stmt) => {
1141            validate_param(&stmt.trigger_name, query_ctx)?;
1142        }
1143        Statement::ShowCreateView(stmt) => {
1144            validate_param(&stmt.view_name, query_ctx)?;
1145        }
1146        Statement::CreateExternalTable(stmt) => {
1147            validate_param(&stmt.name, query_ctx)?;
1148        }
1149        Statement::CreateFlow(stmt) => {
1150            // TODO: should also validate source table name here?
1151            validate_param(&stmt.sink_table_name, query_ctx)?;
1152        }
1153        #[cfg(feature = "enterprise")]
1154        Statement::CreateTrigger(stmt) => {
1155            validate_param(&stmt.trigger_name, query_ctx)?;
1156        }
1157        Statement::CreateView(stmt) => {
1158            validate_param(&stmt.name, query_ctx)?;
1159        }
1160        Statement::AlterTable(stmt) => {
1161            validate_param(stmt.table_name(), query_ctx)?;
1162        }
1163        #[cfg(feature = "enterprise")]
1164        Statement::AlterTrigger(_) => {}
1165        // set/show variable now only alter/show variable in session
1166        Statement::SetVariables(_) | Statement::ShowVariables(_) => {}
1167        // show charset and show collation won't be checked
1168        Statement::ShowCharset(_) | Statement::ShowCollation(_) => {}
1169
1170        Statement::Comment(comment) => match &comment.object {
1171            CommentObject::Table(table) => validate_param(table, query_ctx)?,
1172            CommentObject::Column { table, .. } => validate_param(table, query_ctx)?,
1173            CommentObject::Flow(flow) => validate_flow(flow, query_ctx)?,
1174        },
1175
1176        Statement::Insert(insert) => {
1177            let name = insert.table_name().context(ParseSqlSnafu)?;
1178            validate_param(name, query_ctx)?;
1179        }
1180        Statement::CreateTable(stmt) => {
1181            validate_param(&stmt.name, query_ctx)?;
1182        }
1183        Statement::CreateTableLike(stmt) => {
1184            validate_param(&stmt.table_name, query_ctx)?;
1185            validate_param(&stmt.source_name, query_ctx)?;
1186        }
1187        Statement::DropTable(drop_stmt) => {
1188            for table_name in drop_stmt.table_names() {
1189                validate_param(table_name, query_ctx)?;
1190            }
1191        }
1192        Statement::DropView(stmt) => {
1193            validate_param(&stmt.view_name, query_ctx)?;
1194        }
1195        Statement::ShowTables(stmt) => {
1196            validate_db_permission!(stmt, query_ctx);
1197        }
1198        Statement::ShowTableStatus(stmt) => {
1199            validate_db_permission!(stmt, query_ctx);
1200        }
1201        Statement::ShowColumns(stmt) => {
1202            validate_db_permission!(stmt, query_ctx);
1203        }
1204        Statement::ShowIndex(stmt) => {
1205            validate_db_permission!(stmt, query_ctx);
1206        }
1207        Statement::ShowRegion(stmt) => {
1208            validate_db_permission!(stmt, query_ctx);
1209        }
1210        Statement::ShowViews(stmt) => {
1211            validate_db_permission!(stmt, query_ctx);
1212        }
1213        Statement::ShowFlows(stmt) => {
1214            validate_db_permission!(stmt, query_ctx);
1215        }
1216        #[cfg(feature = "enterprise")]
1217        Statement::ShowTriggers(_stmt) => {
1218            // The trigger is organized based on the catalog dimension, so there
1219            // is no need to check the permission of the database(schema).
1220        }
1221        Statement::ShowStatus(_stmt) => {}
1222        Statement::ShowSearchPath(_stmt) => {}
1223        Statement::DescribeTable(stmt) => {
1224            validate_param(stmt.name(), query_ctx)?;
1225        }
1226        Statement::Copy(sql::statements::copy::Copy::CopyTable(stmt)) => match stmt {
1227            CopyTable::To(copy_table_to) => validate_param(&copy_table_to.table_name, query_ctx)?,
1228            CopyTable::From(copy_table_from) => {
1229                validate_param(&copy_table_from.table_name, query_ctx)?
1230            }
1231        },
1232        Statement::Copy(sql::statements::copy::Copy::CopyDatabase(copy_database)) => {
1233            match copy_database {
1234                CopyDatabase::To(stmt) => validate_database(&stmt.database_name, query_ctx)?,
1235                CopyDatabase::From(stmt) => validate_database(&stmt.database_name, query_ctx)?,
1236            }
1237        }
1238        Statement::TruncateTable(stmt) => {
1239            validate_param(stmt.table_name(), query_ctx)?;
1240        }
1241        // cursor operations are always allowed once it's created
1242        Statement::FetchCursor(_) | Statement::CloseCursor(_) => {}
1243        // User can only kill process in their own catalog.
1244        Statement::Kill(_) => {}
1245        // SHOW PROCESSLIST
1246        Statement::ShowProcesslist(_) => {}
1247    }
1248    Ok(())
1249}
1250
1251fn validate_param(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1252    let (catalog, schema, _) = table_idents_to_full_name(name, query_ctx)
1253        .map_err(BoxedError::new)
1254        .context(ExternalSnafu)?;
1255
1256    validate_catalog_and_schema(&catalog, &schema, query_ctx)
1257        .map_err(BoxedError::new)
1258        .context(SqlExecInterceptedSnafu)
1259}
1260
1261fn validate_flow(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1262    let catalog = match &name.0[..] {
1263        [_flow] => query_ctx.current_catalog().to_string(),
1264        [catalog, _flow] => catalog.to_string_unquoted(),
1265        _ => {
1266            return InvalidSqlSnafu {
1267                err_msg: format!(
1268                    "expect flow name to be <catalog>.<flow_name> or <flow_name>, actual: {name}",
1269                ),
1270            }
1271            .fail();
1272        }
1273    };
1274
1275    let schema = query_ctx.current_schema();
1276
1277    validate_catalog_and_schema(&catalog, &schema, query_ctx)
1278        .map_err(BoxedError::new)
1279        .context(SqlExecInterceptedSnafu)
1280}
1281
1282fn validate_database(name: &ObjectName, query_ctx: &QueryContextRef) -> Result<()> {
1283    let (catalog, schema) = match &name.0[..] {
1284        [schema] => (
1285            query_ctx.current_catalog().to_string(),
1286            schema.to_string_unquoted(),
1287        ),
1288        [catalog, schema] => (catalog.to_string_unquoted(), schema.to_string_unquoted()),
1289        _ => InvalidSqlSnafu {
1290            err_msg: format!(
1291                "expect database name to be <catalog>.<schema> or <schema>, actual: {name}",
1292            ),
1293        }
1294        .fail()?,
1295    };
1296
1297    validate_catalog_and_schema(&catalog, &schema, query_ctx)
1298        .map_err(BoxedError::new)
1299        .context(SqlExecInterceptedSnafu)
1300}
1301
1302fn is_readonly_plan(plan: &LogicalPlan) -> bool {
1303    !matches!(plan, LogicalPlan::Dml(_) | LogicalPlan::Ddl(_))
1304}
1305
1306fn should_track_statement_process(stmt: &Statement) -> bool {
1307    stmt.is_readonly()
1308        || matches!(stmt, Statement::Insert(insert) if insert.has_non_values_query_source())
1309}
1310
1311fn should_track_plan_process(stmt: Option<&Statement>, plan: &LogicalPlan) -> bool {
1312    is_readonly_plan(plan)
1313        || matches!(stmt, Some(Statement::Insert(insert)) if insert.has_non_values_query_source())
1314}
1315
1316#[cfg(test)]
1317mod tests {
1318    use std::collections::HashMap;
1319    use std::future::Future;
1320    use std::pin::Pin;
1321    use std::sync::atomic::{AtomicBool, Ordering};
1322    use std::sync::{Arc, Barrier};
1323    use std::task::{Context, Poll};
1324    use std::thread;
1325    use std::time::{Duration, Instant};
1326
1327    use api::v1::meta::{ProcedureDetailResponse, ReconcileRequest, ReconcileResponse};
1328    use catalog::process_manager::ProcessManager;
1329    use common_base::Plugins;
1330    use common_error::ext::{BoxedError, PlainError};
1331    use common_error::status_code::StatusCode;
1332    use common_meta::cache::LayeredCacheRegistryBuilder;
1333    use common_meta::kv_backend::memory::MemoryKvBackend;
1334    use common_meta::procedure_executor::{ExecutorContext, ProcedureExecutor};
1335    use common_meta::rpc::ddl::{SubmitDdlTaskRequest, SubmitDdlTaskResponse};
1336    use common_meta::rpc::procedure::{
1337        MigrateRegionRequest, MigrateRegionResponse, ProcedureStateResponse,
1338    };
1339    use common_query::Output;
1340    use common_recordbatch::{
1341        OrderOption, RecordBatch, RecordBatchStream, SendableRecordBatchStream,
1342    };
1343    use datafusion::arrow::datatypes::{DataType, Field, Schema, SchemaRef};
1344    use datafusion_expr::dml::InsertOp;
1345    use datafusion_expr::{LogicalPlanBuilder, LogicalTableSource};
1346    use datatypes::prelude::ConcreteDataType;
1347    use datatypes::schema::{ColumnSchema, Schema as GtSchema, SchemaRef as GtSchemaRef};
1348    use query::query_engine::options::QueryOptions;
1349    use session::context::{Channel, ConnInfo, QueryContext, QueryContextBuilder};
1350    use snafu::{Location, Snafu};
1351    use sql::dialect::GreptimeDbDialect;
1352    use store_api::data_source::DataSource;
1353    use store_api::storage::ScanRequest;
1354    use strfmt::Format;
1355    use table::metadata::{FilterPushDownType, TableInfo, TableInfoBuilder, TableMetaBuilder};
1356    use table::test_util::EmptyTable;
1357    use table::{Table, TableRef};
1358    use tokio::sync::{mpsc, oneshot};
1359
1360    use super::*;
1361    use crate::frontend::FrontendOptions;
1362    use crate::instance::builder::FrontendBuilder;
1363
1364    fn parse_test_sql(sql: &str) -> Vec<Statement> {
1365        parse_stmt(sql, &GreptimeDbDialect {}).unwrap()
1366    }
1367
1368    #[test]
1369    fn test_validate_analyze_stream_statement_strictness() {
1370        for sql in [
1371            "select 1",
1372            "explain analyze select 1",
1373            "explain analyze verbose format text select 1",
1374            "explain analyze verbose format graphviz select 1",
1375        ] {
1376            let mut stmts = parse_test_sql(sql);
1377            assert!(
1378                validate_analyze_stream_statement(&mut stmts[0]).is_err(),
1379                "{sql}"
1380            );
1381        }
1382
1383        for sql in [
1384            "explain analyze verbose select 1",
1385            "explain analyze verbose format json select 1",
1386        ] {
1387            let mut stmts = parse_test_sql(sql);
1388            assert!(
1389                validate_analyze_stream_statement(&mut stmts[0]).is_ok(),
1390                "{sql}"
1391            );
1392            let Statement::Explain(explain) = &stmts[0] else {
1393                unreachable!();
1394            };
1395            assert!(explain.format.is_none());
1396        }
1397
1398        assert_eq!(
1399            parse_test_sql("explain analyze verbose select 1; select 2").len(),
1400            2
1401        );
1402    }
1403
1404    #[derive(Debug, Snafu)]
1405    enum TestError {
1406        #[snafu(display("Failed to build test cache registry"))]
1407        BuildCacheRegistry {
1408            source: cache::error::Error,
1409            #[snafu(implicit)]
1410            location: Location,
1411        },
1412
1413        #[snafu(display("Failed to build test table meta for table: {table_name}"))]
1414        BuildTableMeta {
1415            table_name: String,
1416            source: table::metadata::TableMetaBuilderError,
1417            #[snafu(implicit)]
1418            location: Location,
1419        },
1420
1421        #[snafu(display("Failed to build test table info for table: {table_name}"))]
1422        BuildTableInfo {
1423            table_name: String,
1424            source: table::metadata::TableInfoBuilderError,
1425            #[snafu(implicit)]
1426            location: Location,
1427        },
1428
1429        #[snafu(display("Failed to register test table: {table_name}"))]
1430        RegisterTable {
1431            table_name: String,
1432            source: catalog::error::Error,
1433            #[snafu(implicit)]
1434            location: Location,
1435        },
1436
1437        #[snafu(display("Failed to build test frontend instance"))]
1438        BuildFrontend {
1439            source: crate::error::Error,
1440            #[snafu(implicit)]
1441            location: Location,
1442        },
1443
1444        #[snafu(display("Expected exactly one output for SQL `{sql}`, got {actual}"))]
1445        UnexpectedOutputCount {
1446            sql: String,
1447            actual: usize,
1448            #[snafu(implicit)]
1449            location: Location,
1450        },
1451
1452        #[snafu(display("Failed to execute SQL `{sql}`"))]
1453        ExecuteSql {
1454            sql: String,
1455            source: crate::error::Error,
1456            #[snafu(implicit)]
1457            location: Location,
1458        },
1459
1460        #[snafu(display("Timed out waiting for insert-select start notification"))]
1461        InsertStartTimeout {
1462            source: tokio::time::error::Elapsed,
1463            #[snafu(implicit)]
1464            location: Location,
1465        },
1466
1467        #[snafu(display("Insert-select start notification channel closed"))]
1468        InsertStartChannelClosed {
1469            #[snafu(implicit)]
1470            location: Location,
1471        },
1472
1473        #[snafu(display("Failed to release blocking insert-select interceptor"))]
1474        ReleaseBlockedInsert {
1475            #[snafu(implicit)]
1476            location: Location,
1477        },
1478
1479        #[snafu(display("Timed out waiting for insert-select source to be polled"))]
1480        SourcePollTimeout {
1481            source: tokio::time::error::Elapsed,
1482            #[snafu(implicit)]
1483            location: Location,
1484        },
1485
1486        #[snafu(display("Insert-select source poll notification channel closed"))]
1487        SourcePollChannelClosed {
1488            source: oneshot::error::RecvError,
1489            #[snafu(implicit)]
1490            location: Location,
1491        },
1492
1493        #[snafu(display("Timed out waiting for insert task to finish"))]
1494        InsertTaskTimeout {
1495            source: tokio::time::error::Elapsed,
1496            #[snafu(implicit)]
1497            location: Location,
1498        },
1499
1500        #[snafu(display("Insert task panicked"))]
1501        InsertTaskPanic {
1502            source: tokio::task::JoinError,
1503            #[snafu(implicit)]
1504            location: Location,
1505        },
1506
1507        #[snafu(display("Expected insert-select to be cancelled"))]
1508        InsertSelectNotCancelled {
1509            #[snafu(implicit)]
1510            location: Location,
1511        },
1512    }
1513
1514    type TestResult<T> = std::result::Result<T, TestError>;
1515
1516    fn parse_one_sql(sql: &str) -> Statement {
1517        parse_stmt(sql, &GreptimeDbDialect {}).unwrap().remove(0)
1518    }
1519
1520    fn test_query_ctx(process_id: u32) -> QueryContextRef {
1521        Arc::new(
1522            QueryContextBuilder::default()
1523                .channel(Channel::Mysql)
1524                .conn_info(ConnInfo::new(None, Channel::Mysql))
1525                .process_id(process_id)
1526                .build(),
1527        )
1528    }
1529
1530    struct BlockingInsertSelectInterceptor {
1531        started_tx: mpsc::UnboundedSender<()>,
1532        finish_rx: std::sync::Mutex<Option<oneshot::Receiver<()>>>,
1533    }
1534
1535    impl BlockingInsertSelectInterceptor {
1536        fn new(started_tx: mpsc::UnboundedSender<()>, finish_rx: oneshot::Receiver<()>) -> Self {
1537            Self {
1538                started_tx,
1539                finish_rx: std::sync::Mutex::new(Some(finish_rx)),
1540            }
1541        }
1542    }
1543
1544    impl SqlQueryInterceptor for BlockingInsertSelectInterceptor {
1545        type Error = Error;
1546
1547        fn pre_execute(
1548            &self,
1549            statement: Option<&Statement>,
1550            _plan: Option<&LogicalPlan>,
1551            _query_ctx: QueryContextRef,
1552        ) -> Result<()> {
1553            let Some(Statement::Insert(insert)) = statement else {
1554                return Ok(());
1555            };
1556            if !insert.has_non_values_query_source() {
1557                return Ok(());
1558            }
1559
1560            let finish_rx = self.finish_rx.lock().unwrap().take().unwrap();
1561            let _ = self.started_tx.send(());
1562            tokio::task::block_in_place(|| {
1563                tokio::runtime::Handle::current()
1564                    .block_on(finish_rx)
1565                    .unwrap();
1566            });
1567            Ok(())
1568        }
1569    }
1570
1571    struct PendingRecordBatchStream {
1572        schema: GtSchemaRef,
1573        polled_tx: Option<oneshot::Sender<()>>,
1574        _finish_tx: oneshot::Sender<()>,
1575        finish_rx: Pin<Box<oneshot::Receiver<()>>>,
1576    }
1577
1578    impl RecordBatchStream for PendingRecordBatchStream {
1579        fn schema(&self) -> GtSchemaRef {
1580            self.schema.clone()
1581        }
1582
1583        fn output_ordering(&self) -> Option<&[OrderOption]> {
1584            None
1585        }
1586
1587        fn metrics(&self) -> Option<common_recordbatch::adapter::RecordBatchMetrics> {
1588            None
1589        }
1590    }
1591
1592    impl Stream for PendingRecordBatchStream {
1593        type Item = common_recordbatch::error::Result<RecordBatch>;
1594
1595        fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1596            if let Some(polled_tx) = self.polled_tx.take() {
1597                let _ = polled_tx.send(());
1598            }
1599
1600            match self.finish_rx.as_mut().poll(cx) {
1601                Poll::Ready(_) => Poll::Ready(None),
1602                Poll::Pending => Poll::Pending,
1603            }
1604        }
1605    }
1606
1607    impl Unpin for PendingRecordBatchStream {}
1608
1609    struct PendingDataSource {
1610        schema: GtSchemaRef,
1611        polled_tx: std::sync::Mutex<Option<oneshot::Sender<()>>>,
1612    }
1613
1614    impl DataSource for PendingDataSource {
1615        fn get_stream(
1616            &self,
1617            _request: ScanRequest,
1618        ) -> std::result::Result<SendableRecordBatchStream, BoxedError> {
1619            let (finish_tx, finish_rx) = oneshot::channel();
1620            let mut polled_tx = self.polled_tx.lock().map_err(|_| {
1621                BoxedError::new(PlainError::new(
1622                    "pending data source lock poisoned".to_string(),
1623                    StatusCode::Unexpected,
1624                ))
1625            })?;
1626            Ok(Box::pin(PendingRecordBatchStream {
1627                schema: self.schema.clone(),
1628                polled_tx: polled_tx.take(),
1629                _finish_tx: finish_tx,
1630                finish_rx: Box::pin(finish_rx),
1631            }))
1632        }
1633    }
1634
1635    struct NoopProcedureExecutor;
1636
1637    #[async_trait::async_trait]
1638    impl ProcedureExecutor for NoopProcedureExecutor {
1639        async fn submit_ddl_task(
1640            &self,
1641            _ctx: &ExecutorContext,
1642            _request: SubmitDdlTaskRequest,
1643        ) -> common_meta::error::Result<SubmitDdlTaskResponse> {
1644            common_meta::error::UnsupportedSnafu {
1645                operation: "submit_ddl_task",
1646            }
1647            .fail()
1648        }
1649
1650        async fn migrate_region(
1651            &self,
1652            _ctx: &ExecutorContext,
1653            _request: MigrateRegionRequest,
1654        ) -> common_meta::error::Result<MigrateRegionResponse> {
1655            common_meta::error::UnsupportedSnafu {
1656                operation: "migrate_region",
1657            }
1658            .fail()
1659        }
1660
1661        async fn reconcile(
1662            &self,
1663            _ctx: &ExecutorContext,
1664            _request: ReconcileRequest,
1665        ) -> common_meta::error::Result<ReconcileResponse> {
1666            common_meta::error::UnsupportedSnafu {
1667                operation: "reconcile",
1668            }
1669            .fail()
1670        }
1671
1672        async fn query_procedure_state(
1673            &self,
1674            _ctx: &ExecutorContext,
1675            _pid: &str,
1676        ) -> common_meta::error::Result<ProcedureStateResponse> {
1677            common_meta::error::UnsupportedSnafu {
1678                operation: "query_procedure_state",
1679            }
1680            .fail()
1681        }
1682
1683        async fn list_procedures(
1684            &self,
1685            _ctx: &ExecutorContext,
1686        ) -> common_meta::error::Result<ProcedureDetailResponse> {
1687            common_meta::error::UnsupportedSnafu {
1688                operation: "list_procedures",
1689            }
1690            .fail()
1691        }
1692    }
1693
1694    fn test_cache_registry(
1695        kv_backend: common_meta::kv_backend::KvBackendRef,
1696    ) -> TestResult<common_meta::cache::LayeredCacheRegistryRef> {
1697        Ok(Arc::new(
1698            cache::with_default_composite_cache_registry(
1699                LayeredCacheRegistryBuilder::default()
1700                    .add_cache_registry(cache::build_fundamental_cache_registry(kv_backend)),
1701            )
1702            .context(BuildCacheRegistrySnafu)?
1703            .build(),
1704        ))
1705    }
1706
1707    fn test_table_info(table_id: u32, table_name: &str) -> TestResult<TableInfo> {
1708        let schema = Arc::new(GtSchema::new(vec![
1709            ColumnSchema::new("id", ConcreteDataType::int32_datatype(), false),
1710            ColumnSchema::new(
1711                "ts",
1712                ConcreteDataType::timestamp_millisecond_datatype(),
1713                false,
1714            )
1715            .with_time_index(true),
1716        ]));
1717        let table_meta = TableMetaBuilder::empty()
1718            .schema(schema)
1719            .primary_key_indices(vec![0])
1720            .value_indices(vec![1])
1721            .next_column_id(1024)
1722            .build()
1723            .with_context(|_| BuildTableMetaSnafu {
1724                table_name: table_name.to_string(),
1725            })?;
1726
1727        TableInfoBuilder::new(table_name, table_meta)
1728            .table_id(table_id)
1729            .build()
1730            .with_context(|_| BuildTableInfoSnafu {
1731                table_name: table_name.to_string(),
1732            })
1733    }
1734
1735    fn test_table(table_id: u32, table_name: &str) -> TestResult<table::TableRef> {
1736        let table_info = test_table_info(table_id, table_name)?;
1737        Ok(EmptyTable::from_table_info(&table_info))
1738    }
1739
1740    fn pending_table(
1741        table_id: u32,
1742        table_name: &str,
1743        polled_tx: oneshot::Sender<()>,
1744    ) -> TestResult<table::TableRef> {
1745        let table_info = test_table_info(table_id, table_name)?;
1746        let data_source = Arc::new(PendingDataSource {
1747            schema: table_info.meta.schema.clone(),
1748            polled_tx: std::sync::Mutex::new(Some(polled_tx)),
1749        });
1750
1751        Ok(Arc::new(Table::new(
1752            Arc::new(table_info),
1753            FilterPushDownType::Unsupported,
1754            data_source,
1755        )))
1756    }
1757
1758    async fn test_instance_with_tables(
1759        source_table: TableRef,
1760        target_table: TableRef,
1761    ) -> TestResult<Instance> {
1762        test_instance_with_plugins(source_table, target_table, Plugins::new()).await
1763    }
1764
1765    async fn test_instance_with_insert_select_interceptor(
1766        interceptor: SqlQueryInterceptorRef<Error>,
1767    ) -> TestResult<Instance> {
1768        let plugins = Plugins::new();
1769        plugins.insert::<SqlQueryInterceptorRef<Error>>(interceptor);
1770
1771        test_instance_with_plugins(
1772            test_table(1024, "source")?,
1773            test_table(1025, "target")?,
1774            plugins,
1775        )
1776        .await
1777    }
1778
1779    async fn test_instance_with_plugins(
1780        source_table: TableRef,
1781        target_table: TableRef,
1782        plugins: Plugins,
1783    ) -> TestResult<Instance> {
1784        let kv_backend = Arc::new(MemoryKvBackend::new());
1785        let process_manager = Arc::new(ProcessManager::new("test-frontend".to_string(), None));
1786        let catalog_manager = catalog::memory::MemoryCatalogManager::new_with_table(source_table);
1787        let target_table_name = "target";
1788        catalog_manager
1789            .register_table_sync(catalog::RegisterTableRequest {
1790                catalog: "greptime".to_string(),
1791                schema: "public".to_string(),
1792                table_name: target_table_name.to_string(),
1793                table_id: 1025,
1794                table: target_table,
1795            })
1796            .with_context(|_| RegisterTableSnafu {
1797                table_name: target_table_name.to_string(),
1798            })?;
1799        catalog_manager.register_process_list_table(process_manager.clone());
1800
1801        let cache_registry = test_cache_registry(kv_backend.clone())?;
1802
1803        FrontendBuilder::new(
1804            FrontendOptions::default(),
1805            kv_backend,
1806            cache_registry,
1807            catalog_manager,
1808            Arc::new(client::client_manager::NodeClients::default()),
1809            Arc::new(NoopProcedureExecutor),
1810            process_manager,
1811        )
1812        .with_plugin(plugins)
1813        .try_build()
1814        .await
1815        .context(BuildFrontendSnafu)
1816    }
1817
1818    async fn execute_one_sql(
1819        instance: &Instance,
1820        sql: &str,
1821        query_ctx: QueryContextRef,
1822    ) -> TestResult<Output> {
1823        let mut results = instance.do_query_inner(sql, query_ctx).await;
1824        ensure!(
1825            results.len() == 1,
1826            UnexpectedOutputCountSnafu {
1827                sql: sql.to_string(),
1828                actual: results.len(),
1829            }
1830        );
1831        results.remove(0).with_context(|_| ExecuteSqlSnafu {
1832            sql: sql.to_string(),
1833        })
1834    }
1835
1836    #[test]
1837    fn test_fast_legacy_check_deadlock_prevention() {
1838        // Create a DashMap to simulate the cache
1839        let cache = DashMap::new();
1840
1841        // Pre-populate cache with some entries
1842        cache.insert("metric1".to_string(), true); // legacy mode
1843        cache.insert("metric2".to_string(), false); // prom mode
1844        cache.insert("metric3".to_string(), true); // legacy mode
1845
1846        // Test case 1: Normal operation with cache hits
1847        let metric1 = "metric1".to_string();
1848        let metric4 = "metric4".to_string();
1849        let names1 = vec![&metric1, &metric4];
1850        let result = fast_legacy_check(&cache, &names1);
1851        assert!(result.is_ok());
1852        assert_eq!(result.unwrap(), Some(true)); // should return legacy mode
1853
1854        // Verify that metric4 was added to cache
1855        assert!(cache.contains_key("metric4"));
1856        assert!(*cache.get("metric4").unwrap().value());
1857
1858        // Test case 2: No cache hits
1859        let metric5 = "metric5".to_string();
1860        let metric6 = "metric6".to_string();
1861        let names2 = vec![&metric5, &metric6];
1862        let result = fast_legacy_check(&cache, &names2);
1863        assert!(result.is_ok());
1864        assert_eq!(result.unwrap(), None); // should return None as no cache hits
1865
1866        // Test case 3: Incompatible modes should return error
1867        let cache_incompatible = DashMap::new();
1868        cache_incompatible.insert("metric1".to_string(), true); // legacy
1869        cache_incompatible.insert("metric2".to_string(), false); // prom
1870        let metric1_test = "metric1".to_string();
1871        let metric2_test = "metric2".to_string();
1872        let names3 = vec![&metric1_test, &metric2_test];
1873        let result = fast_legacy_check(&cache_incompatible, &names3);
1874        assert!(result.is_err()); // should error due to incompatible modes
1875
1876        // Test case 4: Intensive concurrent access to test deadlock prevention
1877        // This test specifically targets the scenario where multiple threads
1878        // access the same cache entries simultaneously
1879        let cache_concurrent = Arc::new(DashMap::new());
1880        cache_concurrent.insert("shared_metric".to_string(), true);
1881
1882        let num_threads = 8;
1883        let operations_per_thread = 100;
1884        let barrier = Arc::new(Barrier::new(num_threads));
1885        let success_flag = Arc::new(AtomicBool::new(true));
1886
1887        let handles: Vec<_> = (0..num_threads)
1888            .map(|thread_id| {
1889                let cache_clone = Arc::clone(&cache_concurrent);
1890                let barrier_clone = Arc::clone(&barrier);
1891                let success_flag_clone = Arc::clone(&success_flag);
1892
1893                thread::spawn(move || {
1894                    // Wait for all threads to be ready
1895                    barrier_clone.wait();
1896
1897                    let start_time = Instant::now();
1898                    for i in 0..operations_per_thread {
1899                        // Each operation references existing cache entry and adds new ones
1900                        let shared_metric = "shared_metric".to_string();
1901                        let new_metric = format!("thread_{}_metric_{}", thread_id, i);
1902                        let names = vec![&shared_metric, &new_metric];
1903
1904                        match fast_legacy_check(&cache_clone, &names) {
1905                            Ok(_) => {}
1906                            Err(_) => {
1907                                success_flag_clone.store(false, Ordering::Relaxed);
1908                                return;
1909                            }
1910                        }
1911
1912                        // If the test takes too long, it likely means deadlock
1913                        if start_time.elapsed() > Duration::from_secs(10) {
1914                            success_flag_clone.store(false, Ordering::Relaxed);
1915                            return;
1916                        }
1917                    }
1918                })
1919            })
1920            .collect();
1921
1922        // Join all threads with timeout
1923        let start_time = Instant::now();
1924        for (i, handle) in handles.into_iter().enumerate() {
1925            let join_result = handle.join();
1926
1927            // Check if we're taking too long (potential deadlock)
1928            if start_time.elapsed() > Duration::from_secs(30) {
1929                panic!("Test timed out - possible deadlock detected!");
1930            }
1931
1932            if join_result.is_err() {
1933                panic!("Thread {} panicked during execution", i);
1934            }
1935        }
1936
1937        // Verify all operations completed successfully
1938        assert!(
1939            success_flag.load(Ordering::Relaxed),
1940            "Some operations failed"
1941        );
1942
1943        // Verify that many new entries were added (proving operations completed)
1944        let final_count = cache_concurrent.len();
1945        assert!(
1946            final_count > 1 + num_threads * operations_per_thread / 2,
1947            "Expected more cache entries, got {}",
1948            final_count
1949        );
1950    }
1951
1952    #[test]
1953    fn test_should_track_statement_process() {
1954        assert!(should_track_statement_process(&parse_one_sql(
1955            "SELECT * FROM demo"
1956        )));
1957        assert!(should_track_statement_process(&parse_one_sql(
1958            "INSERT INTO demo SELECT * FROM source"
1959        )));
1960        assert!(!should_track_statement_process(&parse_one_sql(
1961            "INSERT INTO demo VALUES (1)"
1962        )));
1963        assert!(!should_track_statement_process(&parse_one_sql(
1964            "INSERT INTO demo VALUES (now())"
1965        )));
1966    }
1967
1968    #[test]
1969    fn test_should_track_plan_process() {
1970        let select_stmt = parse_one_sql("SELECT * FROM demo");
1971        let insert_select_stmt = parse_one_sql("INSERT INTO demo SELECT * FROM source");
1972        let insert_values_stmt = parse_one_sql("INSERT INTO demo VALUES (now())");
1973
1974        let empty_plan = LogicalPlanBuilder::empty(false).build().unwrap();
1975        assert!(should_track_plan_process(Some(&select_stmt), &empty_plan));
1976        assert!(should_track_plan_process(
1977            Some(&insert_select_stmt),
1978            &insert_dml_plan()
1979        ));
1980        assert!(!should_track_plan_process(
1981            Some(&insert_values_stmt),
1982            &insert_dml_plan()
1983        ));
1984        assert!(!should_track_plan_process(None, &insert_dml_plan()));
1985    }
1986
1987    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1988    async fn test_insert_select_is_visible_in_show_processlist() -> TestResult<()> {
1989        let insert_sql = "INSERT INTO target SELECT * FROM source";
1990        let (started_tx, mut started_rx) = mpsc::unbounded_channel();
1991        let (finish_tx, finish_rx) = oneshot::channel();
1992        let interceptor = Arc::new(BlockingInsertSelectInterceptor::new(started_tx, finish_rx));
1993        let instance = Arc::new(test_instance_with_insert_select_interceptor(interceptor).await?);
1994
1995        let insert_task = tokio::spawn({
1996            let instance = instance.clone();
1997            async move { execute_one_sql(&instance, insert_sql, test_query_ctx(4242)).await }
1998        });
1999
2000        tokio::time::timeout(Duration::from_secs(5), started_rx.recv())
2001            .await
2002            .context(InsertStartTimeoutSnafu)?
2003            .context(InsertStartChannelClosedSnafu)?;
2004
2005        let output = execute_one_sql(&instance, "SHOW PROCESSLIST", test_query_ctx(43)).await?;
2006        let process_list = output.data.pretty_print().await;
2007        assert!(
2008            process_list.contains(insert_sql),
2009            "process list did not contain running insert:\n{process_list}"
2010        );
2011
2012        finish_tx
2013            .send(())
2014            .map_err(|_| ReleaseBlockedInsertSnafu.build())?;
2015        insert_task.await.context(InsertTaskPanicSnafu)??;
2016
2017        Ok(())
2018    }
2019
2020    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2021    async fn test_kill_query_cancels_insert_select() -> TestResult<()> {
2022        assert_kill_cancels_insert_select("KILL QUERY 4242").await
2023    }
2024
2025    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2026    async fn test_kill_process_id_cancels_insert_select() -> TestResult<()> {
2027        assert_kill_cancels_insert_select("KILL 'test-frontend/4242'").await
2028    }
2029
2030    async fn assert_kill_cancels_insert_select(kill_sql: &str) -> TestResult<()> {
2031        let insert_sql = "INSERT INTO target SELECT * FROM source";
2032        let (source_polled_tx, source_polled_rx) = oneshot::channel();
2033        let instance = Arc::new(
2034            test_instance_with_tables(
2035                pending_table(1024, "source", source_polled_tx)?,
2036                test_table(1025, "target")?,
2037            )
2038            .await?,
2039        );
2040
2041        let insert_task = tokio::spawn({
2042            let instance = instance.clone();
2043            async move { execute_one_sql(&instance, insert_sql, test_query_ctx(4242)).await }
2044        });
2045
2046        tokio::time::timeout(Duration::from_secs(5), source_polled_rx)
2047            .await
2048            .context(SourcePollTimeoutSnafu)?
2049            .context(SourcePollChannelClosedSnafu)?;
2050
2051        let output = execute_one_sql(&instance, kill_sql, test_query_ctx(43)).await?;
2052        assert!(matches!(output.data, OutputData::AffectedRows(1)));
2053
2054        let insert_result = tokio::time::timeout(Duration::from_secs(5), insert_task)
2055            .await
2056            .context(InsertTaskTimeoutSnafu)?
2057            .context(InsertTaskPanicSnafu)?;
2058        let err = match insert_result {
2059            Ok(_) => return InsertSelectNotCancelledSnafu.fail(),
2060            Err(TestError::ExecuteSql { source, .. }) => source,
2061            Err(err) => return Err(err),
2062        };
2063        assert_eq!(StatusCode::Cancelled, err.status_code());
2064
2065        let output = execute_one_sql(&instance, "SHOW PROCESSLIST", test_query_ctx(43)).await?;
2066        let process_list = output.data.pretty_print().await;
2067        assert!(
2068            !process_list.contains(insert_sql),
2069            "process list still contains killed insert:\n{process_list}"
2070        );
2071
2072        Ok(())
2073    }
2074
2075    fn insert_dml_plan() -> LogicalPlan {
2076        let schema = SchemaRef::new(Schema::new(vec![Field::new(
2077            "value",
2078            DataType::Int64,
2079            true,
2080        )]));
2081        let target = Arc::new(LogicalTableSource::new(schema));
2082        let input = LogicalPlanBuilder::empty(false).build().unwrap();
2083
2084        LogicalPlanBuilder::insert_into(input, "demo", target, InsertOp::Append)
2085            .unwrap()
2086            .build()
2087            .unwrap()
2088    }
2089
2090    #[test]
2091    fn test_exec_validation() {
2092        let query_ctx = QueryContext::arc();
2093        let plugins: Plugins = Plugins::new();
2094        plugins.insert(QueryOptions {
2095            disallow_cross_catalog_query: true,
2096        });
2097
2098        let sql = r#"
2099        SELECT * FROM demo;
2100        EXPLAIN SELECT * FROM demo;
2101        CREATE DATABASE test_database;
2102        SHOW DATABASES;
2103        "#;
2104        let stmts = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2105        assert_eq!(stmts.len(), 4);
2106        for stmt in stmts {
2107            let re = check_permission(plugins.clone(), &stmt, &query_ctx);
2108            re.unwrap();
2109        }
2110
2111        let sql = r#"
2112        SHOW CREATE TABLE demo;
2113        ALTER TABLE demo ADD COLUMN new_col INT;
2114        "#;
2115        let stmts = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2116        assert_eq!(stmts.len(), 2);
2117        for stmt in stmts {
2118            let re = check_permission(plugins.clone(), &stmt, &query_ctx);
2119            re.unwrap();
2120        }
2121
2122        fn replace_test(template_sql: &str, plugins: Plugins, query_ctx: &QueryContextRef) {
2123            // test right
2124            let right = vec![("", ""), ("", "public."), ("greptime.", "public.")];
2125            for (catalog, schema) in right {
2126                let sql = do_fmt(template_sql, catalog, schema);
2127                do_test(&sql, plugins.clone(), query_ctx, true);
2128            }
2129
2130            let wrong = vec![
2131                ("wrongcatalog.", "public."),
2132                ("wrongcatalog.", "wrongschema."),
2133            ];
2134            for (catalog, schema) in wrong {
2135                let sql = do_fmt(template_sql, catalog, schema);
2136                do_test(&sql, plugins.clone(), query_ctx, false);
2137            }
2138        }
2139
2140        fn do_fmt(template: &str, catalog: &str, schema: &str) -> String {
2141            let vars = HashMap::from([
2142                ("catalog".to_string(), catalog),
2143                ("schema".to_string(), schema),
2144            ]);
2145            template.format(&vars).unwrap()
2146        }
2147
2148        fn do_test(sql: &str, plugins: Plugins, query_ctx: &QueryContextRef, is_ok: bool) {
2149            let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
2150            let re = check_permission(plugins, stmt, query_ctx);
2151            if is_ok {
2152                re.unwrap();
2153            } else {
2154                assert!(re.is_err());
2155            }
2156        }
2157
2158        // test insert
2159        let sql = "INSERT INTO {catalog}{schema}monitor(host) VALUES ('host1');";
2160        replace_test(sql, plugins.clone(), &query_ctx);
2161
2162        // test create table
2163        let sql = r#"CREATE TABLE {catalog}{schema}demo(
2164                            host STRING,
2165                            ts TIMESTAMP,
2166                            TIME INDEX (ts),
2167                            PRIMARY KEY(host)
2168                        ) engine=mito;"#;
2169        replace_test(sql, plugins.clone(), &query_ctx);
2170
2171        // test drop table
2172        let sql = "DROP TABLE {catalog}{schema}demo;";
2173        replace_test(sql, plugins.clone(), &query_ctx);
2174
2175        // test show tables
2176        let sql = "SHOW TABLES FROM public";
2177        let stmt = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2178        check_permission(plugins.clone(), &stmt[0], &query_ctx).unwrap();
2179
2180        let sql = "SHOW TABLES FROM private";
2181        let stmt = parse_stmt(sql, &GreptimeDbDialect {}).unwrap();
2182        let re = check_permission(plugins.clone(), &stmt[0], &query_ctx);
2183        assert!(re.is_ok());
2184
2185        // test describe table
2186        let sql = "DESC TABLE {catalog}{schema}demo;";
2187        replace_test(sql, plugins.clone(), &query_ctx);
2188
2189        let comment_flow_cases = [
2190            ("COMMENT ON FLOW my_flow IS 'comment';", true),
2191            ("COMMENT ON FLOW greptime.my_flow IS 'comment';", true),
2192            ("COMMENT ON FLOW wrongcatalog.my_flow IS 'comment';", false),
2193        ];
2194        for (sql, is_ok) in comment_flow_cases {
2195            let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
2196            let result = check_permission(plugins.clone(), stmt, &query_ctx);
2197            assert_eq!(result.is_ok(), is_ok);
2198        }
2199
2200        let show_flow_cases = [
2201            ("SHOW CREATE FLOW my_flow;", true),
2202            ("SHOW CREATE FLOW greptime.my_flow;", true),
2203            ("SHOW CREATE FLOW wrongcatalog.my_flow;", false),
2204        ];
2205        for (sql, is_ok) in show_flow_cases {
2206            let stmt = &parse_stmt(sql, &GreptimeDbDialect {}).unwrap()[0];
2207            let result = check_permission(plugins.clone(), stmt, &query_ctx);
2208            assert_eq!(result.is_ok(), is_ok);
2209        }
2210    }
2211}