Skip to content

Add FFI query planner support - #1677

Open
timsaucer wants to merge 4 commits into
mainfrom
feat/ffi-query-planner-core
Open

Add FFI query planner support#1677
timsaucer wants to merge 4 commits into
mainfrom
feat/ffi-query-planner-core

Conversation

@timsaucer

@timsaucer timsaucer commented Aug 7, 2026

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 plumbing that a datafusion-distributed integration can build on.

This is part 1 of 3 in the split of #1672. These are enabled as a github stack so you should be able to swab between the 3 PRs in github interface (above, next to the "Open" oval).

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.

What changes are included in this PR?

  • 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.
  • New docs/source/contributor-guide/ffi.md sections covering the query planner capsule protocol and the three-library setup.

Are there any user-facing changes?

New public APIs: SessionContext.with_query_planner and SessionContext.__datafusion_query_planner__. A new example crate ships under examples/. No breaking changes to existing APIs.

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.:
Ok(())
}

pub fn with_query_planner(&self, planner: Bound<'_, PyAny>) -> PyDataFusionResult<Self> {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API is the main reason for this PR. Here we allow changing out the default query planner with a user provided query planner.

Comment on lines +199 to +207
- name: Build FFI query planner test library
if: matrix.python-tag == 'abi3'
uses: PyO3/maturin-action@v1
with:
target: x86_64-unknown-linux-gnu
manylinux: "2_28"
working-directory: examples/datafusion-ffi-query-planner-example
args: --out dist
rustup-components: rust-std

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to prove that the 3 library approach works where we have different codecs and different execution plans provided, we are adding a second test library. This way we can make sure there is no accidental ability to reach into a foreign code block.

Comment on lines +236 to +238
struct RuntimeAwareQueryPlanner {
planner: FFI_QueryPlanner,
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the docstring says, the purpose of this is to make sure we attach the runtime handle when needed.

Comment on lines +1456 to +1459
pub fn __datafusion_query_planner__<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyCapsule>> {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need our session context to export it's own query planner because we have a use case where one query planner can depend on another. This is already supported by datafusion-distributed, so we want to be certain we support it here.

Comment on lines +35 to +38
#[derive(Clone, Debug)]
pub(crate) struct PlannerConfig {
pub max_rows: usize,
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm adding this to the query planner example because it's a very common pattern that we will need custom configs for the query planner, so it is reasonable to need insurance that configs pass over the FFI boundary properly and to use as a demonstration to anyone who is providing such a library.

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