blob: 6d24d93110cb8e64bd0101f2d12fe6dcab63d988 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! SQL context integration tests for paimon-datafusion.
use std::sync::Arc;
use datafusion::catalog::CatalogProvider;
use paimon::catalog::Identifier;
use paimon::spec::{ArrayType, BlobType, DataType, IntType, MapType, VarCharType};
use paimon::{Catalog, CatalogOptions, FileSystemCatalog, Options};
use paimon_datafusion::{PaimonCatalogProvider, SQLContext};
use tempfile::TempDir;
fn create_test_env() -> (TempDir, Arc<FileSystemCatalog>) {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let warehouse = format!("file://{}", temp_dir.path().display());
let mut options = Options::new();
options.set(CatalogOptions::WAREHOUSE, warehouse);
let catalog = FileSystemCatalog::new(options).expect("Failed to create catalog");
(temp_dir, Arc::new(catalog))
}
async fn create_sql_context(catalog: Arc<FileSystemCatalog>) -> SQLContext {
let mut ctx = SQLContext::new();
ctx.register_catalog("paimon", catalog).await.unwrap();
ctx
}
// ======================= CREATE / DROP SCHEMA =======================
#[tokio::test]
async fn test_create_schema() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
sql_context
.sql("CREATE SCHEMA paimon.test_db")
.await
.expect("CREATE SCHEMA should succeed");
let databases = catalog.list_databases().await.unwrap();
assert!(
databases.contains(&"test_db".to_string()),
"Database test_db should exist after CREATE SCHEMA"
);
}
#[tokio::test]
async fn test_drop_schema() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("drop_me", false, Default::default())
.await
.unwrap();
sql_context
.sql("DROP SCHEMA paimon.drop_me CASCADE")
.await
.expect("DROP SCHEMA should succeed");
let databases = catalog.list_databases().await.unwrap();
assert!(
!databases.contains(&"drop_me".to_string()),
"Database drop_me should not exist after DROP SCHEMA"
);
}
#[tokio::test]
async fn test_schema_names_via_catalog_provider() {
let (_tmp, catalog) = create_test_env();
let provider = PaimonCatalogProvider::new(catalog.clone());
catalog
.create_database("db_a", false, Default::default())
.await
.unwrap();
catalog
.create_database("db_b", false, Default::default())
.await
.unwrap();
let names = provider.schema_names();
assert!(names.contains(&"db_a".to_string()));
assert!(names.contains(&"db_b".to_string()));
}
// ======================= CREATE TABLE =======================
#[tokio::test]
async fn test_create_table() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
sql_context
.sql(
"CREATE TABLE paimon.mydb.users (
id INT NOT NULL,
name STRING,
age INT,
PRIMARY KEY (id)
)",
)
.await
.expect("CREATE TABLE should succeed");
let tables = catalog.list_tables("mydb").await.unwrap();
assert!(
tables.contains(&"users".to_string()),
"Table users should exist after CREATE TABLE"
);
// Verify schema
let table = catalog
.get_table(&Identifier::new("mydb", "users"))
.await
.unwrap();
let schema = table.schema();
assert_eq!(schema.fields().len(), 3);
assert_eq!(schema.primary_keys(), &["id"]);
}
#[tokio::test]
async fn test_create_table_with_blob_type() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
sql_context
.sql(
"CREATE TABLE paimon.mydb.assets (
id INT NOT NULL,
payload BLOB,
PRIMARY KEY (id)
) WITH ('data-evolution.enabled' = 'true')",
)
.await
.expect("CREATE TABLE with BLOB should succeed");
let table = catalog
.get_table(&Identifier::new("mydb", "assets"))
.await
.unwrap();
let schema = table.schema();
assert_eq!(schema.fields().len(), 2);
assert_eq!(schema.primary_keys(), &["id"]);
assert_eq!(
*schema.fields()[1].data_type(),
DataType::Blob(BlobType::new())
);
}
#[tokio::test]
async fn test_create_table_with_partition() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
sql_context
.sql(
"CREATE TABLE paimon.mydb.events (
id INT NOT NULL,
name STRING,
dt STRING,
PRIMARY KEY (id, dt)
) PARTITIONED BY (dt)
WITH ('bucket' = '2')",
)
.await
.expect("CREATE TABLE with partition should succeed");
let table = catalog
.get_table(&Identifier::new("mydb", "events"))
.await
.unwrap();
let schema = table.schema();
assert_eq!(schema.partition_keys(), &["dt"]);
assert_eq!(schema.primary_keys(), &["id", "dt"]);
assert_eq!(
schema.options().get("bucket"),
Some(&"2".to_string()),
"Table option 'bucket' should be preserved"
);
}
#[tokio::test]
async fn test_create_table_partitioned_by_rejects_typed_columns() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
let err = sql_context
.sql(
"CREATE TABLE paimon.mydb.events (
id INT NOT NULL,
dt STRING
) PARTITIONED BY (dt STRING)",
)
.await
.expect_err("PARTITIONED BY with typed columns should fail");
let msg = err.to_string();
assert!(
msg.contains("should not specify a type"),
"unexpected error: {msg}"
);
}
#[tokio::test]
async fn test_create_table_if_not_exists() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
let sql = "CREATE TABLE IF NOT EXISTS paimon.mydb.t1 (
id INT NOT NULL
)";
// First create should succeed
sql_context
.sql(sql)
.await
.expect("First CREATE should succeed");
// Second create with IF NOT EXISTS should also succeed
sql_context
.sql(sql)
.await
.expect("Second CREATE with IF NOT EXISTS should succeed");
}
#[tokio::test]
async fn test_create_external_table_rejected() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
let result = sql_context
.sql(
"CREATE EXTERNAL TABLE paimon.mydb.bad (
id INT NOT NULL
) STORED AS PARQUET
LOCATION '/some/path'",
)
.await;
assert!(result.is_err(), "CREATE EXTERNAL TABLE should be rejected");
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("CREATE EXTERNAL TABLE is not supported"),
"Error should mention CREATE EXTERNAL TABLE is not supported, got: {err_msg}"
);
}
// ======================= CREATE TABLE with complex types =======================
#[tokio::test]
async fn test_create_table_with_array_and_map() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
sql_context
.sql(
"CREATE TABLE paimon.mydb.complex_types (
id INT NOT NULL,
tags ARRAY<STRING>,
props MAP(STRING, INT),
PRIMARY KEY (id)
)",
)
.await
.expect("CREATE TABLE with ARRAY and MAP should succeed");
let table = catalog
.get_table(&Identifier::new("mydb", "complex_types"))
.await
.unwrap();
let schema = table.schema();
assert_eq!(schema.fields().len(), 3);
assert_eq!(schema.primary_keys(), &["id"]);
// Verify ARRAY<STRING> column
let tags_field = &schema.fields()[1];
assert_eq!(tags_field.name(), "tags");
assert_eq!(
*tags_field.data_type(),
DataType::Array(ArrayType::new(
DataType::VarChar(VarCharType::string_type())
))
);
// Verify MAP(STRING, INT) column
let props_field = &schema.fields()[2];
assert_eq!(props_field.name(), "props");
assert_eq!(
*props_field.data_type(),
DataType::Map(MapType::new(
DataType::VarChar(VarCharType::string_type())
.copy_with_nullable(false)
.unwrap(),
DataType::Int(IntType::new()),
))
);
}
#[tokio::test]
async fn test_create_table_with_row_type() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
sql_context
.sql(
"CREATE TABLE paimon.mydb.row_table (
id INT NOT NULL,
address STRUCT<city STRING, zip INT>,
PRIMARY KEY (id)
)",
)
.await
.expect("CREATE TABLE with STRUCT should succeed");
let table = catalog
.get_table(&Identifier::new("mydb", "row_table"))
.await
.unwrap();
let schema = table.schema();
assert_eq!(schema.fields().len(), 2);
// Verify STRUCT<city STRING, zip INT> column
let address_field = &schema.fields()[1];
assert_eq!(address_field.name(), "address");
if let DataType::Row(row) = address_field.data_type() {
assert_eq!(row.fields().len(), 2);
assert_eq!(row.fields()[0].name(), "city");
assert!(matches!(row.fields()[0].data_type(), DataType::VarChar(_)));
assert_eq!(row.fields()[1].name(), "zip");
assert!(matches!(row.fields()[1].data_type(), DataType::Int(_)));
} else {
panic!("expected Row type for address column");
}
}
// ======================= DROP TABLE =======================
#[tokio::test]
async fn test_drop_table() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
// Create a table first
let schema = paimon::spec::Schema::builder()
.column(
"id",
paimon::spec::DataType::Int(paimon::spec::IntType::new()),
)
.build()
.unwrap();
catalog
.create_table(&Identifier::new("mydb", "to_drop"), schema, false)
.await
.unwrap();
assert!(catalog
.list_tables("mydb")
.await
.unwrap()
.contains(&"to_drop".to_string()));
sql_context
.sql("DROP TABLE paimon.mydb.to_drop")
.await
.expect("DROP TABLE should succeed");
assert!(
!catalog
.list_tables("mydb")
.await
.unwrap()
.contains(&"to_drop".to_string()),
"Table should not exist after DROP TABLE"
);
}
// ======================= ALTER TABLE =======================
#[tokio::test]
async fn test_alter_table_add_column() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
let schema = paimon::spec::Schema::builder()
.column(
"id",
paimon::spec::DataType::Int(paimon::spec::IntType::new()),
)
.column(
"name",
paimon::spec::DataType::VarChar(paimon::spec::VarCharType::string_type()),
)
.build()
.unwrap();
catalog
.create_table(&Identifier::new("mydb", "alter_test"), schema, false)
.await
.unwrap();
// ALTER TABLE is not yet implemented in FileSystemCatalog, so we expect an error
let result = sql_context
.sql("ALTER TABLE paimon.mydb.alter_test ADD COLUMN age INT")
.await;
// FileSystemCatalog does not support AddColumn schema change yet
assert!(
result.is_err(),
"ALTER TABLE ADD COLUMN should fail because AddColumn is not yet supported"
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("not yet implemented")
|| err_msg.contains("Unsupported")
|| err_msg.contains("not yet supported"),
"Error should indicate alter_table is not implemented, got: {err_msg}"
);
}
#[tokio::test]
async fn test_alter_table_rename() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
let schema = paimon::spec::Schema::builder()
.column(
"id",
paimon::spec::DataType::Int(paimon::spec::IntType::new()),
)
.build()
.unwrap();
catalog
.create_table(&Identifier::new("mydb", "old_name"), schema, false)
.await
.unwrap();
sql_context
.sql("ALTER TABLE mydb.old_name RENAME TO new_name")
.await
.expect("ALTER TABLE RENAME should succeed");
let tables = catalog.list_tables("mydb").await.unwrap();
assert!(
!tables.contains(&"old_name".to_string()),
"old_name should not exist after rename"
);
assert!(
tables.contains(&"new_name".to_string()),
"new_name should exist after rename"
);
}
#[tokio::test]
async fn test_ddl_context_delegates_select() {
let (_tmp, catalog) = create_test_env();
let sql_context = create_sql_context(catalog.clone()).await;
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
let schema = paimon::spec::Schema::builder()
.column(
"id",
paimon::spec::DataType::Int(paimon::spec::IntType::new()),
)
.build()
.unwrap();
catalog
.create_table(&Identifier::new("mydb", "t1"), schema, false)
.await
.unwrap();
// SELECT should be delegated to DataFusion
let df = sql_context
.sql("SELECT * FROM paimon.mydb.t1")
.await
.expect("SELECT should be delegated to DataFusion");
let batches = df.collect().await.expect("SELECT should execute");
// Empty table, but should succeed
let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
assert_eq!(total_rows, 0, "Empty table should return 0 rows");
}
// ======================= MULTI-CATALOG =======================
#[tokio::test]
async fn test_multi_catalog_register_and_query() {
let (_tmp1, catalog1) = create_test_env();
let (_tmp2, catalog2) = create_test_env();
let mut ctx = SQLContext::new();
ctx.register_catalog("cat1", catalog1).await.unwrap();
ctx.register_catalog("cat2", catalog2).await.unwrap();
ctx.sql("CREATE SCHEMA cat1.db1").await.unwrap();
ctx.sql("CREATE SCHEMA cat2.db2").await.unwrap();
ctx.sql("CREATE TABLE cat1.db1.t (id INT NOT NULL, name STRING, PRIMARY KEY (id))")
.await
.unwrap();
ctx.sql("CREATE TABLE cat2.db2.t (id INT NOT NULL, value STRING, PRIMARY KEY (id))")
.await
.unwrap();
ctx.sql("INSERT INTO cat1.db1.t VALUES (1, 'alice')")
.await
.unwrap()
.collect()
.await
.unwrap();
ctx.sql("INSERT INTO cat2.db2.t VALUES (2, 'hello')")
.await
.unwrap()
.collect()
.await
.unwrap();
let batches = ctx
.sql("SELECT id, name FROM cat1.db1.t")
.await
.unwrap()
.collect()
.await
.unwrap();
assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), 1);
let batches = ctx
.sql("SELECT id, value FROM cat2.db2.t")
.await
.unwrap()
.collect()
.await
.unwrap();
assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), 1);
}
#[tokio::test]
async fn test_set_current_catalog() {
let (_tmp1, catalog1) = create_test_env();
let (_tmp2, catalog2) = create_test_env();
let mut ctx = SQLContext::new();
ctx.register_catalog("cat1", catalog1).await.unwrap();
ctx.register_catalog("cat2", catalog2).await.unwrap();
ctx.sql("CREATE SCHEMA cat1.mydb").await.unwrap();
ctx.sql("CREATE TABLE cat1.mydb.t (id INT NOT NULL, PRIMARY KEY (id))")
.await
.unwrap();
ctx.set_current_catalog("cat1").await.unwrap();
ctx.set_current_database("mydb").await.unwrap();
// Unqualified query should resolve against cat1.mydb
let df = ctx.sql("SELECT * FROM t").await;
assert!(
df.is_ok(),
"Unqualified table should resolve via current catalog/database"
);
// Switching to unknown catalog should fail
let err = ctx.set_current_catalog("nonexistent").await;
assert!(err.is_err());
}
#[tokio::test]
async fn test_set_default_catalog_via_datafusion_config() {
let (_tmp1, catalog1) = create_test_env();
let (_tmp2, catalog2) = create_test_env();
let mut ctx = SQLContext::new();
ctx.register_catalog("cat1", catalog1).await.unwrap();
ctx.register_catalog("cat2", catalog2).await.unwrap();
// Create a table in cat2
ctx.sql("CREATE SCHEMA cat2.mydb").await.unwrap();
ctx.sql("CREATE TABLE cat2.mydb.t (id INT NOT NULL, name VARCHAR, PRIMARY KEY (id))")
.await
.unwrap();
ctx.sql("INSERT INTO cat2.mydb.t VALUES (1, 'hello')")
.await
.unwrap()
.collect()
.await
.unwrap();
// Switch default catalog via raw DataFusion SET instead of set_current_catalog()
ctx.sql("SET datafusion.catalog.default_catalog = 'cat2'")
.await
.unwrap();
ctx.set_current_database("mydb").await.unwrap();
// Unqualified query should now resolve against cat2.mydb
let batches = ctx
.sql("SELECT id, name FROM t")
.await
.expect("Unqualified table should resolve via DataFusion default_catalog config")
.collect()
.await
.unwrap();
let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
assert_eq!(total_rows, 1);
// DDL on unqualified name should also go to cat2.
// CREATE TABLE in mydb should succeed because cat2.mydb exists.
ctx.sql("CREATE TABLE mydb.t2 (id INT NOT NULL, PRIMARY KEY (id))")
.await
.expect("CREATE TABLE should resolve against cat2 after SET default_catalog");
// Verify the table was created in cat2 by querying with fully qualified name
let df = ctx.sql("SELECT * FROM cat2.mydb.t2").await;
assert!(
df.is_ok(),
"Table t2 should exist in cat2.mydb after unqualified CREATE TABLE"
);
}
#[tokio::test]
async fn test_first_registered_catalog_is_default() {
let (_tmp, catalog) = create_test_env();
let mut ctx = SQLContext::new();
ctx.register_catalog("paimon", catalog).await.unwrap();
ctx.sql("CREATE SCHEMA paimon.mydb").await.unwrap();
ctx.sql("CREATE TABLE paimon.mydb.t (id INT NOT NULL, PRIMARY KEY (id))")
.await
.unwrap();
ctx.set_current_database("mydb").await.unwrap();
// Should resolve to paimon.mydb.t without calling set_current_catalog
let df = ctx.sql("SELECT * FROM t").await;
assert!(
df.is_ok(),
"First registered catalog should be the default for unqualified queries"
);
}
#[tokio::test]
async fn test_one_part_table_name_uses_current_database() {
let (_tmp, catalog) = create_test_env();
let mut ctx = SQLContext::new();
ctx.register_catalog("paimon", catalog.clone())
.await
.unwrap();
catalog
.create_database("mydb", false, Default::default())
.await
.unwrap();
ctx.set_current_database("mydb").await.unwrap();
// 1-part name: "users" should resolve to paimon.mydb.users
ctx.sql(
"CREATE TABLE users (
id INT NOT NULL,
name STRING,
PRIMARY KEY (id)
)",
)
.await
.expect("CREATE TABLE with 1-part name should succeed");
let tables = catalog.list_tables("mydb").await.unwrap();
assert!(
tables.contains(&"users".to_string()),
"Table should be created in the current database"
);
// SELECT with 1-part name should also work
let df = ctx.sql("SELECT * FROM users").await;
assert!(
df.is_ok(),
"SELECT with 1-part name should resolve correctly"
);
}
// ======================= TEMP TABLE =======================
use datafusion::arrow::array::Int32Array;
use datafusion::arrow::datatypes::{DataType as ArrowDataType, Field as ArrowField};
#[tokio::test]
async fn test_register_temp_table_fully_qualified() {
let (_tmp, catalog) = create_test_env();
let ctx = create_sql_context(catalog.clone()).await;
let schema = Arc::new(Schema::new(vec![ArrowField::new(
"id",
ArrowDataType::Int32,
false,
)]));
let batch = RecordBatch::try_new(
schema.clone(),
vec![Arc::new(Int32Array::from(vec![1, 2, 3]))],
)
.unwrap();
// Fully qualified: catalog.database.table
ctx.register_temp_table("paimon.my_db.my_temp", schema, vec![batch])
.unwrap();
// Query the temp table via SQL
let batches = ctx
.sql("SELECT * FROM paimon.my_db.my_temp")
.await
.unwrap()
.collect()
.await
.unwrap();
let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
assert_eq!(total_rows, 3);
}
#[tokio::test]
async fn test_register_temp_table_database_qualified() {
let (_tmp, catalog) = create_test_env();
let ctx = create_sql_context(catalog.clone()).await;
let schema = Arc::new(Schema::new(vec![
ArrowField::new("id", ArrowDataType::Int32, false),
ArrowField::new("name", ArrowDataType::Utf8, true),
]));
let batch = RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(Int32Array::from(vec![1, 2, 3, 4])),
Arc::new(StringArray::from(vec![
Some("alice"),
Some("bob"),
Some("charlie"),
Some("dave"),
])),
],
)
.unwrap();
// Database-qualified: database.table (uses current catalog)
ctx.register_temp_table("my_db.users", schema, vec![batch])
.unwrap();
let batches = ctx
.sql("SELECT id, name FROM paimon.my_db.users WHERE id > 2")
.await
.unwrap()
.collect()
.await
.unwrap();
let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
assert_eq!(total_rows, 2);
}
#[tokio::test]
async fn test_register_temp_table_bare() {
let (_tmp, catalog) = create_test_env();
let ctx = create_sql_context(catalog.clone()).await;
// Create a database and set it as current database
ctx.sql("CREATE DATABASE paimon.my_db").await.unwrap();
ctx.set_current_database("my_db").await.unwrap();
let schema = Arc::new(Schema::new(vec![ArrowField::new(
"id",
ArrowDataType::Int32,
false,
)]));
let batch = RecordBatch::try_new(
schema.clone(),
vec![Arc::new(Int32Array::from(vec![1, 2, 3]))],
)
.unwrap();
// Bare: just table name (uses current catalog + current database)
ctx.register_temp_table("my_temp", schema, vec![batch])
.unwrap();
// Query via paimon.my_db.my_temp
let batches = ctx
.sql("SELECT * FROM paimon.my_db.my_temp")
.await
.unwrap()
.collect()
.await
.unwrap();
let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
assert_eq!(total_rows, 3);
}
#[tokio::test]
async fn test_register_temp_table_unknown_catalog() {
let (_tmp, catalog) = create_test_env();
let ctx = create_sql_context(catalog.clone()).await;
let schema = Arc::new(Schema::new(vec![ArrowField::new(
"id",
ArrowDataType::Int32,
false,
)]));
let result = ctx.register_temp_table("nonexistent.my_db.t", schema, vec![]);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("Unknown catalog"));
}
#[tokio::test]
async fn test_deregister_temp_table() {
let (_tmp, catalog) = create_test_env();
let ctx = create_sql_context(catalog.clone()).await;
let schema = Arc::new(Schema::new(vec![ArrowField::new(
"id",
ArrowDataType::Int32,
false,
)]));
let batch =
RecordBatch::try_new(schema.clone(), vec![Arc::new(Int32Array::from(vec![1, 2]))]).unwrap();
ctx.register_temp_table("paimon.my_db.my_temp", schema.clone(), vec![batch])
.unwrap();
// Deregister with flexible name
ctx.deregister_temp_table("paimon.my_db.my_temp").unwrap();
// Query should fail
let result = ctx.sql("SELECT * FROM paimon.my_db.my_temp").await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_multiple_temp_tables_in_same_database() {
let (_tmp, catalog) = create_test_env();
let ctx = create_sql_context(catalog.clone()).await;
let schema1 = Arc::new(Schema::new(vec![ArrowField::new(
"id",
ArrowDataType::Int32,
false,
)]));
let batch1 = RecordBatch::try_new(
schema1.clone(),
vec![Arc::new(Int32Array::from(vec![1, 2]))],
)
.unwrap();
let schema2 = Arc::new(Schema::new(vec![ArrowField::new(
"value",
ArrowDataType::Int32,
false,
)]));
let batch2 = RecordBatch::try_new(
schema2.clone(),
vec![Arc::new(Int32Array::from(vec![10, 20, 30]))],
)
.unwrap();
ctx.register_temp_table("my_db.t1", schema1, vec![batch1])
.unwrap();
ctx.register_temp_table("my_db.t2", schema2, vec![batch2])
.unwrap();
// Both should be queryable
let rows1 = ctx
.sql("SELECT * FROM paimon.my_db.t1")
.await
.unwrap()
.collect()
.await
.unwrap()
.iter()
.map(|b| b.num_rows())
.sum::<usize>();
assert_eq!(rows1, 2);
let rows2 = ctx
.sql("SELECT * FROM paimon.my_db.t2")
.await
.unwrap()
.collect()
.await
.unwrap()
.iter()
.map(|b| b.num_rows())
.sum::<usize>();
assert_eq!(rows2, 3);
}
use datafusion::arrow::array::StringArray;
use datafusion::arrow::datatypes::Schema;
use datafusion::arrow::record_batch::RecordBatch;