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 pg_oid_alias_expr_planner;
20mod planner;
21
22use std::any::Any;
23use std::collections::HashMap;
24use std::sync::Arc;
25
26use async_trait::async_trait;
27use common_base::Plugins;
28use common_catalog::consts::is_readonly_table;
29use common_error::ext::BoxedError;
30use common_function::function::FunctionContext;
31use common_function::function_factory::ScalarFunctionFactory;
32use common_query::{Output, OutputData, OutputMeta};
33use common_recordbatch::adapter::{RecordBatchStreamAdapter, RegionQueryStatCounters};
34use common_recordbatch::{EmptyRecordBatchStream, SendableRecordBatchStream};
35use common_telemetry::tracing;
36use datafusion::catalog::TableFunction;
37use datafusion::dataframe::DataFrame;
38use datafusion::physical_plan::ExecutionPlan;
39use datafusion::physical_plan::analyze::AnalyzeExec;
40use datafusion::physical_plan::coalesce_partitions::CoalescePartitionsExec;
41use datafusion_common::ResolvedTableReference;
42use datafusion_expr::{
43    AggregateUDF, DmlStatement, LogicalPlan as DfLogicalPlan, LogicalPlan, WindowUDF, WriteOp,
44};
45use datatypes::prelude::VectorRef;
46use datatypes::schema::Schema;
47use futures_util::StreamExt;
48use session::context::QueryContextRef;
49use snafu::{OptionExt, ResultExt, ensure};
50use sqlparser::ast::AnalyzeFormat;
51use table::TableRef;
52use table::requests::{DeleteRequest, InsertRequest};
53use table::table::scan::{REGION_SCAN_EXEC_NAME, RegionScanExec};
54use tracing::Span;
55
56use crate::analyze::DistAnalyzeExec;
57pub use crate::datafusion::planner::DfContextProviderAdapter;
58use crate::dist_plan::{
59    DistPlannerOptions, MergeScanLogicalPlan, RemoteDynFilterReceiverInjectorRef,
60};
61use crate::error::{
62    CatalogSnafu, CreateRecordBatchSnafu, MissingTableMutationHandlerSnafu,
63    MissingTimestampColumnSnafu, QueryExecutionSnafu, Result, TableMutationSnafu,
64    TableNotFoundSnafu, TableReadOnlySnafu, UnsupportedExprSnafu,
65};
66use crate::executor::QueryExecutor;
67use crate::metrics::{
68    OnDone, QUERY_STAGE_ELAPSED, maybe_attach_region_watermark_metrics,
69    should_collect_region_watermark_from_query_ctx,
70};
71use crate::optimizer::expr_depth;
72use crate::options::ScheduledTimeExtension;
73use crate::physical_wrapper::PhysicalPlanWrapperRef;
74use crate::planner::{DfLogicalPlanner, LogicalPlanner};
75use crate::query_engine::{DescribeResult, QueryEngineContext, QueryEngineState};
76use crate::{QueryEngine, metrics};
77
78/// Query parallelism hint key.
79/// This hint can be set in the query context to control the parallelism of the query execution.
80pub const QUERY_PARALLELISM_HINT: &str = "query_parallelism";
81
82/// Whether to fallback to the original plan when failed to push down.
83pub const QUERY_FALLBACK_HINT: &str = "query_fallback";
84
85fn query_load_region_id(plan: &Arc<dyn ExecutionPlan>) -> Option<u64> {
86    let mut region_id = None;
87    let mut stack = vec![plan.clone()];
88
89    while let Some(plan) = stack.pop() {
90        if plan.name() == REGION_SCAN_EXEC_NAME
91            && let Some(scan) = plan.downcast_ref::<RegionScanExec>()
92            && let Some(scan_region_id) = scan.query_load_region_id()
93        {
94            match region_id {
95                Some(region_id) if region_id != scan_region_id => return None,
96                Some(_) => {}
97                None => region_id = Some(scan_region_id),
98            }
99        }
100        stack.extend(plan.children().into_iter().cloned());
101    }
102
103    region_id
104}
105
106// Finds the region-owned query statistic counters from the local datanode scan plan.
107//
108// Unlike the Prometheus read-load reporting in `MergeScanExec`, the heartbeat
109// counters must be updated before metrics leave the datanode process. The
110// `RecordBatchStreamAdapter` that resolves `RecordBatchMetrics` does not know
111// the owning `MitoRegion`, so we extract the counters from `RegionScanExec` and
112// pass them to the adapter. If a plan contains scans from different regions,
113// return `None` to avoid charging the whole plan metrics to one region.
114fn query_stat_counters(plan: &Arc<dyn ExecutionPlan>) -> Option<RegionQueryStatCounters> {
115    let mut counters: Option<RegionQueryStatCounters> = None;
116    let mut stack = vec![plan.clone()];
117
118    while let Some(plan) = stack.pop() {
119        if plan.name() == REGION_SCAN_EXEC_NAME
120            && let Some(scan) = plan.downcast_ref::<RegionScanExec>()
121            && let Some(scan_counters) = scan.query_stat_counters()
122        {
123            match &counters {
124                Some(counters)
125                    if !Arc::ptr_eq(&counters.query_cpu_time, &scan_counters.query_cpu_time)
126                        || !Arc::ptr_eq(
127                            &counters.query_scanned_bytes,
128                            &scan_counters.query_scanned_bytes,
129                        ) =>
130                {
131                    return None;
132                }
133                Some(_) => {}
134                None => counters = Some(scan_counters),
135            }
136        }
137        stack.extend(plan.children().into_iter().cloned());
138    }
139
140    counters
141}
142
143pub struct DatafusionQueryEngine {
144    state: Arc<QueryEngineState>,
145    plugins: Plugins,
146}
147
148impl DatafusionQueryEngine {
149    pub fn new(state: Arc<QueryEngineState>, plugins: Plugins) -> Self {
150        Self { state, plugins }
151    }
152
153    #[tracing::instrument(skip_all)]
154    async fn exec_query_plan(
155        &self,
156        plan: LogicalPlan,
157        query_ctx: QueryContextRef,
158    ) -> Result<Output> {
159        let mut ctx = self.engine_context(query_ctx.clone());
160        let plan = if let Some(receiver_injector) =
161            self.plugins.get::<RemoteDynFilterReceiverInjectorRef>()
162        {
163            receiver_injector.maybe_inject(plan, query_ctx.clone())
164        } else {
165            plan
166        };
167
168        // `create_physical_plan` will optimize logical plan internally
169        let physical_plan = self.create_physical_plan(&mut ctx, &plan).await?;
170        let physical_plan = self.optimize_physical_plan(&mut ctx, physical_plan)?;
171        let physical_plan = if let Some(wrapper) = self.plugins.get::<PhysicalPlanWrapperRef>() {
172            wrapper.wrap(physical_plan, query_ctx)
173        } else {
174            physical_plan
175        };
176
177        let stream = self.execute_stream(&ctx, &physical_plan)?;
178
179        Ok(Output::new(
180            OutputData::Stream(stream),
181            OutputMeta::new_with_plan(physical_plan),
182        ))
183    }
184
185    #[tracing::instrument(skip_all)]
186    async fn exec_dml_statement(
187        &self,
188        dml: DmlStatement,
189        query_ctx: QueryContextRef,
190    ) -> Result<Output> {
191        ensure!(
192            matches!(dml.op, WriteOp::Insert(_) | WriteOp::Delete),
193            UnsupportedExprSnafu {
194                name: format!("DML op {}", dml.op),
195            }
196        );
197
198        let _timer = QUERY_STAGE_ELAPSED
199            .with_label_values(&[dml.op.name()])
200            .start_timer();
201
202        let default_catalog = &query_ctx.current_catalog().to_owned();
203        let default_schema = &query_ctx.current_schema();
204        let table_name = dml.table_name.resolve(default_catalog, default_schema);
205        let table = self.find_table(&table_name, &query_ctx).await?;
206
207        let Output { data, meta } = self
208            .exec_query_plan((*dml.input).clone(), query_ctx.clone())
209            .await?;
210        let mut stream = match data {
211            OutputData::RecordBatches(batches) => batches.as_stream(),
212            OutputData::Stream(stream) => stream,
213            _ => unreachable!(),
214        };
215
216        let mut affected_rows = 0;
217        let mut insert_cost = 0;
218
219        match dml.op {
220            WriteOp::Insert(_) => {
221                while let Some(batch) = stream.next().await {
222                    let batch = batch.context(CreateRecordBatchSnafu)?;
223                    let column_vectors = batch
224                        .column_vectors(&table_name.to_string(), table.schema())
225                        .map_err(BoxedError::new)
226                        .context(QueryExecutionSnafu)?;
227                    // We ignore the insert op.
228                    let output = self
229                        .insert(&table_name, column_vectors, query_ctx.clone())
230                        .await?;
231                    let (rows, cost) = output.extract_rows_and_cost();
232                    affected_rows += rows;
233                    insert_cost += cost;
234                }
235            }
236            WriteOp::Delete => {
237                while let Some(batch) = stream.next().await {
238                    let batch = batch.context(CreateRecordBatchSnafu)?;
239                    let column_vectors = batch
240                        .column_vectors(&table_name.to_string(), table.schema())
241                        .map_err(BoxedError::new)
242                        .context(QueryExecutionSnafu)?;
243                    affected_rows += self
244                        .delete(&table_name, &table, column_vectors, query_ctx.clone())
245                        .await?;
246                }
247            }
248            _ => unreachable!("guarded by the 'ensure!' at the beginning"),
249        }
250        Ok(Output::new(
251            OutputData::AffectedRows(affected_rows),
252            OutputMeta::new(meta.plan, insert_cost),
253        ))
254    }
255
256    #[tracing::instrument(skip_all)]
257    async fn delete(
258        &self,
259        table_name: &ResolvedTableReference,
260        table: &TableRef,
261        column_vectors: HashMap<String, VectorRef>,
262        query_ctx: QueryContextRef,
263    ) -> Result<usize> {
264        let catalog_name = table_name.catalog.to_string();
265        let schema_name = table_name.schema.to_string();
266        let table_name = table_name.table.to_string();
267        let table_schema = table.schema();
268
269        ensure!(
270            !is_readonly_table(&schema_name, &table_name),
271            TableReadOnlySnafu { table: table_name }
272        );
273
274        let ts_column = table_schema
275            .timestamp_column()
276            .map(|x| &x.name)
277            .with_context(|| MissingTimestampColumnSnafu {
278                table_name: table_name.clone(),
279            })?;
280
281        let table_info = table.table_info();
282        let rowkey_columns = table_info
283            .meta
284            .row_key_column_names()
285            .collect::<Vec<&String>>();
286        let column_vectors = column_vectors
287            .into_iter()
288            .filter(|x| &x.0 == ts_column || rowkey_columns.contains(&&x.0))
289            .collect::<HashMap<_, _>>();
290
291        let request = DeleteRequest {
292            catalog_name,
293            schema_name,
294            table_name,
295            key_column_values: column_vectors,
296        };
297
298        self.state
299            .table_mutation_handler()
300            .context(MissingTableMutationHandlerSnafu)?
301            .delete(request, query_ctx)
302            .await
303            .context(TableMutationSnafu)
304    }
305
306    #[tracing::instrument(skip_all)]
307    async fn insert(
308        &self,
309        table_name: &ResolvedTableReference,
310        column_vectors: HashMap<String, VectorRef>,
311        query_ctx: QueryContextRef,
312    ) -> Result<Output> {
313        let catalog_name = table_name.catalog.to_string();
314        let schema_name = table_name.schema.to_string();
315        let table_name = table_name.table.to_string();
316
317        ensure!(
318            !is_readonly_table(&schema_name, &table_name),
319            TableReadOnlySnafu { table: table_name }
320        );
321
322        let request = InsertRequest {
323            catalog_name,
324            schema_name,
325            table_name,
326            columns_values: column_vectors,
327            skip_wal: query_ctx.skip_wal(),
328        };
329
330        self.state
331            .table_mutation_handler()
332            .context(MissingTableMutationHandlerSnafu)?
333            .insert(request, query_ctx)
334            .await
335            .context(TableMutationSnafu)
336    }
337
338    async fn find_table(
339        &self,
340        table_name: &ResolvedTableReference,
341        query_context: &QueryContextRef,
342    ) -> Result<TableRef> {
343        let catalog_name = table_name.catalog.as_ref();
344        let schema_name = table_name.schema.as_ref();
345        let table_name = table_name.table.as_ref();
346
347        self.state
348            .catalog_manager()
349            .table(catalog_name, schema_name, table_name, Some(query_context))
350            .await
351            .context(CatalogSnafu)?
352            .with_context(|| TableNotFoundSnafu { table: table_name })
353    }
354
355    #[tracing::instrument(skip_all)]
356    async fn create_physical_plan(
357        &self,
358        ctx: &mut QueryEngineContext,
359        logical_plan: &LogicalPlan,
360    ) -> Result<Arc<dyn ExecutionPlan>> {
361        /// Only print context on panic, to avoid cluttering logs.
362        ///
363        /// TODO(discord9): remove this once we catch the bug
364        #[derive(Debug)]
365        struct PanicLogger<'a> {
366            input_logical_plan: &'a LogicalPlan,
367            after_analyze: Option<LogicalPlan>,
368            after_optimize: Option<LogicalPlan>,
369            phy_plan: Option<Arc<dyn ExecutionPlan>>,
370        }
371        impl Drop for PanicLogger<'_> {
372            fn drop(&mut self) {
373                if std::thread::panicking() {
374                    common_telemetry::error!(
375                        "Panic while creating physical plan, input logical plan: {:?}, after analyze: {:?}, after optimize: {:?}, final physical plan: {:?}",
376                        self.input_logical_plan,
377                        self.after_analyze,
378                        self.after_optimize,
379                        self.phy_plan
380                    );
381                }
382            }
383        }
384
385        expr_depth::validate_plan(logical_plan)?;
386
387        let mut logger = PanicLogger {
388            input_logical_plan: logical_plan,
389            after_analyze: None,
390            after_optimize: None,
391            phy_plan: None,
392        };
393
394        let _timer = metrics::CREATE_PHYSICAL_ELAPSED.start_timer();
395        let state = ctx.state();
396
397        common_telemetry::debug!("Create physical plan, input plan: {logical_plan}");
398
399        // special handle EXPLAIN plan
400        if matches!(logical_plan, DfLogicalPlan::Explain(_)) {
401            return state
402                .create_physical_plan(logical_plan)
403                .await
404                .map_err(Into::into);
405        }
406
407        // analyze first
408        let analyzed_plan = state.analyzer().execute_and_check(
409            logical_plan.clone(),
410            state.config_options(),
411            |_, _| {},
412        )?;
413
414        expr_depth::validate_plan(&analyzed_plan)?;
415        logger.after_analyze = Some(analyzed_plan.clone());
416
417        common_telemetry::debug!("Create physical plan, analyzed plan: {analyzed_plan}");
418
419        // skip optimize for MergeScan
420        let optimized_plan = if let DfLogicalPlan::Extension(ext) = &analyzed_plan
421            && ext.node.name() == MergeScanLogicalPlan::name()
422        {
423            analyzed_plan.clone()
424        } else {
425            state
426                .optimizer()
427                .optimize(analyzed_plan, state, |_, _| {})?
428        };
429
430        expr_depth::validate_plan(&optimized_plan)?;
431        common_telemetry::debug!("Create physical plan, optimized plan: {optimized_plan}");
432        logger.after_optimize = Some(optimized_plan.clone());
433
434        let physical_plan = state
435            .query_planner()
436            .create_physical_plan(&optimized_plan, state)
437            .await?;
438
439        logger.phy_plan = Some(physical_plan.clone());
440        drop(logger);
441        Ok(physical_plan)
442    }
443
444    #[tracing::instrument(skip_all)]
445    fn optimize_physical_plan(
446        &self,
447        ctx: &mut QueryEngineContext,
448        plan: Arc<dyn ExecutionPlan>,
449    ) -> Result<Arc<dyn ExecutionPlan>> {
450        let _timer = metrics::OPTIMIZE_PHYSICAL_ELAPSED.start_timer();
451
452        // TODO(ruihang): `self.create_physical_plan()` already optimize the plan, check
453        // if we need to optimize it again here.
454        // let state = ctx.state();
455        // let config = state.config_options();
456
457        // skip optimize AnalyzeExec plan
458        let optimized_plan = if let Some(analyze_plan) = plan.downcast_ref::<AnalyzeExec>() {
459            let format = if let Some(format) = ctx.query_ctx().explain_format()
460                && format.to_lowercase() == "json"
461            {
462                AnalyzeFormat::JSON
463            } else {
464                AnalyzeFormat::TEXT
465            };
466            // Sets the verbose flag of the query context.
467            // The MergeScanExec plan uses the verbose flag to determine whether to print the plan in verbose mode.
468            ctx.query_ctx().set_explain_verbose(analyze_plan.verbose());
469
470            Arc::new(DistAnalyzeExec::new(
471                analyze_plan.input().clone(),
472                analyze_plan.verbose(),
473                format,
474            ))
475            // let mut new_plan = analyze_plan.input().clone();
476            // for optimizer in state.physical_optimizers() {
477            //     new_plan = optimizer
478            //         .optimize(new_plan, config)
479            //         .context(DataFusionSnafu)?;
480            // }
481            // Arc::new(DistAnalyzeExec::new(new_plan))
482        } else {
483            plan
484            // let mut new_plan = plan;
485            // for optimizer in state.physical_optimizers() {
486            //     new_plan = optimizer
487            //         .optimize(new_plan, config)
488            //         .context(DataFusionSnafu)?;
489            // }
490            // new_plan
491        };
492
493        Ok(optimized_plan)
494    }
495}
496
497#[async_trait]
498impl QueryEngine for DatafusionQueryEngine {
499    fn as_any(&self) -> &dyn Any {
500        self
501    }
502
503    fn planner(&self) -> Arc<dyn LogicalPlanner> {
504        Arc::new(DfLogicalPlanner::new(self.state.clone()))
505    }
506
507    fn name(&self) -> &str {
508        "datafusion"
509    }
510
511    async fn describe(
512        &self,
513        plan: LogicalPlan,
514        _query_ctx: QueryContextRef,
515    ) -> Result<DescribeResult> {
516        Ok(DescribeResult { logical_plan: plan })
517    }
518
519    async fn execute(&self, plan: LogicalPlan, query_ctx: QueryContextRef) -> Result<Output> {
520        match plan {
521            LogicalPlan::Dml(dml) => self.exec_dml_statement(dml, query_ctx).await,
522            _ => self.exec_query_plan(plan, query_ctx).await,
523        }
524    }
525
526    /// Note in SQL queries, aggregate names are looked up using
527    /// lowercase unless the query uses quotes. For example,
528    ///
529    /// `SELECT MY_UDAF(x)...` will look for an aggregate named `"my_udaf"`
530    /// `SELECT "my_UDAF"(x)` will look for an aggregate named `"my_UDAF"`
531    ///
532    /// So it's better to make UDAF name lowercase when creating one.
533    fn register_aggregate_function(&self, func: AggregateUDF) {
534        self.state.register_aggr_function(func);
535    }
536
537    /// Register an scalar function.
538    /// Will override if the function with same name is already registered.
539    fn register_scalar_function(&self, func: ScalarFunctionFactory) {
540        self.state.register_scalar_function(func);
541    }
542
543    fn register_table_function(&self, func: Arc<TableFunction>) {
544        self.state.register_table_function(func);
545    }
546
547    fn register_window_function(&self, func: WindowUDF) {
548        self.state.register_window_function(func);
549    }
550
551    fn read_table(&self, table: TableRef) -> Result<DataFrame> {
552        self.state.read_table(table).map_err(Into::into)
553    }
554
555    fn engine_context(&self, query_ctx: QueryContextRef) -> QueryEngineContext {
556        let mut state = self.state.session_state();
557        state.config_mut().set_extension(query_ctx.clone());
558        state.config_mut().set_extension(self.state.clone());
559        // note that hints in "x-greptime-hints" is automatically parsed
560        // and set to query context's extension, so we can get it from query context.
561        if let Some(parallelism) = query_ctx.extension(QUERY_PARALLELISM_HINT) {
562            if let Ok(n) = parallelism.parse::<u64>() {
563                if n > 0 {
564                    let new_cfg = state.config().clone().with_target_partitions(n as usize);
565                    *state.config_mut() = new_cfg;
566                }
567            } else {
568                common_telemetry::warn!(
569                    "Failed to parse query_parallelism: {}, using default value",
570                    parallelism
571                );
572            }
573        }
574
575        // configure execution options
576        state.config_mut().options_mut().execution.time_zone =
577            Some(query_ctx.timezone().to_string());
578
579        // usually it's impossible to have both `set variable` set by sql client and
580        // hint in header by grpc client, so only need to deal with them separately
581        if query_ctx.configuration_parameter().allow_query_fallback() {
582            state
583                .config_mut()
584                .options_mut()
585                .extensions
586                .insert(DistPlannerOptions {
587                    allow_query_fallback: true,
588                });
589        } else if let Some(fallback) = query_ctx.extension(QUERY_FALLBACK_HINT) {
590            // also check the query context for fallback hint
591            // if it is set, we will enable the fallback
592            if fallback.to_lowercase().parse::<bool>().unwrap_or(false) {
593                state
594                    .config_mut()
595                    .options_mut()
596                    .extensions
597                    .insert(DistPlannerOptions {
598                        allow_query_fallback: true,
599                    });
600            }
601        }
602
603        state
604            .config_mut()
605            .options_mut()
606            .extensions
607            .insert(FunctionContext {
608                query_ctx: query_ctx.clone(),
609                state: self.engine_state().function_state(),
610            });
611
612        // Carry scheduled Flow time through ConfigOptions.extensions so that
613        // the distributed plan analyzer can read it during expression
614        // simplification (preventing wall-clock constant-folding of `now()`).
615        state
616            .config_mut()
617            .options_mut()
618            .extensions
619            .insert(ScheduledTimeExtension {
620                scheduled_time: crate::options::scheduled_time_from_ctx(&query_ctx),
621            });
622
623        let config_options = state.config_options().clone();
624        let _ = state
625            .execution_props_mut()
626            .config_options
627            .insert(config_options);
628
629        // Apply scheduled time from query context if present, so that `now()` /
630        // `current_timestamp()` functions evaluate against the logical scheduled time
631        // rather than wall-clock.
632        match crate::options::parse_scheduled_time_datetime(&query_ctx.extensions()) {
633            Ok(Some(scheduled_rt)) => {
634                state.execution_props_mut().query_execution_start_time = Some(scheduled_rt);
635            }
636            Ok(None) => {}
637            Err(err) => {
638                common_telemetry::warn!(err; "Ignoring invalid scheduled time query extension");
639            }
640        }
641
642        QueryEngineContext::new(state, query_ctx)
643    }
644
645    fn engine_state(&self) -> &QueryEngineState {
646        &self.state
647    }
648}
649
650impl QueryExecutor for DatafusionQueryEngine {
651    #[tracing::instrument(skip_all)]
652    fn execute_stream(
653        &self,
654        ctx: &QueryEngineContext,
655        plan: &Arc<dyn ExecutionPlan>,
656    ) -> Result<SendableRecordBatchStream> {
657        let query_ctx = ctx.query_ctx();
658        let explain_verbose = query_ctx.explain_verbose();
659        let should_collect_region_watermark =
660            should_collect_region_watermark_from_query_ctx(&query_ctx)?;
661        let output_partitions = plan.properties().output_partitioning().partition_count();
662        if explain_verbose {
663            common_telemetry::info!("Executing query plan, output_partitions: {output_partitions}");
664        }
665
666        let exec_timer = metrics::EXEC_PLAN_ELAPSED.start_timer();
667        let task_ctx = ctx.build_task_ctx();
668        let span = Span::current();
669
670        match plan.properties().output_partitioning().partition_count() {
671            0 => {
672                let schema = Arc::new(
673                    Schema::try_from(plan.schema())
674                        .map_err(BoxedError::new)
675                        .context(QueryExecutionSnafu)?,
676                );
677                Ok(Box::pin(EmptyRecordBatchStream::new(schema)))
678            }
679            1 => {
680                let df_stream = plan.execute(0, task_ctx)?;
681                let mut stream = RecordBatchStreamAdapter::try_new_with_span(df_stream, span)
682                    .context(error::ConvertDfRecordBatchStreamSnafu)
683                    .map_err(BoxedError::new)
684                    .context(QueryExecutionSnafu)?;
685                stream.set_metrics2(plan.clone());
686                stream.set_query_load_region_id(query_load_region_id(plan));
687                stream.set_query_stat_counters(query_stat_counters(plan));
688                stream.set_explain_verbose(explain_verbose);
689                let stream = OnDone::new(Box::pin(stream), move || {
690                    let exec_cost = exec_timer.stop_and_record();
691                    if explain_verbose {
692                        common_telemetry::info!(
693                            "DatafusionQueryEngine execute 1 stream, cost: {:?}s",
694                            exec_cost,
695                        );
696                    }
697                });
698                Ok(maybe_attach_region_watermark_metrics(
699                    Box::pin(stream),
700                    plan.clone(),
701                    should_collect_region_watermark,
702                ))
703            }
704            _ => {
705                // merge into a single partition
706                let merged_plan = CoalescePartitionsExec::new(plan.clone());
707                // CoalescePartitionsExec must produce a single partition
708                assert_eq!(
709                    1,
710                    merged_plan
711                        .properties()
712                        .output_partitioning()
713                        .partition_count()
714                );
715                let df_stream = merged_plan.execute(0, task_ctx)?;
716                let mut stream = RecordBatchStreamAdapter::try_new_with_span(df_stream, span)
717                    .context(error::ConvertDfRecordBatchStreamSnafu)
718                    .map_err(BoxedError::new)
719                    .context(QueryExecutionSnafu)?;
720                stream.set_metrics2(plan.clone());
721                stream.set_query_load_region_id(query_load_region_id(plan));
722                stream.set_query_stat_counters(query_stat_counters(plan));
723                stream.set_explain_verbose(explain_verbose);
724                let stream = OnDone::new(Box::pin(stream), move || {
725                    let exec_cost = exec_timer.stop_and_record();
726                    if explain_verbose {
727                        common_telemetry::info!(
728                            "DatafusionQueryEngine execute {output_partitions} stream, cost: {:?}s",
729                            exec_cost
730                        );
731                    }
732                });
733                Ok(maybe_attach_region_watermark_metrics(
734                    Box::pin(stream),
735                    plan.clone(),
736                    should_collect_region_watermark,
737                ))
738            }
739        }
740    }
741}
742
743#[cfg(test)]
744mod tests {
745    use std::fmt;
746    use std::sync::Arc;
747    use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
748
749    use api::v1::SemanticType;
750    use arrow::array::{ArrayRef, UInt64Array};
751    use arrow_schema::SortOptions;
752    use async_trait::async_trait;
753    use catalog::RegisterTableRequest;
754    use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, NUMBERS_TABLE_ID};
755    use common_error::ext::BoxedError;
756    use common_recordbatch::{
757        EmptyRecordBatchStream, RecordBatch, SendableRecordBatchStream, util,
758    };
759    use datafusion::physical_plan::display::{DisplayAs, DisplayFormatType};
760    use datafusion::physical_plan::expressions::PhysicalSortExpr;
761    use datafusion::physical_plan::joins::{HashJoinExec, JoinOn, PartitionMode};
762    use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
763    use datafusion::physical_plan::{ExecutionPlan, PhysicalExpr};
764    use datafusion::prelude::{col, lit};
765    use datafusion_common::{JoinType, NullEquality, ScalarValue};
766    use datafusion_physical_expr::expressions::Column;
767    use datatypes::prelude::ConcreteDataType;
768    use datatypes::schema::{ColumnSchema, SchemaRef};
769    use datatypes::vectors::{Helper, UInt32Vector, VectorRef};
770    use session::context::{QueryContext, QueryContextBuilder};
771    use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder, RegionMetadataRef};
772    use store_api::region_engine::{
773        PartitionRange, PrepareRequest, QueryScanContext, RegionScanner, ScannerProperties,
774    };
775    use store_api::storage::{RegionId, ScanRequest};
776    use table::metadata::{TableInfoBuilder, TableMetaBuilder};
777    use table::table::numbers::{NUMBERS_TABLE_NAME, NumbersTable};
778    use table::table::scan::RegionScanExec;
779
780    use super::*;
781    use crate::options::QueryOptions;
782    use crate::parser::{QueryLanguageParser, QueryStatement};
783    use crate::part_sort::PartSortExec;
784    use crate::query_engine::{QueryEngineFactory, QueryEngineRef};
785
786    #[derive(Debug)]
787    struct RecordingScanner {
788        schema: SchemaRef,
789        metadata: RegionMetadataRef,
790        properties: ScannerProperties,
791        update_calls: Arc<AtomicUsize>,
792        last_filter_len: Arc<AtomicUsize>,
793    }
794
795    impl RecordingScanner {
796        fn new(
797            schema: SchemaRef,
798            metadata: RegionMetadataRef,
799            update_calls: Arc<AtomicUsize>,
800            last_filter_len: Arc<AtomicUsize>,
801        ) -> Self {
802            Self {
803                schema,
804                metadata,
805                properties: ScannerProperties::default(),
806                update_calls,
807                last_filter_len,
808            }
809        }
810    }
811
812    impl RegionScanner for RecordingScanner {
813        fn name(&self) -> &str {
814            "RecordingScanner"
815        }
816
817        fn properties(&self) -> &ScannerProperties {
818            &self.properties
819        }
820
821        fn schema(&self) -> SchemaRef {
822            self.schema.clone()
823        }
824
825        fn metadata(&self) -> RegionMetadataRef {
826            self.metadata.clone()
827        }
828
829        fn prepare(&mut self, request: PrepareRequest) -> std::result::Result<(), BoxedError> {
830            self.properties.prepare(request);
831            Ok(())
832        }
833
834        fn scan_partition(
835            &self,
836            _ctx: &QueryScanContext,
837            _metrics_set: &ExecutionPlanMetricsSet,
838            _partition: usize,
839        ) -> std::result::Result<SendableRecordBatchStream, BoxedError> {
840            Ok(Box::pin(EmptyRecordBatchStream::new(self.schema.clone())))
841        }
842
843        fn has_predicate_without_region(&self) -> bool {
844            true
845        }
846
847        fn add_dyn_filter_to_predicate(
848            &mut self,
849            filter_exprs: Vec<Arc<dyn PhysicalExpr>>,
850        ) -> Vec<bool> {
851            self.update_calls.fetch_add(1, Ordering::Relaxed);
852            self.last_filter_len
853                .store(filter_exprs.len(), Ordering::Relaxed);
854            vec![true; filter_exprs.len()]
855        }
856
857        fn set_logical_region(&mut self, logical_region: bool) {
858            self.properties.set_logical_region(logical_region);
859        }
860
861        fn set_query_load_region_id(&mut self, region_id: store_api::storage::RegionId) {
862            self.properties.set_query_load_region_id(region_id);
863        }
864    }
865
866    impl DisplayAs for RecordingScanner {
867        fn fmt_as(&self, _t: DisplayFormatType, f: &mut fmt::Formatter<'_>) -> fmt::Result {
868            write!(f, "RecordingScanner")
869        }
870    }
871
872    fn build_query_load_region_scan(
873        query_load_region_id: Option<RegionId>,
874    ) -> Arc<dyn ExecutionPlan> {
875        build_region_scan(query_load_region_id, None)
876    }
877
878    fn build_query_stat_counter_region_scan(
879        counters: RegionQueryStatCounters,
880    ) -> Arc<dyn ExecutionPlan> {
881        build_region_scan(None, Some(counters))
882    }
883
884    fn build_region_scan(
885        query_load_region_id: Option<RegionId>,
886        query_stat_counters: Option<RegionQueryStatCounters>,
887    ) -> Arc<dyn ExecutionPlan> {
888        let schema = Arc::new(datatypes::schema::Schema::new(vec![ColumnSchema::new(
889            "ts",
890            ConcreteDataType::timestamp_millisecond_datatype(),
891            false,
892        )]));
893
894        let mut metadata_builder = RegionMetadataBuilder::new(RegionId::new(1024, 1));
895        metadata_builder
896            .push_column_metadata(ColumnMetadata {
897                column_schema: ColumnSchema::new(
898                    "ts",
899                    ConcreteDataType::timestamp_millisecond_datatype(),
900                    false,
901                )
902                .with_time_index(true),
903                semantic_type: SemanticType::Timestamp,
904                column_id: 1,
905            })
906            .primary_key(vec![]);
907        let metadata = Arc::new(metadata_builder.build().unwrap());
908        let mut scanner = RecordingScanner::new(
909            schema,
910            metadata,
911            Arc::new(AtomicUsize::new(0)),
912            Arc::new(AtomicUsize::new(0)),
913        );
914        if let Some(region_id) = query_load_region_id {
915            scanner.set_query_load_region_id(region_id);
916        }
917        if let Some(counters) = query_stat_counters {
918            scanner.properties.set_query_stat_counters(counters);
919        }
920
921        Arc::new(RegionScanExec::new(Box::new(scanner), ScanRequest::default(), None).unwrap())
922    }
923
924    fn query_stat_counters_for_test() -> RegionQueryStatCounters {
925        RegionQueryStatCounters {
926            query_cpu_time: Arc::new(AtomicU64::new(0)),
927            query_scanned_bytes: Arc::new(AtomicU64::new(0)),
928        }
929    }
930
931    #[test]
932    fn query_load_region_id_ignores_scans_without_region_id() {
933        let query_load_region_id = RegionId::new(1024, 42);
934        let scan_without_region_id = build_query_load_region_scan(None);
935        let scan_with_region_id = build_query_load_region_scan(Some(query_load_region_id));
936        let on: JoinOn = vec![(
937            Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
938            Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
939        )];
940        let plan: Arc<dyn ExecutionPlan> = Arc::new(
941            HashJoinExec::try_new(
942                scan_without_region_id,
943                scan_with_region_id,
944                on,
945                None,
946                &JoinType::Inner,
947                None,
948                PartitionMode::CollectLeft,
949                NullEquality::NullEqualsNull,
950                false,
951            )
952            .unwrap(),
953        );
954
955        assert_eq!(
956            super::query_load_region_id(&plan),
957            Some(query_load_region_id.as_u64())
958        );
959    }
960
961    #[test]
962    fn query_stat_counters_returns_shared_counter_for_multi_scan_plan() {
963        let counters = query_stat_counters_for_test();
964        let left = build_query_stat_counter_region_scan(counters.clone());
965        let right = build_query_stat_counter_region_scan(counters.clone());
966        let on: JoinOn = vec![(
967            Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
968            Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
969        )];
970        let plan: Arc<dyn ExecutionPlan> = Arc::new(
971            HashJoinExec::try_new(
972                left,
973                right,
974                on,
975                None,
976                &JoinType::Inner,
977                None,
978                PartitionMode::CollectLeft,
979                NullEquality::NullEqualsNull,
980                false,
981            )
982            .unwrap(),
983        );
984
985        let actual = super::query_stat_counters(&plan).unwrap();
986        assert!(Arc::ptr_eq(
987            &actual.query_cpu_time,
988            &counters.query_cpu_time
989        ));
990        assert!(Arc::ptr_eq(
991            &actual.query_scanned_bytes,
992            &counters.query_scanned_bytes
993        ));
994    }
995
996    #[test]
997    fn query_stat_counters_ignores_mixed_counter_plan() {
998        let left = build_query_stat_counter_region_scan(query_stat_counters_for_test());
999        let right = build_query_stat_counter_region_scan(query_stat_counters_for_test());
1000        let on: JoinOn = vec![(
1001            Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
1002            Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
1003        )];
1004        let plan: Arc<dyn ExecutionPlan> = Arc::new(
1005            HashJoinExec::try_new(
1006                left,
1007                right,
1008                on,
1009                None,
1010                &JoinType::Inner,
1011                None,
1012                PartitionMode::CollectLeft,
1013                NullEquality::NullEqualsNull,
1014                false,
1015            )
1016            .unwrap(),
1017        );
1018
1019        assert!(super::query_stat_counters(&plan).is_none());
1020    }
1021
1022    async fn create_test_engine() -> QueryEngineRef {
1023        let catalog_manager = catalog::memory::new_memory_catalog_manager().unwrap();
1024        let req = RegisterTableRequest {
1025            catalog: DEFAULT_CATALOG_NAME.to_string(),
1026            schema: DEFAULT_SCHEMA_NAME.to_string(),
1027            table_name: NUMBERS_TABLE_NAME.to_string(),
1028            table_id: NUMBERS_TABLE_ID,
1029            table: NumbersTable::table(NUMBERS_TABLE_ID),
1030        };
1031        catalog_manager.register_table_sync(req).unwrap();
1032
1033        QueryEngineFactory::new(
1034            catalog_manager,
1035            None,
1036            None,
1037            None,
1038            None,
1039            false,
1040            QueryOptions::default(),
1041        )
1042        .query_engine()
1043    }
1044
1045    #[tokio::test]
1046    async fn test_sql_to_plan() {
1047        let engine = create_test_engine().await;
1048        let sql = "select sum(number) from numbers limit 20";
1049
1050        let stmt = QueryLanguageParser::parse_sql(sql, &QueryContext::arc()).unwrap();
1051        let plan = engine
1052            .planner()
1053            .plan(&stmt, QueryContext::arc())
1054            .await
1055            .unwrap();
1056
1057        assert_eq!(
1058            plan.to_string(),
1059            r#"Limit: skip=0, fetch=20
1060  Projection: sum(numbers.number)
1061    Aggregate: groupBy=[[]], aggr=[[sum(numbers.number)]]
1062      TableScan: numbers"#
1063        );
1064    }
1065
1066    #[tokio::test]
1067    async fn test_purge_table_is_not_available_to_select() {
1068        let engine = create_test_engine().await;
1069        let stmt =
1070            QueryLanguageParser::parse_sql("select purge_table('numbers')", &QueryContext::arc())
1071                .unwrap();
1072
1073        assert!(
1074            engine
1075                .planner()
1076                .plan(&stmt, QueryContext::arc())
1077                .await
1078                .is_err()
1079        );
1080    }
1081
1082    #[tokio::test]
1083    async fn test_execute() {
1084        let engine = create_test_engine().await;
1085        let sql = "select sum(number) from numbers limit 20";
1086
1087        let stmt = QueryLanguageParser::parse_sql(sql, &QueryContext::arc()).unwrap();
1088        let plan = engine
1089            .planner()
1090            .plan(&stmt, QueryContext::arc())
1091            .await
1092            .unwrap();
1093
1094        let output = engine.execute(plan, QueryContext::arc()).await.unwrap();
1095
1096        match output.data {
1097            OutputData::Stream(recordbatch) => {
1098                let numbers = util::collect(recordbatch).await.unwrap();
1099                assert_eq!(1, numbers.len());
1100                assert_eq!(numbers[0].num_columns(), 1);
1101                assert_eq!(1, numbers[0].schema.num_columns());
1102                assert_eq!(
1103                    "sum(numbers.number)",
1104                    numbers[0].schema.column_schemas()[0].name
1105                );
1106
1107                let batch = &numbers[0];
1108                assert_eq!(1, batch.num_columns());
1109                assert_eq!(batch.column(0).len(), 1);
1110
1111                let expected = Arc::new(UInt64Array::from_iter_values([4950])) as ArrayRef;
1112                assert_eq!(batch.column(0), &expected);
1113            }
1114            _ => unreachable!(),
1115        }
1116    }
1117
1118    #[tokio::test]
1119    async fn test_read_table() {
1120        let engine = create_test_engine().await;
1121
1122        let engine = engine
1123            .as_any()
1124            .downcast_ref::<DatafusionQueryEngine>()
1125            .unwrap();
1126        let query_ctx = Arc::new(QueryContextBuilder::default().build());
1127        let table = engine
1128            .find_table(
1129                &ResolvedTableReference {
1130                    catalog: "greptime".into(),
1131                    schema: "public".into(),
1132                    table: "numbers".into(),
1133                },
1134                &query_ctx,
1135            )
1136            .await
1137            .unwrap();
1138
1139        let df = engine.read_table(table).unwrap();
1140        let df = df
1141            .select_columns(&["number"])
1142            .unwrap()
1143            .filter(col("number").lt(lit(10)))
1144            .unwrap();
1145        let batches = df.collect().await.unwrap();
1146        assert_eq!(1, batches.len());
1147        let batch = &batches[0];
1148
1149        assert_eq!(1, batch.num_columns());
1150        assert_eq!(batch.column(0).len(), 10);
1151
1152        assert_eq!(
1153            Helper::try_into_vector(batch.column(0)).unwrap(),
1154            Arc::new(UInt32Vector::from_slice([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) as VectorRef
1155        );
1156    }
1157
1158    #[tokio::test]
1159    async fn test_describe() {
1160        let engine = create_test_engine().await;
1161        let sql = "select sum(number) from numbers limit 20";
1162
1163        let stmt = QueryLanguageParser::parse_sql(sql, &QueryContext::arc()).unwrap();
1164
1165        let plan = engine
1166            .planner()
1167            .plan(&stmt, QueryContext::arc())
1168            .await
1169            .unwrap();
1170
1171        let DescribeResult { logical_plan } =
1172            engine.describe(plan, QueryContext::arc()).await.unwrap();
1173
1174        let schema: Schema = logical_plan.schema().clone().try_into().unwrap();
1175
1176        assert_eq!(
1177            schema.column_schemas()[0],
1178            ColumnSchema::new(
1179                "sum(numbers.number)",
1180                ConcreteDataType::uint64_datatype(),
1181                true
1182            )
1183        );
1184        assert_eq!(
1185            "Limit: skip=0, fetch=20\n  Projection: sum(numbers.number)\n    Aggregate: groupBy=[[]], aggr=[[sum(numbers.number)]]\n      TableScan: numbers",
1186            format!("{}", logical_plan.display_indent())
1187        );
1188    }
1189
1190    #[tokio::test]
1191    async fn test_topk_dynamic_filter_pushdown_reaches_region_scan() {
1192        let engine = create_test_engine().await;
1193        let engine = engine
1194            .as_any()
1195            .downcast_ref::<DatafusionQueryEngine>()
1196            .unwrap();
1197        let engine_ctx = engine.engine_context(QueryContext::arc());
1198        let state = engine_ctx.state();
1199
1200        let schema = Arc::new(datatypes::schema::Schema::new(vec![ColumnSchema::new(
1201            "ts",
1202            ConcreteDataType::timestamp_millisecond_datatype(),
1203            false,
1204        )]));
1205
1206        let mut metadata_builder = RegionMetadataBuilder::new(RegionId::new(1024, 1));
1207        metadata_builder
1208            .push_column_metadata(ColumnMetadata {
1209                column_schema: ColumnSchema::new(
1210                    "ts",
1211                    ConcreteDataType::timestamp_millisecond_datatype(),
1212                    false,
1213                )
1214                .with_time_index(true),
1215                semantic_type: SemanticType::Timestamp,
1216                column_id: 1,
1217            })
1218            .primary_key(vec![]);
1219        let metadata = Arc::new(metadata_builder.build().unwrap());
1220
1221        let update_calls = Arc::new(AtomicUsize::new(0));
1222        let last_filter_len = Arc::new(AtomicUsize::new(0));
1223        let scanner = Box::new(RecordingScanner::new(
1224            schema,
1225            metadata,
1226            update_calls.clone(),
1227            last_filter_len.clone(),
1228        ));
1229        let scan = Arc::new(RegionScanExec::new(scanner, ScanRequest::default(), None).unwrap());
1230
1231        let sort_expr = PhysicalSortExpr {
1232            expr: Arc::new(Column::new("ts", 0)),
1233            options: SortOptions {
1234                descending: true,
1235                ..Default::default()
1236            },
1237        };
1238        let partition_ranges: Vec<Vec<PartitionRange>> = vec![vec![]];
1239        let mut plan: Arc<dyn ExecutionPlan> =
1240            Arc::new(PartSortExec::try_new(sort_expr, Some(3), partition_ranges, scan).unwrap());
1241
1242        for optimizer in state.physical_optimizers() {
1243            plan = optimizer.optimize(plan, state.config_options()).unwrap();
1244        }
1245
1246        assert!(update_calls.load(Ordering::Relaxed) > 0);
1247        assert!(last_filter_len.load(Ordering::Relaxed) > 0);
1248    }
1249
1250    #[tokio::test]
1251    async fn test_join_dynamic_filter_pushdown_reaches_region_scan() {
1252        let engine = create_test_engine().await;
1253        let engine = engine
1254            .as_any()
1255            .downcast_ref::<DatafusionQueryEngine>()
1256            .unwrap();
1257        let engine_ctx = engine.engine_context(QueryContext::arc());
1258        let state = engine_ctx.state();
1259
1260        assert!(
1261            state
1262                .config_options()
1263                .optimizer
1264                .enable_join_dynamic_filter_pushdown
1265        );
1266
1267        let schema = Arc::new(datatypes::schema::Schema::new(vec![ColumnSchema::new(
1268            "ts",
1269            ConcreteDataType::timestamp_millisecond_datatype(),
1270            false,
1271        )]));
1272
1273        let mut left_metadata_builder = RegionMetadataBuilder::new(RegionId::new(2048, 1));
1274        left_metadata_builder
1275            .push_column_metadata(ColumnMetadata {
1276                column_schema: ColumnSchema::new(
1277                    "ts",
1278                    ConcreteDataType::timestamp_millisecond_datatype(),
1279                    false,
1280                )
1281                .with_time_index(true),
1282                semantic_type: SemanticType::Timestamp,
1283                column_id: 1,
1284            })
1285            .primary_key(vec![]);
1286        let left_metadata = Arc::new(left_metadata_builder.build().unwrap());
1287
1288        let mut right_metadata_builder = RegionMetadataBuilder::new(RegionId::new(2048, 2));
1289        right_metadata_builder
1290            .push_column_metadata(ColumnMetadata {
1291                column_schema: ColumnSchema::new(
1292                    "ts",
1293                    ConcreteDataType::timestamp_millisecond_datatype(),
1294                    false,
1295                )
1296                .with_time_index(true),
1297                semantic_type: SemanticType::Timestamp,
1298                column_id: 1,
1299            })
1300            .primary_key(vec![]);
1301        let right_metadata = Arc::new(right_metadata_builder.build().unwrap());
1302
1303        let left_update_calls = Arc::new(AtomicUsize::new(0));
1304        let left_last_filter_len = Arc::new(AtomicUsize::new(0));
1305        let right_update_calls = Arc::new(AtomicUsize::new(0));
1306        let right_last_filter_len = Arc::new(AtomicUsize::new(0));
1307
1308        let left_scan = Arc::new(
1309            RegionScanExec::new(
1310                Box::new(RecordingScanner::new(
1311                    schema.clone(),
1312                    left_metadata,
1313                    left_update_calls.clone(),
1314                    left_last_filter_len.clone(),
1315                )),
1316                ScanRequest::default(),
1317                None,
1318            )
1319            .unwrap(),
1320        );
1321        let right_scan = Arc::new(
1322            RegionScanExec::new(
1323                Box::new(RecordingScanner::new(
1324                    schema,
1325                    right_metadata,
1326                    right_update_calls.clone(),
1327                    right_last_filter_len.clone(),
1328                )),
1329                ScanRequest::default(),
1330                None,
1331            )
1332            .unwrap(),
1333        );
1334
1335        let on: JoinOn = vec![(
1336            Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
1337            Arc::new(Column::new("ts", 0)) as Arc<dyn PhysicalExpr>,
1338        )];
1339
1340        let mut plan: Arc<dyn ExecutionPlan> = Arc::new(
1341            HashJoinExec::try_new(
1342                left_scan,
1343                right_scan,
1344                on,
1345                None,
1346                &JoinType::Inner,
1347                None,
1348                PartitionMode::CollectLeft,
1349                NullEquality::NullEqualsNull,
1350                false,
1351            )
1352            .unwrap(),
1353        );
1354
1355        for optimizer in state.physical_optimizers() {
1356            plan = optimizer.optimize(plan, state.config_options()).unwrap();
1357        }
1358
1359        assert!(left_update_calls.load(Ordering::Relaxed) > 0);
1360        assert_eq!(0, left_last_filter_len.load(Ordering::Relaxed));
1361        assert!(right_update_calls.load(Ordering::Relaxed) > 0);
1362        assert!(right_last_filter_len.load(Ordering::Relaxed) > 0);
1363    }
1364    #[derive(Default)]
1365    struct RecordingMutationHandler {
1366        inserts: std::sync::Mutex<Vec<table::requests::InsertRequest>>,
1367    }
1368
1369    #[async_trait]
1370    impl common_function::handlers::TableMutationHandler for RecordingMutationHandler {
1371        async fn insert(
1372            &self,
1373            request: table::requests::InsertRequest,
1374            _ctx: session::context::QueryContextRef,
1375        ) -> common_query::error::Result<common_query::Output> {
1376            self.inserts.lock().unwrap().push(request);
1377            Ok(common_query::Output::new_with_affected_rows(1))
1378        }
1379
1380        async fn delete(
1381            &self,
1382            _request: table::requests::DeleteRequest,
1383            _ctx: session::context::QueryContextRef,
1384        ) -> common_query::error::Result<common_base::AffectedRows> {
1385            unimplemented!("unexpected delete")
1386        }
1387
1388        async fn flush(
1389            &self,
1390            _request: table::requests::FlushTableRequest,
1391            _ctx: session::context::QueryContextRef,
1392        ) -> common_query::error::Result<common_base::AffectedRows> {
1393            unimplemented!("unexpected flush")
1394        }
1395
1396        async fn compact(
1397            &self,
1398            _request: table::requests::CompactTableRequest,
1399            _ctx: session::context::QueryContextRef,
1400        ) -> common_query::error::Result<common_base::AffectedRows> {
1401            unimplemented!("unexpected compact")
1402        }
1403
1404        async fn build_index(
1405            &self,
1406            _request: table::requests::BuildIndexTableRequest,
1407            _ctx: session::context::QueryContextRef,
1408        ) -> common_query::error::Result<common_base::AffectedRows> {
1409            unimplemented!("unexpected build_index")
1410        }
1411
1412        async fn flush_region(
1413            &self,
1414            _region_id: store_api::storage::RegionId,
1415            _ctx: session::context::QueryContextRef,
1416        ) -> common_query::error::Result<common_base::AffectedRows> {
1417            unimplemented!("unexpected flush_region")
1418        }
1419
1420        async fn compact_region(
1421            &self,
1422            _region_id: store_api::storage::RegionId,
1423            _ctx: session::context::QueryContextRef,
1424        ) -> common_query::error::Result<common_base::AffectedRows> {
1425            unimplemented!("unexpected compact_region")
1426        }
1427
1428        async fn discard_unflushed_data(
1429            &self,
1430            _region_id: store_api::storage::RegionId,
1431            _ctx: session::context::QueryContextRef,
1432        ) -> common_query::error::Result<common_base::AffectedRows> {
1433            unimplemented!("unexpected discard_unflushed_data")
1434        }
1435
1436        async fn discard_unflushed_data_by_table(
1437            &self,
1438            _table_name: table::table_name::TableName,
1439            _ctx: session::context::QueryContextRef,
1440        ) -> common_query::error::Result<common_base::AffectedRows> {
1441            unimplemented!("unexpected discard_unflushed_data_by_table")
1442        }
1443    }
1444
1445    fn native_schema() -> Arc<Schema> {
1446        Arc::new(Schema::new(vec![
1447            ColumnSchema::new("dim", ConcreteDataType::date_datatype(), true),
1448            ColumnSchema::new("amount", ConcreteDataType::decimal128_datatype(30, 2), true),
1449            ColumnSchema::new(
1450                "elapsed",
1451                ConcreteDataType::duration_millisecond_datatype(),
1452                true,
1453            ),
1454            ColumnSchema::new(
1455                "ts",
1456                ConcreteDataType::timestamp_millisecond_datatype(),
1457                false,
1458            )
1459            .with_time_index(true),
1460            ColumnSchema::new(
1461                "updated_at",
1462                ConcreteDataType::timestamp_millisecond_datatype(),
1463                true,
1464            ),
1465            ColumnSchema::new("marker", ConcreteDataType::uint8_datatype(), true),
1466            ColumnSchema::new("payload", ConcreteDataType::binary_datatype(), true),
1467            ColumnSchema::new("epoch", ConcreteDataType::uint64_datatype(), true),
1468        ]))
1469    }
1470
1471    fn register_native_tables(catalog: &catalog::memory::MemoryCatalogManager) {
1472        let schema = native_schema();
1473        let meta = TableMetaBuilder::empty()
1474            .schema(schema.clone())
1475            .primary_key_indices(vec![])
1476            .value_indices((0..schema.num_columns()).collect())
1477            .next_column_id(8)
1478            .build()
1479            .unwrap();
1480        let info = TableInfoBuilder::default()
1481            .name("native_regression")
1482            .table_id(9001)
1483            .table_version(0)
1484            .meta(meta)
1485            .build()
1486            .unwrap();
1487        catalog
1488            .register_table_sync(RegisterTableRequest {
1489                catalog: DEFAULT_CATALOG_NAME.to_string(),
1490                schema: DEFAULT_SCHEMA_NAME.to_string(),
1491                table_name: "native_regression".to_string(),
1492                table_id: 9001,
1493                table: table::test_util::EmptyTable::from_table_info(&info),
1494            })
1495            .unwrap();
1496
1497        let schema = Arc::new(Schema::new(vec![
1498            ColumnSchema::new("dim", ConcreteDataType::date_datatype(), true),
1499            ColumnSchema::new("amount", ConcreteDataType::decimal128_datatype(20, 2), true),
1500            ColumnSchema::new(
1501                "elapsed",
1502                ConcreteDataType::duration_millisecond_datatype(),
1503                true,
1504            ),
1505            ColumnSchema::new(
1506                "ts",
1507                ConcreteDataType::timestamp_millisecond_datatype(),
1508                false,
1509            )
1510            .with_time_index(true),
1511        ]));
1512        let rows = RecordBatch::new(
1513            schema,
1514            vec![
1515                Arc::new(datatypes::vectors::DateVector::from_slice([0, 2])) as VectorRef,
1516                Arc::new(
1517                    datatypes::vectors::Decimal128Vector::from_slice([10000, 20000])
1518                        .with_precision_and_scale(20, 2)
1519                        .unwrap(),
1520                ) as VectorRef,
1521                Arc::new(datatypes::vectors::DurationMillisecondVector::from_values(
1522                    [10, 20],
1523                )) as VectorRef,
1524                Arc::new(datatypes::vectors::TimestampMillisecondVector::from_slice(
1525                    [1, 2],
1526                )) as VectorRef,
1527            ],
1528        )
1529        .unwrap();
1530        catalog
1531            .register_table_sync(RegisterTableRequest {
1532                catalog: DEFAULT_CATALOG_NAME.to_string(),
1533                schema: DEFAULT_SCHEMA_NAME.to_string(),
1534                table_name: "native_aggregate".to_string(),
1535                table_id: 9002,
1536                table: table::test_util::MemTable::table("native_aggregate", rows),
1537            })
1538            .unwrap();
1539    }
1540
1541    async fn run_sql(engine: &QueryEngineRef, sql: &str) -> Vec<RecordBatch> {
1542        let stmt = QueryLanguageParser::parse_sql(sql, &QueryContext::arc()).unwrap();
1543        let plan = engine
1544            .planner()
1545            .plan(&stmt, QueryContext::arc())
1546            .await
1547            .unwrap();
1548        match engine
1549            .execute(plan, QueryContext::arc())
1550            .await
1551            .unwrap()
1552            .data
1553        {
1554            OutputData::Stream(stream) => util::collect(stream).await.unwrap(),
1555            _ => unreachable!(),
1556        }
1557    }
1558
1559    #[tokio::test]
1560    async fn test_native_executor_null_insert_and_aggregates() {
1561        let catalog = catalog::memory::new_memory_catalog_manager().unwrap();
1562        register_native_tables(&catalog);
1563        let handler = Arc::new(RecordingMutationHandler::default());
1564        let engine = QueryEngineFactory::new(
1565            catalog,
1566            None,
1567            Some(handler.clone()),
1568            None,
1569            None,
1570            false,
1571            QueryOptions::default(),
1572        )
1573        .query_engine();
1574
1575        // The CASTs force Insert::can_extract_values false. This records the
1576        // QueryEngine DML path selected by operator/src/statement/dml.rs.
1577        let insert_sql = "INSERT INTO native_regression (dim, amount, elapsed, ts, updated_at, marker, payload, epoch) VALUES (NULL, NULL, NULL, CAST(-62135596799999 AS TIMESTAMP(3)), CAST(NULL AS TIMESTAMP(3)), CAST(1 AS UInt8), X'0102', CAST(1 AS UInt64))";
1578        let stmt = QueryLanguageParser::parse_sql(insert_sql, &QueryContext::arc()).unwrap();
1579        let QueryStatement::Sql(sql::statements::statement::Statement::Insert(insert)) = &stmt
1580        else {
1581            unreachable!()
1582        };
1583        assert!(!insert.can_extract_values());
1584        let plan = engine
1585            .planner()
1586            .plan(&stmt, QueryContext::arc())
1587            .await
1588            .unwrap();
1589        assert!(matches!(
1590            engine
1591                .execute(plan, QueryContext::arc())
1592                .await
1593                .unwrap()
1594                .data,
1595            OutputData::AffectedRows(1)
1596        ));
1597
1598        let request = handler.inserts.lock().unwrap().pop().unwrap();
1599        for (name, data_type) in [
1600            ("dim", ConcreteDataType::date_datatype()),
1601            ("amount", ConcreteDataType::decimal128_datatype(30, 2)),
1602            ("elapsed", ConcreteDataType::duration_millisecond_datatype()),
1603            (
1604                "updated_at",
1605                ConcreteDataType::timestamp_millisecond_datatype(),
1606            ),
1607        ] {
1608            let vector = request.columns_values.get(name).unwrap();
1609            assert_eq!(vector.len(), 1, "{name}");
1610            assert_eq!(vector.data_type(), data_type, "{name}");
1611            assert!(vector.is_null(0), "{name}");
1612        }
1613        assert!(!request.columns_values["ts"].is_null(0));
1614        for (name, data_type) in [
1615            ("marker", ConcreteDataType::uint8_datatype()),
1616            ("payload", ConcreteDataType::binary_datatype()),
1617            ("epoch", ConcreteDataType::uint64_datatype()),
1618        ] {
1619            assert_eq!(
1620                request.columns_values[name].data_type(),
1621                data_type,
1622                "{name}"
1623            );
1624        }
1625
1626        let batches = run_sql(
1627            &engine,
1628            "SELECT MIN(dim), MAX(dim), SUM(amount), SUM(elapsed) FROM native_aggregate",
1629        )
1630        .await;
1631        let batch = &batches[0];
1632        assert_eq!(batch.num_rows(), 1);
1633        for (index, (data_type, value)) in [
1634            (
1635                ConcreteDataType::date_datatype(),
1636                ScalarValue::Date32(Some(0)),
1637            ),
1638            (
1639                ConcreteDataType::date_datatype(),
1640                ScalarValue::Date32(Some(2)),
1641            ),
1642            (
1643                ConcreteDataType::decimal128_datatype(30, 2),
1644                ScalarValue::Decimal128(Some(30000), 30, 2),
1645            ),
1646            (
1647                ConcreteDataType::duration_millisecond_datatype(),
1648                ScalarValue::DurationMillisecond(Some(30)),
1649            ),
1650        ]
1651        .into_iter()
1652        .enumerate()
1653        {
1654            assert_eq!(batch.schema.column_schemas()[index].data_type, data_type);
1655            assert_eq!(
1656                ScalarValue::try_from_array(batch.column(index).as_ref(), 0).unwrap(),
1657                value
1658            );
1659        }
1660
1661        let batches = run_sql(
1662            &engine,
1663            "SELECT aggregate.amount_sum + aggregate.amount_sum AS amount_add, \
1664                    aggregate.elapsed_sum + aggregate.elapsed_sum AS elapsed_add, \
1665                    CASE WHEN aggregate.min_dim < CAST('1970-01-02' AS DATE) THEN true ELSE false END AS min_before, \
1666                    CASE WHEN aggregate.max_dim > CAST('1970-01-01' AS DATE) THEN true ELSE false END AS max_after \
1667             FROM (SELECT MIN(dim) AS min_dim, MAX(dim) AS max_dim, SUM(amount) AS amount_sum, SUM(elapsed) AS elapsed_sum \
1668                   FROM native_aggregate) AS aggregate",
1669        )
1670        .await;
1671        let batch = &batches[0];
1672        assert_eq!(batch.num_rows(), 1);
1673        for (index, (data_type, value)) in [
1674            (
1675                ConcreteDataType::decimal128_datatype(31, 2),
1676                ScalarValue::Decimal128(Some(60000), 31, 2),
1677            ),
1678            (
1679                ConcreteDataType::duration_millisecond_datatype(),
1680                ScalarValue::DurationMillisecond(Some(60)),
1681            ),
1682            (
1683                ConcreteDataType::boolean_datatype(),
1684                ScalarValue::Boolean(Some(true)),
1685            ),
1686            (
1687                ConcreteDataType::boolean_datatype(),
1688                ScalarValue::Boolean(Some(true)),
1689            ),
1690        ]
1691        .into_iter()
1692        .enumerate()
1693        {
1694            assert_eq!(batch.schema.column_schemas()[index].data_type, data_type);
1695            assert_eq!(
1696                ScalarValue::try_from_array(batch.column(index).as_ref(), 0).unwrap(),
1697                value
1698            );
1699        }
1700    }
1701}