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 75dc7227 fix(datafusion): resolve temporary objects first in SHOW 
CREATE TABLE (#1126)
75dc7227 is described below

commit 75dc72279b8f1dee379b63424abd25dbb87aed6f
Author: Jiajia Li <[email protected]>
AuthorDate: Thu Oct 15 14:18:14 2026 +0800

    fix(datafusion): resolve temporary objects first in SHOW CREATE TABLE 
(#1126)
---
 crates/integrations/datafusion/src/catalog.rs      |  18 +++
 crates/integrations/datafusion/src/sql_context.rs  |  74 +++++++++-
 .../datafusion/tests/sql_context_tests.rs          | 159 +++++++++++++++++++++
 3 files changed, 248 insertions(+), 3 deletions(-)

diff --git a/crates/integrations/datafusion/src/catalog.rs 
b/crates/integrations/datafusion/src/catalog.rs
index 19770788..e5093f14 100644
--- a/crates/integrations/datafusion/src/catalog.rs
+++ b/crates/integrations/datafusion/src/catalog.rs
@@ -718,6 +718,24 @@ impl PaimonCatalogProvider {
             .get(database)
             .is_some_and(|db| db.table_exist(table_name))
     }
+
+    /// Returns the temp table or view with the given name in the specified 
database.
+    pub(crate) async fn temp_table(
+        &self,
+        database: &str,
+        table_name: &str,
+    ) -> DFResult<Option<Arc<dyn TableProvider>>> {
+        let temp = self
+            .temp_tables
+            .read()
+            .unwrap_or_else(|e| e.into_inner())
+            .get(database)
+            .cloned();
+        match temp {
+            Some(temp) => temp.table(table_name).await,
+            None => Ok(None),
+        }
+    }
 }
 
 /// Represents a [`SchemaProvider`] for the Paimon [`Catalog`], managing
diff --git a/crates/integrations/datafusion/src/sql_context.rs 
b/crates/integrations/datafusion/src/sql_context.rs
index f4c02dc1..3c8d34dd 100644
--- a/crates/integrations/datafusion/src/sql_context.rs
+++ b/crates/integrations/datafusion/src/sql_context.rs
@@ -1262,6 +1262,17 @@ impl SQLContext {
         name: &ObjectName,
         sql_options: SQLOptions,
     ) -> DFResult<DataFrame> {
+        // Temporary objects shadow catalog tables; as in DataFusion, one 
without a
+        // definition shows NULL.
+        if let Some((catalog_name, database, table, temp)) = 
self.temp_table_for(name).await? {
+            return self.show_create_result(
+                catalog_name,
+                database,
+                table,
+                temp.get_table_definition(),
+                true,
+            );
+        }
         let (catalog, catalog_name, identifier) = 
self.resolve_catalog_and_table(name)?;
         let table = match catalog.get_table(&identifier).await {
             Ok(table) => table,
@@ -1272,19 +1283,76 @@ impl SQLContext {
         };
         crate::table_loader::ensure_paimon_served(&table, &identifier)?;
         let definition = crate::table::build_table_definition(&table)?;
+        self.show_create_result(
+            catalog_name,
+            identifier.database().to_string(),
+            identifier.object().to_string(),
+            Some(&definition),
+            false,
+        )
+    }
+
+    /// The temporary table or view `name` resolves to in queries.
+    async fn temp_table_for(
+        &self,
+        name: &ObjectName,
+    ) -> DFResult<Option<(String, String, String, Arc<dyn TableProvider>)>> {
+        let enable_ident_normalization = self.ctx.enable_ident_normalization();
+        let parts: Vec<String> = name
+            .0
+            .iter()
+            .filter_map(|part| part.as_ident())
+            .map(|ident| normalize_schema_identifier(ident, 
enable_ident_normalization))
+            .collect();
+        let table_ref = match parts.as_slice() {
+            [table] => TableReference::bare(table.as_str()),
+            [database, table] => TableReference::partial(database.as_str(), 
table.as_str()),
+            [catalog, database, table] => {
+                TableReference::full(catalog.as_str(), database.as_str(), 
table.as_str())
+            }
+            _ => return Ok(None),
+        };
+        let (catalog_name, database, table) = 
self.resolve_temp_table_name(table_ref)?;
+        // Queries resolve information_schema before consulting the catalog.
+        if database == 
datafusion::catalog::information_schema::INFORMATION_SCHEMA
+            && self.ctx.copied_config().information_schema()
+        {
+            return Ok(None);
+        }
+        let Some(catalog_provider) = self.ctx.catalog(&catalog_name) else {
+            return Ok(None);
+        };
+        let Some(provider) =
+            
catalog_provider.downcast_ref::<crate::catalog::PaimonCatalogProvider>()
+        else {
+            return Ok(None);
+        };
+        Ok(provider
+            .temp_table(&database, &table)
+            .await?
+            .map(|temp| (catalog_name, database, table, temp)))
+    }
 
+    fn show_create_result(
+        &self,
+        catalog_name: String,
+        database: String,
+        table: String,
+        definition: Option<&str>,
+        nullable: bool,
+    ) -> DFResult<DataFrame> {
         let schema = Arc::new(Schema::new(vec![
             Field::new("table_catalog", ArrowDataType::Utf8, false),
             Field::new("table_schema", ArrowDataType::Utf8, false),
             Field::new("table_name", ArrowDataType::Utf8, false),
-            Field::new("definition", ArrowDataType::Utf8, false),
+            Field::new("definition", ArrowDataType::Utf8, nullable),
         ]));
         let batch = RecordBatch::try_new(
             schema,
             vec![
                 Arc::new(StringArray::from(vec![catalog_name])),
-                
Arc::new(StringArray::from(vec![identifier.database().to_string()])),
-                
Arc::new(StringArray::from(vec![identifier.object().to_string()])),
+                Arc::new(StringArray::from(vec![database])),
+                Arc::new(StringArray::from(vec![table])),
                 Arc::new(StringArray::from(vec![definition])),
             ],
         )?;
diff --git a/crates/integrations/datafusion/tests/sql_context_tests.rs 
b/crates/integrations/datafusion/tests/sql_context_tests.rs
index 8e0eccea..b4ddf07c 100644
--- a/crates/integrations/datafusion/tests/sql_context_tests.rs
+++ b/crates/integrations/datafusion/tests/sql_context_tests.rs
@@ -2852,6 +2852,165 @@ async fn 
test_show_create_table_round_trip_with_quoted_identifiers_and_options()
     assert_schema_equivalent(&original_schema, recreated.schema());
 }
 
+/// The `table_name` and `definition` (`None` for NULL) of a one-row `SHOW 
CREATE TABLE` result.
+async fn show_create_row(sql_context: &SQLContext, table_ref: &str) -> 
(String, Option<String>) {
+    let batches = sql_context
+        .sql(&format!("SHOW CREATE TABLE {table_ref}"))
+        .await
+        .unwrap()
+        .collect()
+        .await
+        .unwrap();
+    let rows: Vec<_> = batches.iter().filter(|b| b.num_rows() > 0).collect();
+    assert_eq!(rows.len(), 1);
+    assert_eq!(rows[0].num_rows(), 1);
+    let column = |name| {
+        rows[0]
+            .column_by_name(name)
+            .unwrap()
+            .as_any()
+            .downcast_ref::<datafusion::arrow::array::StringArray>()
+            .unwrap()
+    };
+    let definition = column("definition");
+    (
+        column("table_name").value(0).to_string(),
+        definition
+            .is_valid(0)
+            .then(|| definition.value(0).to_string()),
+    )
+}
+
+#[tokio::test]
+async fn test_show_create_table_prefers_temporary_objects() {
+    let (_tmp, catalog) = create_test_env();
+    let sql_context = create_sql_context(catalog).await;
+    sql_context
+        .sql("CREATE SCHEMA paimon.test_db")
+        .await
+        .unwrap();
+    for table in ["t", "u"] {
+        sql_context
+            .sql(&format!("CREATE TABLE paimon.test_db.{table} (id INT)"))
+            .await
+            .unwrap();
+    }
+    let definition = collect_definition(&sql_context, 
"paimon.test_db.t").await;
+    assert!(
+        definition.contains("CREATE TABLE \"test_db\".\"t\""),
+        "{definition}"
+    );
+
+    sql_context
+        .sql("CREATE TEMPORARY VIEW paimon.test_db.t AS SELECT 1 AS id")
+        .await
+        .unwrap();
+    sql_context
+        .sql("CREATE TEMPORARY TABLE paimon.test_db.u (id INT)")
+        .await
+        .unwrap();
+    assert_eq!(
+        show_create_row(&sql_context, "paimon.test_db.t").await,
+        ("t".to_string(), Some("SELECT 1 AS id".to_string()))
+    );
+    assert_eq!(
+        show_create_row(&sql_context, "paimon.test_db.u").await,
+        ("u".to_string(), None)
+    );
+}
+
+#[tokio::test]
+async fn test_show_create_table_names_temporary_objects_like_queries() {
+    let (_tmp, catalog) = create_test_env();
+    let sql_context = create_sql_context(catalog).await;
+    sql_context
+        .sql("CREATE SCHEMA paimon.test_db")
+        .await
+        .unwrap();
+    let schema = Arc::new(datafusion::arrow::datatypes::Schema::empty());
+    sql_context
+        .register_temp_table(
+            "paimon.test_db.\"Foo\"",
+            Arc::new(MemTable::try_new(schema, vec![vec![]]).unwrap()),
+        )
+        .unwrap();
+    sql_context
+        .sql("CREATE TEMPORARY VIEW paimon.test_db.foo AS SELECT 1 AS id")
+        .await
+        .unwrap();
+
+    assert_eq!(
+        show_create_row(&sql_context, "paimon.test_db.Foo").await,
+        ("foo".to_string(), Some("SELECT 1 AS id".to_string()))
+    );
+    assert_eq!(
+        show_create_row(&sql_context, "paimon.test_db.\"Foo\"").await,
+        ("Foo".to_string(), None)
+    );
+}
+
+#[tokio::test]
+async fn test_show_create_temporary_object_ignores_unrelated_tables() {
+    let (tmp, catalog) = create_test_env();
+    let sql_context = create_sql_context(catalog).await;
+    sql_context
+        .sql("CREATE SCHEMA paimon.test_db")
+        .await
+        .unwrap();
+    sql_context
+        .sql("CREATE TABLE paimon.test_db.broken (id INT)")
+        .await
+        .unwrap();
+    std::fs::write(
+        tmp.path().join("test_db.db/broken/schema/schema-0"),
+        "not a schema",
+    )
+    .unwrap();
+    sql_context
+        .sql("SELECT * FROM information_schema.views")
+        .await
+        .unwrap()
+        .collect()
+        .await
+        .expect_err("a catalog scan should load the broken table");
+
+    sql_context
+        .sql("CREATE TEMPORARY VIEW paimon.test_db.v AS SELECT 1 AS id")
+        .await
+        .unwrap();
+    assert_eq!(
+        show_create_row(&sql_context, "paimon.test_db.v").await,
+        ("v".to_string(), Some("SELECT 1 AS id".to_string()))
+    );
+}
+
+#[tokio::test]
+async fn test_show_create_table_resolves_information_schema_like_queries() {
+    let (_tmp, catalog) = create_test_env();
+    let sql_context = create_sql_context(catalog).await;
+    sql_context
+        .sql("CREATE TEMPORARY VIEW paimon.information_schema.tables AS SELECT 
1 AS id")
+        .await
+        .unwrap();
+
+    let df = sql_context
+        .sql("SELECT * FROM paimon.information_schema.tables")
+        .await
+        .unwrap();
+    assert!(df.schema().has_column_with_unqualified_name("table_name"));
+    let batches = sql_context
+        .sql("SHOW CREATE TABLE paimon.information_schema.tables")
+        .await
+        .unwrap()
+        .collect()
+        .await
+        .unwrap();
+    let shown = 
datafusion::arrow::util::pretty::pretty_format_batches(&batches)
+        .unwrap()
+        .to_string();
+    assert!(!shown.contains("SELECT 1 AS id"), "{shown}");
+}
+
 #[tokio::test]
 async fn test_show_create_table_rejects_non_round_trippable_types() {
     let (_tmp, catalog) = create_test_env();

Reply via email to