sql_splitter/convert/
warnings.rs

1//! Warning system for convert command.
2//!
3//! Tracks and reports unsupported features, lossy conversions,
4//! and other issues that arise during dialect conversion.
5
6use serde::Serialize;
7
8/// Warning types that can occur during conversion
9#[derive(Debug, Clone, PartialEq, Serialize)]
10#[serde(tag = "type", rename_all = "snake_case")]
11pub enum ConvertWarning {
12    /// Feature not supported in target dialect
13    UnsupportedFeature {
14        feature: String,
15        suggestion: Option<String>,
16    },
17    /// Data type conversion may lose precision
18    LossyConversion {
19        from_type: String,
20        to_type: String,
21        table: Option<String>,
22        column: Option<String>,
23    },
24    /// Statement was skipped
25    SkippedStatement {
26        reason: String,
27        statement_preview: String,
28    },
29    /// COPY statement needs conversion (PostgreSQL)
30    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/// Collects warnings during conversion
79#[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, // Limit to avoid memory issues
90        }
91    }
92
93    pub fn with_limit(limit: usize) -> Self {
94        Self {
95            warnings: Vec::new(),
96            max_warnings: limit,
97        }
98    }
99
100    /// Add a warning
101    pub fn add(&mut self, warning: ConvertWarning) {
102        if self.warnings.len() < self.max_warnings {
103            // Deduplicate similar warnings
104            if !self.warnings.iter().any(|w| Self::is_similar(w, &warning)) {
105                self.warnings.push(warning);
106            }
107        }
108    }
109
110    /// Check if two warnings are similar enough to deduplicate
111    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    /// Get all collected warnings
134    pub fn warnings(&self) -> &[ConvertWarning] {
135        &self.warnings
136    }
137
138    /// Check if any warnings were collected
139    pub fn has_warnings(&self) -> bool {
140        !self.warnings.is_empty()
141    }
142
143    /// Get warning count
144    pub fn count(&self) -> usize {
145        self.warnings.len()
146    }
147
148    /// Print summary of warnings
149    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}