1use std::collections::hash_map::DefaultHasher;
13use std::hash::{Hash, Hasher};
14
15use sqlparser::ast::{BinaryOperator, Expr, SelectItem, SetExpr, Statement};
16use sqlparser::dialect::GenericDialect;
17use sqlparser::parser::Parser;
18
19#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub struct ColRef {
24 pub qualifier: Option<String>,
26 pub name: String,
28}
29
30pub fn parse_predicate(s: &str) -> Option<Expr> {
37 let sql = format!("SELECT 1 WHERE {s}");
38 let dialect = GenericDialect {};
39 let stmts = Parser::parse_sql(&dialect, &sql).ok()?;
40 if let Some(Statement::Query(q)) = stmts.into_iter().next() {
41 if let SetExpr::Select(sel) = *q.body {
42 return sel.selection;
43 }
44 }
45 None
46}
47
48pub fn parse_expr(s: &str) -> Option<Expr> {
54 let sql = format!("SELECT {s}");
55 let dialect = GenericDialect {};
56 let stmts = Parser::parse_sql(&dialect, &sql).ok()?;
57 if let Some(Statement::Query(q)) = stmts.into_iter().next() {
58 if let SetExpr::Select(sel) = *q.body {
59 if let Some(SelectItem::UnnamedExpr(e)) = sel.projection.into_iter().next() {
60 return Some(e);
61 }
62 }
63 }
64 None
65}
66
67pub fn render(e: &Expr) -> String {
71 e.to_string()
72}
73
74pub fn split_conjuncts(e: Expr) -> Vec<Expr> {
81 match e {
82 Expr::BinaryOp {
83 left,
84 op: BinaryOperator::And,
85 right,
86 } => {
87 let mut v = split_conjuncts(*left);
88 v.extend(split_conjuncts(*right));
89 v
90 }
91 other => vec![other],
92 }
93}
94
95pub fn join_conjuncts(exprs: Vec<Expr>) -> Option<Expr> {
100 exprs.into_iter().reduce(|acc, e| Expr::BinaryOp {
101 left: Box::new(acc),
102 op: BinaryOperator::And,
103 right: Box::new(e),
104 })
105}
106
107pub fn equi_key(e: &Expr) -> Option<(ColRef, ColRef)> {
112 if let Expr::BinaryOp {
113 left,
114 op: BinaryOperator::Eq,
115 right,
116 } = e
117 {
118 let l = expr_to_colref(left)?;
119 let r = expr_to_colref(right)?;
120 Some((l, r))
121 } else {
122 None
123 }
124}
125
126fn expr_to_colref(e: &Expr) -> Option<ColRef> {
127 match e {
128 Expr::Identifier(id) => Some(ColRef {
129 qualifier: None,
130 name: id.value.clone(),
131 }),
132 Expr::CompoundIdentifier(parts) if parts.len() == 2 => Some(ColRef {
133 qualifier: Some(parts[0].value.clone()),
134 name: parts[1].value.clone(),
135 }),
136 _ => None,
137 }
138}
139
140pub fn collect_colrefs(e: &Expr) -> Vec<ColRef> {
148 let mut refs = Vec::new();
149 collect_colrefs_inner(e, &mut refs);
150 refs
151}
152
153fn collect_colrefs_inner(e: &Expr, out: &mut Vec<ColRef>) {
154 match e {
155 Expr::Identifier(id) => out.push(ColRef {
156 qualifier: None,
157 name: id.value.clone(),
158 }),
159 Expr::CompoundIdentifier(parts) if parts.len() == 2 => out.push(ColRef {
160 qualifier: Some(parts[0].value.clone()),
161 name: parts[1].value.clone(),
162 }),
163 Expr::BinaryOp { left, right, .. } => {
164 collect_colrefs_inner(left, out);
165 collect_colrefs_inner(right, out);
166 }
167 Expr::UnaryOp { expr, .. } => collect_colrefs_inner(expr, out),
168 Expr::IsNull(e) | Expr::IsNotNull(e) => collect_colrefs_inner(e, out),
169 Expr::Between {
170 expr, low, high, ..
171 } => {
172 collect_colrefs_inner(expr, out);
173 collect_colrefs_inner(low, out);
174 collect_colrefs_inner(high, out);
175 }
176 Expr::InList { expr, list, .. } => {
177 collect_colrefs_inner(expr, out);
178 for item in list {
179 collect_colrefs_inner(item, out);
180 }
181 }
182 Expr::Like { expr, pattern, .. } | Expr::ILike { expr, pattern, .. } => {
183 collect_colrefs_inner(expr, out);
184 collect_colrefs_inner(pattern, out);
185 }
186 _ => {}
187 }
188}
189
190pub fn canonical_hash(e: &Expr) -> u64 {
198 let mut h = DefaultHasher::new();
199 hash_expr(e, &mut h);
200 h.finish()
201}
202
203fn hash_expr(e: &Expr, h: &mut DefaultHasher) {
204 use std::mem::discriminant;
205 discriminant(e).hash(h);
206 match e {
207 Expr::Identifier(id) => id.value.hash(h),
208 Expr::CompoundIdentifier(parts) => {
209 for p in parts {
210 p.value.hash(h);
211 }
212 }
213 Expr::Value(v) => v.to_string().hash(h),
214 Expr::BinaryOp { left, op, right } => {
215 let is_commutative = matches!(
216 op,
217 BinaryOperator::And
218 | BinaryOperator::Or
219 | BinaryOperator::Plus
220 | BinaryOperator::Multiply
221 );
222 op.to_string().hash(h);
223 if is_commutative {
224 let lh = {
225 let mut tmp = DefaultHasher::new();
226 hash_expr(left, &mut tmp);
227 tmp.finish()
228 };
229 let rh = {
230 let mut tmp = DefaultHasher::new();
231 hash_expr(right, &mut tmp);
232 tmp.finish()
233 };
234 let (a, b) = if lh <= rh { (lh, rh) } else { (rh, lh) };
235 a.hash(h);
236 b.hash(h);
237 } else {
238 hash_expr(left, h);
239 hash_expr(right, h);
240 }
241 }
242 Expr::UnaryOp { op, expr } => {
243 op.to_string().hash(h);
244 hash_expr(expr, h);
245 }
246 other => other.to_string().hash(h),
247 }
248}
249
250pub fn find_common_subexprs(e: &Expr) -> Vec<(u64, String, usize)> {
260 use std::collections::HashMap;
261 let mut counts: HashMap<u64, (String, usize)> = HashMap::new();
262 count_subexprs(e, &mut counts);
263 counts
264 .into_iter()
265 .filter(|(_, (_, cnt))| *cnt > 1)
266 .map(|(hash, (s, cnt))| (hash, s, cnt))
267 .collect()
268}
269
270fn count_subexprs(e: &Expr, counts: &mut std::collections::HashMap<u64, (String, usize)>) {
271 let is_nontrivial = !matches!(
273 e,
274 Expr::Identifier(_) | Expr::CompoundIdentifier(_) | Expr::Value(_)
275 );
276 if is_nontrivial {
277 let h = canonical_hash(e);
278 let entry = counts.entry(h).or_insert_with(|| (render(e), 0));
279 entry.1 += 1;
280 }
281 match e {
283 Expr::BinaryOp { left, right, .. } => {
284 count_subexprs(left, counts);
285 count_subexprs(right, counts);
286 }
287 Expr::UnaryOp { expr, .. } => count_subexprs(expr, counts),
288 Expr::Between {
289 expr, low, high, ..
290 } => {
291 count_subexprs(expr, counts);
292 count_subexprs(low, counts);
293 count_subexprs(high, counts);
294 }
295 Expr::InList { expr, list, .. } => {
296 count_subexprs(expr, counts);
297 for item in list {
298 count_subexprs(item, counts);
299 }
300 }
301 _ => {}
302 }
303}
304
305#[cfg(test)]
308mod tests {
309 use super::*;
310
311 #[test]
312 fn test_parse_predicate_simple() {
313 let e = parse_predicate("x > 1");
314 assert!(e.is_some(), "should parse simple predicate");
315 }
316
317 #[test]
318 fn test_parse_predicate_and() {
319 let e = parse_predicate("x > 1 AND y < 10");
320 assert!(e.is_some(), "should parse AND predicate");
321 }
322
323 #[test]
324 fn test_parse_expr_column() {
325 let e = parse_expr("a.b");
326 assert!(e.is_some(), "should parse qualified identifier");
327 }
328
329 #[test]
330 fn test_split_conjuncts_single() {
331 let e = parse_predicate("x > 1").expect("parse");
332 let parts = split_conjuncts(e);
333 assert_eq!(parts.len(), 1);
334 }
335
336 #[test]
337 fn test_split_conjuncts_and() {
338 let e = parse_predicate("x > 1 AND y < 10 AND z = 5").expect("parse");
339 let parts = split_conjuncts(e);
340 assert_eq!(parts.len(), 3, "AND should yield 3 conjuncts");
341 }
342
343 #[test]
344 fn test_join_conjuncts_empty_is_none() {
345 assert!(join_conjuncts(vec![]).is_none());
346 }
347
348 #[test]
349 fn test_join_conjuncts_single() {
350 let e = parse_predicate("x > 1").expect("parse");
351 let joined = join_conjuncts(vec![e.clone()]);
352 assert!(joined.is_some());
353 }
354
355 #[test]
356 fn test_equi_key_qualified() {
357 let e = parse_predicate("a.id = b.id").expect("parse");
358 let key = equi_key(&e);
359 assert!(key.is_some(), "equi_key should find a.id = b.id");
360 let (l, r) = key.expect("key");
361 assert_eq!(l.qualifier.as_deref(), Some("a"));
362 assert_eq!(l.name, "id");
363 assert_eq!(r.qualifier.as_deref(), Some("b"));
364 }
365
366 #[test]
367 fn test_equi_key_non_eq_returns_none() {
368 let e = parse_predicate("x > 1").expect("parse");
369 assert!(equi_key(&e).is_none(), "non-equality should return None");
370 }
371
372 #[test]
373 fn test_collect_colrefs() {
374 let e = parse_predicate("a.x = b.y AND z > 1").expect("parse");
375 let refs = collect_colrefs(&e);
376 assert!(refs.len() >= 2, "should collect at least a.x and b.y");
377 }
378
379 #[test]
380 fn test_canonical_hash_commutative() {
381 let e1 = parse_predicate("x = 1 AND y = 2").expect("parse");
382 let e2 = parse_predicate("y = 2 AND x = 1").expect("parse");
383 assert_eq!(
384 canonical_hash(&e1),
385 canonical_hash(&e2),
386 "commutative AND should produce same hash"
387 );
388 }
389
390 #[test]
391 fn test_canonical_hash_non_commutative() {
392 let e1 = parse_predicate("x > 1").expect("parse e1");
393 let e2 = parse_predicate("1 > x").expect("parse e2");
394 assert_ne!(
395 canonical_hash(&e1),
396 canonical_hash(&e2),
397 "non-commutative > should produce different hashes"
398 );
399 }
400
401 #[test]
402 fn test_find_common_subexprs_no_repeat() {
403 let e = parse_predicate("x > 1 AND y < 2").expect("parse");
404 let common = find_common_subexprs(&e);
405 assert!(
406 common.is_empty(),
407 "no repeated subexpressions in x>1 AND y<2"
408 );
409 }
410
411 #[test]
412 fn test_find_common_subexprs_with_repeat() {
413 let e = parse_predicate("(x + 1) > 0 AND (x + 1) < 10").expect("parse");
415 let common = find_common_subexprs(&e);
416 assert!(!common.is_empty(), "should detect repeated subexpr `x + 1`");
417 }
418
419 #[test]
420 fn test_render_roundtrip() {
421 let e = parse_predicate("a.id = b.id").expect("parse");
422 let s = render(&e);
423 assert!(!s.is_empty(), "render should produce non-empty string");
424 assert!(
426 parse_predicate(&s).is_some(),
427 "rendered string should be re-parseable"
428 );
429 }
430}