Skip to main content

nuwax_cli/commands/
diff_sql.rs

1use anyhow::Result;
2use client_core::sql_diff::generate_schema_diff;
3use rust_i18n::t;
4use std::fs;
5use std::path::PathBuf;
6use tracing::info;
7
8/// 对比两个SQL文件并生成差异SQL
9pub async fn run_diff_sql(
10    old_sql_path: PathBuf,
11    new_sql_path: PathBuf,
12    old_version: Option<String>,
13    new_version: Option<String>,
14    output_file: String,
15) -> Result<()> {
16    info!("🔄 Starting SQL file diff comparison...");
17    info!("📄 Old version SQL: {path}", path = old_sql_path.display());
18    info!("📄 New version SQL: {path}", path = new_sql_path.display());
19
20    // 检查输入文件是否存在
21    if !old_sql_path.exists() {
22        return Err(anyhow::anyhow!(
23            t!("diff_sql.old_sql_not_found", path = old_sql_path.display()).to_string()
24        ));
25    }
26
27    if !new_sql_path.exists() {
28        return Err(anyhow::anyhow!(
29            t!("diff_sql.new_sql_not_found", path = new_sql_path.display()).to_string()
30        ));
31    }
32
33    // 读取文件内容
34    info!("📖 Reading SQL files...");
35    let old_sql_content = fs::read_to_string(&old_sql_path).map_err(|e| {
36        client_core::error::DuckError::custom(
37            t!("diff_sql.read_old_failed", error = e.to_string()).to_string(),
38        )
39    })?;
40
41    let new_sql_content = fs::read_to_string(&new_sql_path).map_err(|e| {
42        client_core::error::DuckError::custom(
43            t!("diff_sql.read_new_failed", error = e.to_string()).to_string(),
44        )
45    })?;
46
47    // 设置默认版本号
48    let from_version = old_version.as_deref().unwrap_or("unknown");
49    let to_version = new_version.as_deref().unwrap_or("latest");
50
51    // 生成差异SQL
52    info!("🔍 Analyzing SQL differences...");
53    let (diff_sql, description) = generate_schema_diff(
54        Some(&old_sql_content),
55        &new_sql_content,
56        Some(from_version),
57        to_version,
58    )
59    .map_err(|e| {
60        client_core::error::DuckError::custom(
61            t!("diff_sql.generate_failed", error = e.to_string()).to_string(),
62        )
63    })?;
64
65    info!(
66        "📊 SQL diff analysis result: {description}",
67        description = description
68    );
69
70    // 检查是否有实际的SQL语句需要执行
71    let meaningful_lines: Vec<&str> = diff_sql
72        .lines()
73        .filter(|line| !line.trim().is_empty() && !line.trim().starts_with("--"))
74        .collect();
75
76    if meaningful_lines.is_empty() {
77        info!("✅ No database schema changes, upgrade not needed");
78        info!("📄 Generated empty diff file: {file}", file = output_file);
79
80        // 创建包含说明的空差异文件
81        let empty_diff_content = format!(
82            "-- SQL diff analysis result\n-- {description}\n-- No SQL execution needed; database schema unchanged\n"
83        );
84        fs::write(&output_file, empty_diff_content).map_err(|e| {
85            client_core::error::DuckError::custom(
86                t!("diff_sql.write_failed", error = e.to_string()).to_string(),
87            )
88        })?;
89    } else {
90        // 保存差异SQL文件
91        fs::write(&output_file, &diff_sql).map_err(|e| {
92            client_core::error::DuckError::custom(
93                t!("diff_sql.write_failed", error = e.to_string()).to_string(),
94            )
95        })?;
96
97        info!("📄 SQL diff file saved: {file}", file = output_file);
98        info!(
99            "📋 Found {count} executable SQL statements",
100            count = meaningful_lines.len()
101        );
102
103        // 显示差异SQL内容(截取前10行)
104        let diff_lines: Vec<&str> = diff_sql.lines().take(10).collect();
105        info!("📋 SQL diff preview (first 10 lines):");
106        for line in diff_lines {
107            if !line.trim().is_empty() {
108                info!("    {}", line);
109            }
110        }
111
112        if diff_sql.lines().count() > 10 {
113            info!("    ... See more in file: {file}", file = output_file);
114        }
115    }
116
117    // 显示执行建议
118    info!("💡 Usage tips:");
119    info!("   1. Backup your database first");
120    info!("   2. Verify the diff SQL in test environment");
121    info!("   3. Execute in production after confirmation");
122
123    if !meaningful_lines.is_empty() {
124        info!(
125            "   4. Execute example: mysql -u username -p database_name < {file}",
126            file = output_file
127        );
128    }
129
130    info!("✅ SQL diff comparison completed");
131    Ok(())
132}