@@ -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:: {
@@ -423,15 +426,29 @@ impl PySessionContext {
423426 }
424427
425428 pub fn enable_url_table ( & self ) -> PyResult < Self > {
426- // Pre-existing caveat, unrelated to query planners: this is the one
427- // method that mints a second `Arc<SessionContext>` for a session, and
428- // it also forks the session's state while keeping its id. Any weak
429- // `FFI_TaskContextProvider` handed out by the receiver stays bound to
430- // the receiver, so the returned context must not outlive it. See
431- // `set_session_query_planner` for why everything else mutates in place.
432- // Tracked as a bug in <https://github.com/apache/datafusion-python/issues/1708>.
429+ let state_ref = self . ctx . state_ref ( ) ;
430+ {
431+ // Check and replace under one lock so concurrent calls cannot nest
432+ // wrappers or overwrite a newer catalog list.
433+ let mut state = state_ref. write ( ) ;
434+ if !state. catalog_list ( ) . is :: < DynamicFileCatalog > ( ) {
435+ let factory = Arc :: new ( DynamicListTableFactory :: default ( ) ) ;
436+ // Bind before publishing the catalog: a reader must never see
437+ // a factory whose session store has not been initialized.
438+ factory
439+ . session_store ( )
440+ . with_state ( self . ctx . state_weak_ref ( ) ) ;
441+ let catalog_list = Arc :: new ( DynamicFileCatalog :: new (
442+ Arc :: clone ( state. catalog_list ( ) ) ,
443+ factory as Arc < dyn UrlTableFactory > ,
444+ ) ) ;
445+ // Only the catalog changes. In particular, preserve the state
446+ // and context allocations targeted by weak FFI providers.
447+ state. register_catalog_list ( catalog_list) ;
448+ }
449+ }
433450 Ok ( PySessionContext {
434- ctx : Arc :: new ( self . ctx . as_ref ( ) . clone ( ) . enable_url_table ( ) ) ,
451+ ctx : Arc :: clone ( & self . ctx ) ,
435452 logical_codec : Arc :: clone ( & self . logical_codec ) ,
436453 physical_codec : Arc :: clone ( & self . physical_codec ) ,
437454 } )
@@ -1434,14 +1451,13 @@ impl PySessionContext {
14341451 /// time, and a payload written through one would resolve to the other on
14351452 /// decode. See [`SESSION_CODEC_ID_PREFIX`].
14361453 ///
1437- /// Handles derived from one session — `with_python_udf_inlining`,
1438- /// `with_logical_extension_codec`, [`Self::_install_extension_codecs`] —
1439- /// report the same id even though their codec chains differ, so installing
1440- /// two of them on one target is refused. That is the intended answer: they
1441- /// share a `state_ref`, so their payloads would resolve against the same
1442- /// session and are indistinguishable on decode. Every derivation shares the
1443- /// session for exactly this reason; `enable_url_table` is the one that does
1444- /// not, and it is tracked as a bug.
1454+ /// Handles derived from one session — `enable_url_table`,
1455+ /// `with_python_udf_inlining`, `with_logical_extension_codec`,
1456+ /// [`Self::_install_extension_codecs`] — report the same id even though
1457+ /// their codec chains may differ, so installing two of them on one target
1458+ /// is refused. That is the intended answer: they share a `state_ref`, so
1459+ /// their payloads would resolve against the same session and are
1460+ /// indistinguishable on decode.
14451461 #[ getter]
14461462 pub fn __datafusion_codec_id__ ( & self ) -> String {
14471463 format ! ( "{SESSION_CODEC_ID_PREFIX}{}" , self . ctx. session_id( ) )
0 commit comments