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 view_info_cache(&self) -> Result<ViewInfoCacheRef> {
98 self.cache_registry.get().context(CacheNotFoundSnafu {
99 name: "view_info_cache",
100 })
101 }
102
103 pub fn information_extension(&self) -> InformationExtensionRef {
105 self.information_extension.clone()
106 }
107
108 pub fn entity_graph_provider(&self) -> Option<EntityGraphProviderRef> {
112 self.entity_graph_provider.get().cloned()
113 }
114
115 pub fn set_entity_graph_provider(&self, provider: EntityGraphProviderRef) {
118 let _ = self.entity_graph_provider.set(provider);
119 }
120
121 pub fn partition_manager(&self) -> PartitionRuleManagerRef {
122 self.partition_manager.clone()
123 }
124
125 pub fn table_metadata_manager_ref(&self) -> &TableMetadataManagerRef {
126 &self.table_metadata_manager
127 }
128
129 pub fn procedure_manager(&self) -> Option<ProcedureManagerRef> {
130 self.procedure_manager.clone()
131 }
132
133 async fn override_logical_table_partition_key_indices(
135 table_route_cache: &TableRouteCacheRef,
136 table_info_cache: &TableInfoCacheRef,
137 table: TableRef,
138 ) -> Result<TableRef> {
139 if table.table_info().meta.engine != METRIC_ENGINE_NAME {
141 return Ok(table);
142 }
143
144 if let Some(table_route_value) = table_route_cache
145 .get(table.table_info().table_id())
146 .await
147 .context(TableMetadataManagerSnafu)?
148 && let TableRoute::Logical(logical_route) = &*table_route_value
149 && let Some(physical_table_info) = table_info_cache
150 .get(logical_route.physical_table_id())
151 .await
152 .context(TableMetadataManagerSnafu)?
153 {
154 let mut new_table_info = (*table.table_info()).clone();
155
156 let mut phy_part_cols_not_in_logical_table = vec![];
157
158 new_table_info.meta.partition_key_indices = physical_table_info
160 .meta
161 .partition_key_indices
162 .iter()
163 .filter_map(|&physical_index| {
164 physical_table_info
166 .meta
167 .schema
168 .column_schemas()
169 .get(physical_index)
170 .and_then(|physical_column| {
171 let idx = new_table_info
173 .meta
174 .schema
175 .column_index_by_name(physical_column.name.as_str());
176 if idx.is_none() {
177 phy_part_cols_not_in_logical_table
179 .push(physical_column.name.clone());
180 }
181
182 idx
183 })
184 })
185 .collect();
186
187 let partition_rules = if !phy_part_cols_not_in_logical_table.is_empty() {
188 Some(PartitionRules {
189 extra_phy_cols_not_in_logical_table: phy_part_cols_not_in_logical_table,
190 })
191 } else {
192 None
193 };
194
195 let new_table = DistTable::table_partitioned(Arc::new(new_table_info), partition_rules);
196
197 return Ok(new_table);
198 }
199
200 Ok(table)
201 }
202}
203
204#[async_trait::async_trait]
205impl CatalogManager for KvBackendCatalogManager {
206 fn as_any(&self) -> &dyn Any {
207 self
208 }
209
210 async fn catalog_names(&self) -> Result<Vec<String>> {
211 let stream = self
212 .table_metadata_manager
213 .catalog_manager()
214 .catalog_names();
215
216 let keys = stream
217 .try_collect::<Vec<_>>()
218 .await
219 .map_err(BoxedError::new)
220 .context(ListCatalogsSnafu)?;
221
222 Ok(keys)
223 }
224
225 async fn schema_names(
226 &self,
227 catalog: &str,
228 query_ctx: Option<&QueryContext>,
229 ) -> Result<Vec<String>> {
230 let stream = self
231 .table_metadata_manager
232 .schema_manager()
233 .schema_names(catalog);
234 let mut keys = stream
235 .try_collect::<BTreeSet<_>>()
236 .await
237 .map_err(BoxedError::new)
238 .context(ListSchemasSnafu { catalog })?;
239
240 keys.extend(self.system_catalog.schema_names(query_ctx));
241
242 Ok(keys.into_iter().collect())
243 }
244
245 async fn table_names(
246 &self,
247 catalog: &str,
248 schema: &str,
249 query_ctx: Option<&QueryContext>,
250 ) -> Result<Vec<String>> {
251 let mut tables = self
252 .table_metadata_manager
253 .table_name_manager()
254 .tables(catalog, schema)
255 .map_ok(|(table_name, _)| table_name)
256 .try_collect::<Vec<_>>()
257 .await
258 .map_err(BoxedError::new)
259 .context(ListTablesSnafu { catalog, schema })?;
260
261 tables.extend(self.system_catalog.table_names(schema, query_ctx));
262 Ok(tables)
263 }
264
265 async fn catalog_exists(&self, catalog: &str) -> Result<bool> {
266 self.table_metadata_manager
267 .catalog_manager()
268 .exists(CatalogNameKey::new(catalog))
269 .await
270 .context(TableMetadataManagerSnafu)
271 }
272
273 async fn schema_exists(
274 &self,
275 catalog: &str,
276 schema: &str,
277 query_ctx: Option<&QueryContext>,
278 ) -> Result<bool> {
279 if self.system_catalog.schema_exists(schema, query_ctx) {
280 return Ok(true);
281 }
282
283 self.table_metadata_manager
284 .schema_manager()
285 .exists(SchemaNameKey::new(catalog, schema))
286 .await
287 .context(TableMetadataManagerSnafu)
288 }
289
290 async fn table_exists(
291 &self,
292 catalog: &str,
293 schema: &str,
294 table: &str,
295 query_ctx: Option<&QueryContext>,
296 ) -> Result<bool> {
297 if self.system_catalog.table_exists(schema, table, query_ctx) {
298 return Ok(true);
299 }
300
301 let key = TableNameKey::new(catalog, schema, table);
302 self.table_metadata_manager
303 .table_name_manager()
304 .get(key)
305 .await
306 .context(TableMetadataManagerSnafu)
307 .map(|x| x.is_some())
308 }
309
310 async fn table(
311 &self,
312 catalog_name: &str,
313 schema_name: &str,
314 table_name: &str,
315 query_ctx: Option<&QueryContext>,
316 ) -> Result<Option<TableRef>> {
317 let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
318 if let Some(table) =
319 self.system_catalog
320 .table(catalog_name, schema_name, table_name, query_ctx)
321 {
322 return Ok(Some(table));
323 }
324
325 let table_cache: TableCacheRef = self.cache_registry.get().context(CacheNotFoundSnafu {
326 name: "table_cache",
327 })?;
328
329 let table = table_cache
330 .get_by_ref(&TableName {
331 catalog_name: catalog_name.to_string(),
332 schema_name: schema_name.to_string(),
333 table_name: table_name.to_string(),
334 })
335 .await
336 .context(GetTableCacheSnafu)?;
337
338 if let Some(table) = table {
339 let table_route_cache: TableRouteCacheRef =
340 self.cache_registry.get().context(CacheNotFoundSnafu {
341 name: "table_route_cache",
342 })?;
343 let table_info_cache: TableInfoCacheRef =
344 self.cache_registry.get().context(CacheNotFoundSnafu {
345 name: "table_info_cache",
346 })?;
347 return Self::override_logical_table_partition_key_indices(
348 &table_route_cache,
349 &table_info_cache,
350 table,
351 )
352 .await
353 .map(Some);
354 }
355
356 if channel == Channel::Postgres {
357 if let Some(table) =
359 self.system_catalog
360 .table(catalog_name, PG_CATALOG_NAME, table_name, query_ctx)
361 {
362 return Ok(Some(table));
363 }
364 }
365
366 Ok(None)
367 }
368
369 async fn table_id(
370 &self,
371 catalog_name: &str,
372 schema_name: &str,
373 table_name: &str,
374 query_ctx: Option<&QueryContext>,
375 ) -> Result<Option<TableId>> {
376 let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
377 if let Some(table) =
378 self.system_catalog
379 .table(catalog_name, schema_name, table_name, query_ctx)
380 {
381 return Ok(Some(table.table_info().table_id()));
382 }
383
384 let table_cache: TableNameCacheRef =
385 self.cache_registry.get().context(CacheNotFoundSnafu {
386 name: "table_name_cache",
387 })?;
388
389 let table = table_cache
390 .get_by_ref(&TableName {
391 catalog_name: catalog_name.to_string(),
392 schema_name: schema_name.to_string(),
393 table_name: table_name.to_string(),
394 })
395 .await
396 .context(GetTableCacheSnafu)?;
397
398 if let Some(table) = table {
399 return Ok(Some(table));
400 }
401
402 if channel == Channel::Postgres {
403 if let Some(table) =
405 self.system_catalog
406 .table(catalog_name, PG_CATALOG_NAME, table_name, query_ctx)
407 {
408 return Ok(Some(table.table_info().table_id()));
409 }
410 }
411
412 Ok(None)
413 }
414
415 async fn table_info_by_id(&self, table_id: TableId) -> Result<Option<TableInfoRef>> {
416 let table_info_cache: TableInfoCacheRef =
417 self.cache_registry.get().context(CacheNotFoundSnafu {
418 name: "table_info_cache",
419 })?;
420 table_info_cache
421 .get_by_ref(&table_id)
422 .await
423 .context(GetTableCacheSnafu)
424 }
425
426 async fn tables_by_ids(
427 &self,
428 catalog: &str,
429 schema: &str,
430 table_ids: &[TableId],
431 ) -> Result<Vec<TableRef>> {
432 let table_info_values = self
433 .table_metadata_manager
434 .table_info_manager()
435 .batch_get(table_ids)
436 .await
437 .context(TableMetadataManagerSnafu)?;
438
439 let tables = table_info_values
440 .into_values()
441 .filter(|t| t.table_info.catalog_name == catalog && t.table_info.schema_name == schema)
442 .map(build_table)
443 .collect::<Vec<_>>();
444
445 Ok(tables)
446 }
447
448 fn tables<'a>(
449 &'a self,
450 catalog: &'a str,
451 schema: &'a str,
452 query_ctx: Option<&'a QueryContext>,
453 ) -> BoxStream<'a, Result<TableRef>> {
454 let sys_tables = try_stream!({
455 let sys_table_names = self.system_catalog.table_names(schema, query_ctx);
457 for table_name in sys_table_names {
458 if let Some(table) =
459 self.system_catalog
460 .table(catalog, schema, &table_name, query_ctx)
461 {
462 yield table;
463 }
464 }
465 });
466
467 const BATCH_SIZE: usize = 128;
468 const CONCURRENCY: usize = 8;
469
470 let (tx, rx) = tokio::sync::mpsc::channel(64);
471 let metadata_manager = self.table_metadata_manager.clone();
472 let catalog = catalog.to_string();
473 let schema = schema.to_string();
474 let semaphore = Arc::new(Semaphore::new(CONCURRENCY));
475 let table_route_cache: Result<TableRouteCacheRef> =
476 self.cache_registry.get().context(CacheNotFoundSnafu {
477 name: "table_route_cache",
478 });
479 let table_info_cache: Result<TableInfoCacheRef> =
480 self.cache_registry.get().context(CacheNotFoundSnafu {
481 name: "table_info_cache",
482 });
483
484 common_runtime::spawn_global(async move {
485 let table_route_cache = match table_route_cache {
486 Ok(table_route_cache) => table_route_cache,
487 Err(e) => {
488 let _ = tx.send(Err(e)).await;
489 return;
490 }
491 };
492 let table_info_cache = match table_info_cache {
493 Ok(table_info_cache) => table_info_cache,
494 Err(e) => {
495 let _ = tx.send(Err(e)).await;
496 return;
497 }
498 };
499
500 let table_id_stream = metadata_manager
501 .table_name_manager()
502 .tables(&catalog, &schema)
503 .map_ok(|(_, v)| v.table_id());
504 let mut table_id_chunks = table_id_stream.ready_chunks(BATCH_SIZE);
506
507 while let Some(table_ids) = table_id_chunks.next().await {
508 let table_ids = match table_ids
509 .into_iter()
510 .collect::<std::result::Result<Vec<_>, _>>()
511 .map_err(BoxedError::new)
512 .context(ListTablesSnafu {
513 catalog: &catalog,
514 schema: &schema,
515 }) {
516 Ok(table_ids) => table_ids,
517 Err(e) => {
518 let _ = tx.send(Err(e)).await;
519 return;
520 }
521 };
522
523 let metadata_manager = metadata_manager.clone();
524 let tx = tx.clone();
525 let semaphore = semaphore.clone();
526 let table_route_cache = table_route_cache.clone();
527 let table_info_cache = table_info_cache.clone();
528 common_runtime::spawn_global(async move {
529 let _ = semaphore.acquire().await;
531 let table_info_values = match metadata_manager
532 .table_info_manager()
533 .batch_get(&table_ids)
534 .await
535 .context(TableMetadataManagerSnafu)
536 {
537 Ok(table_info_values) => table_info_values,
538 Err(e) => {
539 let _ = tx.send(Err(e)).await;
540 return;
541 }
542 };
543
544 for table in table_info_values.into_values().map(build_table) {
545 let table = Self::override_logical_table_partition_key_indices(
546 &table_route_cache,
547 &table_info_cache,
548 table,
549 )
550 .await;
551 if tx.send(table).await.is_err() {
552 return;
553 }
554 }
555 });
556 }
557 });
558
559 let user_tables = ReceiverStream::new(rx);
560 Box::pin(sys_tables.chain(user_tables))
561 }
562}
563
564fn build_table(table_info_value: TableInfoValue) -> TableRef {
565 let table_info = table_info_value.table_info;
566 DistTable::table(Arc::new(table_info))
567}
568
569#[derive(Clone)]
577pub(super) struct SystemCatalog {
578 pub(super) catalog_manager: Weak<KvBackendCatalogManager>,
579 pub(super) catalog_cache: Cache<String, Arc<InformationSchemaProvider>>,
580 pub(super) pg_catalog_cache: Cache<String, Arc<PGCatalogProvider>>,
581
582 pub(super) information_schema_provider: Arc<InformationSchemaProvider>,
584 pub(super) pg_catalog_provider: Arc<PGCatalogProvider>,
585 pub(super) numbers_table_provider: NumbersTableProvider,
586 pub(super) backend: KvBackendRef,
587 pub(super) process_manager: Option<ProcessManagerRef>,
588 pub(super) extra_information_table_factories:
589 std::collections::HashMap<String, InformationSchemaTableFactoryRef>,
590}
591
592impl SystemCatalog {
593 fn schema_names(&self, query_ctx: Option<&QueryContext>) -> Vec<String> {
594 let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
595 match channel {
596 Channel::Postgres => vec![
598 INFORMATION_SCHEMA_NAME.to_string(),
599 PG_CATALOG_NAME.to_string(),
600 ],
601 _ => {
602 vec![INFORMATION_SCHEMA_NAME.to_string()]
603 }
604 }
605 }
606
607 fn table_names(&self, schema: &str, query_ctx: Option<&QueryContext>) -> Vec<String> {
608 let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
609 match schema {
610 INFORMATION_SCHEMA_NAME => self.information_schema_provider.table_names(),
611 PG_CATALOG_NAME if channel == Channel::Postgres => {
612 self.pg_catalog_provider.table_names()
613 }
614 DEFAULT_SCHEMA_NAME => self.numbers_table_provider.table_names(),
615 DEFAULT_PRIVATE_SCHEMA_NAME => SemanticGraphTableProvider::table_names(),
618 _ => vec![],
619 }
620 }
621
622 fn schema_exists(&self, schema: &str, query_ctx: Option<&QueryContext>) -> bool {
623 let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
624 match channel {
625 Channel::Postgres => schema == PG_CATALOG_NAME || schema == INFORMATION_SCHEMA_NAME,
626 _ => schema == INFORMATION_SCHEMA_NAME,
627 }
628 }
629
630 fn table_exists(&self, schema: &str, table: &str, query_ctx: Option<&QueryContext>) -> bool {
631 let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
632 if schema == INFORMATION_SCHEMA_NAME {
633 self.information_schema_provider.table(table).is_some()
634 } else if schema == DEFAULT_SCHEMA_NAME {
635 self.numbers_table_provider.table_exists(table)
636 } else if schema == PG_CATALOG_NAME && channel == Channel::Postgres {
637 self.pg_catalog_provider.table(table).is_some()
638 } else if schema == DEFAULT_PRIVATE_SCHEMA_NAME {
639 SemanticGraphTableProvider::table_exists(table)
640 } else {
641 false
642 }
643 }
644
645 fn table(
646 &self,
647 catalog: &str,
648 schema: &str,
649 table_name: &str,
650 query_ctx: Option<&QueryContext>,
651 ) -> Option<TableRef> {
652 let channel = query_ctx.map_or(Channel::Unknown, |ctx| ctx.channel());
653 if schema == INFORMATION_SCHEMA_NAME {
654 let information_schema_provider =
655 self.catalog_cache.get_with_by_ref(catalog, move || {
656 let provider = InformationSchemaProvider::new(
657 catalog.to_string(),
658 self.catalog_manager.clone(),
659 Arc::new(FlowMetadataManager::new(self.backend.clone())),
660 self.process_manager.clone(),
661 self.backend.clone(),
662 );
663 let provider = provider
664 .with_extra_table_factories(self.extra_information_table_factories.clone());
665 Arc::new(provider)
666 });
667 information_schema_provider.table(table_name)
668 } else if schema == PG_CATALOG_NAME && channel == Channel::Postgres {
669 if catalog == DEFAULT_CATALOG_NAME {
670 self.pg_catalog_provider.table(table_name)
671 } else {
672 let pg_catalog_provider =
673 self.pg_catalog_cache.get_with_by_ref(catalog, move || {
674 Arc::new(PGCatalogProvider::new(
675 catalog.to_string(),
676 self.catalog_manager.clone(),
677 ))
678 });
679 pg_catalog_provider.table(table_name)
680 }
681 } else if schema == DEFAULT_SCHEMA_NAME {
682 self.numbers_table_provider.table(table_name)
683 } else if schema == DEFAULT_PRIVATE_SCHEMA_NAME
684 && SemanticGraphTableProvider::table_exists(table_name)
685 {
686 SemanticGraphTableProvider::new(
690 catalog.to_string(),
691 self.catalog_manager.clone(),
692 query_ctx.cloned().map(Arc::new),
693 )
694 .table(table_name)
695 } else {
696 None
697 }
698 }
699}
700
701#[cfg(test)]
702mod tests {
703 use std::sync::atomic::{AtomicUsize, Ordering};
704
705 use common_meta::cache::{CacheContainer, Initializer};
706 use common_meta::key::table_route::LogicalTableRouteValue;
707 use moka::future::Cache as FutureCache;
708 use table::metadata::TableInfo;
709
710 use super::*;
711
712 fn metric_table_info(
713 table_id: TableId,
714 table_name: &str,
715 partition_key_indices: Vec<usize>,
716 ) -> TableInfo {
717 let mut table_info =
718 common_meta::ddl::test_util::create_table::test_create_table_task(table_name, table_id)
719 .table_info;
720 table_info.meta.engine = METRIC_ENGINE_NAME.to_string();
721 table_info.meta.partition_key_indices = partition_key_indices;
722 table_info
723 }
724
725 #[tokio::test]
726 async fn test_logical_table_uses_cached_physical_table_info() {
727 const LOGICAL_TABLE_ID: TableId = 1;
728 const PHYSICAL_TABLE_ID: TableId = 2;
729
730 let route = Arc::new(TableRoute::Logical(Arc::new(LogicalTableRouteValue::new(
731 PHYSICAL_TABLE_ID,
732 ))));
733 let route_initializer: Initializer<TableId, Arc<TableRoute>> = Arc::new(move |_| {
734 let route = route.clone();
735 Box::pin(async move { Ok(Some(route)) })
736 });
737 let table_route_cache = Arc::new(CacheContainer::new(
738 "test_table_route_cache".to_string(),
739 FutureCache::new(16),
740 Box::new(|_, _| Box::pin(async { Ok(()) })),
741 route_initializer,
742 |_| true,
743 ));
744
745 let load_count = Arc::new(AtomicUsize::new(0));
746 let physical_table_info =
747 Arc::new(metric_table_info(PHYSICAL_TABLE_ID, "physical", vec![1]));
748 let table_info_initializer: Initializer<TableId, Arc<TableInfo>> = {
749 let load_count = load_count.clone();
750 let table_info = physical_table_info.clone();
751 Arc::new(move |_| {
752 load_count.fetch_add(1, Ordering::Relaxed);
753 let table_info = table_info.clone();
754 Box::pin(async move { Ok(Some(table_info)) })
755 })
756 };
757 let table_info_cache = Arc::new(CacheContainer::new(
758 "test_table_info_cache".to_string(),
759 FutureCache::new(16),
760 Box::new(|_, _| Box::pin(async { Ok(()) })),
761 table_info_initializer,
762 |_| true,
763 ));
764
765 let logical_table = DistTable::table(Arc::new(metric_table_info(
766 LOGICAL_TABLE_ID,
767 "logical",
768 vec![],
769 )));
770 for _ in 0..2 {
771 let table = KvBackendCatalogManager::override_logical_table_partition_key_indices(
772 &table_route_cache,
773 &table_info_cache,
774 logical_table.clone(),
775 )
776 .await
777 .unwrap();
778 assert_eq!(table.table_info().meta.partition_key_indices, vec![1]);
779 }
780 assert_eq!(load_count.load(Ordering::Relaxed), 1);
781 }
782}