Skip to main content

Module cte_scope

Module cte_scope 

Source
Expand description

CTE scope tracking for SQL AST visitors.

Common Table Expressions (CTEs) introduce names that shadow database objects. When traversing a SQL AST to collect dependencies, extract aliases, or transform names, CTE references must be distinguished from real object references. This module provides CteScope, a stack-based tracker that manages CTE name visibility across nested queries.

§Scoping Rules

  • Simple CTEs (WITH a AS (...), b AS (...) SELECT ...): names are introduced incrementally, left to right. Each simple CTE’s body sees only the CTEs declared before it; the name of CTE i is not in scope while visiting CTE i’s own body. All names are visible to the main query body. This mirrors Materialize’s name resolver (fold_query in src/sql/src/names.rs, plan_ctes in src/sql/src/plan/query.rs), which resolves each CteBlock::Simple body before inserting that CTE’s name. As a result a simple CTE whose name shadows a catalog object can still reference that object inside its own definition, e.g. WITH products AS (SELECT * FROM products WHERE active) SELECT * FROM products — the inner products is the catalog table, the outer is the CTE. Callers drive this by pushing an empty scope, then visiting each body and calling insert_current after each.

  • Mutually Recursive CTEs (WITH MUTUALLY RECURSIVE a AS (...), b AS (...) SELECT ...): All CTE names are visible to all CTE definitions and the main query body. Self-references and forward references are valid, so all names are pushed up front.

  • Nested queries: Each subquery can introduce its own CTEs. The scope stack ensures inner CTE names shadow outer ones, and are properly removed when the subquery scope ends.

§Usage Pattern

Used with mz-sql-parser’s Visit / VisitMut traits by overriding visit_query:

fn visit_query(&mut self, node: &'ast Query<Raw>) {
    let names = CteScope::collect_cte_names(&node.ctes);
    self.cte_scope.push(names);
    visit::visit_query(self, node);  // default traversal
    self.cte_scope.pop();
}

Then in visit_table_factor, check self.cte_scope.is_cte(name) before treating a single-ident reference as a database object.

Structs§

CteScope 🔒
Stack-based tracker for CTE names currently in scope.