Skip to main content

catalog/kvbackend/
manager.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
15use std::any::Any;
16use std::collections::BTreeSet;
17use std::sync::{Arc, OnceLock, Weak};
18
19use async_stream::try_stream;
20use common_catalog::consts::{
21    DEFAULT_CATALOG_NAME, DEFAULT_PRIVATE_SCHEMA_NAME, DEFAULT_SCHEMA_NAME,
22    INFORMATION_SCHEMA_NAME, PG_CATALOG_NAME,
23};
24use common_error::ext::BoxedError;
25use common_meta::cache::{
26    LayeredCacheRegistryRef, TableInfoCacheRef, TableNameCacheRef, TableRoute, TableRouteCacheRef,
27    ViewInfoCacheRef,
28};
29use common_meta::key::TableMetadataManagerRef;
30use common_meta::key::catalog_name::CatalogNameKey;
31use common_meta::key::flow::FlowMetadataManager;
32use common_meta::key::schema_name::SchemaNameKey;
33use common_meta::key::table_info::TableInfoValue;
34use common_meta::key::table_name::TableNameKey;
35use common_meta::kv_backend::KvBackendRef;
36use common_procedure::ProcedureManagerRef;
37use futures_util::stream::BoxStream;
38use futures_util::{StreamExt, TryStreamExt};
39use moka::sync::Cache;
40use partition::manager::PartitionRuleManagerRef;
41use session::context::{Channel, QueryContext};
42use snafu::prelude::*;
43use store_api::metric_engine_consts::METRIC_ENGINE_NAME;
44use table::TableRef;
45use table::dist_table::DistTable;
46use table::metadata::{TableId, TableInfoRef};
47use table::table::PartitionRules;
48use table::table_name::TableName;
49use tokio::sync::Semaphore;
50use tokio_stream::wrappers::ReceiverStream;
51
52use crate::CatalogManager;
53use crate::error::{
54    CacheNotFoundSnafu, GetTableCacheSnafu, ListCatalogsSnafu, ListSchemasSnafu, ListTablesSnafu,
55    Result, TableMetadataManagerSnafu,
56};
57use crate::information_schema::{
58    InformationExtensionRef, InformationSchemaProvider, InformationSchemaTableFactoryRef,
59};
60use crate::kvbackend::TableCacheRef;
61use crate::process_manager::ProcessManagerRef;
62use crate::system_schema::SystemSchemaProvider;
63use crate::system_schema::numbers_table_provider::NumbersTableProvider;
64use crate::system_schema::pg_catalog::PGCatalogProvider;
65use crate::system_schema::semantic_graph::{EntityGraphProviderRef, SemanticGraphTableProvider};
66
67/// Access all existing catalog, schema and tables.
68///
69/// The result comes from two source, all the user tables are presented in
70/// a kv-backend which persists the metadata of a table. And system tables
71/// comes from `SystemCatalog`, which is static and read-only.
72#[derive(Clone)]
73pub struct KvBackendCatalogManager {
74    /// Provides the extension methods for the `information_schema` tables
75    pub(super) information_extension: InformationExtensionRef,
76    /// Backs the computed entity-graph tables (`semantic_entities` /
77    /// `semantic_relationships`). Set once, after the query engine is built, to
78    /// break the `catalog -> query` cycle; `None` until then. `Arc` so the cell
79    /// stays shared across `Clone`s of the manager (a plain `OnceLock` would
80    /// fork on clone).
81    pub(super) entity_graph_provider: Arc<OnceLock<EntityGraphProviderRef>>,
82    /// Manages partition rules.
83    pub(super) partition_manager: PartitionRuleManagerRef,
84    /// Manages table metadata.
85    pub(super) table_metadata_manager: TableMetadataManagerRef,
86    /// A sub-CatalogManager that handles system tables
87    pub(super) system_catalog: SystemCatalog,
88    /// Cache registry for all caches.
89    pub(super) cache_registry: LayeredCacheRegistryRef,
90    /// Only available in `Standalone` mode.
91    pub(super) procedure_manager: Option<ProcedureManagerRef>,
92}
93
94pub(super) const CATALOG_CACHE_MAX_CAPACITY: u64 = 128;
95
96impl KvBackendCatalogManager {
97    /// Returns the registered system tables, excluding user-created metadata.
98    pub fn information_schema_provider(&self) -> &InformationSchemaProvider {
99        &self.system_catalog.information_schema_provider
100    }
101
102    pub fn view_info_cache(&self) -> Result<ViewInfoCacheRef> {
103        self.cache_registry.get().context(CacheNotFoundSnafu {
104            name: "view_info_cache",
105        })
106    }
107
108    /// Returns the [`InformationExtension`].
109    pub fn information_extension(&self) -> InformationExtensionRef {
110        self.information_extension.clone()
111    }
112
113    /// Returns the entity-graph provider, or `None` if it has not been injected
114    /// yet (before the query engine is built). The computed graph tables stream
115    /// empty until it is set.
116    pub fn entity_graph_provider(&self) -> Option<EntityGraphProviderRef> {
117        self.entity_graph_provider.get().cloned()
118    }
119
120    /// Injects the entity-graph provider once the query engine exists. A second
121    /// call is a no-op (the first binding wins).
122    pub fn set_entity_graph_provider(&self, provider: EntityGraphProviderRef) {
123        let _ = self.entity_graph_provider.set(provider);
124    }
125
126    pub fn partition_manager(&self) -> PartitionRuleManagerRef {
127        self.partition_manager.clone()
128    }
129
130    pub fn table_metadata_manager_ref(&self) -> &TableMetadataManagerRef {
131        &self.table_metadata_manager
132    }
133
134    pub fn procedure_manager(&self) -> Option<ProcedureManagerRef> {
135        self.procedure_manager.clone()
136    }
137
138    // Override logical table's partition key indices with physical table's.
139    async fn override_logical_table_partition_key_indices(
140        table_route_cache: &TableRouteCacheRef,
141        table_info_cache: &TableInfoCacheRef,
142        table: TableRef,
143    ) -> Result<TableRef> {
144        // If the table is not a metric table, return the table directly.
145        if table.table_info().meta.engine != METRIC_ENGINE_NAME {
146            return Ok(table);
147        }
148
149        if let Some(table_route_value) = table_route_cache
150            .get(table.table_info().table_id())
151            .await
152            .context(TableMetadataManagerSnafu)?
153            && let TableRoute::Logical(logical_route) = &*table_route_value
154            && let Some(physical_table_info) = table_info_cache
155                .get(logical_route.physical_table_id())
156                .await
157                .context(TableMetadataManagerSnafu)?
158        {
159            let mut new_table_info = (*table.table_info()).clone();
160
161            let mut phy_part_cols_not_in_logical_table = vec![];
162
163            // Remap partition key indices from physical table to logical table
164            new_table_info.meta.partition_key_indices = physical_table_info
165                .meta
166                .partition_key_indices
167                .iter()
168                .filter_map(|&physical_index| {
169                    // Get the column name from the physical table using the physical index
170                    physical_table_info
171                        .meta
172                        .schema
173                        .column_schemas()
174                        .get(physical_index)
175                        .and_then(|physical_column| {
176                            // Find the corresponding index in the logical table schema
177                            let idx = new_table_info
178                                .meta
179                                .schema
180                                .column_index_by_name(physical_column.name.as_str());
181                            if idx.is_none() {
182                                // not all part columns in physical table that are also in logical table
183                                phy_part_cols_not_in_logical_table
184                                    .push(physical_column.name.clone());
185                            }
186
187                            idx
188                        })
189                })
190                .collect();
191
192            let partition_rules = if !phy_part_cols_not_in_logical_table.is_empty() {
193                Some(PartitionRules {
194                    extra_phy_cols_not_in_logical_table: phy_part_cols_not_in_logical_table,
195                })
196            } else {
197                None
198            };
199
200            let new_table = DistTable::table_partitioned(Arc::new(new_table_info), partition_rules);
201
202            return Ok(new_table);
203        }
204
205        Ok(table)
206    }
207}
208
209#[async_trait::async_trait]
210impl CatalogManager for KvBackendCatalogManager {
211    fn as_any(&self) -> &dyn Any {
212        self
213    }
214
215    async fn catalog_names(&self) -> Result<Vec<String>> {
216        let stream = self
217            .table_metadata_manager
218            .catalog_manager()
219            .catalog_names();
220
221        let keys = stream
222            .try_collect::<Vec<_>>()
223            .await
224            .map_err(BoxedError::new)
225            .context(ListCatalogsSnafu)?;
226
227        Ok(keys)
228    }
229
230    async fn schema_names(
231        &self,
232        catalog: &str,
233        query_ctx: Option<&QueryContext>,
234    ) -> Result<Vec<String>> {
235        let stream = self
236            .table_metadata_manager
237            .schema_manager()
238            .schema_names(catalog);
239        let mut keys = stream
240            .try_collect::<BTreeSet<_>>()
241            .await
242            .map_err(BoxedError::new)
243            .context(ListSchemasSnafu { catalog })?;
244
245        keys.extend(self.system_catalog.schema_names(query_ctx));
246
247        Ok(keys.into_iter().collect())
248    }
249
250    async fn table_names(
251        &self,
252        catalog: &str,
253        schema: &str,
254        query_ctx: Option<&QueryContext>,
255    ) -> Result<Vec<String>> {
256        let mut tables = self
257            .table_metadata_manager
258            .table_name_manager()
259            .tables(catalog, schema)
260            .map_ok(|(table_name, _)| table_name)
261            .try_collect::<Vec<_>>()
262            .await
263            .map_err(BoxedError::new)
264            .context(ListTablesSnafu { catalog, schema })?;
265
266        tables.extend(self.system_catalog.table_names(schema, query_ctx));
267        Ok(tables)
268    }
269
270    async fn catalog_exists(&self, catalog: &str) -> Result<bool> {
271        self.table_metadata_manager
272            .catalog_manager()
273            .exists(CatalogNameKey::new(catalog))
274            .await
275            .context(TableMetadataManagerSnafu)
276    }
277
278    async fn schema_exists(
279        &self,
280        catalog: &str,
281        schema: &str,
282        query_ctx: Option<&QueryContext>,
283    ) -> Result<bool> {
284        if self.system_catalog.schema_exists(schema, query_ctx) {
285            return Ok(true);
286        }
287
288        self.table_metadata_manager
289            .schema_manager()
290            .exists(SchemaNameKey::new(catalog, schema))
291            .await
292            .context(TableMetadataManagerSnafu)
293    }
294
295    async fn table_exists(
296        &self,
297        catalog: &str,
298        schema: &str,
299        table: &str,
300        query_ctx: Option<&QueryContext>,
301    ) -> Result<bool> {
302        if self.system_catalog.table_exists(schema, table, query_ctx) {
303            return Ok(true);
304        }
305
306        let key = TableNameKey::new(catalog, schema, table);
307        self.table_metadata_manager
308            .table_name_manager()
309            .get(key)
310            .await
311            .context(TableMetadataManagerSnafu)
312            .map(|x| x.is_some())
313    }
314
315    async fn table(
316        &self,
317        catalog_name: &str,
318        schema_name: &str,
319        table_name: &str,
320        query_ctx: Option<&QueryContext>,
321    ) -> Result<Option<TableRef>> {
322        let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
323        if let Some(table) =
324            self.system_catalog
325                .table(catalog_name, schema_name, table_name, query_ctx)
326        {
327            return Ok(Some(table));
328        }
329
330        let table_cache: TableCacheRef = self.cache_registry.get().context(CacheNotFoundSnafu {
331            name: "table_cache",
332        })?;
333
334        let table = table_cache
335            .get_by_ref(&TableName {
336                catalog_name: catalog_name.to_string(),
337                schema_name: schema_name.to_string(),
338                table_name: table_name.to_string(),
339            })
340            .await
341            .context(GetTableCacheSnafu)?;
342
343        if let Some(table) = table {
344            let table_route_cache: TableRouteCacheRef =
345                self.cache_registry.get().context(CacheNotFoundSnafu {
346                    name: "table_route_cache",
347                })?;
348            let table_info_cache: TableInfoCacheRef =
349                self.cache_registry.get().context(CacheNotFoundSnafu {
350                    name: "table_info_cache",
351                })?;
352            return Self::override_logical_table_partition_key_indices(
353                &table_route_cache,
354                &table_info_cache,
355                table,
356            )
357            .await
358            .map(Some);
359        }
360
361        if channel == Channel::Postgres {
362            // falldown to pg_catalog
363            if let Some(table) =
364                self.system_catalog
365                    .table(catalog_name, PG_CATALOG_NAME, table_name, query_ctx)
366            {
367                return Ok(Some(table));
368            }
369        }
370
371        Ok(None)
372    }
373
374    async fn table_id(
375        &self,
376        catalog_name: &str,
377        schema_name: &str,
378        table_name: &str,
379        query_ctx: Option<&QueryContext>,
380    ) -> Result<Option<TableId>> {
381        let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
382        if let Some(table) =
383            self.system_catalog
384                .table(catalog_name, schema_name, table_name, query_ctx)
385        {
386            return Ok(Some(table.table_info().table_id()));
387        }
388
389        let table_cache: TableNameCacheRef =
390            self.cache_registry.get().context(CacheNotFoundSnafu {
391                name: "table_name_cache",
392            })?;
393
394        let table = table_cache
395            .get_by_ref(&TableName {
396                catalog_name: catalog_name.to_string(),
397                schema_name: schema_name.to_string(),
398                table_name: table_name.to_string(),
399            })
400            .await
401            .context(GetTableCacheSnafu)?;
402
403        if let Some(table) = table {
404            return Ok(Some(table));
405        }
406
407        if channel == Channel::Postgres {
408            // falldown to pg_catalog
409            if let Some(table) =
410                self.system_catalog
411                    .table(catalog_name, PG_CATALOG_NAME, table_name, query_ctx)
412            {
413                return Ok(Some(table.table_info().table_id()));
414            }
415        }
416
417        Ok(None)
418    }
419
420    async fn table_info_by_id(&self, table_id: TableId) -> Result<Option<TableInfoRef>> {
421        let table_info_cache: TableInfoCacheRef =
422            self.cache_registry.get().context(CacheNotFoundSnafu {
423                name: "table_info_cache",
424            })?;
425        table_info_cache
426            .get_by_ref(&table_id)
427            .await
428            .context(GetTableCacheSnafu)
429    }
430
431    async fn tables_by_ids(
432        &self,
433        catalog: &str,
434        schema: &str,
435        table_ids: &[TableId],
436    ) -> Result<Vec<TableRef>> {
437        let table_info_values = self
438            .table_metadata_manager
439            .table_info_manager()
440            .batch_get(table_ids)
441            .await
442            .context(TableMetadataManagerSnafu)?;
443
444        let tables = table_info_values
445            .into_values()
446            .filter(|t| t.table_info.catalog_name == catalog && t.table_info.schema_name == schema)
447            .map(build_table)
448            .collect::<Vec<_>>();
449
450        Ok(tables)
451    }
452
453    fn tables<'a>(
454        &'a self,
455        catalog: &'a str,
456        schema: &'a str,
457        query_ctx: Option<&'a QueryContext>,
458    ) -> BoxStream<'a, Result<TableRef>> {
459        let sys_tables = try_stream!({
460            // System tables
461            let sys_table_names = self.system_catalog.table_names(schema, query_ctx);
462            for table_name in sys_table_names {
463                if let Some(table) =
464                    self.system_catalog
465                        .table(catalog, schema, &table_name, query_ctx)
466                {
467                    yield table;
468                }
469            }
470        });
471
472        const BATCH_SIZE: usize = 128;
473        const CONCURRENCY: usize = 8;
474
475        let (tx, rx) = tokio::sync::mpsc::channel(64);
476        let metadata_manager = self.table_metadata_manager.clone();
477        let catalog = catalog.to_string();
478        let schema = schema.to_string();
479        let semaphore = Arc::new(Semaphore::new(CONCURRENCY));
480        let table_route_cache: Result<TableRouteCacheRef> =
481            self.cache_registry.get().context(CacheNotFoundSnafu {
482                name: "table_route_cache",
483            });
484        let table_info_cache: Result<TableInfoCacheRef> =
485            self.cache_registry.get().context(CacheNotFoundSnafu {
486                name: "table_info_cache",
487            });
488
489        common_runtime::spawn_global(async move {
490            let table_route_cache = match table_route_cache {
491                Ok(table_route_cache) => table_route_cache,
492                Err(e) => {
493                    let _ = tx.send(Err(e)).await;
494                    return;
495                }
496            };
497            let table_info_cache = match table_info_cache {
498                Ok(table_info_cache) => table_info_cache,
499                Err(e) => {
500                    let _ = tx.send(Err(e)).await;
501                    return;
502                }
503            };
504
505            let table_id_stream = metadata_manager
506                .table_name_manager()
507                .tables(&catalog, &schema)
508                .map_ok(|(_, v)| v.table_id());
509            // Split table ids into chunks
510            let mut table_id_chunks = table_id_stream.ready_chunks(BATCH_SIZE);
511
512            while let Some(table_ids) = table_id_chunks.next().await {
513                let table_ids = match table_ids
514                    .into_iter()
515                    .collect::<std::result::Result<Vec<_>, _>>()
516                    .map_err(BoxedError::new)
517                    .context(ListTablesSnafu {
518                        catalog: &catalog,
519                        schema: &schema,
520                    }) {
521                    Ok(table_ids) => table_ids,
522                    Err(e) => {
523                        let _ = tx.send(Err(e)).await;
524                        return;
525                    }
526                };
527
528                let metadata_manager = metadata_manager.clone();
529                let tx = tx.clone();
530                let semaphore = semaphore.clone();
531                let table_route_cache = table_route_cache.clone();
532                let table_info_cache = table_info_cache.clone();
533                common_runtime::spawn_global(async move {
534                    // we don't explicitly close the semaphore so just ignore the potential error.
535                    let _ = semaphore.acquire().await;
536                    let table_info_values = match metadata_manager
537                        .table_info_manager()
538                        .batch_get(&table_ids)
539                        .await
540                        .context(TableMetadataManagerSnafu)
541                    {
542                        Ok(table_info_values) => table_info_values,
543                        Err(e) => {
544                            let _ = tx.send(Err(e)).await;
545                            return;
546                        }
547                    };
548
549                    for table in table_info_values.into_values().map(build_table) {
550                        let table = Self::override_logical_table_partition_key_indices(
551                            &table_route_cache,
552                            &table_info_cache,
553                            table,
554                        )
555                        .await;
556                        if tx.send(table).await.is_err() {
557                            return;
558                        }
559                    }
560                });
561            }
562        });
563
564        let user_tables = ReceiverStream::new(rx);
565        Box::pin(sys_tables.chain(user_tables))
566    }
567}
568
569fn build_table(table_info_value: TableInfoValue) -> TableRef {
570    let table_info = table_info_value.table_info;
571    DistTable::table(Arc::new(table_info))
572}
573
574// TODO: This struct can hold a static map of all system tables when
575// the upper layer (e.g., procedure) can inform the catalog manager
576// a new catalog is created.
577/// Existing system tables:
578/// - public.numbers
579/// - information_schema.{tables}
580/// - pg_catalog.{tables}
581#[derive(Clone)]
582pub(super) struct SystemCatalog {
583    pub(super) catalog_manager: Weak<KvBackendCatalogManager>,
584    pub(super) catalog_cache: Cache<String, Arc<InformationSchemaProvider>>,
585    pub(super) pg_catalog_cache: Cache<String, Arc<PGCatalogProvider>>,
586
587    // system_schema_provider for default catalog
588    pub(super) information_schema_provider: Arc<InformationSchemaProvider>,
589    pub(super) pg_catalog_provider: Arc<PGCatalogProvider>,
590    pub(super) numbers_table_provider: NumbersTableProvider,
591    pub(super) backend: KvBackendRef,
592    pub(super) process_manager: Option<ProcessManagerRef>,
593    pub(super) extra_information_table_factories:
594        std::collections::HashMap<String, InformationSchemaTableFactoryRef>,
595}
596
597impl SystemCatalog {
598    fn schema_names(&self, query_ctx: Option<&QueryContext>) -> Vec<String> {
599        let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
600        match channel {
601            // pg_catalog only visible under postgres protocol
602            Channel::Postgres => vec![
603                INFORMATION_SCHEMA_NAME.to_string(),
604                PG_CATALOG_NAME.to_string(),
605            ],
606            _ => {
607                vec![INFORMATION_SCHEMA_NAME.to_string()]
608            }
609        }
610    }
611
612    fn table_names(&self, schema: &str, query_ctx: Option<&QueryContext>) -> Vec<String> {
613        let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
614        match schema {
615            INFORMATION_SCHEMA_NAME => self.information_schema_provider.table_names(),
616            PG_CATALOG_NAME if channel == Channel::Postgres => {
617                self.pg_catalog_provider.table_names()
618            }
619            DEFAULT_SCHEMA_NAME => self.numbers_table_provider.table_names(),
620            // Computed entity-graph tables overlay the physical tables of
621            // `greptime_private` (the caller appends these to the physical list).
622            DEFAULT_PRIVATE_SCHEMA_NAME => SemanticGraphTableProvider::table_names(),
623            _ => vec![],
624        }
625    }
626
627    fn schema_exists(&self, schema: &str, query_ctx: Option<&QueryContext>) -> bool {
628        let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
629        match channel {
630            Channel::Postgres => schema == PG_CATALOG_NAME || schema == INFORMATION_SCHEMA_NAME,
631            _ => schema == INFORMATION_SCHEMA_NAME,
632        }
633    }
634
635    fn table_exists(&self, schema: &str, table: &str, query_ctx: Option<&QueryContext>) -> bool {
636        let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
637        if schema == INFORMATION_SCHEMA_NAME {
638            self.information_schema_provider.table(table).is_some()
639        } else if schema == DEFAULT_SCHEMA_NAME {
640            self.numbers_table_provider.table_exists(table)
641        } else if schema == PG_CATALOG_NAME && channel == Channel::Postgres {
642            self.pg_catalog_provider.table(table).is_some()
643        } else if schema == DEFAULT_PRIVATE_SCHEMA_NAME {
644            SemanticGraphTableProvider::table_exists(table)
645        } else {
646            false
647        }
648    }
649
650    fn table(
651        &self,
652        catalog: &str,
653        schema: &str,
654        table_name: &str,
655        query_ctx: Option<&QueryContext>,
656    ) -> Option<TableRef> {
657        let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
658        if schema == INFORMATION_SCHEMA_NAME {
659            let information_schema_provider =
660                self.catalog_cache.get_with_by_ref(catalog, move || {
661                    let provider = InformationSchemaProvider::new(
662                        catalog.to_string(),
663                        self.catalog_manager.clone(),
664                        Arc::new(FlowMetadataManager::new(self.backend.clone())),
665                        self.process_manager.clone(),
666                        self.backend.clone(),
667                    );
668                    let provider = provider
669                        .with_extra_table_factories(self.extra_information_table_factories.clone());
670                    Arc::new(provider)
671                });
672            information_schema_provider.table(table_name)
673        } else if schema == PG_CATALOG_NAME && channel == Channel::Postgres {
674            if catalog == DEFAULT_CATALOG_NAME {
675                self.pg_catalog_provider.table(table_name)
676            } else {
677                let pg_catalog_provider =
678                    self.pg_catalog_cache.get_with_by_ref(catalog, move || {
679                        Arc::new(PGCatalogProvider::new(
680                            catalog.to_string(),
681                            self.catalog_manager.clone(),
682                        ))
683                    });
684                pg_catalog_provider.table(table_name)
685            }
686        } else if schema == DEFAULT_SCHEMA_NAME {
687            self.numbers_table_provider.table(table_name)
688        } else if schema == DEFAULT_PRIVATE_SCHEMA_NAME
689            && SemanticGraphTableProvider::table_exists(table_name)
690        {
691            // Constructed on demand (the provider is a name + a weak ref); the
692            // system catalog is consulted before physical resolution, so the
693            // computed tables shadow same-named physical tables by design.
694            SemanticGraphTableProvider::new(
695                catalog.to_string(),
696                self.catalog_manager.clone(),
697                query_ctx.cloned().map(Arc::new),
698            )
699            .table(table_name)
700        } else {
701            None
702        }
703    }
704}
705
706#[cfg(test)]
707mod tests {
708    use std::sync::atomic::{AtomicUsize, Ordering};
709
710    use common_meta::cache::{CacheContainer, Initializer};
711    use common_meta::key::table_route::LogicalTableRouteValue;
712    use moka::future::Cache as FutureCache;
713    use table::metadata::TableInfo;
714
715    use super::*;
716
717    fn metric_table_info(
718        table_id: TableId,
719        table_name: &str,
720        partition_key_indices: Vec<usize>,
721    ) -> TableInfo {
722        let mut table_info =
723            common_meta::ddl::test_util::create_table::test_create_table_task(table_name, table_id)
724                .table_info;
725        table_info.meta.engine = METRIC_ENGINE_NAME.to_string();
726        table_info.meta.partition_key_indices = partition_key_indices;
727        table_info
728    }
729
730    #[tokio::test]
731    async fn test_logical_table_uses_cached_physical_table_info() {
732        const LOGICAL_TABLE_ID: TableId = 1;
733        const PHYSICAL_TABLE_ID: TableId = 2;
734
735        let route = Arc::new(TableRoute::Logical(Arc::new(LogicalTableRouteValue::new(
736            PHYSICAL_TABLE_ID,
737        ))));
738        let route_initializer: Initializer<TableId, Arc<TableRoute>> = Arc::new(move |_| {
739            let route = route.clone();
740            Box::pin(async move { Ok(Some(route)) })
741        });
742        let table_route_cache = Arc::new(CacheContainer::new(
743            "test_table_route_cache".to_string(),
744            FutureCache::new(16),
745            Box::new(|_, _| Box::pin(async { Ok(()) })),
746            route_initializer,
747            |_| true,
748        ));
749
750        let load_count = Arc::new(AtomicUsize::new(0));
751        let physical_table_info =
752            Arc::new(metric_table_info(PHYSICAL_TABLE_ID, "physical", vec![1]));
753        let table_info_initializer: Initializer<TableId, Arc<TableInfo>> = {
754            let load_count = load_count.clone();
755            let table_info = physical_table_info.clone();
756            Arc::new(move |_| {
757                load_count.fetch_add(1, Ordering::Relaxed);
758                let table_info = table_info.clone();
759                Box::pin(async move { Ok(Some(table_info)) })
760            })
761        };
762        let table_info_cache = Arc::new(CacheContainer::new(
763            "test_table_info_cache".to_string(),
764            FutureCache::new(16),
765            Box::new(|_, _| Box::pin(async { Ok(()) })),
766            table_info_initializer,
767            |_| true,
768        ));
769
770        let logical_table = DistTable::table(Arc::new(metric_table_info(
771            LOGICAL_TABLE_ID,
772            "logical",
773            vec![],
774        )));
775        for _ in 0..2 {
776            let table = KvBackendCatalogManager::override_logical_table_partition_key_indices(
777                &table_route_cache,
778                &table_info_cache,
779                logical_table.clone(),
780            )
781            .await
782            .unwrap();
783            assert_eq!(table.table_info().meta.partition_key_indices, vec![1]);
784        }
785        assert_eq!(load_count.load(Ordering::Relaxed), 1);
786    }
787}