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;
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
104pub struct DatafusionQueryEngine {
105    state: Arc<QueryEngineState>,
106    plugins: Plugins,
107}
108
109impl DatafusionQueryEngine {
110    pub fn new(state: Arc<QueryEngineState>, plugins: Plugins) -> Self {
111        Self { state, plugins }
112    }
113
114    #[tracing::instrument(skip_all)]
115    async fn exec_query_plan(
116        &self,
117        plan: LogicalPlan,
118        query_ctx: QueryContextRef,
119    ) -> Result<Output> {
120        let mut ctx = self.engine_context(query_ctx.clone());
121        let plan = if let Some(receiver_injector) =
122            self.plugins.get::<RemoteDynFilterReceiverInjectorRef>()
123        {
124            receiver_injector.maybe_inject(plan, query_ctx.clone())
125        } else {
126            plan
127        };
128
129        // `create_physical_plan` will optimize logical plan internally
130        let physical_plan = self.create_physical_plan(&mut ctx, &plan).await?;
131        let physical_plan = self.optimize_physical_plan(&mut ctx, physical_plan)?;
132        let physical_plan = if let Some(wrapper) = self.plugins.get::<PhysicalPlanWrapperRef>() {
133            wrapper.wrap(physical_plan, query_ctx)
134        } else {
135            physical_plan
136        };
137
138        let stream = self.execute_stream(&ctx, &physical_plan)?;
139
140        Ok(Output::new(
141            OutputData::Stream(stream),
142            OutputMeta::new_with_plan(physical_plan),
143        ))
144    }
145
146    #[tracing::instrument(skip_all)]
147    async fn exec_dml_statement(
148        &self,
149        dml: DmlStatement,
150        query_ctx: QueryContextRef,
151    ) -> Result<Output> {
152        ensure!(
153            matches!(dml.op, WriteOp::Insert(_) | WriteOp::Delete),
154            UnsupportedExprSnafu {
155                name: format!("DML op {}", dml.op),
156            }
157        );
158
159        let _timer = QUERY_STAGE_ELAPSED
160            .with_label_values(&[dml.op.name()])
161            .start_timer();
162
163        let default_catalog = &query_ctx.current_catalog().to_owned();
164        let default_schema = &query_ctx.current_schema();
165        let table_name = dml.table_name.resolve(default_catalog, default_schema);
166        let table = self.find_table(&table_name, &query_ctx).await?;
167
168        let Output { data, meta } = self
169            .exec_query_plan((*dml.input).clone(), query_ctx.clone())
170            .await?;
171        let mut stream = match data {
172            OutputData::RecordBatches(batches) => batches.as_stream(),
173            OutputData::Stream(stream) => stream,
174            _ => unreachable!(),
175        };
176
177        let mut affected_rows = 0;
178        let mut insert_cost = 0;
179
180        while let Some(batch) = stream.next().await {
181            let batch = batch.context(CreateRecordBatchSnafu)?;
182            let column_vectors = batch
183                .column_vectors(&table_name.to_string(), table.schema())
184                .map_err(BoxedError::new)
185                .context(QueryExecutionSnafu)?;
186
187            match dml.op {
188                WriteOp::Insert(_) => {
189                    // We ignore the insert op.
190                    let output = self
191                        .insert(&table_name, column_vectors, query_ctx.clone())
192                        .await?;
193                    let (rows, cost) = output.extract_rows_and_cost();
194                    affected_rows += rows;
195                    insert_cost += cost;
196                }
197                WriteOp::Delete => {
198                    affected_rows += self
199                        .delete(&table_name, &table, column_vectors, query_ctx.clone())
200                        .await?;
201                }
202                _ => unreachable!("guarded by the 'ensure!' at the beginning"),
203            }
204        }
205        Ok(Output::new(
206            OutputData::AffectedRows(affected_rows),
207            OutputMeta::new(meta.plan, insert_cost),
208        ))
209    }
210
211    #[tracing::instrument(skip_all)]
212    async fn delete(
213        &self,
214        table_name: &ResolvedTableReference,
215        table: &TableRef,
216        column_vectors: HashMap<String, VectorRef>,
217        query_ctx: QueryContextRef,
218    ) -> Result<usize> {
219        let catalog_name = table_name.catalog.to_string();
220        let schema_name = table_name.schema.to_string();
221        let table_name = table_name.table.to_string();
222        let table_schema = table.schema();
223
224        ensure!(
225            !is_readonly_schema(&schema_name),
226            TableReadOnlySnafu { table: table_name }
227        );
228
229        let ts_column = table_schema
230            .timestamp_column()
231            .map(|x| &x.name)
232            .with_context(|| MissingTimestampColumnSnafu {
233                table_name: table_name.clone(),
234            })?;
235
236        let table_info = table.table_info();
237        let rowkey_columns = table_info
238            .meta
239            .row_key_column_names()
240            .collect::<Vec<&String>>();
241        let column_vectors = column_vectors
242            .into_iter()
243            .filter(|x| &x.0 == ts_column || rowkey_columns.contains(&&x.0))
244            .collect::<HashMap<_, _>>();
245
246        let request = DeleteRequest {
247            catalog_name,
248            schema_name,
249            table_name,
250            key_column_values: column_vectors,
251        };
252
253        self.state
254            .table_mutation_handler()
255            .context(MissingTableMutationHandlerSnafu)?
256            .delete(request, query_ctx)
257            .await
258            .context(TableMutationSnafu)
259    }
260
261    #[tracing::instrument(skip_all)]
262    async fn insert(
263        &self,
264        table_name: &ResolvedTableReference,
265        column_vectors: HashMap<String, VectorRef>,
266        query_ctx: QueryContextRef,
267    ) -> Result<Output> {
268        let catalog_name = table_name.catalog.to_string();
269        let schema_name = table_name.schema.to_string();
270        let table_name = table_name.table.to_string();
271
272        ensure!(
273            !is_readonly_schema(&schema_name),
274            TableReadOnlySnafu { table: table_name }
275        );
276
277        let request = InsertRequest {
278            catalog_name,
279            schema_name,
280            table_name,
281            columns_values: column_vectors,
282        };
283
284        self.state
285            .table_mutation_handler()
286            .context(MissingTableMutationHandlerSnafu)?
287            .insert(request, query_ctx)
288            .await
289            .context(TableMutationSnafu)
290    }
291
292    async fn find_table(
293        &self,
294        table_name: &ResolvedTableReference,
295        query_context: &QueryContextRef,
296    ) -> Result<TableRef> {
297        let catalog_name = table_name.catalog.as_ref();
298        let schema_name = table_name.schema.as_ref();
299        let table_name = table_name.table.as_ref();
300
301        self.state
302            .catalog_manager()
303            .table(catalog_name, schema_name, table_name, Some(query_context))
304            .await
305            .context(CatalogSnafu)?
306            .with_context(|| TableNotFoundSnafu { table: table_name })
307    }
308
309    #[tracing::instrument(skip_all)]
310    async fn create_physical_plan(
311        &self,
312        ctx: &mut QueryEngineContext,
313        logical_plan: &LogicalPlan,
314    ) -> Result<Arc<dyn ExecutionPlan>> {
315        /// Only print context on panic, to avoid cluttering logs.
316        ///
317        /// TODO(discord9): remove this once we catch the bug
318        #[derive(Debug)]
319        struct PanicLogger<'a> {
320            input_logical_plan: &'a LogicalPlan,
321            after_analyze: Option<LogicalPlan>,
322            after_optimize: Option<LogicalPlan>,
323            phy_plan: Option<Arc<dyn ExecutionPlan>>,
324        }
325        impl Drop for PanicLogger<'_> {
326            fn drop(&mut self) {
327                if std::thread::panicking() {
328                    common_telemetry::error!(
329                        "Panic while creating physical plan, input logical plan: {:?}, after analyze: {:?}, after optimize: {:?}, final physical plan: {:?}",
330                        self.input_logical_plan,
331                        self.after_analyze,
332                        self.after_optimize,
333                        self.phy_plan
334                    );
335                }
336            }
337        }
338
339        let mut logger = PanicLogger {
340            input_logical_plan: logical_plan,
341            after_analyze: None,
342            after_optimize: None,
343            phy_plan: None,
344        };
345
346        let _timer = metrics::CREATE_PHYSICAL_ELAPSED.start_timer();
347        let state = ctx.state();
348
349        common_telemetry::debug!("Create physical plan, input plan: {logical_plan}");
350
351        // special handle EXPLAIN plan
352        if matches!(logical_plan, DfLogicalPlan::Explain(_)) {
353            return state
354                .create_physical_plan(logical_plan)
355                .await
356                .map_err(Into::into);
357        }
358
359        // analyze first
360        let analyzed_plan = state.analyzer().execute_and_check(
361            logical_plan.clone(),
362            state.config_options(),
363            |_, _| {},
364        )?;
365
366        logger.after_analyze = Some(analyzed_plan.clone());
367
368        common_telemetry::debug!("Create physical plan, analyzed plan: {analyzed_plan}");
369
370        // skip optimize for MergeScan
371        let optimized_plan = if let DfLogicalPlan::Extension(ext) = &analyzed_plan
372            && ext.node.name() == MergeScanLogicalPlan::name()
373        {
374            analyzed_plan.clone()
375        } else {
376            state
377                .optimizer()
378                .optimize(analyzed_plan, state, |_, _| {})?
379        };
380
381        common_telemetry::debug!("Create physical plan, optimized plan: {optimized_plan}");
382        logger.after_optimize = Some(optimized_plan.clone());
383
384        let physical_plan = state
385            .query_planner()
386            .create_physical_plan(&optimized_plan, state)
387            .await?;
388
389        logger.phy_plan = Some(physical_plan.clone());
390        drop(logger);
391        Ok(physical_plan)
392    }
393
394    #[tracing::instrument(skip_all)]
395    fn optimize_physical_plan(
396        &self,
397        ctx: &mut QueryEngineContext,
398        plan: Arc<dyn ExecutionPlan>,
399    ) -> Result<Arc<dyn ExecutionPlan>> {
400        let _timer = metrics::OPTIMIZE_PHYSICAL_ELAPSED.start_timer();
401
402        // TODO(ruihang): `self.create_physical_plan()` already optimize the plan, check
403        // if we need to optimize it again here.
404        // let state = ctx.state();
405        // let config = state.config_options();
406
407        // skip optimize AnalyzeExec plan
408        let optimized_plan = if let Some(analyze_plan) = plan.as_any().downcast_ref::<AnalyzeExec>()
409        {
410            let format = if let Some(format) = ctx.query_ctx().explain_format()
411                && format.to_lowercase() == "json"
412            {
413                AnalyzeFormat::JSON
414            } else {
415                AnalyzeFormat::TEXT
416            };
417            // Sets the verbose flag of the query context.
418            // The MergeScanExec plan uses the verbose flag to determine whether to print the plan in verbose mode.
419            ctx.query_ctx().set_explain_verbose(analyze_plan.verbose());
420
421            Arc::new(DistAnalyzeExec::new(
422                analyze_plan.input().clone(),
423                analyze_plan.verbose(),
424                format,
425            ))
426            // let mut new_plan = analyze_plan.input().clone();
427            // for optimizer in state.physical_optimizers() {
428            //     new_plan = optimizer
429            //         .optimize(new_plan, config)
430            //         .context(DataFusionSnafu)?;
431            // }
432            // Arc::new(DistAnalyzeExec::new(new_plan))
433        } else {
434            plan
435            // let mut new_plan = plan;
436            // for optimizer in state.physical_optimizers() {
437            //     new_plan = optimizer
438            //         .optimize(new_plan, config)
439            //         .context(DataFusionSnafu)?;
440            // }
441            // new_plan
442        };
443
444        Ok(optimized_plan)
445    }
446}
447
448#[async_trait]
449impl QueryEngine for DatafusionQueryEngine {
450    fn as_any(&self) -> &dyn Any {
451        self
452    }
453
454    fn planner(&self) -> Arc<dyn LogicalPlanner> {
455        Arc::new(DfLogicalPlanner::new(self.state.clone()))
456    }
457
458    fn name(&self) -> &str {
459        "datafusion"
460    }
461
462    async fn describe(
463        &self,
464        plan: LogicalPlan,
465        _query_ctx: QueryContextRef,
466    ) -> Result<DescribeResult> {
467        Ok(DescribeResult { logical_plan: plan })
468    }
469
470    async fn execute(&self, plan: LogicalPlan, query_ctx: QueryContextRef) -> Result<Output> {
471        match plan {
472            LogicalPlan::Dml(dml) => self.exec_dml_statement(dml, query_ctx).await,
473            _ => self.exec_query_plan(plan, query_ctx).await,
474        }
475    }
476
477    /// Note in SQL queries, aggregate names are looked up using
478    /// lowercase unless the query uses quotes. For example,
479    ///
480    /// `SELECT MY_UDAF(x)...` will look for an aggregate named `"my_udaf"`
481    /// `SELECT "my_UDAF"(x)` will look for an aggregate named `"my_UDAF"`
482    ///
483    /// So it's better to make UDAF name lowercase when creating one.
484    fn register_aggregate_function(&self, func: AggregateUDF) {
485        self.state.register_aggr_function(func);
486    }
487
488    /// Register an scalar function.
489    /// Will override if the function with same name is already registered.
490    fn register_scalar_function(&self, func: ScalarFunctionFactory) {
491        self.state.register_scalar_function(func);
492    }
493
494    fn register_table_function(&self, func: Arc<TableFunction>) {
495        self.state.register_table_function(func);
496    }
497
498    fn register_window_function(&self, func: WindowUDF) {
499        self.state.register_window_function(func);
500    }
501
502    fn read_table(&self, table: TableRef) -> Result<DataFrame> {
503        self.state.read_table(table).map_err(Into::into)
504    }
505
506    fn engine_context(&self, query_ctx: QueryContextRef) -> QueryEngineContext {
507        let mut state = self.state.session_state();
508        state.config_mut().set_extension(query_ctx.clone());
509        state.config_mut().set_extension(self.state.clone());
510        // note that hints in "x-greptime-hints" is automatically parsed
511        // and set to query context's extension, so we can get it from query context.
512        if let Some(parallelism) = query_ctx.extension(QUERY_PARALLELISM_HINT) {
513            if let Ok(n) = parallelism.parse::<u64>() {
514                if n > 0 {
515                    let new_cfg = state.config().clone().with_target_partitions(n as usize);
516                    *state.config_mut() = new_cfg;
517                }
518            } else {
519                common_telemetry::warn!(
520                    "Failed to parse query_parallelism: {}, using default value",
521                    parallelism
522                );
523            }
524        }
525
526        // configure execution options
527        state.config_mut().options_mut().execution.time_zone =
528            Some(query_ctx.timezone().to_string());
529
530        // usually it's impossible to have both `set variable` set by sql client and
531        // hint in header by grpc client, so only need to deal with them separately
532        if query_ctx.configuration_parameter().allow_query_fallback() {
533            state
534                .config_mut()
535                .options_mut()
536                .extensions
537                .insert(DistPlannerOptions {
538                    allow_query_fallback: true,
539                });
540        } else if let Some(fallback) = query_ctx.extension(QUERY_FALLBACK_HINT) {
541            // also check the query context for fallback hint
542            // if it is set, we will enable the fallback
543            if fallback.to_lowercase().parse::<bool>().unwrap_or(false) {
544                state
545                    .config_mut()
546                    .options_mut()
547                    .extensions
548                    .insert(DistPlannerOptions {
549                        allow_query_fallback: true,
550                    });
551            }
552        }
553
554        state
555            .config_mut()
556            .options_mut()
557            .extensions
558            .insert(FunctionContext {
559                query_ctx: query_ctx.clone(),
560                state: self.engine_state().function_state(),
561            });
562
563        // Carry scheduled Flow time through ConfigOptions.extensions so that
564        // the distributed plan analyzer can read it during expression
565        // simplification (preventing wall-clock constant-folding of `now()`).
566        state
567            .config_mut()
568            .options_mut()
569            .extensions
570            .insert(ScheduledTimeExtension {
571                scheduled_time: crate::options::scheduled_time_from_ctx(&query_ctx),
572            });
573
574        let config_options = state.config_options().clone();
575        let _ = state
576            .execution_props_mut()
577            .config_options
578            .insert(config_options);
579
580        // Apply scheduled time from query context if present, so that `now()` /
581        // `current_timestamp()` functions evaluate against the logical scheduled time
582        // rather than wall-clock.
583        match crate::options::parse_scheduled_time_datetime(&query_ctx.extensions()) {
584            Ok(Some(scheduled_rt)) => {
585                state.execution_props_mut().query_execution_start_time = Some(scheduled_rt);
586            }
587            Ok(None) => {}
588            Err(err) => {
589                common_telemetry::warn!(err; "Ignoring invalid scheduled time query extension");
590            }
591        }
592
593        QueryEngineContext::new(state, query_ctx)
594    }
595
596    fn engine_state(&self) -> &QueryEngineState {
597        &self.state
598    }
599}
600
601impl QueryExecutor for DatafusionQueryEngine {
602    #[tracing::instrument(skip_all)]
603    fn execute_stream(
604        &self,
605        ctx: &QueryEngineContext,
606        plan: &Arc<dyn ExecutionPlan>,
607    ) -> Result<SendableRecordBatchStream> {
608        let query_ctx = ctx.query_ctx();
609        let explain_verbose = query_ctx.explain_verbose();
610        let should_collect_region_watermark =
611            should_collect_region_watermark_from_query_ctx(&query_ctx)?;
612        let output_partitions = plan.properties().output_partitioning().partition_count();
613        if explain_verbose {
614            common_telemetry::info!("Executing query plan, output_partitions: {output_partitions}");
615        }
616
617        let exec_timer = metrics::EXEC_PLAN_ELAPSED.start_timer();
618        let task_ctx = ctx.build_task_ctx();
619        let span = Span::current();
620
621        match plan.properties().output_partitioning().partition_count() {
622            0 => {
623                let schema = Arc::new(
624                    Schema::try_from(plan.schema())
625                        .map_err(BoxedError::new)
626                        .context(QueryExecutionSnafu)?,
627                );
628                Ok(Box::pin(EmptyRecordBatchStream::new(schema)))
629            }
630            1 => {
631                let df_stream = plan.execute(0, task_ctx)?;
632                let mut stream = RecordBatchStreamAdapter::try_new_with_span(df_stream, span)
633                    .context(error::ConvertDfRecordBatchStreamSnafu)
634                    .map_err(BoxedError::new)
635                    .context(QueryExecutionSnafu)?;
636                stream.set_metrics2(plan.clone());
637                stream.set_query_load_region_id(query_load_region_id(plan));
638                stream.set_explain_verbose(explain_verbose);
639                let stream = OnDone::new(Box::pin(stream), move || {
640                    let exec_cost = exec_timer.stop_and_record();
641                    if explain_verbose {
642                        common_telemetry::info!(
643                            "DatafusionQueryEngine execute 1 stream, cost: {:?}s",
644                            exec_cost,
645                        );
646                    }
647                });
648                Ok(maybe_attach_region_watermark_metrics(
649                    Box::pin(stream),
650                    plan.clone(),
651                    should_collect_region_watermark,
652                ))
653            }
654            _ => {
655                // merge into a single partition
656                let merged_plan = CoalescePartitionsExec::new(plan.clone());
657                // CoalescePartitionsExec must produce a single partition
658                assert_eq!(
659                    1,
660                    merged_plan
661                        .properties()
662                        .output_partitioning()
663                        .partition_count()
664                );
665                let df_stream = merged_plan.execute(0, task_ctx)?;
666                let mut stream = RecordBatchStreamAdapter::try_new_with_span(df_stream, span)
667                    .context(error::ConvertDfRecordBatchStreamSnafu)
668                    .map_err(BoxedError::new)
669                    .context(QueryExecutionSnafu)?;
670                stream.set_metrics2(plan.clone());
671                stream.set_query_load_region_id(query_load_region_id(plan));
672                stream.set_explain_verbose(explain_verbose);
673                let stream = OnDone::new(Box::pin(stream), move || {
674                    let exec_cost = exec_timer.stop_and_record();
675                    if explain_verbose {
676                        common_telemetry::info!(
677                            "DatafusionQueryEngine execute {output_partitions} stream, cost: {:?}s",
678                            exec_cost
679                        );
680                    }
681                });
682                Ok(maybe_attach_region_watermark_metrics(
683                    Box::pin(stream),
684                    plan.clone(),
685                    should_collect_region_watermark,
686                ))
687            }
688        }
689    }
690}
691
692#[cfg(test)]
693mod tests {
694    use std::fmt;
695    use std::sync::Arc;
696    use std::sync::atomic::{AtomicUsize, Ordering};
697
698    use api::v1::SemanticType;
699    use arrow::array::{ArrayRef, UInt64Array};
700    use arrow_schema::SortOptions;
701    use catalog::RegisterTableRequest;
702    use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, NUMBERS_TABLE_ID};
703    use common_error::ext::BoxedError;
704    use common_recordbatch::{EmptyRecordBatchStream, SendableRecordBatchStream, util};
705    use datafusion::physical_plan::display::{DisplayAs, DisplayFormatType};
706    use datafusion::physical_plan::expressions::PhysicalSortExpr;
707    use datafusion::physical_plan::joins::{HashJoinExec, JoinOn, PartitionMode};
708    use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
709    use datafusion::physical_plan::{ExecutionPlan, PhysicalExpr};
710    use datafusion::prelude::{col, lit};
711    use datafusion_common::{JoinType, NullEquality};
712    use datafusion_physical_expr::expressions::Column;
713    use datatypes::prelude::ConcreteDataType;
714    use datatypes::schema::{ColumnSchema, SchemaRef};
715    use datatypes::vectors::{Helper, UInt32Vector, VectorRef};
716    use session::context::{QueryContext, QueryContextBuilder};
717    use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder, RegionMetadataRef};
718    use store_api::region_engine::{
719        PartitionRange, PrepareRequest, QueryScanContext, RegionScanner, ScannerProperties,
720    };
721    use store_api::storage::{RegionId, ScanRequest};
722    use table::table::numbers::{NUMBERS_TABLE_NAME, NumbersTable};
723    use table::table::scan::RegionScanExec;
724
725    use super::*;
726    use crate::options::QueryOptions;
727    use crate::parser::QueryLanguageParser;
728    use crate::part_sort::PartSortExec;
729    use crate::query_engine::{QueryEngineFactory, QueryEngineRef};
730
731    #[derive(Debug)]
732    struct RecordingScanner {
733        schema: SchemaRef,
734        metadata: RegionMetadataRef,
735        properties: ScannerProperties,
736        update_calls: Arc<AtomicUsize>,
737        last_filter_len: Arc<AtomicUsize>,
738    }
739
740    impl RecordingScanner {
741        fn new(
742            schema: SchemaRef,
743            metadata: RegionMetadataRef,
744            update_calls: Arc<AtomicUsize>,
745            last_filter_len: Arc<AtomicUsize>,
746        ) -> Self {
747            Self {
748                schema,
749                metadata,
750                properties: ScannerProperties::default(),
751                update_calls,
752                last_filter_len,
753            }
754        }
755    }
756
757    impl RegionScanner for RecordingScanner {
758        fn name(&self) -> &str {
759            "RecordingScanner"
760        }
761
762        fn properties(&self) -> &ScannerProperties {
763            &self.properties
764        }
765
766        fn schema(&self) -> SchemaRef {
767            self.schema.clone()
768        }
769
770        fn metadata(&self) -> RegionMetadataRef {
771            self.metadata.clone()
772        }
773
774        fn prepare(&mut self, request: PrepareRequest) -> std::result::Result<(), BoxedError> {
775            self.properties.prepare(request);
776            Ok(())
777        }
778
779        fn scan_partition(
780            &self,
781            _ctx: &QueryScanContext,
782            _metrics_set: &ExecutionPlanMetricsSet,
783            _partition: usize,
784        ) -> std::result::Result<SendableRecordBatchStream, BoxedError> {
785            Ok(Box::pin(EmptyRecordBatchStream::new(self.schema.clone())))
786        }
787
788        fn has_predicate_without_region(&self) -> bool {
789            true
790        }
791
792        fn add_dyn_filter_to_predicate(
793            &mut self,
794            filter_exprs: Vec<Arc<dyn PhysicalExpr>>,
795        ) -> Vec<bool> {
796            self.update_calls.fetch_add(1, Ordering::Relaxed);
797            self.last_filter_len
798                .store(filter_exprs.len(), Ordering::Relaxed);
799            vec![true; filter_exprs.len()]
800        }
801
802        fn set_logical_region(&mut self, logical_region: bool) {
803            self.properties.set_logical_region(logical_region);
804        }
805
806        fn set_query_load_region_id(&mut self, region_id: store_api::storage::RegionId) {
807            self.properties.set_query_load_region_id(region_id);
808        }
809    }
810
811    impl DisplayAs for RecordingScanner {
812        fn fmt_as(&self, _t: DisplayFormatType, f: &mut fmt::Formatter<'_>) -> fmt::Result {
813            write!(f, "RecordingScanner")
814        }
815    }
816
817    fn build_query_load_region_scan(
818        query_load_region_id: Option<RegionId>,
819    ) -> Arc<dyn ExecutionPlan> {
820        let schema = Arc::new(datatypes::schema::Schema::new(vec![ColumnSchema::new(
821            "ts",
822            ConcreteDataType::timestamp_millisecond_datatype(),
823            false,
824        )]));
825
826        let mut metadata_builder = RegionMetadataBuilder::new(RegionId::new(1024, 1));
827        metadata_builder
828            .push_column_metadata(ColumnMetadata {
829                column_schema: ColumnSchema::new(
830                    "ts",
831                    ConcreteDataType::timestamp_millisecond_datatype(),
832                    false,
833                )
834                .with_time_index(true),
835                semantic_type: SemanticType::Timestamp,
836                column_id: 1,
837            })
838            .primary_key(vec![]);
839        let metadata = Arc::new(metadata_builder.build().unwrap());
840        let mut scanner = RecordingScanner::new(
841            schema,
842            metadata,
843            Arc::new(AtomicUsize::new(0)),
844            Arc::new(AtomicUsize::new(0)),
845        );
846        if let Some(region_id) = query_load_region_id {
847            scanner.set_query_load_region_id(region_id);
848        }
849
850        Arc::new(RegionScanExec::new(Box::new(scanner), ScanRequest::default(), None).unwrap())
851    }
852
853    #[test]
854    fn query_load_region_id_ignores_scans_without_region_id() {
855        let query_load_region_id = RegionId::new(1024, 42);
856        let scan_without_region_id = build_query_load_region_scan(None);
857        let scan_with_region_id = build_query_load_region_scan(Some(query_load_region_id));
858        let on: JoinOn = vec![(
859            Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
860            Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
861        )];
862        let plan: Arc<dyn ExecutionPlan> = Arc::new(
863            HashJoinExec::try_new(
864                scan_without_region_id,
865                scan_with_region_id,
866                on,
867                None,
868                &JoinType::Inner,
869                None,
870                PartitionMode::CollectLeft,
871                NullEquality::NullEqualsNull,
872                false,
873            )
874            .unwrap(),
875        );
876
877        assert_eq!(
878            super::query_load_region_id(&plan),
879            Some(query_load_region_id.as_u64())
880        );
881    }
882
883    async fn create_test_engine() -> QueryEngineRef {
884        let catalog_manager = catalog::memory::new_memory_catalog_manager().unwrap();
885        let req = RegisterTableRequest {
886            catalog: DEFAULT_CATALOG_NAME.to_string(),
887            schema: DEFAULT_SCHEMA_NAME.to_string(),
888            table_name: NUMBERS_TABLE_NAME.to_string(),
889            table_id: NUMBERS_TABLE_ID,
890            table: NumbersTable::table(NUMBERS_TABLE_ID),
891        };
892        catalog_manager.register_table_sync(req).unwrap();
893
894        QueryEngineFactory::new(
895            catalog_manager,
896            None,
897            None,
898            None,
899            None,
900            false,
901            QueryOptions::default(),
902        )
903        .query_engine()
904    }
905
906    #[tokio::test]
907    async fn test_sql_to_plan() {
908        let engine = create_test_engine().await;
909        let sql = "select sum(number) from numbers limit 20";
910
911        let stmt = QueryLanguageParser::parse_sql(sql, &QueryContext::arc()).unwrap();
912        let plan = engine
913            .planner()
914            .plan(&stmt, QueryContext::arc())
915            .await
916            .unwrap();
917
918        assert_eq!(
919            plan.to_string(),
920            r#"Limit: skip=0, fetch=20
921  Projection: sum(numbers.number)
922    Aggregate: groupBy=[[]], aggr=[[sum(numbers.number)]]
923      TableScan: numbers"#
924        );
925    }
926
927    #[tokio::test]
928    async fn test_execute() {
929        let engine = create_test_engine().await;
930        let sql = "select sum(number) from numbers limit 20";
931
932        let stmt = QueryLanguageParser::parse_sql(sql, &QueryContext::arc()).unwrap();
933        let plan = engine
934            .planner()
935            .plan(&stmt, QueryContext::arc())
936            .await
937            .unwrap();
938
939        let output = engine.execute(plan, QueryContext::arc()).await.unwrap();
940
941        match output.data {
942            OutputData::Stream(recordbatch) => {
943                let numbers = util::collect(recordbatch).await.unwrap();
944                assert_eq!(1, numbers.len());
945                assert_eq!(numbers[0].num_columns(), 1);
946                assert_eq!(1, numbers[0].schema.num_columns());
947                assert_eq!(
948                    "sum(numbers.number)",
949                    numbers[0].schema.column_schemas()[0].name
950                );
951
952                let batch = &numbers[0];
953                assert_eq!(1, batch.num_columns());
954                assert_eq!(batch.column(0).len(), 1);
955
956                let expected = Arc::new(UInt64Array::from_iter_values([4950])) as ArrayRef;
957                assert_eq!(batch.column(0), &expected);
958            }
959            _ => unreachable!(),
960        }
961    }
962
963    #[tokio::test]
964    async fn test_read_table() {
965        let engine = create_test_engine().await;
966
967        let engine = engine
968            .as_any()
969            .downcast_ref::<DatafusionQueryEngine>()
970            .unwrap();
971        let query_ctx = Arc::new(QueryContextBuilder::default().build());
972        let table = engine
973            .find_table(
974                &ResolvedTableReference {
975                    catalog: "greptime".into(),
976                    schema: "public".into(),
977                    table: "numbers".into(),
978                },
979                &query_ctx,
980            )
981            .await
982            .unwrap();
983
984        let df = engine.read_table(table).unwrap();
985        let df = df
986            .select_columns(&["number"])
987            .unwrap()
988            .filter(col("number").lt(lit(10)))
989            .unwrap();
990        let batches = df.collect().await.unwrap();
991        assert_eq!(1, batches.len());
992        let batch = &batches[0];
993
994        assert_eq!(1, batch.num_columns());
995        assert_eq!(batch.column(0).len(), 10);
996
997        assert_eq!(
998            Helper::try_into_vector(batch.column(0)).unwrap(),
999            Arc::new(UInt32Vector::from_slice([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) as VectorRef
1000        );
1001    }
1002
1003    #[tokio::test]
1004    async fn test_describe() {
1005        let engine = create_test_engine().await;
1006        let sql = "select sum(number) from numbers limit 20";
1007
1008        let stmt = QueryLanguageParser::parse_sql(sql, &QueryContext::arc()).unwrap();
1009
1010        let plan = engine
1011            .planner()
1012            .plan(&stmt, QueryContext::arc())
1013            .await
1014            .unwrap();
1015
1016        let DescribeResult { logical_plan } =
1017            engine.describe(plan, QueryContext::arc()).await.unwrap();
1018
1019        let schema: Schema = logical_plan.schema().clone().try_into().unwrap();
1020
1021        assert_eq!(
1022            schema.column_schemas()[0],
1023            ColumnSchema::new(
1024                "sum(numbers.number)",
1025                ConcreteDataType::uint64_datatype(),
1026                true
1027            )
1028        );
1029        assert_eq!(
1030            "Limit: skip=0, fetch=20\n  Projection: sum(numbers.number)\n    Aggregate: groupBy=[[]], aggr=[[sum(numbers.number)]]\n      TableScan: numbers",
1031            format!("{}", logical_plan.display_indent())
1032        );
1033    }
1034
1035    #[tokio::test]
1036    async fn test_topk_dynamic_filter_pushdown_reaches_region_scan() {
1037        let engine = create_test_engine().await;
1038        let engine = engine
1039            .as_any()
1040            .downcast_ref::<DatafusionQueryEngine>()
1041            .unwrap();
1042        let engine_ctx = engine.engine_context(QueryContext::arc());
1043        let state = engine_ctx.state();
1044
1045        let schema = Arc::new(datatypes::schema::Schema::new(vec![ColumnSchema::new(
1046            "ts",
1047            ConcreteDataType::timestamp_millisecond_datatype(),
1048            false,
1049        )]));
1050
1051        let mut metadata_builder = RegionMetadataBuilder::new(RegionId::new(1024, 1));
1052        metadata_builder
1053            .push_column_metadata(ColumnMetadata {
1054                column_schema: ColumnSchema::new(
1055                    "ts",
1056                    ConcreteDataType::timestamp_millisecond_datatype(),
1057                    false,
1058                )
1059                .with_time_index(true),
1060                semantic_type: SemanticType::Timestamp,
1061                column_id: 1,
1062            })
1063            .primary_key(vec![]);
1064        let metadata = Arc::new(metadata_builder.build().unwrap());
1065
1066        let update_calls = Arc::new(AtomicUsize::new(0));
1067        let last_filter_len = Arc::new(AtomicUsize::new(0));
1068        let scanner = Box::new(RecordingScanner::new(
1069            schema,
1070            metadata,
1071            update_calls.clone(),
1072            last_filter_len.clone(),
1073        ));
1074        let scan = Arc::new(RegionScanExec::new(scanner, ScanRequest::default(), None).unwrap());
1075
1076        let sort_expr = PhysicalSortExpr {
1077            expr: Arc::new(Column::new("ts", 0)),
1078            options: SortOptions {
1079                descending: true,
1080                ..Default::default()
1081            },
1082        };
1083        let partition_ranges: Vec<Vec<PartitionRange>> = vec![vec![]];
1084        let mut plan: Arc<dyn ExecutionPlan> =
1085            Arc::new(PartSortExec::try_new(sort_expr, Some(3), partition_ranges, scan).unwrap());
1086
1087        for optimizer in state.physical_optimizers() {
1088            plan = optimizer.optimize(plan, state.config_options()).unwrap();
1089        }
1090
1091        assert!(update_calls.load(Ordering::Relaxed) > 0);
1092        assert!(last_filter_len.load(Ordering::Relaxed) > 0);
1093    }
1094
1095    #[tokio::test]
1096    async fn test_join_dynamic_filter_pushdown_reaches_region_scan() {
1097        let engine = create_test_engine().await;
1098        let engine = engine
1099            .as_any()
1100            .downcast_ref::<DatafusionQueryEngine>()
1101            .unwrap();
1102        let engine_ctx = engine.engine_context(QueryContext::arc());
1103        let state = engine_ctx.state();
1104
1105        assert!(
1106            state
1107                .config_options()
1108                .optimizer
1109                .enable_join_dynamic_filter_pushdown
1110        );
1111
1112        let schema = Arc::new(datatypes::schema::Schema::new(vec![ColumnSchema::new(
1113            "ts",
1114            ConcreteDataType::timestamp_millisecond_datatype(),
1115            false,
1116        )]));
1117
1118        let mut left_metadata_builder = RegionMetadataBuilder::new(RegionId::new(2048, 1));
1119        left_metadata_builder
1120            .push_column_metadata(ColumnMetadata {
1121                column_schema: ColumnSchema::new(
1122                    "ts",
1123                    ConcreteDataType::timestamp_millisecond_datatype(),
1124                    false,
1125                )
1126                .with_time_index(true),
1127                semantic_type: SemanticType::Timestamp,
1128                column_id: 1,
1129            })
1130            .primary_key(vec![]);
1131        let left_metadata = Arc::new(left_metadata_builder.build().unwrap());
1132
1133        let mut right_metadata_builder = RegionMetadataBuilder::new(RegionId::new(2048, 2));
1134        right_metadata_builder
1135            .push_column_metadata(ColumnMetadata {
1136                column_schema: ColumnSchema::new(
1137                    "ts",
1138                    ConcreteDataType::timestamp_millisecond_datatype(),
1139                    false,
1140                )
1141                .with_time_index(true),
1142                semantic_type: SemanticType::Timestamp,
1143                column_id: 1,
1144            })
1145            .primary_key(vec![]);
1146        let right_metadata = Arc::new(right_metadata_builder.build().unwrap());
1147
1148        let left_update_calls = Arc::new(AtomicUsize::new(0));
1149        let left_last_filter_len = Arc::new(AtomicUsize::new(0));
1150        let right_update_calls = Arc::new(AtomicUsize::new(0));
1151        let right_last_filter_len = Arc::new(AtomicUsize::new(0));
1152
1153        let left_scan = Arc::new(
1154            RegionScanExec::new(
1155                Box::new(RecordingScanner::new(
1156                    schema.clone(),
1157                    left_metadata,
1158                    left_update_calls.clone(),
1159                    left_last_filter_len.clone(),
1160                )),
1161                ScanRequest::default(),
1162                None,
1163            )
1164            .unwrap(),
1165        );
1166        let right_scan = Arc::new(
1167            RegionScanExec::new(
1168                Box::new(RecordingScanner::new(
1169                    schema,
1170                    right_metadata,
1171                    right_update_calls.clone(),
1172                    right_last_filter_len.clone(),
1173                )),
1174                ScanRequest::default(),
1175                None,
1176            )
1177            .unwrap(),
1178        );
1179
1180        let on: JoinOn = vec![(
1181            Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
1182            Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
1183        )];
1184
1185        let mut plan: Arc<dyn ExecutionPlan> = Arc::new(
1186            HashJoinExec::try_new(
1187                left_scan,
1188                right_scan,
1189                on,
1190                None,
1191                &JoinType::Inner,
1192                None,
1193                PartitionMode::CollectLeft,
1194                NullEquality::NullEqualsNull,
1195                false,
1196            )
1197            .unwrap(),
1198        );
1199
1200        for optimizer in state.physical_optimizers() {
1201            plan = optimizer.optimize(plan, state.config_options()).unwrap();
1202        }
1203
1204        assert!(left_update_calls.load(Ordering::Relaxed) > 0);
1205        assert_eq!(0, left_last_filter_len.load(Ordering::Relaxed));
1206        assert!(right_update_calls.load(Ordering::Relaxed) > 0);
1207        assert!(right_last_filter_len.load(Ordering::Relaxed) > 0);
1208    }
1209}