Skip to main content

query/
datafusion.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Planner, QueryEngine implementations based on DataFusion.
16
17mod 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
76/// Query parallelism hint key.
77/// This hint can be set in the query context to control the parallelism of the query execution.
78pub const QUERY_PARALLELISM_HINT: &str = "query_parallelism";
79
80/// Whether to fallback to the original plan when failed to push down.
81pub 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
104// Finds the region-owned query statistic counters from the local datanode scan plan.
105//
106// Unlike the Prometheus read-load reporting in `MergeScanExec`, the heartbeat
107// counters must be updated before metrics leave the datanode process. The
108// `RecordBatchStreamAdapter` that resolves `RecordBatchMetrics` does not know
109// the owning `MitoRegion`, so we extract the counters from `RegionScanExec` and
110// pass them to the adapter. If a plan contains scans from different regions,
111// return `None` to avoid charging the whole plan metrics to one region.
112fn 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        // `create_physical_plan` will optimize logical plan internally
167        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                    // We ignore the insert op.
227                    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        /// Only print context on panic, to avoid cluttering logs.
353        ///
354        /// TODO(discord9): remove this once we catch the bug
355        #[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        // special handle EXPLAIN plan
389        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        // analyze first
397        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        // skip optimize for MergeScan
408        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        // TODO(ruihang): `self.create_physical_plan()` already optimize the plan, check
440        // if we need to optimize it again here.
441        // let state = ctx.state();
442        // let config = state.config_options();
443
444        // skip optimize AnalyzeExec plan
445        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            // Sets the verbose flag of the query context.
455            // The MergeScanExec plan uses the verbose flag to determine whether to print the plan in verbose mode.
456            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            // let mut new_plan = analyze_plan.input().clone();
464            // for optimizer in state.physical_optimizers() {
465            //     new_plan = optimizer
466            //         .optimize(new_plan, config)
467            //         .context(DataFusionSnafu)?;
468            // }
469            // Arc::new(DistAnalyzeExec::new(new_plan))
470        } else {
471            plan
472            // let mut new_plan = plan;
473            // for optimizer in state.physical_optimizers() {
474            //     new_plan = optimizer
475            //         .optimize(new_plan, config)
476            //         .context(DataFusionSnafu)?;
477            // }
478            // new_plan
479        };
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    /// Note in SQL queries, aggregate names are looked up using
515    /// lowercase unless the query uses quotes. For example,
516    ///
517    /// `SELECT MY_UDAF(x)...` will look for an aggregate named `"my_udaf"`
518    /// `SELECT "my_UDAF"(x)` will look for an aggregate named `"my_UDAF"`
519    ///
520    /// So it's better to make UDAF name lowercase when creating one.
521    fn register_aggregate_function(&self, func: AggregateUDF) {
522        self.state.register_aggr_function(func);
523    }
524
525    /// Register an scalar function.
526    /// Will override if the function with same name is already registered.
527    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        // note that hints in "x-greptime-hints" is automatically parsed
548        // and set to query context's extension, so we can get it from query context.
549        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        // configure execution options
564        state.config_mut().options_mut().execution.time_zone =
565            Some(query_ctx.timezone().to_string());
566
567        // usually it's impossible to have both `set variable` set by sql client and
568        // hint in header by grpc client, so only need to deal with them separately
569        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            // also check the query context for fallback hint
579            // if it is set, we will enable the fallback
580            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        // Carry scheduled Flow time through ConfigOptions.extensions so that
601        // the distributed plan analyzer can read it during expression
602        // simplification (preventing wall-clock constant-folding of `now()`).
603        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        // Apply scheduled time from query context if present, so that `now()` /
618        // `current_timestamp()` functions evaluate against the logical scheduled time
619        // rather than wall-clock.
620        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                // merge into a single partition
694                let merged_plan = CoalescePartitionsExec::new(plan.clone());
695                // CoalescePartitionsExec must produce a single partition
696                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}