Skip to main content

catalog/system_schema/
information_schema.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod cluster_info;
16pub mod columns;
17pub mod flow_statistics;
18pub mod flows;
19mod information_memory_table;
20pub mod key_column_usage;
21mod partitions;
22mod procedure_info;
23pub mod process_list;
24#[cfg(feature = "enterprise")]
25mod recycle_bin;
26mod region_info;
27pub mod region_peers;
28mod region_statistics;
29pub mod schemata;
30mod ssts;
31pub mod statistics;
32mod table_constraints;
33mod table_names;
34mod table_semantics;
35pub mod tables;
36mod views;
37
38#[cfg(all(test, feature = "enterprise"))]
39mod recycle_bin_test;
40
41use std::collections::{HashMap, HashSet};
42use std::sync::{Arc, Weak};
43
44use common_catalog::consts::{self, DEFAULT_CATALOG_NAME, INFORMATION_SCHEMA_NAME};
45use common_error::ext::ErrorExt;
46use common_meta::cluster::NodeInfo;
47use common_meta::datanode::RegionStat;
48use common_meta::key::flow::FlowMetadataManager;
49use common_meta::key::flow::flow_state::FlowStat;
50use common_meta::kv_backend::KvBackendRef;
51use common_procedure::ProcedureInfo;
52use common_recordbatch::SendableRecordBatchStream;
53use datafusion::error::DataFusionError;
54use datafusion::logical_expr::LogicalPlan;
55use datafusion::physical_plan::ExecutionPlan;
56use datatypes::schema::SchemaRef;
57use lazy_static::lazy_static;
58use paste::paste;
59use process_list::InformationSchemaProcessList;
60use region_info::InformationSchemaRegionInfo;
61use store_api::metric_engine_consts::{
62    MEMTABLE_PARTITION_TREE_PRIMARY_KEY_ENCODING, PRIMARY_KEY_ENCODING,
63};
64use store_api::region_info::RegionInfoEntry;
65use store_api::sst_entry::{ManifestSstEntry, PuffinIndexMetaEntry, StorageSstEntry};
66use store_api::storage::ScanRequest;
67use table::TableRef;
68use table::requests::{SEMANTIC_TABLE_SCOPE, TableOptions};
69pub use table_names::*;
70use views::InformationSchemaViews;
71
72use self::columns::InformationSchemaColumns;
73use crate::CatalogManager;
74use crate::error::Error;
75use crate::process_manager::ProcessManagerRef;
76use crate::system_schema::information_schema::cluster_info::InformationSchemaClusterInfo;
77use crate::system_schema::information_schema::flow_statistics::InformationSchemaFlowStatistics;
78use crate::system_schema::information_schema::flows::InformationSchemaFlows;
79use crate::system_schema::information_schema::information_memory_table::get_schema_columns;
80use crate::system_schema::information_schema::key_column_usage::InformationSchemaKeyColumnUsage;
81use crate::system_schema::information_schema::partitions::InformationSchemaPartitions;
82#[cfg(feature = "enterprise")]
83use crate::system_schema::information_schema::recycle_bin::InformationSchemaRecycleBin;
84use crate::system_schema::information_schema::region_peers::InformationSchemaRegionPeers;
85use crate::system_schema::information_schema::schemata::InformationSchemaSchemata;
86use crate::system_schema::information_schema::ssts::{
87    InformationSchemaSstsIndexMeta, InformationSchemaSstsManifest, InformationSchemaSstsStorage,
88};
89use crate::system_schema::information_schema::statistics::InformationSchemaStatistics;
90use crate::system_schema::information_schema::table_constraints::InformationSchemaTableConstraints;
91use crate::system_schema::information_schema::table_semantics::InformationSchemaTableSemantics;
92use crate::system_schema::information_schema::tables::InformationSchemaTables;
93use crate::system_schema::memory_table::MemoryTable;
94pub(crate) use crate::system_schema::predicate::Predicates;
95use crate::system_schema::{SystemSchemaProvider, SystemTable, build_system_table};
96
97const DENSE_PRIMARY_KEY_ENCODING: &str = "dense";
98const SPARSE_PRIMARY_KEY_ENCODING: &str = "sparse";
99
100pub(crate) fn primary_key_encoding_index_type(options: &HashMap<String, String>) -> &'static str {
101    options
102        .get(PRIMARY_KEY_ENCODING)
103        .or_else(|| options.get(MEMTABLE_PARTITION_TREE_PRIMARY_KEY_ENCODING))
104        .map(|value| {
105            if value.eq_ignore_ascii_case(SPARSE_PRIMARY_KEY_ENCODING) {
106                SPARSE_PRIMARY_KEY_ENCODING
107            } else {
108                DENSE_PRIMARY_KEY_ENCODING
109            }
110        })
111        .unwrap_or(DENSE_PRIMARY_KEY_ENCODING)
112}
113
114lazy_static! {
115    // Memory tables in `information_schema`.
116    static ref MEMORY_TABLES: &'static [&'static str] = &[
117        ENGINES,
118        COLUMN_PRIVILEGES,
119        COLUMN_STATISTICS,
120        CHARACTER_SETS,
121        COLLATIONS,
122        COLLATION_CHARACTER_SET_APPLICABILITY,
123        CHECK_CONSTRAINTS,
124        EVENTS,
125        FILES,
126        OPTIMIZER_TRACE,
127        PARAMETERS,
128        PROFILING,
129        REFERENTIAL_CONSTRAINTS,
130        ROUTINES,
131        SCHEMA_PRIVILEGES,
132        TABLE_PRIVILEGES,
133        GLOBAL_STATUS,
134        SESSION_STATUS,
135        PLUGINS,
136        USER_PRIVILEGES,
137        PROCESSLIST,
138    ];
139}
140
141macro_rules! setup_memory_table {
142    ($name: expr) => {
143        paste! {
144            {
145                let (schema, columns) = get_schema_columns($name);
146                Some(Arc::new(MemoryTable::new(
147                    consts::[<INFORMATION_SCHEMA_ $name  _TABLE_ID>],
148                    $name,
149                    schema,
150                    columns
151                )) as _)
152            }
153        }
154    };
155}
156
157pub struct MakeInformationTableRequest {
158    pub catalog_name: String,
159    pub catalog_manager: Weak<dyn CatalogManager>,
160    pub kv_backend: KvBackendRef,
161}
162
163/// The visibility guaranteed by an information-schema table provider.
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub enum InformationSchemaTableScope {
166    /// Every query is filtered to the catalog supplied when constructing the provider.
167    Catalog,
168    /// The table contains catalog-independent or shared data, including compatibility stubs.
169    Global,
170}
171
172impl InformationSchemaTableScope {
173    pub const fn as_str(self) -> &'static str {
174        match self {
175            Self::Catalog => "catalog",
176            Self::Global => "global",
177        }
178    }
179}
180
181/// A factory trait for making information schema tables.
182///
183/// This trait allows for extensibility of the information schema by providing
184/// a way to dynamically create custom information schema tables.
185pub trait InformationSchemaTableFactory {
186    /// Creates a table whose scope is declared by its [`InformationTable`] implementation.
187    fn make_information_table(&self, req: MakeInformationTableRequest) -> InformationTableRef;
188}
189
190pub type InformationSchemaTableFactoryRef = Arc<dyn InformationSchemaTableFactory + Send + Sync>;
191
192/// The `information_schema` tables info provider.
193pub struct InformationSchemaProvider {
194    catalog_name: String,
195    catalog_manager: Weak<dyn CatalogManager>,
196    process_manager: Option<ProcessManagerRef>,
197    flow_metadata_manager: Arc<FlowMetadataManager>,
198    tables: HashMap<String, TableRef>,
199    kv_backend: KvBackendRef,
200    extra_table_factories: HashMap<String, InformationSchemaTableFactoryRef>,
201}
202
203impl SystemSchemaProvider for InformationSchemaProvider {
204    fn tables(&self) -> &HashMap<String, TableRef> {
205        assert!(!self.tables.is_empty());
206
207        &self.tables
208    }
209}
210
211impl InformationSchemaProvider {
212    fn information_table(&self, name: &str) -> Option<InformationTableRef> {
213        if let Some(factory) = self.extra_table_factories.get(name) {
214            let req = MakeInformationTableRequest {
215                catalog_name: self.catalog_name.clone(),
216                catalog_manager: self.catalog_manager.clone(),
217                kv_backend: self.kv_backend.clone(),
218            };
219            return Some(factory.make_information_table(req));
220        }
221
222        match name.to_ascii_lowercase().as_str() {
223            TABLES => Some(Arc::new(InformationSchemaTables::new(
224                self.catalog_name.clone(),
225                self.catalog_manager.clone(),
226            )) as _),
227            COLUMNS => Some(Arc::new(InformationSchemaColumns::new(
228                self.catalog_name.clone(),
229                self.catalog_manager.clone(),
230            )) as _),
231            ENGINES => setup_memory_table!(ENGINES),
232            COLUMN_PRIVILEGES => setup_memory_table!(COLUMN_PRIVILEGES),
233            COLUMN_STATISTICS => setup_memory_table!(COLUMN_STATISTICS),
234            BUILD_INFO => setup_memory_table!(BUILD_INFO),
235            CHARACTER_SETS => setup_memory_table!(CHARACTER_SETS),
236            COLLATIONS => setup_memory_table!(COLLATIONS),
237            COLLATION_CHARACTER_SET_APPLICABILITY => {
238                setup_memory_table!(COLLATION_CHARACTER_SET_APPLICABILITY)
239            }
240            CHECK_CONSTRAINTS => setup_memory_table!(CHECK_CONSTRAINTS),
241            EVENTS => setup_memory_table!(EVENTS),
242            FILES => setup_memory_table!(FILES),
243            OPTIMIZER_TRACE => setup_memory_table!(OPTIMIZER_TRACE),
244            PARAMETERS => setup_memory_table!(PARAMETERS),
245            PROFILING => setup_memory_table!(PROFILING),
246            REFERENTIAL_CONSTRAINTS => setup_memory_table!(REFERENTIAL_CONSTRAINTS),
247            ROUTINES => setup_memory_table!(ROUTINES),
248            SCHEMA_PRIVILEGES => setup_memory_table!(SCHEMA_PRIVILEGES),
249            TABLE_PRIVILEGES => setup_memory_table!(TABLE_PRIVILEGES),
250            GLOBAL_STATUS => setup_memory_table!(GLOBAL_STATUS),
251            SESSION_STATUS => setup_memory_table!(SESSION_STATUS),
252            PLUGINS => setup_memory_table!(PLUGINS),
253            USER_PRIVILEGES => setup_memory_table!(USER_PRIVILEGES),
254            PROCESSLIST => setup_memory_table!(PROCESSLIST),
255            KEY_COLUMN_USAGE => Some(Arc::new(InformationSchemaKeyColumnUsage::new(
256                self.catalog_name.clone(),
257                self.catalog_manager.clone(),
258            )) as _),
259            SCHEMATA => Some(Arc::new(InformationSchemaSchemata::new(
260                self.catalog_name.clone(),
261                self.catalog_manager.clone(),
262            )) as _),
263            PARTITIONS => Some(Arc::new(InformationSchemaPartitions::new(
264                self.catalog_name.clone(),
265                self.catalog_manager.clone(),
266            )) as _),
267            REGION_PEERS => Some(Arc::new(InformationSchemaRegionPeers::new(
268                self.catalog_name.clone(),
269                self.catalog_manager.clone(),
270            )) as _),
271            TABLE_CONSTRAINTS => Some(Arc::new(InformationSchemaTableConstraints::new(
272                self.catalog_name.clone(),
273                self.catalog_manager.clone(),
274            )) as _),
275            STATISTICS => Some(Arc::new(InformationSchemaStatistics::new(
276                self.catalog_name.clone(),
277                self.catalog_manager.clone(),
278            )) as _),
279            CLUSTER_INFO => Some(Arc::new(InformationSchemaClusterInfo::new(
280                self.catalog_manager.clone(),
281            )) as _),
282            VIEWS => Some(Arc::new(InformationSchemaViews::new(
283                self.catalog_name.clone(),
284                self.catalog_manager.clone(),
285            )) as _),
286            FLOWS => Some(Arc::new(InformationSchemaFlows::new(
287                self.catalog_name.clone(),
288                self.catalog_manager.clone(),
289                self.flow_metadata_manager.clone(),
290            )) as _),
291            FLOW_STATISTICS => Some(Arc::new(InformationSchemaFlowStatistics::new(
292                self.catalog_name.clone(),
293                self.catalog_manager.clone(),
294                self.flow_metadata_manager.clone(),
295            )) as _),
296            PROCEDURE_INFO => Some(
297                Arc::new(procedure_info::InformationSchemaProcedureInfo::new(
298                    self.catalog_manager.clone(),
299                )) as _,
300            ),
301            #[cfg(feature = "enterprise")]
302            RECYCLE_BIN => Some(Arc::new(InformationSchemaRecycleBin::new(
303                self.catalog_name.clone(),
304                self.catalog_manager.clone(),
305            )) as _),
306            REGION_STATISTICS => Some(Arc::new(
307                region_statistics::InformationSchemaRegionStatistics::new(
308                    self.catalog_manager.clone(),
309                ),
310            ) as _),
311            REGION_INFO => Some(Arc::new(InformationSchemaRegionInfo::new(
312                self.catalog_manager.clone(),
313            )) as _),
314            PROCESS_LIST => self
315                .process_manager
316                .as_ref()
317                .map(|p| Arc::new(InformationSchemaProcessList::new(p.clone())) as _),
318            SSTS_MANIFEST => Some(Arc::new(InformationSchemaSstsManifest::new(
319                self.catalog_manager.clone(),
320            )) as _),
321            SSTS_STORAGE => Some(Arc::new(InformationSchemaSstsStorage::new(
322                self.catalog_manager.clone(),
323            )) as _),
324            SSTS_INDEX_META => Some(Arc::new(InformationSchemaSstsIndexMeta::new(
325                self.catalog_manager.clone(),
326            )) as _),
327            TABLE_SEMANTICS => Some(Arc::new(InformationSchemaTableSemantics::new(
328                self.catalog_name.clone(),
329                self.catalog_manager.clone(),
330            )) as _),
331            _ => None,
332        }
333    }
334}
335
336impl InformationSchemaProvider {
337    pub fn new(
338        catalog_name: String,
339        catalog_manager: Weak<dyn CatalogManager>,
340        flow_metadata_manager: Arc<FlowMetadataManager>,
341        process_manager: Option<ProcessManagerRef>,
342        kv_backend: KvBackendRef,
343    ) -> Self {
344        let mut provider = Self {
345            catalog_name,
346            catalog_manager,
347            flow_metadata_manager,
348            process_manager,
349            tables: HashMap::new(),
350            kv_backend,
351            extra_table_factories: HashMap::new(),
352        };
353
354        provider.build_tables();
355
356        provider
357    }
358
359    pub(crate) fn with_extra_table_factories(
360        mut self,
361        factories: HashMap<String, InformationSchemaTableFactoryRef>,
362    ) -> Self {
363        self.extra_table_factories = factories;
364        self.build_tables();
365        self
366    }
367
368    fn build_tables(&mut self) {
369        let mut table_names = HashSet::new();
370
371        // SECURITY NOTE:
372        // Carefully consider the tables that may expose sensitive cluster configurations,
373        // authentication details, and other critical information.
374        // Only put these tables under `greptime` catalog to prevent info leak.
375        if self.catalog_name == DEFAULT_CATALOG_NAME {
376            table_names.extend([
377                BUILD_INFO,
378                REGION_PEERS,
379                CLUSTER_INFO,
380                PROCEDURE_INFO,
381                REGION_STATISTICS,
382                REGION_INFO,
383                SSTS_MANIFEST,
384                SSTS_STORAGE,
385                SSTS_INDEX_META,
386            ]);
387        }
388
389        table_names.extend([
390            TABLES,
391            VIEWS,
392            SCHEMATA,
393            COLUMNS,
394            KEY_COLUMN_USAGE,
395            TABLE_CONSTRAINTS,
396            STATISTICS,
397            FLOWS,
398            FLOW_STATISTICS,
399            #[cfg(feature = "enterprise")]
400            RECYCLE_BIN,
401            TABLE_SEMANTICS,
402            PARTITIONS,
403        ]);
404        if self.process_manager.is_some() {
405            table_names.insert(PROCESS_LIST);
406        }
407        table_names.extend(MEMORY_TABLES.iter().copied());
408        table_names.extend(self.extra_table_factories.keys().map(String::as_str));
409        self.tables = table_names
410            .into_iter()
411            .map(|name| {
412                let table = self.information_table(name).expect(name);
413                let options = TableOptions {
414                    extra_options: HashMap::from([(
415                        SEMANTIC_TABLE_SCOPE.to_string(),
416                        table.scope().as_str().to_string(),
417                    )]),
418                    ..Default::default()
419                };
420                let table = build_system_table(
421                    self.catalog_name.clone(),
422                    INFORMATION_SCHEMA_NAME,
423                    table,
424                    options,
425                );
426                (name.to_string(), table)
427            })
428            .collect();
429    }
430}
431
432/// An information-schema table with an explicit visibility guarantee.
433pub trait InformationTable: SystemTable {
434    /// Returns the visibility guaranteed by this table's query implementation.
435    fn scope(&self) -> InformationSchemaTableScope;
436}
437
438pub type InformationTableRef = Arc<dyn InformationTable + Send + Sync>;
439
440pub type InformationExtensionRef = Arc<dyn InformationExtension<Error = Error> + Send + Sync>;
441
442/// The `InformationExtension` trait provides the extension methods for the `information_schema` tables.
443#[async_trait::async_trait]
444pub trait InformationExtension {
445    type Error: ErrorExt;
446
447    /// Gets the nodes information.
448    async fn nodes(&self) -> std::result::Result<Vec<NodeInfo>, Self::Error>;
449
450    /// Gets the procedures information.
451    async fn procedures(&self) -> std::result::Result<Vec<(String, ProcedureInfo)>, Self::Error>;
452
453    /// Gets the region statistics.
454    async fn region_stats(&self) -> std::result::Result<Vec<RegionStat>, Self::Error>;
455
456    /// Get the flow statistics. If no flownode is available, return `None`.
457    async fn flow_stats(&self) -> std::result::Result<Option<FlowStat>, Self::Error>;
458
459    /// Inspects the datanode.
460    async fn inspect_datanode(
461        &self,
462        request: DatanodeInspectRequest,
463    ) -> std::result::Result<SendableRecordBatchStream, Self::Error>;
464
465    /// Builds a physical plan for datanode inspect if the extension can expose
466    /// the distributed fan-in semantics to DataFusion.
467    fn inspect_datanode_plan(
468        &self,
469        _request: DatanodeInspectRequest,
470        _schema: SchemaRef,
471    ) -> std::result::Result<Option<Arc<dyn ExecutionPlan>>, Self::Error> {
472        Ok(None)
473    }
474}
475
476/// The request to inspect the datanode.
477#[derive(Debug, Clone, PartialEq)]
478pub struct DatanodeInspectRequest {
479    /// Kind to fetch from datanode.
480    pub kind: DatanodeInspectKind,
481
482    /// Pushdown scan configuration (projection/predicate/limit) for the returned stream.
483    /// This allows server-side filtering to reduce I/O and network costs.
484    pub scan: ScanRequest,
485}
486
487/// The kind of the datanode inspect request.
488#[derive(Debug, Clone, Copy, PartialEq, Eq)]
489pub enum DatanodeInspectKind {
490    /// List SST entries recorded in manifest
491    SstManifest,
492    /// List SST entries discovered in storage layer
493    SstStorage,
494    /// List index metadata collected from manifest
495    SstIndexMeta,
496    /// List region runtime and manifest info
497    RegionInfo,
498}
499
500impl DatanodeInspectRequest {
501    /// Builds a logical plan for the datanode inspect request.
502    pub fn build_plan(self) -> std::result::Result<LogicalPlan, DataFusionError> {
503        match self.kind {
504            DatanodeInspectKind::SstManifest => ManifestSstEntry::build_plan(self.scan),
505            DatanodeInspectKind::SstStorage => StorageSstEntry::build_plan(self.scan),
506            DatanodeInspectKind::SstIndexMeta => PuffinIndexMetaEntry::build_plan(self.scan),
507            DatanodeInspectKind::RegionInfo => RegionInfoEntry::build_plan(self.scan),
508        }
509    }
510}
511pub struct NoopInformationExtension;
512
513#[async_trait::async_trait]
514impl InformationExtension for NoopInformationExtension {
515    type Error = Error;
516
517    async fn nodes(&self) -> std::result::Result<Vec<NodeInfo>, Self::Error> {
518        Ok(vec![])
519    }
520
521    async fn procedures(&self) -> std::result::Result<Vec<(String, ProcedureInfo)>, Self::Error> {
522        Ok(vec![])
523    }
524
525    async fn region_stats(&self) -> std::result::Result<Vec<RegionStat>, Self::Error> {
526        Ok(vec![])
527    }
528
529    async fn flow_stats(&self) -> std::result::Result<Option<FlowStat>, Self::Error> {
530        Ok(None)
531    }
532
533    async fn inspect_datanode(
534        &self,
535        _request: DatanodeInspectRequest,
536    ) -> std::result::Result<SendableRecordBatchStream, Self::Error> {
537        Ok(common_recordbatch::RecordBatches::empty().as_stream())
538    }
539}
540
541#[cfg(test)]
542mod tests {
543    use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
544    use common_meta::cache::{CacheRegistryBuilder, LayeredCacheRegistryBuilder};
545    use common_meta::ddl::test_util::create_table::test_create_table_task;
546    use common_meta::key::schema_name::SchemaNameKey;
547    use common_meta::key::table_route::TableRouteValue;
548    use common_meta::kv_backend::memory::MemoryKvBackend;
549    use common_meta::kv_backend::txn::TxnService;
550    use common_meta::wal_provider::RegionWalOptions;
551    use datatypes::schema::Schema;
552    use futures_util::TryStreamExt;
553    use store_api::region_info::RegionInfoEntry;
554    use store_api::storage::TableId;
555
556    use super::*;
557    use crate::error::Result;
558    use crate::information_schema::NoopInformationExtension;
559    use crate::kvbackend::KvBackendCatalogManagerBuilder;
560    use crate::memory::MemoryCatalogManager;
561    use crate::process_manager::ProcessManager;
562
563    struct ScopedFactory {
564        name: &'static str,
565        scope: InformationSchemaTableScope,
566    }
567
568    impl InformationSchemaTableFactory for ScopedFactory {
569        fn make_information_table(&self, _req: MakeInformationTableRequest) -> InformationTableRef {
570            Arc::new(ScopedTable {
571                name: self.name,
572                scope: self.scope,
573            })
574        }
575    }
576
577    struct ScopedTable {
578        name: &'static str,
579        scope: InformationSchemaTableScope,
580    }
581
582    impl InformationTable for ScopedTable {
583        fn scope(&self) -> InformationSchemaTableScope {
584            self.scope
585        }
586    }
587
588    impl SystemTable for ScopedTable {
589        fn table_id(&self) -> TableId {
590            999
591        }
592
593        fn table_name(&self) -> &'static str {
594            self.name
595        }
596
597        fn schema(&self) -> SchemaRef {
598            Arc::new(Schema::new(vec![]))
599        }
600
601        fn to_stream(&self, _request: ScanRequest) -> Result<SendableRecordBatchStream> {
602            Ok(common_recordbatch::RecordBatches::empty().as_stream())
603        }
604    }
605
606    #[test]
607    fn every_registered_information_schema_table_has_scope() {
608        use InformationSchemaTableScope::{Catalog, Global};
609
610        let manager: Arc<dyn CatalogManager> = MemoryCatalogManager::new();
611        let backend = Arc::new(MemoryKvBackend::default());
612        let factories: HashMap<_, InformationSchemaTableFactoryRef> = [
613            ("future_catalog_table", Catalog),
614            ("future_global_table", Global),
615            (ENGINES, Catalog),
616        ]
617        .into_iter()
618        .map(|(name, scope)| {
619            (
620                name.to_string(),
621                Arc::new(ScopedFactory { name, scope }) as _,
622            )
623        })
624        .collect();
625        let catalog_tables = [
626            TABLES,
627            VIEWS,
628            SCHEMATA,
629            COLUMNS,
630            KEY_COLUMN_USAGE,
631            TABLE_CONSTRAINTS,
632            STATISTICS,
633            FLOWS,
634            FLOW_STATISTICS,
635            TABLE_SEMANTICS,
636            PARTITIONS,
637            REGION_PEERS,
638            #[cfg(feature = "enterprise")]
639            RECYCLE_BIN,
640            "future_catalog_table",
641            ENGINES,
642        ];
643
644        for (catalog, with_process_manager) in [
645            (DEFAULT_CATALOG_NAME, true),
646            (DEFAULT_CATALOG_NAME, false),
647            ("tenant", true),
648            ("tenant", false),
649        ] {
650            let provider = InformationSchemaProvider::new(
651                catalog.to_string(),
652                Arc::downgrade(&manager),
653                Arc::new(FlowMetadataManager::new(backend.clone())),
654                with_process_manager.then(|| Arc::new(ProcessManager::new(String::new(), None))),
655                backend.clone(),
656            );
657            assert_eq!(
658                provider
659                    .table(ENGINES)
660                    .unwrap()
661                    .table_info()
662                    .meta
663                    .options
664                    .extra_options
665                    .get(SEMANTIC_TABLE_SCOPE)
666                    .map(String::as_str),
667                Some(Global.as_str()),
668            );
669            // An extension replacing a built-in supplies the replacement table's scope.
670            let provider = provider.with_extra_table_factories(factories.clone());
671            let expected_count =
672                43 + usize::from(with_process_manager) + usize::from(cfg!(feature = "enterprise"))
673                    - if catalog == DEFAULT_CATALOG_NAME {
674                        0
675                    } else {
676                        9
677                    };
678            assert_eq!(provider.tables().len(), expected_count);
679            for (name, table) in provider.tables() {
680                let scope = if catalog_tables.contains(&name.as_str()) {
681                    Catalog
682                } else {
683                    Global
684                };
685                assert_eq!(
686                    table
687                        .table_info()
688                        .meta
689                        .options
690                        .extra_options
691                        .get(SEMANTIC_TABLE_SCOPE)
692                        .map(String::as_str),
693                    Some(scope.as_str()),
694                    "{catalog}.{name}"
695                );
696            }
697            assert_eq!(
698                provider.table(REGION_PEERS).is_some(),
699                catalog == DEFAULT_CATALOG_NAME
700            );
701            assert_eq!(provider.table(PROCESS_LIST).is_some(), with_process_manager);
702            if let Some(table) = provider.table(PROCESS_LIST) {
703                assert!(table.schema().column_schema_by_name("catalog").is_some());
704            }
705        }
706    }
707
708    #[tokio::test]
709    async fn newly_accessible_information_schema_tables_filter_catalogs() {
710        let backend = Arc::new(MemoryKvBackend::default());
711        let caches = LayeredCacheRegistryBuilder::default()
712            .add_cache_registry(CacheRegistryBuilder::default().build())
713            .add_cache_registry(build_fundamental_cache_registry(backend.clone()));
714        let manager = KvBackendCatalogManagerBuilder::new(
715            Arc::new(NoopInformationExtension),
716            backend.clone(),
717            Arc::new(
718                with_default_composite_cache_registry(caches)
719                    .unwrap()
720                    .build(),
721            ),
722        )
723        .build();
724        let metadata = manager.table_metadata_manager_ref();
725        let flow_metadata = FlowMetadataManager::new(backend.clone());
726        for (catalog, name, id) in [
727            ("tenant_a", "visible_a", 1024),
728            ("tenant_b", "visible_b", 1025),
729        ] {
730            metadata
731                .schema_manager()
732                .create(SchemaNameKey::new(catalog, "public"), None, false)
733                .await
734                .unwrap();
735            let mut info = test_create_table_task(name, id).table_info;
736            info.catalog_name = catalog.to_string();
737            info.meta
738                .options
739                .extra_options
740                .insert(SEMANTIC_TABLE_SCOPE.to_string(), "catalog".to_string());
741            metadata
742                .create_table_metadata(
743                    info,
744                    TableRouteValue::physical(vec![]),
745                    RegionWalOptions::default(),
746                )
747                .await
748                .unwrap();
749            let (txn, _) = flow_metadata
750                .flow_name_manager()
751                .build_create_txn(catalog, name, id)
752                .unwrap();
753            backend.txn(txn).await.unwrap();
754        }
755
756        for (catalog, visible, hidden) in [
757            ("tenant_a", "visible_a", "visible_b"),
758            ("tenant_b", "visible_b", "visible_a"),
759        ] {
760            for name in [TABLE_SEMANTICS, STATISTICS, FLOW_STATISTICS] {
761                let table = manager
762                    .table(catalog, INFORMATION_SCHEMA_NAME, name, None)
763                    .await
764                    .unwrap()
765                    .unwrap();
766                let batches = table
767                    .scan_to_stream(ScanRequest::default())
768                    .await
769                    .unwrap()
770                    .try_collect::<Vec<_>>()
771                    .await
772                    .unwrap();
773                let output = batches
774                    .iter()
775                    .map(|batch| batch.pretty_print())
776                    .collect::<Vec<_>>()
777                    .join("\n");
778                assert!(output.contains(visible), "{catalog}.{name}: {output}");
779                assert!(!output.contains(hidden), "{catalog}.{name}: {output}");
780            }
781        }
782        assert!(
783            !manager
784                .information_schema_provider()
785                .tables()
786                .contains_key("visible_a")
787        );
788    }
789
790    #[test]
791    fn test_datanode_inspect_region_info_build_plan() {
792        let plan = DatanodeInspectRequest {
793            kind: DatanodeInspectKind::RegionInfo,
794            scan: ScanRequest::default(),
795        }
796        .build_plan()
797        .unwrap();
798
799        let LogicalPlan::TableScan(scan) = plan else {
800            panic!("expected table scan");
801        };
802        assert_eq!(
803            scan.table_name.to_string(),
804            RegionInfoEntry::reserved_table_name_for_inspection()
805        );
806    }
807}