Skip to main content

uqa_sql/schema/
dependencies.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Traverse stored expressions and recognize their column dependencies.
8
9#[expect(
10    clippy::too_many_lines,
11    reason = "one traversal covers every stored expression child"
12)]
13pub fn walk_schema_expr_mut(
14    expression: &mut crate::ast::Expr,
15    visit: &mut impl FnMut(&mut crate::ast::Expr) -> Result<(), String>,
16) -> Result<(), String> {
17    use crate::ast::{Expr, FrameBound};
18
19    visit(expression)?;
20    match expression {
21        Expr::Func {
22            args,
23            order_by,
24            filter,
25            ..
26        } => {
27            for argument in args {
28                walk_schema_expr_mut(argument, visit)?;
29            }
30            for order in order_by {
31                walk_schema_expr_mut(&mut order.expr, visit)?;
32            }
33            if let Some(filter) = filter {
34                walk_schema_expr_mut(filter, visit)?;
35            }
36        }
37        Expr::Array(items)
38        | Expr::Row(items)
39        | Expr::CompositeRow { items, .. }
40        | Expr::And(items)
41        | Expr::Or(items) => {
42            for item in items {
43                walk_schema_expr_mut(item, visit)?;
44            }
45        }
46        Expr::Binary { lhs, rhs, .. } => {
47            walk_schema_expr_mut(lhs, visit)?;
48            walk_schema_expr_mut(rhs, visit)?;
49        }
50        Expr::Not(inner)
51        | Expr::UnaryMinus(inner)
52        | Expr::IsNull { expr: inner, .. }
53        | Expr::Cast { expr: inner, .. } => {
54            walk_schema_expr_mut(inner, visit)?;
55        }
56        Expr::Between { expr, low, high } => {
57            walk_schema_expr_mut(expr, visit)?;
58            walk_schema_expr_mut(low, visit)?;
59            walk_schema_expr_mut(high, visit)?;
60        }
61        Expr::InList { expr, list, .. } => {
62            walk_schema_expr_mut(expr, visit)?;
63            for item in list {
64                walk_schema_expr_mut(item, visit)?;
65            }
66        }
67        Expr::WindowCall {
68            args, spec, filter, ..
69        } => {
70            for expression in args
71                .iter_mut()
72                .chain(filter.as_deref_mut())
73                .chain(&mut spec.partition_by)
74                .chain(spec.order_by.iter_mut().map(|order| &mut order.expr))
75            {
76                walk_schema_expr_mut(expression, visit)?;
77            }
78            for bound in spec
79                .frame
80                .iter_mut()
81                .flat_map(|frame| [&mut frame.start, &mut frame.end])
82            {
83                if let FrameBound::Preceding(expression) | FrameBound::Following(expression) = bound
84                {
85                    walk_schema_expr_mut(expression, visit)?;
86                }
87            }
88        }
89        Expr::Case {
90            base,
91            when,
92            else_branch,
93        } => {
94            if let Some(base) = base {
95                walk_schema_expr_mut(base, visit)?;
96            }
97            for (condition, result) in when {
98                walk_schema_expr_mut(condition, visit)?;
99                walk_schema_expr_mut(result, visit)?;
100            }
101            if let Some(else_branch) = else_branch {
102                walk_schema_expr_mut(else_branch, visit)?;
103            }
104        }
105        Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
106            return Err(
107                "schema expression contains a subquery whose dependencies cannot be rewritten safely"
108                    .into(),
109            );
110        }
111        Expr::Default
112        | Expr::Star
113        | Expr::QualifiedStar(_)
114        | Expr::Column(_)
115        | Expr::QualifiedColumn { .. }
116        | Expr::InternalColumn(_)
117        | Expr::Literal(_)
118        | Expr::TypedLiteral { .. }
119        | Expr::Param(_) => {}
120    }
121    Ok(())
122}
123
124pub fn schema_expr_references_column(expression: &crate::ast::Expr, column: &str) -> bool {
125    let mut expression = expression.clone();
126    let mut referenced = false;
127    let result = walk_schema_expr_mut(&mut expression, &mut |node| {
128        referenced |= match node {
129            crate::ast::Expr::Star | crate::ast::Expr::QualifiedStar(_) => true,
130            crate::ast::Expr::Column(name)
131            | crate::ast::Expr::QualifiedColumn { column: name, .. } => name == column,
132            _ => false,
133        };
134        Ok(())
135    });
136    result.is_err() || referenced
137}
138
139pub mod oid_alias;
140pub mod regclass;
141pub mod registration;
142pub mod rewrites;