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