use rupg_catalog::{Catalog, RelKind, Relation};
use rupg_common::{Error, Result, SqlState};
use rupg_types::Value;
use super::Definer;
use super::alter::Target;
use crate::expr::{Expr, ExprKind};
const MAX_DEPTH: usize = 32;
pub(super) fn row_relation(catalog: &Catalog, ty: u32) -> Option<&Relation> {
let mut ty = ty;
for _ in 0..MAX_DEPTH {
let found = catalog.type_by_oid(ty)?;
match &found.domain {
Some(domain) => ty = domain.base,
None => return catalog.relation(found.relation),
}
}
None
}
fn move_fields(expr: &mut Expr, types: &[u32], at: usize) -> bool {
let ty = expr.ty;
let mut changed = true;
match &mut expr.kind {
ExprKind::FieldSelect(arg, field) if *field > at && types.contains(&arg.ty) => {
*field -= 1;
}
ExprKind::Row(args, _) if at < args.len() && types.contains(&ty) => {
args.remove(at);
}
ExprKind::Const(Value::Record(record))
if at < record.values.len() && types.contains(&ty) =>
{
record.values.remove(at);
record.types.remove(at);
}
_ => changed = false,
}
for child in expr.children_mut() {
changed |= move_fields(child, types, at);
}
changed
}
impl Definer<'_, '_> {
pub(super) fn check_member_of_itself(&self, ty: u32, row_type: u32) -> Result<()> {
if row_type != 0 && self.holds_type(ty, row_type, true, 0) {
return Err(Error::new(
SqlState::INVALID_TABLE_DEFINITION,
format!(
"composite type {} cannot be made a member of itself",
self.type_text(row_type)
),
));
}
Ok(())
}
pub(super) fn check_row_type_users(&self, target: &Target) -> Result<()> {
let row_type = self.catalog.relation(target.oid).map_or(0, |r| r.row_type);
let Some((table, column)) = self.row_type_user(row_type, 0) else { return Ok(()) };
let message = if target.kind == b'c' {
format!(
"cannot alter type \"{}\" because column \"{table}.{column}\" uses it",
target.name
)
} else {
format!(
"cannot alter table \"{}\" because column \"{table}.{column}\" uses its row type",
target.name
)
};
Err(Error::new(SqlState::FEATURE_NOT_SUPPORTED, message))
}
pub(super) fn drop_field(&mut self, relation: u32, at: usize) -> Result<()> {
let catalog = &*self.catalog;
let types: Vec<u32> = catalog
.types()
.filter(|t| row_relation(catalog, t.oid).is_some_and(|r| r.oid == relation))
.map(|t| t.oid)
.collect();
if types.is_empty() {
return Ok(());
}
self.catalog.change_stored_exprs(&mut |text, list| {
if list {
let mut exprs = crate::node::read_list(text)?;
let mut changed = false;
for expr in &mut exprs {
changed |= move_fields(expr, &types, at);
}
return if changed { crate::node::write_list(&exprs).map(Some) } else { Ok(None) };
}
let mut expr = crate::node::read(text)?;
if move_fields(&mut expr, &types, at) {
crate::node::write(&expr).map(Some)
} else {
Ok(None)
}
})
}
fn row_type_user(&self, ty: u32, depth: usize) -> Option<(String, String)> {
if ty == 0 || depth > MAX_DEPTH {
return None;
}
for rel in self.catalog.relations() {
for column in rel.columns.iter().filter(|c| !c.dropped) {
if !self.holds_type(column.ty, ty, false, 0) {
continue;
}
if matches!(rel.kind, RelKind::Table | RelKind::Partitioned) {
return Some((rel.name.clone(), column.name.clone()));
}
if let Some(found) = self.row_type_user(rel.row_type, depth + 1) {
return Some(found);
}
}
}
None
}
fn holds_type(&self, ty: u32, target: u32, fields: bool, depth: usize) -> bool {
if ty == target {
return true;
}
let Some(found) = self.catalog.type_by_oid(ty).filter(|_| depth <= MAX_DEPTH) else {
return false;
};
if let Some(domain) = &found.domain {
return self.holds_type(domain.base, target, fields, depth + 1);
}
if found.element != 0 {
return self.holds_type(found.element, target, fields, depth + 1);
}
fields
&& self.catalog.relation(found.relation).is_some_and(|rel| {
rel.columns
.iter()
.filter(|c| !c.dropped)
.any(|c| self.holds_type(c.ty, target, fields, depth + 1))
})
}
}