@@ -286,6 +286,45 @@ def __datafusion_session_extension__(
286286 assert batches [0 ].column (0 ).to_pylist () == [0 , 1 , 2 , 3 , 4 , 5 ]
287287
288288
289+ def test_with_extensions_rebinds_existing_planner ():
290+ """Codec-only bundles installed on a context that already has an FFI
291+ planner rebind that planner to the new codec chains."""
292+ config = SessionConfig ().with_extension (PlannerConfig (max_rows = 2 ))
293+ planner = MyQueryPlanner ()
294+ ctx = SessionContext (config ).with_query_planner (planner )
295+ provider_ext = ProviderCodecsExtension ()
296+ ctx = ctx .with_extensions (provider_ext )
297+ ctx .register_table ("numbers" , MyTableProvider (1 , 6 , 1 ))
298+
299+ batches = ctx .sql ('SELECT "A" FROM numbers ORDER BY "A"' ).collect ()
300+ assert batches [0 ].column (0 ).to_pylist () == [0 , 1 ]
301+ assert planner .last_max_rows () == 2
302+ # The planner only sees these codecs if it was rebound to the chains
303+ # built during with_extensions.
304+ assert provider_ext .logical_codec .table_provider_decode_calls () > 0
305+ assert provider_ext .physical_codec .execution_plan_decode_calls () > 0
306+
307+
308+ def test_with_extensions_codec_precedence ():
309+ """Extensions are processed left to right and prepend to the codec
310+ chain, so codecs from later extensions are consulted first. Both
311+ bundles' codecs can handle the payload (they share the provider
312+ library's token registry); only the one consulted first is used."""
313+ config = SessionConfig ().with_extension (PlannerConfig (max_rows = 2 ))
314+ ext_a = ProviderCodecsExtension ()
315+ ext_b = ProviderCodecsExtension ()
316+ ctx = SessionContext (config ).with_extensions (ext_a , ext_b , MyPlannerExtension ())
317+ ctx .register_table ("numbers" , MyTableProvider (1 , 6 , 1 ))
318+
319+ batches = ctx .sql ('SELECT "A" FROM numbers ORDER BY "A"' ).collect ()
320+ assert batches [0 ].column (0 ).to_pylist () == [0 , 1 ]
321+
322+ assert ext_b .logical_codec .table_provider_encode_calls () > 0
323+ assert ext_b .logical_codec .table_provider_decode_calls () > 0
324+ assert ext_a .logical_codec .table_provider_encode_calls () == 0
325+ assert ext_a .logical_codec .table_provider_decode_calls () == 0
326+
327+
289328def test_dataframe_outliving_context_fails_cleanly ():
290329 """A DataFrame does not keep its SessionContext alive. FFI components
291330 resolve the task context through a weak reference, so using the
0 commit comments