nuwax_cli/commands/
diff_sql.rs1use 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
8pub 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 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 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 let from_version = old_version.as_deref().unwrap_or("unknown");
49 let to_version = new_version.as_deref().unwrap_or("latest");
50
51 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 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 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 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 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 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}