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