use crate::catalog::operator::Operator;
use crate::catalog::target::AttrTarget;
use crate::diff::comment_utils;
use crate::diff::operations::{MigrationStep, OperatorIdentifier, OperatorOperation};
pub fn diff(old: Option<&Operator>, new: Option<&Operator>) -> Vec<MigrationStep> {
match (old, new) {
(None, Some(new_operator)) => {
let mut steps = vec![MigrationStep::Operator(OperatorOperation::Create {
operator: Box::new(new_operator.clone()),
})];
if let Some(comment_op) = comment_utils::handle_comment_creation(
&new_operator.comment,
AttrTarget::object(new_operator.id()),
) {
steps.push(MigrationStep::Operator(OperatorOperation::Comment(
comment_op,
)));
}
steps
}
(Some(old_operator), None) => {
vec![MigrationStep::Operator(OperatorOperation::Drop {
identifier: OperatorIdentifier::from_operator(old_operator),
})]
}
(Some(old_operator), Some(new_operator)) => {
let mut steps = Vec::new();
if operators_differ_structurally(old_operator, new_operator) {
steps.push(MigrationStep::Operator(OperatorOperation::Replace {
old_operator: Box::new(old_operator.clone()),
new_operator: Box::new(new_operator.clone()),
}));
} else {
let comment_ops = comment_utils::handle_comment_diff(
Some(old_operator),
Some(new_operator),
|| AttrTarget::object(new_operator.id()),
);
for comment_op in comment_ops {
steps.push(MigrationStep::Operator(OperatorOperation::Comment(
comment_op,
)));
}
}
steps
}
(None, None) => vec![],
}
}
fn operators_differ_structurally(old: &Operator, new: &Operator) -> bool {
old.definition != new.definition
}
#[cfg(test)]
mod tests {
use super::*;
use crate::catalog::id::DbObjectId;
fn test_operator(name: &str) -> Operator {
Operator {
schema: "public".to_string(),
name: name.to_string(),
arguments: "integer, integer".to_string(),
definition: format!(
"CREATE OPERATOR public.{} (\n FUNCTION = public.my_eq,\n LEFTARG = integer,\n RIGHTARG = integer\n)",
name
),
comment: None,
depends_on: vec![
DbObjectId::Schema {
name: "public".to_string(),
},
DbObjectId::Function {
schema: "public".to_string(),
name: "my_eq".to_string(),
arguments: "integer, integer".to_string(),
},
],
}
}
#[test]
fn test_diff_no_changes() {
let op = test_operator("===");
assert!(diff(Some(&op), Some(&op)).is_empty());
}
#[test]
fn test_diff_create() {
let op = test_operator("===");
let steps = diff(None, Some(&op));
assert_eq!(steps.len(), 1);
assert!(matches!(
&steps[0],
MigrationStep::Operator(OperatorOperation::Create { .. })
));
}
#[test]
fn test_diff_create_with_comment() {
let mut op = test_operator("===");
op.comment = Some("custom equality".to_string());
let steps = diff(None, Some(&op));
assert_eq!(steps.len(), 2);
assert!(matches!(
&steps[0],
MigrationStep::Operator(OperatorOperation::Create { .. })
));
assert!(matches!(
&steps[1],
MigrationStep::Operator(OperatorOperation::Comment(_))
));
}
#[test]
fn test_diff_drop() {
let op = test_operator("===");
let steps = diff(Some(&op), None);
assert_eq!(steps.len(), 1);
match &steps[0] {
MigrationStep::Operator(OperatorOperation::Drop { identifier }) => {
assert_eq!(identifier.name, "===");
assert_eq!(identifier.arguments, "integer, integer");
}
_ => panic!("Expected OperatorOperation::Drop"),
}
}
#[test]
fn test_diff_replace_on_structural_change() {
let old = test_operator("===");
let mut new = test_operator("===");
new.definition.push_str(",\n HASHES");
let steps = diff(Some(&old), Some(&new));
assert_eq!(steps.len(), 1);
assert!(matches!(
&steps[0],
MigrationStep::Operator(OperatorOperation::Replace { .. })
));
}
#[test]
fn test_diff_comment_only_change() {
let old = test_operator("===");
let mut new = test_operator("===");
new.comment = Some("now documented".to_string());
let steps = diff(Some(&old), Some(&new));
assert_eq!(steps.len(), 1);
assert!(matches!(
&steps[0],
MigrationStep::Operator(OperatorOperation::Comment(_))
));
}
}