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