use super::super::{
dml_storage_error, referrers_to_for_actions, BTreeMap, BTreeSet, Engine, SQLError, Value,
};
pub(super) fn can_patch_update_without_full_row(
engine: &Engine,
table: &str,
updates: &BTreeMap<String, Value>,
) -> Result<bool, SQLError> {
if engine
.try_check_constraint_definitions(table)
.map_err(|err| dml_storage_error("UPDATE", err))?
.iter()
.any(|constraint| constraint.enforced)
{
return Ok(false);
}
let update_keys: BTreeSet<&str> = updates.keys().map(String::as_str).collect();
if engine
.try_describe_table(table)
.map_err(|err| dml_storage_error("UPDATE", err))?
.ok_or_else(|| SQLError::UnknownTable(table.to_string()))?
.iter()
.any(|col| {
col.not_null
&& col.auto_increment.is_none()
&& matches!(updates.get(&col.name), Some(Value::Null))
})
{
return Ok(false);
}
if engine
.enforced_keys(table)
.map_err(|err| dml_storage_error("UPDATE", err))?
.iter()
.any(|constraint| {
constraint.predicate.as_deref().is_some_and(|predicate| {
update_keys.iter().any(|column| {
crate::engine_table_storage::schema_expr_references_column(predicate, column)
})
}) || constraint.keys.iter().any(|key| {
update_keys.iter().any(|column| {
crate::engine_table_storage::schema_expr_references_column(
&key.expression(),
column,
)
})
})
})
{
return Ok(false);
}
if engine
.try_foreign_keys(table)
.map_err(|err| dml_storage_error("UPDATE", err))?
.iter()
.filter(|fk| fk.enforced)
.any(|fk| {
fk.local_columns
.iter()
.any(|column| update_keys.contains(column.as_str()))
})
{
return Ok(false);
}
if referrers_to_for_actions(engine, table)?
.iter()
.any(|(_, fk)| {
fk.ref_columns
.iter()
.any(|column| update_keys.contains(column.as_str()))
})
{
return Ok(false);
}
Ok(true)
}
pub(super) fn point_lookup_field_is_unique(
engine: &Engine,
table: &str,
lookup_field: &str,
) -> Result<bool, SQLError> {
Ok(engine
.enforced_keys(table)
.map_err(|err| dml_storage_error("UPDATE", err))?
.iter()
.any(|constraint| {
constraint.predicate.is_none()
&& constraint.keys.len() == 1
&& constraint.keys[0].column() == Some(lookup_field)
}))
}