@@ -27,8 +27,11 @@ use arrow::pyarrow::FromPyArrow;
2727use datafusion:: arrow:: datatypes:: { DataType , Schema , SchemaRef } ;
2828use datafusion:: arrow:: pyarrow:: PyArrowType ;
2929use datafusion:: arrow:: record_batch:: RecordBatch ;
30- use datafusion:: catalog:: { CatalogProvider , CatalogProviderList , TableProviderFactory } ;
30+ use datafusion:: catalog:: {
31+ CatalogProvider , CatalogProviderList , DynamicFileCatalog , TableProviderFactory , UrlTableFactory ,
32+ } ;
3133use datafusion:: common:: { DFSchema , ScalarValue , TableReference , exec_err} ;
34+ use datafusion:: datasource:: dynamic_file:: DynamicListTableFactory ;
3235use datafusion:: datasource:: file_format:: file_compression_type:: FileCompressionType ;
3336use datafusion:: datasource:: file_format:: parquet:: ParquetFormat ;
3437use datafusion:: datasource:: listing:: {
@@ -65,6 +68,7 @@ use datafusion_python_util::{
6568 ffi_query_planner_from_pycapsule, get_global_ctx, get_tokio_runtime,
6669 physical_optimizer_rule_from_pycapsule, spawn_future, validate_pycapsule, wait_for_future,
6770} ;
71+ use datafusion_session:: SessionStore ;
6872use object_store:: ObjectStore ;
6973use pyo3:: IntoPyObjectExt ;
7074use pyo3:: exceptions:: { PyKeyError , PyRuntimeError , PyTypeError , PyValueError } ;
@@ -452,15 +456,30 @@ impl PySessionContext {
452456 }
453457
454458 pub fn enable_url_table ( & self ) -> PyResult < Self > {
455- // Pre-existing caveat, unrelated to query planners: this is the one
456- // method that mints a second `Arc<SessionContext>` for a session, and
457- // it also forks the session's state while keeping its id. Any weak
458- // `FFI_TaskContextProvider` handed out by the receiver stays bound to
459- // the receiver, so the returned context must not outlive it. See
460- // `set_session_query_planner` for why everything else mutates in place.
461- // Tracked as a bug in <https://github.com/apache/datafusion-python/issues/1708>.
459+ let state_ref = self . ctx . state_ref ( ) ;
460+ let mut guard = state_ref. write ( ) ;
461+ if guard
462+ . catalog_list ( )
463+ . downcast_ref :: < DynamicFileCatalog > ( )
464+ . is_none ( )
465+ {
466+ let factory = Arc :: new ( DynamicListTableFactory :: new ( SessionStore :: new ( ) ) ) ;
467+ let catalog_list = Arc :: new ( DynamicFileCatalog :: new (
468+ Arc :: clone ( guard. catalog_list ( ) ) ,
469+ Arc :: clone ( & factory) as Arc < dyn UrlTableFactory > ,
470+ ) ) ;
471+ factory
472+ . session_store ( )
473+ . with_state ( self . ctx . state_weak_ref ( ) ) ;
474+
475+ * guard = SessionStateBuilder :: new_from_existing ( guard. clone ( ) )
476+ . with_session_id ( guard. session_id ( ) . to_string ( ) )
477+ . with_catalog_list ( catalog_list)
478+ . build ( ) ;
479+ }
480+
462481 Ok ( PySessionContext {
463- ctx : Arc :: new ( self . ctx . as_ref ( ) . clone ( ) . enable_url_table ( ) ) ,
482+ ctx : Arc :: clone ( & self . ctx ) ,
464483 logical_codec : Arc :: clone ( & self . logical_codec ) ,
465484 physical_codec : Arc :: clone ( & self . physical_codec ) ,
466485 } )
@@ -1471,14 +1490,12 @@ impl PySessionContext {
14711490 /// time, and a payload written through one would resolve to the other on
14721491 /// decode. See [`SESSION_CODEC_ID_PREFIX`].
14731492 ///
1474- /// Handles derived from one session — `with_python_udf_inlining`,
1475- /// `with_logical_extension_codec`, [`Self::_install_extension_codecs`] —
1476- /// report the same id even though their codec chains differ, so installing
1477- /// two of them on one target is refused. That is the intended answer: they
1478- /// share a `state_ref`, so their payloads would resolve against the same
1479- /// session and are indistinguishable on decode. Every derivation shares the
1480- /// session for exactly this reason; `enable_url_table` is the one that does
1481- /// not, and it is tracked as a bug.
1493+ /// Handles derived from one session — including `with_python_udf_inlining`,
1494+ /// `with_logical_extension_codec`, [`Self::_install_extension_codecs`], and
1495+ /// `enable_url_table` — report the same id even though their codec chains
1496+ /// differ, so installing two of them on one target is refused. That is the
1497+ /// intended answer: they share a `state_ref`, so their payloads would
1498+ /// resolve against the same session and are indistinguishable on decode.
14821499 #[ getter]
14831500 pub fn __datafusion_codec_id__ ( & self ) -> String {
14841501 format ! ( "{SESSION_CODEC_ID_PREFIX}{}" , self . ctx. session_id( ) )
0 commit comments