uqa_sql/schema/dependencies/
regclass.rs1use super::walk_schema_expr_mut;
9use crate::ast::{ColumnDef, Expr, TableCheck};
10use crate::schema::sequences::implicit_ownership::StoredSequenceNames;
11use uqa_core::Value;
12
13#[cfg(test)]
14mod tests;
15
16pub trait SchemaReferenceCatalog {
17 fn loaded_relation_name(&self, reference: &str) -> Result<Option<String>, String>;
18 fn bound_relation_oid(&self, canonical: &str) -> Result<Option<i64>, String>;
19 fn visible_relation_oid(&self, reference: &str) -> Result<Option<i64>, String>;
20 fn sequence_for_binding(&self, reference: &str) -> Result<String, String>;
21}
22
23fn is_regclass(ty: &str) -> bool {
24 ty.eq_ignore_ascii_case("regclass") || ty.eq_ignore_ascii_case("pg_catalog.regclass")
25}
26
27pub fn bind_schema_regclass_constants(
28 catalog: &dyn SchemaReferenceCatalog,
29 expression: &mut Expr,
30 loaded: bool,
31) -> Result<bool, String> {
32 let mut changed = false;
33 walk_schema_expr_mut(expression, &mut |node| {
34 let Expr::Cast { expr, ty, .. } = node else {
35 return Ok(());
36 };
37 if !is_regclass(ty) {
38 return Ok(());
39 }
40 let Expr::Literal(Value::Str(reference)) = expr.as_ref() else {
41 return Ok(());
42 };
43 let oid = if loaded {
44 match reference.parse::<u32>() {
45 Ok(oid) => Some(i64::from(oid)),
46 Err(_) => match catalog.loaded_relation_name(reference)? {
47 Some(canonical) => catalog.bound_relation_oid(&canonical)?,
48 None => None,
49 },
50 }
51 } else {
52 catalog.visible_relation_oid(reference)?
53 }
54 .ok_or_else(|| format!("relation \"{reference}\" does not exist"))?;
55 **expr = Expr::TypedLiteral {
56 composite_source: None,
57 value: Value::Int(oid),
58 ty: "regclass".into(),
59 };
60 changed = true;
61 Ok(())
62 })?;
63 Ok(changed)
64}
65
66pub fn bind_table_schema_regclass_constants(
67 catalog: &dyn SchemaReferenceCatalog,
68 columns: &mut [ColumnDef],
69 checks: &mut [TableCheck],
70 loaded: bool,
71) -> Result<bool, String> {
72 let mut changed = false;
73 for column in columns {
74 for expression in [&mut column.default, &mut column.check]
75 .into_iter()
76 .flatten()
77 {
78 changed |= bind_schema_regclass_constants(catalog, expression, loaded)?;
79 }
80 if let Some(generated) = &mut column.generated {
81 changed |= bind_schema_regclass_constants(catalog, &mut generated.expression, loaded)?;
82 }
83 }
84 for check in checks {
85 changed |= bind_schema_regclass_constants(catalog, &mut check.expr, loaded)?;
86 }
87 Ok(changed)
88}
89pub fn bind_sequence_references_in_expr(
90 catalog: &dyn SchemaReferenceCatalog,
91 expression: &mut Expr,
92) -> Result<(), String> {
93 super::rewrites::rewrite_sequence_function_references(expression, &mut |reference| {
94 *reference = catalog.sequence_for_binding(reference)?;
95 Ok(())
96 })?;
97 bind_schema_regclass_constants(catalog, expression, false)?;
98 Ok(())
99}
100
101pub fn bind_legacy_sequence_regclass_constants(
105 catalog: &dyn SchemaReferenceCatalog,
106 sequences: &dyn StoredSequenceNames,
107 expression: &mut Expr,
108) -> Result<bool, String> {
109 let mut changed = false;
110 walk_schema_expr_mut(expression, &mut |node| {
111 let Expr::Func {
112 name,
113 binding: Some(binding),
114 args,
115 ..
116 } = node
117 else {
118 return Ok(());
119 };
120 if !super::rewrites::is_sequence_function(name, Some(binding)) {
121 return Ok(());
122 }
123 let Some(argument @ Expr::Literal(Value::Str(_))) = args.first_mut() else {
124 return Ok(());
125 };
126 let Expr::Literal(Value::Str(reference)) = &*argument else {
127 unreachable!("legacy sequence argument is a string literal");
128 };
129 let canonical = sequences.stored_sequence_name(reference)?;
130 let oid = catalog
131 .bound_relation_oid(&canonical)?
132 .ok_or_else(|| format!("relation \"{canonical}\" does not exist"))?;
133 *argument = Expr::TypedLiteral {
134 composite_source: None,
135 value: Value::Int(oid),
136 ty: "regclass".into(),
137 };
138 if let Some(target) = binding.argument_types.first_mut() {
139 "regclass".clone_into(target);
140 }
141 changed = true;
142 Ok(())
143 })?;
144 Ok(changed)
145}