Skip to main content

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