1mod error;
18mod json_expr_planner;
19mod planner;
20
21use std::any::Any;
22use std::collections::HashMap;
23use std::sync::Arc;
24
25use async_trait::async_trait;
26use common_base::Plugins;
27use common_catalog::consts::is_readonly_schema;
28use common_error::ext::BoxedError;
29use common_function::function::FunctionContext;
30use common_function::function_factory::ScalarFunctionFactory;
31use common_query::{Output, OutputData, OutputMeta};
32use common_recordbatch::adapter::{RecordBatchStreamAdapter, RegionQueryStatCounters};
33use common_recordbatch::{EmptyRecordBatchStream, SendableRecordBatchStream};
34use common_telemetry::tracing;
35use datafusion::catalog::TableFunction;
36use datafusion::dataframe::DataFrame;
37use datafusion::physical_plan::ExecutionPlan;
38use datafusion::physical_plan::analyze::AnalyzeExec;
39use datafusion::physical_plan::coalesce_partitions::CoalescePartitionsExec;
40use datafusion_common::ResolvedTableReference;
41use datafusion_expr::{
42 AggregateUDF, DmlStatement, LogicalPlan as DfLogicalPlan, LogicalPlan, WindowUDF, WriteOp,
43};
44use datatypes::prelude::VectorRef;
45use datatypes::schema::Schema;
46use futures_util::StreamExt;
47use session::context::QueryContextRef;
48use snafu::{OptionExt, ResultExt, ensure};
49use sqlparser::ast::AnalyzeFormat;
50use table::TableRef;
51use table::requests::{DeleteRequest, InsertRequest};
52use table::table::scan::{REGION_SCAN_EXEC_NAME, RegionScanExec};
53use tracing::Span;
54
55use crate::analyze::DistAnalyzeExec;
56pub use crate::datafusion::planner::DfContextProviderAdapter;
57use crate::dist_plan::{
58 DistPlannerOptions, MergeScanLogicalPlan, RemoteDynFilterReceiverInjectorRef,
59};
60use crate::error::{
61 CatalogSnafu, CreateRecordBatchSnafu, MissingTableMutationHandlerSnafu,
62 MissingTimestampColumnSnafu, QueryExecutionSnafu, Result, TableMutationSnafu,
63 TableNotFoundSnafu, TableReadOnlySnafu, UnsupportedExprSnafu,
64};
65use crate::executor::QueryExecutor;
66use crate::metrics::{
67 OnDone, QUERY_STAGE_ELAPSED, maybe_attach_region_watermark_metrics,
68 should_collect_region_watermark_from_query_ctx,
69};
70use crate::options::ScheduledTimeExtension;
71use crate::physical_wrapper::PhysicalPlanWrapperRef;
72use crate::planner::{DfLogicalPlanner, LogicalPlanner};
73use crate::query_engine::{DescribeResult, QueryEngineContext, QueryEngineState};
74use crate::{QueryEngine, metrics};
75
76pub const QUERY_PARALLELISM_HINT: &str = "query_parallelism";
79
80pub const QUERY_FALLBACK_HINT: &str = "query_fallback";
82
83fn query_load_region_id(plan: &Arc<dyn ExecutionPlan>) -> Option<u64> {
84 let mut region_id = None;
85 let mut stack = vec![plan.clone()];
86
87 while let Some(plan) = stack.pop() {
88 if plan.name() == REGION_SCAN_EXEC_NAME
89 && let Some(scan) = plan.as_any().downcast_ref::<RegionScanExec>()
90 && let Some(scan_region_id) = scan.query_load_region_id()
91 {
92 match region_id {
93 Some(region_id) if region_id != scan_region_id => return None,
94 Some(_) => {}
95 None => region_id = Some(scan_region_id),
96 }
97 }
98 stack.extend(plan.children().into_iter().cloned());
99 }
100
101 region_id
102}
103
104fn query_stat_counters(plan: &Arc<dyn ExecutionPlan>) -> Option<RegionQueryStatCounters> {
113 let mut counters: Option<RegionQueryStatCounters> = None;
114 let mut stack = vec![plan.clone()];
115
116 while let Some(plan) = stack.pop() {
117 if plan.name() == REGION_SCAN_EXEC_NAME
118 && let Some(scan) = plan.as_any().downcast_ref::<RegionScanExec>()
119 && let Some(scan_counters) = scan.query_stat_counters()
120 {
121 match &counters {
122 Some(counters)
123 if !Arc::ptr_eq(&counters.query_cpu_time, &scan_counters.query_cpu_time)
124 || !Arc::ptr_eq(
125 &counters.query_scanned_bytes,
126 &scan_counters.query_scanned_bytes,
127 ) =>
128 {
129 return None;
130 }
131 Some(_) => {}
132 None => counters = Some(scan_counters),
133 }
134 }
135 stack.extend(plan.children().into_iter().cloned());
136 }
137
138 counters
139}
140
141pub struct DatafusionQueryEngine {
142 state: Arc<QueryEngineState>,
143 plugins: Plugins,
144}
145
146impl DatafusionQueryEngine {
147 pub fn new(state: Arc<QueryEngineState>, plugins: Plugins) -> Self {
148 Self { state, plugins }
149 }
150
151 #[tracing::instrument(skip_all)]
152 async fn exec_query_plan(
153 &self,
154 plan: LogicalPlan,
155 query_ctx: QueryContextRef,
156 ) -> Result<Output> {
157 let mut ctx = self.engine_context(query_ctx.clone());
158 let plan = if let Some(receiver_injector) =
159 self.plugins.get::<RemoteDynFilterReceiverInjectorRef>()
160 {
161 receiver_injector.maybe_inject(plan, query_ctx.clone())
162 } else {
163 plan
164 };
165
166 let physical_plan = self.create_physical_plan(&mut ctx, &plan).await?;
168 let physical_plan = self.optimize_physical_plan(&mut ctx, physical_plan)?;
169 let physical_plan = if let Some(wrapper) = self.plugins.get::<PhysicalPlanWrapperRef>() {
170 wrapper.wrap(physical_plan, query_ctx)
171 } else {
172 physical_plan
173 };
174
175 let stream = self.execute_stream(&ctx, &physical_plan)?;
176
177 Ok(Output::new(
178 OutputData::Stream(stream),
179 OutputMeta::new_with_plan(physical_plan),
180 ))
181 }
182
183 #[tracing::instrument(skip_all)]
184 async fn exec_dml_statement(
185 &self,
186 dml: DmlStatement,
187 query_ctx: QueryContextRef,
188 ) -> Result<Output> {
189 ensure!(
190 matches!(dml.op, WriteOp::Insert(_) | WriteOp::Delete),
191 UnsupportedExprSnafu {
192 name: format!("DML op {}", dml.op),
193 }
194 );
195
196 let _timer = QUERY_STAGE_ELAPSED
197 .with_label_values(&[dml.op.name()])
198 .start_timer();
199
200 let default_catalog = &query_ctx.current_catalog().to_owned();
201 let default_schema = &query_ctx.current_schema();
202 let table_name = dml.table_name.resolve(default_catalog, default_schema);
203 let table = self.find_table(&table_name, &query_ctx).await?;
204
205 let Output { data, meta } = self
206 .exec_query_plan((*dml.input).clone(), query_ctx.clone())
207 .await?;
208 let mut stream = match data {
209 OutputData::RecordBatches(batches) => batches.as_stream(),
210 OutputData::Stream(stream) => stream,
211 _ => unreachable!(),
212 };
213
214 let mut affected_rows = 0;
215 let mut insert_cost = 0;
216
217 while let Some(batch) = stream.next().await {
218 let batch = batch.context(CreateRecordBatchSnafu)?;
219 let column_vectors = batch
220 .column_vectors(&table_name.to_string(), table.schema())
221 .map_err(BoxedError::new)
222 .context(QueryExecutionSnafu)?;
223
224 match dml.op {
225 WriteOp::Insert(_) => {
226 let output = self
228 .insert(&table_name, column_vectors, query_ctx.clone())
229 .await?;
230 let (rows, cost) = output.extract_rows_and_cost();
231 affected_rows += rows;
232 insert_cost += cost;
233 }
234 WriteOp::Delete => {
235 affected_rows += self
236 .delete(&table_name, &table, column_vectors, query_ctx.clone())
237 .await?;
238 }
239 _ => unreachable!("guarded by the 'ensure!' at the beginning"),
240 }
241 }
242 Ok(Output::new(
243 OutputData::AffectedRows(affected_rows),
244 OutputMeta::new(meta.plan, insert_cost),
245 ))
246 }
247
248 #[tracing::instrument(skip_all)]
249 async fn delete(
250 &self,
251 table_name: &ResolvedTableReference,
252 table: &TableRef,
253 column_vectors: HashMap<String, VectorRef>,
254 query_ctx: QueryContextRef,
255 ) -> Result<usize> {
256 let catalog_name = table_name.catalog.to_string();
257 let schema_name = table_name.schema.to_string();
258 let table_name = table_name.table.to_string();
259 let table_schema = table.schema();
260
261 ensure!(
262 !is_readonly_schema(&schema_name),
263 TableReadOnlySnafu { table: table_name }
264 );
265
266 let ts_column = table_schema
267 .timestamp_column()
268 .map(|x| &x.name)
269 .with_context(|| MissingTimestampColumnSnafu {
270 table_name: table_name.clone(),
271 })?;
272
273 let table_info = table.table_info();
274 let rowkey_columns = table_info
275 .meta
276 .row_key_column_names()
277 .collect::<Vec<&String>>();
278 let column_vectors = column_vectors
279 .into_iter()
280 .filter(|x| &x.0 == ts_column || rowkey_columns.contains(&&x.0))
281 .collect::<HashMap<_, _>>();
282
283 let request = DeleteRequest {
284 catalog_name,
285 schema_name,
286 table_name,
287 key_column_values: column_vectors,
288 };
289
290 self.state
291 .table_mutation_handler()
292 .context(MissingTableMutationHandlerSnafu)?
293 .delete(request, query_ctx)
294 .await
295 .context(TableMutationSnafu)
296 }
297
298 #[tracing::instrument(skip_all)]
299 async fn insert(
300 &self,
301 table_name: &ResolvedTableReference,
302 column_vectors: HashMap<String, VectorRef>,
303 query_ctx: QueryContextRef,
304 ) -> Result<Output> {
305 let catalog_name = table_name.catalog.to_string();
306 let schema_name = table_name.schema.to_string();
307 let table_name = table_name.table.to_string();
308
309 ensure!(
310 !is_readonly_schema(&schema_name),
311 TableReadOnlySnafu { table: table_name }
312 );
313
314 let request = InsertRequest {
315 catalog_name,
316 schema_name,
317 table_name,
318 columns_values: column_vectors,
319 };
320
321 self.state
322 .table_mutation_handler()
323 .context(MissingTableMutationHandlerSnafu)?
324 .insert(request, query_ctx)
325 .await
326 .context(TableMutationSnafu)
327 }
328
329 async fn find_table(
330 &self,
331 table_name: &ResolvedTableReference,
332 query_context: &QueryContextRef,
333 ) -> Result<TableRef> {
334 let catalog_name = table_name.catalog.as_ref();
335 let schema_name = table_name.schema.as_ref();
336 let table_name = table_name.table.as_ref();
337
338 self.state
339 .catalog_manager()
340 .table(catalog_name, schema_name, table_name, Some(query_context))
341 .await
342 .context(CatalogSnafu)?
343 .with_context(|| TableNotFoundSnafu { table: table_name })
344 }
345
346 #[tracing::instrument(skip_all)]
347 async fn create_physical_plan(
348 &self,
349 ctx: &mut QueryEngineContext,
350 logical_plan: &LogicalPlan,
351 ) -> Result<Arc<dyn ExecutionPlan>> {
352 #[derive(Debug)]
356 struct PanicLogger<'a> {
357 input_logical_plan: &'a LogicalPlan,
358 after_analyze: Option<LogicalPlan>,
359 after_optimize: Option<LogicalPlan>,
360 phy_plan: Option<Arc<dyn ExecutionPlan>>,
361 }
362 impl Drop for PanicLogger<'_> {
363 fn drop(&mut self) {
364 if std::thread::panicking() {
365 common_telemetry::error!(
366 "Panic while creating physical plan, input logical plan: {:?}, after analyze: {:?}, after optimize: {:?}, final physical plan: {:?}",
367 self.input_logical_plan,
368 self.after_analyze,
369 self.after_optimize,
370 self.phy_plan
371 );
372 }
373 }
374 }
375
376 let mut logger = PanicLogger {
377 input_logical_plan: logical_plan,
378 after_analyze: None,
379 after_optimize: None,
380 phy_plan: None,
381 };
382
383 let _timer = metrics::CREATE_PHYSICAL_ELAPSED.start_timer();
384 let state = ctx.state();
385
386 common_telemetry::debug!("Create physical plan, input plan: {logical_plan}");
387
388 if matches!(logical_plan, DfLogicalPlan::Explain(_)) {
390 return state
391 .create_physical_plan(logical_plan)
392 .await
393 .map_err(Into::into);
394 }
395
396 let analyzed_plan = state.analyzer().execute_and_check(
398 logical_plan.clone(),
399 state.config_options(),
400 |_, _| {},
401 )?;
402
403 logger.after_analyze = Some(analyzed_plan.clone());
404
405 common_telemetry::debug!("Create physical plan, analyzed plan: {analyzed_plan}");
406
407 let optimized_plan = if let DfLogicalPlan::Extension(ext) = &analyzed_plan
409 && ext.node.name() == MergeScanLogicalPlan::name()
410 {
411 analyzed_plan.clone()
412 } else {
413 state
414 .optimizer()
415 .optimize(analyzed_plan, state, |_, _| {})?
416 };
417
418 common_telemetry::debug!("Create physical plan, optimized plan: {optimized_plan}");
419 logger.after_optimize = Some(optimized_plan.clone());
420
421 let physical_plan = state
422 .query_planner()
423 .create_physical_plan(&optimized_plan, state)
424 .await?;
425
426 logger.phy_plan = Some(physical_plan.clone());
427 drop(logger);
428 Ok(physical_plan)
429 }
430
431 #[tracing::instrument(skip_all)]
432 fn optimize_physical_plan(
433 &self,
434 ctx: &mut QueryEngineContext,
435 plan: Arc<dyn ExecutionPlan>,
436 ) -> Result<Arc<dyn ExecutionPlan>> {
437 let _timer = metrics::OPTIMIZE_PHYSICAL_ELAPSED.start_timer();
438
439 let optimized_plan = if let Some(analyze_plan) = plan.as_any().downcast_ref::<AnalyzeExec>()
446 {
447 let format = if let Some(format) = ctx.query_ctx().explain_format()
448 && format.to_lowercase() == "json"
449 {
450 AnalyzeFormat::JSON
451 } else {
452 AnalyzeFormat::TEXT
453 };
454 ctx.query_ctx().set_explain_verbose(analyze_plan.verbose());
457
458 Arc::new(DistAnalyzeExec::new(
459 analyze_plan.input().clone(),
460 analyze_plan.verbose(),
461 format,
462 ))
463 } else {
471 plan
472 };
480
481 Ok(optimized_plan)
482 }
483}
484
485#[async_trait]
486impl QueryEngine for DatafusionQueryEngine {
487 fn as_any(&self) -> &dyn Any {
488 self
489 }
490
491 fn planner(&self) -> Arc<dyn LogicalPlanner> {
492 Arc::new(DfLogicalPlanner::new(self.state.clone()))
493 }
494
495 fn name(&self) -> &str {
496 "datafusion"
497 }
498
499 async fn describe(
500 &self,
501 plan: LogicalPlan,
502 _query_ctx: QueryContextRef,
503 ) -> Result<DescribeResult> {
504 Ok(DescribeResult { logical_plan: plan })
505 }
506
507 async fn execute(&self, plan: LogicalPlan, query_ctx: QueryContextRef) -> Result<Output> {
508 match plan {
509 LogicalPlan::Dml(dml) => self.exec_dml_statement(dml, query_ctx).await,
510 _ => self.exec_query_plan(plan, query_ctx).await,
511 }
512 }
513
514 fn register_aggregate_function(&self, func: AggregateUDF) {
522 self.state.register_aggr_function(func);
523 }
524
525 fn register_scalar_function(&self, func: ScalarFunctionFactory) {
528 self.state.register_scalar_function(func);
529 }
530
531 fn register_table_function(&self, func: Arc<TableFunction>) {
532 self.state.register_table_function(func);
533 }
534
535 fn register_window_function(&self, func: WindowUDF) {
536 self.state.register_window_function(func);
537 }
538
539 fn read_table(&self, table: TableRef) -> Result<DataFrame> {
540 self.state.read_table(table).map_err(Into::into)
541 }
542
543 fn engine_context(&self, query_ctx: QueryContextRef) -> QueryEngineContext {
544 let mut state = self.state.session_state();
545 state.config_mut().set_extension(query_ctx.clone());
546 state.config_mut().set_extension(self.state.clone());
547 if let Some(parallelism) = query_ctx.extension(QUERY_PARALLELISM_HINT) {
550 if let Ok(n) = parallelism.parse::<u64>() {
551 if n > 0 {
552 let new_cfg = state.config().clone().with_target_partitions(n as usize);
553 *state.config_mut() = new_cfg;
554 }
555 } else {
556 common_telemetry::warn!(
557 "Failed to parse query_parallelism: {}, using default value",
558 parallelism
559 );
560 }
561 }
562
563 state.config_mut().options_mut().execution.time_zone =
565 Some(query_ctx.timezone().to_string());
566
567 if query_ctx.configuration_parameter().allow_query_fallback() {
570 state
571 .config_mut()
572 .options_mut()
573 .extensions
574 .insert(DistPlannerOptions {
575 allow_query_fallback: true,
576 });
577 } else if let Some(fallback) = query_ctx.extension(QUERY_FALLBACK_HINT) {
578 if fallback.to_lowercase().parse::<bool>().unwrap_or(false) {
581 state
582 .config_mut()
583 .options_mut()
584 .extensions
585 .insert(DistPlannerOptions {
586 allow_query_fallback: true,
587 });
588 }
589 }
590
591 state
592 .config_mut()
593 .options_mut()
594 .extensions
595 .insert(FunctionContext {
596 query_ctx: query_ctx.clone(),
597 state: self.engine_state().function_state(),
598 });
599
600 state
604 .config_mut()
605 .options_mut()
606 .extensions
607 .insert(ScheduledTimeExtension {
608 scheduled_time: crate::options::scheduled_time_from_ctx(&query_ctx),
609 });
610
611 let config_options = state.config_options().clone();
612 let _ = state
613 .execution_props_mut()
614 .config_options
615 .insert(config_options);
616
617 match crate::options::parse_scheduled_time_datetime(&query_ctx.extensions()) {
621 Ok(Some(scheduled_rt)) => {
622 state.execution_props_mut().query_execution_start_time = Some(scheduled_rt);
623 }
624 Ok(None) => {}
625 Err(err) => {
626 common_telemetry::warn!(err; "Ignoring invalid scheduled time query extension");
627 }
628 }
629
630 QueryEngineContext::new(state, query_ctx)
631 }
632
633 fn engine_state(&self) -> &QueryEngineState {
634 &self.state
635 }
636}
637
638impl QueryExecutor for DatafusionQueryEngine {
639 #[tracing::instrument(skip_all)]
640 fn execute_stream(
641 &self,
642 ctx: &QueryEngineContext,
643 plan: &Arc<dyn ExecutionPlan>,
644 ) -> Result<SendableRecordBatchStream> {
645 let query_ctx = ctx.query_ctx();
646 let explain_verbose = query_ctx.explain_verbose();
647 let should_collect_region_watermark =
648 should_collect_region_watermark_from_query_ctx(&query_ctx)?;
649 let output_partitions = plan.properties().output_partitioning().partition_count();
650 if explain_verbose {
651 common_telemetry::info!("Executing query plan, output_partitions: {output_partitions}");
652 }
653
654 let exec_timer = metrics::EXEC_PLAN_ELAPSED.start_timer();
655 let task_ctx = ctx.build_task_ctx();
656 let span = Span::current();
657
658 match plan.properties().output_partitioning().partition_count() {
659 0 => {
660 let schema = Arc::new(
661 Schema::try_from(plan.schema())
662 .map_err(BoxedError::new)
663 .context(QueryExecutionSnafu)?,
664 );
665 Ok(Box::pin(EmptyRecordBatchStream::new(schema)))
666 }
667 1 => {
668 let df_stream = plan.execute(0, task_ctx)?;
669 let mut stream = RecordBatchStreamAdapter::try_new_with_span(df_stream, span)
670 .context(error::ConvertDfRecordBatchStreamSnafu)
671 .map_err(BoxedError::new)
672 .context(QueryExecutionSnafu)?;
673 stream.set_metrics2(plan.clone());
674 stream.set_query_load_region_id(query_load_region_id(plan));
675 stream.set_query_stat_counters(query_stat_counters(plan));
676 stream.set_explain_verbose(explain_verbose);
677 let stream = OnDone::new(Box::pin(stream), move || {
678 let exec_cost = exec_timer.stop_and_record();
679 if explain_verbose {
680 common_telemetry::info!(
681 "DatafusionQueryEngine execute 1 stream, cost: {:?}s",
682 exec_cost,
683 );
684 }
685 });
686 Ok(maybe_attach_region_watermark_metrics(
687 Box::pin(stream),
688 plan.clone(),
689 should_collect_region_watermark,
690 ))
691 }
692 _ => {
693 let merged_plan = CoalescePartitionsExec::new(plan.clone());
695 assert_eq!(
697 1,
698 merged_plan
699 .properties()
700 .output_partitioning()
701 .partition_count()
702 );
703 let df_stream = merged_plan.execute(0, task_ctx)?;
704 let mut stream = RecordBatchStreamAdapter::try_new_with_span(df_stream, span)
705 .context(error::ConvertDfRecordBatchStreamSnafu)
706 .map_err(BoxedError::new)
707 .context(QueryExecutionSnafu)?;
708 stream.set_metrics2(plan.clone());
709 stream.set_query_load_region_id(query_load_region_id(plan));
710 stream.set_query_stat_counters(query_stat_counters(plan));
711 stream.set_explain_verbose(explain_verbose);
712 let stream = OnDone::new(Box::pin(stream), move || {
713 let exec_cost = exec_timer.stop_and_record();
714 if explain_verbose {
715 common_telemetry::info!(
716 "DatafusionQueryEngine execute {output_partitions} stream, cost: {:?}s",
717 exec_cost
718 );
719 }
720 });
721 Ok(maybe_attach_region_watermark_metrics(
722 Box::pin(stream),
723 plan.clone(),
724 should_collect_region_watermark,
725 ))
726 }
727 }
728 }
729}
730
731#[cfg(test)]
732mod tests {
733 use std::fmt;
734 use std::sync::Arc;
735 use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
736
737 use api::v1::SemanticType;
738 use arrow::array::{ArrayRef, UInt64Array};
739 use arrow_schema::SortOptions;
740 use catalog::RegisterTableRequest;
741 use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, NUMBERS_TABLE_ID};
742 use common_error::ext::BoxedError;
743 use common_recordbatch::{EmptyRecordBatchStream, SendableRecordBatchStream, util};
744 use datafusion::physical_plan::display::{DisplayAs, DisplayFormatType};
745 use datafusion::physical_plan::expressions::PhysicalSortExpr;
746 use datafusion::physical_plan::joins::{HashJoinExec, JoinOn, PartitionMode};
747 use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
748 use datafusion::physical_plan::{ExecutionPlan, PhysicalExpr};
749 use datafusion::prelude::{col, lit};
750 use datafusion_common::{JoinType, NullEquality};
751 use datafusion_physical_expr::expressions::Column;
752 use datatypes::prelude::ConcreteDataType;
753 use datatypes::schema::{ColumnSchema, SchemaRef};
754 use datatypes::vectors::{Helper, UInt32Vector, VectorRef};
755 use session::context::{QueryContext, QueryContextBuilder};
756 use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder, RegionMetadataRef};
757 use store_api::region_engine::{
758 PartitionRange, PrepareRequest, QueryScanContext, RegionScanner, ScannerProperties,
759 };
760 use store_api::storage::{RegionId, ScanRequest};
761 use table::table::numbers::{NUMBERS_TABLE_NAME, NumbersTable};
762 use table::table::scan::RegionScanExec;
763
764 use super::*;
765 use crate::options::QueryOptions;
766 use crate::parser::QueryLanguageParser;
767 use crate::part_sort::PartSortExec;
768 use crate::query_engine::{QueryEngineFactory, QueryEngineRef};
769
770 #[derive(Debug)]
771 struct RecordingScanner {
772 schema: SchemaRef,
773 metadata: RegionMetadataRef,
774 properties: ScannerProperties,
775 update_calls: Arc<AtomicUsize>,
776 last_filter_len: Arc<AtomicUsize>,
777 }
778
779 impl RecordingScanner {
780 fn new(
781 schema: SchemaRef,
782 metadata: RegionMetadataRef,
783 update_calls: Arc<AtomicUsize>,
784 last_filter_len: Arc<AtomicUsize>,
785 ) -> Self {
786 Self {
787 schema,
788 metadata,
789 properties: ScannerProperties::default(),
790 update_calls,
791 last_filter_len,
792 }
793 }
794 }
795
796 impl RegionScanner for RecordingScanner {
797 fn name(&self) -> &str {
798 "RecordingScanner"
799 }
800
801 fn properties(&self) -> &ScannerProperties {
802 &self.properties
803 }
804
805 fn schema(&self) -> SchemaRef {
806 self.schema.clone()
807 }
808
809 fn metadata(&self) -> RegionMetadataRef {
810 self.metadata.clone()
811 }
812
813 fn prepare(&mut self, request: PrepareRequest) -> std::result::Result<(), BoxedError> {
814 self.properties.prepare(request);
815 Ok(())
816 }
817
818 fn scan_partition(
819 &self,
820 _ctx: &QueryScanContext,
821 _metrics_set: &ExecutionPlanMetricsSet,
822 _partition: usize,
823 ) -> std::result::Result<SendableRecordBatchStream, BoxedError> {
824 Ok(Box::pin(EmptyRecordBatchStream::new(self.schema.clone())))
825 }
826
827 fn has_predicate_without_region(&self) -> bool {
828 true
829 }
830
831 fn add_dyn_filter_to_predicate(
832 &mut self,
833 filter_exprs: Vec<Arc<dyn PhysicalExpr>>,
834 ) -> Vec<bool> {
835 self.update_calls.fetch_add(1, Ordering::Relaxed);
836 self.last_filter_len
837 .store(filter_exprs.len(), Ordering::Relaxed);
838 vec![true; filter_exprs.len()]
839 }
840
841 fn set_logical_region(&mut self, logical_region: bool) {
842 self.properties.set_logical_region(logical_region);
843 }
844
845 fn set_query_load_region_id(&mut self, region_id: store_api::storage::RegionId) {
846 self.properties.set_query_load_region_id(region_id);
847 }
848 }
849
850 impl DisplayAs for RecordingScanner {
851 fn fmt_as(&self, _t: DisplayFormatType, f: &mut fmt::Formatter<'_>) -> fmt::Result {
852 write!(f, "RecordingScanner")
853 }
854 }
855
856 fn build_query_load_region_scan(
857 query_load_region_id: Option<RegionId>,
858 ) -> Arc<dyn ExecutionPlan> {
859 build_region_scan(query_load_region_id, None)
860 }
861
862 fn build_query_stat_counter_region_scan(
863 counters: RegionQueryStatCounters,
864 ) -> Arc<dyn ExecutionPlan> {
865 build_region_scan(None, Some(counters))
866 }
867
868 fn build_region_scan(
869 query_load_region_id: Option<RegionId>,
870 query_stat_counters: Option<RegionQueryStatCounters>,
871 ) -> Arc<dyn ExecutionPlan> {
872 let schema = Arc::new(datatypes::schema::Schema::new(vec![ColumnSchema::new(
873 "ts",
874 ConcreteDataType::timestamp_millisecond_datatype(),
875 false,
876 )]));
877
878 let mut metadata_builder = RegionMetadataBuilder::new(RegionId::new(1024, 1));
879 metadata_builder
880 .push_column_metadata(ColumnMetadata {
881 column_schema: ColumnSchema::new(
882 "ts",
883 ConcreteDataType::timestamp_millisecond_datatype(),
884 false,
885 )
886 .with_time_index(true),
887 semantic_type: SemanticType::Timestamp,
888 column_id: 1,
889 })
890 .primary_key(vec![]);
891 let metadata = Arc::new(metadata_builder.build().unwrap());
892 let mut scanner = RecordingScanner::new(
893 schema,
894 metadata,
895 Arc::new(AtomicUsize::new(0)),
896 Arc::new(AtomicUsize::new(0)),
897 );
898 if let Some(region_id) = query_load_region_id {
899 scanner.set_query_load_region_id(region_id);
900 }
901 if let Some(counters) = query_stat_counters {
902 scanner.properties.set_query_stat_counters(counters);
903 }
904
905 Arc::new(RegionScanExec::new(Box::new(scanner), ScanRequest::default(), None).unwrap())
906 }
907
908 fn query_stat_counters_for_test() -> RegionQueryStatCounters {
909 RegionQueryStatCounters {
910 query_cpu_time: Arc::new(AtomicU64::new(0)),
911 query_scanned_bytes: Arc::new(AtomicU64::new(0)),
912 }
913 }
914
915 #[test]
916 fn query_load_region_id_ignores_scans_without_region_id() {
917 let query_load_region_id = RegionId::new(1024, 42);
918 let scan_without_region_id = build_query_load_region_scan(None);
919 let scan_with_region_id = build_query_load_region_scan(Some(query_load_region_id));
920 let on: JoinOn = vec![(
921 Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
922 Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
923 )];
924 let plan: Arc<dyn ExecutionPlan> = Arc::new(
925 HashJoinExec::try_new(
926 scan_without_region_id,
927 scan_with_region_id,
928 on,
929 None,
930 &JoinType::Inner,
931 None,
932 PartitionMode::CollectLeft,
933 NullEquality::NullEqualsNull,
934 false,
935 )
936 .unwrap(),
937 );
938
939 assert_eq!(
940 super::query_load_region_id(&plan),
941 Some(query_load_region_id.as_u64())
942 );
943 }
944
945 #[test]
946 fn query_stat_counters_returns_shared_counter_for_multi_scan_plan() {
947 let counters = query_stat_counters_for_test();
948 let left = build_query_stat_counter_region_scan(counters.clone());
949 let right = build_query_stat_counter_region_scan(counters.clone());
950 let on: JoinOn = vec![(
951 Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
952 Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
953 )];
954 let plan: Arc<dyn ExecutionPlan> = Arc::new(
955 HashJoinExec::try_new(
956 left,
957 right,
958 on,
959 None,
960 &JoinType::Inner,
961 None,
962 PartitionMode::CollectLeft,
963 NullEquality::NullEqualsNull,
964 false,
965 )
966 .unwrap(),
967 );
968
969 let actual = super::query_stat_counters(&plan).unwrap();
970 assert!(Arc::ptr_eq(
971 &actual.query_cpu_time,
972 &counters.query_cpu_time
973 ));
974 assert!(Arc::ptr_eq(
975 &actual.query_scanned_bytes,
976 &counters.query_scanned_bytes
977 ));
978 }
979
980 #[test]
981 fn query_stat_counters_ignores_mixed_counter_plan() {
982 let left = build_query_stat_counter_region_scan(query_stat_counters_for_test());
983 let right = build_query_stat_counter_region_scan(query_stat_counters_for_test());
984 let on: JoinOn = vec![(
985 Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
986 Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
987 )];
988 let plan: Arc<dyn ExecutionPlan> = Arc::new(
989 HashJoinExec::try_new(
990 left,
991 right,
992 on,
993 None,
994 &JoinType::Inner,
995 None,
996 PartitionMode::CollectLeft,
997 NullEquality::NullEqualsNull,
998 false,
999 )
1000 .unwrap(),
1001 );
1002
1003 assert!(super::query_stat_counters(&plan).is_none());
1004 }
1005
1006 async fn create_test_engine() -> QueryEngineRef {
1007 let catalog_manager = catalog::memory::new_memory_catalog_manager().unwrap();
1008 let req = RegisterTableRequest {
1009 catalog: DEFAULT_CATALOG_NAME.to_string(),
1010 schema: DEFAULT_SCHEMA_NAME.to_string(),
1011 table_name: NUMBERS_TABLE_NAME.to_string(),
1012 table_id: NUMBERS_TABLE_ID,
1013 table: NumbersTable::table(NUMBERS_TABLE_ID),
1014 };
1015 catalog_manager.register_table_sync(req).unwrap();
1016
1017 QueryEngineFactory::new(
1018 catalog_manager,
1019 None,
1020 None,
1021 None,
1022 None,
1023 false,
1024 QueryOptions::default(),
1025 )
1026 .query_engine()
1027 }
1028
1029 #[tokio::test]
1030 async fn test_sql_to_plan() {
1031 let engine = create_test_engine().await;
1032 let sql = "select sum(number) from numbers limit 20";
1033
1034 let stmt = QueryLanguageParser::parse_sql(sql, &QueryContext::arc()).unwrap();
1035 let plan = engine
1036 .planner()
1037 .plan(&stmt, QueryContext::arc())
1038 .await
1039 .unwrap();
1040
1041 assert_eq!(
1042 plan.to_string(),
1043 r#"Limit: skip=0, fetch=20
1044 Projection: sum(numbers.number)
1045 Aggregate: groupBy=[[]], aggr=[[sum(numbers.number)]]
1046 TableScan: numbers"#
1047 );
1048 }
1049
1050 #[tokio::test]
1051 async fn test_execute() {
1052 let engine = create_test_engine().await;
1053 let sql = "select sum(number) from numbers limit 20";
1054
1055 let stmt = QueryLanguageParser::parse_sql(sql, &QueryContext::arc()).unwrap();
1056 let plan = engine
1057 .planner()
1058 .plan(&stmt, QueryContext::arc())
1059 .await
1060 .unwrap();
1061
1062 let output = engine.execute(plan, QueryContext::arc()).await.unwrap();
1063
1064 match output.data {
1065 OutputData::Stream(recordbatch) => {
1066 let numbers = util::collect(recordbatch).await.unwrap();
1067 assert_eq!(1, numbers.len());
1068 assert_eq!(numbers[0].num_columns(), 1);
1069 assert_eq!(1, numbers[0].schema.num_columns());
1070 assert_eq!(
1071 "sum(numbers.number)",
1072 numbers[0].schema.column_schemas()[0].name
1073 );
1074
1075 let batch = &numbers[0];
1076 assert_eq!(1, batch.num_columns());
1077 assert_eq!(batch.column(0).len(), 1);
1078
1079 let expected = Arc::new(UInt64Array::from_iter_values([4950])) as ArrayRef;
1080 assert_eq!(batch.column(0), &expected);
1081 }
1082 _ => unreachable!(),
1083 }
1084 }
1085
1086 #[tokio::test]
1087 async fn test_read_table() {
1088 let engine = create_test_engine().await;
1089
1090 let engine = engine
1091 .as_any()
1092 .downcast_ref::<DatafusionQueryEngine>()
1093 .unwrap();
1094 let query_ctx = Arc::new(QueryContextBuilder::default().build());
1095 let table = engine
1096 .find_table(
1097 &ResolvedTableReference {
1098 catalog: "greptime".into(),
1099 schema: "public".into(),
1100 table: "numbers".into(),
1101 },
1102 &query_ctx,
1103 )
1104 .await
1105 .unwrap();
1106
1107 let df = engine.read_table(table).unwrap();
1108 let df = df
1109 .select_columns(&["number"])
1110 .unwrap()
1111 .filter(col("number").lt(lit(10)))
1112 .unwrap();
1113 let batches = df.collect().await.unwrap();
1114 assert_eq!(1, batches.len());
1115 let batch = &batches[0];
1116
1117 assert_eq!(1, batch.num_columns());
1118 assert_eq!(batch.column(0).len(), 10);
1119
1120 assert_eq!(
1121 Helper::try_into_vector(batch.column(0)).unwrap(),
1122 Arc::new(UInt32Vector::from_slice([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) as VectorRef
1123 );
1124 }
1125
1126 #[tokio::test]
1127 async fn test_describe() {
1128 let engine = create_test_engine().await;
1129 let sql = "select sum(number) from numbers limit 20";
1130
1131 let stmt = QueryLanguageParser::parse_sql(sql, &QueryContext::arc()).unwrap();
1132
1133 let plan = engine
1134 .planner()
1135 .plan(&stmt, QueryContext::arc())
1136 .await
1137 .unwrap();
1138
1139 let DescribeResult { logical_plan } =
1140 engine.describe(plan, QueryContext::arc()).await.unwrap();
1141
1142 let schema: Schema = logical_plan.schema().clone().try_into().unwrap();
1143
1144 assert_eq!(
1145 schema.column_schemas()[0],
1146 ColumnSchema::new(
1147 "sum(numbers.number)",
1148 ConcreteDataType::uint64_datatype(),
1149 true
1150 )
1151 );
1152 assert_eq!(
1153 "Limit: skip=0, fetch=20\n Projection: sum(numbers.number)\n Aggregate: groupBy=[[]], aggr=[[sum(numbers.number)]]\n TableScan: numbers",
1154 format!("{}", logical_plan.display_indent())
1155 );
1156 }
1157
1158 #[tokio::test]
1159 async fn test_topk_dynamic_filter_pushdown_reaches_region_scan() {
1160 let engine = create_test_engine().await;
1161 let engine = engine
1162 .as_any()
1163 .downcast_ref::<DatafusionQueryEngine>()
1164 .unwrap();
1165 let engine_ctx = engine.engine_context(QueryContext::arc());
1166 let state = engine_ctx.state();
1167
1168 let schema = Arc::new(datatypes::schema::Schema::new(vec![ColumnSchema::new(
1169 "ts",
1170 ConcreteDataType::timestamp_millisecond_datatype(),
1171 false,
1172 )]));
1173
1174 let mut metadata_builder = RegionMetadataBuilder::new(RegionId::new(1024, 1));
1175 metadata_builder
1176 .push_column_metadata(ColumnMetadata {
1177 column_schema: ColumnSchema::new(
1178 "ts",
1179 ConcreteDataType::timestamp_millisecond_datatype(),
1180 false,
1181 )
1182 .with_time_index(true),
1183 semantic_type: SemanticType::Timestamp,
1184 column_id: 1,
1185 })
1186 .primary_key(vec![]);
1187 let metadata = Arc::new(metadata_builder.build().unwrap());
1188
1189 let update_calls = Arc::new(AtomicUsize::new(0));
1190 let last_filter_len = Arc::new(AtomicUsize::new(0));
1191 let scanner = Box::new(RecordingScanner::new(
1192 schema,
1193 metadata,
1194 update_calls.clone(),
1195 last_filter_len.clone(),
1196 ));
1197 let scan = Arc::new(RegionScanExec::new(scanner, ScanRequest::default(), None).unwrap());
1198
1199 let sort_expr = PhysicalSortExpr {
1200 expr: Arc::new(Column::new("ts", 0)),
1201 options: SortOptions {
1202 descending: true,
1203 ..Default::default()
1204 },
1205 };
1206 let partition_ranges: Vec<Vec<PartitionRange>> = vec![vec![]];
1207 let mut plan: Arc<dyn ExecutionPlan> =
1208 Arc::new(PartSortExec::try_new(sort_expr, Some(3), partition_ranges, scan).unwrap());
1209
1210 for optimizer in state.physical_optimizers() {
1211 plan = optimizer.optimize(plan, state.config_options()).unwrap();
1212 }
1213
1214 assert!(update_calls.load(Ordering::Relaxed) > 0);
1215 assert!(last_filter_len.load(Ordering::Relaxed) > 0);
1216 }
1217
1218 #[tokio::test]
1219 async fn test_join_dynamic_filter_pushdown_reaches_region_scan() {
1220 let engine = create_test_engine().await;
1221 let engine = engine
1222 .as_any()
1223 .downcast_ref::<DatafusionQueryEngine>()
1224 .unwrap();
1225 let engine_ctx = engine.engine_context(QueryContext::arc());
1226 let state = engine_ctx.state();
1227
1228 assert!(
1229 state
1230 .config_options()
1231 .optimizer
1232 .enable_join_dynamic_filter_pushdown
1233 );
1234
1235 let schema = Arc::new(datatypes::schema::Schema::new(vec![ColumnSchema::new(
1236 "ts",
1237 ConcreteDataType::timestamp_millisecond_datatype(),
1238 false,
1239 )]));
1240
1241 let mut left_metadata_builder = RegionMetadataBuilder::new(RegionId::new(2048, 1));
1242 left_metadata_builder
1243 .push_column_metadata(ColumnMetadata {
1244 column_schema: ColumnSchema::new(
1245 "ts",
1246 ConcreteDataType::timestamp_millisecond_datatype(),
1247 false,
1248 )
1249 .with_time_index(true),
1250 semantic_type: SemanticType::Timestamp,
1251 column_id: 1,
1252 })
1253 .primary_key(vec![]);
1254 let left_metadata = Arc::new(left_metadata_builder.build().unwrap());
1255
1256 let mut right_metadata_builder = RegionMetadataBuilder::new(RegionId::new(2048, 2));
1257 right_metadata_builder
1258 .push_column_metadata(ColumnMetadata {
1259 column_schema: ColumnSchema::new(
1260 "ts",
1261 ConcreteDataType::timestamp_millisecond_datatype(),
1262 false,
1263 )
1264 .with_time_index(true),
1265 semantic_type: SemanticType::Timestamp,
1266 column_id: 1,
1267 })
1268 .primary_key(vec![]);
1269 let right_metadata = Arc::new(right_metadata_builder.build().unwrap());
1270
1271 let left_update_calls = Arc::new(AtomicUsize::new(0));
1272 let left_last_filter_len = Arc::new(AtomicUsize::new(0));
1273 let right_update_calls = Arc::new(AtomicUsize::new(0));
1274 let right_last_filter_len = Arc::new(AtomicUsize::new(0));
1275
1276 let left_scan = Arc::new(
1277 RegionScanExec::new(
1278 Box::new(RecordingScanner::new(
1279 schema.clone(),
1280 left_metadata,
1281 left_update_calls.clone(),
1282 left_last_filter_len.clone(),
1283 )),
1284 ScanRequest::default(),
1285 None,
1286 )
1287 .unwrap(),
1288 );
1289 let right_scan = Arc::new(
1290 RegionScanExec::new(
1291 Box::new(RecordingScanner::new(
1292 schema,
1293 right_metadata,
1294 right_update_calls.clone(),
1295 right_last_filter_len.clone(),
1296 )),
1297 ScanRequest::default(),
1298 None,
1299 )
1300 .unwrap(),
1301 );
1302
1303 let on: JoinOn = vec![(
1304 Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
1305 Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
1306 )];
1307
1308 let mut plan: Arc<dyn ExecutionPlan> = Arc::new(
1309 HashJoinExec::try_new(
1310 left_scan,
1311 right_scan,
1312 on,
1313 None,
1314 &JoinType::Inner,
1315 None,
1316 PartitionMode::CollectLeft,
1317 NullEquality::NullEqualsNull,
1318 false,
1319 )
1320 .unwrap(),
1321 );
1322
1323 for optimizer in state.physical_optimizers() {
1324 plan = optimizer.optimize(plan, state.config_options()).unwrap();
1325 }
1326
1327 assert!(left_update_calls.load(Ordering::Relaxed) > 0);
1328 assert_eq!(0, left_last_filter_len.load(Ordering::Relaxed));
1329 assert!(right_update_calls.load(Ordering::Relaxed) > 0);
1330 assert!(right_last_filter_len.load(Ordering::Relaxed) > 0);
1331 }
1332}