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