use rupg_catalog::RelKind;
use rupg_common::{Error, Result, SqlState};
use rupg_sql::nodes::{DropBehavior, TypeName};
use super::super::Definer;
use super::super::alter::Target;
use super::{NewColumn, Plan};
use crate::types;
impl Definer<'_, '_> {
pub(in crate::ddl) fn of_type(&mut self, name: &TypeName) -> Result<u32> {
let (ty, _) = self.an.type_name(name)?;
let standalone = match self.catalog.type_by_oid(ty) {
Some(row) if row.relation != 0 => Some(
self.catalog.relation(row.relation).is_some_and(|r| r.kind == RelKind::Composite),
),
Some(_) => None,
None => types::row(ty).filter(|row| row.kind == b'c').map(|_| false),
};
match standalone {
Some(true) => Ok(ty),
Some(false) => Err(Error::new(
SqlState::WRONG_OBJECT_TYPE,
format!("type {} is the row type of another table", self.type_text(ty)),
)
.with_detail(
"A typed table must use a stand-alone composite type created with CREATE TYPE.",
)),
None => Err(Error::new(
SqlState::WRONG_OBJECT_TYPE,
format!("type {} is not a composite type", self.type_text(ty)),
)),
}
}
pub(super) fn merge_type_columns(&self, plan: &mut Plan, ty: u32) -> Result<()> {
let relation = self.catalog.type_by_oid(ty).map_or(0, |row| row.relation);
let rel = self.catalog.relation(relation).ok_or_else(|| {
Error::internal(format!("cache lookup failed for relation {relation}"))
})?;
let mut columns: Vec<(NewColumn, bool)> = rel
.columns
.iter()
.filter(|c| !c.dropped)
.map(|c| {
let type_name = TypeName {
typeOid: c.ty,
typemod: c.typmod,
location: -1,
..TypeName::default()
};
let column = NewColumn {
name: c.name.clone(),
type_name,
collate: None,
collation: c.collation,
is_not_null: false,
default: None,
identity: 0,
generated: 0,
storage: None,
compression: None,
inherited: None,
};
(column, true)
})
.collect();
let mut missing = None;
for local in std::mem::take(&mut plan.columns) {
let Some((column, from_type)) = columns.iter_mut().find(|(c, _)| c.name == local.name)
else {
missing = missing.or(Some(local.name));
continue;
};
if !*from_type {
return Err(Error::new(
SqlState::DUPLICATE_COLUMN,
format!("column \"{}\" specified more than once", local.name),
));
}
column.is_not_null = local.is_not_null;
column.default = local.default;
column.identity = local.identity;
*from_type = false;
}
if let Some(name) = missing {
return Err(Error::new(
SqlState::UNDEFINED_COLUMN,
format!("column \"{name}\" does not exist"),
));
}
plan.columns = columns.into_iter().map(|(c, _)| c).collect();
Ok(())
}
pub(in crate::ddl) fn check_not_typed(&self, oid: u32, message: &str) -> Result<()> {
if self.catalog.relation(oid).is_some_and(|r| r.of_type != 0) {
return Err(Error::new(SqlState::WRONG_OBJECT_TYPE, message.to_string()));
}
Ok(())
}
pub(in crate::ddl) fn typed_tables(
&self,
target: &Target,
behavior: DropBehavior,
) -> Result<Vec<Target>> {
let ty = self.catalog.relation(target.oid).map_or(0, |r| r.row_type);
let tables: Vec<u32> = self
.catalog
.relations()
.filter(|r| ty != 0 && r.of_type == ty)
.map(|r| r.oid)
.collect();
if !tables.is_empty() && behavior != DropBehavior::DROP_CASCADE {
return Err(Error::new(
SqlState::DEPENDENT_OBJECTS_STILL_EXIST,
format!(
"cannot alter type \"{}\" because it is the type of a typed table",
target.name
),
)
.with_hint("Use ALTER ... CASCADE to alter the typed tables too."));
}
Ok(tables.into_iter().map(|oid| self.target_of(oid)).collect())
}
pub(in crate::ddl) fn add_of(&mut self, target: &Target, name: &TypeName) -> Result<()> {
let ty = self.of_type(name)?;
let lookup = |oid: u32| Error::internal(format!("cache lookup failed for relation {oid}"));
let rel = self.catalog.relation(target.oid).cloned().ok_or_else(|| lookup(target.oid))?;
if rel.partition_of.is_some() || rel.inherits.iter().any(|&p| p != 0) {
return Err(Error::new(SqlState::WRONG_OBJECT_TYPE, "typed tables cannot inherit"));
}
let relation = self.catalog.type_by_oid(ty).map_or(0, |row| row.relation);
let shape = self.catalog.relation(relation).cloned().ok_or_else(|| lookup(relation))?;
let mismatch = |message: String| Error::new(SqlState::DATATYPE_MISMATCH, message);
let mut table = rel.columns.iter().filter(|c| !c.dropped);
for attr in shape.columns.iter().filter(|c| !c.dropped) {
let Some(column) = table.next() else {
return Err(mismatch(format!("table is missing column \"{}\"", attr.name)));
};
if column.name != attr.name {
return Err(mismatch(format!(
"table has column \"{}\" where type requires \"{}\"",
column.name, attr.name
)));
}
if column.ty != attr.ty
|| column.typmod != attr.typmod
|| column.collation != attr.collation
{
return Err(mismatch(format!(
"table \"{}\" has different type for column \"{}\"",
rel.name, attr.name
)));
}
}
if let Some(extra) = table.next() {
return Err(mismatch(format!("table has extra column \"{}\"", extra.name)));
}
self.catalog.set_of_type(target.oid, ty)
}
pub(in crate::ddl) fn drop_of(&mut self, target: &Target) -> Result<()> {
if self.catalog.relation(target.oid).is_none_or(|r| r.of_type == 0) {
return Err(Error::new(
SqlState::WRONG_OBJECT_TYPE,
format!("\"{}\" is not a typed table", target.name),
));
}
self.catalog.set_of_type(target.oid, 0)
}
}