Skip to content

Commit 08a8dc0

Browse files
authored
fix: mangled errors (#1377)
closes #1226
1 parent 95b4a00 commit 08a8dc0

File tree

5 files changed

+55
-5
lines changed

5 files changed

+55
-5
lines changed

python/tests/test_catalog.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ def table_exist(self, name: str) -> bool:
8181
return name in self.tables
8282

8383

84+
class CustomErrorSchemaProvider(CustomSchemaProvider):
85+
def table(self, name: str) -> Table | None:
86+
message = f"{name} is not an acceptable name"
87+
raise ValueError(message)
88+
89+
8490
class CustomCatalogProvider(dfn.catalog.CatalogProvider):
8591
def __init__(self):
8692
self.schemas = {"my_schema": CustomSchemaProvider()}
@@ -219,6 +225,33 @@ def test_schema_register_table_with_pyarrow_dataset(ctx: SessionContext):
219225
schema.deregister_table(table_name)
220226

221227

228+
def test_exception_not_mangled(ctx: SessionContext):
229+
"""Test registering all python providers and running a query against them."""
230+
231+
catalog_name = "custom_catalog"
232+
schema_name = "custom_schema"
233+
234+
ctx.register_catalog_provider(catalog_name, CustomCatalogProvider())
235+
236+
catalog = ctx.catalog(catalog_name)
237+
238+
# Clean out previous schemas if they exist so we can start clean
239+
for schema_name in catalog.schema_names():
240+
catalog.deregister_schema(schema_name, cascade=False)
241+
242+
catalog.register_schema(schema_name, CustomErrorSchemaProvider())
243+
244+
schema = catalog.schema(schema_name)
245+
246+
for table_name in schema.table_names():
247+
schema.deregister_table(table_name)
248+
249+
schema.register_table("test_table", create_dataset())
250+
251+
with pytest.raises(ValueError, match="^test_table is not an acceptable name$"):
252+
ctx.sql(f"select * from {catalog_name}.{schema_name}.test_table")
253+
254+
222255
def test_in_end_to_end_python_providers(ctx: SessionContext):
223256
"""Test registering all python providers and running a query against them."""
224257

python/tests/test_sql.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929

3030

3131
def test_no_table(ctx):
32-
with pytest.raises(Exception, match="DataFusion error"):
32+
with pytest.raises(
33+
ValueError,
34+
match="^Error during planning: table 'datafusion.public.b' not found$",
35+
):
3336
ctx.sql("SELECT a FROM b").collect()
3437

3538

src/catalog.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,8 @@ impl SchemaProvider for RustWrappedPySchemaProvider {
364364
&self,
365365
name: &str,
366366
) -> datafusion::common::Result<Option<Arc<dyn TableProvider>>, DataFusionError> {
367-
self.table_inner(name).map_err(to_datafusion_err)
367+
self.table_inner(name)
368+
.map_err(|e| DataFusionError::External(Box::new(e)))
368369
}
369370

370371
fn register_table(

src/context.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ use crate::catalog::{
6565
use crate::common::data_type::PyScalarValue;
6666
use crate::dataframe::PyDataFrame;
6767
use crate::dataset::Dataset;
68-
use crate::errors::{py_datafusion_err, PyDataFusionError, PyDataFusionResult};
68+
use crate::errors::{
69+
from_datafusion_error, py_datafusion_err, PyDataFusionError, PyDataFusionResult,
70+
};
6971
use crate::expr::sort_expr::PySortExpr;
7072
use crate::options::PyCsvReadOptions;
7173
use crate::physical_plan::PyExecutionPlan;
@@ -465,7 +467,8 @@ impl PySessionContext {
465467

466468
let mut df = wait_for_future(py, async {
467469
self.ctx.sql_with_options(&query, options).await
468-
})??;
470+
})?
471+
.map_err(from_datafusion_error)?;
469472

470473
if !param_values.is_empty() {
471474
df = df.with_param_values(param_values)?;

src/errors.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use std::fmt::Debug;
2222
use datafusion::arrow::error::ArrowError;
2323
use datafusion::error::DataFusionError as InnerDataFusionError;
2424
use prost::EncodeError;
25-
use pyo3::exceptions::PyException;
25+
use pyo3::exceptions::{PyException, PyValueError};
2626
use pyo3::PyErr;
2727

2828
pub type PyDataFusionResult<T> = std::result::Result<T, PyDataFusionError>;
@@ -96,3 +96,13 @@ pub fn py_unsupported_variant_err(e: impl Debug) -> PyErr {
9696
pub fn to_datafusion_err(e: impl Debug) -> InnerDataFusionError {
9797
InnerDataFusionError::Execution(format!("{e:?}"))
9898
}
99+
100+
pub fn from_datafusion_error(err: InnerDataFusionError) -> PyErr {
101+
match err {
102+
InnerDataFusionError::External(boxed) => match boxed.downcast::<PyErr>() {
103+
Ok(py_err) => *py_err,
104+
Err(original_boxed) => PyValueError::new_err(format!("{original_boxed}")),
105+
},
106+
_ => PyValueError::new_err(format!("{err}")),
107+
}
108+
}

0 commit comments

Comments
 (0)