1use 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#[derive(Clone)]
73pub struct KvBackendCatalogManager {
74 pub(super) information_extension: InformationExtensionRef,
76 pub(super) entity_graph_provider: Arc<OnceLock<EntityGraphProviderRef>>,
82 pub(super) partition_manager: PartitionRuleManagerRef,
84 pub(super) table_metadata_manager: TableMetadataManagerRef,
86 pub(super) system_catalog: SystemCatalog,
88 pub(super) cache_registry: LayeredCacheRegistryRef,
90 pub(super) procedure_manager: Option<ProcedureManagerRef>,
92}
93
94pub(super) const CATALOG_CACHE_MAX_CAPACITY: u64 = 128;
95
96impl KvBackendCatalogManager {
97 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 pub fn information_extension(&self) -> InformationExtensionRef {
110 self.information_extension.clone()
111 }
112
113 pub fn entity_graph_provider(&self) -> Option<EntityGraphProviderRef> {
117 self.entity_graph_provider.get().cloned()
118 }
119
120 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 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 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 new_table_info.meta.partition_key_indices = physical_table_info
165 .meta
166 .partition_key_indices
167 .iter()
168 .filter_map(|&physical_index| {
169 physical_table_info
171 .meta
172 .schema
173 .column_schemas()
174 .get(physical_index)
175 .and_then(|physical_column| {
176 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 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 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 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 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 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 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#[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 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 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 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 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}