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]
