blob: 91d42a3c5c1ccb746281871efb05ec002745bd75 [file] [view]
<!---
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
(ffi)=
(extension_guide)=
# Extension Guide
This section is for people **writing** a library that plugs into
datafusion-python — a package that contributes table providers, functions, a
catalog, extension codecs, or a query planner, usually written in Rust and
exposed through [PyO3](https://pyo3.rs).
Two neighbouring audiences are served elsewhere:
- **Using** an extension library someone else published:
{ref}`user_guide_extensions`.
- **Changing datafusion-python itself**, including the framing that makes this
protocol work: {doc}`../contributor-guide/index`.
The protocol described here is a public, versioned contract. When a hook's
signature changes, the change is documented in
{doc}`../user-guide/upgrade-guides` with a before-and-after, and objects built
against a different DataFusion major version are rejected with a clear error
rather than used as-is.
## Start here
If you have not built an extension before, read {doc}`why-ffi` and
{doc}`capsule-protocol` in order. Together they explain why your library must
not depend on the `datafusion-python` crate, and the one convention every hook
follows. After that the pages are independent — go to the one matching what
you are contributing.
## The three roles in a query
A single query can involve three independent native libraries, and much of
this section only makes sense once they are distinct in your head:
- **datafusion-python** — the host. Owns the session, and decodes whatever
comes back from the other two.
- **A provider library** — owns table providers, catalogs, and functions, plus
the codecs that serialize them.
- **A planner library** — owns a query planner and the configuration it needs.
The worked examples in this repository use two separate crates,
[`datafusion-ffi-example`] and [`datafusion-ffi-query-planner-example`], so
each role has a distinct shared-library identity. A real library may play more
than one role; keeping them separate in the examples is what makes the
boundaries observable.
The session owns the codecs used for the exchange and supplies them to the
foreign planner. That is what lets the planner decode provider-owned objects,
and lets datafusion-python decode the physical plan the planner returns.
## Hook reference
Every integration point is a dunder method named `__datafusion_*__`. The
convention is uniform enough to be worth stating once: your object exposes the
getter, datafusion-python calls it, and it returns a `PyCapsule` wrapping an
FFI-safe struct. See {doc}`capsule-protocol` for what that means and
{ref}`extension_getter_argument` for the argument every getter in the middle
group receives.
Each hook's capsule name follows from its own name by
{ref}`the naming rule <extension_capsule_protocol>`, so it is not repeated
here.
| Hook | Contributes | Argument | Documented on |
| --- | --- | --- | --- |
| `__datafusion_table_provider__` | one table | codec source | {doc}`table-providers` |
| `__datafusion_table_provider_factory__` | tables built by `CREATE EXTERNAL TABLE` | codec source | {doc}`table-providers` |
| `__datafusion_catalog_provider__` | a named set of schemas | codec source | {doc}`table-providers` |
| `__datafusion_catalog_provider_list__` | the whole catalog namespace | codec source | {doc}`table-providers` |
| `__datafusion_schema_provider__` | a named set of tables | codec source | {doc}`table-providers` |
| `__datafusion_table_function__` | a table-valued function | session | {doc}`functions` |
| `__datafusion_scalar_udf__` | a scalar function | none | {doc}`functions` |
| `__datafusion_aggregate_udf__` | an aggregate function | none | {doc}`functions` |
| `__datafusion_window_udf__` | a window function | none | {doc}`functions` |
| `__datafusion_logical_extension_codec__` | a logical codec | session | {doc}`codecs` |
| `__datafusion_physical_extension_codec__` | a physical codec | session | {doc}`codecs` |
| `__datafusion_codec_id__` | the wire id a codec's payloads carry | — | {ref}`extension_codec_ids` |
| `__datafusion_query_planner__` | a query planner | session | {doc}`query-planners` |
| `__datafusion_session_components__` | a bundle's codecs | `ctx` | {doc}`bundles` |
| `__datafusion_session_planner__` | a bundle's planner, wrapping `fallback` | `ctx`, `fallback` | {doc}`bundles` |
| `__datafusion_physical_optimizer_rule__` | a physical optimizer rule | none | {ref}`extension_other_hooks` |
| `__datafusion_extension_options__` | typed entries in the session config | none | {ref}`extension_other_hooks` |
| `__datafusion_task_context_provider__` | the host's task context | none | {ref}`extension_task_context_provider` |
Three rows are not like the others. `__datafusion_task_context_provider__` is
implemented by the **host**, not by your library — you read it off the session
you are handed. `__datafusion_codec_id__` is a plain string attribute rather
than a method returning a capsule. And the two `session_` hooks are dispatched
from Python and return objects rather than capsules.
"codec source" in the argument column means the value is something you can
read the host's logical extension codec off, which is not always a session.
{ref}`extension_getter_argument` explains why, and what to do with it.
`python/tests/test_docstrings.py` compares this table against the hook names
the package actually dispatches, so a hook added or renamed without touching
this page fails the suite.
```{toctree}
:maxdepth: 2
why-ffi
capsule-protocol
table-providers
functions
codecs
bundles
query-planners
other-components
sessions
checklist
```
[`datafusion-ffi-example`]: https://github.com/apache/datafusion-python/tree/main/examples/datafusion-ffi-example
[`datafusion-ffi-query-planner-example`]: https://github.com/apache/datafusion-python/tree/main/examples/datafusion-ffi-query-planner-example