use qail_core::prelude::{Action, Expr, Qail};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MigrationClass {
Reversible,
DataLosing,
Irreversible,
}
impl std::fmt::Display for MigrationClass {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MigrationClass::Reversible => write!(f, "reversible"),
MigrationClass::DataLosing => write!(f, "data-losing"),
MigrationClass::Irreversible => write!(f, "irreversible"),
}
}
}
pub fn classify_migration(cmd: &Qail) -> MigrationClass {
match cmd.action {
Action::Make => MigrationClass::Reversible,
Action::Index => MigrationClass::Reversible,
Action::Drop => MigrationClass::DataLosing,
Action::AlterDrop => MigrationClass::DataLosing,
Action::DropIndex => MigrationClass::Reversible,
Action::AlterType => {
if let Some(Expr::Def { data_type, .. }) = cmd.columns.first() {
let target = data_type.as_str();
if is_narrowing_type(target) {
MigrationClass::Irreversible
} else {
MigrationClass::Reversible
}
} else {
MigrationClass::Reversible
}
}
Action::Alter => MigrationClass::Reversible,
Action::Mod => MigrationClass::Reversible,
_ => MigrationClass::Reversible,
}
}
pub fn is_narrowing_type(target: &str) -> bool {
matches!(
target.to_uppercase().as_str(),
"INT"
| "INTEGER"
| "BIGINT"
| "SMALLINT"
| "BOOLEAN"
| "BOOL"
| "UUID"
| "NUMERIC"
| "DECIMAL"
| "REAL"
| "FLOAT"
| "DOUBLE PRECISION"
| "DATE"
| "TIME"
| "TIMESTAMP"
| "TIMESTAMPTZ"
)
}
pub fn is_safe_cast(from: &str, to: &str) -> bool {
let from_upper = from.to_uppercase();
let to_upper = to.to_uppercase();
if from_upper == to_upper {
return true;
}
if to_upper == "TEXT" || to_upper == "VARCHAR" {
return true;
}
if (from_upper == "INT" || from_upper == "INTEGER")
&& (to_upper == "BIGINT" || to_upper == "TEXT")
{
return true;
}
if from_upper == "SMALLINT"
&& (to_upper == "INT" || to_upper == "INTEGER" || to_upper == "BIGINT")
{
return true;
}
if is_narrowing_type(&to_upper) && (from_upper == "TEXT" || from_upper == "VARCHAR") {
return false;
}
!is_narrowing_type(&to_upper)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_narrowing_type() {
assert!(is_narrowing_type("INT"));
assert!(is_narrowing_type("integer"));
assert!(is_narrowing_type("BOOLEAN"));
assert!(is_narrowing_type("UUID"));
assert!(!is_narrowing_type("TEXT"));
assert!(!is_narrowing_type("VARCHAR"));
}
#[test]
fn test_is_safe_cast() {
assert!(is_safe_cast("INT", "TEXT"));
assert!(is_safe_cast("INT", "BIGINT"));
assert!(is_safe_cast("SMALLINT", "INT"));
assert!(is_safe_cast("TEXT", "TEXT"));
assert!(!is_safe_cast("TEXT", "INT"));
}
#[test]
fn test_classify_alter_type_to_int() {
let mut cmd = Qail::make("inquiries");
cmd.action = Action::AlterType;
cmd.columns = vec![Expr::Def {
name: "priority".to_string(),
data_type: "INT".to_string(),
constraints: vec![],
}];
assert_eq!(classify_migration(&cmd), MigrationClass::Irreversible);
}
}