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