Commit a30cf37
Add relation planner extension support to customize SQL planning (#17843)
## Which issue does this PR close?
- Closes #18078.
- Closes #17633
- Closes #13563
- Closes #17824
## Rationale for this change
DataFusion currently lacks a clean way to customize how SQL table
factors (FROM clause elements) are planned into logical plans. The
proposed workaround in #17633 has a critical limitation: it only works
at the query root level and cannot handle custom relations inside JOINs,
CTEs, or subqueries.
This PR introduces a `RelationPlanner` extension API that allows users
to intercept and customize table factor planning at any nesting level,
enabling support for SQL syntax extensions that go beyond simple
table-valued functions. For example, you can now combine multiple custom
relation types in a single query:
```rust
let ctx = SessionContext::new();
// Register custom planners for SQL syntax extensions
ctx.register_relation_planner(Arc::new(TableSamplePlanner))?;
ctx.register_relation_planner(Arc::new(MatchRecognizePlanner))?;
ctx.register_relation_planner(Arc::new(PivotUnpivotPlanner))?;
// Use multiple custom table modifiers together - even in nested JOINs or CTEs
let df = ctx.sql(r#"
WITH sampled_data AS (
SELECT * FROM stock_prices
TABLESAMPLE BERNOULLI(10 PERCENT) REPEATABLE(42)
)
SELECT symbol, quarter, price
FROM sampled_data
MATCH_RECOGNIZE (
PARTITION BY symbol ORDER BY time
MEASURES LAST(price) AS price, quarter
PATTERN (UP+ DOWN+)
DEFINE
UP AS price > PREV(price),
DOWN AS price < PREV(price)
) AS patterns
PIVOT (
AVG(price) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4')
) AS pivoted
"#).await?;
df.show().await?;
```
**Why not use `TableFunctionImpl`?** The existing `TableFunctionImpl`
trait is perfect for simple table-valued functions (like
`generate_series(1, 10)`), but it cannot handle:
- SQL clause modifiers (e.g., `TABLESAMPLE` that modifies an existing
table reference)
- New table factor syntaxes (e.g., `MATCH_RECOGNIZE`, `PIVOT`,
`UNPIVOT`)
- Complex syntax that doesn't follow the function call pattern
`RelationPlanner` fills this gap by intercepting arbitrary `TableFactor`
AST nodes and transforming them into logical plans.
## What changes are included in this PR?
**Core API (feat commit):**
- New `RelationPlanner` trait for customizing SQL table factor planning
- `RelationPlannerContext` trait providing SQL utilities to extension
planners
- `SessionContext::register_relation_planner()` for registering custom
planners
- `SessionState` integration with priority-based planner chain
- Integration into `SqlToRel` to invoke planners at all nesting levels
(not just root)
**Tests (test commit):**
- Comprehensive tests in
`datafusion/core/tests/user_defined/relation_planner.rs`
- Coverage for basic planner registration, priority ordering, and nested
relations
- Tests demonstrating custom table factors and syntax extensions
**Examples (example commit):**
- `table_sample.rs` - SQL TABLESAMPLE clause support (BERNOULLI/SYSTEM
methods, REPEATABLE seed), adapted from
#17633.
- `match_recognize.rs` - SQL MATCH_RECOGNIZE pattern matching on event
streams
- `pivot_unpivot.rs` - SQL PIVOT/UNPIVOT transformations for data
reshaping
**Note:** The examples are intentionally verbose to demonstrate the full
design and capabilities of the API. They should be simplified and
streamlined before merging as there is no need for three different
examples of the same feature: the `PIVOT/UNPIVOT` example is probably
more than enough.
## Are these changes tested?
Yes:
- Unit tests for planner registration, priority ordering, and chaining
- Integration tests demonstrating nested relation handling (JOINs, CTEs,
subqueries)
- Example programs serve as additional end-to-end tests
- All examples include multiple test cases showing different usage
patterns
- Examples demonstrate syntax that cannot be implemented with
`TableFunctionImpl`
## Are there any user-facing changes?
Yes, this is a new public API:
**New APIs:**
- `datafusion_expr::planner::RelationPlanner` trait
- `datafusion_expr::planner::RelationPlannerContext` trait
- `datafusion_expr::planner::PlannedRelation` struct
- `datafusion_expr::planner::RelationPlanning` enum
- `SessionContext::register_relation_planner()`
- `SessionState::register_relation_planner()` and `relation_planners()`
- `SessionStateBuilder::with_relation_planners()`
- `ContextProvider::get_relation_planners()`
This is an additive change that extends existing extensibility APIs
(`ExprPlanner`, `TypePlanner`) and requires the `sql` feature flag.
## AI-Generated Code Disclosure
This PR was developed with significant assistance from
`claude-sonnet-4.5`. The AI was heavily involved in all parts of the
process, from initial design to actual code to writing the PR
description, which greatly sped up development. All its output was
however carefully reviewed before submitting.
---------
Co-authored-by: Andrew Lamb <[email protected]>1 parent 7ea5066 commit a30cf37
File tree
13 files changed
+2755
-9
lines changed- datafusion-examples
- examples/relation_planner
- datafusion
- core
- src/execution
- context
- tests/user_defined
- expr/src
- sql/src/relation
13 files changed
+2755
-9
lines changedSome generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
| 50 | + | |
49 | 51 | | |
50 | 52 | | |
| 53 | + | |
51 | 54 | | |
52 | 55 | | |
| 56 | + | |
53 | 57 | | |
54 | 58 | | |
55 | 59 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
89 | 92 | | |
90 | 93 | | |
91 | 94 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
0 commit comments