1use crate::safety::quote_identifier;
12use crate::table::TableSpec;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct LiveColumn {
17 pub name: String,
18 pub type_name: String,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct ColumnDiff {
25 pub name: String,
26 pub ch_type: String,
27}
28
29pub fn diff_columns(table: &TableSpec, live: &[LiveColumn]) -> Vec<ColumnDiff> {
36 table
37 .columns
38 .iter()
39 .filter(|c| !live.iter().any(|l| l.name == c.name))
40 .map(|c| ColumnDiff {
41 name: c.name.clone(),
42 ch_type: c.type_spec.to_ch_type(),
43 })
44 .collect()
45}
46
47pub fn alter_add_columns_sql(table: &TableSpec, missing: &[ColumnDiff]) -> Vec<String> {
52 let quoted_table = quote_identifier(&table.name);
53 missing
54 .iter()
55 .map(|d| {
56 format!(
57 "ALTER TABLE {} ADD COLUMN IF NOT EXISTS {} {}",
58 quoted_table,
59 quote_identifier(&d.name),
60 d.ch_type,
61 )
62 })
63 .collect()
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::safety::{ColumnTypeSpec, ScalarType};
70 use crate::table::ColumnSpec;
71
72 fn col(name: &str, t: ColumnTypeSpec) -> ColumnSpec {
73 ColumnSpec {
74 name: name.into(),
75 type_spec: t,
76 default: None,
77 }
78 }
79
80 fn sample() -> TableSpec {
81 TableSpec {
82 name: "events".into(),
83 columns: vec![
84 col("id", ColumnTypeSpec::Scalar(ScalarType::Uuid)),
85 col("ts", ColumnTypeSpec::Scalar(ScalarType::DateTime64)),
86 col("name", ColumnTypeSpec::Scalar(ScalarType::String)),
87 ],
88 engine: "MergeTree()".into(),
89 order_by: vec!["id".into()],
90 partition_by: None,
91 ttl: None,
92 indexes: vec![],
93 settings: vec![],
94 }
95 }
96
97 fn live(name: &str, type_name: &str) -> LiveColumn {
98 LiveColumn {
99 name: name.into(),
100 type_name: type_name.into(),
101 }
102 }
103
104 #[test]
105 fn diff_finds_only_missing_columns() {
106 let table = sample();
107 let live = [live("id", "UUID"), live("extra", "String")];
109 let diff = diff_columns(&table, &live);
110 assert_eq!(
111 diff,
112 vec![
113 ColumnDiff {
114 name: "ts".into(),
115 ch_type: "DateTime64(3)".into(),
116 },
117 ColumnDiff {
118 name: "name".into(),
119 ch_type: "String".into(),
120 },
121 ]
122 );
123 }
124
125 #[test]
126 fn diff_ignores_live_only_and_retyped_columns() {
127 let table = sample();
128 let live = [
132 live("id", "String"),
133 live("ts", "DateTime"),
134 live("name", "Int64"),
135 live("extra", "String"),
136 ];
137 assert!(diff_columns(&table, &live).is_empty());
138 }
139
140 #[test]
141 fn diff_ch_type_comes_from_kit_not_live() {
142 let table = sample();
143 let live = [live("id", "UUID"), live("name", "String")];
146 let diff = diff_columns(&table, &live);
147 assert_eq!(
148 diff,
149 vec![ColumnDiff {
150 name: "ts".into(),
151 ch_type: "DateTime64(3)".into(),
152 }]
153 );
154 }
155
156 #[test]
157 fn alter_emits_add_column_if_not_exists_with_quoted_identifiers() {
158 let table = sample();
159 let missing = vec![
160 ColumnDiff {
161 name: "ts".into(),
162 ch_type: "DateTime64(3)".into(),
163 },
164 ColumnDiff {
165 name: "name".into(),
166 ch_type: "String".into(),
167 },
168 ];
169 let sql = alter_add_columns_sql(&table, &missing);
170 assert_eq!(
171 sql,
172 vec![
173 "ALTER TABLE `events` ADD COLUMN IF NOT EXISTS `ts` DateTime64(3)".to_string(),
174 "ALTER TABLE `events` ADD COLUMN IF NOT EXISTS `name` String".to_string(),
175 ]
176 );
177 }
178
179 #[test]
180 fn alter_backtick_quotes_table_and_column() {
181 let table = sample();
182 let missing = vec![ColumnDiff {
183 name: "name".into(),
184 ch_type: "String".into(),
185 }];
186 let sql = alter_add_columns_sql(&table, &missing);
187 assert_eq!(sql.len(), 1);
188 assert!(sql[0].contains("ALTER TABLE `events`"));
189 assert!(sql[0].contains("ADD COLUMN IF NOT EXISTS `name`"));
190 }
191
192 #[test]
193 fn empty_when_in_sync() {
194 let table = sample();
195 let live = [
196 live("id", "UUID"),
197 live("ts", "DateTime64(3)"),
198 live("name", "String"),
199 ];
200 let diff = diff_columns(&table, &live);
201 assert!(diff.is_empty());
202 assert!(alter_add_columns_sql(&table, &diff).is_empty());
203 }
204}