use super::{find_constraint, ConstraintLocation};
use crate::{
ast::{ColumnDef, TableConstraintSet},
SQLError,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ForeignKeyTarget<'a> {
pub location: ConstraintLocation,
pub name: &'a str,
pub object_id: [u8; 16],
pub referenced_table: &'a str,
}
impl<'a> ForeignKeyTarget<'a> {
pub fn by_name(
columns: &'a [ColumnDef],
constraints: &'a TableConstraintSet,
name: &str,
) -> Result<Option<Self>, SQLError> {
find_constraint(columns, constraints, name).map_or(Ok(None), |location| {
Self::at(columns, constraints, location)
})
}
pub fn by_id(
columns: &'a [ColumnDef],
constraints: &'a TableConstraintSet,
object_id: [u8; 16],
) -> Result<Option<Self>, SQLError> {
let location = columns
.iter()
.position(|column| {
column
.references
.as_ref()
.is_some_and(|reference| reference.object_id == Some(object_id))
})
.map(ConstraintLocation::ColumnForeignKey)
.or_else(|| {
constraints
.foreign_keys
.iter()
.position(|reference| reference.object_id == Some(object_id))
.map(ConstraintLocation::TableForeignKey)
});
location.map_or(Ok(None), |location| {
Self::at(columns, constraints, location)
})
}
fn at(
columns: &'a [ColumnDef],
constraints: &'a TableConstraintSet,
location: ConstraintLocation,
) -> Result<Option<Self>, SQLError> {
let (name, object_id, referenced_table) = match location {
ConstraintLocation::ColumnForeignKey(index) => {
let reference = columns[index]
.references
.as_ref()
.ok_or_else(|| SQLError::Internal("column FOREIGN KEY disappeared".into()))?;
(
reference.name.as_deref(),
reference.object_id,
reference.table.as_str(),
)
}
ConstraintLocation::TableForeignKey(index) => {
let reference = &constraints.foreign_keys[index];
(
reference.name.as_deref(),
reference.object_id,
reference.ref_table.as_str(),
)
}
_ => return Ok(None),
};
Ok(Some(Self {
location,
name: name
.ok_or_else(|| SQLError::Internal("FOREIGN KEY has no durable name".into()))?,
object_id: object_id
.ok_or_else(|| SQLError::Internal("FOREIGN KEY has no durable identity".into()))?,
referenced_table,
}))
}
}
pub fn remove_foreign_key(
columns: &mut [ColumnDef],
constraints: &mut TableConstraintSet,
object_id: [u8; 16],
) -> Result<bool, SQLError> {
let Some(target) = ForeignKeyTarget::by_id(columns, constraints, object_id)? else {
return Ok(false);
};
match target.location {
ConstraintLocation::ColumnForeignKey(index) => columns[index].references = None,
ConstraintLocation::TableForeignKey(index) => {
constraints.foreign_keys.remove(index);
}
_ => unreachable!("a foreign-key target has a foreign-key location"),
}
constraints
.hierarchy
.partition_inherited_foreign_keys
.retain(|key| key.object_id != Some(object_id));
Ok(true)
}
#[cfg(test)]
mod tests;