Skip to main content

uqa_sql/schema/dependencies/
regclass.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bind literal relation references in persisted SQL schema expressions.
8use super::walk_schema_expr_mut;
9use crate::ast::{ColumnDef, Expr, TableCheck};
10use uqa_core::Value;
11
12pub trait SchemaReferenceCatalog {
13    fn loaded_relation_name(&self, reference: &str) -> Result<Option<String>, String>;
14    fn bound_relation_oid(&self, canonical: &str) -> Result<Option<i64>, String>;
15    fn visible_relation_oid(&self, reference: &str) -> Result<Option<i64>, String>;
16    fn sequence_for_binding(&self, reference: &str) -> Result<String, String>;
17}
18
19pub fn regclass_constant_oid(expression: &Expr) -> Option<i64> {
20    match expression {
21        Expr::TypedLiteral {
22            value: Value::Int(oid),
23            ty,
24        } if is_regclass(ty) => Some(*oid),
25        _ => None,
26    }
27}
28
29fn is_regclass(ty: &str) -> bool {
30    ty.eq_ignore_ascii_case("regclass") || ty.eq_ignore_ascii_case("pg_catalog.regclass")
31}
32
33pub fn bind_schema_regclass_constants(
34    catalog: &dyn SchemaReferenceCatalog,
35    expression: &mut Expr,
36    loaded: bool,
37) -> Result<bool, String> {
38    let mut changed = false;
39    walk_schema_expr_mut(expression, &mut |node| {
40        let Expr::Cast { expr, ty } = node else {
41            return Ok(());
42        };
43        if !is_regclass(ty) {
44            return Ok(());
45        }
46        let Expr::Literal(Value::Str(reference)) = expr.as_ref() else {
47            return Ok(());
48        };
49        let oid = if loaded {
50            match reference.parse::<u32>() {
51                Ok(oid) => Some(i64::from(oid)),
52                Err(_) => match catalog.loaded_relation_name(reference)? {
53                    Some(canonical) => catalog.bound_relation_oid(&canonical)?,
54                    None => None,
55                },
56            }
57        } else {
58            catalog.visible_relation_oid(reference)?
59        }
60        .ok_or_else(|| format!("relation \"{reference}\" does not exist"))?;
61        **expr = Expr::TypedLiteral {
62            value: Value::Int(oid),
63            ty: "regclass".into(),
64        };
65        changed = true;
66        Ok(())
67    })?;
68    Ok(changed)
69}
70
71pub fn bind_table_schema_regclass_constants(
72    catalog: &dyn SchemaReferenceCatalog,
73    columns: &mut [ColumnDef],
74    checks: &mut [TableCheck],
75    loaded: bool,
76) -> Result<bool, String> {
77    let mut changed = false;
78    for column in columns {
79        for expression in [&mut column.default, &mut column.check]
80            .into_iter()
81            .flatten()
82        {
83            changed |= bind_schema_regclass_constants(catalog, expression, loaded)?;
84        }
85        if let Some(generated) = &mut column.generated {
86            changed |= bind_schema_regclass_constants(catalog, &mut generated.expression, loaded)?;
87        }
88    }
89    for check in checks {
90        changed |= bind_schema_regclass_constants(catalog, &mut check.expr, loaded)?;
91    }
92    Ok(changed)
93}
94pub fn bind_sequence_references_in_expr(
95    catalog: &dyn SchemaReferenceCatalog,
96    expression: &mut Expr,
97) -> Result<(), String> {
98    super::rewrites::rewrite_sequence_function_references(expression, &mut |reference| {
99        *reference = catalog.sequence_for_binding(reference)?;
100        Ok(())
101    })?;
102    bind_schema_regclass_constants(catalog, expression, false)?;
103    Ok(())
104}