sql_splitter/differ/output/
text.rs1use crate::differ::DiffResult;
4
5pub fn format_text(result: &DiffResult) -> String {
7 let mut output = String::new();
8
9 if let Some(ref schema) = result.schema {
11 output.push_str("Schema Changes:\n");
12
13 if schema.tables_added.is_empty()
14 && schema.tables_removed.is_empty()
15 && schema.tables_modified.is_empty()
16 {
17 output.push_str(" (no schema changes)\n");
18 } else {
19 for table in &schema.tables_added {
21 output.push_str(&format!(" + Table '{}' (new)\n", table.name));
22 for col in &table.columns {
23 let nullable = if col.is_nullable { "NULL" } else { "NOT NULL" };
24 let pk = if col.is_primary_key { " [PK]" } else { "" };
25 output.push_str(&format!(
26 " + {} {} {}{}\n",
27 col.name, col.col_type, nullable, pk
28 ));
29 }
30 }
31
32 for table_name in &schema.tables_removed {
34 output.push_str(&format!(" - Table '{}' (removed)\n", table_name));
35 }
36
37 for modification in &schema.tables_modified {
39 output.push_str(&format!(" ~ Table '{}':\n", modification.table_name));
40
41 for col in &modification.columns_added {
42 let nullable = if col.is_nullable { "NULL" } else { "NOT NULL" };
43 output.push_str(&format!(
44 " + Column '{}' {} {}\n",
45 col.name, col.col_type, nullable
46 ));
47 }
48
49 for col in &modification.columns_removed {
50 output.push_str(&format!(" - Column '{}' {}\n", col.name, col.col_type));
51 }
52
53 for change in &modification.columns_modified {
54 let mut changes = Vec::new();
55 if let (Some(old_type), Some(new_type)) = (&change.old_type, &change.new_type) {
56 changes.push(format!("{} → {}", old_type, new_type));
57 }
58 if let (Some(old_null), Some(new_null)) =
59 (change.old_nullable, change.new_nullable)
60 {
61 let old_str = if old_null { "NULL" } else { "NOT NULL" };
62 let new_str = if new_null { "NULL" } else { "NOT NULL" };
63 changes.push(format!("{} → {}", old_str, new_str));
64 }
65 output.push_str(&format!(
66 " ~ Column '{}': {}\n",
67 change.name,
68 changes.join(", ")
69 ));
70 }
71
72 if modification.pk_changed {
73 let old_pk = modification
74 .old_pk
75 .as_ref()
76 .map(|pk| pk.join(", "))
77 .unwrap_or_else(|| "(none)".to_string());
78 let new_pk = modification
79 .new_pk
80 .as_ref()
81 .map(|pk| pk.join(", "))
82 .unwrap_or_else(|| "(none)".to_string());
83 output.push_str(&format!(
84 " ~ PRIMARY KEY: ({}) → ({})\n",
85 old_pk, new_pk
86 ));
87 }
88
89 for fk in &modification.fks_added {
90 output.push_str(&format!(
91 " + FK ({}) → {}.({}))\n",
92 fk.columns.join(", "),
93 fk.referenced_table,
94 fk.referenced_columns.join(", ")
95 ));
96 }
97
98 for fk in &modification.fks_removed {
99 output.push_str(&format!(
100 " - FK ({}) → {}.({}))\n",
101 fk.columns.join(", "),
102 fk.referenced_table,
103 fk.referenced_columns.join(", ")
104 ));
105 }
106
107 for idx in &modification.indexes_added {
108 let unique_marker = if idx.is_unique { " [unique]" } else { "" };
109 let type_marker = idx
110 .index_type
111 .as_ref()
112 .map(|t| format!(" [{}]", t))
113 .unwrap_or_default();
114 output.push_str(&format!(
115 " + Index '{}' on ({}){}{}\n",
116 idx.name,
117 idx.columns.join(", "),
118 unique_marker,
119 type_marker
120 ));
121 }
122
123 for idx in &modification.indexes_removed {
124 let unique_marker = if idx.is_unique { " [unique]" } else { "" };
125 let type_marker = idx
126 .index_type
127 .as_ref()
128 .map(|t| format!(" [{}]", t))
129 .unwrap_or_default();
130 output.push_str(&format!(
131 " - Index '{}' on ({}){}{}\n",
132 idx.name,
133 idx.columns.join(", "),
134 unique_marker,
135 type_marker
136 ));
137 }
138 }
139 }
140
141 output.push('\n');
142 }
143
144 if let Some(ref data) = result.data {
146 output.push_str("Data Changes:\n");
147
148 if data.tables.is_empty() {
149 output.push_str(" (no data changes)\n");
150 } else {
151 let mut table_names: Vec<_> = data.tables.keys().collect();
153 table_names.sort();
154
155 for table_name in table_names {
156 let diff = &data.tables[table_name];
157
158 if diff.added_count == 0 && diff.removed_count == 0 && diff.modified_count == 0 {
160 continue;
161 }
162
163 let mut parts = Vec::new();
164 if diff.added_count > 0 {
165 parts.push(format!("+{} rows", diff.added_count));
166 }
167 if diff.removed_count > 0 {
168 parts.push(format!("-{} rows", diff.removed_count));
169 }
170 if diff.modified_count > 0 {
171 parts.push(format!("~{} modified", diff.modified_count));
172 }
173
174 let truncated_note = if diff.truncated { " [truncated]" } else { "" };
175
176 output.push_str(&format!(
177 " Table '{}': {}{}\n",
178 table_name,
179 parts.join(", "),
180 truncated_note
181 ));
182
183 if !diff.sample_added_pks.is_empty() {
185 let samples = &diff.sample_added_pks;
186 let remaining = diff.added_count as usize - samples.len();
187 let suffix = if remaining > 0 {
188 format!("... (+{} more)", remaining)
189 } else {
190 String::new()
191 };
192 output.push_str(&format!(
193 " Added PKs: {}{}\n",
194 samples.join(", "),
195 suffix
196 ));
197 }
198
199 if !diff.sample_removed_pks.is_empty() {
200 let samples = &diff.sample_removed_pks;
201 let remaining = diff.removed_count as usize - samples.len();
202 let suffix = if remaining > 0 {
203 format!("... (+{} more)", remaining)
204 } else {
205 String::new()
206 };
207 output.push_str(&format!(
208 " Removed PKs: {}{}\n",
209 samples.join(", "),
210 suffix
211 ));
212 }
213
214 if !diff.sample_modified_pks.is_empty() {
215 let samples = &diff.sample_modified_pks;
216 let remaining = diff.modified_count as usize - samples.len();
217 let suffix = if remaining > 0 {
218 format!("... (+{} more)", remaining)
219 } else {
220 String::new()
221 };
222 output.push_str(&format!(
223 " Modified PKs: {}{}\n",
224 samples.join(", "),
225 suffix
226 ));
227 }
228 }
229 }
230
231 output.push('\n');
232 }
233
234 if !result.warnings.is_empty() {
236 output.push_str("Warnings:\n");
237 for warning in &result.warnings {
238 if let Some(ref table) = warning.table {
239 output.push_str(&format!(" ⚠ Table '{}': {}\n", table, warning.message));
240 } else {
241 output.push_str(&format!(" ⚠ {}\n", warning.message));
242 }
243 }
244 output.push('\n');
245 }
246
247 output.push_str("Summary:\n");
249 output.push_str(&format!(
250 " {} tables added, {} removed, {} modified\n",
251 result.summary.tables_added, result.summary.tables_removed, result.summary.tables_modified
252 ));
253 output.push_str(&format!(
254 " {} rows added, {} removed, {} modified\n",
255 result.summary.rows_added, result.summary.rows_removed, result.summary.rows_modified
256 ));
257
258 if result.summary.truncated {
259 output.push_str(" (some tables truncated due to memory limits)\n");
260 }
261
262 output
263}