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