You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[DISCUSS] Separate stable API into separate crates #25124
Is your feature request related to a problem or challenge?
Currently everything in DataFusion is released together which makes the upgrade path painful and causes dependency conflicts if you have a project which depends on multiple 3rd party dependencies which themselves depend on DataFusion.
For example, if I am building a query engine which should support multiple different table formats and want to use 3rd party dependencies which expose DataFusion TableProviders (e.g https://github.com/apache/iceberg-rust and https://github.com/delta-io/delta-rs), but also implement the core execution engine on top of DataFusion then I need to ensure all dependencies are in sync in terms of their DF dependency.
Describe the solution you'd like
Separate the stable API for internal implementation as separate crates. For instance instead of having just a datafusion-catalog crate with both the stable API and implementations we can have two crate:
datafusion-catalog-api
datafusion-catalog (which depends on datafusion-catalog-api
The -api crate would contain only trait definitions like: TableProvider CatalogProvider
etc.
The -api should (theoretically) evolve much more slowly than the implementation so 3rd party crates that want to expose a TableProvider can depend only on datafusion-catalog-api and I can consume that dependency while using any version of datafusion-catalog I want (provided it uses the same stable API).
Obviously there are quite a few details to work out about which APIs to "stabilize" in this way, which I will hand-wave past :)
Describe alternatives you've considered
A lot of alternative suggestions to the exact same issue have been proposed in #16622
Thanks @thinkharderdev, the pain is real, and it's the same one described in #16622. I support the goal but have concerns about the mechanism. Here's where I land.
1. Why -api crates alone won't solve this
A trait is only as stable as the types in its signatures. Following the closure of TableProvider::scan:
&dyn Session → ConfigOptions, RuntimeEnv, function registry
plus Statistics, Constraints, DataFusionError, and arrow's SchemaRef
So datafusion-catalog-api either depends on datafusion-expr / datafusion-physical-plan and breaks whenever they do, or absorbs those types and changes as often as core does. These are some of our most actively changing APIs.
Two more limits:
Arrow sets a floor. Arrow types appear in every signature, and arrow-rs releases a new major version roughly quarterly. Downstream crates like delta-rs and iceberg-rust also expose arrow types directly, so apps must match arrow versions with them regardless of what we do.
Providers use more than traits. Many build on our implementation crates (parquet scan, FileScanConfig, pruning), so they'd still depend on those crates.
A crate split would add crates and publishing overhead without making the API more stable.
2. Proposal: longer breaking-change period (1 month → 2 months, extend if needed)
Keep releasing monthly, but only every other release is a major (breaking) release. The release in between is semver-compatible (N.0 → N.1 → (N+1).0), so downstream crates get a two-month upgrade window instead of a monthly race.
Prefer "add new + deprecate old" so most changes can still land in minor releases. That also keeps the queue of held PRs small.
Re-evaluate after a few cycles. If downstream still can't keep up, extend to 3 months, which would also line up with arrow's major cadence.
This needs no refactoring, only a release policy plus CI.
3. Teach contributors (human and AI agents) how to handle breaking changes
A lot of PRs are now written with coding agents. Longer breaking periods only work if both humans and agents default to non-breaking changes. Two lightweight pieces:
a) A repo-level AGENTS.md section on API changes, which most agents read automatically, e.g.:
## Public API changes- Treat every `pub` item in published crates as public API.
- Prefer additive changes: new trait methods with default impls, new
constructors/builders, `#[non_exhaustive]` on new enums/structs.
- Never change an existing signature in place. Add the new API and mark the
old one `#[deprecated(since = "X.Y.0", note = "use `new_fn` instead")]`,
following the API health policy.
- Run `cargo semver-checks` before opening a PR. If a break is unavoidable,
say so in the PR description, add the `api-change` label, and add an
entry to the upgrade guide.
b) Inline markers on key extension points (TableProvider, ExecutionPlan, Expr, Session, ...), so an agent sees the rule right where it edits:
/// ...////// # API stability/// This trait is a public extension point implemented by downstream crates./// Do not change existing method signatures; add new methods with default/// implementations instead. See AGENTS.md#public-api-changes.pubtraitTableProvider: ...
The doc section also helps human contributors and shows up on docs.rs. cargo-semver-checks in CI remains the backstop, since neither humans nor agents will get it right every time.
Those are good points @jayzhan211 -- I think the fact that Arrow types appear in most APIs is ok as Arrow itself has longer stable windows (3-4 months rather than 1 month) but it is still a concern.
However, it is also a great point that actually figuring out what types are part of the public API, and thus can not change all that frequently, will likely be hard (handwaving aside) and maybe that makes this infeasible 🤔
A lot of PRs are now written with coding agents. Longer breaking periods only work if both humans and agents default to non-breaking changes. Two lightweight pieces:
Activity
jayzhan211 commentedon Sep 10, 2026
Thanks @thinkharderdev, the pain is real, and it's the same one described in #16622. I support the goal but have concerns about the mechanism. Here's where I land.
1. Why
-apicrates alone won't solve thisA trait is only as stable as the types in its signatures. Following the closure of
TableProvider::scan:&dyn Session→ConfigOptions,RuntimeEnv, function registry&[Expr]→ UDF types,Subquery→LogicalPlanArc<dyn ExecutionPlan>→PlanProperties,EquivalenceProperties,PhysicalExpr,TaskContextStatistics,Constraints,DataFusionError, and arrow'sSchemaRefSo
datafusion-catalog-apieither depends ondatafusion-expr/datafusion-physical-planand breaks whenever they do, or absorbs those types and changes as often as core does. These are some of our most actively changing APIs.Two more limits:
FileScanConfig, pruning), so they'd still depend on those crates.A crate split would add crates and publishing overhead without making the API more stable.
2. Proposal: longer breaking-change period (1 month → 2 months, extend if needed)
N.0 → N.1 → (N+1).0), so downstream crates get a two-month upgrade window instead of a monthly race.cargo-semver-checksin CI (Semver-checks for all crate on merge and push #16078). Breaking PRs get anapi-changelabel and wait for the next major.This needs no refactoring, only a release policy plus CI.
3. Teach contributors (human and AI agents) how to handle breaking changes
A lot of PRs are now written with coding agents. Longer breaking periods only work if both humans and agents default to non-breaking changes. Two lightweight pieces:
a) A repo-level
AGENTS.mdsection on API changes, which most agents read automatically, e.g.:b) Inline markers on key extension points (
TableProvider,ExecutionPlan,Expr,Session, ...), so an agent sees the rule right where it edits:The doc section also helps human contributors and shows up on docs.rs.
cargo-semver-checksin CI remains the backstop, since neither humans nor agents will get it right every time.alamb commentedon Sep 12, 2026
Thanks for filing this @thinkharderdev
Those are good points @jayzhan211 -- I think the fact that Arrow types appear in most APIs is ok as Arrow itself has longer stable windows (3-4 months rather than 1 month) but it is still a concern.
However, it is also a great point that actually figuring out what types are part of the public API, and thus can not change all that frequently, will likely be hard (handwaving aside) and maybe that makes this infeasible 🤔
Yeah, I agree this is the core tradeoff