use rupg_catalog::{DepKind, ObjRef, PG_CLASS};
use rupg_common::{Error, Result, SqlState};
use rupg_sql::nodes::{AlterSeqStmt, Constraint, DefElem, Node, RangeVar};
use super::Definer;
use super::alter::Target;
use crate::from::system_column;
fn no_column(column: &str, table: &str) -> Error {
Error::new(
SqlState::UNDEFINED_COLUMN,
format!("column \"{column}\" of relation \"{table}\" does not exist"),
)
}
fn system(column: &str) -> Error {
Error::new(SqlState::FEATURE_NOT_SUPPORTED, format!("cannot alter system column \"{column}\""))
}
fn check_partitions(target: &Target, partition: bool, recurse: bool, action: &str) -> Result<()> {
if target.kind == b'p' && !recurse {
return Err(Error::new(
SqlState::INVALID_TABLE_DEFINITION,
format!("cannot {action} only the partitioned table"),
)
.with_hint("Do not specify the ONLY keyword."));
}
if partition {
return Err(Error::new(
SqlState::INVALID_TABLE_DEFINITION,
format!("cannot {action} a partition"),
));
}
Ok(())
}
fn not_identity(column: &str, table: &str) -> Error {
Error::new(
SqlState::OBJECT_NOT_IN_PREREQUISITE_STATE,
format!("column \"{column}\" of relation \"{table}\" is not an identity column"),
)
}
impl Definer<'_, '_> {
fn identity_column(&self, target: &Target, column: &str) -> Result<(i16, u32)> {
let rel = self.catalog.relation(target.oid);
if let Some(number) = rel.and_then(|r| r.column_number(column)) {
let ty = rel.and_then(|r| r.column(number)).map_or(0, |c| c.ty);
return Ok((number, ty));
}
system_column(column).ok_or_else(|| no_column(column, &target.name))
}
fn is_partition(&self, target: &Target) -> bool {
self.catalog.relation(target.oid).is_some_and(|r| r.partition_of.is_some())
}
fn partition_columns(&self, target: &Target, column: &str) -> Result<Vec<(Target, i16)>> {
let mut out = Vec::new();
if target.kind != b'p' {
return Ok(out);
}
for child in self.all_inheritors(target.oid) {
let number = self.catalog.relation(child.oid).and_then(|r| r.column_number(column));
let Some(number) = number else { return Err(no_column(column, &child.name)) };
out.push((child, number));
}
Ok(out)
}
pub(super) fn add_identity(
&mut self,
target: &Target,
column: &str,
con: &Constraint,
recurse: bool,
) -> Result<()> {
let partition = self.is_partition(target);
check_partitions(target, partition, recurse, "add identity to a column of")?;
let (number, ty) = self.identity_column(target, column)?;
let sequence =
self.identity_sequence(&target.name, target.namespace, column, ty, &con.options)?;
let info = self.identity_sequence_info(&sequence.options)?;
let unlogged = self.is_unlogged(target.oid);
let oid = self.new_sequence(sequence.namespace, &sequence.name, info, unlogged)?;
let table = (target.oid, target.namespace, target.name.as_str());
self.own_identity_sequence(oid, sequence.namespace, table, number)?;
self.check_add_identity(target, number, column)?;
self.catalog.set_identity(target.oid, number, con.generated_when)?;
for (child, number) in self.partition_columns(target, column)? {
self.check_add_identity(&child, number, column)?;
self.catalog.set_identity(child.oid, number, con.generated_when)?;
}
Ok(())
}
fn check_add_identity(&self, target: &Target, number: i16, column: &str) -> Result<()> {
let Some(attr) = self.catalog.relation(target.oid).and_then(|r| r.column(number)) else {
return Err(system(column));
};
let prerequisite = |message: String| {
Error::new(
SqlState::OBJECT_NOT_IN_PREREQUISITE_STATE,
format!("column \"{column}\" of relation \"{}\" {message}", target.name),
)
};
if !attr.not_null {
return Err(prerequisite(
"must be declared NOT NULL before identity can be added".into(),
));
}
if attr.identity != 0 {
return Err(prerequisite("is already an identity column".into()));
}
if attr.has_default {
return Err(prerequisite("already has a default value".into()));
}
Ok(())
}
pub(super) fn set_identity(
&mut self,
target: &Target,
column: &str,
options: &[Option<Node>],
recurse: bool,
) -> Result<()> {
let partition = self.is_partition(target);
check_partitions(target, partition, recurse, "change identity column of")?;
let (generated, rest): (Vec<_>, Vec<_>) = options.iter().cloned().partition(|option| {
matches!(option, Some(Node::DefElem(def)) if def.defname.as_deref() == Some("generated"))
});
let (number, _) = self.identity_column(target, column)?;
if number <= 0 {
return Err(system(column));
}
if !rest.is_empty() {
let Some(sequence) = self.catalog.identity_sequence(target.oid, number) else {
return Err(not_identity(column, &target.name));
};
let (namespace, name) = self
.catalog
.relation(sequence)
.map(|r| (r.namespace, r.name.clone()))
.unwrap_or_default();
let rv = RangeVar {
schemaname: Some(self.schema_name(namespace).into()),
relname: Some(name.into()),
inh: true,
location: -1,
..RangeVar::default()
};
let stmt = AlterSeqStmt {
sequence: Some(Box::new(rv)),
options: rest,
for_identity: true,
missing_ok: false,
};
self.alter_sequence(&stmt)?;
}
let mut when: Option<&DefElem> = None;
for option in &generated {
let Some(Node::DefElem(def)) = option else { continue };
if when.is_some() {
return Err(Error::new(SqlState::SYNTAX_ERROR, "conflicting or redundant options"));
}
when = Some(def);
}
let identity = self
.catalog
.relation(target.oid)
.and_then(|r| r.column(number))
.map_or(0, |c| c.identity);
if identity == 0 {
return Err(not_identity(column, &target.name));
}
if let Some(def) = when {
let value = match &def.arg {
Some(Node::Integer(value)) => u8::try_from(*value).unwrap_or(identity),
_ => identity,
};
self.catalog.set_identity(target.oid, number, value)?;
for (child, number) in self.partition_columns(target, column)? {
self.catalog.set_identity(child.oid, number, value)?;
}
}
Ok(())
}
pub(super) fn drop_identity(
&mut self,
target: &Target,
column: &str,
missing_ok: bool,
recurse: bool,
) -> Result<()> {
let partition = self.is_partition(target);
check_partitions(target, partition, recurse, "drop identity from a column of")?;
let (number, _) = self.identity_column(target, column)?;
if number <= 0 {
return Err(system(column));
}
let identity = self
.catalog
.relation(target.oid)
.and_then(|r| r.column(number))
.map_or(0, |c| c.identity);
if identity == 0 {
let error = not_identity(column, &target.name);
if !missing_ok {
return Err(error);
}
self.notice(SqlState::SUCCESSFUL_COMPLETION, format!("{}, skipping", error.message()));
return Ok(());
}
self.catalog.set_identity(target.oid, number, 0)?;
for (child, number) in self.partition_columns(target, column)? {
self.catalog.set_identity(child.oid, number, 0)?;
}
let Some(sequence) = self.catalog.identity_sequence(target.oid, number) else {
return Ok(());
};
self.catalog.set_owned_by(sequence, None, DepKind::Internal);
self.perform(&[ObjRef::new(PG_CLASS, sequence)], false)
}
}