1use std::sync::Arc;
18
19use ahash::HashMap;
20use async_trait::async_trait;
21use catalog::CatalogManagerRef;
22use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
23use common_telemetry::debug;
24use datafusion::common::Result;
25use datafusion::datasource::DefaultTableSource;
26use datafusion::execution::context::SessionState;
27use datafusion::physical_plan::ExecutionPlan;
28use datafusion::physical_planner::{ExtensionPlanner, PhysicalPlanner};
29use datafusion_common::tree_node::{TreeNode, TreeNodeRecursion, TreeNodeVisitor};
30use datafusion_common::{DataFusionError, TableReference};
31use datafusion_expr::{LogicalPlan, UserDefinedLogicalNode};
32use partition::manager::{PartitionRuleManagerRef, create_partitions_from_region_routes};
33use session::context::QueryContext;
34use snafu::{OptionExt, ResultExt};
35use store_api::storage::RegionId;
36pub use table::metadata::TableType;
37use table::table::adapter::DfTableProviderAdapter;
38use table::table_name::TableName;
39
40use crate::dist_plan::PredicateExtractor;
41use crate::dist_plan::merge_scan::{MergeScanExec, MergeScanLogicalPlan};
42use crate::dist_plan::merge_sort::MergeSortLogicalPlan;
43use crate::dist_plan::region_pruner::ConstraintPruner;
44use crate::error::{CatalogSnafu, PartitionRuleManagerSnafu, TableNotFoundSnafu};
45use crate::region_query::RegionQueryHandlerRef;
46
47pub struct MergeSortExtensionPlanner {}
57
58#[async_trait]
59impl ExtensionPlanner for MergeSortExtensionPlanner {
60 async fn plan_extension(
61 &self,
62 planner: &dyn PhysicalPlanner,
63 node: &dyn UserDefinedLogicalNode,
64 _logical_inputs: &[&LogicalPlan],
65 physical_inputs: &[Arc<dyn ExecutionPlan>],
66 session_state: &SessionState,
67 ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
68 if let Some(merge_sort) = node.as_any().downcast_ref::<MergeSortLogicalPlan>() {
69 if let LogicalPlan::Extension(ext) = &merge_sort.input.as_ref()
70 && ext
71 .node
72 .as_any()
73 .downcast_ref::<MergeScanLogicalPlan>()
74 .is_some()
75 {
76 let merge_scan_exec = physical_inputs
77 .first()
78 .and_then(|p| p.as_any().downcast_ref::<MergeScanExec>())
79 .ok_or(DataFusionError::Internal(format!(
80 "Expect MergeSort's input is a MergeScanExec, found {:?}",
81 physical_inputs
82 )))?;
83
84 let partition_cnt = merge_scan_exec.partition_count();
85 let region_cnt = merge_scan_exec.region_count();
86 let can_merge_sort = partition_cnt >= region_cnt;
89 if can_merge_sort {
90 }
92 let ret = planner
95 .create_physical_plan(&merge_sort.clone().into_sort(), session_state)
96 .await?;
97 Ok(Some(ret))
98 } else {
99 Ok(None)
100 }
101 } else {
102 Ok(None)
103 }
104 }
105}
106
107pub struct DistExtensionPlanner {
108 catalog_manager: CatalogManagerRef,
109 partition_rule_manager: PartitionRuleManagerRef,
110 region_query_handler: RegionQueryHandlerRef,
111 enable_per_region_metrics: bool,
112}
113
114impl DistExtensionPlanner {
115 pub fn new(
116 catalog_manager: CatalogManagerRef,
117 partition_rule_manager: PartitionRuleManagerRef,
118 region_query_handler: RegionQueryHandlerRef,
119 enable_per_region_metrics: bool,
120 ) -> Self {
121 Self {
122 catalog_manager,
123 partition_rule_manager,
124 region_query_handler,
125 enable_per_region_metrics,
126 }
127 }
128}
129
130#[async_trait]
131impl ExtensionPlanner for DistExtensionPlanner {
132 async fn plan_extension(
133 &self,
134 planner: &dyn PhysicalPlanner,
135 node: &dyn UserDefinedLogicalNode,
136 _logical_inputs: &[&LogicalPlan],
137 _physical_inputs: &[Arc<dyn ExecutionPlan>],
138 session_state: &SessionState,
139 ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
140 let Some(merge_scan) = node.as_any().downcast_ref::<MergeScanLogicalPlan>() else {
141 return Ok(None);
142 };
143
144 let input_plan = merge_scan.input();
145 let fallback = |logical_plan| async move {
146 let optimized_plan = self.optimize_input_logical_plan(session_state, logical_plan)?;
147 planner
148 .create_physical_plan(&optimized_plan, session_state)
149 .await
150 .map(Some)
151 };
152
153 if merge_scan.is_placeholder() {
154 return fallback(input_plan).await;
156 }
157
158 let optimized_plan = input_plan;
159 let Some(table_name) = Self::extract_full_table_name(input_plan)? else {
160 return fallback(optimized_plan).await;
162 };
163
164 let Ok(regions) = self.get_regions(&table_name, input_plan).await else {
165 return fallback(optimized_plan).await;
167 };
168
169 let schema = optimized_plan.schema().as_arrow();
171 let query_ctx = session_state
172 .config()
173 .get_extension()
174 .unwrap_or_else(QueryContext::arc);
175 let merge_scan_plan = MergeScanExec::new(
176 session_state,
177 table_name,
178 regions,
179 input_plan.clone(),
180 schema,
181 self.region_query_handler.clone(),
182 query_ctx,
183 session_state.config().target_partitions(),
184 merge_scan.partition_cols().clone(),
185 merge_scan.remote_dyn_filter_producer_id(),
186 self.enable_per_region_metrics,
187 )?;
188 Ok(Some(Arc::new(merge_scan_plan) as _))
189 }
190}
191
192impl DistExtensionPlanner {
193 fn extract_full_table_name(plan: &LogicalPlan) -> Result<Option<TableName>> {
195 let mut extractor = TableNameExtractor::default();
196 let _ = plan.visit(&mut extractor)?;
197 Ok(extractor.table_name)
198 }
199
200 async fn get_regions(
201 &self,
202 table_name: &TableName,
203 logical_plan: &LogicalPlan,
204 ) -> Result<Vec<RegionId>> {
205 let table = self
206 .catalog_manager
207 .table(
208 &table_name.catalog_name,
209 &table_name.schema_name,
210 &table_name.table_name,
211 None,
212 )
213 .await
214 .context(CatalogSnafu)?
215 .with_context(|| TableNotFoundSnafu {
216 table: table_name.to_string(),
217 })?;
218
219 let table_info = table.table_info();
220 let physical_table_route = self
221 .partition_rule_manager
222 .find_physical_table_route(table_info.table_id())
223 .await
224 .context(PartitionRuleManagerSnafu)?;
225 let all_regions = physical_table_route
226 .region_routes
227 .iter()
228 .map(|r| RegionId::new(table_info.table_id(), r.region.id.region_number()))
229 .collect::<Vec<_>>();
230 let partition_columns: Vec<String> =
232 table_info.meta.partition_column_names().cloned().collect();
233 debug!(
234 "DistExtensionPlanner: loaded table partition metadata, table: {}, table_id: {}, partition_key_indices: {:?}, partition_columns: {:?}, all_regions: {:?}",
235 table_name,
236 table_info.table_id(),
237 table_info.meta.partition_key_indices,
238 partition_columns,
239 all_regions,
240 );
241 if partition_columns.is_empty() {
242 return Ok(all_regions);
243 }
244 let partition_column_types = partition_columns
245 .iter()
246 .map(|col_name| {
247 let data_type = table_info
248 .meta
249 .schema
250 .column_schema_by_name(col_name)
251 .unwrap()
253 .data_type
254 .clone();
255 (col_name.clone(), data_type)
256 })
257 .collect::<HashMap<_, _>>();
258
259 let partition_expressions = match PredicateExtractor::extract_partition_expressions(
261 logical_plan,
262 &partition_columns,
263 ) {
264 Ok(expressions) => expressions,
265 Err(err) => {
266 common_telemetry::debug!(
267 "Failed to extract partition expressions for table {} (id: {}), using all regions: {:?}",
268 table_name,
269 table.table_info().table_id(),
270 err
271 );
272 return Ok(all_regions);
273 }
274 };
275
276 if partition_expressions.is_empty() {
277 return Ok(all_regions);
278 }
279
280 let partitions = match create_partitions_from_region_routes(
282 table_info.table_id(),
283 &physical_table_route.region_routes,
284 ) {
285 Ok(partitions) => partitions,
286 Err(err) => {
287 common_telemetry::debug!(
288 "Failed to get partition information for table {}, using all regions: {:?}",
289 table_name,
290 err
291 );
292 return Ok(all_regions);
293 }
294 };
295 if partitions.is_empty() {
296 return Ok(all_regions);
297 }
298
299 let pruned_regions = match ConstraintPruner::prune_regions(
301 &partition_expressions,
302 &partitions,
303 partition_column_types,
304 ) {
305 Ok(regions) => regions,
306 Err(err) => {
307 common_telemetry::debug!(
308 "Failed to prune regions for table {}, using all regions: {:?}",
309 table_name,
310 err
311 );
312 return Ok(all_regions);
313 }
314 };
315
316 common_telemetry::debug!(
317 "Region pruning for table {}: {} partition expressions applied, pruned from {} to {} regions",
318 table_name,
319 partition_expressions.len(),
320 all_regions.len(),
321 pruned_regions.len()
322 );
323
324 Ok(pruned_regions)
325 }
326
327 fn optimize_input_logical_plan(
329 &self,
330 session_state: &SessionState,
331 plan: &LogicalPlan,
332 ) -> Result<LogicalPlan> {
333 let state = session_state.clone();
334 state.optimizer().optimize(plan.clone(), &state, |_, _| {})
335 }
336}
337
338#[derive(Default)]
340struct TableNameExtractor {
341 pub table_name: Option<TableName>,
342}
343
344impl TreeNodeVisitor<'_> for TableNameExtractor {
345 type Node = LogicalPlan;
346
347 fn f_down(&mut self, node: &Self::Node) -> Result<TreeNodeRecursion> {
348 match node {
349 LogicalPlan::TableScan(scan) => {
350 if let Some(source) = scan.source.as_any().downcast_ref::<DefaultTableSource>()
351 && let Some(provider) = source
352 .table_provider
353 .as_any()
354 .downcast_ref::<DfTableProviderAdapter>()
355 {
356 if provider.table().table_type() == TableType::Base {
357 let info = provider.table().table_info();
358 self.table_name = Some(TableName::new(
359 info.catalog_name.clone(),
360 info.schema_name.clone(),
361 info.name.clone(),
362 ));
363 }
364 return Ok(TreeNodeRecursion::Stop);
365 }
366 match &scan.table_name {
367 TableReference::Full {
368 catalog,
369 schema,
370 table,
371 } => {
372 self.table_name = Some(TableName::new(
373 catalog.to_string(),
374 schema.to_string(),
375 table.to_string(),
376 ));
377 Ok(TreeNodeRecursion::Stop)
378 }
379 TableReference::Partial { schema, table } => {
381 self.table_name = Some(TableName::new(
382 DEFAULT_CATALOG_NAME.to_string(),
383 schema.to_string(),
384 table.to_string(),
385 ));
386 Ok(TreeNodeRecursion::Stop)
387 }
388 TableReference::Bare { table } => {
389 self.table_name = Some(TableName::new(
390 DEFAULT_CATALOG_NAME.to_string(),
391 DEFAULT_SCHEMA_NAME.to_string(),
392 table.to_string(),
393 ));
394 Ok(TreeNodeRecursion::Stop)
395 }
396 }
397 }
398 _ => Ok(TreeNodeRecursion::Continue),
399 }
400 }
401}