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
6/// Warning types that can occur during conversion
7#[derive(Debug, Clone, PartialEq)]
8pub enum ConvertWarning {
9    /// Feature not supported in target dialect
10    UnsupportedFeature {
11        feature: String,
12        suggestion: Option<String>,
13    },
14    /// Data type conversion may lose precision
15    LossyConversion {
16        from_type: String,
17        to_type: String,
18        table: Option<String>,
19        column: Option<String>,
20    },
21    /// Statement was skipped
22    SkippedStatement {
23        reason: String,
24        statement_preview: String,
25    },
26    /// COPY statement needs conversion (PostgreSQL)
27    CopyNotConverted { table: String },
28}
29
30impl std::fmt::Display for ConvertWarning {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            ConvertWarning::UnsupportedFeature {
34                feature,
35                suggestion,
36            } => {
37                write!(f, "Unsupported feature: {}", feature)?;
38                if let Some(s) = suggestion {
39                    write!(f, " ({})", s)?;
40                }
41                Ok(())
42            }
43            ConvertWarning::LossyConversion {
44                from_type,
45                to_type,
46                table,
47                column,
48            } => {
49                write!(f, "Lossy conversion: {} → {}", from_type, to_type)?;
50                if let Some(t) = table {
51                    write!(f, " in table {}", t)?;
52                    if let Some(c) = column {
53                        write!(f, ".{}", c)?;
54                    }
55                }
56                Ok(())
57            }
58            ConvertWarning::SkippedStatement {
59                reason,
60                statement_preview,
61            } => {
62                write!(f, "Skipped: {} ({})", reason, statement_preview)
63            }
64            ConvertWarning::CopyNotConverted { table } => {
65                write!(
66                    f,
67                    "COPY statement for table '{}' not converted - requires INSERT conversion",
68                    table
69                )
70            }
71        }
72    }
73}
74
75/// Collects warnings during conversion
76#[derive(Debug, Default)]
77pub struct WarningCollector {
78    warnings: Vec<ConvertWarning>,
79    max_warnings: usize,
80}
81
82impl WarningCollector {
83    pub fn new() -> Self {
84        Self {
85            warnings: Vec::new(),
86            max_warnings: 100, // Limit to avoid memory issues
87        }
88    }
89
90    pub fn with_limit(limit: usize) -> Self {
91        Self {
92            warnings: Vec::new(),
93            max_warnings: limit,
94        }
95    }
96
97    /// Add a warning
98    pub fn add(&mut self, warning: ConvertWarning) {
99        if self.warnings.len() < self.max_warnings {
100            // Deduplicate similar warnings
101            if !self.warnings.iter().any(|w| Self::is_similar(w, &warning)) {
102                self.warnings.push(warning);
103            }
104        }
105    }
106
107    /// Check if two warnings are similar enough to deduplicate
108    fn is_similar(a: &ConvertWarning, b: &ConvertWarning) -> bool {
109        match (a, b) {
110            (
111                ConvertWarning::UnsupportedFeature { feature: f1, .. },
112                ConvertWarning::UnsupportedFeature { feature: f2, .. },
113            ) => f1 == f2,
114            (
115                ConvertWarning::LossyConversion {
116                    from_type: f1,
117                    to_type: t1,
118                    ..
119                },
120                ConvertWarning::LossyConversion {
121                    from_type: f2,
122                    to_type: t2,
123                    ..
124                },
125            ) => f1 == f2 && t1 == t2,
126            _ => false,
127        }
128    }
129
130    /// Get all collected warnings
131    pub fn warnings(&self) -> &[ConvertWarning] {
132        &self.warnings
133    }
134
135    /// Check if any warnings were collected
136    pub fn has_warnings(&self) -> bool {
137        !self.warnings.is_empty()
138    }
139
140    /// Get warning count
141    pub fn count(&self) -> usize {
142        self.warnings.len()
143    }
144
145    /// Print summary of warnings
146    pub fn print_summary(&self) {
147        if self.warnings.is_empty() {
148            return;
149        }
150
151        eprintln!("\nConversion warnings ({}):", self.warnings.len());
152        for warning in &self.warnings {
153            eprintln!("  ⚠ {}", warning);
154        }
155
156        if self.warnings.len() >= self.max_warnings {
157            eprintln!("  ... (additional warnings truncated)");
158        }
159    }
160}