phas02 opened a new issue, #24460:
URL: https://github.com/apache/datafusion/issues/24460

   ### Is your feature request related to a problem or challenge?
   
   `RelationPlanner` runs before DataFusion's own CTE lookup, and 
`RelationPlannerContext` gives an implementer no way to ask whether a name is 
bound as a CTE — so a planner that resolves bare table names silently inverts 
SQL's precedence (a CTE is supposed to shadow a same-named table).
   
   The ordering: `create_relation` tries extension planners first and only 
falls back to default planning — where `planner_context.get_cte(&table_name)` 
is consulted — when they return `RelationPlanning::Original` 
(`datafusion/sql/src/relation/mod.rs:87-98`, CTE lookup at `:190`, in 54.1 and 
unchanged on main). `RelationPlannerContext` exposes `context_provider`, 
`plan`, `sql_to_expr`, `sql_expr_to_logical_expr`, `normalize_ident`, 
`object_name_to_table_reference` (`datafusion/expr/src/planner.rs:410`) — 
nothing about CTE scope.
   
   Concretely: we register a `RelationPlanner` that resolves bare names to 
snapshot-pinned tables (per the [extending-SQL 
blog's](https://datafusion.apache.org/blog/2026/01/12/extending-sql) pattern). 
A user query like
   
   ```sql
   WITH cells AS (SELECT ... FROM other) SELECT ... FROM cells
   ```
   
   against a catalog that also contains a table named `cells` planned the 
*table* where stock SQL semantics (and DataFusion without the extension 
planner) would plan the CTE — silently, since both succeed.
   
   The only cure available today is re-parsing the statement and collecting CTE 
names ourselves before planning, then declining those names in the planner. 
That works but loses scope precision (without re-tracking scope, a name bound 
in any subquery has to decline everywhere) and re-implements knowledge the 
planner already holds: the concrete `RelationPlannerContext` impl 
(`datafusion/sql/src/relation/mod.rs:42`) wraps the very `PlannerContext` whose 
`get_cte` the default path consults.
   
   ### Describe the solution you'd like
   
   Expose the CTE probe on the trait, e.g.:
   
   ```rust
   pub trait RelationPlannerContext {
       // ...
       /// The plan a CTE in the current scope binds for `name`, if any —
       /// planners that resolve bare table names should decline these to
       /// preserve SQL's shadowing.
       fn get_cte(&self, name: &str) -> Option<&LogicalPlan>;
       // or, minimally: fn is_cte(&self, name: &str) -> bool;
   }
   ```
   
   The concrete impl already has the data; this is a pass-through. A line in 
the `RelationPlanner` docs telling implementers to decline CTE-bound names 
would make the hazard discoverable.
   
   ### Describe alternatives you've considered
   
   - Consulting CTEs *before* extension planners in `create_relation` — a 
behavior change for planners that deliberately want to intercept names 
regardless of CTEs (reserved names), so probably not desirable as a default.
   - Status quo: each implementer re-parses and tracks CTE names themselves, 
which is what we do today.
   
   Happy to send a PR for the trait method if this sounds right.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to