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