ursa_engine/
file_table.rs1use std::sync::{Arc, OnceLock, Weak};
14
15use async_trait::async_trait;
16use datafusion::catalog::{TableProvider, UrlTableFactory};
17use datafusion::datasource::listing::{ListingTable, ListingTableConfig, ListingTableConfigExt};
18use datafusion::error::{DataFusionError, Result};
19use datafusion::execution::context::SessionState;
20use parking_lot::RwLock;
21
22use crate::error::{matched, user_message};
23use crate::paths::{looks_like_location, table_url};
24use crate::stores::ensure_object_store;
25
26#[derive(Debug, Default)]
28pub(crate) struct FileTableFactory {
29 session: OnceLock<Weak<RwLock<SessionState>>>,
31}
32
33impl FileTableFactory {
34 pub(crate) fn attach(&self, session: Weak<RwLock<SessionState>>) {
35 let _ = self.session.set(session);
36 }
37}
38
39#[async_trait]
40impl UrlTableFactory for FileTableFactory {
41 async fn try_new(&self, name: &str) -> Result<Option<Arc<dyn TableProvider>>> {
42 if !looks_like_location(name) {
43 return Ok(None);
44 }
45 let state = self
46 .session
47 .get()
48 .and_then(Weak::upgrade)
49 .map(|state| state.read().clone())
50 .ok_or_else(|| DataFusionError::Internal("the session has been dropped".into()))?;
51 open_file_table(&state, name).await.map(Some)
52 }
53}
54
55pub async fn open_file_table(
62 state: &SessionState,
63 location: &str,
64) -> Result<Arc<dyn TableProvider>> {
65 let url = table_url(location)?;
66 ensure_object_store(state.runtime_env(), url.get_url()).await?;
67 let config = ListingTableConfig::new(url)
68 .infer_options(state)
69 .await
70 .map_err(|e| explain(location, &e))?
71 .infer_partitions_from_path(state)
72 .await
73 .map_err(|e| explain(location, &e))?
74 .infer_schema(state)
75 .await
76 .map_err(|e| explain(location, &e))?;
77 Ok(Arc::new(ListingTable::try_new(config)?))
78}
79
80fn explain(location: &str, err: &DataFusionError) -> DataFusionError {
82 let message = user_message(err);
83 if message.contains(matched::NO_FILES_FOR_TABLE) {
84 DataFusionError::Plan(format!("no file found at '{location}'"))
85 } else if message.contains(matched::NO_FILE_FORMAT_FOR_EXTENSION) {
86 DataFusionError::Plan(format!(
87 "cannot tell the format of '{location}' from its extension; Ursa reads .parquet, \
88 .csv, .json and .arrow files, and compressed CSV and JSON (.gz, .bz2, .xz, .zst)"
89 ))
90 } else {
91 DataFusionError::Plan(format!("cannot read '{location}': {message}"))
92 }
93}