This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git
The following commit(s) were added to refs/heads/main by this push:
new 856d4173 fix(datafusion): avoid loading tables for SHOW TABLES (#576)
856d4173 is described below
commit 856d4173a6af114a0d31b5897ffcc266e425b4dc
Author: shyjsarah <[email protected]>
AuthorDate: Tue Jul 21 20:49:06 2026 +0800
fix(datafusion): avoid loading tables for SHOW TABLES (#576)
---
crates/integrations/datafusion/src/catalog.rs | 56 ++++++--
.../datafusion/tests/sql_context_tests.rs | 143 ++++++++++++++++++++-
2 files changed, 190 insertions(+), 9 deletions(-)
diff --git a/crates/integrations/datafusion/src/catalog.rs
b/crates/integrations/datafusion/src/catalog.rs
index 26436397..83ef810f 100644
--- a/crates/integrations/datafusion/src/catalog.rs
+++ b/crates/integrations/datafusion/src/catalog.rs
@@ -25,7 +25,7 @@ use std::sync::RwLock;
use async_trait::async_trait;
use datafusion::catalog::{CatalogProvider, MemorySchemaProvider,
SchemaProvider};
use datafusion::common::{plan_datafusion_err, Column};
-use datafusion::datasource::TableProvider;
+use datafusion::datasource::{TableProvider, TableType};
use datafusion::error::Result as DFResult;
use datafusion::execution::SessionState;
use datafusion::logical_expr::{expr_fn::cast, Expr, LogicalPlan,
LogicalPlanBuilder};
@@ -338,6 +338,8 @@ pub struct PaimonSchemaProvider {
dynamic_options: DynamicOptions,
/// Optional temporary in-memory provider for temp tables and views.
temp_provider: Option<Arc<MemorySchemaProvider>>,
+ /// Table types populated together with `table_names` for metadata-only
lookups.
+ catalog_table_types: RwLock<HashMap<String, TableType>>,
blob_reader_registry: BlobReaderRegistry,
session_state: Option<SessionStateProvider>,
schema_force_view_types: bool,
@@ -369,6 +371,7 @@ impl PaimonSchemaProvider {
database,
dynamic_options,
temp_provider,
+ catalog_table_types: RwLock::new(HashMap::new()),
blob_reader_registry,
session_state,
schema_force_view_types: true,
@@ -386,30 +389,46 @@ impl SchemaProvider for PaimonSchemaProvider {
fn table_names(&self) -> Vec<String> {
let catalog = Arc::clone(&self.catalog);
let database = self.database.clone();
- let mut names = block_on_with_runtime(
+ let (mut names, views) = block_on_with_runtime(
{
let db = database.clone();
async move {
- let mut names = match catalog.list_tables(&db).await {
+ let names = match catalog.list_tables(&db).await {
Ok(names) => names,
Err(e) => {
log::error!("failed to list tables in '{}': {e}",
db);
vec![]
}
};
- match catalog.list_views(&db).await {
- Ok(views) => names.extend(views),
- Err(paimon::Error::Unsupported { .. }) => {}
+ let views = match catalog.list_views(&db).await {
+ Ok(views) => views,
+ Err(paimon::Error::Unsupported { .. }) => vec![],
Err(error) => {
log::error!("failed to list views in '{}':
{error}", db);
+ vec![]
}
- }
- names
+ };
+ (names, views)
}
},
"paimon catalog access thread panicked",
);
+ let mut catalog_table_types = HashMap::with_capacity(names.len() +
views.len());
+ for name in &names {
+ catalog_table_types.insert(name.clone(), TableType::Base);
+ }
+ for view in views {
+ catalog_table_types
+ .entry(view.clone())
+ .or_insert(TableType::View);
+ names.push(view);
+ }
+ *self
+ .catalog_table_types
+ .write()
+ .unwrap_or_else(|e| e.into_inner()) = catalog_table_types;
+
if let Some(temp) = &self.temp_provider {
names.extend(temp.table_names());
}
@@ -540,6 +559,27 @@ impl SchemaProvider for PaimonSchemaProvider {
.await
}
+ async fn table_type(&self, name: &str) -> DFResult<Option<TableType>> {
+ if let Some(temp) = &self.temp_provider {
+ if let Some(table_type) = temp.table_type(name).await? {
+ return Ok(Some(table_type));
+ }
+ }
+
+ if let Some(table_type) = self
+ .catalog_table_types
+ .read()
+ .unwrap_or_else(|e| e.into_inner())
+ .get(name)
+ {
+ return Ok(Some(*table_type));
+ }
+
+ self.table(name)
+ .await
+ .map(|table| table.map(|table| table.table_type()))
+ }
+
fn table_exist(&self, name: &str) -> bool {
if let Some(temp) = &self.temp_provider {
if temp.table_exist(name) {
diff --git a/crates/integrations/datafusion/tests/sql_context_tests.rs
b/crates/integrations/datafusion/tests/sql_context_tests.rs
index f04f1848..d0f1c244 100644
--- a/crates/integrations/datafusion/tests/sql_context_tests.rs
+++ b/crates/integrations/datafusion/tests/sql_context_tests.rs
@@ -17,7 +17,7 @@
//! SQL context integration tests for paimon-datafusion.
-use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
@@ -50,6 +50,105 @@ async fn create_sql_context(catalog:
Arc<FileSystemCatalog>) -> SQLContext {
ctx
}
+struct MetadataListingCatalog {
+ get_table_calls: AtomicUsize,
+}
+
+impl MetadataListingCatalog {
+ fn new() -> Self {
+ Self {
+ get_table_calls: AtomicUsize::new(0),
+ }
+ }
+
+ fn get_table_calls(&self) -> usize {
+ self.get_table_calls.load(Ordering::SeqCst)
+ }
+}
+
+#[async_trait]
+impl Catalog for MetadataListingCatalog {
+ async fn list_databases(&self) -> paimon::Result<Vec<String>> {
+ Ok(vec!["default".to_string()])
+ }
+
+ async fn create_database(
+ &self,
+ _name: &str,
+ _ignore_if_exists: bool,
+ _properties: std::collections::HashMap<String, String>,
+ ) -> paimon::Result<()> {
+ Ok(())
+ }
+
+ async fn get_database(&self, name: &str) ->
paimon::Result<paimon::catalog::Database> {
+ Ok(paimon::catalog::Database::new(
+ name.to_string(),
+ std::collections::HashMap::new(),
+ None,
+ ))
+ }
+
+ async fn drop_database(
+ &self,
+ _name: &str,
+ _ignore_if_not_exists: bool,
+ _cascade: bool,
+ ) -> paimon::Result<()> {
+ Ok(())
+ }
+
+ async fn get_table(&self, _identifier: &Identifier) ->
paimon::Result<paimon::table::Table> {
+ self.get_table_calls.fetch_add(1, Ordering::SeqCst);
+ Err(paimon::Error::Unsupported {
+ message: "table loading is unavailable".to_string(),
+ })
+ }
+
+ async fn list_tables(&self, _database_name: &str) ->
paimon::Result<Vec<String>> {
+ Ok(vec!["metadata_only".to_string()])
+ }
+
+ async fn list_views(&self, _database_name: &str) ->
paimon::Result<Vec<String>> {
+ Ok(vec!["metadata_view".to_string()])
+ }
+
+ async fn create_table(
+ &self,
+ _identifier: &Identifier,
+ _creation: paimon::spec::Schema,
+ _ignore_if_exists: bool,
+ ) -> paimon::Result<()> {
+ Ok(())
+ }
+
+ async fn drop_table(
+ &self,
+ _identifier: &Identifier,
+ _ignore_if_not_exists: bool,
+ ) -> paimon::Result<()> {
+ Ok(())
+ }
+
+ async fn rename_table(
+ &self,
+ _from: &Identifier,
+ _to: &Identifier,
+ _ignore_if_not_exists: bool,
+ ) -> paimon::Result<()> {
+ Ok(())
+ }
+
+ async fn alter_table(
+ &self,
+ _identifier: &Identifier,
+ _changes: Vec<SchemaChange>,
+ _ignore_if_not_exists: bool,
+ ) -> paimon::Result<()> {
+ Ok(())
+ }
+}
+
struct PartitionCatalog {
inner: Arc<FileSystemCatalog>,
fail_list_partitions: AtomicBool,
@@ -265,6 +364,48 @@ async fn test_show_tables_is_enabled() {
.expect("SHOW TABLES should execute");
}
+#[tokio::test]
+async fn test_show_tables_does_not_load_table_providers() {
+ let catalog = Arc::new(MetadataListingCatalog::new());
+ let mut sql_context = SQLContext::new();
+ sql_context
+ .register_catalog("paimon", catalog.clone())
+ .await
+ .unwrap();
+
+ let table_names = collect_string_column(&sql_context, "SHOW TABLES",
"table_name").await;
+
+ assert!(table_names.contains(&"metadata_only".to_string()));
+ assert_eq!(catalog.get_table_calls(), 0);
+
+ assert!(sql_context
+ .sql("SELECT * FROM metadata_only")
+ .await
+ .is_err());
+ assert!(catalog.get_table_calls() > 0);
+}
+
+#[tokio::test]
+async fn test_show_tables_preserves_catalog_view_type() {
+ let catalog = Arc::new(MetadataListingCatalog::new());
+ let mut sql_context = SQLContext::new();
+ sql_context
+ .register_catalog("paimon", catalog.clone())
+ .await
+ .unwrap();
+
+ let table_types = collect_string_column(
+ &sql_context,
+ "SELECT table_type FROM information_schema.tables \
+ WHERE table_schema = 'default' AND table_name = 'metadata_view'",
+ "table_type",
+ )
+ .await;
+
+ assert_eq!(table_types, vec!["VIEW"]);
+ assert_eq!(catalog.get_table_calls(), 0);
+}
+
#[tokio::test]
async fn test_select_branch_table_reads_branch_snapshot() {
let (_tmp, catalog) = create_test_env();