use crate::Conventions;
use crate::schema::{Column, Table};
pub const INCLUDE_DELETED: &str = "include_deleted";
pub fn apply(tables: &mut [Table], conventions: &Conventions) {
let version = conventions.version_column.as_deref();
for table in tables {
table.version_column = version
.filter(|name| table.columns.iter().any(|column| column.sql_name == *name))
.map(str::to_owned);
for column in &mut table.columns {
column.is_managed = conventions.managed_columns.contains(&column.sql_name)
|| Some(column.sql_name.as_str()) == version;
}
let present = |name: &Option<String>| {
name.as_ref()
.filter(|name| table.columns.iter().any(|column| column.sql_name == **name))
.cloned()
};
let soft_delete_column = present(&conventions.soft_delete_column);
let tenant_column = present(&conventions.tenant_column);
table.soft_delete_column = soft_delete_column;
table.tenant_column = tenant_column;
}
}
pub fn check(tables: &[Table], conventions: &Conventions) -> Result<(), String> {
let Some(name) = conventions.soft_delete_column.as_deref() else {
return Ok(());
};
for table in tables {
let Some(column) = table.columns.iter().find(|column| column.sql_name == name) else {
continue;
};
let subject = format!(
"soft-delete column `{}` of `{}`",
column.sql_name,
table.display_name()
);
if !is_timestamp(column) {
return Err(format!(
"{subject} must be a nullable TIMESTAMP or TIMESTAMPTZ, but its type maps to `{}`",
column.sqlx_type
));
}
if !column.nullable {
return Err(format!(
"{subject} must be a nullable TIMESTAMP or TIMESTAMPTZ, but it is NOT NULL"
));
}
let collision = table
.columns
.iter()
.find(|other| other.rust_name == INCLUDE_DELETED);
if let Some(other) = collision {
return Err(format!(
"column `{}` of `{}` collides with the search filter's `{INCLUDE_DELETED}` switch, which the {subject} adds",
other.sql_name,
table.display_name()
));
}
}
Ok(())
}
fn is_timestamp(column: &Column) -> bool {
column.base_type().is_timestamp()
}