sql_splitter/convert/
warnings.rs1use serde::Serialize;
7
8#[derive(Debug, Clone, PartialEq, Serialize)]
10#[serde(tag = "type", rename_all = "snake_case")]
11pub enum ConvertWarning {
12 UnsupportedFeature {
14 feature: String,
15 suggestion: Option<String>,
16 },
17 LossyConversion {
19 from_type: String,
20 to_type: String,
21 table: Option<String>,
22 column: Option<String>,
23 },
24 SkippedStatement {
26 reason: String,
27 statement_preview: String,
28 },
29 CopyNotConverted { table: String },
31}
32
33impl std::fmt::Display for ConvertWarning {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 match self {
36 ConvertWarning::UnsupportedFeature {
37 feature,
38 suggestion,
39 } => {
40 write!(f, "Unsupported feature: {}", feature)?;
41 if let Some(s) = suggestion {
42 write!(f, " ({})", s)?;
43 }
44 Ok(())
45 }
46 ConvertWarning::LossyConversion {
47 from_type,
48 to_type,
49 table,
50 column,
51 } => {
52 write!(f, "Lossy conversion: {} → {}", from_type, to_type)?;
53 if let Some(t) = table {
54 write!(f, " in table {}", t)?;
55 if let Some(c) = column {
56 write!(f, ".{}", c)?;
57 }
58 }
59 Ok(())
60 }
61 ConvertWarning::SkippedStatement {
62 reason,
63 statement_preview,
64 } => {
65 write!(f, "Skipped: {} ({})", reason, statement_preview)
66 }
67 ConvertWarning::CopyNotConverted { table } => {
68 write!(
69 f,
70 "COPY statement for table '{}' not converted - requires INSERT conversion",
71 table
72 )
73 }
74 }
75 }
76}
77
78#[derive(Debug, Default)]
80pub struct WarningCollector {
81 warnings: Vec<ConvertWarning>,
82 max_warnings: usize,
83}
84
85impl WarningCollector {
86 pub fn new() -> Self {
87 Self {
88 warnings: Vec::new(),
89 max_warnings: 100, }
91 }
92
93 pub fn with_limit(limit: usize) -> Self {
94 Self {
95 warnings: Vec::new(),
96 max_warnings: limit,
97 }
98 }
99
100 pub fn add(&mut self, warning: ConvertWarning) {
102 if self.warnings.len() < self.max_warnings {
103 if !self.warnings.iter().any(|w| Self::is_similar(w, &warning)) {
105 self.warnings.push(warning);
106 }
107 }
108 }
109
110 fn is_similar(a: &ConvertWarning, b: &ConvertWarning) -> bool {
112 match (a, b) {
113 (
114 ConvertWarning::UnsupportedFeature { feature: f1, .. },
115 ConvertWarning::UnsupportedFeature { feature: f2, .. },
116 ) => f1 == f2,
117 (
118 ConvertWarning::LossyConversion {
119 from_type: f1,
120 to_type: t1,
121 ..
122 },
123 ConvertWarning::LossyConversion {
124 from_type: f2,
125 to_type: t2,
126 ..
127 },
128 ) => f1 == f2 && t1 == t2,
129 _ => false,
130 }
131 }
132
133 pub fn warnings(&self) -> &[ConvertWarning] {
135 &self.warnings
136 }
137
138 pub fn has_warnings(&self) -> bool {
140 !self.warnings.is_empty()
141 }
142
143 pub fn count(&self) -> usize {
145 self.warnings.len()
146 }
147
148 pub fn print_summary(&self) {
150 if self.warnings.is_empty() {
151 return;
152 }
153
154 eprintln!("\nConversion warnings ({}):", self.warnings.len());
155 for warning in &self.warnings {
156 eprintln!(" ⚠ {}", warning);
157 }
158
159 if self.warnings.len() >= self.max_warnings {
160 eprintln!(" ... (additional warnings truncated)");
161 }
162 }
163}