1mod inspect;
16
17use std::collections::HashMap;
18use std::sync::Arc;
19
20use datafusion::catalog::{
21 CatalogProvider, CatalogProviderList, MemTable, SchemaProvider, TableProvider,
22};
23use datafusion::datasource::provider_as_source;
24use datafusion::error as df_error;
25use datafusion::error::Result as DfResult;
26use datafusion_common::DataFusionError;
27use datafusion_common::tree_node::{Transformed, TreeNode, TreeNodeRecursion, TreeNodeRewriter};
28use datafusion_expr::{LogicalPlan, TableSource};
29use futures::TryStreamExt;
30use session::context::QueryContextRef;
31use snafu::{OptionExt, ResultExt};
32use store_api::region_info::RegionInfoEntry;
33use store_api::sst_entry::{ManifestSstEntry, PuffinIndexMetaEntry, StorageSstEntry};
34use store_api::storage::RegionId;
35
36use crate::error::{DataFusionSnafu, ListStorageSstsSnafu, Result, UnexpectedSnafu};
37use crate::region_server::RegionServer;
38use crate::region_server::catalog::inspect::{InspectTable, ManifestSstSource};
39
40#[allow(clippy::enum_variant_names)]
43#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
44enum InternalTableKind {
45 InspectSstManifest,
46 InspectSstStorage,
47 InspectSstIndexMeta,
48 InspectRegionInfo,
49}
50
51impl InternalTableKind {
52 pub fn from_table_name(name: &str) -> Option<Self> {
54 if name.eq_ignore_ascii_case(ManifestSstEntry::reserved_table_name_for_inspection()) {
55 return Some(Self::InspectSstManifest);
56 }
57 if name.eq_ignore_ascii_case(StorageSstEntry::reserved_table_name_for_inspection()) {
58 return Some(Self::InspectSstStorage);
59 }
60 if name.eq_ignore_ascii_case(PuffinIndexMetaEntry::reserved_table_name_for_inspection()) {
61 return Some(Self::InspectSstIndexMeta);
62 }
63 if name.eq_ignore_ascii_case(RegionInfoEntry::reserved_table_name_for_inspection()) {
64 return Some(Self::InspectRegionInfo);
65 }
66 None
67 }
68
69 pub async fn table_provider(&self, server: &RegionServer) -> Result<Arc<dyn TableProvider>> {
71 match self {
72 Self::InspectSstManifest => server.inspect_sst_manifest_provider().await,
73 Self::InspectSstStorage => server.inspect_sst_storage_provider().await,
74 Self::InspectSstIndexMeta => server.inspect_sst_index_meta_provider().await,
75 Self::InspectRegionInfo => server.inspect_region_info_provider().await,
76 }
77 }
78}
79
80impl RegionServer {
81 pub async fn inspect_sst_manifest_provider(&self) -> Result<Arc<dyn TableProvider>> {
83 let mito = {
84 let guard = self.inner.mito_engine.read().unwrap();
85 guard.as_ref().cloned().context(UnexpectedSnafu {
86 violated: "mito engine not available",
87 })?
88 };
89
90 Ok(Arc::new(InspectTable::new(Arc::new(
91 ManifestSstSource::new(mito),
92 ))))
93 }
94
95 pub async fn inspect_sst_storage_provider(&self) -> Result<Arc<dyn TableProvider>> {
97 let mito = {
98 let guard = self.inner.mito_engine.read().unwrap();
99 guard.as_ref().cloned().context(UnexpectedSnafu {
100 violated: "mito engine not available",
101 })?
102 };
103 let entries = mito
104 .all_ssts_from_storage()
105 .try_collect::<Vec<_>>()
106 .await
107 .context(ListStorageSstsSnafu)?;
108 let schema = StorageSstEntry::schema().arrow_schema().clone();
109 let batch = StorageSstEntry::to_record_batch(&entries)
110 .map_err(DataFusionError::from)
111 .context(DataFusionSnafu)?;
112
113 let table = MemTable::try_new(schema, vec![vec![batch]]).context(DataFusionSnafu)?;
114 Ok(Arc::new(table))
115 }
116
117 pub async fn inspect_sst_index_meta_provider(&self) -> Result<Arc<dyn TableProvider>> {
119 let mito = {
120 let guard = self.inner.mito_engine.read().unwrap();
121 guard.as_ref().cloned().context(UnexpectedSnafu {
122 violated: "mito engine not available",
123 })?
124 };
125
126 let entries = mito.all_index_metas().await;
127 let schema = PuffinIndexMetaEntry::schema().arrow_schema().clone();
128 let batch = PuffinIndexMetaEntry::to_record_batch(&entries)
129 .map_err(DataFusionError::from)
130 .context(DataFusionSnafu)?;
131
132 let table = MemTable::try_new(schema, vec![vec![batch]]).context(DataFusionSnafu)?;
133 Ok(Arc::new(table))
134 }
135
136 pub async fn inspect_region_info_provider(&self) -> Result<Arc<dyn TableProvider>> {
138 let mito = {
139 let guard = self.inner.mito_engine.read().unwrap();
140 guard.as_ref().cloned().context(UnexpectedSnafu {
141 violated: "mito engine not available",
142 })?
143 };
144
145 let entries = mito.all_region_infos().await;
146 let schema = RegionInfoEntry::schema().arrow_schema().clone();
147 let batch = RegionInfoEntry::to_record_batch(&entries)
148 .map_err(DataFusionError::from)
149 .context(DataFusionSnafu)?;
150
151 let table = MemTable::try_new(schema, vec![vec![batch]]).context(DataFusionSnafu)?;
152 Ok(Arc::new(table))
153 }
154}
155
156#[derive(Clone, Debug)]
160pub(crate) struct NameAwareCatalogList {
161 catalog: NameAwareCatalogProvider,
162}
163
164impl NameAwareCatalogList {
165 pub fn new(server: RegionServer, region_id: RegionId, query_ctx: QueryContextRef) -> Self {
167 let schema_provider = NameAwareSchemaProvider {
168 server,
169 region_id,
170 query_ctx,
171 };
172 let catalog = NameAwareCatalogProvider {
173 schema: schema_provider,
174 };
175 Self { catalog }
176 }
177}
178
179impl CatalogProviderList for NameAwareCatalogList {
180 fn register_catalog(
181 &self,
182 _name: String,
183 _catalog: Arc<dyn CatalogProvider>,
184 ) -> Option<Arc<dyn CatalogProvider>> {
185 None
186 }
187 fn catalog_names(&self) -> Vec<String> {
188 vec![]
189 }
190 fn catalog(&self, _name: &str) -> Option<Arc<dyn CatalogProvider>> {
191 Some(Arc::new(self.catalog.clone()))
192 }
193}
194
195#[derive(Clone, Debug)]
196struct NameAwareCatalogProvider {
197 schema: NameAwareSchemaProvider,
198}
199
200impl CatalogProvider for NameAwareCatalogProvider {
201 fn schema_names(&self) -> Vec<String> {
202 vec![]
203 }
204 fn schema(&self, _name: &str) -> Option<Arc<dyn SchemaProvider>> {
205 Some(Arc::new(self.schema.clone()))
206 }
207}
208
209#[derive(Clone)]
210struct NameAwareSchemaProvider {
211 server: RegionServer,
212 region_id: RegionId,
213 query_ctx: QueryContextRef,
214}
215
216impl std::fmt::Debug for NameAwareSchemaProvider {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 write!(f, "NameAwareSchemaProvider")
219 }
220}
221
222#[async_trait::async_trait]
223impl SchemaProvider for NameAwareSchemaProvider {
224 fn table_names(&self) -> Vec<String> {
225 vec![]
226 }
227
228 async fn table(&self, name: &str) -> DfResult<Option<Arc<dyn TableProvider>>> {
229 if let Some(kind) = InternalTableKind::from_table_name(name) {
231 return kind
232 .table_provider(&self.server)
233 .await
234 .map(Some)
235 .map_err(|e| df_error::DataFusionError::External(Box::new(e)));
236 }
237
238 let provider = self
240 .server
241 .table_provider(self.region_id, Some(self.query_ctx.clone()))
242 .await
243 .map_err(|e| df_error::DataFusionError::External(Box::new(e)))?;
244 Ok(Some(provider))
245 }
246
247 fn table_exist(&self, _name: &str) -> bool {
248 true
249 }
250}
251pub(crate) struct NameAwareDataSourceInjectorBuilder {
257 need_region_provider: bool,
259 reserved_table_needed: Vec<InternalTableKind>,
261}
262
263impl NameAwareDataSourceInjectorBuilder {
264 pub fn from_plan(plan: &LogicalPlan) -> DfResult<Self> {
267 let mut need_region_provider = false;
268 let mut reserved_table_needed = Vec::new();
269 plan.apply(|node| {
270 if let LogicalPlan::TableScan(ts) = node {
271 let name = ts.table_name.to_string();
272 if let Some(kind) = InternalTableKind::from_table_name(&name) {
273 if !reserved_table_needed.contains(&kind) {
274 reserved_table_needed.push(kind);
275 }
276 } else {
277 need_region_provider = true;
279 }
280 }
281 Ok(TreeNodeRecursion::Continue)
282 })?;
283
284 Ok(Self {
285 need_region_provider,
286 reserved_table_needed,
287 })
288 }
289
290 pub async fn build(
291 self,
292 server: &RegionServer,
293 region_id: RegionId,
294 query_ctx: QueryContextRef,
295 ) -> Result<NameAwareDataSourceInjector> {
296 let region = if self.need_region_provider {
297 let provider = server.table_provider(region_id, Some(query_ctx)).await?;
298 Some(provider_as_source(provider))
299 } else {
300 None
301 };
302
303 let mut reserved_sources = HashMap::new();
304 for kind in &self.reserved_table_needed {
305 let provider = kind.table_provider(server).await?;
306 reserved_sources.insert(*kind, provider_as_source(provider));
307 }
308
309 Ok(NameAwareDataSourceInjector {
310 reserved_sources,
311 region_source: region,
312 })
313 }
314}
315
316pub(crate) struct NameAwareDataSourceInjector {
319 reserved_sources: HashMap<InternalTableKind, Arc<dyn TableSource>>,
321 region_source: Option<Arc<dyn TableSource>>,
323}
324
325impl TreeNodeRewriter for NameAwareDataSourceInjector {
326 type Node = LogicalPlan;
327
328 fn f_up(&mut self, node: Self::Node) -> DfResult<Transformed<Self::Node>> {
329 Ok(match node {
330 LogicalPlan::TableScan(mut scan) => {
331 let name = scan.table_name.to_string();
332 if let Some(kind) = InternalTableKind::from_table_name(&name)
333 && let Some(source) = self.reserved_sources.get(&kind)
334 {
335 scan.source = source.clone();
337 } else {
338 let Some(region) = &self.region_source else {
339 return Err(datafusion::error::DataFusionError::Plan(
341 "region provider not available".to_string(),
342 ));
343 };
344 scan.source = region.clone();
346 }
347 Transformed::yes(LogicalPlan::TableScan(scan))
348 }
349 _ => Transformed::no(node),
350 })
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 mod manifest;
357
358 use std::sync::Arc;
359
360 use datafusion::catalog::MemTable as DfMemTable;
361 use datafusion_common::tree_node::TreeNode;
362 use datafusion_expr::{LogicalPlanBuilder, table_scan};
363 use datatypes::arrow::array::Int32Array;
364 use datatypes::arrow::datatypes::{DataType, Field, Schema};
365 use datatypes::arrow::record_batch::RecordBatch;
366 use store_api::region_info::RegionInfoEntry;
367
368 use super::*; fn test_schema() -> Schema {
371 Schema::new(vec![Field::new("a", DataType::Int32, true)])
372 }
373
374 fn empty_mem_table() -> Arc<DfMemTable> {
375 let schema = Arc::new(test_schema());
376 let batch = RecordBatch::try_new(
377 schema.clone(),
378 vec![Arc::new(Int32Array::from(Vec::<i32>::new()))],
379 )
380 .unwrap();
381 Arc::new(DfMemTable::try_new(schema, vec![vec![batch]]).unwrap())
382 }
383
384 #[test]
385 fn test_injector_builder_from_plan_flags() {
386 let schema = test_schema();
387 let reserved = ManifestSstEntry::reserved_table_name_for_inspection();
388 let plan1 = table_scan(Some(reserved), &schema, None)
390 .unwrap()
391 .build()
392 .unwrap();
393 let b1 = NameAwareDataSourceInjectorBuilder::from_plan(&plan1).unwrap();
394 assert!(!b1.need_region_provider);
395 assert_eq!(
396 b1.reserved_table_needed,
397 vec![InternalTableKind::InspectSstManifest]
398 );
399
400 let plan2 = table_scan(Some("normal_table"), &schema, None)
402 .unwrap()
403 .build()
404 .unwrap();
405 let b2 = NameAwareDataSourceInjectorBuilder::from_plan(&plan2).unwrap();
406 assert!(b2.need_region_provider);
407 assert!(b2.reserved_table_needed.is_empty());
408
409 let p_res = table_scan(Some(reserved), &schema, None)
411 .unwrap()
412 .build()
413 .unwrap();
414 let p_norm = table_scan(Some("normal_table"), &schema, None)
415 .unwrap()
416 .build()
417 .unwrap();
418 let plan3 = LogicalPlanBuilder::from(p_res)
419 .union(LogicalPlanBuilder::from(p_norm).build().unwrap())
420 .unwrap()
421 .build()
422 .unwrap();
423 let b3 = NameAwareDataSourceInjectorBuilder::from_plan(&plan3).unwrap();
424 assert!(b3.need_region_provider);
425 assert_eq!(
426 b3.reserved_table_needed,
427 vec![InternalTableKind::InspectSstManifest]
428 );
429
430 let region_info = RegionInfoEntry::reserved_table_name_for_inspection();
431 let plan4 = table_scan(Some(region_info), &schema, None)
432 .unwrap()
433 .build()
434 .unwrap();
435 let b4 = NameAwareDataSourceInjectorBuilder::from_plan(&plan4).unwrap();
436 assert!(!b4.need_region_provider);
437 assert_eq!(
438 b4.reserved_table_needed,
439 vec![InternalTableKind::InspectRegionInfo]
440 );
441 }
442
443 #[test]
444 fn test_rewriter_replaces_with_reserved_source() {
445 let schema = test_schema();
446 let table_name = ManifestSstEntry::reserved_table_name_for_inspection();
447 let plan = table_scan(Some(table_name), &schema, None)
448 .unwrap()
449 .build()
450 .unwrap();
451
452 let provider = empty_mem_table();
453 let source = provider_as_source(provider);
454
455 let mut injector = NameAwareDataSourceInjector {
456 reserved_sources: {
457 let mut m = HashMap::new();
458 m.insert(InternalTableKind::InspectSstManifest, source.clone());
459 m
460 },
461 region_source: None,
462 };
463
464 let transformed = plan.rewrite(&mut injector).unwrap();
465 let new_plan = transformed.data;
466
467 if let LogicalPlan::TableScan(scan) = new_plan {
468 let src_ptr = Arc::as_ptr(&scan.source);
470 let want_ptr = Arc::as_ptr(&source);
471 assert!(std::ptr::eq(src_ptr, want_ptr));
472 } else {
473 panic!("expected TableScan after rewrite");
474 }
475 }
476
477 #[test]
478 fn test_rewriter_replaces_with_region_info_reserved_source() {
479 let schema = test_schema();
480 let table_name = RegionInfoEntry::reserved_table_name_for_inspection();
481 let plan = table_scan(Some(table_name), &schema, None)
482 .unwrap()
483 .build()
484 .unwrap();
485
486 let provider = empty_mem_table();
487 let source = provider_as_source(provider);
488
489 let mut injector = NameAwareDataSourceInjector {
490 reserved_sources: {
491 let mut m = HashMap::new();
492 m.insert(InternalTableKind::InspectRegionInfo, source.clone());
493 m
494 },
495 region_source: None,
496 };
497
498 let transformed = plan.rewrite(&mut injector).unwrap();
499 let new_plan = transformed.data;
500
501 if let LogicalPlan::TableScan(scan) = new_plan {
502 let src_ptr = Arc::as_ptr(&scan.source);
503 let want_ptr = Arc::as_ptr(&source);
504 assert!(std::ptr::eq(src_ptr, want_ptr));
505 } else {
506 panic!("expected TableScan after rewrite");
507 }
508 }
509
510 #[test]
511 fn test_rewriter_replaces_with_region_source_for_normal() {
512 let schema = test_schema();
513 let plan = table_scan(Some("normal_table"), &schema, None)
514 .unwrap()
515 .build()
516 .unwrap();
517
518 let provider = empty_mem_table();
519 let region_source = provider_as_source(provider);
520
521 let mut injector = NameAwareDataSourceInjector {
522 reserved_sources: HashMap::new(),
523 region_source: Some(region_source.clone()),
524 };
525
526 let transformed = plan.rewrite(&mut injector).unwrap();
527 let new_plan = transformed.data;
528
529 if let LogicalPlan::TableScan(scan) = new_plan {
530 let src_ptr = Arc::as_ptr(&scan.source);
531 let want_ptr = Arc::as_ptr(®ion_source);
532 assert!(std::ptr::eq(src_ptr, want_ptr));
533 } else {
534 panic!("expected TableScan after rewrite");
535 }
536 }
537}