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