1use std::sync::Arc;
18
19use ahash::HashMap;
20use arrow_schema::SortOptions;
21use async_trait::async_trait;
22use catalog::CatalogManagerRef;
23use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
24use common_telemetry::debug;
25use datafusion::common::Result;
26use datafusion::datasource::DefaultTableSource;
27use datafusion::execution::context::SessionState;
28use datafusion::physical_plan::ExecutionPlan;
29use datafusion::physical_planner::{ExtensionPlanner, PhysicalPlanner};
30use datafusion_common::tree_node::{TreeNode, TreeNodeRecursion, TreeNodeVisitor};
31use datafusion_common::{DataFusionError, TableReference};
32use datafusion_expr::{LogicalPlan, UserDefinedLogicalNode};
33use datafusion_physical_expr::{LexOrdering, PhysicalSortExpr};
34use datatypes::prelude::ConcreteDataType;
35use partition::expr::PartitionExpr;
36use partition::manager::{PartitionRuleManagerRef, create_partitions_from_region_routes};
37use session::context::QueryContext;
38use snafu::{OptionExt, ResultExt};
39use store_api::storage::RegionId;
40use table::metadata::TableInfo;
41pub use table::metadata::TableType;
42use table::table::adapter::DfTableProviderAdapter;
43use table::table_name::TableName;
44
45use crate::dist_plan::PredicateExtractor;
46use crate::dist_plan::merge_scan::{MergeScanExec, MergeScanLogicalPlan};
47use crate::dist_plan::merge_sort::{MergeSortExec, MergeSortLogicalPlan};
48use crate::dist_plan::region_pruner::ConstraintPruner;
49use crate::error::{CatalogSnafu, PartitionRuleManagerSnafu, TableNotFoundSnafu};
50use crate::region_query::RegionQueryHandlerRef;
51
52pub struct MergeSortExtensionPlanner {}
60
61impl MergeSortExtensionPlanner {
62 fn ordering(
63 session_state: &SessionState,
64 merge_sort: &MergeSortLogicalPlan,
65 ) -> Result<LexOrdering> {
66 let ordering = merge_sort
67 .expr
68 .iter()
69 .map(|sort_expr| {
70 let physical_expr = session_state
71 .create_physical_expr(sort_expr.expr.clone(), merge_sort.input.schema())?;
72 Ok(PhysicalSortExpr::new(
73 physical_expr,
74 SortOptions {
75 descending: !sort_expr.asc,
76 nulls_first: sort_expr.nulls_first,
77 },
78 ))
79 })
80 .collect::<Result<Vec<_>>>()?;
81
82 LexOrdering::new(ordering).ok_or_else(|| {
83 DataFusionError::Internal(
84 "Expect MergeSort to have non-empty sort expressions".to_string(),
85 )
86 })
87 }
88}
89
90#[async_trait]
91impl ExtensionPlanner for MergeSortExtensionPlanner {
92 async fn plan_extension(
93 &self,
94 _planner: &dyn PhysicalPlanner,
95 node: &dyn UserDefinedLogicalNode,
96 _logical_inputs: &[&LogicalPlan],
97 physical_inputs: &[Arc<dyn ExecutionPlan>],
98 session_state: &SessionState,
99 ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
100 if let Some(merge_sort) = node.as_any().downcast_ref::<MergeSortLogicalPlan>() {
101 if let LogicalPlan::Extension(ext) = &merge_sort.input.as_ref()
102 && ext
103 .node
104 .as_any()
105 .downcast_ref::<MergeScanLogicalPlan>()
106 .is_some()
107 {
108 let input = physical_inputs.first().cloned().ok_or_else(|| {
109 DataFusionError::Internal(
110 "Expect MergeSort to have one physical input".to_string(),
111 )
112 })?;
113 if input.as_any().downcast_ref::<MergeScanExec>().is_none() {
114 return Err(DataFusionError::Internal(format!(
115 "Expect MergeSort's input is a MergeScanExec, found {:?}",
116 physical_inputs
117 )));
118 }
119
120 let ordering = Self::ordering(session_state, merge_sort)?;
121 Ok(Some(Arc::new(MergeSortExec::new(
122 ordering,
123 input,
124 merge_sort.fetch,
125 ))))
126 } else {
127 Ok(None)
128 }
129 } else {
130 Ok(None)
131 }
132 }
133}
134
135pub struct DistExtensionPlanner {
136 catalog_manager: CatalogManagerRef,
137 partition_rule_manager: PartitionRuleManagerRef,
138 region_query_handler: RegionQueryHandlerRef,
139 enable_per_region_metrics: bool,
140}
141
142impl DistExtensionPlanner {
143 pub fn new(
144 catalog_manager: CatalogManagerRef,
145 partition_rule_manager: PartitionRuleManagerRef,
146 region_query_handler: RegionQueryHandlerRef,
147 enable_per_region_metrics: bool,
148 ) -> Self {
149 Self {
150 catalog_manager,
151 partition_rule_manager,
152 region_query_handler,
153 enable_per_region_metrics,
154 }
155 }
156}
157
158#[async_trait]
159impl ExtensionPlanner for DistExtensionPlanner {
160 async fn plan_extension(
161 &self,
162 planner: &dyn PhysicalPlanner,
163 node: &dyn UserDefinedLogicalNode,
164 _logical_inputs: &[&LogicalPlan],
165 _physical_inputs: &[Arc<dyn ExecutionPlan>],
166 session_state: &SessionState,
167 ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
168 let Some(merge_scan) = node.as_any().downcast_ref::<MergeScanLogicalPlan>() else {
169 return Ok(None);
170 };
171
172 let input_plan = merge_scan.input();
173 let fallback = |logical_plan| async move {
174 let optimized_plan = self.optimize_input_logical_plan(session_state, logical_plan)?;
175 planner
176 .create_physical_plan(&optimized_plan, session_state)
177 .await
178 .map(Some)
179 };
180
181 if merge_scan.is_placeholder() {
182 return fallback(input_plan).await;
184 }
185
186 let optimized_plan = input_plan;
187 let Some(table_name) = Self::extract_full_table_name(input_plan)? else {
188 return fallback(optimized_plan).await;
190 };
191
192 let Ok(regions) = self.get_regions(&table_name, input_plan).await else {
193 return fallback(optimized_plan).await;
195 };
196
197 let schema = merge_scan.schema().as_arrow();
199 let query_ctx = session_state
200 .config()
201 .get_extension()
202 .unwrap_or_else(QueryContext::arc);
203 let merge_scan_plan = MergeScanExec::new(
204 session_state,
205 table_name,
206 regions,
207 input_plan.clone(),
208 schema,
209 self.region_query_handler.clone(),
210 query_ctx,
211 session_state.config().target_partitions(),
212 merge_scan.partition_cols().clone(),
213 merge_scan.remote_dyn_filter_producer_id(),
214 self.enable_per_region_metrics,
215 )?;
216 Ok(Some(Arc::new(merge_scan_plan) as _))
217 }
218}
219
220impl DistExtensionPlanner {
221 fn extract_full_table_name(plan: &LogicalPlan) -> Result<Option<TableName>> {
223 let mut extractor = TableNameExtractor::default();
224 let _ = plan.visit(&mut extractor)?;
225 Ok(extractor.table_name)
226 }
227
228 async fn get_regions(
229 &self,
230 table_name: &TableName,
231 logical_plan: &LogicalPlan,
232 ) -> Result<Vec<RegionId>> {
233 let table = self
234 .catalog_manager
235 .table(
236 &table_name.catalog_name,
237 &table_name.schema_name,
238 &table_name.table_name,
239 None,
240 )
241 .await
242 .context(CatalogSnafu)?
243 .with_context(|| TableNotFoundSnafu {
244 table: table_name.to_string(),
245 })?;
246
247 let table_info = table.table_info();
248 let (physical_table_id, physical_table_route) = self
249 .partition_rule_manager
250 .find_physical_table_route_with_id(table_info.table_id())
251 .await
252 .context(PartitionRuleManagerSnafu)?;
253 let all_regions = physical_table_route
254 .region_routes
255 .iter()
256 .map(|r| RegionId::new(table_info.table_id(), r.region.id.region_number()))
257 .collect::<Vec<_>>();
258 let logical_partition_columns = partition_column_types(&table_info);
259 let partition_columns = logical_partition_columns
260 .iter()
261 .map(|(name, _)| name.clone())
262 .collect::<Vec<_>>();
263 debug!(
264 "DistExtensionPlanner: loaded table partition metadata, table: {}, table_id: {}, partition_key_indices: {:?}, partition_columns: {:?}, all_regions: {:?}",
265 table_name,
266 table_info.table_id(),
267 table_info.meta.partition_key_indices,
268 partition_columns,
269 all_regions,
270 );
271 if partition_columns.is_empty() {
272 return Ok(all_regions);
273 }
274 let partition_expressions = match PredicateExtractor::extract_partition_expressions(
276 logical_plan,
277 &partition_columns,
278 ) {
279 Ok(expressions) => expressions,
280 Err(err) => {
281 common_telemetry::debug!(
282 "Failed to extract partition expressions for table {} (id: {}), using all regions: {:?}",
283 table_name,
284 table.table_info().table_id(),
285 err
286 );
287 return Ok(all_regions);
288 }
289 };
290
291 if partition_expressions.is_empty() {
292 return Ok(all_regions);
293 }
294
295 let Some(partition_column_types) = self
296 .partition_column_types_for_pruning(
297 table_name,
298 table_info.as_ref(),
299 physical_table_id,
300 &partition_expressions,
301 &all_regions,
302 )
303 .await
304 else {
305 return Ok(all_regions);
306 };
307
308 let partitions = match create_partitions_from_region_routes(
310 table_info.table_id(),
311 &physical_table_route.region_routes,
312 ) {
313 Ok(partitions) => partitions,
314 Err(err) => {
315 common_telemetry::debug!(
316 "Failed to get partition information for table {}, using all regions: {:?}",
317 table_name,
318 err
319 );
320 return Ok(all_regions);
321 }
322 };
323 if partitions.is_empty() {
324 return Ok(all_regions);
325 }
326 let pruned_regions = match ConstraintPruner::prune_regions(
328 &partition_expressions,
329 &partitions,
330 partition_column_types,
331 ) {
332 Ok(regions) => regions,
333 Err(err) => {
334 common_telemetry::debug!(
335 "Failed to prune regions for table {}, using all regions: {:?}",
336 table_name,
337 err
338 );
339 return Ok(all_regions);
340 }
341 };
342
343 common_telemetry::debug!(
344 "Region pruning for table {}: {} partition expressions applied, pruned from {} to {} regions",
345 table_name,
346 partition_expressions.len(),
347 all_regions.len(),
348 pruned_regions.len()
349 );
350
351 Ok(pruned_regions)
352 }
353
354 async fn partition_column_types_for_pruning(
362 &self,
363 table_name: &TableName,
364 logical_table_info: &TableInfo,
365 physical_table_id: u32,
366 partition_expressions: &[PartitionExpr],
367 all_regions: &[RegionId],
368 ) -> Option<HashMap<String, ConcreteDataType>> {
369 let physical_partition_columns = if physical_table_id == logical_table_info.table_id() {
370 partition_column_types(logical_table_info)
371 } else {
372 match self
373 .catalog_manager
374 .table_info_by_id(physical_table_id)
375 .await
376 {
377 Ok(Some(physical_table_info)) => {
378 partition_column_types(physical_table_info.as_ref())
379 }
380 Ok(None) => {
381 debug!(
382 "DistExtensionPlanner: physical table info not found for table {} (id: {}), using all regions: {:?}",
383 table_name, physical_table_id, all_regions
384 );
385 return None;
386 }
387 Err(err) => {
388 debug!(
389 "DistExtensionPlanner: failed to load physical table info for table {} (id: {}): {}, using all regions: {:?}",
390 table_name, physical_table_id, err, all_regions
391 );
392 return None;
393 }
394 }
395 };
396 let physical_column_types = physical_partition_columns
397 .into_iter()
398 .collect::<HashMap<_, _>>();
399 let logical_column_types = partition_column_types(logical_table_info)
400 .into_iter()
401 .collect::<HashMap<_, _>>();
402 let mut predicate_column_names = std::collections::HashSet::new();
403 for expression in partition_expressions {
404 expression.collect_column_names(&mut predicate_column_names);
405 }
406 if predicate_column_names
407 .iter()
408 .any(|name| logical_column_types.get(name) != physical_column_types.get(name))
409 {
410 debug!(
411 "DistExtensionPlanner: logical and physical partition metadata mismatch for table {} (physical id: {}), using all regions: {:?}",
412 table_name, physical_table_id, all_regions
413 );
414 return None;
415 }
416
417 Some(physical_column_types)
418 }
419
420 fn optimize_input_logical_plan(
422 &self,
423 session_state: &SessionState,
424 plan: &LogicalPlan,
425 ) -> Result<LogicalPlan> {
426 let state = session_state.clone();
427 state.optimizer().optimize(plan.clone(), &state, |_, _| {})
428 }
429}
430
431fn partition_column_types(table_info: &TableInfo) -> Vec<(String, ConcreteDataType)> {
432 table_info
433 .meta
434 .partition_columns()
435 .map(|column| (column.name.clone(), column.data_type.clone()))
436 .collect()
437}
438
439#[derive(Default)]
441struct TableNameExtractor {
442 pub table_name: Option<TableName>,
443}
444
445impl TreeNodeVisitor<'_> for TableNameExtractor {
446 type Node = LogicalPlan;
447
448 fn f_down(&mut self, node: &Self::Node) -> Result<TreeNodeRecursion> {
449 match node {
450 LogicalPlan::TableScan(scan) => {
451 if let Some(source) = scan.source.as_any().downcast_ref::<DefaultTableSource>()
452 && let Some(provider) = source
453 .table_provider
454 .as_any()
455 .downcast_ref::<DfTableProviderAdapter>()
456 {
457 if provider.table().table_type() == TableType::Base {
458 let info = provider.table().table_info();
459 self.table_name = Some(TableName::new(
460 info.catalog_name.clone(),
461 info.schema_name.clone(),
462 info.name.clone(),
463 ));
464 }
465 return Ok(TreeNodeRecursion::Stop);
466 }
467 match &scan.table_name {
468 TableReference::Full {
469 catalog,
470 schema,
471 table,
472 } => {
473 self.table_name = Some(TableName::new(
474 catalog.to_string(),
475 schema.to_string(),
476 table.to_string(),
477 ));
478 Ok(TreeNodeRecursion::Stop)
479 }
480 TableReference::Partial { schema, table } => {
482 self.table_name = Some(TableName::new(
483 DEFAULT_CATALOG_NAME.to_string(),
484 schema.to_string(),
485 table.to_string(),
486 ));
487 Ok(TreeNodeRecursion::Stop)
488 }
489 TableReference::Bare { table } => {
490 self.table_name = Some(TableName::new(
491 DEFAULT_CATALOG_NAME.to_string(),
492 DEFAULT_SCHEMA_NAME.to_string(),
493 table.to_string(),
494 ));
495 Ok(TreeNodeRecursion::Stop)
496 }
497 }
498 }
499 _ => Ok(TreeNodeRecursion::Continue),
500 }
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use std::collections::HashMap;
507 use std::sync::Arc;
508
509 use api::v1::region::{RemoteDynFilterUnregister, RemoteDynFilterUpdate};
510 use async_trait::async_trait;
511 use catalog::memory::MemoryCatalogManager;
512 use catalog::{CatalogManagerRef, RegisterTableRequest};
513 use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
514 use common_meta::cache::new_table_route_cache;
515 use common_meta::key::TableMetadataManager;
516 use common_meta::key::table_route::TableRouteValue;
517 use common_meta::kv_backend::memory::MemoryKvBackend;
518 use common_meta::rpc::router::{Region, RegionRoute};
519 use common_query::request::QueryRequest;
520 use common_recordbatch::SendableRecordBatchStream;
521 use datafusion::datasource::DefaultTableSource;
522 use datafusion_expr::{LogicalPlan, LogicalPlanBuilder, col as df_col, lit};
523 use datatypes::prelude::ConcreteDataType;
524 use datatypes::schema::{ColumnSchema, Schema};
525 use datatypes::value::Value;
526 use moka::future::CacheBuilder;
527 use partition::cache::new_partition_info_cache;
528 use partition::expr::{PartitionExpr, col as partition_col};
529 use partition::manager::PartitionRuleManager;
530 use session::ReadPreference;
531 use store_api::storage::RegionId;
532 use table::metadata::{TableInfo, TableInfoBuilder, TableMeta, TableType};
533 use table::table::adapter::DfTableProviderAdapter;
534 use table::table_name::TableName;
535 use table::test_util::EmptyTable;
536
537 use super::DistExtensionPlanner;
538 use crate::region_query::{RegionQueryHandler, RegionQueryTarget};
539
540 const LOGICAL_TABLE_ID: u32 = 1024;
541 const PHYSICAL_TABLE_ID: u32 = 2048;
542
543 struct UnusedRegionQueryHandler;
544
545 #[async_trait]
546 impl RegionQueryHandler for UnusedRegionQueryHandler {
547 async fn select_target(
548 &self,
549 _read_preference: ReadPreference,
550 _region_id: RegionId,
551 ) -> crate::error::Result<RegionQueryTarget> {
552 unreachable!("get_regions does not select region query targets")
553 }
554
555 async fn do_get(
556 &self,
557 _target: &RegionQueryTarget,
558 _request: QueryRequest,
559 ) -> crate::error::Result<SendableRecordBatchStream> {
560 unreachable!("get_regions does not query regions")
561 }
562
563 async fn handle_remote_dyn_filter_update(
564 &self,
565 _target: &RegionQueryTarget,
566 _query_id: String,
567 _update: RemoteDynFilterUpdate,
568 ) -> crate::error::Result<()> {
569 unreachable!("get_regions does not update dynamic filters")
570 }
571
572 async fn handle_remote_dyn_filter_unregister(
573 &self,
574 _target: &RegionQueryTarget,
575 _query_id: String,
576 _unregister: RemoteDynFilterUnregister,
577 ) -> crate::error::Result<()> {
578 unreachable!("get_regions does not unregister dynamic filters")
579 }
580 }
581
582 fn table_info(
583 table_id: u32,
584 name: &str,
585 columns: &[&str],
586 partition_keys: Vec<usize>,
587 ) -> TableInfo {
588 let schema = Arc::new(Schema::new(
589 columns
590 .iter()
591 .map(|name| ColumnSchema::new(*name, ConcreteDataType::string_datatype(), true))
592 .collect(),
593 ));
594 let meta = TableMeta {
595 schema,
596 primary_key_indices: vec![],
597 value_indices: vec![],
598 engine: "metric".to_string(),
599 next_column_id: columns.len() as u32,
600 options: Default::default(),
601 created_on: Default::default(),
602 updated_on: Default::default(),
603 partition_key_indices: partition_keys,
604 column_ids: (0..columns.len() as u32).collect(),
605 };
606 TableInfoBuilder::default()
607 .table_id(table_id)
608 .table_version(0)
609 .name(name.to_string())
610 .catalog_name(DEFAULT_CATALOG_NAME.to_string())
611 .schema_name(DEFAULT_SCHEMA_NAME.to_string())
612 .desc(None)
613 .table_type(TableType::Base)
614 .meta(meta)
615 .build()
616 .unwrap()
617 }
618
619 fn region_route(region_number: u32, expression: Option<PartitionExpr>) -> RegionRoute {
620 RegionRoute {
621 region: Region {
622 id: RegionId::new(PHYSICAL_TABLE_ID, region_number),
623 partition_expr: expression
624 .map(|expression| expression.as_json_str().unwrap())
625 .unwrap_or_default(),
626 ..Default::default()
627 },
628 ..Default::default()
629 }
630 }
631
632 async fn planner_and_plan(
633 physical_partition_keys: Vec<usize>,
634 expressions: Vec<Option<PartitionExpr>>,
635 ) -> (DistExtensionPlanner, LogicalPlan, TableName) {
636 let logical_info = table_info(LOGICAL_TABLE_ID, "logical", &["host"], vec![0]);
637 let physical_info = table_info(
638 PHYSICAL_TABLE_ID,
639 "physical",
640 &["host", "rack"],
641 physical_partition_keys,
642 );
643 let logical_table = EmptyTable::from_table_info(&logical_info);
644 let physical_table = EmptyTable::from_table_info(&physical_info);
645 let catalog_manager = MemoryCatalogManager::with_default_setup();
646 for table in [&logical_table, &physical_table] {
647 let info = table.table_info();
648 catalog_manager
649 .register_table_sync(RegisterTableRequest {
650 catalog: info.catalog_name.clone(),
651 schema: info.schema_name.clone(),
652 table_name: info.name.clone(),
653 table_id: info.table_id(),
654 table: table.clone(),
655 })
656 .unwrap();
657 }
658
659 let backend = Arc::new(MemoryKvBackend::default());
660 let metadata_manager = TableMetadataManager::new(backend.clone());
661 let routes = expressions
662 .into_iter()
663 .enumerate()
664 .map(|(index, expression)| region_route(index as u32 + 1, expression))
665 .collect();
666 metadata_manager
667 .create_table_metadata(
668 physical_info,
669 TableRouteValue::physical(routes),
670 HashMap::new(),
671 )
672 .await
673 .unwrap();
674 metadata_manager
675 .create_table_metadata(
676 logical_info,
677 TableRouteValue::logical(PHYSICAL_TABLE_ID),
678 HashMap::new(),
679 )
680 .await
681 .unwrap();
682
683 let table_route_cache = Arc::new(new_table_route_cache(
684 "planner-test-routes".to_string(),
685 CacheBuilder::new(16).build(),
686 backend.clone(),
687 ));
688 let partition_info_cache = Arc::new(new_partition_info_cache(
689 "planner-test-partitions".to_string(),
690 CacheBuilder::new(16).build(),
691 table_route_cache.clone(),
692 ));
693 let partition_rule_manager = Arc::new(PartitionRuleManager::new(
694 backend,
695 table_route_cache,
696 partition_info_cache,
697 ));
698 let (resolved_physical_id, physical_route) = partition_rule_manager
699 .find_physical_table_route_with_id(PHYSICAL_TABLE_ID)
700 .await
701 .unwrap();
702 assert_eq!(PHYSICAL_TABLE_ID, resolved_physical_id);
703 let (resolved_logical_id, logical_route) = partition_rule_manager
704 .find_physical_table_route_with_id(LOGICAL_TABLE_ID)
705 .await
706 .unwrap();
707 assert_eq!(PHYSICAL_TABLE_ID, resolved_logical_id);
708 assert_eq!(physical_route.region_routes, logical_route.region_routes);
709 let catalog_manager: CatalogManagerRef = catalog_manager;
710 let planner = DistExtensionPlanner::new(
711 catalog_manager,
712 partition_rule_manager,
713 Arc::new(UnusedRegionQueryHandler),
714 false,
715 );
716 let table_source = Arc::new(DefaultTableSource::new(Arc::new(
717 DfTableProviderAdapter::new(logical_table),
718 )));
719 let plan = LogicalPlanBuilder::scan_with_filters("logical", table_source, None, vec![])
720 .unwrap()
721 .filter(df_col("host").eq(lit("a")))
722 .unwrap()
723 .build()
724 .unwrap();
725 (
726 planner,
727 plan,
728 TableName::new(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, "logical"),
729 )
730 }
731
732 fn physical_partition_expressions() -> Vec<Option<PartitionExpr>> {
733 vec![
734 Some(partition_col("host").lt(Value::String("m".into()))),
735 Some(
736 partition_col("host")
737 .gt_eq(Value::String("m".into()))
738 .and(partition_col("rack").lt(Value::String("n".into()))),
739 ),
740 Some(
741 partition_col("host")
742 .gt_eq(Value::String("m".into()))
743 .and(partition_col("rack").gt_eq(Value::String("n".into()))),
744 ),
745 ]
746 }
747
748 #[tokio::test]
749 async fn logical_table_pruning_uses_physical_partition_datatypes() {
750 let (planner, plan, table_name) =
751 planner_and_plan(vec![0, 1], physical_partition_expressions()).await;
752
753 assert_eq!(
754 vec![RegionId::new(LOGICAL_TABLE_ID, 1)],
755 planner.get_regions(&table_name, &plan).await.unwrap()
756 );
757 }
758
759 #[tokio::test]
760 async fn missing_physical_partition_datatype_falls_back_to_all_logical_regions() {
761 let (planner, plan, table_name) =
762 planner_and_plan(vec![0], physical_partition_expressions()).await;
763
764 assert_all_logical_regions(planner.get_regions(&table_name, &plan).await.unwrap());
765 }
766
767 #[tokio::test]
768 async fn missing_route_partition_expression_falls_back_to_all_logical_regions() {
769 let mut expressions = physical_partition_expressions();
770 expressions[1] = None;
771 let (planner, plan, table_name) = planner_and_plan(vec![0, 1], expressions).await;
772
773 assert_all_logical_regions(planner.get_regions(&table_name, &plan).await.unwrap());
774 }
775
776 fn assert_all_logical_regions(mut regions: Vec<RegionId>) {
777 regions.sort_unstable();
778 assert_eq!(
779 vec![
780 RegionId::new(LOGICAL_TABLE_ID, 1),
781 RegionId::new(LOGICAL_TABLE_ID, 2),
782 RegionId::new(LOGICAL_TABLE_ID, 3),
783 ],
784 regions
785 );
786 }
787}