Is your feature request related to a problem or challenge?
This is a follow on to #24028
datafusion-ffi passes logical extension codecs, physical extension codecs, and task context providers across library boundaries. These values describe one serialization environment and must remain consistent, but the current API carries them separately.
FFI_LogicalExtensionCodec and FFI_PhysicalExtensionCodec each contain an FFI_TaskContextProvider. Several FFI wrappers carry only an FFI_LogicalExtensionCodec because they historically needed to serialize only logical expressions and plans. Now that FFI_SessionRef exposes Session::query_planner, those wrappers can also initiate physical-plan serialization through a session callback.
FFI_SessionRef::new has no physical codec available, so it creates a DefaultPhysicalExtensionCodec and gives it a clone of the logical codec's task context provider. This has two limitations:
- A query planner obtained through that session reference cannot round-trip custom physical extension nodes. The failure occurs later during physical-plan encoding or decoding as
PhysicalExtensionCodec is not provided.
- The cloned task context provider is not guaranteed to represent the session passed to
FFI_SessionRef::new. It may have arrived with a codec exported by another library, and its weak reference may expire.
Callers can avoid these limitations with FFI_SessionRef::new_with_ffi_codecs, but wrappers such as FFI_TableProvider, FFI_TableProviderFactory, and FFI_TableFunction do not currently carry a matching physical codec to supply. Passing codecs independently also makes it easy to combine codecs and a provider that were not configured together.
Describe the solution you'd like
Add an ABI-stable bundle that represents the complete serialization environment used at an FFI boundary. The exact representation should be determined during implementation, but its public abstraction should contain:
- one
FFI_TaskContextProvider;
- one
FFI_LogicalExtensionCodec;
- one
FFI_PhysicalExtensionCodec.
For example:
#[repr(C)]
pub struct FFI_ExtensionCodecBundle {
task_ctx_provider: FFI_TaskContextProvider,
logical_codec: FFI_LogicalExtensionCodec,
physical_codec: FFI_PhysicalExtensionCodec,
}
The fields should remain private so constructors can enforce that both codecs use the intended provider. Constructors should support:
- creating a bundle from one task context provider and explicit native logical and physical codecs;
- explicitly creating a bundle with both default codecs;
- safely cloning or re-exporting an existing FFI bundle without nesting foreign wrappers;
- preserving the live task context behavior needed when functions or other session state change after construction.
Individual codec wrappers currently need direct access to a task context provider because their function pointers receive only the codec. The initial implementation may retain provider clones inside each codec while the bundle acts as the canonical pairing mechanism. It should not introduce a codec-to-bundle reference or a self-referential structure. The dependency direction should remain bundle to codecs to task context provider.
Propagate the bundle through FFI wrappers that create nested providers or export a session, including the relevant catalog, schema, table provider, table provider factory, and table function paths. Change FFI_SessionRef and FFI_QueryPlanner construction to consume the bundle, or otherwise require codecs obtained from the same bundle. Default physical codec use should always be explicit.
Add cross-library integration coverage for this scenario:
- Library A owns a session, task context provider, and custom physical codec.
- Library A installs a query planner owned by library C.
- Library A calls a table provider owned by library B.
- Library B accesses the planner through the received session.
- The planner returns a custom physical extension node.
- The node is serialized through the configured physical codec and reconstructed with the expected local type identity.
The tests should also verify that nested catalog/schema/table-provider construction preserves the bundle and that an expired provider produces a clear error.
Describe alternatives you've considered
- Document the current limitation. This prevents accidental misuse but does not allow custom physical nodes through session callbacks made by wrappers that carry only a logical codec.
- Add a physical codec field independently to each affected wrapper. This is smaller locally but repeats propagation logic and still permits mismatched logical codecs, physical codecs, and task context providers.
- Derive the provider or codec from
Session. Session returns an Arc<TaskContext>, but it does not expose the reference-counted TaskContextProvider required for live FFI callbacks or a physical extension codec registry.
- Preserve codecs only when re-exporting an existing
ForeignQueryPlanner. This fixes one planner-swap topology but does not support local planners that produce custom physical nodes.
- Continue synthesizing a default physical codec. This preserves current behavior but causes custom-node support to fail after the query planner has already crossed the boundary.
Additional context
The relevant code is in:
datafusion/ffi/src/session/mod.rs
datafusion/ffi/src/query_planner.rs
datafusion/ffi/src/proto/logical_extension_codec.rs
datafusion/ffi/src/proto/physical_extension_codec.rs
- the catalog, schema, table provider, table provider factory, and table function FFI wrappers
Is your feature request related to a problem or challenge?
This is a follow on to #24028
datafusion-ffipasses logical extension codecs, physical extension codecs, and task context providers across library boundaries. These values describe one serialization environment and must remain consistent, but the current API carries them separately.FFI_LogicalExtensionCodecandFFI_PhysicalExtensionCodeceach contain anFFI_TaskContextProvider. Several FFI wrappers carry only anFFI_LogicalExtensionCodecbecause they historically needed to serialize only logical expressions and plans. Now thatFFI_SessionRefexposesSession::query_planner, those wrappers can also initiate physical-plan serialization through a session callback.FFI_SessionRef::newhas no physical codec available, so it creates aDefaultPhysicalExtensionCodecand gives it a clone of the logical codec's task context provider. This has two limitations:PhysicalExtensionCodec is not provided.FFI_SessionRef::new. It may have arrived with a codec exported by another library, and its weak reference may expire.Callers can avoid these limitations with
FFI_SessionRef::new_with_ffi_codecs, but wrappers such asFFI_TableProvider,FFI_TableProviderFactory, andFFI_TableFunctiondo not currently carry a matching physical codec to supply. Passing codecs independently also makes it easy to combine codecs and a provider that were not configured together.Describe the solution you'd like
Add an ABI-stable bundle that represents the complete serialization environment used at an FFI boundary. The exact representation should be determined during implementation, but its public abstraction should contain:
FFI_TaskContextProvider;FFI_LogicalExtensionCodec;FFI_PhysicalExtensionCodec.For example:
The fields should remain private so constructors can enforce that both codecs use the intended provider. Constructors should support:
Individual codec wrappers currently need direct access to a task context provider because their function pointers receive only the codec. The initial implementation may retain provider clones inside each codec while the bundle acts as the canonical pairing mechanism. It should not introduce a codec-to-bundle reference or a self-referential structure. The dependency direction should remain bundle to codecs to task context provider.
Propagate the bundle through FFI wrappers that create nested providers or export a session, including the relevant catalog, schema, table provider, table provider factory, and table function paths. Change
FFI_SessionRefandFFI_QueryPlannerconstruction to consume the bundle, or otherwise require codecs obtained from the same bundle. Default physical codec use should always be explicit.Add cross-library integration coverage for this scenario:
The tests should also verify that nested catalog/schema/table-provider construction preserves the bundle and that an expired provider produces a clear error.
Describe alternatives you've considered
Session.Sessionreturns anArc<TaskContext>, but it does not expose the reference-countedTaskContextProviderrequired for live FFI callbacks or a physical extension codec registry.ForeignQueryPlanner. This fixes one planner-swap topology but does not support local planners that produce custom physical nodes.Additional context
The relevant code is in:
datafusion/ffi/src/session/mod.rsdatafusion/ffi/src/query_planner.rsdatafusion/ffi/src/proto/logical_extension_codec.rsdatafusion/ffi/src/proto/physical_extension_codec.rs