Skip to content

Commit 791ba5b

Browse files
authored
fix(query): avoid SHOW TABLES refresh failures (#19759)
* fix(query): avoid SHOW TABLES refresh failures * refactor(query): reuse disabled catalog in SHOW TABLES
1 parent 6a21faa commit 791ba5b

2 files changed

Lines changed: 139 additions & 11 deletions

File tree

src/query/service/tests/it/storages/system.rs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ use std::sync::Arc;
1717

1818
use databend_common_catalog::table::Table;
1919
use databend_common_exception::Result;
20+
use databend_common_expression::TableDataType;
21+
use databend_common_expression::TableField;
22+
use databend_common_expression::TableSchema;
2023
use databend_common_expression::block_debug::box_render;
2124
use databend_common_expression::block_debug::pretty_format_blocks;
25+
use databend_common_expression::types::NumberDataType;
2226
use databend_common_meta_app::principal::AuthInfo;
2327
use databend_common_meta_app::principal::AuthType;
2428
use databend_common_meta_app::principal::RoleInfo;
@@ -27,11 +31,15 @@ use databend_common_meta_app::schema::CreateOption;
2731
use databend_common_meta_app::schema::CreateOption::Create;
2832
use databend_common_meta_app::schema::CreateOption::CreateIfNotExists;
2933
use 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;
3037
use databend_common_meta_app::storage::StorageFsConfig;
3138
use databend_common_meta_app::storage::StorageParams;
3239
use databend_common_meta_app::storage::StorageS3Config;
3340
use databend_common_sql::executor::table_read_plan::ToReadDataSourcePlan;
3441
use databend_common_storages_basic::view_table::QUERY;
42+
use databend_common_storages_fuse::FUSE_OPT_KEY_ENABLE_AUTO_ANALYZE;
3543
use databend_common_storages_information_schema::CharacterSetsTable;
3644
use databend_common_storages_system::BuildOptionsTable;
3745
use databend_common_storages_system::CachesTable;
@@ -62,11 +70,16 @@ use databend_query::stream::ReadDataBlockStream;
6270
use databend_query::test_kits::ClusterDescriptor;
6371
use databend_query::test_kits::ConfigBuilder;
6472
use 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;
6577
use futures::TryStreamExt;
6678
use goldenfile::Mint;
6779
use wiremock::Mock;
6880
use wiremock::MockServer;
6981
use wiremock::ResponseTemplate;
82+
use wiremock::matchers::any;
7083
use wiremock::matchers::method;
7184
use 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+
}

src/query/storages/system/src/tables_table.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -672,18 +672,10 @@ where TablesTable<WITH_HISTORY, WITHOUT_VIEW>: HistoryAware
672672
let ctl_name = catalog_impl.name();
673673
let is_external_catalog = catalog_impl.is_external();
674674

675-
let ctls = if !catalog_name.is_empty() {
676-
let mut res = vec![];
677-
for name in &catalog_name {
678-
if *name == ctl_name {
679-
let ctl = ctx.get_catalog(name).await?;
680-
res.push((name.to_string(), ctl));
681-
}
682-
}
683-
// If empty return empty result
684-
res
685-
} else {
675+
let ctls = if catalog_name.is_empty() || catalog_name.iter().any(|name| name == &ctl_name) {
686676
vec![(ctl_name, catalog_impl)]
677+
} else {
678+
vec![]
687679
};
688680

689681
let no_filters = push_downs

0 commit comments

Comments
 (0)