Skip to main content

client_core/sql_diff/
generator.rs

1use super::differ::generate_mysql_diff;
2use super::parser::parse_sql_tables;
3use super::types::{TableColumn, TableDefinition, TableIndex};
4use crate::error::DuckError;
5use crate::mysql_executor::MySqlExecutor;
6use tracing::info;
7
8/// 生成SQL架构差异
9/// 这个版本专注于生成实际可执行的MySQL差异SQL,而不是简单的文本比较
10pub fn generate_schema_diff(
11    from_sql: Option<&str>,
12    to_sql: &str,
13    from_version: Option<&str>,
14    to_version: &str,
15) -> Result<(String, String), DuckError> {
16    match from_sql {
17        None => {
18            // 初始版本,返回完整的创建脚本
19            info!(
20                "Generating complete database schema for initial version {}",
21                to_version
22            );
23            let description = format!(
24                "Complete database schema for initial version {}",
25                to_version
26            );
27            Ok((to_sql.to_string(), description))
28        }
29        Some(from_content) => {
30            info!(
31                "Starting to generate SQL diff from version {} to {}",
32                from_version.unwrap_or("unknown"),
33                to_version
34            );
35
36            // 如果内容完全相同,返回空差异
37            if from_content.trim() == to_sql.trim() {
38                info!("Version content is identical, no diff needed");
39                return Ok((
40                    String::new(),
41                    format!(
42                        "Version {} to {}: No changes",
43                        from_version.unwrap_or("unknown"),
44                        to_version
45                    ),
46                ));
47            }
48
49            // 解析两个SQL文件的表结构
50            let from_tables = parse_sql_tables(from_content)?;
51            let to_tables = parse_sql_tables(to_sql)?;
52
53            // 生成差异SQL
54            let (diff_sql, _stats) = generate_mysql_diff(&from_tables, &to_tables)?;
55
56            let description = if diff_sql.trim().is_empty() {
57                format!(
58                    "Version {} to {}: only comments or format changes, no actual schema differences",
59                    from_version.unwrap_or("unknown"),
60                    to_version
61                )
62            } else {
63                let lines_count = diff_sql
64                    .lines()
65                    .filter(|line| !line.trim().is_empty() && !line.trim().starts_with("--"))
66                    .count();
67
68                // 分析差异类型
69                let mut change_types = Vec::new();
70                if diff_sql.contains("CREATE TABLE") {
71                    change_types.push("new tables");
72                }
73                if diff_sql.contains("DROP TABLE") {
74                    change_types.push("dropped tables");
75                }
76                if diff_sql.contains("ALTER TABLE") && diff_sql.contains("ADD COLUMN") {
77                    change_types.push("new columns");
78                }
79                if diff_sql.contains("ALTER TABLE") && diff_sql.contains("DROP COLUMN") {
80                    change_types.push("dropped columns");
81                }
82                if diff_sql.contains("ALTER TABLE") && diff_sql.contains("MODIFY COLUMN") {
83                    change_types.push("modified columns");
84                }
85                if diff_sql.contains("ALTER TABLE") && diff_sql.contains("ADD KEY") {
86                    change_types.push("new indexes");
87                }
88                if diff_sql.contains("ALTER TABLE") && diff_sql.contains("DROP KEY") {
89                    change_types.push("dropped indexes");
90                }
91
92                let change_summary = if change_types.is_empty() {
93                    "schema changes".to_string()
94                } else {
95                    change_types.join(", ")
96                };
97
98                format!(
99                    "Version {} to {}: {} - generated {} lines of executable diff SQL",
100                    from_version.unwrap_or("unknown"),
101                    to_version,
102                    change_summary,
103                    lines_count
104                )
105            };
106
107            info!("Diff generation completed: {}", description);
108            Ok((diff_sql, description))
109        }
110    }
111}
112
113/// 基于在线数据库架构与模板SQL生成差异(Live Diff)
114/// 返回:SchemaDiffResult 结构体,包含差异SQL、描述、在线架构SQL和执行标记
115pub async fn generate_live_schema_diff(
116    executor: &MySqlExecutor,
117    to_sql: &str,
118    to_version: &str,
119) -> Result<super::types::SchemaDiffResult, DuckError> {
120    info!(
121        "Starting to generate online schema to {} SQL diff",
122        to_version
123    );
124
125    // 解析目标模板
126    let to_tables = parse_sql_tables(to_sql)?;
127
128    // 抓取在线架构并生成差异(同时获取原始 SQL)
129    let (live_tables, live_sql) = executor
130        .fetch_live_schema_with_sql()
131        .await
132        .map_err(|e| DuckError::custom(format!("Failed to fetch online schema: {e}")))?;
133
134    let (diff_sql, stats) = generate_mysql_diff(&live_tables, &to_tables)?;
135
136    // 使用统计信息判断是否有可执行SQL和警告
137    let has_executable_sql = stats.has_executable_operations();
138    let has_warnings = stats.has_dangerous_operations();
139
140    let description = if !stats.has_changes() {
141        format!("Online schema to {to_version}: no actual schema differences")
142    } else if !has_executable_sql && has_warnings {
143        format!(
144            "Online schema to {to_version}: only includes delete operation warnings, no executable SQL"
145        )
146    } else {
147        let executable_lines = diff_sql
148            .lines()
149            .filter(|line| !line.trim().is_empty() && !line.trim().starts_with("--"))
150            .count();
151
152        format!(
153            "Online schema to {}: {} - generated {} lines of executable diff SQL",
154            to_version,
155            stats.summary(),
156            executable_lines
157        )
158    };
159
160    info!("Live Diff completed: {}", description);
161
162    Ok(super::types::SchemaDiffResult {
163        diff_sql,
164        description,
165        live_sql: Some(live_sql),
166        has_executable_sql,
167        has_warnings,
168    })
169}
170
171/// 格式化默认值用于SQL输出,正确处理不同类型的值
172fn format_default_value_for_sql(default: &str) -> String {
173    // 检查是否是MySQL关键字/函数(不需要引号)
174    let mysql_keywords = [
175        "CURRENT_TIMESTAMP",
176        "NOW()",
177        "CURRENT_DATE",
178        "CURRENT_TIME",
179        "LOCALTIMESTAMP",
180        "LOCALTIME",
181        "NULL",
182        "TRUE",
183        "FALSE",
184    ];
185
186    let upper_default = default.to_uppercase();
187
188    // 如果是MySQL关键字,直接返回(不加引号)
189    if mysql_keywords.contains(&upper_default.as_str()) {
190        return default.to_string();
191    }
192
193    // 如果是纯数字(可能包含负号和小数点),直接返回
194    if default
195        .chars()
196        .all(|c| c.is_ascii_digit() || c == '-' || c == '.')
197    {
198        return default.to_string();
199    }
200
201    // 如果已经是引号包围的,直接返回
202    if (default.starts_with('\'') && default.ends_with('\''))
203        || (default.starts_with('"') && default.ends_with('"'))
204    {
205        return default.to_string();
206    }
207
208    // 其他情况作为字符串处理,添加单引号
209    format!("'{}'", default)
210}
211
212/// 生成CREATE TABLE SQL
213pub fn generate_create_table_sql(table: &TableDefinition) -> String {
214    let mut sql = format!("CREATE TABLE `{}` (", table.name);
215
216    // 添加列定义
217    let mut parts = Vec::new();
218    for column in &table.columns {
219        parts.push(format!("  {}", generate_column_sql(column)));
220    }
221
222    // 添加索引定义
223    for index in &table.indexes {
224        parts.push(format!("  {}", generate_index_sql(index)));
225    }
226
227    sql.push_str(&parts.join(",\n"));
228    sql.push_str("\n)");
229
230    // 添加表选项
231    if let Some(engine) = &table.engine {
232        sql.push_str(&format!(" ENGINE={engine}"));
233    }
234    if let Some(charset) = &table.charset {
235        sql.push_str(&format!(" DEFAULT CHARSET={charset}"));
236    }
237
238    sql.push(';');
239    sql
240}
241
242/// 生成列定义SQL
243pub fn generate_column_sql(column: &TableColumn) -> String {
244    let mut sql = format!("`{}` {}", column.name, column.data_type);
245
246    if !column.nullable {
247        sql.push_str(" NOT NULL");
248    }
249
250    if let Some(default) = &column.default_value {
251        sql.push_str(&format!(
252            " DEFAULT {}",
253            format_default_value_for_sql(default)
254        ));
255    }
256
257    if column.auto_increment {
258        sql.push_str(" AUTO_INCREMENT");
259    }
260
261    if let Some(comment) = &column.comment {
262        sql.push_str(&format!(" COMMENT '{comment}'"));
263    }
264
265    sql
266}
267
268/// 生成索引定义SQL
269pub fn generate_index_sql(index: &TableIndex) -> String {
270    if index.is_primary {
271        format!(
272            "PRIMARY KEY ({})",
273            index
274                .columns
275                .iter()
276                .map(|c| format!("`{c}`"))
277                .collect::<Vec<_>>()
278                .join(", ")
279        )
280    } else if index.is_unique {
281        format!(
282            "UNIQUE KEY `{}` ({})",
283            index.name,
284            index
285                .columns
286                .iter()
287                .map(|c| format!("`{c}`"))
288                .collect::<Vec<_>>()
289                .join(", ")
290        )
291    } else {
292        format!(
293            "KEY `{}` ({})",
294            index.name,
295            index
296                .columns
297                .iter()
298                .map(|c| format!("`{c}`"))
299                .collect::<Vec<_>>()
300                .join(", ")
301        )
302    }
303}