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