use std::collections::{BTreeMap, BTreeSet};
use crate::ast::{ColumnDef, ForeignKey, TableConstraintSet};
use crate::SQLError;
#[cfg(test)]
mod tests;
fn local_name(table: &str) -> Result<String, SQLError> {
uqa_core::RelationIdentity::from_legacy_name(table)
.map(|relation| relation.name)
.map_err(SQLError::Internal)
}
pub fn attachable_foreign_key<'a>(
partition: &str,
parent_key: &ForeignKey,
candidates: &'a [ForeignKey],
) -> Result<Option<&'a ForeignKey>, SQLError> {
let mut ordered = candidates.iter().collect::<Vec<_>>();
ordered.sort_by(|left, right| left.name.cmp(&right.name));
for candidate in ordered {
if candidate.ref_table != parent_key.ref_table
|| candidate.local_columns != parent_key.local_columns
|| candidate.ref_columns != parent_key.ref_columns
|| candidate.period != parent_key.period
{
continue;
}
if candidate.enforced != parent_key.enforced {
return Err(SQLError::Diagnostic {
sqlstate: "42P16".into(),
message: format!(
"constraint \"{}\" enforceability conflicts with constraint \"{}\" on relation \"{}\"",
parent_key.name.as_deref().unwrap_or_default(),
candidate.name.as_deref().unwrap_or_default(),
local_name(partition)?
),
detail: None,
hint: None,
});
}
if candidate.deferrable == parent_key.deferrable
&& candidate.initially_deferred == parent_key.initially_deferred
&& candidate.on_update == parent_key.on_update
&& candidate.on_delete == parent_key.on_delete
&& candidate.match_type == parent_key.match_type
{
return Ok(Some(candidate));
}
}
Ok(None)
}
pub fn partition_foreign_key_copy(
parent_key: &ForeignKey,
used: &BTreeSet<String>,
schema: &mut BTreeSet<String>,
) -> Result<ForeignKey, SQLError> {
let mut copy = parent_key.clone();
copy.catalog_identity = None;
copy.referenced_partitions.clear();
if let Some(name) = copy.name.as_ref().filter(|name| used.contains(*name)) {
copy.name = Some(
crate::schema::constraint_metadata::choose_suffixed_constraint_name(name, schema)
.map_err(|error| SQLError::Internal(error.to_string()))?,
);
}
Ok(copy)
}
pub fn declared_foreign_key_families(
columns: &[ColumnDef],
constraints: &TableConstraintSet,
) -> BTreeSet<[u8; 16]> {
columns
.iter()
.filter_map(|column| column.references.as_ref()?.object_id)
.chain(
constraints
.foreign_keys
.iter()
.filter_map(|foreign_key| foreign_key.object_id),
)
.collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeclaredForeignKey {
Column(usize),
Table(usize),
}
impl DeclaredForeignKey {
pub fn by_catalog_identity(
columns: &[ColumnDef],
constraints: &TableConstraintSet,
identity: crate::ast::ConstraintCatalogIdentity,
) -> Option<Self> {
columns
.iter()
.position(|column| {
column
.references
.as_ref()
.is_some_and(|reference| reference.catalog_identity == Some(identity))
})
.map(Self::Column)
.or_else(|| {
constraints
.foreign_keys
.iter()
.position(|foreign_key| foreign_key.catalog_identity == Some(identity))
.map(Self::Table)
})
}
pub fn by_family(
columns: &[ColumnDef],
constraints: &TableConstraintSet,
object_id: [u8; 16],
) -> Option<Self> {
columns
.iter()
.position(|column| {
column
.references
.as_ref()
.is_some_and(|reference| reference.object_id == Some(object_id))
})
.map(Self::Column)
.or_else(|| {
constraints
.foreign_keys
.iter()
.position(|foreign_key| foreign_key.object_id == Some(object_id))
.map(Self::Table)
})
}
pub fn foreign_key(
self,
columns: &[ColumnDef],
constraints: &TableConstraintSet,
) -> Option<ForeignKey> {
match self {
Self::Column(index) => {
let column = columns.get(index)?;
column.references.as_ref().map(|reference| {
crate::schema::foreign_keys::column_foreign_key(column, reference)
})
}
Self::Table(index) => constraints.foreign_keys.get(index).cloned(),
}
}
pub fn validated(self, columns: &[ColumnDef], constraints: &TableConstraintSet) -> bool {
match self {
Self::Column(index) => columns[index]
.references
.as_ref()
.is_some_and(|reference| reference.validated),
Self::Table(index) => constraints.foreign_keys[index].validated,
}
}
pub fn set_validated(
self,
columns: &mut [ColumnDef],
constraints: &mut TableConstraintSet,
validated: bool,
) {
match self {
Self::Column(index) => {
if let Some(reference) = columns[index].references.as_mut() {
reference.validated = validated;
}
}
Self::Table(index) => constraints.foreign_keys[index].validated = validated,
}
}
pub fn alter(
self,
columns: &mut [ColumnDef],
constraints: &mut TableConstraintSet,
enforceability: Option<bool>,
deferrability: Option<(bool, bool)>,
) -> bool {
let (enforced, validated, deferrable, initially_deferred) = match self {
Self::Column(index) => {
let Some(reference) = columns[index].references.as_mut() else {
return false;
};
(
&mut reference.enforced,
&mut reference.validated,
&mut reference.deferrable,
&mut reference.initially_deferred,
)
}
Self::Table(index) => {
let foreign_key = &mut constraints.foreign_keys[index];
(
&mut foreign_key.enforced,
&mut foreign_key.validated,
&mut foreign_key.deferrable,
&mut foreign_key.initially_deferred,
)
}
};
let mut became_enforced = false;
match enforceability {
Some(false) => {
*enforced = false;
*validated = false;
}
Some(true) if !*enforced => {
*enforced = true;
*validated = false;
became_enforced = true;
}
Some(true) | None => {}
}
if let Some((deferrable_value, initially_deferred_value)) = deferrability {
*deferrable = deferrable_value;
*initially_deferred = initially_deferred_value;
}
became_enforced
}
pub fn set_family(
self,
columns: &mut [ColumnDef],
constraints: &mut TableConstraintSet,
object_id: [u8; 16],
) {
match self {
Self::Column(index) => {
if let Some(reference) = columns[index].references.as_mut() {
reference.object_id = Some(object_id);
}
}
Self::Table(index) => constraints.foreign_keys[index].object_id = Some(object_id),
}
}
}
pub fn rejoin_foreign_key_families(
columns: &mut [ColumnDef],
constraints: &mut TableConstraintSet,
joined: &BTreeMap<[u8; 16], [u8; 16]>,
) -> bool {
let mut changed = false;
for object_id in columns
.iter_mut()
.filter_map(|column| column.references.as_mut())
.map(|reference| &mut reference.object_id)
.chain(
constraints
.foreign_keys
.iter_mut()
.map(|foreign_key| &mut foreign_key.object_id),
)
{
if let Some(target) = object_id.and_then(|current| joined.get(¤t)) {
*object_id = Some(*target);
changed = true;
}
}
changed
}