use vibesql_storage::DeferredFkViolation;
use crate::errors::ExecutorError;
use crate::foreign_key_check::{check_fk_row_existence, FkRowCheck};
pub fn validate_foreign_key_constraints(
db: &mut vibesql_storage::Database,
table_name: &str,
row_values: &[vibesql_types::SqlValue],
) -> Result<(), ExecutorError> {
if !db.foreign_keys_enabled() {
return Ok(());
}
let schema = db
.catalog
.get_table(table_name)
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?
.clone();
let mut deferred: Vec<DeferredFkViolation> = Vec::new();
for (fk_idx, fk) in schema.foreign_keys.iter().enumerate() {
if let Some((child, parent)) =
crate::foreign_key_check::detect_fk_mismatch(db, table_name, fk)
{
return Err(ExecutorError::ForeignKeyMismatch { child, parent });
}
let fk_values: Vec<vibesql_types::SqlValue> =
fk.column_indices.iter().map(|&idx| row_values[idx].clone()).collect();
if fk_values.iter().any(|v| v.is_null()) {
continue;
}
match check_fk_row_existence(db, table_name, fk, fk_idx, &fk_values, row_values, &[])? {
FkRowCheck::Ok => continue,
FkRowCheck::Deferred(v) => {
deferred.push(v);
continue;
}
FkRowCheck::Violation => {
return Err(ExecutorError::ConstraintViolation(format!(
"FOREIGN KEY constraint '{}' violated: key ({}) not found in table '{}'",
fk.name.as_deref().unwrap_or(""),
fk.column_names.join(", "),
fk.parent_table
)));
}
}
}
for v in deferred {
db.queue_deferred_fk_violation(v);
}
Ok(())
}