Skip to main content

uqa_sql/schema/constraints/
references.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Borrow the distinct variables of an analyzed CHECK in their original expression order.
8
9use crate::{ast::Expr, SQLError};
10use std::collections::BTreeSet;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum CheckReference<'a> {
14    WholeRow,
15    Column(&'a str),
16}
17
18impl<'a> CheckReference<'a> {
19    /// Whole rows have a relation dependency but no attribute name.
20    #[must_use]
21    pub const fn column_name(self) -> Option<&'a str> {
22        match self {
23            Self::WholeRow => None,
24            Self::Column(name) => Some(name),
25        }
26    }
27
28    /// CHECK permits tableoid as its only system attribute; user attributes keep the declaration's durable numbers.
29    pub fn attribute_number(
30        self,
31        lookup: impl FnOnce(&str) -> Result<i16, SQLError>,
32    ) -> Result<i16, SQLError> {
33        match self {
34            Self::WholeRow => Ok(0),
35            Self::Column("tableoid") => Ok(-6),
36            Self::Column(name) => lookup(name),
37        }
38    }
39}
40
41/// Repeated references retain their first position, including the whole-row variable. Neither the syntax nor its values are cloned or evaluated.
42#[must_use]
43pub fn check_references(expression: &Expr) -> Vec<CheckReference<'_>> {
44    let mut references = References::default();
45    collect_references(expression, &mut references);
46    references.ordered
47}
48
49#[derive(Default)]
50struct References<'a> {
51    seen: BTreeSet<Option<&'a str>>,
52    ordered: Vec<CheckReference<'a>>,
53}
54
55impl<'a> References<'a> {
56    fn insert(&mut self, reference: CheckReference<'a>) {
57        if self.seen.insert(reference.column_name()) {
58            self.ordered.push(reference);
59        }
60    }
61}
62
63#[expect(
64    clippy::too_many_lines,
65    reason = "one traversal covers every constraint expression child"
66)]
67fn collect_references<'a>(expression: &'a crate::ast::Expr, output: &mut References<'a>) {
68    use crate::ast::{Expr, FrameBound};
69    match expression {
70        Expr::Column(name) | Expr::QualifiedColumn { column: name, .. } => {
71            output.insert(CheckReference::Column(name));
72        }
73        Expr::Func {
74            args,
75            order_by,
76            filter,
77            ..
78        } => {
79            for argument in args {
80                collect_references(argument, output);
81            }
82            for order in order_by {
83                collect_references(&order.expr, output);
84            }
85            if let Some(filter) = filter {
86                collect_references(filter, output);
87            }
88        }
89        Expr::Array(items)
90        | Expr::Row(items)
91        | Expr::CompositeRow { items, .. }
92        | Expr::And(items)
93        | Expr::Or(items) => {
94            for item in items {
95                collect_references(item, output);
96            }
97        }
98        Expr::Binary { lhs, rhs, .. } => {
99            collect_references(lhs, output);
100            collect_references(rhs, output);
101        }
102        Expr::Not(inner)
103        | Expr::UnaryMinus(inner)
104        | Expr::IsNull { expr: inner, .. }
105        | Expr::Cast { expr: inner, .. } => {
106            collect_references(inner, output);
107        }
108        Expr::Between { expr, low, high } => {
109            collect_references(expr, output);
110            collect_references(low, output);
111            collect_references(high, output);
112        }
113        Expr::InList { expr, list, .. } => {
114            collect_references(expr, output);
115            for item in list {
116                collect_references(item, output);
117            }
118        }
119        Expr::WindowCall {
120            args, spec, filter, ..
121        } => {
122            for expression in args
123                .iter()
124                .chain(filter.as_deref())
125                .chain(&spec.partition_by)
126                .chain(spec.order_by.iter().map(|order| &order.expr))
127            {
128                collect_references(expression, output);
129            }
130            for bound in spec
131                .frame
132                .iter()
133                .flat_map(|frame| [&frame.start, &frame.end])
134            {
135                if let FrameBound::Preceding(expression) | FrameBound::Following(expression) = bound
136                {
137                    collect_references(expression, output);
138                }
139            }
140        }
141        Expr::Case {
142            base,
143            when,
144            else_branch,
145        } => {
146            if let Some(base) = base {
147                collect_references(base, output);
148            }
149            for (condition, result) in when {
150                collect_references(condition, output);
151                collect_references(result, output);
152            }
153            if let Some(else_branch) = else_branch {
154                collect_references(else_branch, output);
155            }
156        }
157        Expr::InSubquery { expr, .. } => collect_references(expr, output),
158        Expr::Star | Expr::QualifiedStar(_) => {
159            output.insert(CheckReference::WholeRow);
160        }
161        Expr::Default
162        | Expr::InternalColumn(_)
163        | Expr::Literal(_)
164        | Expr::TypedLiteral { .. }
165        | Expr::Param(_)
166        | Expr::ScalarSubquery(_)
167        | Expr::Exists { .. } => {}
168    }
169}