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