use std::any::Any;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
use std::sync::RwLock;
use async_trait::async_trait;
use datafusion::catalog::{CatalogProvider, MemorySchemaProvider, SchemaProvider};
use datafusion::common::plan_datafusion_err;
use datafusion::datasource::TableProvider;
use datafusion::error::Result as DFResult;
use paimon::catalog::{Catalog, Identifier};
use crate::error::to_datafusion_error;
use crate::runtime::{await_with_runtime, block_on_with_runtime};
use crate::system_tables;
use crate::table::PaimonTableProvider;
use crate::DynamicOptions;
pub struct PaimonCatalogProvider {
catalog: Arc<dyn Catalog>,
dynamic_options: DynamicOptions,
temp_tables: Arc<RwLock<HashMap<String, Arc<MemorySchemaProvider>>>>,
}
impl Debug for PaimonCatalogProvider {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PaimonCatalogProvider").finish()
}
}
impl PaimonCatalogProvider {
pub fn new(catalog: Arc<dyn Catalog>) -> Self {
PaimonCatalogProvider {
catalog,
dynamic_options: Default::default(),
temp_tables: Arc::new(RwLock::new(HashMap::new())),
}
}
pub(crate) fn with_dynamic_options(
catalog: Arc<dyn Catalog>,
dynamic_options: DynamicOptions,
) -> Self {
PaimonCatalogProvider {
catalog,
dynamic_options,
temp_tables: Arc::new(RwLock::new(HashMap::new())),
}
}
}
impl CatalogProvider for PaimonCatalogProvider {
fn as_any(&self) -> &dyn Any {
self
}
fn schema_names(&self) -> Vec<String> {
let catalog = Arc::clone(&self.catalog);
block_on_with_runtime(
async move {
catalog.list_databases().await.unwrap_or_else(|e| {
log::error!("failed to list databases: {e}");
vec![]
})
},
"paimon catalog access thread panicked",
)
}
fn schema(&self, name: &str) -> Option<Arc<dyn SchemaProvider>> {
let catalog = Arc::clone(&self.catalog);
let dynamic_options = Arc::clone(&self.dynamic_options);
let name = name.to_string();
let temp_provider = {
let databases = self.temp_tables.read().unwrap_or_else(|e| e.into_inner());
databases.get(&name).cloned()
};
block_on_with_runtime(
async move {
match catalog.get_database(&name).await {
Ok(_) => Some(Arc::new(PaimonSchemaProvider::new(
Arc::clone(&catalog),
name,
dynamic_options,
temp_provider,
)) as Arc<dyn SchemaProvider>),
Err(paimon::Error::DatabaseNotExist { .. }) => {
if temp_provider.is_some() {
Some(Arc::new(PaimonSchemaProvider::new(
Arc::clone(&catalog),
name,
dynamic_options,
temp_provider,
)) as Arc<dyn SchemaProvider>)
} else {
None
}
}
Err(e) => {
log::error!("failed to get database '{}': {e}", name);
None
}
}
},
"paimon catalog access thread panicked",
)
}
fn register_schema(
&self,
name: &str,
_schema: Arc<dyn SchemaProvider>,
) -> DFResult<Option<Arc<dyn SchemaProvider>>> {
let catalog = Arc::clone(&self.catalog);
let dynamic_options = Arc::clone(&self.dynamic_options);
let name = name.to_string();
block_on_with_runtime(
async move {
catalog
.create_database(&name, false, HashMap::new())
.await
.map_err(to_datafusion_error)?;
Ok(Some(Arc::new(PaimonSchemaProvider::new(
Arc::clone(&catalog),
name,
dynamic_options,
None,
)) as Arc<dyn SchemaProvider>))
},
"paimon catalog access thread panicked",
)
}
fn deregister_schema(
&self,
name: &str,
cascade: bool,
) -> DFResult<Option<Arc<dyn SchemaProvider>>> {
let catalog = Arc::clone(&self.catalog);
let dynamic_options = Arc::clone(&self.dynamic_options);
let name = name.to_string();
block_on_with_runtime(
async move {
catalog
.drop_database(&name, false, cascade)
.await
.map_err(to_datafusion_error)?;
Ok(Some(Arc::new(PaimonSchemaProvider::new(
Arc::clone(&catalog),
name,
dynamic_options,
None,
)) as Arc<dyn SchemaProvider>))
},
"paimon catalog access thread panicked",
)
}
}
impl PaimonCatalogProvider {
pub fn register_temp_table(
&self,
database: &str,
table_name: &str,
table: Arc<dyn TableProvider>,
) -> DFResult<()> {
let catalog = Arc::clone(&self.catalog);
let db = database.to_string();
let tbl = table_name.to_string();
let identifier = Identifier::new(db, tbl);
if let Ok(true) = block_on_with_runtime(
async move {
match catalog.get_table(&identifier).await {
Ok(_) => Ok::<bool, paimon::Error>(true),
Err(paimon::Error::TableNotExist { .. }) => Ok(false),
Err(_) => Ok(false),
}
},
"paimon catalog access thread panicked",
) {
log::warn!(
"Temporary table '{database}.{table_name}' shadows an existing Paimon table"
);
}
let mut databases = self.temp_tables.write().unwrap_or_else(|e| e.into_inner());
let mem_database = databases
.entry(database.to_string())
.or_insert_with(|| Arc::new(MemorySchemaProvider::new()));
let old = mem_database.register_table(table_name.to_string(), table)?;
if old.is_some() {
return Err(plan_datafusion_err!(
"Temporary table '{database}.{table_name}' already exists"
));
}
Ok(())
}
pub fn deregister_temp_table(
&self,
database: &str,
table_name: &str,
) -> DFResult<Option<Arc<dyn TableProvider>>> {
let databases = self.temp_tables.read().unwrap_or_else(|e| e.into_inner());
let mem_database = databases
.get(database)
.ok_or_else(|| plan_datafusion_err!("Unknown temp database '{database}'"))?;
mem_database.deregister_table(table_name)
}
pub fn has_temp_table_database(&self, name: &str) -> bool {
self.temp_tables
.read()
.unwrap_or_else(|e| e.into_inner())
.contains_key(name)
}
pub fn temp_table_exist(&self, database: &str, table_name: &str) -> bool {
let databases = self.temp_tables.read().unwrap_or_else(|e| e.into_inner());
databases
.get(database)
.is_some_and(|db| db.table_exist(table_name))
}
}
pub struct PaimonSchemaProvider {
catalog: Arc<dyn Catalog>,
database: String,
dynamic_options: DynamicOptions,
temp_provider: Option<Arc<MemorySchemaProvider>>,
}
impl Debug for PaimonSchemaProvider {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PaimonSchemaProvider")
.field("database", &self.database)
.field("has_temp_provider", &self.temp_provider.is_some())
.finish()
}
}
impl PaimonSchemaProvider {
pub fn new(
catalog: Arc<dyn Catalog>,
database: String,
dynamic_options: DynamicOptions,
temp_provider: Option<Arc<MemorySchemaProvider>>,
) -> Self {
PaimonSchemaProvider {
catalog,
database,
dynamic_options,
temp_provider,
}
}
}
#[async_trait]
impl SchemaProvider for PaimonSchemaProvider {
fn as_any(&self) -> &dyn Any {
self
}
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 db = database.clone();
async move {
match catalog.list_tables(&db).await {
Ok(names) => names,
Err(e) => {
log::error!("failed to list tables in '{}': {e}", db);
vec![]
}
}
}
},
"paimon catalog access thread panicked",
);
if let Some(temp) = &self.temp_provider {
names.extend(temp.table_names());
}
let mut seen = std::collections::HashSet::new();
names.retain(|name| seen.insert(name.clone()));
names
}
async fn table(&self, name: &str) -> DFResult<Option<Arc<dyn TableProvider>>> {
if let Some(temp) = &self.temp_provider {
if let Some(table) = temp.table(name).await? {
return Ok(Some(table));
}
}
let (base, system_name) = system_tables::split_object_name(name);
if let Some(system_name) = system_name {
return await_with_runtime(system_tables::load(
Arc::clone(&self.catalog),
self.database.clone(),
base.to_string(),
system_name.to_string(),
))
.await;
}
let catalog = Arc::clone(&self.catalog);
let dynamic_options = Arc::clone(&self.dynamic_options);
let identifier = Identifier::new(self.database.clone(), base);
await_with_runtime(async move {
match catalog.get_table(&identifier).await {
Ok(table) => {
let opts = dynamic_options.read().unwrap().clone();
let table = if opts.is_empty() {
table
} else {
table.copy_with_options(opts)
};
let provider = PaimonTableProvider::try_new(table)?;
Ok(Some(Arc::new(provider) as Arc<dyn TableProvider>))
}
Err(paimon::Error::TableNotExist { .. }) => Ok(None),
Err(e) => Err(to_datafusion_error(e)),
}
})
.await
}
fn table_exist(&self, name: &str) -> bool {
if let Some(temp) = &self.temp_provider {
if temp.table_exist(name) {
return true;
}
}
let (base, system_name) = system_tables::split_object_name(name);
if let Some(system_name) = system_name {
if !system_tables::is_registered(system_name) {
return false;
}
}
let catalog = Arc::clone(&self.catalog);
let identifier = Identifier::new(self.database.clone(), base.to_string());
block_on_with_runtime(
async move {
match catalog.get_table(&identifier).await {
Ok(_) => true,
Err(paimon::Error::TableNotExist { .. }) => false,
Err(e) => {
log::error!("failed to check table '{}': {e}", identifier);
false
}
}
},
"paimon catalog access thread panicked",
)
}
fn register_table(
&self,
_name: String,
table: Arc<dyn TableProvider>,
) -> DFResult<Option<Arc<dyn TableProvider>>> {
Ok(Some(table))
}
fn deregister_table(&self, name: &str) -> DFResult<Option<Arc<dyn TableProvider>>> {
let catalog = Arc::clone(&self.catalog);
let identifier = Identifier::new(self.database.clone(), name);
block_on_with_runtime(
async move {
let table = match catalog.get_table(&identifier).await {
Ok(t) => t,
Err(paimon::Error::TableNotExist { .. }) => return Ok(None),
Err(e) => return Err(to_datafusion_error(e)),
};
let provider = PaimonTableProvider::try_new(table)?;
catalog
.drop_table(&identifier, false)
.await
.map_err(to_datafusion_error)?;
Ok(Some(Arc::new(provider) as Arc<dyn TableProvider>))
},
"paimon catalog access thread panicked",
)
}
}