use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SchemaDiff {
pub tables_added: Vec<String>,
pub tables_removed: Vec<String>,
pub tables_modified: HashMap<String, TableDiff>,
pub views_added: Vec<String>,
pub views_removed: Vec<String>,
pub views_modified: HashMap<String, ViewDiff>,
pub functions_added: Vec<String>,
pub functions_removed: Vec<String>,
pub functions_modified: HashMap<String, FunctionDiff>,
pub types_added: Vec<String>,
pub types_removed: Vec<String>,
pub types_modified: HashMap<String, TypeDiff>,
}
impl SchemaDiff {
pub fn is_empty(&self) -> bool {
self.tables_added.is_empty()
&& self.tables_removed.is_empty()
&& self.tables_modified.is_empty()
&& self.views_added.is_empty()
&& self.views_removed.is_empty()
&& self.views_modified.is_empty()
&& self.functions_added.is_empty()
&& self.functions_removed.is_empty()
&& self.functions_modified.is_empty()
&& self.types_added.is_empty()
&& self.types_removed.is_empty()
&& self.types_modified.is_empty()
}
pub fn change_count(&self) -> usize {
self.tables_added.len()
+ self.tables_removed.len()
+ self.tables_modified.len()
+ self.views_added.len()
+ self.views_removed.len()
+ self.views_modified.len()
+ self.functions_added.len()
+ self.functions_removed.len()
+ self.functions_modified.len()
+ self.types_added.len()
+ self.types_removed.len()
+ self.types_modified.len()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TableDiff {
pub columns_added: Vec<String>,
pub columns_removed: Vec<String>,
pub columns_modified: HashMap<String, ColumnDiff>,
pub primary_key_changed: bool,
pub foreign_keys_added: Vec<String>,
pub foreign_keys_removed: Vec<String>,
pub unique_constraints_added: Vec<String>,
pub unique_constraints_removed: Vec<String>,
pub indexes_added: Vec<String>,
pub indexes_removed: Vec<String>,
pub check_constraints_added: Vec<String>,
pub check_constraints_removed: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ColumnDiff {
pub data_type_changed: bool,
pub old_data_type: Option<String>,
pub new_data_type: Option<String>,
pub nullability_changed: bool,
pub default_changed: bool,
pub old_default: Option<String>,
pub new_default: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ViewDiff {
pub definition_changed: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FunctionDiff {
pub signature_changed: bool,
pub body_changed: bool,
pub return_type_changed: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TypeDiff {
pub definition_changed: bool,
}
pub trait DiffCalculator: Send + Sync {
fn calculate_diff(&self, from: &crate::snapshot::SchemaSnapshot, to: &crate::snapshot::SchemaSnapshot) -> SchemaDiff;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_diff() {
let diff = SchemaDiff {
tables_added: vec![],
tables_removed: vec![],
tables_modified: HashMap::new(),
views_added: vec![],
views_removed: vec![],
views_modified: HashMap::new(),
functions_added: vec![],
functions_removed: vec![],
functions_modified: HashMap::new(),
types_added: vec![],
types_removed: vec![],
types_modified: HashMap::new(),
};
assert!(diff.is_empty());
assert_eq!(diff.change_count(), 0);
}
#[test]
fn test_diff_with_changes() {
let mut diff = SchemaDiff {
tables_added: vec!["users".to_string()],
tables_removed: vec!["old_table".to_string()],
tables_modified: HashMap::new(),
views_added: vec![],
views_removed: vec![],
views_modified: HashMap::new(),
functions_added: vec![],
functions_removed: vec![],
functions_modified: HashMap::new(),
types_added: vec![],
types_removed: vec![],
types_modified: HashMap::new(),
};
assert!(!diff.is_empty());
assert_eq!(diff.change_count(), 2);
let mut table_diff = TableDiff {
columns_added: vec!["email".to_string()],
columns_removed: vec![],
columns_modified: HashMap::new(),
primary_key_changed: false,
foreign_keys_added: vec![],
foreign_keys_removed: vec![],
unique_constraints_added: vec![],
unique_constraints_removed: vec![],
indexes_added: vec![],
indexes_removed: vec![],
check_constraints_added: vec![],
check_constraints_removed: vec![],
};
diff.tables_modified.insert("posts".to_string(), table_diff);
assert_eq!(diff.change_count(), 3);
}
#[test]
fn test_diff_serialization() {
let diff = SchemaDiff {
tables_added: vec!["users".to_string()],
tables_removed: vec![],
tables_modified: HashMap::new(),
views_added: vec![],
views_removed: vec![],
views_modified: HashMap::new(),
functions_added: vec![],
functions_removed: vec![],
functions_modified: HashMap::new(),
types_added: vec![],
types_removed: vec![],
types_modified: HashMap::new(),
};
let json = serde_json::to_string(&diff).unwrap();
let deserialized: SchemaDiff = serde_json::from_str(&json).unwrap();
assert_eq!(diff, deserialized);
}
}