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