@@ -17,8 +17,12 @@ use std::sync::Arc;
1717
1818use databend_common_catalog:: table:: Table ;
1919use databend_common_exception:: Result ;
20+ use databend_common_expression:: TableDataType ;
21+ use databend_common_expression:: TableField ;
22+ use databend_common_expression:: TableSchema ;
2023use databend_common_expression:: block_debug:: box_render;
2124use databend_common_expression:: block_debug:: pretty_format_blocks;
25+ use databend_common_expression:: types:: NumberDataType ;
2226use databend_common_meta_app:: principal:: AuthInfo ;
2327use databend_common_meta_app:: principal:: AuthType ;
2428use databend_common_meta_app:: principal:: RoleInfo ;
@@ -27,11 +31,15 @@ use databend_common_meta_app::schema::CreateOption;
2731use databend_common_meta_app:: schema:: CreateOption :: Create ;
2832use databend_common_meta_app:: schema:: CreateOption :: CreateIfNotExists ;
2933use databend_common_meta_app:: schema:: CreateOption :: CreateOrReplace ;
34+ use databend_common_meta_app:: schema:: CreateTableReq ;
35+ use databend_common_meta_app:: schema:: TableMeta ;
36+ use databend_common_meta_app:: schema:: TableNameIdent ;
3037use databend_common_meta_app:: storage:: StorageFsConfig ;
3138use databend_common_meta_app:: storage:: StorageParams ;
3239use databend_common_meta_app:: storage:: StorageS3Config ;
3340use databend_common_sql:: executor:: table_read_plan:: ToReadDataSourcePlan ;
3441use databend_common_storages_basic:: view_table:: QUERY ;
42+ use databend_common_storages_fuse:: FUSE_OPT_KEY_ENABLE_AUTO_ANALYZE ;
3543use databend_common_storages_information_schema:: CharacterSetsTable ;
3644use databend_common_storages_system:: BuildOptionsTable ;
3745use databend_common_storages_system:: CachesTable ;
@@ -62,11 +70,16 @@ use databend_query::stream::ReadDataBlockStream;
6270use databend_query:: test_kits:: ClusterDescriptor ;
6371use databend_query:: test_kits:: ConfigBuilder ;
6472use databend_query:: test_kits:: TestFixture ;
73+ use databend_query:: test_kits:: execute_command;
74+ use databend_query:: test_kits:: execute_query;
75+ use databend_storages_common_table_meta:: table:: OPT_KEY_DATABASE_ID ;
76+ use databend_storages_common_table_meta:: table:: OPT_KEY_TABLE_ATTACHED_DATA_URI ;
6577use futures:: TryStreamExt ;
6678use goldenfile:: Mint ;
6779use wiremock:: Mock ;
6880use wiremock:: MockServer ;
6981use wiremock:: ResponseTemplate ;
82+ use wiremock:: matchers:: any;
7083use wiremock:: matchers:: method;
7184use wiremock:: matchers:: path;
7285
@@ -514,3 +527,126 @@ async fn test_caches_table() -> anyhow::Result<()> {
514527
515528 Ok ( ( ) )
516529}
530+
531+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
532+ async fn test_show_tables_ignores_broken_attached_table_refresh ( ) -> anyhow:: Result < ( ) > {
533+ let fixture = TestFixture :: setup ( ) . await ?;
534+ let ctx = fixture. new_query_ctx ( ) . await ?;
535+ let tenant = ctx. get_tenant ( ) ;
536+ let catalog = ctx. get_catalog ( "default" ) . await ?;
537+ let database = catalog. get_database ( & tenant, "default" ) . await ?;
538+
539+ execute_command ( ctx. clone ( ) , "create table default.healthy(a int)" ) . await ?;
540+
541+ // Simulate a broken attached-table storage: any attempt to refresh from this
542+ // S3 endpoint will fail with 403, matching the original issue's behavior.
543+ let mock_server = MockServer :: start ( ) . await ;
544+ Mock :: given ( any ( ) )
545+ . respond_with ( ResponseTemplate :: new ( 403 ) )
546+ . mount ( & mock_server)
547+ . await ;
548+
549+ let broken_schema = Arc :: new ( TableSchema :: new ( vec ! [ TableField :: new(
550+ "a" ,
551+ TableDataType :: Number ( NumberDataType :: Int32 ) ,
552+ ) ] ) ) ;
553+
554+ // Register a FUSE attached table whose refresh path points at the failing mock
555+ // endpoint above. Without disable_table_info_refresh, loading this table will
556+ // try to fetch the last snapshot hint and fail.
557+ catalog
558+ . create_table ( CreateTableReq {
559+ create_option : CreateOption :: Create ,
560+ catalog_name : None ,
561+ name_ident : TableNameIdent {
562+ tenant : tenant. clone ( ) ,
563+ db_name : "default" . to_string ( ) ,
564+ table_name : "broken_attached" . to_string ( ) ,
565+ } ,
566+ table_meta : TableMeta {
567+ schema : broken_schema,
568+ engine : "FUSE" . to_string ( ) ,
569+ options : [
570+ (
571+ OPT_KEY_DATABASE_ID . to_string ( ) ,
572+ database. get_db_info ( ) . database_id . db_id . to_string ( ) ,
573+ ) ,
574+ (
575+ FUSE_OPT_KEY_ENABLE_AUTO_ANALYZE . to_string ( ) ,
576+ "0" . to_string ( ) ,
577+ ) ,
578+ (
579+ OPT_KEY_TABLE_ATTACHED_DATA_URI . to_string ( ) ,
580+ "s3://broken-bucket/broken-attached/" . to_string ( ) ,
581+ ) ,
582+ ]
583+ . into ( ) ,
584+ storage_params : Some ( StorageParams :: S3 ( StorageS3Config {
585+ region : "us-east-2" . to_string ( ) ,
586+ endpoint_url : mock_server. uri ( ) ,
587+ bucket : "broken-bucket" . to_string ( ) ,
588+ root : "/" . to_string ( ) ,
589+ access_key_id : "access_key_id" . to_string ( ) ,
590+ secret_access_key : "secret_access_key" . to_string ( ) ,
591+ disable_credential_loader : true ,
592+ ..Default :: default ( )
593+ } ) ) ,
594+ ..TableMeta :: default ( )
595+ } ,
596+ as_dropped : false ,
597+ table_properties : None ,
598+ table_partition : None ,
599+ } )
600+ . await ?;
601+
602+ let list_tables_error = catalog. list_tables ( & tenant, "default" ) . await ;
603+ assert ! (
604+ list_tables_error. is_err( ) ,
605+ "loading attached tables without disabling refresh should fail"
606+ ) ;
607+
608+ let disabled_catalog = catalog. clone ( ) . disable_table_info_refresh ( ) ?;
609+ let disabled_list_tables = disabled_catalog. list_tables ( & tenant, "default" ) . await ;
610+ assert ! (
611+ disabled_list_tables. is_ok( ) ,
612+ "loading attached tables with disable_table_info_refresh should succeed"
613+ ) ;
614+
615+ mock_server. reset ( ) . await ;
616+ Mock :: given ( any ( ) )
617+ . respond_with ( ResponseTemplate :: new ( 403 ) )
618+ . mount ( & mock_server)
619+ . await ;
620+
621+ let result = execute_query ( ctx. clone ( ) , "show tables from default" ) . await ?;
622+ let blocks = result. try_collect :: < Vec < _ > > ( ) . await ?;
623+ let output = pretty_format_blocks ( & blocks) ?;
624+ println ! ( "{}" , output) ;
625+
626+ assert ! (
627+ output. contains( "broken_attached" ) ,
628+ "SHOW TABLES should keep the broken attached table visible: {output}"
629+ ) ;
630+ assert ! (
631+ output. contains( "healthy" ) ,
632+ "SHOW TABLES should still return healthy tables in the same database: {output}"
633+ ) ;
634+
635+ let warnings = ctx. pop_warnings ( ) ;
636+ assert ! (
637+ warnings. is_empty( ) ,
638+ "SHOW TABLES should not emit storage warnings once refresh is disabled: {warnings:?}"
639+ ) ;
640+
641+ // The key regression check: listing system.tables must not touch table-level storage.
642+ let requests = mock_server
643+ . received_requests ( )
644+ . await
645+ . expect ( "request recording should be enabled" ) ;
646+ assert ! (
647+ requests. is_empty( ) ,
648+ "SHOW TABLES should not touch table-level storage when listing system.tables: {requests:?}"
649+ ) ;
650+
651+ Ok ( ( ) )
652+ }
0 commit comments