use crate::catalog::Catalog;
use crate::catalog::attached::Attached;
use crate::catalog::id::DbObjectId;
use crate::catalog::target::AttrTarget;
use crate::diff::operations::{CommentOperation, MigrationStep};
use std::collections::BTreeMap;
fn set(target: AttrTarget, comment: String) -> MigrationStep {
MigrationStep::Comment(CommentOperation::Set { target, comment })
}
fn drop(target: AttrTarget) -> MigrationStep {
MigrationStep::Comment(CommentOperation::Drop { target })
}
pub fn desired_comment_steps(obj: &dyn Attached) -> Vec<MigrationStep> {
obj.comment_targets()
.into_iter()
.filter_map(|(target, comment)| comment.map(|c| set(target, c)))
.collect()
}
pub fn diff_comments(old: &Catalog, new: &Catalog) -> Vec<MigrationStep> {
let old_by_id: BTreeMap<DbObjectId, Vec<(AttrTarget, Option<String>)>> = old
.attached_objects()
.into_iter()
.map(|o| (o.object_id(), o.comment_targets()))
.collect();
let mut steps = Vec::new();
for obj in new.attached_objects() {
match old_by_id.get(&obj.object_id()) {
None => steps.extend(desired_comment_steps(obj)),
Some(old_targets) => steps.extend(diff_targets(old_targets, &obj.comment_targets())),
}
}
steps
}
fn diff_targets(
old: &[(AttrTarget, Option<String>)],
new: &[(AttrTarget, Option<String>)],
) -> Vec<MigrationStep> {
let old_by_target: BTreeMap<&AttrTarget, &Option<String>> =
old.iter().map(|(t, c)| (t, c)).collect();
let mut steps = Vec::new();
for (target, new_comment) in new {
let old_comment = old_by_target.get(target).and_then(|c| c.as_ref());
match (old_comment, new_comment.as_ref()) {
(None, None) => {}
(Some(o), Some(n)) if o == n => {}
(_, Some(n)) => steps.push(set(target.clone(), n.clone())),
(Some(_), None) => steps.push(drop(target.clone())),
}
}
steps
}
#[cfg(test)]
mod tests {
use super::*;
use crate::catalog::id::DbObjectId;
fn obj(name: &str) -> AttrTarget {
AttrTarget::object(DbObjectId::Table {
schema: "s".into(),
name: name.into(),
})
}
fn col(table: &str, c: &str) -> AttrTarget {
AttrTarget::column(
DbObjectId::Table {
schema: "s".into(),
name: table.into(),
},
c,
)
}
fn some(c: &str) -> Option<String> {
Some(c.to_string())
}
fn kinds(steps: &[MigrationStep]) -> Vec<(&'static str, Option<String>)> {
steps
.iter()
.map(|s| match s {
MigrationStep::Comment(CommentOperation::Set { comment, .. }) => {
("set", Some(comment.clone()))
}
MigrationStep::Comment(CommentOperation::Drop { .. }) => ("drop", None),
other => panic!("unexpected step: {other:?}"),
})
.collect()
}
#[test]
fn in_place_add_change_keep_drop() {
assert_eq!(
kinds(&diff_targets(
&[(obj("t"), None)],
&[(obj("t"), some("hi"))]
)),
vec![("set", some("hi"))]
);
assert!(diff_targets(&[(obj("t"), some("hi"))], &[(obj("t"), some("hi"))]).is_empty());
assert_eq!(
kinds(&diff_targets(
&[(obj("t"), some("old"))],
&[(obj("t"), some("new"))]
)),
vec![("set", some("new"))]
);
assert_eq!(
kinds(&diff_targets(
&[(obj("t"), some("hi"))],
&[(obj("t"), None)]
)),
vec![("drop", None)]
);
}
#[test]
fn added_sub_object_with_comment_emits_set() {
let steps = diff_targets(
&[(obj("t"), None)],
&[(obj("t"), None), (col("t", "c"), some("col"))],
);
assert_eq!(kinds(&steps), vec![("set", some("col"))]);
}
#[test]
fn dropped_sub_object_emits_no_drop() {
let steps = diff_targets(
&[(obj("t"), None), (col("t", "c"), some("col"))],
&[(obj("t"), None)],
);
assert!(steps.is_empty());
}
#[test]
fn desired_comment_steps_emits_present_skips_empty() {
struct Fixture(Vec<(AttrTarget, Option<String>)>);
impl Attached for Fixture {
fn object_id(&self) -> DbObjectId {
DbObjectId::Table {
schema: "s".into(),
name: "f".into(),
}
}
fn own_comment(&self) -> Option<String> {
None
}
fn comment_targets(&self) -> Vec<(AttrTarget, Option<String>)> {
self.0.clone()
}
}
let fixture = Fixture(vec![
(obj("t"), some("a")),
(col("t", "c"), None),
(col("t", "d"), some("b")),
]);
assert_eq!(
kinds(&desired_comment_steps(&fixture)),
vec![("set", some("a")), ("set", some("b"))]
);
}
}