Skip to content

Add FFI query planner support with composable extension codecs - #1672

Draft
timsaucer wants to merge 5 commits into
apache:mainfrom
timsaucer:feat/ffi-query-planner
Draft

Add FFI query planner support with composable extension codecs#1672
timsaucer wants to merge 5 commits into
apache:mainfrom
timsaucer:feat/ffi-query-planner

Conversation

@timsaucer

Copy link
Copy Markdown
Member

Which issue does this PR close?

Related to #1612. This PR does not close it, but provides the FFI query planner and codec plumbing that a datafusion-distributed integration can build on.

Rationale for this change

Extension libraries (for example distributed execution engines) need to supply their own QueryPlanner to a SessionContext without compiling against the datafusion-python crate. This PR exposes the query planner over the FFI boundary, following the same PyCapsule pattern used for table providers and catalogs.

Supporting a foreign planner also surfaced a codec problem: a query can involve three independent native libraries (datafusion-python, a provider library, and a planner library), and each library needs its extension codecs active on the session at the same time. Previously, installing a logical or physical extension codec replaced the prior codec, so the second library's install silently discarded the first — plans then failed later with a confusing decode error. Codecs now compose.

What changes are included in this PR?

FFI query planner

  • SessionContext.with_query_planner(planner) installs a planner exported via a __datafusion_query_planner__ PyCapsule, preserving existing session state and codec settings.
  • SessionContext.__datafusion_query_planner__() exports the current planner so another planner can wrap it as an explicit fallback (a session holds exactly one planner; layering is explicit delegation).
  • A RuntimeAwareQueryPlanner adapter binds foreign planners to the Tokio runtime owned by datafusion-python.
  • New example crate datafusion-ffi-query-planner-example demonstrating a real three-library plan exchange (host, provider library, planner library as separate cdylibs), including session config transfer via SessionConfig.with_extension.

Composable extension codecs

  • with_logical_extension_codec / with_physical_extension_codec now prepend to a codec chain instead of replacing the prior codec. The most recently installed codec is consulted first, falling through codec by codec to DataFusion's default codec. A codec signals "not mine" by returning an error.
  • Encoding runs each codec against a scratch buffer so failed attempts leave no partial bytes, and treats Ok-with-no-bytes (encode by name) as "no opinion" so later codecs still get a chance.
  • When every codec in the chain fails, the errors are aggregated so the owning codec's diagnostic is not masked by the default codec's generic error.
  • Fixed a latent bug where installing a codec silently reset python_udf_inlining back to enabled.

Documentation and tests

  • docs/source/contributor-guide/ffi.md gains sections on composable codecs (family-prefix discipline, registration order between libraries no longer matters) and planner layering (install all codecs before exporting or chaining planners, since a planner capsule captures the codecs at export time), including a full three-library registration recipe.
  • Rust unit tests for chain dispatch semantics; Python integration tests for codec composition on both layers plus an end-to-end three-library test with composed codecs under a foreign planner.

Are there any user-facing changes?

Yes:

  • New APIs: SessionContext.with_query_planner and SessionContext.__datafusion_query_planner__.
  • Behavior change: with_logical_extension_codec / with_physical_extension_codec now compose with previously installed codecs instead of replacing them. Sessions that install a single codec are unaffected.
  • These methods no longer reset the with_python_udf_inlining setting.
  • New example package datafusion-ffi-query-planner-example in the examples folder (not shipped in the wheel).

🤖 Generated with Claude Code

timsaucer and others added 5 commits August 6, 2026 10:54
AI Disclosure: This code was written in part by an AI agent.:
AI Disclosure: This code was written in part by an AI agent.:
AI Disclosure: This code was written in part by an AI agent.:
Installing a logical or physical extension codec now prepends it to a
codec chain instead of replacing the prior codec. The most recently
installed codec is consulted first, falling through codec by codec to
the default codec. This lets multiple independent extension libraries
install codecs on the same session, and removes the codec registration
ordering requirement between libraries.

Chain dispatch treats a codec error as "not mine". Encoding runs each
codec against a scratch buffer so failed attempts leave no partial
bytes, and treats Ok-with-no-bytes (encode by name) as no opinion so
later codecs still get a chance. When every codec fails, the errors
are aggregated so the owning codec's diagnostic is not masked by the
default codec's generic error.

Also preserves the python_udf_inlining setting when installing a
codec; previously it was silently reset to enabled.

Documents the remaining planner constraint: a session holds one query
planner, layering is explicit via fallback capsules, and codecs must
be installed before exporting or chaining planners because a planner
capsule captures the codecs at export time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant