1#[cfg(test)]
16use std::cell::Cell;
17use std::sync::{Arc, Mutex};
18use std::time::Duration;
19
20use ahash::HashMap;
21use arrow_schema::{
22 ArrowError, DataType as ArrowDataType, Field, Schema as ArrowSchema,
23 SchemaRef as ArrowSchemaRef, SortOptions,
24};
25use async_stream::stream;
26use common_catalog::parse_catalog_and_schema_from_db_string;
27use common_error::ext::BoxedError;
28use common_plugins::GREPTIME_EXEC_READ_COST;
29use common_query::request::QueryRequest;
30use common_recordbatch::adapter::{RecordBatchMetrics, region_scan_output_bytes};
31use common_telemetry::tracing_context::TracingContext;
32use datafusion::execution::{SessionState, TaskContext};
33use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
34use datafusion::physical_plan::filter_pushdown::{
35 ChildPushdownResult, FilterPushdownPhase, FilterPushdownPropagation, PushedDown,
36};
37use datafusion::physical_plan::metrics::{
38 Count, ExecutionPlanMetricsSet, Gauge, MetricBuilder, MetricsSet, Time,
39};
40use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
41use datafusion::physical_plan::{
42 DisplayAs, DisplayFormatType, ExecutionPlan, Partitioning, PlanProperties,
43 SendableRecordBatchStream, StatisticsArgs, apply_expression_roots,
44};
45use datafusion_common::stats::Precision;
46use datafusion_common::tree_node::TreeNodeRecursion;
47use datafusion_common::{DFSchemaRef, DataFusionError, Result, Statistics};
48use datafusion_expr::{Expr, Extension, FetchType, LogicalPlan, UserDefinedLogicalNodeCore};
49use datafusion_physical_expr::{EquivalenceProperties, PhysicalSortExpr};
50use datatypes::extension::json::is_any_json_extension_type;
51use futures_util::StreamExt;
52use greptime_proto::v1::region::RegionRequestHeader;
53use meter_core::data::ReadItem;
54use meter_macros::read_meter;
55use session::context::{
56 FLIGHT_METRICS_HEARTBEAT_INTERVAL, QueryContextRef,
57 SUPPORT_FLIGHT_METRICS_BEFORE_BATCH_EXTENSION_KEY,
58};
59use store_api::metrics::{REGION_QUERY_CPU_TIME, REGION_QUERY_SCANNED_BYTES};
60use store_api::storage::RegionId;
61use table::table_name::TableName;
62use tokio::time;
63use tokio::time::Instant;
64use tracing::{Instrument, Span};
65
66use crate::dist_plan::analyzer::utils::patch_batch_timezone;
67use crate::dist_plan::dyn_filter_bridge::{
68 CapturedDynFilter, capture_remote_dyn_filters_for_pushdown,
69 query_context_with_refreshed_initial_dyn_filter_regs,
70 register_dyn_filter_subscribers_for_region, register_remote_dyn_filters,
71 resolve_scalar_subquery_dyn_filters,
72};
73use crate::dist_plan::{
74 FilterId, RemoteDynFilterProducerId, RemoteDynFilterRegistryLease, Subscriber,
75};
76use crate::metrics::{MERGE_SCAN_ERRORS_TOTAL, MERGE_SCAN_POLL_ELAPSED, MERGE_SCAN_REGIONS};
77use crate::options::{FlowQueryExtensions, remote_dyn_filter_pushdown_enabled_from_extensions};
78use crate::query_engine::QueryEngineState;
79use crate::region_query::RegionQueryHandlerRef;
80
81fn query_engine_state_from_task_context(context: &TaskContext) -> Option<Arc<QueryEngineState>> {
82 context.session_config().get_extension()
83}
84
85fn remote_plan_row_bound(plan: &LogicalPlan) -> Option<usize> {
91 match plan {
92 LogicalPlan::Limit(limit) => {
93 let input_bound = remote_plan_row_bound(&limit.input);
94 match limit.get_fetch_type() {
95 Ok(FetchType::Literal(Some(fetch))) => {
96 Some(input_bound.map_or(fetch, |bound| bound.min(fetch)))
97 }
98 _ => input_bound,
99 }
100 }
101 LogicalPlan::Sort(sort) => {
102 let input_bound = remote_plan_row_bound(&sort.input);
103 sort.fetch
104 .map(|fetch| input_bound.map_or(fetch, |bound| bound.min(fetch)))
105 .or(input_bound)
106 }
107 LogicalPlan::Projection(projection) => remote_plan_row_bound(&projection.input),
108 LogicalPlan::Filter(filter) => remote_plan_row_bound(&filter.input),
109 LogicalPlan::SubqueryAlias(alias) => remote_plan_row_bound(&alias.input),
110 LogicalPlan::Window(window) => remote_plan_row_bound(&window.input),
111 LogicalPlan::Repartition(repartition) => remote_plan_row_bound(&repartition.input),
112 LogicalPlan::Distinct(distinct) => remote_plan_row_bound(distinct.input()),
113 LogicalPlan::Aggregate(aggregate) => {
114 if aggregate
115 .group_expr
116 .iter()
117 .any(|expr| matches!(expr, Expr::GroupingSet(_)))
118 {
119 None
120 } else if aggregate.group_expr.is_empty() {
121 Some(1)
122 } else {
123 remote_plan_row_bound(&aggregate.input)
124 }
125 }
126 _ => None,
127 }
128}
129
130fn pushdown_phase_slot(phase: FilterPushdownPhase) -> usize {
131 match phase {
132 FilterPushdownPhase::Pre => 0,
133 FilterPushdownPhase::Post => 1,
134 }
135}
136
137fn first_producer_local_ordinal(phase: FilterPushdownPhase) -> usize {
140 match phase {
141 FilterPushdownPhase::Pre => 1 << 31,
142 FilterPushdownPhase::Post => 0,
143 }
144}
145
146fn remote_dyn_filter_enabled(query_ctx: &QueryContextRef) -> Result<bool> {
147 remote_dyn_filter_pushdown_enabled_from_extensions(&query_ctx.extensions())
148 .map_err(|err| DataFusionError::External(Box::new(err)))
149}
150
151fn remote_schema_mismatch(message: impl Into<String>) -> DataFusionError {
152 DataFusionError::ArrowError(Box::new(ArrowError::SchemaError(message.into())), None)
153}
154
155fn record_merge_scan_schema_error() {
156 MERGE_SCAN_ERRORS_TOTAL.inc();
157
158 #[cfg(test)]
159 TEST_MERGE_SCAN_SCHEMA_ERRORS.with(|count| count.set(count.get() + 1));
160}
161
162#[cfg(test)]
163thread_local! {
164 static TEST_MERGE_SCAN_SCHEMA_ERRORS: Cell<u64> = const { Cell::new(0) };
168}
169
170#[cfg(test)]
171fn merge_scan_schema_error_count_for_test() -> u64 {
172 TEST_MERGE_SCAN_SCHEMA_ERRORS.with(Cell::get)
173}
174
175fn is_json_field(field: &Field) -> bool {
179 is_any_json_extension_type(field)
180 || field
181 .metadata()
182 .get(datatypes::schema::TYPE_KEY)
183 .map(String::as_ref)
184 == Some("Json")
185}
186
187fn nullability_compatible(expected_field: &Field, actual_field: &Field) -> bool {
196 expected_field.is_nullable() || !actual_field.is_nullable()
197}
198
199fn fields_semantically_equal(expected_field: &Field, actual_field: &Field) -> bool {
209 expected_field.name() == actual_field.name()
210 && expected_field.data_type() == actual_field.data_type()
211 && nullability_compatible(expected_field, actual_field)
212}
213
214fn json_fields_compatible(expected_field: &Field, actual_field: &Field) -> bool {
225 is_json_field(expected_field)
226 && is_json_field(actual_field)
227 && expected_field.name() == actual_field.name()
228 && nullability_compatible(expected_field, actual_field)
229 && expected_field.metadata().get(datatypes::schema::TYPE_KEY)
231 == actual_field.metadata().get(datatypes::schema::TYPE_KEY)
232 && expected_field
234 .metadata()
235 .get(arrow_schema::extension::EXTENSION_TYPE_METADATA_KEY)
236 == actual_field
237 .metadata()
238 .get(arrow_schema::extension::EXTENSION_TYPE_METADATA_KEY)
239}
240
241fn validate_remote_schema(
254 expected: &ArrowSchema,
255 actual: &ArrowSchema,
256 source: &str,
257) -> Result<()> {
258 if expected.fields().len() != actual.fields().len() {
259 return Err(remote_schema_mismatch(format!(
260 "MergeScan {source} schema field count mismatch: expected {}, actual {}",
261 expected.fields().len(),
262 actual.fields().len()
263 )));
264 }
265
266 for (index, (expected_field, actual_field)) in expected
267 .fields()
268 .iter()
269 .zip(actual.fields().iter())
270 .enumerate()
271 {
272 if fields_semantically_equal(expected_field, actual_field) {
274 continue;
275 }
276
277 if json_fields_compatible(expected_field, actual_field) {
281 continue;
282 }
283
284 let timezone_only_difference = matches!(
286 (expected_field.data_type(), actual_field.data_type()),
287 (
288 ArrowDataType::Timestamp(expected_unit, expected_timezone),
289 ArrowDataType::Timestamp(actual_unit, actual_timezone),
290 ) if expected_unit == actual_unit
291 && expected_timezone != actual_timezone
292 && expected_field.name() == actual_field.name()
293 && nullability_compatible(expected_field, actual_field)
294 );
295 if !timezone_only_difference {
296 return Err(remote_schema_mismatch(format!(
297 "MergeScan {source} schema field mismatch at position {index}: expected {:?}, actual {:?}",
298 expected_field, actual_field
299 )));
300 }
301 }
302
303 Ok(())
304}
305
306fn acquire_remote_dyn_filter_registry_lease(
307 context: &TaskContext,
308 query_ctx: &QueryContextRef,
309 captured_dyn_filters: &[CapturedDynFilter],
310) -> Option<RemoteDynFilterRegistryLease> {
311 if captured_dyn_filters.is_empty() {
312 return None;
313 }
314
315 let query_id = query_ctx.remote_query_id_value()?;
316 let query_engine_state = query_engine_state_from_task_context(context)?;
317 Some(
318 query_engine_state
319 .dyn_filter_registry_manager()
320 .acquire_lease(query_id),
321 )
322}
323
324fn register_remote_dyn_filters_for_region(
325 remote_dyn_filter_registry_lease: Option<&RemoteDynFilterRegistryLease>,
326 captured_dyn_filters: &[CapturedDynFilter],
327) {
328 if let Some(remote_dyn_filter_registry_lease) = remote_dyn_filter_registry_lease {
329 register_remote_dyn_filters(
330 remote_dyn_filter_registry_lease.registry(),
331 captured_dyn_filters,
332 );
333 }
334}
335
336struct SubscriberRollbackGuard<'a> {
337 registry: &'a crate::dist_plan::QueryDynFilterRegistry,
338 added: Vec<(FilterId, Subscriber)>,
339}
340
341impl<'a> SubscriberRollbackGuard<'a> {
342 fn new(
343 registry: &'a crate::dist_plan::QueryDynFilterRegistry,
344 added: Vec<(FilterId, Subscriber)>,
345 ) -> Self {
346 Self { registry, added }
347 }
348
349 fn disarm(&mut self) {
350 self.added.clear();
351 }
352}
353
354impl Drop for SubscriberRollbackGuard<'_> {
355 fn drop(&mut self) {
356 for (filter_id, subscriber) in &self.added {
357 self.registry.remove_subscriber(filter_id, subscriber);
358 }
359 }
360}
361
362#[derive(Debug, Hash, PartialEq, Eq, Clone)]
363pub struct MergeScanLogicalPlan {
364 input: LogicalPlan,
366 output_schema: DFSchemaRef,
368 is_placeholder: bool,
370 remote_dyn_filter_producer_id: Option<RemoteDynFilterProducerId>,
372}
373
374impl PartialOrd for MergeScanLogicalPlan {
375 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
376 let Self {
377 input,
378 output_schema,
379 is_placeholder,
380 remote_dyn_filter_producer_id,
381 } = self;
382 let Self {
383 input: other_input,
384 output_schema: other_output_schema,
385 is_placeholder: other_is_placeholder,
386 remote_dyn_filter_producer_id: other_remote_dyn_filter_producer_id,
387 } = other;
388
389 let ordering = (input, is_placeholder, remote_dyn_filter_producer_id).partial_cmp(&(
390 other_input,
391 other_is_placeholder,
392 other_remote_dyn_filter_producer_id,
393 ));
394 match ordering {
395 Some(std::cmp::Ordering::Equal) if output_schema != other_output_schema => None,
396 ordering => ordering,
397 }
398 }
399}
400
401impl UserDefinedLogicalNodeCore for MergeScanLogicalPlan {
402 fn name(&self) -> &str {
403 Self::name()
404 }
405
406 fn inputs(&self) -> Vec<&LogicalPlan> {
409 vec![]
410 }
411
412 fn schema(&self) -> &datafusion_common::DFSchemaRef {
413 &self.output_schema
414 }
415
416 fn expressions(&self) -> Vec<datafusion_expr::Expr> {
418 vec![]
419 }
420
421 fn fmt_for_explain(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
422 write!(
423 f,
424 "MergeScan [is_placeholder={}, remote_input=[\n{}\n]]",
425 self.is_placeholder, self.input
426 )
427 }
428
429 fn with_exprs_and_inputs(
430 &self,
431 _exprs: Vec<datafusion::prelude::Expr>,
432 _inputs: Vec<LogicalPlan>,
433 ) -> Result<Self> {
434 Ok(self.clone())
435 }
436}
437
438impl MergeScanLogicalPlan {
439 pub fn new(input: LogicalPlan, is_placeholder: bool) -> Self {
441 Self {
442 output_schema: input.schema().clone(),
443 input,
444 is_placeholder,
445 remote_dyn_filter_producer_id: None,
446 }
447 }
448
449 pub(crate) fn with_output_schema(mut self, output_schema: DFSchemaRef) -> Self {
451 self.output_schema = output_schema;
452 self
453 }
454
455 pub(crate) fn with_remote_dyn_filter_producer_id(
456 mut self,
457 remote_dyn_filter_producer_id: RemoteDynFilterProducerId,
458 ) -> Self {
459 self.remote_dyn_filter_producer_id = Some(remote_dyn_filter_producer_id);
460 self
461 }
462
463 pub fn name() -> &'static str {
464 "MergeScan"
465 }
466
467 pub fn into_logical_plan(self) -> LogicalPlan {
469 LogicalPlan::Extension(Extension {
470 node: Arc::new(self),
471 })
472 }
473
474 pub fn is_placeholder(&self) -> bool {
475 self.is_placeholder
476 }
477
478 pub fn input(&self) -> &LogicalPlan {
479 &self.input
480 }
481
482 pub fn remote_dyn_filter_producer_id(&self) -> Option<RemoteDynFilterProducerId> {
483 self.remote_dyn_filter_producer_id
484 }
485}
486
487#[derive(Clone)]
488pub struct MergeScanExec {
489 table: TableName,
490 regions: Vec<RegionId>,
491 plan: LogicalPlan,
492 arrow_schema: ArrowSchemaRef,
493 region_query_handler: RegionQueryHandlerRef,
494 metric: ExecutionPlanMetricsSet,
495 properties: Arc<PlanProperties>,
496 sub_stage_metrics: Arc<Mutex<HashMap<RegionId, RecordBatchMetrics>>>,
498 partition_metrics: Arc<Mutex<HashMap<usize, PartitionMetrics>>>,
500 query_ctx: QueryContextRef,
501 remote_dyn_filter_producer_id: Option<RemoteDynFilterProducerId>,
503 captured_remote_dyn_filters: Arc<Mutex<[Vec<CapturedDynFilter>; 2]>>,
507 target_partition: usize,
508 enable_per_region_metrics: bool,
509}
510
511impl std::fmt::Debug for MergeScanExec {
512 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
513 f.debug_struct("MergeScanExec")
514 .field("table", &self.table)
515 .field("regions", &self.regions)
516 .field("plan", &self.plan)
517 .finish()
518 }
519}
520
521impl MergeScanExec {
522 #[allow(clippy::too_many_arguments)]
523 pub fn new(
524 session_state: &SessionState,
525 table: TableName,
526 regions: Vec<RegionId>,
527 plan: LogicalPlan,
528 arrow_schema: &ArrowSchema,
529 region_query_handler: RegionQueryHandlerRef,
530 query_ctx: QueryContextRef,
531 target_partition: usize,
532 remote_dyn_filter_producer_id: Option<RemoteDynFilterProducerId>,
533 enable_per_region_metrics: bool,
534 ) -> Result<Self> {
535 let arrow_schema = Arc::new(arrow_schema.clone());
536 let output_partition_count = Self::output_partition_count(regions.len(), target_partition);
537
538 let eq_properties = if let LogicalPlan::Sort(sort) = &plan
546 && output_partition_count >= regions.len()
547 {
548 let lex_ordering = sort
549 .expr
550 .iter()
551 .map(|sort_expr| {
552 let physical_expr = session_state
553 .create_physical_expr(sort_expr.expr.clone(), plan.schema())?;
554 Ok(PhysicalSortExpr::new(
555 physical_expr,
556 SortOptions {
557 descending: !sort_expr.asc,
558 nulls_first: sort_expr.nulls_first,
559 },
560 ))
561 })
562 .collect::<Result<Vec<_>>>()?;
563 EquivalenceProperties::new_with_orderings(arrow_schema.clone(), vec![lex_ordering])
564 } else {
565 EquivalenceProperties::new(arrow_schema.clone())
566 };
567
568 let partitioning = Partitioning::UnknownPartitioning(output_partition_count);
570
571 let properties = Arc::new(PlanProperties::new(
572 eq_properties,
573 partitioning,
574 EmissionType::Incremental,
575 Boundedness::Bounded,
576 ));
577 Ok(Self {
578 table,
579 regions,
580 plan,
581 arrow_schema,
582 region_query_handler,
583 metric: ExecutionPlanMetricsSet::new(),
584 sub_stage_metrics: Arc::default(),
585 partition_metrics: Arc::default(),
586 properties,
587 query_ctx,
588 remote_dyn_filter_producer_id,
589 captured_remote_dyn_filters: Arc::default(),
590 target_partition,
591 enable_per_region_metrics,
592 })
593 }
594
595 fn estimated_num_rows(&self) -> Precision<usize> {
600 if self.regions.is_empty() {
601 return Precision::Inexact(0);
602 }
603
604 let Some(rows_per_region) = remote_plan_row_bound(&self.plan) else {
605 return Precision::Absent;
606 };
607 rows_per_region
608 .checked_mul(self.regions.len())
609 .map_or(Precision::Absent, Precision::Inexact)
610 }
611
612 fn output_partition_count(num_regions: usize, target_partition: usize) -> usize {
614 num_regions.max(1).min(target_partition.max(1))
615 }
616
617 pub fn to_stream(
618 &self,
619 context: Arc<TaskContext>,
620 partition: usize,
621 ) -> Result<SendableRecordBatchStream> {
622 let regions = self.regions.clone();
624 let region_query_handler = self.region_query_handler.clone();
625 let metric = MergeScanMetric::new(&self.metric);
626 let arrow_schema = self.arrow_schema.clone();
627 let query_ctx = self.query_ctx.clone();
628 let sub_stage_metrics_moved = self.sub_stage_metrics.clone();
629 let partition_metrics_moved = self.partition_metrics.clone();
630 let plan = self.plan.clone();
631 let target_partition =
632 Self::output_partition_count(self.regions.len(), self.target_partition);
633 let remote_dyn_filter_enabled = remote_dyn_filter_enabled(&self.query_ctx)?;
634 let captured_remote_dyn_filters = if remote_dyn_filter_enabled {
635 self.captured_remote_dyn_filters()
636 } else {
637 Vec::new()
638 };
639 resolve_scalar_subquery_dyn_filters(&captured_remote_dyn_filters);
640 let dbname = context.task_id().unwrap_or_default();
641 let tracing_context = TracingContext::from_json(context.session_id().as_str());
642 let current_channel = self.query_ctx.channel();
643 let read_preference = self.query_ctx.read_preference();
644 let explain_verbose = self.query_ctx.explain_verbose();
645 let live_analyze_metrics = explain_verbose && self.query_ctx.live_analyze_metrics_enabled();
646 let remote_dyn_filter_registry_lease = acquire_remote_dyn_filter_registry_lease(
647 context.as_ref(),
648 &query_ctx,
649 &captured_remote_dyn_filters,
650 );
651
652 let stream = Box::pin(stream!({
653 let remote_dyn_filter_registry_lease = remote_dyn_filter_registry_lease;
654 if partition == 0 {
656 MERGE_SCAN_REGIONS.observe(regions.len() as f64);
657 }
658
659 let _finish_timer = metric.finish_time().timer();
660 let mut ready_timer = metric.ready_time().timer();
661 let mut first_consume_timer = Some(metric.first_consume_time().timer());
662
663 let partition_start = Instant::now();
665 let mut partition_ready_time: Option<Duration> = None;
666 let mut partition_first_consume_time: Option<Duration> = None;
667
668 for region_id in regions
669 .iter()
670 .skip(partition)
671 .step_by(target_partition)
672 .copied()
673 {
674 let region_span = tracing_context.attach(tracing::info_span!(
675 parent: &Span::current(),
676 "merge_scan_region",
677 region_id = %region_id,
678 partition = partition
679 ));
680 let region_start = Instant::now();
681 register_remote_dyn_filters_for_region(
682 remote_dyn_filter_registry_lease.as_ref(),
683 &captured_remote_dyn_filters,
684 );
685 let select_target_start = Instant::now();
686 let target = region_query_handler
687 .select_target(read_preference, region_id)
688 .instrument(region_span.clone())
689 .await
690 .map_err(|e| {
691 MERGE_SCAN_ERRORS_TOTAL.inc();
692 DataFusionError::External(Box::new(e))
693 })?;
694 let select_target_cost = select_target_start.elapsed();
695 let mut subscriber_rollback =
696 remote_dyn_filter_registry_lease.as_ref().map(|lease| {
697 SubscriberRollbackGuard::new(
698 lease.registry(),
699 register_dyn_filter_subscribers_for_region(
700 lease.registry(),
701 region_id,
702 target.clone(),
703 &captured_remote_dyn_filters,
704 ),
705 )
706 });
707 let mut region_query_ctx = query_context_with_refreshed_initial_dyn_filter_regs(
708 &query_ctx,
709 region_id,
710 &captured_remote_dyn_filters,
711 );
712 if live_analyze_metrics {
713 let remote_query_id = region_query_ctx.remote_query_id().map(str::to_string);
714 if let Some(remote_query_id) = remote_query_id {
715 region_query_ctx.set_extension(
716 SUPPORT_FLIGHT_METRICS_BEFORE_BATCH_EXTENSION_KEY,
717 remote_query_id,
718 );
719 }
720 }
721 let request = QueryRequest {
722 header: Some(RegionRequestHeader {
723 tracing_context: tracing_context.to_w3c(),
724 dbname: dbname.clone(),
725 query_context: Some((®ion_query_ctx).into()),
726 }),
727 region_id,
728 plan: plan.clone(),
729 };
730 if explain_verbose {
731 common_telemetry::info!(
732 "Merge scan one region, partition: {}, region_id: {}",
733 partition,
734 region_id
735 );
736 }
737
738 let do_get_start = Instant::now();
739 let do_get_result = region_query_handler
740 .do_get(&target, request)
741 .instrument(region_span.clone())
742 .await;
743 if do_get_result.is_err() {
744 drop(subscriber_rollback.take());
745 }
746 let mut stream = do_get_result.map_err(|e| {
747 MERGE_SCAN_ERRORS_TOTAL.inc();
748 DataFusionError::External(Box::new(BoxedError::new(e)))
749 })?;
750
751 if let Some(subscriber_rollback) = subscriber_rollback.as_mut() {
752 subscriber_rollback.disarm();
753 }
754 let mut advertised_schema = stream.schema().arrow_schema().clone();
755 validate_remote_schema(
756 arrow_schema.as_ref(),
757 advertised_schema.as_ref(),
758 "advertised remote stream",
759 )
760 .inspect_err(|_| record_merge_scan_schema_error())?;
761 let do_get_cost = select_target_cost + do_get_start.elapsed();
762
763 if let Some(remote_dyn_filter_registry_lease) =
764 remote_dyn_filter_registry_lease.as_ref()
765 {
766 remote_dyn_filter_registry_lease
767 .ensure_fanout_task(region_query_handler.clone());
768 }
769
770 ready_timer.stop();
771 if partition_ready_time.is_none() {
772 partition_ready_time = Some(partition_start.elapsed());
773 }
774
775 let mut poll_duration = Duration::ZERO;
776 let mut poll_timer = Instant::now();
777 loop {
778 let batch = if live_analyze_metrics {
779 match time::timeout(
780 FLIGHT_METRICS_HEARTBEAT_INTERVAL,
781 stream.next().instrument(region_span.clone()),
782 )
783 .await
784 {
785 Ok(batch) => batch,
786 Err(_) => {
787 if let Some(metrics) = stream.metrics() {
788 let mut sub_stage_metrics =
789 sub_stage_metrics_moved.lock().unwrap();
790 sub_stage_metrics.insert(region_id, metrics);
791 }
792 continue;
793 }
794 }
795 } else {
796 stream.next().instrument(region_span.clone()).await
797 };
798 let Some(batch) = batch else {
799 if let Some(metrics) = stream.metrics() {
803 let load = region_scan_load(&metrics);
804 let (c, s) = parse_catalog_and_schema_from_db_string(&dbname);
805 let value = read_meter!(c, s, load, current_channel as u8);
806 metric.record_greptime_exec_cost(value as usize);
807 sub_stage_metrics_moved
808 .lock()
809 .unwrap()
810 .insert(region_id, metrics);
811 }
812 break;
813 };
814 let poll_elapsed = poll_timer.elapsed();
815 poll_duration += poll_elapsed;
816
817 let batch = batch
818 .map_err(|e| DataFusionError::External(Box::new(BoxedError::new(e))))?;
819 let df_batch = batch.into_df_record_batch();
820 if !Arc::ptr_eq(&advertised_schema, df_batch.schema_ref()) {
821 validate_remote_schema(
822 arrow_schema.as_ref(),
823 df_batch.schema_ref().as_ref(),
824 "remote record batch",
825 )
826 .inspect_err(|_| record_merge_scan_schema_error())?;
827 advertised_schema = df_batch.schema_ref().clone();
828 }
829 let batch =
830 patch_batch_timezone(arrow_schema.clone(), df_batch.columns().to_vec())?;
831 metric.record_output_batch_rows(batch.num_rows());
832 if let Some(mut first_consume_timer) = first_consume_timer.take() {
833 first_consume_timer.stop();
834 partition_first_consume_time = Some(partition_start.elapsed());
835 }
836
837 if let Some(metrics) = stream.metrics() {
838 let mut sub_stage_metrics = sub_stage_metrics_moved.lock().unwrap();
839 sub_stage_metrics.insert(region_id, metrics);
840 }
841
842 yield Ok(batch);
843 poll_timer = Instant::now();
845 }
846 if let Some(mut first_consume_timer) = first_consume_timer.take() {
849 first_consume_timer.stop();
850 partition_first_consume_time = Some(partition_start.elapsed());
851 }
852 let total_cost = region_start.elapsed();
853
854 let region_metrics = RegionMetrics {
856 region_id,
857 poll_duration,
858 do_get_cost,
859 total_cost,
860 };
861
862 {
864 let mut partition_metrics_guard = partition_metrics_moved.lock().unwrap();
865 let partition_metrics = partition_metrics_guard
866 .entry(partition)
867 .or_insert_with(|| PartitionMetrics::new(partition, explain_verbose));
868 partition_metrics.add_region_metrics(region_metrics);
869 }
870
871 if explain_verbose {
872 common_telemetry::info!(
873 "Merge scan finish one region, partition: {}, region_id: {}, poll_duration: {:?}, first_consume: {}, do_get_cost: {:?}",
874 partition,
875 region_id,
876 poll_duration,
877 metric.first_consume_time(),
878 do_get_cost
879 );
880 }
881
882 MERGE_SCAN_POLL_ELAPSED.observe(poll_duration.as_secs_f64());
883 }
884
885 ready_timer.stop();
888 if let Some(mut first_consume_timer) = first_consume_timer.take() {
889 first_consume_timer.stop();
890 }
891
892 let partition_finish_time = partition_start.elapsed();
894 {
895 let mut partition_metrics_guard = partition_metrics_moved.lock().unwrap();
896 if let Some(partition_metrics) = partition_metrics_guard.get_mut(&partition) {
897 partition_metrics.set_timings(
898 partition_ready_time.unwrap_or_default(),
899 partition_first_consume_time.unwrap_or_default(),
900 partition_finish_time,
901 );
902 partition_metrics.finish();
903 }
904 }
905 }));
906
907 Ok(Box::pin(RecordBatchStreamAdapter::new(
908 self.arrow_schema.clone(),
909 stream,
910 )))
911 }
912
913 fn captured_remote_dyn_filters(&self) -> Vec<CapturedDynFilter> {
914 self.captured_remote_dyn_filters.lock().unwrap().concat()
915 }
916
917 pub fn sub_stage_metrics(&self) -> Vec<RecordBatchMetrics> {
918 let sub_stage_metrics = self.sub_stage_metrics.lock().unwrap();
919 let mut metrics: Vec<_> = sub_stage_metrics.iter().collect();
920 metrics.sort_unstable_by_key(|(region_id, _)| **region_id);
921 metrics
922 .into_iter()
923 .map(|(_, metrics)| metrics.clone())
924 .collect()
925 }
926
927 pub fn regions(&self) -> &[RegionId] {
928 &self.regions
929 }
930
931 pub fn is_flow_sink_scan(&self) -> bool {
932 let Some(sink_table_id) =
933 FlowQueryExtensions::parse_flow_extensions(&self.query_ctx.extensions())
934 .ok()
935 .flatten()
936 .and_then(|extensions| extensions.sink_table_id)
937 else {
938 return false;
939 };
940
941 !self.regions.is_empty()
942 && self
943 .regions
944 .iter()
945 .all(|region_id| region_id.table_id() == sink_table_id)
946 }
947
948 pub fn partition_count(&self) -> usize {
949 Self::output_partition_count(self.regions.len(), self.target_partition)
950 }
951
952 pub fn region_count(&self) -> usize {
953 self.regions.len()
954 }
955
956 fn partition_metrics(&self) -> Vec<PartitionMetrics> {
957 self.partition_metrics
958 .lock()
959 .unwrap()
960 .values()
961 .cloned()
962 .collect()
963 }
964}
965
966#[derive(Debug, Clone)]
968struct RegionMetrics {
969 region_id: RegionId,
970 poll_duration: Duration,
971 do_get_cost: Duration,
972 total_cost: Duration,
974}
975
976#[derive(Debug, Clone)]
978struct PartitionMetrics {
979 partition: usize,
980 region_metrics: Vec<RegionMetrics>,
981 total_poll_duration: Duration,
982 total_do_get_cost: Duration,
983 total_regions: usize,
984 ready_time: Duration,
986 first_consume_time: Duration,
988 finish_time: Duration,
990 explain_verbose: bool,
991 finished: bool,
992}
993
994impl PartitionMetrics {
995 fn new(partition: usize, explain_verbose: bool) -> Self {
996 Self {
997 partition,
998 region_metrics: Vec::new(),
999 total_poll_duration: Duration::ZERO,
1000 total_do_get_cost: Duration::ZERO,
1001 total_regions: 0,
1002 ready_time: Duration::ZERO,
1003 first_consume_time: Duration::ZERO,
1004 finish_time: Duration::ZERO,
1005 explain_verbose,
1006 finished: false,
1007 }
1008 }
1009
1010 fn add_region_metrics(&mut self, region_metrics: RegionMetrics) {
1011 self.total_poll_duration += region_metrics.poll_duration;
1012 self.total_do_get_cost += region_metrics.do_get_cost;
1013 self.total_regions += 1;
1014 self.region_metrics.push(region_metrics);
1015 }
1016
1017 fn set_timings(
1019 &mut self,
1020 ready_time: Duration,
1021 first_consume_time: Duration,
1022 finish_time: Duration,
1023 ) {
1024 self.ready_time = ready_time;
1025 self.first_consume_time = first_consume_time;
1026 self.finish_time = finish_time;
1027 }
1028
1029 fn finish(&mut self) {
1031 if self.finished {
1032 return;
1033 }
1034 self.finished = true;
1035 self.log_metrics();
1036 }
1037
1038 fn log_metrics(&self) {
1040 if self.explain_verbose {
1041 common_telemetry::info!(
1042 "MergeScan partition {} finished: {} regions, total_poll_duration: {:?}, total_do_get_cost: {:?}, ready_time: {:?}, first_consume_time: {:?}, finish_time: {:?}",
1043 self.partition,
1044 self.total_regions,
1045 self.total_poll_duration,
1046 self.total_do_get_cost,
1047 self.ready_time,
1048 self.first_consume_time,
1049 self.finish_time
1050 );
1051 } else {
1052 common_telemetry::debug!(
1053 "MergeScan partition {} finished: {} regions, total_poll_duration: {:?}, total_do_get_cost: {:?}, ready_time: {:?}, first_consume_time: {:?}, finish_time: {:?}",
1054 self.partition,
1055 self.total_regions,
1056 self.total_poll_duration,
1057 self.total_do_get_cost,
1058 self.ready_time,
1059 self.first_consume_time,
1060 self.finish_time
1061 );
1062 }
1063 }
1064}
1065
1066impl Drop for PartitionMetrics {
1067 fn drop(&mut self) {
1068 if !self.finished {
1069 self.log_metrics();
1070 }
1071 }
1072}
1073
1074impl ExecutionPlan for MergeScanExec {
1075 fn schema(&self) -> ArrowSchemaRef {
1076 self.arrow_schema.clone()
1077 }
1078
1079 fn properties(&self) -> &Arc<PlanProperties> {
1080 &self.properties
1081 }
1082
1083 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
1084 vec![]
1085 }
1086
1087 fn apply_expressions(
1088 &self,
1089 f: &mut dyn FnMut(
1090 &Arc<dyn datafusion_physical_expr::PhysicalExpr>,
1091 ) -> Result<TreeNodeRecursion>,
1092 ) -> Result<TreeNodeRecursion> {
1093 let captured_remote_dyn_filters = self.captured_remote_dyn_filters();
1094 apply_expression_roots(
1095 captured_remote_dyn_filters
1096 .into_iter()
1097 .map(|captured_dyn_filter| {
1098 captured_dyn_filter.alive_dyn_filter
1099 as Arc<dyn datafusion_physical_expr::PhysicalExpr>
1100 }),
1101 f,
1102 )
1103 }
1104
1105 fn with_new_children(
1108 self: Arc<Self>,
1109 _children: Vec<Arc<dyn ExecutionPlan>>,
1110 ) -> Result<Arc<dyn ExecutionPlan>> {
1111 Ok(self.clone())
1112 }
1113
1114 fn handle_child_pushdown_result(
1115 &self,
1116 phase: FilterPushdownPhase,
1117 child_pushdown_result: ChildPushdownResult,
1118 _config: &datafusion::config::ConfigOptions,
1119 ) -> Result<FilterPushdownPropagation<Arc<dyn ExecutionPlan>>> {
1120 let parent_filters = child_pushdown_result
1121 .parent_filters
1122 .into_iter()
1123 .map(|filter| filter.filter)
1124 .collect::<Vec<_>>();
1125
1126 if !remote_dyn_filter_enabled(&self.query_ctx)? {
1127 self.captured_remote_dyn_filters.lock().unwrap()[pushdown_phase_slot(phase)].clear();
1131 let new_self = Arc::new(self.clone());
1132
1133 return Ok(FilterPushdownPropagation {
1134 filters: parent_filters.into_iter().map(|_| PushedDown::No).collect(),
1135 updated_node: Some(new_self),
1136 });
1137 }
1138
1139 let Some(remote_dyn_filter_producer_id) = self.remote_dyn_filter_producer_id else {
1140 common_telemetry::warn!(
1142 "MergeScan remote dynamic filter producer id is not assigned; skipping remote dynamic filter pushdown"
1143 );
1144 self.captured_remote_dyn_filters.lock().unwrap()[pushdown_phase_slot(phase)].clear();
1145 let new_self = Arc::new(self.clone());
1146
1147 return Ok(FilterPushdownPropagation {
1148 filters: parent_filters.into_iter().map(|_| PushedDown::No).collect(),
1149 updated_node: Some(new_self),
1150 });
1151 };
1152 let slot = pushdown_phase_slot(phase);
1153 let mut captured_remote_dyn_filters = self.captured_remote_dyn_filters.lock().unwrap();
1154 let remote_dyn_filter_pushdown = capture_remote_dyn_filters_for_pushdown(
1155 remote_dyn_filter_producer_id,
1156 first_producer_local_ordinal(phase),
1157 &captured_remote_dyn_filters[1 - slot],
1158 parent_filters,
1159 );
1160 captured_remote_dyn_filters[slot] = remote_dyn_filter_pushdown.captured_dyn_filters;
1161 drop(captured_remote_dyn_filters);
1162 let new_self = Arc::new(self.clone());
1163
1164 Ok(FilterPushdownPropagation {
1165 filters: remote_dyn_filter_pushdown.pushed_down,
1166 updated_node: Some(new_self),
1167 })
1168 }
1169
1170 fn execute(
1171 &self,
1172 partition: usize,
1173 context: Arc<TaskContext>,
1174 ) -> Result<SendableRecordBatchStream> {
1175 self.to_stream(context, partition)
1176 }
1177
1178 fn metrics(&self) -> Option<MetricsSet> {
1179 Some(self.metric.clone_inner())
1180 }
1181
1182 fn statistics_from_inputs(
1183 &self,
1184 _input_stats: &[Arc<Statistics>],
1185 args: &StatisticsArgs,
1186 ) -> Result<Arc<Statistics>> {
1187 if args.partition().is_some() {
1188 return Ok(Arc::new(Statistics::new_unknown(&self.arrow_schema)));
1189 }
1190
1191 let mut statistics = Statistics::new_unknown(&self.arrow_schema);
1192 statistics.num_rows = self.estimated_num_rows();
1193 Ok(Arc::new(statistics))
1194 }
1195
1196 fn name(&self) -> &str {
1197 "MergeScanExec"
1198 }
1199}
1200
1201impl DisplayAs for MergeScanExec {
1202 fn fmt_as(&self, t: DisplayFormatType, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1203 write!(f, "MergeScanExec: peers=[")?;
1204 for region_id in self.regions.iter() {
1205 write!(f, "{}, ", region_id)?;
1206 }
1207 write!(f, "]")?;
1208
1209 if matches!(t, DisplayFormatType::Verbose) {
1210 let partition_metrics = self.partition_metrics();
1211 if !partition_metrics.is_empty() {
1212 write!(f, ", metrics={{")?;
1213 for (i, pm) in partition_metrics.iter().enumerate() {
1214 if i > 0 {
1215 write!(f, ", ")?;
1216 }
1217 write!(
1218 f,
1219 "\"partition_{}\":{{\"regions\":{},\"total_poll_duration\":\"{:?}\",\"total_do_get_cost\":\"{:?}\",\"ready_time\":\"{:?}\",\"first_consume_time\":\"{:?}\",\"finish_time\":\"{:?}\",\"region_metrics\":[",
1220 pm.partition,
1221 pm.total_regions,
1222 pm.total_poll_duration,
1223 pm.total_do_get_cost,
1224 pm.ready_time,
1225 pm.first_consume_time,
1226 pm.finish_time
1227 )?;
1228 for (j, rm) in pm.region_metrics.iter().enumerate() {
1229 if j > 0 {
1230 write!(f, ",")?;
1231 }
1232 write!(
1233 f,
1234 "{{\"region_id\":\"{}\",\"poll_duration\":\"{:?}\",\"do_get_cost\":\"{:?}\",\"total_cost\":\"{:?}\"}}",
1235 rm.region_id, rm.poll_duration, rm.do_get_cost, rm.total_cost
1236 )?;
1237 }
1238 write!(f, "]}}")?;
1239 }
1240 write!(f, "}}")?;
1241 }
1242 }
1243
1244 Ok(())
1245 }
1246}
1247
1248fn region_scan_load(metrics: &RecordBatchMetrics) -> ReadItem {
1249 ReadItem {
1250 cpu_time: metrics.elapsed_compute as u64,
1251 table_scan: region_scan_output_bytes(metrics) as u64,
1252 }
1253}
1254
1255fn report_region_query_load(region_id: RegionId, load: &ReadItem) {
1256 let region_id = region_id.to_string();
1257 REGION_QUERY_CPU_TIME
1258 .with_label_values(&[®ion_id])
1259 .inc_by(load.cpu_time);
1260 REGION_QUERY_SCANNED_BYTES
1261 .with_label_values(&[®ion_id])
1262 .inc_by(load.table_scan);
1263}
1264
1265fn query_load_region_id(default_region_id: RegionId, metrics: &RecordBatchMetrics) -> RegionId {
1266 metrics
1267 .query_load_region_id
1268 .map(RegionId::from_u64)
1269 .unwrap_or(default_region_id)
1270}
1271
1272impl Drop for MergeScanExec {
1273 fn drop(&mut self) {
1274 if !self.enable_per_region_metrics {
1278 return;
1279 }
1280
1281 let metrics = self.sub_stage_metrics.lock().unwrap();
1282 for (region_id, metrics) in metrics.iter() {
1283 let load = region_scan_load(metrics);
1284 report_region_query_load(query_load_region_id(*region_id, metrics), &load);
1285 }
1286 }
1287}
1288
1289#[derive(Debug, Clone)]
1290struct MergeScanMetric {
1291 ready_time: Time,
1293 first_consume_time: Time,
1295 finish_time: Time,
1297 output_rows: Count,
1299
1300 greptime_exec_cost: Gauge,
1302}
1303
1304impl MergeScanMetric {
1305 pub fn new(metric: &ExecutionPlanMetricsSet) -> Self {
1306 Self {
1307 ready_time: MetricBuilder::new(metric).subset_time("ready_time", 1),
1308 first_consume_time: MetricBuilder::new(metric).subset_time("first_consume_time", 1),
1309 finish_time: MetricBuilder::new(metric).subset_time("finish_time", 1),
1310 output_rows: MetricBuilder::new(metric).output_rows(1),
1311 greptime_exec_cost: MetricBuilder::new(metric).gauge(GREPTIME_EXEC_READ_COST, 1),
1312 }
1313 }
1314
1315 pub fn ready_time(&self) -> &Time {
1316 &self.ready_time
1317 }
1318
1319 pub fn first_consume_time(&self) -> &Time {
1320 &self.first_consume_time
1321 }
1322
1323 pub fn finish_time(&self) -> &Time {
1324 &self.finish_time
1325 }
1326
1327 pub fn record_output_batch_rows(&self, num_rows: usize) {
1328 self.output_rows.add(num_rows);
1329 }
1330
1331 pub fn record_greptime_exec_cost(&self, metrics: usize) {
1332 self.greptime_exec_cost.add(metrics);
1333 }
1334}
1335
1336#[cfg(test)]
1337mod tests {
1338 use std::collections::HashMap as StdHashMap;
1339 use std::pin::Pin;
1340 use std::sync::atomic::{AtomicUsize, Ordering};
1341 use std::task::{Context, Poll};
1342
1343 use arrow::array::{Int64Array, TimestampMillisecondArray};
1344 use arrow_schema::{DataType as TestArrowDataType, Field, TimeUnit};
1345 use async_trait::async_trait;
1346 use common_base::Plugins;
1347 use common_error::ext::{ErrorExt, PlainError};
1348 use common_error::status_code::StatusCode;
1349 use common_meta::peer::Peer;
1350 use common_query::request::{
1351 INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY, InitialDynFilterRegs,
1352 };
1353 use common_recordbatch::adapter::{PlanMetrics, RecordBatchMetrics};
1354 use common_recordbatch::{
1355 DfRecordBatch, EmptyRecordBatchStream, RecordBatch, RecordBatchStream,
1356 };
1357 use datafusion::config::ConfigOptions;
1358 use datafusion::execution::SessionStateBuilder;
1359 use datafusion::physical_plan::Partitioning;
1360 use datafusion::physical_plan::filter_pushdown::ChildFilterPushdownResult;
1361 use datafusion::physical_plan::repartition::RepartitionExec;
1362 use datafusion::physical_plan::{StatisticsArgs, StatisticsContext};
1363 use datafusion_expr::{LogicalPlanBuilder, col, lit};
1364 use datafusion_physical_expr::PhysicalExpr;
1365 use datafusion_physical_expr::expressions::{
1366 Column, DynamicFilterPhysicalExpr, lit as physical_lit,
1367 };
1368 use datatypes::prelude::{ConcreteDataType, VectorRef};
1369 use datatypes::schema::{ColumnSchema, Schema};
1370 use datatypes::vectors::{Int64Vector, StringVector, TimestampMillisecondVector};
1371 use futures_util::{Stream, TryStreamExt};
1372 use session::ReadPreference;
1373 use session::context::QueryContext;
1374 use session::query_id::QueryId;
1375 use snafu::IntoError;
1376 use table::table::scan::REGION_SCAN_EXEC_NAME;
1377 use table::table_name::TableName;
1378 use tokio::sync::{Notify, oneshot};
1379 use uuid::Uuid;
1380
1381 use super::*;
1382 use crate::dist_plan::DynFilterRegistryManager;
1383 use crate::options::QueryOptions;
1384 use crate::query_engine::{QueryEngineContext, QueryEngineState};
1385 use crate::region_query::RegionQueryHandler;
1386
1387 fn test_target(id: u64) -> crate::region_query::RegionQueryTarget {
1388 crate::region_query::RegionQueryTarget::new(Peer {
1389 id,
1390 addr: format!("127.0.0.1:{id}"),
1391 })
1392 }
1393
1394 fn test_query_id(value: u128) -> QueryId {
1395 QueryId::from(Uuid::from_u128(value))
1396 }
1397
1398 fn merge_scan_exec_with_sorted_input(
1399 region_count: u64,
1400 target_partition: usize,
1401 ) -> MergeScanExec {
1402 let plan = LogicalPlanBuilder::empty(true)
1403 .project(vec![lit(1i64).alias("ts")])
1404 .unwrap()
1405 .sort(vec![col("ts").sort(false, true)])
1406 .unwrap()
1407 .build()
1408 .unwrap();
1409 let regions = (0..region_count)
1410 .map(|region_number| RegionId::new(1024, region_number as u32))
1411 .collect();
1412
1413 merge_scan_exec_with_plan(regions, plan, target_partition)
1414 }
1415
1416 fn merge_scan_exec_with_plan(
1417 regions: Vec<RegionId>,
1418 plan: LogicalPlan,
1419 target_partition: usize,
1420 ) -> MergeScanExec {
1421 let session_state = SessionStateBuilder::new().build();
1422 let schema = plan.schema().as_arrow().clone();
1423
1424 MergeScanExec::new(
1425 &session_state,
1426 TableName::new("catalog", "schema", "table"),
1429 regions,
1430 plan,
1431 &schema,
1432 Arc::new(TestRegionQueryHandler::default()),
1433 QueryContext::arc(),
1434 target_partition,
1435 None,
1436 false,
1437 )
1438 .unwrap()
1439 }
1440
1441 fn merge_scan_statistics(exec: &MergeScanExec) -> Arc<Statistics> {
1442 StatisticsContext::new()
1443 .compute(exec, &StatisticsArgs::new())
1444 .unwrap()
1445 }
1446
1447 fn task_context_with_engine_state(
1448 state: Arc<QueryEngineState>,
1449 query_ctx: QueryContextRef,
1450 ) -> Arc<TaskContext> {
1451 let mut session_state = state.session_state();
1452 session_state.config_mut().set_extension(state);
1453 QueryEngineContext::new(session_state, query_ctx).build_task_ctx()
1454 }
1455
1456 fn remote_dyn_filter_test_exec(
1457 handler: crate::region_query::RegionQueryHandlerRef,
1458 query_ctx: QueryContextRef,
1459 ) -> MergeScanExec {
1460 let session_state = SessionStateBuilder::new().build();
1461 let plan = LogicalPlanBuilder::empty(true)
1462 .project(vec![lit(1i32).alias("col1")])
1463 .unwrap()
1464 .build()
1465 .unwrap();
1466 let schema = plan.schema().as_arrow().clone();
1467
1468 MergeScanExec::new(
1469 &session_state,
1470 TableName::new("catalog", "schema", "table"),
1471 vec![RegionId::new(1024, 1)],
1472 plan,
1473 &schema,
1474 handler,
1475 query_ctx,
1476 1,
1477 Some(RemoteDynFilterProducerId::new(42)),
1478 false,
1479 )
1480 .unwrap()
1481 }
1482
1483 fn install_remote_dyn_filter(exec: &MergeScanExec) -> Arc<DynamicFilterPhysicalExpr> {
1484 let dyn_filter = Arc::new(DynamicFilterPhysicalExpr::new(
1485 vec![Arc::new(Column::new("host", 0)) as Arc<_>],
1486 physical_lit(true) as _,
1487 ));
1488 exec.handle_child_pushdown_result(
1489 FilterPushdownPhase::Post,
1490 ChildPushdownResult {
1491 parent_filters: vec![ChildFilterPushdownResult {
1492 filter: dyn_filter.clone() as Arc<dyn datafusion_physical_expr::PhysicalExpr>,
1493 child_results: vec![PushedDown::Yes],
1494 }],
1495 self_filters: Vec::new(),
1496 },
1497 &ConfigOptions::new(),
1498 )
1499 .unwrap();
1500 dyn_filter
1501 }
1502
1503 fn query_engine_state(
1504 handler: crate::region_query::RegionQueryHandlerRef,
1505 ) -> Arc<QueryEngineState> {
1506 Arc::new(QueryEngineState::new(
1507 catalog::memory::new_memory_catalog_manager().unwrap(),
1508 None,
1509 Some(handler),
1510 None,
1511 None,
1512 None,
1513 false,
1514 Plugins::default(),
1515 QueryOptions::default(),
1516 ))
1517 }
1518
1519 fn empty_record_batch_stream(
1520 request: &common_query::request::QueryRequest,
1521 ) -> common_recordbatch::SendableRecordBatchStream {
1522 let arrow_schema = request.plan.schema().as_arrow().clone();
1523 Box::pin(EmptyRecordBatchStream::new(Arc::new(
1524 datatypes::schema::Schema::try_from(Arc::new(arrow_schema)).unwrap(),
1525 )))
1526 }
1527
1528 fn pending_record_batch_stream(
1529 request: &common_query::request::QueryRequest,
1530 ) -> common_recordbatch::SendableRecordBatchStream {
1531 let stream = futures_util::stream::pending::<
1532 datafusion_common::Result<datafusion::arrow::record_batch::RecordBatch>,
1533 >();
1534 let arrow_schema = request.plan.schema().as_arrow().clone();
1535 let stream = RecordBatchStreamAdapter::new(Arc::new(arrow_schema), stream);
1536 Box::pin(
1537 common_recordbatch::adapter::RecordBatchStreamAdapter::try_new(Box::pin(stream))
1538 .unwrap(),
1539 )
1540 }
1541
1542 #[test]
1543 fn merge_scan_does_not_advertise_ordering_when_partition_may_merge_regions() {
1544 let exec = merge_scan_exec_with_sorted_input(3, 2);
1545
1546 assert!(
1547 exec.properties().output_ordering().is_none(),
1548 "target_partition < region_count means one output partition may concatenate multiple sorted region streams"
1549 );
1550 }
1551
1552 #[test]
1553 fn merge_scan_advertises_ordering_when_each_partition_reads_at_most_one_region() {
1554 let exec = merge_scan_exec_with_sorted_input(3, 3);
1555
1556 assert!(exec.properties().output_ordering().is_some());
1557 }
1558
1559 #[test]
1560 fn merge_scan_advertises_ordering_when_partitions_exceed_regions() {
1561 let exec = merge_scan_exec_with_sorted_input(3, 4);
1562
1563 assert!(exec.properties().output_ordering().is_some());
1564 }
1565
1566 #[test]
1567 fn merge_scan_reports_populated_partition_count() {
1568 let cases = [(0, 10, 1), (1, 10, 1), (3, 2, 2), (5, 10, 5), (3, 0, 1)];
1569
1570 for (region_count, target, expected) in cases {
1571 let exec = merge_scan_exec_with_sorted_input(region_count, target);
1572 assert_eq!(exec.partition_count(), expected);
1573 assert_eq!(
1574 exec.properties().output_partitioning().partition_count(),
1575 expected
1576 );
1577 assert!(matches!(
1578 exec.properties().output_partitioning(),
1579 Partitioning::UnknownPartitioning(count) if *count == expected
1580 ));
1581 }
1582 }
1583
1584 #[test]
1585 fn merge_scan_reports_only_deterministic_plan_bounds() {
1586 use datafusion::functions_aggregate::expr_fn::count;
1587 use datafusion_expr::GroupingSet;
1588
1589 let regions = vec![RegionId::new(1024, 1), RegionId::new(1024, 2)];
1590 let limited = LogicalPlanBuilder::empty(true)
1591 .project(vec![lit(1i64).alias("col")])
1592 .unwrap()
1593 .limit(0, Some(50))
1594 .unwrap()
1595 .build()
1596 .unwrap();
1597 assert_eq!(
1598 merge_scan_statistics(&merge_scan_exec_with_plan(regions.clone(), limited, 10))
1599 .num_rows,
1600 Precision::Inexact(100)
1601 );
1602
1603 let large_bound = i32::MAX as usize + 1;
1604 let large_limit = LogicalPlanBuilder::empty(true)
1605 .project(vec![lit(1i64).alias("col")])
1606 .unwrap()
1607 .limit(0, Some(large_bound))
1608 .unwrap()
1609 .build()
1610 .unwrap();
1611 assert_eq!(
1612 merge_scan_statistics(&merge_scan_exec_with_plan(
1613 vec![RegionId::new(1024, 1)],
1614 large_limit,
1615 10,
1616 ))
1617 .num_rows,
1618 Precision::Inexact(large_bound)
1619 );
1620
1621 let uncapped = LogicalPlanBuilder::empty(true)
1622 .project(vec![lit(1i64).alias("col")])
1623 .unwrap()
1624 .build()
1625 .unwrap();
1626 assert_eq!(
1627 merge_scan_statistics(&merge_scan_exec_with_plan(
1628 regions.clone(),
1629 uncapped.clone(),
1630 10,
1631 ))
1632 .num_rows,
1633 Precision::Absent
1634 );
1635 assert_eq!(
1636 merge_scan_statistics(&merge_scan_exec_with_plan(Vec::new(), uncapped, 10)).num_rows,
1637 Precision::Inexact(0)
1638 );
1639
1640 let global_aggregate = LogicalPlanBuilder::empty(true)
1641 .project(vec![lit(1i64).alias("col")])
1642 .unwrap()
1643 .limit(0, Some(0))
1644 .unwrap()
1645 .aggregate(Vec::<Expr>::new(), vec![count(lit(1))])
1646 .unwrap()
1647 .build()
1648 .unwrap();
1649 assert_eq!(
1650 merge_scan_statistics(&merge_scan_exec_with_plan(
1651 regions.clone(),
1652 global_aggregate,
1653 10,
1654 ))
1655 .num_rows,
1656 Precision::Inexact(2)
1657 );
1658
1659 let grouping_sets = LogicalPlanBuilder::empty(true)
1660 .project(vec![lit(1i64).alias("col")])
1661 .unwrap()
1662 .limit(0, Some(50))
1663 .unwrap()
1664 .aggregate(
1665 vec![Expr::GroupingSet(GroupingSet::GroupingSets(vec![
1666 vec![],
1667 vec![col("col")],
1668 ]))],
1669 Vec::<Expr>::new(),
1670 )
1671 .unwrap()
1672 .build()
1673 .unwrap();
1674 assert_eq!(
1675 merge_scan_statistics(&merge_scan_exec_with_plan(regions, grouping_sets, 10)).num_rows,
1676 Precision::Absent
1677 );
1678 }
1679
1680 #[test]
1681 fn sub_stage_metrics_are_sorted_by_region_id() {
1682 let exec = merge_scan_exec_with_sorted_input(0, 1);
1683 let higher_region = RegionId::new(1024, 2);
1684 let lower_region = RegionId::new(1024, 1);
1685 let higher_metrics = RecordBatchMetrics {
1686 plan_metrics: vec![PlanMetrics {
1687 plan: "higher region".to_string(),
1688 plan_name: "higher region".to_string(),
1689 level: 0,
1690 metrics: Vec::new(),
1691 }],
1692 ..Default::default()
1693 };
1694 let lower_metrics = RecordBatchMetrics {
1695 plan_metrics: vec![PlanMetrics {
1696 plan: "lower region".to_string(),
1697 plan_name: "lower region".to_string(),
1698 level: 0,
1699 metrics: Vec::new(),
1700 }],
1701 ..Default::default()
1702 };
1703
1704 let mut sub_stage_metrics = exec.sub_stage_metrics.lock().unwrap();
1705 sub_stage_metrics.insert(higher_region, higher_metrics);
1706 sub_stage_metrics.insert(lower_region, lower_metrics);
1707 drop(sub_stage_metrics);
1708
1709 let metrics = exec.sub_stage_metrics();
1710 let plans: Vec<_> = metrics
1711 .iter()
1712 .map(|metrics| metrics.plan_metrics[0].plan.as_str())
1713 .collect();
1714
1715 assert_eq!(plans, ["lower region", "higher region"]);
1716 }
1717
1718 #[test]
1719 fn remote_dyn_filter_producer_registration_defers_subscriber_registration() {
1720 let registry_manager = Arc::new(DynFilterRegistryManager::default());
1721 let query_ctx = QueryContext::arc();
1722 let query_id = query_ctx
1723 .remote_query_id_value()
1724 .expect("query context must have remote query id");
1725 let lease = registry_manager.acquire_lease(query_id);
1726 let region_id = RegionId::new(1024, 7);
1727 let dyn_filter = Arc::new(DynamicFilterPhysicalExpr::new(
1728 vec![Arc::new(Column::new("host", 0)) as Arc<_>],
1729 physical_lit(true) as _,
1730 )) as Arc<dyn datafusion_physical_expr::PhysicalExpr>;
1731 let captured = capture_remote_dyn_filters_for_pushdown(
1732 RemoteDynFilterProducerId::new(42),
1733 0,
1734 &[],
1735 vec![dyn_filter],
1736 );
1737 assert_eq!(captured.captured_dyn_filters.len(), 1);
1738
1739 register_remote_dyn_filters_for_region(Some(&lease), &captured.captured_dyn_filters);
1740 let region_query_ctx = query_context_with_refreshed_initial_dyn_filter_regs(
1741 &query_ctx,
1742 region_id,
1743 &captured.captured_dyn_filters,
1744 );
1745
1746 let entries = lease.registry().entries();
1747 assert_eq!(entries.len(), 1);
1748 assert!(entries[0].subscribers().is_empty());
1749 assert!(
1750 !entries[0].fanout_started_for_test(),
1751 "fanout must start only after do_get succeeds"
1752 );
1753 assert!(
1754 region_query_ctx
1755 .extension(INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY)
1756 .is_some(),
1757 "initial RDF registrations must be present in the do_get query context"
1758 );
1759 }
1760
1761 #[tokio::test]
1762 async fn failed_do_get_preserves_status_code_and_rolls_back_subscriber() {
1763 let handler = Arc::new(FailingRegionQueryHandler::default());
1764 let query_ctx = QueryContext::arc();
1765 let state = Arc::new(QueryEngineState::new(
1766 catalog::memory::new_memory_catalog_manager().unwrap(),
1767 None,
1768 Some(handler.clone()),
1769 None,
1770 None,
1771 None,
1772 false,
1773 Plugins::default(),
1774 QueryOptions::default(),
1775 ));
1776 let lease = state
1777 .acquire_remote_dyn_filter_registry_lease(&query_ctx)
1778 .unwrap();
1779 handler.set_registry(lease.registry_arc_for_test());
1780 let task_ctx = task_context_with_engine_state(state, query_ctx.clone());
1781 let plan = LogicalPlanBuilder::empty(true)
1782 .project(vec![lit(1i32).alias("col1")])
1783 .unwrap()
1784 .build()
1785 .unwrap();
1786 let schema = plan.schema().as_arrow().clone();
1787 let exec = MergeScanExec::new(
1788 &SessionStateBuilder::new().build(),
1789 TableName::new("catalog", "schema", "table"),
1790 vec![RegionId::new(1024, 1)],
1791 plan,
1792 &schema,
1793 handler.clone(),
1794 query_ctx,
1795 1,
1796 Some(RemoteDynFilterProducerId::new(42)),
1797 false,
1798 )
1799 .unwrap();
1800 let dyn_filter = Arc::new(DynamicFilterPhysicalExpr::new(
1801 vec![Arc::new(Column::new("host", 0)) as Arc<_>],
1802 physical_lit(true) as _,
1803 )) as Arc<dyn datafusion_physical_expr::PhysicalExpr>;
1804 exec.handle_child_pushdown_result(
1805 FilterPushdownPhase::Post,
1806 ChildPushdownResult {
1807 parent_filters: vec![ChildFilterPushdownResult {
1808 filter: dyn_filter,
1809 child_results: vec![PushedDown::Yes],
1810 }],
1811 self_filters: Vec::new(),
1812 },
1813 &ConfigOptions::new(),
1814 )
1815 .unwrap();
1816
1817 let mut stream = common_recordbatch::adapter::RecordBatchStreamAdapter::try_new(
1818 exec.to_stream(task_ctx, 0).unwrap(),
1819 )
1820 .unwrap();
1821 let error = stream.next().await.unwrap().unwrap_err();
1822 assert_eq!(error.status_code(), StatusCode::RequestOutdated);
1823 assert_eq!(handler.do_get_calls.load(Ordering::SeqCst), 1);
1824 assert!(handler.saw_subscriber.load(Ordering::SeqCst));
1825
1826 let entries = lease.registry().entries();
1827 assert_eq!(entries.len(), 1);
1828 assert!(entries[0].subscribers().is_empty());
1829 assert!(!entries[0].fanout_started_for_test());
1830 }
1831
1832 #[tokio::test]
1833 async fn repartitioned_merge_scan_later_stream_error_preserves_status_code() {
1834 let region_id = RegionId::new(1024, 1);
1835 let handler = Arc::new(TestRegionQueryHandler::with_responses(vec![(
1836 region_id,
1837 int64_schema(&["a", "b"]),
1838 vec![Err(common_recordbatch::error::ExternalSnafu.into_error(
1839 BoxedError::new(PlainError::new(
1840 "neutral stream error".to_string(),
1841 StatusCode::RequestOutdated,
1842 )),
1843 ))],
1844 )]));
1845 let merge_scan = Arc::new(merge_scan_exec_with_handler(
1846 vec![region_id],
1847 expected_int64_schema(),
1848 handler,
1849 1,
1850 ));
1851 let repartition =
1852 RepartitionExec::try_new(merge_scan, Partitioning::RoundRobinBatch(2)).unwrap();
1853 assert_eq!(
1854 repartition
1855 .properties()
1856 .output_partitioning()
1857 .partition_count(),
1858 2
1859 );
1860
1861 let mut stream = common_recordbatch::adapter::RecordBatchStreamAdapter::try_new(
1862 repartition
1863 .execute(0, Arc::new(TaskContext::default()))
1864 .unwrap(),
1865 )
1866 .unwrap();
1867
1868 let error = stream.next().await.unwrap().unwrap_err();
1869 assert_eq!(error.status_code(), StatusCode::RequestOutdated);
1870 }
1871
1872 #[tokio::test]
1873 async fn aborting_pending_do_get_poll_rolls_back_subscriber_without_starting_fanout() {
1874 let handler = Arc::new(PendingDoGetHandler::default());
1875 let query_ctx = QueryContext::arc();
1876 let state = query_engine_state(handler.clone());
1877 let lease = state
1878 .acquire_remote_dyn_filter_registry_lease(&query_ctx)
1879 .unwrap();
1880 let exec = remote_dyn_filter_test_exec(handler.clone(), query_ctx.clone());
1881 install_remote_dyn_filter(&exec);
1882 let stream = exec
1883 .to_stream(task_context_with_engine_state(state, query_ctx), 0)
1884 .unwrap();
1885
1886 let poll = tokio::spawn(async move {
1887 let mut stream = stream;
1888 stream.next().await
1889 });
1890 handler.do_get_entered.notified().await;
1891 let entries = lease.registry().entries();
1892 assert_eq!(entries.len(), 1);
1893 assert_eq!(entries[0].subscribers().len(), 1);
1894 assert!(!entries[0].fanout_started_for_test());
1895
1896 poll.abort();
1897 assert!(poll.await.unwrap_err().is_cancelled());
1898
1899 assert!(entries[0].subscribers().is_empty());
1900 assert!(!entries[0].fanout_started_for_test());
1901 }
1902
1903 #[tokio::test]
1904 async fn failed_do_get_preserves_preexisting_duplicate_subscriber() {
1905 let handler = Arc::new(FailingRegionQueryHandler::default());
1906 let query_ctx = QueryContext::arc();
1907 let state = query_engine_state(handler.clone());
1908 let lease = state
1909 .acquire_remote_dyn_filter_registry_lease(&query_ctx)
1910 .unwrap();
1911 let exec = remote_dyn_filter_test_exec(handler.clone(), query_ctx.clone());
1912 install_remote_dyn_filter(&exec);
1913
1914 register_remote_dyn_filters_for_region(Some(&lease), &exec.captured_remote_dyn_filters());
1915 let entry = lease.registry().entries().pop().unwrap();
1916 let subscriber = Subscriber::new(RegionId::new(1024, 1), test_target(1));
1917 assert!(matches!(
1918 lease
1919 .registry()
1920 .register_subscriber(entry.filter_id(), subscriber.clone()),
1921 crate::dist_plan::SubscriberRegistration::Added
1922 ));
1923
1924 let mut stream = exec
1925 .to_stream(task_context_with_engine_state(state, query_ctx), 0)
1926 .unwrap();
1927 assert!(stream.next().await.unwrap().is_err());
1928
1929 assert_eq!(entry.subscribers(), vec![subscriber]);
1930 assert!(!entry.fanout_started_for_test());
1931 }
1932
1933 #[tokio::test]
1934 async fn failed_target_selection_registers_no_subscriber_or_fanout() {
1935 let handler = Arc::new(SelectTargetErrorHandler::default());
1936 let query_ctx = QueryContext::arc();
1937 let state = query_engine_state(handler.clone());
1938 let lease = state
1939 .acquire_remote_dyn_filter_registry_lease(&query_ctx)
1940 .unwrap();
1941 let exec = remote_dyn_filter_test_exec(handler.clone(), query_ctx.clone());
1942 install_remote_dyn_filter(&exec);
1943
1944 let mut stream = exec
1945 .to_stream(task_context_with_engine_state(state, query_ctx), 0)
1946 .unwrap();
1947 assert!(stream.next().await.unwrap().is_err());
1948
1949 assert_eq!(handler.select_target_calls.load(Ordering::SeqCst), 1);
1950 assert_eq!(handler.do_get_calls.load(Ordering::SeqCst), 0);
1951 let entries = lease.registry().entries();
1952 assert_eq!(entries.len(), 1);
1953 assert!(entries[0].subscribers().is_empty());
1954 assert!(!entries[0].fanout_started_for_test());
1955 }
1956
1957 #[tokio::test]
1958 async fn frozen_target_is_reused_for_do_get_update_and_unregister() {
1959 let (first_update_entered_tx, first_update_entered_rx) = oneshot::channel();
1960 let (release_first_update_tx, release_first_update_rx) = oneshot::channel();
1961 let (second_update_entered_tx, second_update_entered_rx) = oneshot::channel();
1962 let (release_second_update_tx, release_second_update_rx) = oneshot::channel();
1963 let (unregister_tx, unregister_rx) = oneshot::channel();
1964 let handler = Arc::new(RoutingRegionQueryHandler::new(
1965 first_update_entered_tx,
1966 release_first_update_rx,
1967 second_update_entered_tx,
1968 release_second_update_rx,
1969 unregister_tx,
1970 ));
1971 let query_ctx = QueryContext::arc();
1972 let state = query_engine_state(handler.clone());
1973 let lease = state
1974 .acquire_remote_dyn_filter_registry_lease(&query_ctx)
1975 .unwrap();
1976 let exec = remote_dyn_filter_test_exec(handler.clone(), query_ctx.clone());
1977 let dyn_filter = install_remote_dyn_filter(&exec);
1978
1979 let stream = exec
1980 .to_stream(task_context_with_engine_state(state, query_ctx), 0)
1981 .unwrap();
1982 let poll = tokio::spawn(async move {
1983 let mut stream = stream;
1984 stream.next().await
1985 });
1986 handler.do_get_entered.notified().await;
1987 assert_eq!(handler.do_get_targets(), vec![1]);
1988
1989 first_update_entered_rx.await.unwrap();
1990 dyn_filter.update(physical_lit(false) as _).unwrap();
1991 release_first_update_tx.send(()).unwrap();
1992 second_update_entered_rx.await.unwrap();
1993 assert_eq!(handler.update_targets(), vec![1, 1]);
1994
1995 poll.abort();
1996 assert!(poll.await.unwrap_err().is_cancelled());
1997 drop(exec);
1998 drop(dyn_filter);
1999 release_second_update_tx.send(()).unwrap();
2000 unregister_rx.await.unwrap();
2001 assert_eq!(handler.unregister_targets(), vec![1]);
2002 drop(lease);
2003 }
2004
2005 #[tokio::test]
2006 async fn immediate_eof_do_get_receives_refreshed_remote_dyn_filter_snapshot() {
2007 let handler = Arc::new(ImmediateEofRegionQueryHandler::default());
2008 let query_ctx = QueryContext::arc();
2009 let state = query_engine_state(handler.clone());
2010 let exec = remote_dyn_filter_test_exec(handler.clone(), query_ctx.clone());
2011 let dyn_filter = install_remote_dyn_filter(&exec);
2012 dyn_filter.update(physical_lit(false) as _).unwrap();
2013
2014 let mut stream = exec
2015 .to_stream(task_context_with_engine_state(state, query_ctx), 0)
2016 .unwrap();
2017 assert!(stream.next().await.is_none());
2018
2019 let registrations = handler.registrations();
2020 assert_eq!(registrations.regs.len(), 1);
2021 let snapshot = registrations.regs[0].initial_snapshot.as_ref().unwrap();
2022 assert!(snapshot.generation > 0);
2023 assert!(!snapshot.is_complete);
2024 assert_eq!(
2025 snapshot
2026 .payload
2027 .decode_datafusion_expr(
2028 &TaskContext::default(),
2029 &ArrowSchema::new(vec![arrow_schema::Field::new(
2030 "host",
2031 arrow_schema::DataType::Boolean,
2032 false,
2033 )]),
2034 common_query::request::REMOTE_DYN_FILTER_PAYLOAD_MAX_BYTES,
2035 )
2036 .unwrap()
2037 .to_string(),
2038 "false"
2039 );
2040 }
2041
2042 #[test]
2043 fn remote_dyn_filter_registry_cleanup_waits_for_last_query_scoped_stream_drop() {
2044 let registry_manager = Arc::new(DynFilterRegistryManager::default());
2045 let query_id = test_query_id(1);
2046
2047 let first = registry_manager.acquire_lease(query_id);
2048 let second = registry_manager.acquire_lease(query_id);
2049
2050 drop(first);
2051 assert_eq!(registry_manager.registry_count(), 1);
2052
2053 drop(second);
2054 assert_eq!(registry_manager.registry_count(), 0);
2055 }
2056
2057 #[test]
2058 fn remote_dyn_filter_registry_cleanup_shares_query_scope_across_independent_leases() {
2059 let registry_manager = Arc::new(DynFilterRegistryManager::default());
2060 let query_id = test_query_id(1);
2061
2062 let first_exec_like_lease = registry_manager.acquire_lease(query_id);
2063 let second_exec_like_lease = registry_manager.acquire_lease(query_id);
2064
2065 drop(first_exec_like_lease);
2066 assert_eq!(registry_manager.registry_count(), 1);
2067
2068 drop(second_exec_like_lease);
2069 assert_eq!(registry_manager.registry_count(), 0);
2070 }
2071
2072 struct TestRegionResponse {
2073 advertised_schema: Arc<Schema>,
2074 batches: Vec<common_recordbatch::error::Result<RecordBatch>>,
2075 }
2076
2077 #[derive(Default)]
2078 struct TestRegionQueryHandler {
2079 responses: HashMap<RegionId, TestRegionResponse>,
2080 }
2081
2082 impl TestRegionQueryHandler {
2083 fn new(responses: impl IntoIterator<Item = (RegionId, RecordBatch)>) -> Self {
2084 let responses = responses
2085 .into_iter()
2086 .map(|(region_id, batch)| {
2087 (
2088 region_id,
2089 TestRegionResponse {
2090 advertised_schema: batch.schema.clone(),
2091 batches: vec![Ok(batch)],
2092 },
2093 )
2094 })
2095 .collect();
2096 Self { responses }
2097 }
2098
2099 fn with_responses(
2100 responses: impl IntoIterator<
2101 Item = (
2102 RegionId,
2103 Arc<Schema>,
2104 Vec<common_recordbatch::error::Result<RecordBatch>>,
2105 ),
2106 >,
2107 ) -> Self {
2108 let responses = responses
2109 .into_iter()
2110 .map(|(region_id, advertised_schema, batches)| {
2111 (
2112 region_id,
2113 TestRegionResponse {
2114 advertised_schema,
2115 batches,
2116 },
2117 )
2118 })
2119 .collect();
2120 Self { responses }
2121 }
2122 }
2123
2124 struct TestRecordBatchStream {
2125 schema: Arc<Schema>,
2126 batches: Vec<common_recordbatch::error::Result<RecordBatch>>,
2127 }
2128
2129 impl Stream for TestRecordBatchStream {
2130 type Item = common_recordbatch::error::Result<RecordBatch>;
2131
2132 fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
2133 if self.batches.is_empty() {
2134 Poll::Ready(None)
2135 } else {
2136 Poll::Ready(Some(self.batches.remove(0)))
2137 }
2138 }
2139 }
2140
2141 impl RecordBatchStream for TestRecordBatchStream {
2142 fn schema(&self) -> Arc<Schema> {
2143 self.schema.clone()
2144 }
2145
2146 fn output_ordering(&self) -> Option<&[common_recordbatch::OrderOption]> {
2147 None
2148 }
2149
2150 fn metrics(&self) -> Option<RecordBatchMetrics> {
2151 None
2152 }
2153 }
2154
2155 #[derive(Default)]
2156 struct FailingRegionQueryHandler {
2157 do_get_calls: AtomicUsize,
2158 saw_subscriber: std::sync::atomic::AtomicBool,
2159 registry: std::sync::Mutex<Option<Arc<crate::dist_plan::QueryDynFilterRegistry>>>,
2160 }
2161
2162 impl FailingRegionQueryHandler {
2163 fn set_registry(&self, registry: Arc<crate::dist_plan::QueryDynFilterRegistry>) {
2164 *self.registry.lock().unwrap() = Some(registry);
2165 }
2166 }
2167
2168 #[async_trait]
2169 impl RegionQueryHandler for FailingRegionQueryHandler {
2170 async fn select_target(
2171 &self,
2172 _read_preference: ReadPreference,
2173 _region_id: RegionId,
2174 ) -> crate::error::Result<crate::region_query::RegionQueryTarget> {
2175 Ok(test_target(1))
2176 }
2177
2178 async fn do_get(
2179 &self,
2180 _target: &crate::region_query::RegionQueryTarget,
2181 _request: common_query::request::QueryRequest,
2182 ) -> crate::error::Result<common_recordbatch::SendableRecordBatchStream> {
2183 self.do_get_calls.fetch_add(1, Ordering::SeqCst);
2184 self.saw_subscriber.store(
2185 self.registry
2186 .lock()
2187 .unwrap()
2188 .as_ref()
2189 .is_some_and(|registry| {
2190 registry
2191 .entries()
2192 .iter()
2193 .any(|entry| !entry.subscribers().is_empty())
2194 }),
2195 Ordering::SeqCst,
2196 );
2197 Err(crate::error::Error::QueryExecution {
2198 source: BoxedError::new(PlainError::new(
2199 "neutral do_get error".to_string(),
2200 StatusCode::RequestOutdated,
2201 )),
2202 location: snafu::Location::default(),
2203 })
2204 }
2205
2206 async fn handle_remote_dyn_filter_update(
2207 &self,
2208 _target: &crate::region_query::RegionQueryTarget,
2209 _query_id: String,
2210 _update: api::v1::region::RemoteDynFilterUpdate,
2211 ) -> crate::error::Result<()> {
2212 unimplemented!("test only")
2213 }
2214
2215 async fn handle_remote_dyn_filter_unregister(
2216 &self,
2217 _target: &crate::region_query::RegionQueryTarget,
2218 _query_id: String,
2219 _unregister: api::v1::region::RemoteDynFilterUnregister,
2220 ) -> crate::error::Result<()> {
2221 unimplemented!("test only")
2222 }
2223 }
2224
2225 #[derive(Default)]
2226 struct PendingDoGetHandler {
2227 do_get_entered: Notify,
2228 never_complete: Notify,
2229 }
2230
2231 #[async_trait]
2232 impl RegionQueryHandler for PendingDoGetHandler {
2233 async fn select_target(
2234 &self,
2235 _read_preference: ReadPreference,
2236 _region_id: RegionId,
2237 ) -> crate::error::Result<crate::region_query::RegionQueryTarget> {
2238 Ok(test_target(1))
2239 }
2240
2241 async fn do_get(
2242 &self,
2243 _target: &crate::region_query::RegionQueryTarget,
2244 _request: common_query::request::QueryRequest,
2245 ) -> crate::error::Result<common_recordbatch::SendableRecordBatchStream> {
2246 self.do_get_entered.notify_one();
2247 self.never_complete.notified().await;
2248 unreachable!("the test aborts the pending do_get future")
2249 }
2250
2251 async fn handle_remote_dyn_filter_update(
2252 &self,
2253 _target: &crate::region_query::RegionQueryTarget,
2254 _query_id: String,
2255 _update: api::v1::region::RemoteDynFilterUpdate,
2256 ) -> crate::error::Result<()> {
2257 unreachable!("fanout must not start while do_get is pending")
2258 }
2259
2260 async fn handle_remote_dyn_filter_unregister(
2261 &self,
2262 _target: &crate::region_query::RegionQueryTarget,
2263 _query_id: String,
2264 _unregister: api::v1::region::RemoteDynFilterUnregister,
2265 ) -> crate::error::Result<()> {
2266 unreachable!("fanout must not start while do_get is pending")
2267 }
2268 }
2269
2270 #[derive(Default)]
2271 struct SelectTargetErrorHandler {
2272 select_target_calls: AtomicUsize,
2273 do_get_calls: AtomicUsize,
2274 }
2275
2276 #[async_trait]
2277 impl RegionQueryHandler for SelectTargetErrorHandler {
2278 async fn select_target(
2279 &self,
2280 _read_preference: ReadPreference,
2281 _region_id: RegionId,
2282 ) -> crate::error::Result<crate::region_query::RegionQueryTarget> {
2283 self.select_target_calls.fetch_add(1, Ordering::SeqCst);
2284 crate::error::UnimplementedSnafu {
2285 operation: "test target selection failure",
2286 }
2287 .fail()
2288 }
2289
2290 async fn do_get(
2291 &self,
2292 _target: &crate::region_query::RegionQueryTarget,
2293 _request: common_query::request::QueryRequest,
2294 ) -> crate::error::Result<common_recordbatch::SendableRecordBatchStream> {
2295 self.do_get_calls.fetch_add(1, Ordering::SeqCst);
2296 unreachable!("do_get must not run after select_target fails")
2297 }
2298
2299 async fn handle_remote_dyn_filter_update(
2300 &self,
2301 _target: &crate::region_query::RegionQueryTarget,
2302 _query_id: String,
2303 _update: api::v1::region::RemoteDynFilterUpdate,
2304 ) -> crate::error::Result<()> {
2305 unreachable!("fanout must not start after select_target fails")
2306 }
2307
2308 async fn handle_remote_dyn_filter_unregister(
2309 &self,
2310 _target: &crate::region_query::RegionQueryTarget,
2311 _query_id: String,
2312 _unregister: api::v1::region::RemoteDynFilterUnregister,
2313 ) -> crate::error::Result<()> {
2314 unreachable!("fanout must not start after select_target fails")
2315 }
2316 }
2317
2318 struct RoutingRegionQueryHandler {
2319 route: Mutex<crate::region_query::RegionQueryTarget>,
2320 do_get_entered: Notify,
2321 do_get_targets: Mutex<Vec<u64>>,
2322 update_targets: Mutex<Vec<u64>>,
2323 unregister_targets: Mutex<Vec<u64>>,
2324 update_calls: AtomicUsize,
2325 first_update_entered_tx: Mutex<Option<oneshot::Sender<()>>>,
2326 release_first_update_rx: Mutex<Option<oneshot::Receiver<()>>>,
2327 second_update_entered_tx: Mutex<Option<oneshot::Sender<()>>>,
2328 release_second_update_rx: Mutex<Option<oneshot::Receiver<()>>>,
2329 unregister_tx: Mutex<Option<oneshot::Sender<()>>>,
2330 }
2331
2332 impl RoutingRegionQueryHandler {
2333 fn new(
2334 first_update_entered_tx: oneshot::Sender<()>,
2335 release_first_update_rx: oneshot::Receiver<()>,
2336 second_update_entered_tx: oneshot::Sender<()>,
2337 release_second_update_rx: oneshot::Receiver<()>,
2338 unregister_tx: oneshot::Sender<()>,
2339 ) -> Self {
2340 Self {
2341 route: Mutex::new(test_target(1)),
2342 do_get_entered: Notify::new(),
2343 do_get_targets: Mutex::new(Vec::new()),
2344 update_targets: Mutex::new(Vec::new()),
2345 unregister_targets: Mutex::new(Vec::new()),
2346 update_calls: AtomicUsize::new(0),
2347 first_update_entered_tx: Mutex::new(Some(first_update_entered_tx)),
2348 release_first_update_rx: Mutex::new(Some(release_first_update_rx)),
2349 second_update_entered_tx: Mutex::new(Some(second_update_entered_tx)),
2350 release_second_update_rx: Mutex::new(Some(release_second_update_rx)),
2351 unregister_tx: Mutex::new(Some(unregister_tx)),
2352 }
2353 }
2354
2355 fn do_get_targets(&self) -> Vec<u64> {
2356 self.do_get_targets.lock().unwrap().clone()
2357 }
2358
2359 fn update_targets(&self) -> Vec<u64> {
2360 self.update_targets.lock().unwrap().clone()
2361 }
2362
2363 fn unregister_targets(&self) -> Vec<u64> {
2364 self.unregister_targets.lock().unwrap().clone()
2365 }
2366 }
2367
2368 #[async_trait]
2369 impl RegionQueryHandler for RoutingRegionQueryHandler {
2370 async fn select_target(
2371 &self,
2372 _read_preference: ReadPreference,
2373 _region_id: RegionId,
2374 ) -> crate::error::Result<crate::region_query::RegionQueryTarget> {
2375 let mut route = self.route.lock().unwrap();
2376 let target = route.clone();
2377 *route = test_target(2);
2378 Ok(target)
2379 }
2380
2381 async fn do_get(
2382 &self,
2383 target: &crate::region_query::RegionQueryTarget,
2384 request: common_query::request::QueryRequest,
2385 ) -> crate::error::Result<common_recordbatch::SendableRecordBatchStream> {
2386 self.do_get_targets.lock().unwrap().push(target.peer().id);
2387 self.do_get_entered.notify_one();
2388 Ok(pending_record_batch_stream(&request))
2389 }
2390
2391 async fn handle_remote_dyn_filter_update(
2392 &self,
2393 target: &crate::region_query::RegionQueryTarget,
2394 _query_id: String,
2395 _update: api::v1::region::RemoteDynFilterUpdate,
2396 ) -> crate::error::Result<()> {
2397 self.update_targets.lock().unwrap().push(target.peer().id);
2398 match self.update_calls.fetch_add(1, Ordering::SeqCst) {
2399 0 => {
2400 if let Some(tx) = self.first_update_entered_tx.lock().unwrap().take() {
2401 let _ = tx.send(());
2402 }
2403 let release = { self.release_first_update_rx.lock().unwrap().take() };
2404 if let Some(release) = release {
2405 let _ = release.await;
2406 }
2407 }
2408 1 => {
2409 if let Some(tx) = self.second_update_entered_tx.lock().unwrap().take() {
2410 let _ = tx.send(());
2411 }
2412 let release = { self.release_second_update_rx.lock().unwrap().take() };
2413 if let Some(release) = release {
2414 let _ = release.await;
2415 }
2416 }
2417 _ => {}
2418 }
2419 Ok(())
2420 }
2421
2422 async fn handle_remote_dyn_filter_unregister(
2423 &self,
2424 target: &crate::region_query::RegionQueryTarget,
2425 _query_id: String,
2426 _unregister: api::v1::region::RemoteDynFilterUnregister,
2427 ) -> crate::error::Result<()> {
2428 self.unregister_targets
2429 .lock()
2430 .unwrap()
2431 .push(target.peer().id);
2432 if let Some(tx) = self.unregister_tx.lock().unwrap().take() {
2433 let _ = tx.send(());
2434 }
2435 Ok(())
2436 }
2437 }
2438
2439 #[derive(Default)]
2440 struct ImmediateEofRegionQueryHandler {
2441 registrations: Mutex<Option<InitialDynFilterRegs>>,
2442 }
2443
2444 impl ImmediateEofRegionQueryHandler {
2445 fn registrations(&self) -> InitialDynFilterRegs {
2446 self.registrations.lock().unwrap().clone().unwrap()
2447 }
2448 }
2449
2450 #[async_trait]
2451 impl RegionQueryHandler for ImmediateEofRegionQueryHandler {
2452 async fn select_target(
2453 &self,
2454 _read_preference: ReadPreference,
2455 _region_id: RegionId,
2456 ) -> crate::error::Result<crate::region_query::RegionQueryTarget> {
2457 Ok(test_target(1))
2458 }
2459
2460 async fn do_get(
2461 &self,
2462 _target: &crate::region_query::RegionQueryTarget,
2463 request: common_query::request::QueryRequest,
2464 ) -> crate::error::Result<common_recordbatch::SendableRecordBatchStream> {
2465 let registrations = request
2466 .header
2467 .clone()
2468 .and_then(|header| header.query_context)
2469 .and_then(|query_context| {
2470 query_context
2471 .extensions
2472 .get(INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY)
2473 .cloned()
2474 })
2475 .map(|serialized| InitialDynFilterRegs::from_extension_value(&serialized).unwrap())
2476 .unwrap();
2477 *self.registrations.lock().unwrap() = Some(registrations);
2478 Ok(empty_record_batch_stream(&request))
2479 }
2480
2481 async fn handle_remote_dyn_filter_update(
2482 &self,
2483 _target: &crate::region_query::RegionQueryTarget,
2484 _query_id: String,
2485 _update: api::v1::region::RemoteDynFilterUpdate,
2486 ) -> crate::error::Result<()> {
2487 Ok(())
2488 }
2489
2490 async fn handle_remote_dyn_filter_unregister(
2491 &self,
2492 _target: &crate::region_query::RegionQueryTarget,
2493 _query_id: String,
2494 _unregister: api::v1::region::RemoteDynFilterUnregister,
2495 ) -> crate::error::Result<()> {
2496 Ok(())
2497 }
2498 }
2499
2500 #[async_trait]
2501 impl RegionQueryHandler for TestRegionQueryHandler {
2502 async fn select_target(
2503 &self,
2504 _read_preference: ReadPreference,
2505 _region_id: RegionId,
2506 ) -> crate::error::Result<crate::region_query::RegionQueryTarget> {
2507 Ok(test_target(1))
2508 }
2509
2510 async fn do_get(
2511 &self,
2512 _target: &crate::region_query::RegionQueryTarget,
2513 request: common_query::request::QueryRequest,
2514 ) -> crate::error::Result<common_recordbatch::SendableRecordBatchStream> {
2515 let response = self
2516 .responses
2517 .get(&request.region_id)
2518 .expect("test handler needs a response for every requested region");
2519 Ok(Box::pin(TestRecordBatchStream {
2520 schema: response.advertised_schema.clone(),
2521 batches: response
2522 .batches
2523 .iter()
2524 .map(|batch| match batch {
2525 Ok(batch) => Ok(batch.clone()),
2526 Err(error) => Err(common_recordbatch::error::ExternalSnafu.into_error(
2527 BoxedError::new(PlainError::new(
2528 error.to_string(),
2529 error.status_code(),
2530 )),
2531 )),
2532 })
2533 .collect(),
2534 }))
2535 }
2536
2537 async fn handle_remote_dyn_filter_update(
2538 &self,
2539 _target: &crate::region_query::RegionQueryTarget,
2540 _query_id: String,
2541 _update: api::v1::region::RemoteDynFilterUpdate,
2542 ) -> crate::error::Result<()> {
2543 unimplemented!("test only")
2544 }
2545
2546 async fn handle_remote_dyn_filter_unregister(
2547 &self,
2548 _target: &crate::region_query::RegionQueryTarget,
2549 _query_id: String,
2550 _unregister: api::v1::region::RemoteDynFilterUnregister,
2551 ) -> crate::error::Result<()> {
2552 unimplemented!("test only")
2553 }
2554 }
2555
2556 fn int64_schema(columns: &[&str]) -> Arc<Schema> {
2557 Arc::new(Schema::new(
2558 columns
2559 .iter()
2560 .map(|name| ColumnSchema::new(*name, ConcreteDataType::int64_datatype(), false))
2561 .collect(),
2562 ))
2563 }
2564
2565 fn record_batch(schema: Arc<Schema>, columns: Vec<VectorRef>) -> RecordBatch {
2566 RecordBatch::new(schema, columns).expect("test record batch must match its schema")
2567 }
2568
2569 fn expected_int64_schema() -> ArrowSchema {
2570 int64_schema(&["a", "b"]).arrow_schema().as_ref().clone()
2571 }
2572
2573 fn merge_scan_exec(
2574 responses: Vec<(RegionId, RecordBatch)>,
2575 expected_schema: ArrowSchema,
2576 target_partition: usize,
2577 ) -> MergeScanExec {
2578 let regions = responses.iter().map(|(region_id, _)| *region_id).collect();
2579 merge_scan_exec_with_handler(
2580 regions,
2581 expected_schema,
2582 Arc::new(TestRegionQueryHandler::new(responses)),
2583 target_partition,
2584 )
2585 }
2586
2587 fn merge_scan_exec_with_handler(
2588 regions: Vec<RegionId>,
2589 expected_schema: ArrowSchema,
2590 handler: Arc<TestRegionQueryHandler>,
2591 target_partition: usize,
2592 ) -> MergeScanExec {
2593 let plan = LogicalPlanBuilder::empty(true).build().unwrap();
2594 MergeScanExec::new(
2595 &SessionStateBuilder::new().build(),
2596 TableName::new("catalog", "schema", "table"),
2597 regions,
2598 plan,
2599 &expected_schema,
2600 handler,
2601 QueryContext::arc(),
2602 target_partition,
2603 None,
2604 false,
2605 )
2606 .unwrap()
2607 }
2608
2609 async fn collect_merge_scan(
2610 exec: MergeScanExec,
2611 ) -> datafusion_common::Result<Vec<DfRecordBatch>> {
2612 exec.execute(0, Arc::new(TaskContext::default()))?
2613 .try_collect()
2614 .await
2615 }
2616
2617 fn assert_int64_batch(batch: &DfRecordBatch, values: (i64, i64)) {
2618 assert_eq!(batch.schema().as_ref(), &expected_int64_schema());
2619 let a = batch
2620 .column(0)
2621 .as_any()
2622 .downcast_ref::<Int64Array>()
2623 .unwrap();
2624 let b = batch
2625 .column(1)
2626 .as_any()
2627 .downcast_ref::<Int64Array>()
2628 .unwrap();
2629 assert_eq!((a.value(0), b.value(0)), values);
2630 }
2631
2632 #[tokio::test]
2633 async fn qbs_merge_scan_remote_schema_identity_canonical_single_region() {
2634 let batch = record_batch(
2635 int64_schema(&["a", "b"]),
2636 vec![
2637 Arc::new(Int64Vector::from_slice([11])) as _,
2638 Arc::new(Int64Vector::from_slice([12])) as _,
2639 ],
2640 );
2641 let batches = collect_merge_scan(merge_scan_exec(
2642 vec![(RegionId::new(1024, 1), batch)],
2643 expected_int64_schema(),
2644 1,
2645 ))
2646 .await
2647 .unwrap();
2648 assert_eq!(batches.len(), 1);
2649 assert_int64_batch(&batches[0], (11, 12));
2650 }
2651
2652 #[tokio::test]
2653 async fn qbs_merge_scan_remote_schema_identity_canonical_two_regions() {
2654 let batch = || {
2655 record_batch(
2656 int64_schema(&["a", "b"]),
2657 vec![
2658 Arc::new(Int64Vector::from_slice([11])) as _,
2659 Arc::new(Int64Vector::from_slice([12])) as _,
2660 ],
2661 )
2662 };
2663 let batches = collect_merge_scan(merge_scan_exec(
2664 vec![
2665 (RegionId::new(1024, 1), batch()),
2666 (RegionId::new(1024, 2), batch()),
2667 ],
2668 expected_int64_schema(),
2669 1,
2670 ))
2671 .await
2672 .unwrap();
2673 assert_eq!(batches.len(), 2);
2674 for batch in &batches {
2675 assert_int64_batch(batch, (11, 12));
2676 }
2677 }
2678
2679 #[tokio::test]
2680 async fn qbs_merge_scan_remote_schema_identity_swapped_columns_never_relabels_positionally() {
2681 let batch = record_batch(
2682 int64_schema(&["b", "a"]),
2683 vec![
2684 Arc::new(Int64Vector::from_slice([2002])) as _,
2685 Arc::new(Int64Vector::from_slice([1002])) as _,
2686 ],
2687 );
2688 assert!(
2689 collect_merge_scan(merge_scan_exec(
2690 vec![(RegionId::new(1024, 1), batch)],
2691 expected_int64_schema(),
2692 1,
2693 ))
2694 .await
2695 .is_err()
2696 );
2697 }
2698
2699 #[tokio::test]
2700 async fn qbs_merge_scan_remote_schema_identity_allows_timestamp_timezone_only_patch() {
2701 let remote_arrow_schema = Arc::new(ArrowSchema::new(vec![Field::new(
2702 "ts",
2703 TestArrowDataType::Timestamp(TimeUnit::Millisecond, Some("UTC".into())),
2704 false,
2705 )]));
2706 let remote_schema = Arc::new(Schema::try_from(remote_arrow_schema).unwrap());
2707 let timestamp_array: Arc<dyn arrow::array::Array> =
2708 Arc::new(TimestampMillisecondArray::from(vec![1002]).with_timezone("UTC"));
2709 let timestamp = TimestampMillisecondVector::try_from_arrow_array(timestamp_array).unwrap();
2710 let batch = record_batch(remote_schema, vec![Arc::new(timestamp) as _]);
2711 let expected_schema = ArrowSchema::new(vec![Field::new(
2712 "ts",
2713 TestArrowDataType::Timestamp(TimeUnit::Millisecond, Some("Asia/Shanghai".into())),
2714 false,
2715 )]);
2716 let batches = collect_merge_scan(merge_scan_exec(
2717 vec![(RegionId::new(1024, 1), batch)],
2718 expected_schema.clone(),
2719 1,
2720 ))
2721 .await
2722 .unwrap();
2723 assert_eq!(batches[0].schema().as_ref(), &expected_schema);
2724 }
2725
2726 #[tokio::test]
2727 async fn qbs_merge_scan_remote_schema_identity_emits_expected_schema_on_nullability_narrowing()
2728 {
2729 let batch = record_batch(
2730 int64_schema(&["a", "b"]),
2731 vec![
2732 Arc::new(Int64Vector::from_slice([11])) as _,
2733 Arc::new(Int64Vector::from_slice([12])) as _,
2734 ],
2735 );
2736 let expected_schema = ArrowSchema::new(vec![
2737 Field::new("a", TestArrowDataType::Int64, true),
2738 Field::new("b", TestArrowDataType::Int64, false),
2739 ]);
2740 let batches = collect_merge_scan(merge_scan_exec(
2741 vec![(RegionId::new(1024, 1), batch)],
2742 expected_schema.clone(),
2743 1,
2744 ))
2745 .await
2746 .unwrap();
2747 assert_eq!(batches[0].schema().as_ref(), &expected_schema);
2748 }
2749
2750 #[tokio::test]
2751 async fn qbs_merge_scan_remote_schema_identity_rejects_incompatible_type() {
2752 let batch = record_batch(
2753 Arc::new(Schema::new(vec![
2754 ColumnSchema::new("a", ConcreteDataType::string_datatype(), false),
2755 ColumnSchema::new("b", ConcreteDataType::int64_datatype(), false),
2756 ])),
2757 vec![
2758 Arc::new(StringVector::from_slice(&["not-an-int"])) as _,
2759 Arc::new(Int64Vector::from_slice([12])) as _,
2760 ],
2761 );
2762 assert!(
2763 collect_merge_scan(merge_scan_exec(
2764 vec![(RegionId::new(1024, 1), batch)],
2765 expected_int64_schema(),
2766 1,
2767 ))
2768 .await
2769 .is_err()
2770 );
2771 }
2772
2773 #[tokio::test]
2774 async fn qbs_merge_scan_remote_schema_identity_rejects_too_few_columns() {
2775 let batch = record_batch(
2776 int64_schema(&["a"]),
2777 vec![Arc::new(Int64Vector::from_slice([11])) as _],
2778 );
2779 assert!(
2780 collect_merge_scan(merge_scan_exec(
2781 vec![(RegionId::new(1024, 1), batch)],
2782 expected_int64_schema(),
2783 1,
2784 ))
2785 .await
2786 .is_err()
2787 );
2788 }
2789
2790 #[tokio::test]
2791 async fn qbs_merge_scan_remote_schema_identity_rejects_too_many_columns() {
2792 let batch = record_batch(
2793 int64_schema(&["a", "b", "extra"]),
2794 vec![
2795 Arc::new(Int64Vector::from_slice([11])) as _,
2796 Arc::new(Int64Vector::from_slice([12])) as _,
2797 Arc::new(Int64Vector::from_slice([13])) as _,
2798 ],
2799 );
2800 assert!(
2801 collect_merge_scan(merge_scan_exec(
2802 vec![(RegionId::new(1024, 1), batch)],
2803 expected_int64_schema(),
2804 1,
2805 ))
2806 .await
2807 .is_err()
2808 );
2809 }
2810
2811 #[test]
2812 fn merge_scan_remote_schema_identity_allows_top_level_metadata_mismatch() {
2813 let fields = expected_int64_schema().fields().clone();
2814 let expected = ArrowSchema::new_with_metadata(
2815 fields.clone(),
2816 StdHashMap::from([("greptime:version".to_string(), "1".to_string())]),
2817 );
2818 let actual = ArrowSchema::new_with_metadata(
2819 fields,
2820 StdHashMap::from([("greptime:version".to_string(), "0".to_string())]),
2821 );
2822 assert!(validate_remote_schema(&expected, &actual, "test").is_ok());
2823 }
2824
2825 fn json_field_metadata(wire_form: bool, json_settings: &str) -> StdHashMap<String, String> {
2829 let mut metadata = StdHashMap::from([
2830 (datatypes::schema::TYPE_KEY.to_string(), "Json".to_string()),
2831 (
2832 arrow_schema::extension::EXTENSION_TYPE_METADATA_KEY.to_string(),
2833 json_settings.to_string(),
2834 ),
2835 ]);
2836 if wire_form {
2837 metadata.insert(
2838 arrow_schema::extension::EXTENSION_TYPE_NAME_KEY.to_string(),
2839 "greptime.json".to_string(),
2840 );
2841 }
2842 metadata
2843 }
2844
2845 #[test]
2846 fn merge_scan_remote_schema_identity_accepts_json_wire_binary_vs_decoded_struct() {
2847 let json_settings = r#"{"json_settings":{"type_hints":[]}}"#;
2854 let expected = ArrowSchema::new(vec![
2855 Field::new("j", TestArrowDataType::Binary, true)
2856 .with_metadata(json_field_metadata(true, json_settings)),
2857 ]);
2858 let actual =
2859 ArrowSchema::new(vec![
2860 Field::new(
2861 "j",
2862 TestArrowDataType::Struct(arrow_schema::Fields::from(vec![Arc::new(
2863 Field::new("a", TestArrowDataType::Utf8View, true),
2864 )])),
2865 true,
2866 )
2867 .with_metadata(json_field_metadata(false, json_settings)),
2868 ]);
2869 assert!(validate_remote_schema(&expected, &actual, "test").is_ok());
2870 }
2871
2872 #[test]
2873 fn merge_scan_remote_schema_identity_accepts_json_decoded_struct_vs_wire_binary() {
2874 let json_settings = r#"{"json_settings":{"type_hints":[]}}"#;
2877 let expected =
2878 ArrowSchema::new(vec![
2879 Field::new(
2880 "j",
2881 TestArrowDataType::Struct(arrow_schema::Fields::from(vec![Arc::new(
2882 Field::new("a", TestArrowDataType::Utf8View, true),
2883 )])),
2884 true,
2885 )
2886 .with_metadata(json_field_metadata(false, json_settings)),
2887 ]);
2888 let actual = ArrowSchema::new(vec![
2889 Field::new("j", TestArrowDataType::Binary, true)
2890 .with_metadata(json_field_metadata(true, json_settings)),
2891 ]);
2892 assert!(validate_remote_schema(&expected, &actual, "test").is_ok());
2893 }
2894
2895 #[test]
2896 fn merge_scan_remote_schema_identity_rejects_json_fields_with_different_json_settings() {
2897 let expected = ArrowSchema::new(vec![
2901 Field::new("j", TestArrowDataType::Binary, true).with_metadata(json_field_metadata(
2902 true,
2903 r#"{"json_settings":{"type_hints":[["a",{"JsonType":"Int64"}]]}}"#,
2904 )),
2905 ]);
2906 let actual =
2907 ArrowSchema::new(vec![
2908 Field::new(
2909 "j",
2910 TestArrowDataType::Struct(arrow_schema::Fields::from(vec![Arc::new(
2911 Field::new("a", TestArrowDataType::Utf8View, true),
2912 )])),
2913 true,
2914 )
2915 .with_metadata(json_field_metadata(
2916 false,
2917 r#"{"json_settings":{"type_hints":[["a",{"JsonType":"String"}]]}}"#,
2918 )),
2919 ]);
2920 assert!(validate_remote_schema(&expected, &actual, "test").is_err());
2921 }
2922
2923 #[test]
2924 fn merge_scan_remote_schema_identity_ignores_json_extension_metadata_when_physical_type_matches()
2925 {
2926 let expected = ArrowSchema::new(vec![
2931 Field::new("j", TestArrowDataType::Binary, true).with_metadata(json_field_metadata(
2932 true,
2933 r#"{"json_settings":{"type_hints":[]}}"#,
2934 )),
2935 ]);
2936 let actual = ArrowSchema::new(vec![Field::new("j", TestArrowDataType::Binary, true)]);
2937 assert!(validate_remote_schema(&expected, &actual, "test").is_ok());
2938
2939 let plain_binary = ArrowSchema::new(vec![Field::new("v", TestArrowDataType::Binary, true)]);
2943 let plain_struct = ArrowSchema::new(vec![Field::new(
2944 "v",
2945 TestArrowDataType::Struct(arrow_schema::Fields::from(vec![Arc::new(Field::new(
2946 "a",
2947 TestArrowDataType::Utf8View,
2948 true,
2949 ))])),
2950 true,
2951 )]);
2952 assert!(validate_remote_schema(&plain_binary, &plain_struct, "test").is_err());
2953 }
2954
2955 #[test]
2956 fn merge_scan_remote_schema_identity_ignores_field_metadata_but_rejects_nullability_widening() {
2957 let expected = expected_int64_schema();
2958 let metadata_mismatch = ArrowSchema::new_with_metadata(
2959 vec![
2960 expected
2961 .field(0)
2962 .as_ref()
2963 .clone()
2964 .with_metadata(StdHashMap::from([(
2965 "remote".to_string(),
2966 "different".to_string(),
2967 )])),
2968 expected.field(1).as_ref().clone(),
2969 ],
2970 expected.metadata().clone(),
2971 );
2972 let nullability_mismatch = ArrowSchema::new_with_metadata(
2973 vec![
2974 expected.field(0).as_ref().clone().with_nullable(true),
2975 expected.field(1).as_ref().clone(),
2976 ],
2977 expected.metadata().clone(),
2978 );
2979 assert!(validate_remote_schema(&expected, &metadata_mismatch, "test").is_ok());
2982 assert!(validate_remote_schema(&expected, &nullability_mismatch, "test").is_err());
2983 }
2984
2985 #[test]
2986 fn merge_scan_remote_schema_identity_accepts_top_level_nullability_narrowing_only() {
2987 let state_type = |child_nullable| {
2988 TestArrowDataType::Struct(arrow_schema::Fields::from(vec![Field::new(
2989 "min[value]",
2990 TestArrowDataType::Int64,
2991 child_nullable,
2992 )]))
2993 };
2994 let schema = |nullable, child_nullable| {
2995 ArrowSchema::new(vec![Field::new(
2996 "__min_state(t.v)",
2997 state_type(child_nullable),
2998 nullable,
2999 )])
3000 };
3001
3002 assert!(validate_remote_schema(&schema(true, true), &schema(false, true), "test").is_ok());
3005 assert!(validate_remote_schema(&schema(false, true), &schema(true, true), "test").is_err());
3006 assert!(validate_remote_schema(&schema(true, true), &schema(true, false), "test").is_err());
3007 }
3008
3009 #[test]
3010 fn merge_scan_remote_schema_identity_ignores_skipping_index_field_metadata() {
3011 let skipping_index_metadata = StdHashMap::from([(
3016 "greptime:skipping_index".to_string(),
3017 r#"{"granularity":1,"false-positive-rate-in-10000":100,"index-type":"BloomFilter"}"#
3018 .to_string(),
3019 )]);
3020 let expected =
3021 ArrowSchema::new(vec![Field::new("value", TestArrowDataType::Float64, true)]);
3022 let actual_with_metadata = ArrowSchema::new(vec![
3023 Field::new("value", TestArrowDataType::Float64, true)
3024 .with_metadata(skipping_index_metadata),
3025 ]);
3026 assert!(validate_remote_schema(&expected, &actual_with_metadata, "test").is_ok());
3027
3028 let wrong_type =
3030 ArrowSchema::new(vec![Field::new("value", TestArrowDataType::Int64, true)]);
3031 assert!(validate_remote_schema(&expected, &wrong_type, "test").is_err());
3032
3033 let wrong_name =
3035 ArrowSchema::new(vec![Field::new("other", TestArrowDataType::Float64, true)]);
3036 assert!(validate_remote_schema(&expected, &wrong_name, "test").is_err());
3037 }
3038
3039 #[test]
3040 fn merge_scan_remote_schema_identity_rejects_timestamp_timezone_plus_field_mismatches() {
3041 let expected = ArrowSchema::new(vec![Field::new(
3042 "ts",
3043 TestArrowDataType::Timestamp(TimeUnit::Millisecond, Some("Asia/Shanghai".into())),
3044 false,
3045 )]);
3046 let different_unit = ArrowSchema::new(vec![Field::new(
3047 "ts",
3048 TestArrowDataType::Timestamp(TimeUnit::Second, Some("UTC".into())),
3049 false,
3050 )]);
3051 let different_name = ArrowSchema::new(vec![Field::new(
3052 "other",
3053 TestArrowDataType::Timestamp(TimeUnit::Millisecond, Some("UTC".into())),
3054 false,
3055 )]);
3056 let nullability = ArrowSchema::new(vec![Field::new(
3057 "ts",
3058 TestArrowDataType::Timestamp(TimeUnit::Millisecond, Some("UTC".into())),
3059 true,
3060 )]);
3061 let metadata = ArrowSchema::new(vec![
3062 Field::new(
3063 "ts",
3064 TestArrowDataType::Timestamp(TimeUnit::Millisecond, Some("UTC".into())),
3065 false,
3066 )
3067 .with_metadata(StdHashMap::from([(
3068 "remote".to_string(),
3069 "different".to_string(),
3070 )])),
3071 ]);
3072 for actual in [&different_unit, &different_name, &nullability] {
3073 assert!(validate_remote_schema(&expected, actual, "test").is_err());
3074 }
3075 assert!(validate_remote_schema(&expected, &metadata, "test").is_ok());
3079 }
3080
3081 #[test]
3082 fn merge_scan_remote_schema_identity_returns_arrow_schema_error() {
3083 let expected = expected_int64_schema();
3084 let actual = ArrowSchema::new(vec![Field::new("other", TestArrowDataType::Int64, false)]);
3085 match validate_remote_schema(&expected, &actual, "test").unwrap_err() {
3086 DataFusionError::ArrowError(error, None) => match error.as_ref() {
3087 ArrowError::SchemaError(message) => {
3088 assert!(message.contains("field count mismatch"))
3089 }
3090 error => panic!("expected ArrowError::SchemaError, got {error:?}"),
3091 },
3092 error => panic!("expected DataFusionError::ArrowError(_, None), got {error:?}"),
3093 }
3094 }
3095
3096 #[tokio::test]
3097 async fn merge_scan_remote_schema_identity_rejects_incompatible_empty_advertised_schema() {
3098 let region_id = RegionId::new(1024, 1);
3099 let exec = merge_scan_exec_with_handler(
3100 vec![region_id],
3101 expected_int64_schema(),
3102 Arc::new(TestRegionQueryHandler::with_responses(vec![(
3103 region_id,
3104 int64_schema(&["a"]),
3105 vec![],
3106 )])),
3107 1,
3108 );
3109 let errors_before = merge_scan_schema_error_count_for_test();
3110 assert!(collect_merge_scan(exec).await.is_err());
3111 assert_eq!(merge_scan_schema_error_count_for_test(), errors_before + 1);
3112 }
3113
3114 #[tokio::test]
3115 async fn merge_scan_remote_schema_identity_allows_top_level_metadata_version_mismatch() {
3116 let fields = expected_int64_schema().fields().clone();
3117 let expected = ArrowSchema::new_with_metadata(
3118 fields.clone(),
3119 StdHashMap::from([("greptime:version".to_string(), "1".to_string())]),
3120 );
3121 let remote_schema = Arc::new(
3122 Schema::try_from(Arc::new(ArrowSchema::new_with_metadata(
3123 fields,
3124 StdHashMap::from([("greptime:version".to_string(), "0".to_string())]),
3125 )))
3126 .unwrap(),
3127 );
3128 let batch = record_batch(
3129 remote_schema.clone(),
3130 vec![
3131 Arc::new(Int64Vector::from_slice([11])) as _,
3132 Arc::new(Int64Vector::from_slice([12])) as _,
3133 ],
3134 );
3135 let batches = collect_merge_scan(merge_scan_exec_with_handler(
3136 vec![RegionId::new(1024, 1)],
3137 expected.clone(),
3138 Arc::new(TestRegionQueryHandler::with_responses(vec![(
3139 RegionId::new(1024, 1),
3140 remote_schema,
3141 vec![Ok(batch)],
3142 )])),
3143 1,
3144 ))
3145 .await
3146 .unwrap();
3147 assert_eq!(batches[0].schema().as_ref(), &expected);
3148 }
3149
3150 #[tokio::test]
3151 async fn merge_scan_remote_schema_identity_rejects_advertised_schema_inner_batch_mismatch() {
3152 let region_id = RegionId::new(1024, 1);
3153 let advertised_schema = int64_schema(&["a", "b"]);
3154 let inner_batch = record_batch(
3155 int64_schema(&["b", "a"]),
3156 vec![
3157 Arc::new(Int64Vector::from_slice([12])) as _,
3158 Arc::new(Int64Vector::from_slice([11])) as _,
3159 ],
3160 )
3161 .into_df_record_batch();
3162 let inner_schema = inner_batch.schema_ref().clone();
3163 let batch = RecordBatch::from_df_record_batch(advertised_schema.clone(), inner_batch);
3164 assert!(Arc::ptr_eq(
3165 advertised_schema.arrow_schema(),
3166 batch.schema.arrow_schema()
3167 ));
3168 assert!(!Arc::ptr_eq(
3169 advertised_schema.arrow_schema(),
3170 &inner_schema
3171 ));
3172 let exec = merge_scan_exec_with_handler(
3173 vec![region_id],
3174 expected_int64_schema(),
3175 Arc::new(TestRegionQueryHandler::with_responses(vec![(
3176 region_id,
3177 advertised_schema,
3178 vec![Ok(batch)],
3179 )])),
3180 1,
3181 );
3182 let errors_before = merge_scan_schema_error_count_for_test();
3183 assert!(collect_merge_scan(exec).await.is_err());
3184 assert_eq!(merge_scan_schema_error_count_for_test(), errors_before + 1);
3185 }
3186
3187 #[tokio::test]
3188 async fn merge_scan_remote_schema_identity_rejects_unchecked_inner_extra_column() {
3189 let region_id = RegionId::new(1024, 1);
3190 let advertised_schema = int64_schema(&["a", "b"]);
3191 let inner_batch = record_batch(
3192 int64_schema(&["a", "b", "extra"]),
3193 vec![
3194 Arc::new(Int64Vector::from_slice([11])) as _,
3195 Arc::new(Int64Vector::from_slice([12])) as _,
3196 Arc::new(Int64Vector::from_slice([13])) as _,
3197 ],
3198 )
3199 .into_df_record_batch();
3200 assert!(!Arc::ptr_eq(
3201 advertised_schema.arrow_schema(),
3202 inner_batch.schema_ref()
3203 ));
3204 let batch = RecordBatch::from_df_record_batch(advertised_schema.clone(), inner_batch);
3205 let exec = merge_scan_exec_with_handler(
3206 vec![region_id],
3207 expected_int64_schema(),
3208 Arc::new(TestRegionQueryHandler::with_responses(vec![(
3209 region_id,
3210 advertised_schema,
3211 vec![Ok(batch)],
3212 )])),
3213 1,
3214 );
3215 assert!(collect_merge_scan(exec).await.is_err());
3216 }
3217
3218 #[tokio::test]
3219 async fn merge_scan_remote_schema_identity_validates_structurally_equal_distinct_batch_schema()
3220 {
3221 let region_id = RegionId::new(1024, 1);
3222 let advertised_schema = int64_schema(&["a", "b"]);
3223 let inner_schema = Arc::new(
3224 Schema::try_from(Arc::new(advertised_schema.arrow_schema().as_ref().clone())).unwrap(),
3225 );
3226 let inner_batch = record_batch(
3227 inner_schema,
3228 vec![
3229 Arc::new(Int64Vector::from_slice([11])) as _,
3230 Arc::new(Int64Vector::from_slice([12])) as _,
3231 ],
3232 )
3233 .into_df_record_batch();
3234 assert_eq!(advertised_schema.arrow_schema(), inner_batch.schema_ref());
3235 assert!(!Arc::ptr_eq(
3236 advertised_schema.arrow_schema(),
3237 inner_batch.schema_ref()
3238 ));
3239 let batch = RecordBatch::from_df_record_batch(advertised_schema.clone(), inner_batch);
3240 let batches = collect_merge_scan(merge_scan_exec_with_handler(
3241 vec![region_id],
3242 expected_int64_schema(),
3243 Arc::new(TestRegionQueryHandler::with_responses(vec![(
3244 region_id,
3245 advertised_schema,
3246 vec![Ok(batch)],
3247 )])),
3248 1,
3249 ))
3250 .await
3251 .unwrap();
3252 assert_eq!(batches.len(), 1);
3253 assert_int64_batch(&batches[0], (11, 12));
3254 }
3255
3256 #[test]
3257 fn merge_scan_apply_expressions_exposes_remote_dyn_filter_id() {
3258 let query_ctx = QueryContext::arc();
3259 let exec =
3260 remote_dyn_filter_test_exec(Arc::new(TestRegionQueryHandler::default()), query_ctx);
3261 let dyn_filter = install_remote_dyn_filter(&exec);
3262 let expected_expression_id = dyn_filter.expression_id();
3263 let mut expression_ids = Vec::new();
3264
3265 exec.apply_expressions(&mut |expr| {
3266 expression_ids.push(expr.expression_id());
3267 Ok(TreeNodeRecursion::Continue)
3268 })
3269 .unwrap();
3270
3271 assert_eq!(expression_ids, vec![expected_expression_id]);
3272 }
3273
3274 #[test]
3275 fn remote_dyn_filter_preflight_removes_parent_filter_after_dn_runtime_is_ready() {
3276 let remote_dyn_filter_producer_id = RemoteDynFilterProducerId::new(42);
3277 let plan = LogicalPlanBuilder::empty(true)
3278 .project(vec![lit(1i32).alias("col1")])
3279 .unwrap()
3280 .build()
3281 .unwrap();
3282
3283 let schema = plan.schema().as_arrow().clone();
3284 let table = TableName::new("catalog", "schema", "table");
3285 let regions = vec![RegionId::new(1024, 1)];
3286 let query_ctx = QueryContext::arc();
3287 let session_state = SessionStateBuilder::new().build();
3288 let handler = Arc::new(TestRegionQueryHandler::default());
3289 let exec = MergeScanExec::new(
3290 &session_state,
3291 table,
3292 regions,
3293 plan,
3294 &schema,
3295 handler,
3296 query_ctx,
3297 1,
3298 Some(remote_dyn_filter_producer_id),
3299 false,
3300 )
3301 .unwrap();
3302 let dyn_filter = Arc::new(DynamicFilterPhysicalExpr::new(
3303 vec![Arc::new(Column::new("host", 0)) as Arc<_>],
3304 physical_lit(true) as _,
3305 )) as Arc<dyn datafusion_physical_expr::PhysicalExpr>;
3306
3307 let propagation = exec
3308 .handle_child_pushdown_result(
3309 FilterPushdownPhase::Post,
3310 ChildPushdownResult {
3311 parent_filters: vec![ChildFilterPushdownResult {
3312 filter: dyn_filter,
3313 child_results: vec![PushedDown::Yes],
3314 }],
3315 self_filters: Vec::new(),
3316 },
3317 &ConfigOptions::new(),
3318 )
3319 .unwrap();
3320
3321 assert_eq!(exec.captured_remote_dyn_filters().len(), 1);
3322 assert!(matches!(propagation.filters.as_slice(), [PushedDown::Yes]));
3323 }
3324
3325 #[test]
3326 fn scan_output_bytes_uses_plan_name() {
3327 let metrics = RecordBatchMetrics {
3328 plan_metrics: vec![PlanMetrics {
3329 plan: "SeqScan: region=1".to_string(),
3330 plan_name: REGION_SCAN_EXEC_NAME.to_string(),
3331 level: 0,
3332 metrics: vec![("output_bytes".to_string(), 42)],
3333 }],
3334 ..Default::default()
3335 };
3336
3337 assert_eq!(region_scan_output_bytes(&metrics), 42);
3338 }
3339
3340 #[test]
3341 fn scan_output_bytes_defaults_to_zero_without_region_scan() {
3342 let metrics = RecordBatchMetrics {
3343 plan_metrics: vec![PlanMetrics {
3344 plan: "ProjectionExec".to_string(),
3345 plan_name: "ProjectionExec".to_string(),
3346 level: 0,
3347 metrics: vec![("output_bytes".to_string(), 42)],
3348 }],
3349 ..Default::default()
3350 };
3351
3352 assert_eq!(region_scan_output_bytes(&metrics), 0);
3353 }
3354
3355 #[test]
3356 fn scan_output_bytes_sums_multiple_region_scans() {
3357 let metrics = RecordBatchMetrics {
3358 plan_metrics: vec![
3359 PlanMetrics {
3360 plan: "RegionScanExec: region=1".to_string(),
3361 plan_name: REGION_SCAN_EXEC_NAME.to_string(),
3362 level: 0,
3363 metrics: vec![("output_bytes".to_string(), 42)],
3364 },
3365 PlanMetrics {
3366 plan: "RegionScanExec: region=2".to_string(),
3367 plan_name: REGION_SCAN_EXEC_NAME.to_string(),
3368 level: 0,
3369 metrics: vec![("output_bytes".to_string(), 18)],
3370 },
3371 ],
3372 ..Default::default()
3373 };
3374
3375 assert_eq!(region_scan_output_bytes(&metrics), 60);
3376 }
3377
3378 #[test]
3379 fn merge_scan_reports_region_query_load_on_drop() {
3380 use store_api::metrics::{REGION_QUERY_CPU_TIME, REGION_QUERY_SCANNED_BYTES};
3381
3382 let region_id = RegionId::new(1024, 10002);
3383 let region_id_label = region_id.to_string();
3384 let labels = [®ion_id_label];
3385 let _ = REGION_QUERY_CPU_TIME.remove_label_values(&labels);
3386 let _ = REGION_QUERY_SCANNED_BYTES.remove_label_values(&labels);
3387
3388 let plan = LogicalPlanBuilder::empty(true)
3389 .project(vec![lit(1i32).alias("col1")])
3390 .unwrap()
3391 .build()
3392 .unwrap();
3393 let schema = plan.schema().as_arrow().clone();
3394 let exec = MergeScanExec::new(
3395 &SessionStateBuilder::new().build(),
3396 TableName::new("catalog", "schema", "table"),
3397 vec![region_id],
3398 plan,
3399 &schema,
3400 Arc::new(TestRegionQueryHandler::default()),
3401 QueryContext::arc(),
3402 1,
3403 None,
3404 true,
3405 )
3406 .unwrap();
3407
3408 let metrics = RecordBatchMetrics {
3409 elapsed_compute: 42,
3410 plan_metrics: vec![PlanMetrics {
3411 plan: "RegionScanExec: region=1".to_string(),
3412 plan_name: REGION_SCAN_EXEC_NAME.to_string(),
3413 level: 0,
3414 metrics: vec![("output_bytes".to_string(), 24)],
3415 }],
3416 ..Default::default()
3417 };
3418 exec.sub_stage_metrics
3419 .lock()
3420 .unwrap()
3421 .insert(region_id, metrics);
3422
3423 assert_eq!(REGION_QUERY_CPU_TIME.with_label_values(&labels).get(), 0);
3424 assert_eq!(
3425 REGION_QUERY_SCANNED_BYTES.with_label_values(&labels).get(),
3426 0
3427 );
3428
3429 drop(exec);
3430
3431 assert_eq!(REGION_QUERY_CPU_TIME.with_label_values(&labels).get(), 42);
3432 assert_eq!(
3433 REGION_QUERY_SCANNED_BYTES.with_label_values(&labels).get(),
3434 24
3435 );
3436
3437 let _ = REGION_QUERY_CPU_TIME.remove_label_values(&labels);
3438 let _ = REGION_QUERY_SCANNED_BYTES.remove_label_values(&labels);
3439 }
3440
3441 #[test]
3442 fn merge_scan_reports_query_load_with_metrics_region_id() {
3443 use store_api::metrics::{REGION_QUERY_CPU_TIME, REGION_QUERY_SCANNED_BYTES};
3444
3445 let logical_region_id = RegionId::new(1024, 10002);
3446 let physical_region_id = RegionId::new(1024, 1);
3447 let logical_region_id_label = logical_region_id.to_string();
3448 let physical_region_id_label = physical_region_id.to_string();
3449 let logical_labels = [&logical_region_id_label];
3450 let physical_labels = [&physical_region_id_label];
3451 let _ = REGION_QUERY_CPU_TIME.remove_label_values(&logical_labels);
3452 let _ = REGION_QUERY_SCANNED_BYTES.remove_label_values(&logical_labels);
3453 let _ = REGION_QUERY_CPU_TIME.remove_label_values(&physical_labels);
3454 let _ = REGION_QUERY_SCANNED_BYTES.remove_label_values(&physical_labels);
3455
3456 let plan = LogicalPlanBuilder::empty(true)
3457 .project(vec![lit(1i32).alias("col1")])
3458 .unwrap()
3459 .build()
3460 .unwrap();
3461 let schema = plan.schema().as_arrow().clone();
3462 let exec = MergeScanExec::new(
3463 &SessionStateBuilder::new().build(),
3464 TableName::new("catalog", "schema", "table"),
3465 vec![logical_region_id],
3466 plan,
3467 &schema,
3468 Arc::new(TestRegionQueryHandler::default()),
3469 QueryContext::arc(),
3470 1,
3471 None,
3472 true,
3473 )
3474 .unwrap();
3475
3476 let metrics = RecordBatchMetrics {
3477 elapsed_compute: 42,
3478 query_load_region_id: Some(physical_region_id.as_u64()),
3479 plan_metrics: vec![PlanMetrics {
3480 plan: "RegionScanExec: region=1".to_string(),
3481 plan_name: REGION_SCAN_EXEC_NAME.to_string(),
3482 level: 0,
3483 metrics: vec![("output_bytes".to_string(), 24)],
3484 }],
3485 ..Default::default()
3486 };
3487 exec.sub_stage_metrics
3488 .lock()
3489 .unwrap()
3490 .insert(logical_region_id, metrics);
3491
3492 drop(exec);
3493
3494 assert_eq!(
3495 REGION_QUERY_CPU_TIME
3496 .with_label_values(&logical_labels)
3497 .get(),
3498 0
3499 );
3500 assert_eq!(
3501 REGION_QUERY_SCANNED_BYTES
3502 .with_label_values(&logical_labels)
3503 .get(),
3504 0
3505 );
3506 assert_eq!(
3507 REGION_QUERY_CPU_TIME
3508 .with_label_values(&physical_labels)
3509 .get(),
3510 42
3511 );
3512 assert_eq!(
3513 REGION_QUERY_SCANNED_BYTES
3514 .with_label_values(&physical_labels)
3515 .get(),
3516 24
3517 );
3518
3519 let _ = REGION_QUERY_CPU_TIME.remove_label_values(&logical_labels);
3520 let _ = REGION_QUERY_SCANNED_BYTES.remove_label_values(&logical_labels);
3521 let _ = REGION_QUERY_CPU_TIME.remove_label_values(&physical_labels);
3522 let _ = REGION_QUERY_SCANNED_BYTES.remove_label_values(&physical_labels);
3523 }
3524}