@@ -27,7 +27,7 @@ use datafusion::{
2727} ;
2828use datafusion_ffi:: table_provider:: { FFI_TableProvider , ForeignTableProvider } ;
2929use pyo3:: prelude:: * ;
30- use pyo3:: { exceptions:: PyValueError , types:: PyCapsule } ;
30+ use pyo3:: { exceptions:: PyValueError , ffi , types:: PyCapsule } ;
3131use std:: {
3232 future:: Future ,
3333 sync:: { Arc , OnceLock } ,
@@ -124,18 +124,73 @@ pub(crate) fn validate_pycapsule(capsule: &Bound<PyCapsule>, name: &str) -> PyRe
124124 Ok ( ( ) )
125125}
126126
127+ fn ensure_capsule_has_destructor ( capsule : & Bound < PyCapsule > ) -> PyResult < ( ) > {
128+ if unsafe { ffi:: PyCapsule_GetDestructor ( capsule. as_ptr ( ) ) } . is_none ( ) {
129+ return Err ( PyValueError :: new_err (
130+ "Table provider capsule is missing a destructor; ensure it was created via datafusion_ffi's helpers." ,
131+ ) ) ;
132+ }
133+
134+ Ok ( ( ) )
135+ }
136+
137+ fn ensure_capsule_pointer ( capsule : & Bound < PyCapsule > ) -> PyResult < ( ) > {
138+ let ptr = capsule. pointer ( ) ;
139+ if ptr. is_null ( ) {
140+ return Err ( PyValueError :: new_err (
141+ "Table provider capsule contained a null pointer." ,
142+ ) ) ;
143+ }
144+
145+ if ( ptr as usize ) % std:: mem:: align_of :: < FFI_TableProvider > ( ) != 0 {
146+ return Err ( PyValueError :: new_err (
147+ "Table provider capsule pointer was not aligned for FFI_TableProvider." ,
148+ ) ) ;
149+ }
150+
151+ Ok ( ( ) )
152+ }
153+
154+ fn validate_foreign_table_provider ( provider : & FFI_TableProvider ) -> PyResult < ( ) > {
155+ if provider. schema as usize == 0
156+ || provider. scan as usize == 0
157+ || provider. table_type as usize == 0
158+ || provider. clone as usize == 0
159+ || provider. release as usize == 0
160+ || provider. version as usize == 0
161+ || provider. private_data . is_null ( )
162+ {
163+ return Err ( PyValueError :: new_err (
164+ "Table provider capsule is missing required function pointers." ,
165+ ) ) ;
166+ }
167+
168+ Ok ( ( ) )
169+ }
170+
171+ pub ( crate ) fn table_provider_from_capsule (
172+ capsule : & Bound < PyCapsule > ,
173+ ) -> PyResult < Arc < dyn TableProvider > > {
174+ validate_pycapsule ( capsule, "datafusion_table_provider" ) ?;
175+ ensure_capsule_has_destructor ( capsule) ?;
176+ ensure_capsule_pointer ( capsule) ?;
177+
178+ let provider = unsafe { capsule. reference :: < FFI_TableProvider > ( ) } ;
179+ validate_foreign_table_provider ( provider) ?;
180+ let provider: ForeignTableProvider = provider. into ( ) ;
181+
182+ Ok ( Arc :: new ( provider) )
183+ }
184+
127185pub ( crate ) fn table_provider_from_pycapsule (
128186 obj : & Bound < PyAny > ,
129187) -> PyResult < Option < Arc < dyn TableProvider > > > {
130188 if obj. hasattr ( "__datafusion_table_provider__" ) ? {
131189 let capsule = obj. getattr ( "__datafusion_table_provider__" ) ?. call0 ( ) ?;
132190 let capsule = capsule. downcast :: < PyCapsule > ( ) . map_err ( py_datafusion_err) ?;
133- validate_pycapsule ( capsule, "datafusion_table_provider" ) ?;
134-
135- let provider = unsafe { capsule. reference :: < FFI_TableProvider > ( ) } ;
136- let provider: ForeignTableProvider = provider. into ( ) ;
191+ let provider = table_provider_from_capsule ( & capsule) ?;
137192
138- Ok ( Some ( Arc :: new ( provider) ) )
193+ Ok ( Some ( provider) )
139194 } else {
140195 Ok ( None )
141196 }
0 commit comments