1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use crate::catalog::custom_type::{CustomType, TypeKind};
use crate::diff::operations::{MigrationStep, TypeOperation};
use crate::render::collation::collate_clause;
/// Build the `CREATE TYPE` step for a custom type. Comments (on the type and its
/// composite attributes) are handled centrally by [`crate::diff::comments`].
fn create_step(n: &CustomType) -> MigrationStep {
let (kind, definition) = match &n.kind {
TypeKind::Enum => {
let values: Vec<String> = n
.enum_values
.iter()
.map(|v| format!("'{}'", v.name))
.collect();
("ENUM".to_string(), format!("({})", values.join(", ")))
}
TypeKind::Composite => {
let attributes: Vec<String> = n
.composite_attributes
.iter()
.map(|attr| {
format!(
"{} {}{}",
attr.name,
attr.type_name,
collate_clause(attr.collation.as_ref())
)
})
.collect();
(
"COMPOSITE".to_string(),
format!("({})", attributes.join(", ")),
)
}
// Range types would need more info from the catalog.
TypeKind::Range => ("RANGE".to_string(), String::new()),
TypeKind::Other(t) => (format!("TYPE ({})", t), String::new()),
};
MigrationStep::Type(TypeOperation::Create {
schema: n.schema.clone(),
name: n.name.clone(),
kind,
definition,
})
}
fn drop_step(o: &CustomType) -> MigrationStep {
MigrationStep::Type(TypeOperation::Drop {
schema: o.schema.clone(),
name: o.name.clone(),
})
}
/// Diff a single custom type's structure.
pub fn diff(old: Option<&CustomType>, new: Option<&CustomType>) -> Vec<MigrationStep> {
match (old, new) {
(None, Some(n)) => vec![create_step(n)],
(Some(o), None) => vec![drop_step(o)],
(Some(o), Some(n)) => {
// A change of kind (very unusual) requires drop + recreate.
if o.kind != n.kind {
return vec![drop_step(o), create_step(n)];
}
match n.kind {
TypeKind::Enum => {
let old_values: Vec<&String> = o.enum_values.iter().map(|v| &v.name).collect();
let new_values: Vec<&String> = n.enum_values.iter().map(|v| &v.name).collect();
if old_values == new_values {
// Only comments could have changed — handled centrally.
Vec::new()
} else if old_values.iter().all(|v| new_values.contains(v)) {
// Only added values: emit one ALTER TYPE ADD VALUE per new value
// (PostgreSQL can't add several in one statement).
let added_values: Vec<String> = n
.enum_values
.iter()
.filter(|v| !old_values.contains(&&v.name))
.map(|v| v.name.clone())
.collect();
if added_values.is_empty() {
// No new values, but order changed: requires drop + recreate.
return vec![drop_step(o), create_step(n)];
}
// Add the first new value after the last existing one — the
// labels are carried in label order — and each subsequent
// value after the previous new one.
let after_clause = match old_values.last() {
Some(last_enum_value) => format!(" AFTER '{}'", last_enum_value),
None => String::new(),
};
added_values
.iter()
.enumerate()
.map(|(i, value)| {
let after = if i == 0 {
after_clause.clone()
} else {
format!(" AFTER '{}'", added_values[i - 1])
};
MigrationStep::Type(TypeOperation::Alter {
schema: n.schema.clone(),
name: n.name.clone(),
action: "ADD VALUE".to_string(),
definition: format!("'{}'{}", value, after),
})
})
.collect()
} else {
// Values were removed (or added and removed): drop + recreate.
vec![drop_step(o), create_step(n)]
}
}
TypeKind::Composite => {
// Compare attribute structure (name + type + collation); comments
// are diffed centrally, so a comment-only change never lands here.
// Collation changes take the drop/recreate path: ALTER TYPE ...
// ALTER ATTRIBUTE ... TYPE resets the collation to the type's
// default unless COLLATE is restated, so recreating is the
// simplest correct move for this rare change.
let attr_key = |attr: &'_ crate::catalog::custom_type::CompositeAttribute| {
(
attr.name.clone(),
attr.type_name.clone(),
attr.collation.clone(),
)
};
let old_attrs: Vec<_> = o.composite_attributes.iter().map(attr_key).collect();
let new_attrs: Vec<_> = n.composite_attributes.iter().map(attr_key).collect();
if old_attrs != new_attrs {
vec![drop_step(o), create_step(n)]
} else {
Vec::new()
}
}
// Other type kinds: nothing structural to do in place.
_ => Vec::new(),
}
}
(None, None) => Vec::new(),
}
}