pg2sqlite_core/transform/
expr_map.rs1use crate::diagnostics::warning::{self, Severity, Warning};
3use crate::ir::Expr;
4
5pub fn map_expr(expr: &Expr, object: &str, warnings: &mut Vec<Warning>) -> Option<Expr> {
8 match expr {
9 Expr::IntegerLiteral(_) | Expr::FloatLiteral(_) | Expr::StringLiteral(_) | Expr::Null => {
11 Some(expr.clone())
12 }
13
14 Expr::BooleanLiteral(b) => Some(Expr::IntegerLiteral(if *b { 1 } else { 0 })),
16
17 Expr::ColumnRef(_) => Some(expr.clone()),
19
20 Expr::CurrentTimestamp => Some(Expr::CurrentTimestamp),
22
23 Expr::NextVal(seq) => {
25 warnings.push(
26 Warning::new(
27 warning::NEXTVAL_REMOVED,
28 Severity::Lossy,
29 format!("nextval('{seq}') default removed"),
30 )
31 .with_object(object),
32 );
33 None
34 }
35
36 Expr::FunctionCall { name, args } => map_function_call(name, args, object, warnings),
38
39 Expr::Cast {
41 expr: inner,
42 type_name,
43 } => {
44 warnings.push(
45 Warning::new(
46 warning::CAST_REMOVED,
47 Severity::Info,
48 format!("cast to {type_name} removed"),
49 )
50 .with_object(object),
51 );
52 map_expr(inner, object, warnings)
53 }
54
55 Expr::BinaryOp { left, op, right } => {
57 let left = map_expr(left, object, warnings)?;
58 let right = map_expr(right, object, warnings)?;
59 Some(Expr::BinaryOp {
60 left: std::boxed::Box::new(left),
61 op: op.clone(),
62 right: std::boxed::Box::new(right),
63 })
64 }
65
66 Expr::UnaryOp { op, expr: inner } => {
68 let mapped = map_expr(inner, object, warnings)?;
69 Some(Expr::UnaryOp {
70 op: op.clone(),
71 expr: std::boxed::Box::new(mapped),
72 })
73 }
74
75 Expr::IsNull {
77 expr: inner,
78 negated,
79 } => {
80 let mapped = map_expr(inner, object, warnings)?;
81 Some(Expr::IsNull {
82 expr: std::boxed::Box::new(mapped),
83 negated: *negated,
84 })
85 }
86
87 Expr::InList {
89 expr: inner,
90 list,
91 negated,
92 } => {
93 let mapped_expr = map_expr(inner, object, warnings)?;
94 let mapped_list: Vec<Expr> = list
95 .iter()
96 .filter_map(|e| map_expr(e, object, warnings))
97 .collect();
98 Some(Expr::InList {
99 expr: std::boxed::Box::new(mapped_expr),
100 list: mapped_list,
101 negated: *negated,
102 })
103 }
104
105 Expr::Between {
107 expr: inner,
108 low,
109 high,
110 negated,
111 } => {
112 let mapped = map_expr(inner, object, warnings)?;
113 let mapped_low = map_expr(low, object, warnings)?;
114 let mapped_high = map_expr(high, object, warnings)?;
115 Some(Expr::Between {
116 expr: std::boxed::Box::new(mapped),
117 low: std::boxed::Box::new(mapped_low),
118 high: std::boxed::Box::new(mapped_high),
119 negated: *negated,
120 })
121 }
122
123 Expr::Nested(inner) => {
125 let mapped = map_expr(inner, object, warnings)?;
126 Some(Expr::Nested(std::boxed::Box::new(mapped)))
127 }
128
129 Expr::Raw(_) => Some(expr.clone()),
131 }
132}
133
134fn map_function_call(
135 name: &str,
136 args: &[Expr],
137 object: &str,
138 warnings: &mut Vec<Warning>,
139) -> Option<Expr> {
140 match name {
141 "now" => Some(Expr::CurrentTimestamp),
143
144 "lower" | "upper" | "length" | "abs" | "max" | "min" | "coalesce" | "nullif" | "typeof"
146 | "trim" | "ltrim" | "rtrim" | "replace" | "substr" | "instr" | "hex" | "quote"
147 | "round" | "random" | "unicode" | "zeroblob" | "total" | "sum" | "avg" | "count"
148 | "group_concat" => {
149 let mapped_args: Vec<Expr> = args
150 .iter()
151 .filter_map(|a| map_expr(a, object, warnings))
152 .collect();
153 Some(Expr::FunctionCall {
154 name: name.to_string(),
155 args: mapped_args,
156 })
157 }
158
159 _ => {
161 warnings.push(
162 Warning::new(
163 warning::DEFAULT_UNSUPPORTED,
164 Severity::Unsupported,
165 format!("unsupported function '{name}()' in expression"),
166 )
167 .with_object(object),
168 );
169 None
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn test_literal_passthrough() {
180 let mut w = Vec::new();
181 let expr = Expr::IntegerLiteral(42);
182 assert_eq!(
183 map_expr(&expr, "t.c", &mut w),
184 Some(Expr::IntegerLiteral(42))
185 );
186 assert!(w.is_empty());
187 }
188
189 #[test]
190 fn test_boolean_to_integer() {
191 let mut w = Vec::new();
192 assert_eq!(
193 map_expr(&Expr::BooleanLiteral(true), "t.c", &mut w),
194 Some(Expr::IntegerLiteral(1))
195 );
196 assert_eq!(
197 map_expr(&Expr::BooleanLiteral(false), "t.c", &mut w),
198 Some(Expr::IntegerLiteral(0))
199 );
200 }
201
202 #[test]
203 fn test_now_to_current_timestamp() {
204 let mut w = Vec::new();
205 let expr = Expr::FunctionCall {
206 name: "now".to_string(),
207 args: vec![],
208 };
209 assert_eq!(map_expr(&expr, "t.c", &mut w), Some(Expr::CurrentTimestamp));
210 }
211
212 #[test]
213 fn test_nextval_removed() {
214 let mut w = Vec::new();
215 let expr = Expr::NextVal("users_id_seq".to_string());
216 assert_eq!(map_expr(&expr, "t.id", &mut w), None);
217 assert_eq!(w[0].code, warning::NEXTVAL_REMOVED);
218 }
219
220 #[test]
221 fn test_cast_stripped() {
222 let mut w = Vec::new();
223 let expr = Expr::Cast {
224 expr: std::boxed::Box::new(Expr::IntegerLiteral(42)),
225 type_name: "integer".to_string(),
226 };
227 assert_eq!(
228 map_expr(&expr, "t.c", &mut w),
229 Some(Expr::IntegerLiteral(42))
230 );
231 assert_eq!(w[0].code, warning::CAST_REMOVED);
232 }
233
234 #[test]
235 fn test_unsupported_function() {
236 let mut w = Vec::new();
237 let expr = Expr::FunctionCall {
238 name: "gen_random_uuid".to_string(),
239 args: vec![],
240 };
241 assert_eq!(map_expr(&expr, "t.c", &mut w), None);
242 assert_eq!(w[0].code, warning::DEFAULT_UNSUPPORTED);
243 }
244
245 #[test]
246 fn test_compatible_function_passthrough() {
247 let mut w = Vec::new();
248 let expr = Expr::FunctionCall {
249 name: "lower".to_string(),
250 args: vec![Expr::ColumnRef("name".to_string())],
251 };
252 let result = map_expr(&expr, "t.c", &mut w);
253 assert!(result.is_some());
254 assert!(w.is_empty());
255 }
256}