use vespertide_core::{CheckViolationStrategy, TableConstraint};
use super::super::helpers::quote_ident;
use super::super::types::{BuiltQuery, DatabaseBackend, RawSql};
use super::{QueryError, TableDef, rebuild_sqlite_table_with_added_constraint};
#[expect(
clippy::too_many_arguments,
reason = "CHECK builder mirrors action fields plus SQLite schema context plus the F4 cleanup strategy; ConstraintContext is a deferred refactor"
)]
pub(super) fn build_check(
backend: DatabaseBackend,
table: &str,
name: &str,
expr: &str,
strategy: &CheckViolationStrategy,
constraint: &TableConstraint,
current_schema: &[TableDef],
pending_constraints: &[TableConstraint],
) -> Result<Vec<BuiltQuery>, QueryError> {
let cleanup = build_check_violation_cleanup(backend, table, expr, strategy)?;
if backend == DatabaseBackend::Sqlite {
let mut queries = cleanup;
queries.extend(rebuild_sqlite_table_with_added_constraint(
backend,
table,
constraint,
current_schema,
pending_constraints,
)?);
return Ok(queries);
}
let mut queries = cleanup;
let quoted_table = quote_ident(table, backend);
let quoted_name = quote_ident(name, backend);
if backend == DatabaseBackend::Postgres {
let add_not_valid = format!(
"ALTER TABLE {quoted_table} ADD CONSTRAINT {quoted_name} CHECK ({expr}) NOT VALID"
);
let validate = format!("ALTER TABLE {quoted_table} VALIDATE CONSTRAINT {quoted_name}");
queries.push(BuiltQuery::Raw(RawSql::uniform(add_not_valid)));
queries.push(BuiltQuery::Raw(RawSql::uniform(validate)));
} else {
let mysql_sql =
format!("ALTER TABLE {quoted_table} ADD CONSTRAINT {quoted_name} CHECK ({expr})");
queries.push(BuiltQuery::Raw(RawSql::uniform(mysql_sql)));
}
Ok(queries)
}
fn build_check_violation_cleanup(
backend: DatabaseBackend,
table: &str,
expr: &str,
strategy: &CheckViolationStrategy,
) -> Result<Vec<BuiltQuery>, QueryError> {
let quoted_table = quote_ident(table, backend);
let sql = match strategy {
CheckViolationStrategy::NullifyViolatingColumn { column } => {
let quoted_col = quote_ident(column.as_str(), backend);
format!("UPDATE {quoted_table} SET {quoted_col} = NULL WHERE NOT ({expr})")
}
CheckViolationStrategy::DeleteViolatingRows => {
format!("DELETE FROM {quoted_table} WHERE NOT ({expr})")
}
#[cfg(not(tarpaulin_include))]
_ => {
return Err(QueryError::UnsupportedAction(format!(
"AddConstraint(Check) on '{table}': unsupported strategy variant"
)));
}
};
Ok(vec![BuiltQuery::Raw(RawSql::uniform(sql))])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_check_violation_cleanup_nullify_emits_update_set_null() {
let queries = build_check_violation_cleanup(
DatabaseBackend::Postgres,
"orders",
"qty > 0",
&CheckViolationStrategy::NullifyViolatingColumn {
column: "qty".into(),
},
)
.expect("nullify arm should succeed");
assert_eq!(queries.len(), 1);
let sql = queries[0].build(DatabaseBackend::Postgres);
assert!(sql.contains("UPDATE \"orders\""));
assert!(sql.contains("SET \"qty\" = NULL"));
assert!(sql.contains("WHERE NOT (qty > 0)"));
}
}