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