use unisql::ast::{self, InsertSource};
use unisql_macro_core::{CheckContext, Checker, Kind, Problem};
pub(crate) struct PersistantChecker;
fn problem(message: String) -> Problem {
Problem {
kind: Kind::Catalog,
message,
}
}
fn same_set(a: &[String], b: &[String]) -> bool {
a.len() == b.len()
&& a.iter()
.all(|x| b.iter().any(|y| y.eq_ignore_ascii_case(x)))
}
impl Checker for PersistantChecker {
fn check(&self, stmt: &ast::Statement, ctx: &CheckContext<'_>) -> Vec<Problem> {
let (Some(schema), ast::Statement::Insert(i)) = (ctx.schema, stmt) else {
return Vec::new();
};
let mut out = Vec::new();
let table = i.table.last();
let names_columns =
!i.columns.is_empty() || matches!(i.source, InsertSource::DefaultValues);
if names_columns && let Some(all) = schema.complete_columns(&i.table) {
for name in all {
if i.columns.iter().any(|c| c.eq_ignore_ascii_case(name)) {
continue;
}
if let Some(col) = schema.get(&i.table, name)
&& col.nullable == Some(false)
&& col.has_default == Some(false)
{
out.push(problem(format!(
"column `{name}` of `{table}` is NOT NULL with no default, but the INSERT leaves it out"
)));
}
}
}
if let Some(oc) = &i.on_conflict
&& !oc.target.is_empty()
&& let Some(keys) = schema.unique_keys(&i.table)
&& !keys.iter().any(|k| same_set(k, &oc.target))
{
let have: Vec<String> = keys.iter().map(|k| format!("({})", k.join(", "))).collect();
out.push(problem(format!(
"ON CONFLICT ({}) is not a unique key of `{table}` (its unique keys: {})",
oc.target.join(", "),
have.join(", ")
)));
}
out
}
}