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