1pub 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#[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 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 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 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 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 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 drop(cache);
409
410 let catalog = ctx.current_catalog();
411 let schema = ctx.current_schema();
412
413 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 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 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 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 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 ensure!(!(hit_legacy && hit_prom), OtlpMetricModeIncompatibleSnafu);
505
506 let flag = hit_legacy;
507 drop(hit_cache);
509
510 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
522fn 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
536fn 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 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 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 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
955pub 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 let query_statement = if let QueryStatement::Promql(eval_stmt, alias) = stmt {
1007 CatalogQueryStatement::Promql(eval_stmt, alias)
1008 } else {
1009 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
1094macro_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 Statement::Admin(_) => {}
1123 Statement::Query(_)
1125 | Statement::Explain(_)
1126 | Statement::Tql(_)
1127 | Statement::Delete(_)
1128 | Statement::DeclareCursor(_)
1129 | Statement::Copy(sql::statements::copy::Copy::CopyQueryTo(_)) => {}
1130 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 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 Statement::SetVariables(_) | Statement::ShowVariables(_) => {}
1176 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 }
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(©_table_to.table_name, query_ctx)?,
1237 CopyTable::From(copy_table_from) => {
1238 validate_param(©_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 Statement::FetchCursor(_) | Statement::CloseCursor(_) => {}
1252 Statement::Kill(_) => {}
1254 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 let cache = DashMap::new();
1849
1850 cache.insert("metric1".to_string(), true); cache.insert("metric2".to_string(), false); cache.insert("metric3".to_string(), true); 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)); assert!(cache.contains_key("metric4"));
1865 assert!(*cache.get("metric4").unwrap().value());
1866
1867 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); let cache_incompatible = DashMap::new();
1877 cache_incompatible.insert("metric1".to_string(), true); cache_incompatible.insert("metric2".to_string(), false); 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()); 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 barrier_clone.wait();
1905
1906 let start_time = Instant::now();
1907 for i in 0..operations_per_thread {
1908 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 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 let start_time = Instant::now();
1933 for (i, handle) in handles.into_iter().enumerate() {
1934 let join_result = handle.join();
1935
1936 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 assert!(
1948 success_flag.load(Ordering::Relaxed),
1949 "Some operations failed"
1950 );
1951
1952 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 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 let sql = "INSERT INTO {catalog}{schema}monitor(host) VALUES ('host1');";
2169 replace_test(sql, plugins.clone(), &query_ctx);
2170
2171 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 let sql = "DROP TABLE {catalog}{schema}demo;";
2182 replace_test(sql, plugins.clone(), &query_ctx);
2183
2184 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 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}