sql_fingerprint/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use sqlparser::ast::{
4    Assignment, AssignmentTarget, ConflictTarget, Delete, Distinct, DoUpdate, Expr, GroupByExpr,
5    Ident, Insert, JoinConstraint, JoinOperator, LimitClause, ObjectName, ObjectNamePart, Offset,
6    OnConflict, OnConflictAction, OnInsert, OrderBy, OrderByKind, Query, SelectItem, SetExpr,
7    Statement, TableAliasColumnDef, TableFactor, Value, ValueWithSpan, VisitMut, VisitorMut,
8};
9use sqlparser::dialect::{Dialect, GenericDialect};
10use sqlparser::parser::Parser;
11use sqlparser::tokenizer::Span;
12use std::collections::HashMap;
13use std::ops::ControlFlow;
14
15/// Fingerprint a single SQL string.
16///
17/// Unparsable SQL is returned as-is.
18///
19/// # Example
20/// ```
21/// use sql_fingerprint::fingerprint_one;
22///
23/// let result = fingerprint_one("SELECT a, b FROM c ORDER BY b", None);
24/// assert_eq!(result, "SELECT ... FROM c ORDER BY ...");
25/// ```
26pub fn fingerprint_one(input: &str, dialect: Option<&dyn Dialect>) -> String {
27    fingerprint_many(vec![input], dialect).join(" ")
28}
29
30/// Fingerprint multiple SQL strings.
31/// Doing so for a batch of strings allows sharing some state, such as savepoint ID aliases.
32///
33/// Unparsable SQL is returned as-is.
34///
35/// # Example
36/// ```
37/// use sql_fingerprint::fingerprint_many;
38///
39/// let result = fingerprint_many(vec!["SELECT a, b FROM c", "SELECT b, c FROM d"], None);
40/// assert_eq!(result, vec!["SELECT ... FROM c", "SELECT ... FROM d"]);
41/// ```
42pub fn fingerprint_many(input: Vec<&str>, dialect: Option<&dyn Dialect>) -> Vec<String> {
43    let dialect = dialect.unwrap_or(&GenericDialect {});
44
45    let mut visitor = FingerprintingVisitor::new();
46
47    input
48        .iter()
49        .map(|sql| match Parser::parse_sql(dialect, sql) {
50            Ok(mut ast) => {
51                for stmt in &mut ast {
52                    let _ = stmt.visit(&mut visitor);
53                }
54
55                ast.into_iter()
56                    .map(|stmt| stmt.to_string())
57                    .collect::<Vec<_>>()
58                    .join(" ")
59            }
60            Err(_) => sql.to_string(),
61        })
62        .collect()
63}
64
65struct FingerprintingVisitor {
66    savepoint_ids: HashMap<String, String>,
67}
68
69impl FingerprintingVisitor {
70    fn new() -> Self {
71        FingerprintingVisitor {
72            savepoint_ids: HashMap::new(),
73        }
74    }
75
76    fn visit_select(&mut self, select: &mut sqlparser::ast::Select) {
77        if !select.projection.is_empty() {
78            if let Some(item) = select.projection.first_mut() {
79                match item {
80                    SelectItem::UnnamedExpr(_) | SelectItem::ExprWithAlias { .. } => {
81                        *item = SelectItem::UnnamedExpr(placeholder_value());
82                    }
83                    _ => {}
84                }
85            }
86            select.projection.truncate(1);
87        }
88
89        if let Some(Distinct::On(exprs)) = &mut select.distinct {
90            if !exprs.is_empty() {
91                *exprs = vec![placeholder_value()];
92            }
93        };
94
95        for table_with_joins in &mut select.from {
96            for join in &mut table_with_joins.joins {
97                match &mut join.join_operator {
98                    JoinOperator::Join(constraint)
99                    | JoinOperator::Inner(constraint)
100                    | JoinOperator::Left(constraint)
101                    | JoinOperator::LeftOuter(constraint)
102                    | JoinOperator::Right(constraint)
103                    | JoinOperator::RightOuter(constraint)
104                    | JoinOperator::FullOuter(constraint)
105                    | JoinOperator::Semi(constraint)
106                    | JoinOperator::LeftSemi(constraint)
107                    | JoinOperator::RightSemi(constraint)
108                    | JoinOperator::Anti(constraint)
109                    | JoinOperator::LeftAnti(constraint)
110                    | JoinOperator::RightAnti(constraint) => {
111                        if let JoinConstraint::On(expr) = constraint {
112                            *expr = placeholder_value();
113                        }
114                    }
115                    _ => {}
116                }
117            }
118        }
119
120        if let Some(selection) = &mut select.selection {
121            *selection = placeholder_value();
122        }
123
124        if let GroupByExpr::Expressions(col_names, ..) = &mut select.group_by {
125            if !col_names.is_empty() {
126                *col_names = vec![placeholder_value()];
127            }
128        }
129    }
130}
131
132impl VisitorMut for FingerprintingVisitor {
133    type Break = ();
134
135    fn pre_visit_statement(&mut self, stmt: &mut Statement) -> ControlFlow<Self::Break> {
136        match stmt {
137            Statement::Savepoint { name } => {
138                let savepoint_id = format!("s{}", self.savepoint_ids.len() + 1);
139                self.savepoint_ids
140                    .insert(name.value.clone(), savepoint_id.clone());
141                *name = Ident::new(savepoint_id);
142            }
143            Statement::ReleaseSavepoint { name } => {
144                if let Some(savepoint_id) = self.savepoint_ids.get(&name.value).cloned() {
145                    *name = Ident::new(savepoint_id);
146                }
147            }
148            Statement::Rollback {
149                savepoint: Some(name),
150                ..
151            } => {
152                if let Some(savepoint_id) = self.savepoint_ids.get(&name.value).cloned() {
153                    *name = Ident::new(savepoint_id);
154                }
155            }
156            Statement::Declare { stmts } => {
157                for stmt in stmts {
158                    if !stmt.names.is_empty() {
159                        stmt.names = vec![Ident::new("...")];
160                    }
161                }
162            }
163            Statement::Insert(Insert {
164                columns,
165                source,
166                on,
167                returning,
168                ..
169            }) => {
170                if !columns.is_empty() {
171                    *columns = vec![Ident::new("...")];
172                }
173                if let Some(source) = source {
174                    if let SetExpr::Values(values) = source.as_mut().body.as_mut() {
175                        values.rows = vec![vec![placeholder_value()]];
176                    }
177                }
178                if let Some(OnInsert::OnConflict(OnConflict {
179                    conflict_target,
180                    action,
181                })) = on
182                {
183                    if let Some(ConflictTarget::Columns(columns)) = conflict_target {
184                        if !columns.is_empty() {
185                            *columns = vec![Ident::new("...")];
186                        }
187                    }
188                    if let OnConflictAction::DoUpdate(DoUpdate {
189                        assignments,
190                        selection,
191                    }) = action
192                    {
193                        if !assignments.is_empty() {
194                            *assignments = vec![Assignment {
195                                target: AssignmentTarget::ColumnName(ObjectName(vec![
196                                    ObjectNamePart::Identifier(Ident::new("...")),
197                                ])),
198                                value: placeholder_value(),
199                            }];
200                        }
201                        if let Some(selection) = selection {
202                            *selection = placeholder_value();
203                        }
204                    }
205                }
206                if let Some(returning) = returning {
207                    if !returning.is_empty() {
208                        *returning = vec![SelectItem::UnnamedExpr(placeholder_value())];
209                    }
210                }
211            }
212            Statement::Update {
213                assignments,
214                selection,
215                returning,
216                ..
217            } => {
218                if !assignments.is_empty() {
219                    *assignments = vec![sqlparser::ast::Assignment {
220                        target: AssignmentTarget::ColumnName(ObjectName(vec![
221                            ObjectNamePart::Identifier(Ident::new("...")),
222                        ])),
223                        value: placeholder_value(),
224                    }];
225                }
226                if let Some(selection) = selection {
227                    *selection = placeholder_value();
228                }
229                if let Some(returning) = returning {
230                    if !returning.is_empty() {
231                        *returning = vec![SelectItem::UnnamedExpr(placeholder_value())];
232                    }
233                }
234            }
235            Statement::Delete(Delete {
236                selection,
237                returning,
238                ..
239            }) => {
240                if let Some(selection) = selection {
241                    *selection = placeholder_value();
242                }
243                if let Some(returning) = returning {
244                    if !returning.is_empty() {
245                        *returning = vec![SelectItem::UnnamedExpr(placeholder_value())];
246                    }
247                }
248            }
249            _ => {}
250        }
251        ControlFlow::Continue(())
252    }
253
254    fn pre_visit_query(&mut self, query: &mut Query) -> ControlFlow<Self::Break> {
255        match query.body.as_mut() {
256            SetExpr::Select(select) => {
257                self.visit_select(select);
258            }
259            SetExpr::SetOperation { left, right, .. } => {
260                // push left and right into a double-ended queue to visit them,
261                // expnading left and right as required.
262                let mut stack = vec![left.as_mut(), right.as_mut()];
263                while let Some(set_expr) = stack.pop() {
264                    match set_expr {
265                        SetExpr::Select(select) => {
266                            self.visit_select(select);
267                        }
268                        SetExpr::SetOperation { left, right, .. } => {
269                            // Push left and right onto the stack for further processing.
270                            stack.push(left.as_mut());
271                            stack.push(right.as_mut());
272                        }
273                        _ => {}
274                    }
275                }
276            }
277            _ => {}
278        }
279        if let Some(order_by) = &mut query.order_by {
280            let OrderBy { kind, .. } = order_by;
281            if let OrderByKind::Expressions(expressions) = kind {
282                if !expressions.is_empty() {
283                    if let Some(expr) = expressions.first_mut() {
284                        expr.expr = placeholder_value();
285                    }
286                    expressions.truncate(1);
287                }
288            }
289        }
290        if let Some(limit_clause) = &mut query.limit_clause {
291            match limit_clause {
292                LimitClause::LimitOffset {
293                    limit,
294                    offset,
295                    limit_by,
296                } => {
297                    if let Some(limit_value) = limit {
298                        *limit_value = placeholder_value();
299                    }
300                    if let Some(Offset { value, .. }) = offset {
301                        *value = placeholder_value();
302                    }
303                    if !limit_by.is_empty() {
304                        *limit_by = vec![placeholder_value()];
305                    }
306                }
307                // MySQL specific, needs testing!Ó
308                LimitClause::OffsetCommaLimit { offset, limit } => {
309                    *offset = placeholder_value();
310                    *limit = placeholder_value();
311                }
312            }
313        }
314        ControlFlow::Continue(())
315    }
316
317    fn pre_visit_relation(&mut self, _relation: &mut ObjectName) -> ControlFlow<Self::Break> {
318        for part in _relation.0.iter_mut() {
319            match part {
320                ObjectNamePart::Identifier(ident) => {
321                    maybe_unquote_ident(ident);
322                }
323            }
324        }
325        ControlFlow::Continue(())
326    }
327
328    fn pre_visit_table_factor(
329        &mut self,
330        table_factor: &mut TableFactor,
331    ) -> ControlFlow<Self::Break> {
332        if let TableFactor::UNNEST {
333            alias, array_exprs, ..
334        } = table_factor
335        {
336            if let Some(alias) = alias {
337                if !alias.columns.is_empty() {
338                    alias.columns = vec![TableAliasColumnDef {
339                        name: Ident::new("..."),
340                        data_type: None,
341                    }];
342                }
343            }
344            if !array_exprs.is_empty() {
345                *array_exprs = vec![placeholder_value()];
346            }
347        }
348        ControlFlow::Continue(())
349    }
350
351    fn pre_visit_expr(&mut self, _expr: &mut Expr) -> ControlFlow<Self::Break> {
352        match _expr {
353            Expr::Identifier(ident) => {
354                maybe_unquote_ident(ident);
355            }
356            Expr::CompoundIdentifier(idents) => {
357                for ident in idents {
358                    maybe_unquote_ident(ident);
359                }
360            }
361            _ => {}
362        }
363        ControlFlow::Continue(())
364    }
365}
366
367fn placeholder_value() -> Expr {
368    Expr::Value(ValueWithSpan {
369        value: Value::Placeholder("...".to_string()),
370        span: Span::empty(),
371    })
372}
373
374fn maybe_unquote_ident(ident: &mut Ident) {
375    let Ident {
376        value, quote_style, ..
377    } = ident;
378
379    if value.chars().all(|c| c.is_alphanumeric() || c == '_') {
380        *quote_style = None;
381    }
382}
383
384#[cfg(test)]
385mod tests {
386    use super::*;
387
388    #[test]
389    fn test_fingerprint_one() {
390        let result = fingerprint_one("SELECT 123", None);
391        assert_eq!(result, "SELECT ...");
392    }
393
394    #[test]
395    fn test_empty() {
396        let result = fingerprint_many(vec![""], None);
397        assert_eq!(result, vec![""]);
398    }
399
400    #[test]
401    fn test_unparsable() {
402        let result = fingerprint_many(vec!["SELECT  SELECT  SELECT  SELECT"], None);
403        assert_eq!(result, vec!["SELECT  SELECT  SELECT  SELECT"]);
404    }
405
406    #[test]
407    fn test_comments_dropped() {
408        let result = fingerprint_many(vec!["SELECT 123 /* magic value */"], None);
409        assert_eq!(result, vec!["SELECT ..."]);
410    }
411
412    #[test]
413    fn test_savepoint() {
414        let result = fingerprint_many(vec!["SAVEPOINT \"s1234\""], None);
415        assert_eq!(result, vec!["SAVEPOINT s1"]);
416    }
417
418    #[test]
419    fn test_multiple_savepoints() {
420        let result = fingerprint_many(vec!["SAVEPOINT \"s1234\"", "SAVEPOINT \"s3456\""], None);
421        assert_eq!(result, vec!["SAVEPOINT s1", "SAVEPOINT s2"]);
422    }
423
424    #[test]
425    fn test_duplicate_savepoints() {
426        let result = fingerprint_many(vec!["SAVEPOINT \"s1234\"", "SAVEPOINT \"s1234\""], None);
427        assert_eq!(result, vec!["SAVEPOINT s1", "SAVEPOINT s2"]);
428    }
429
430    #[test]
431    fn test_release_savepoints() {
432        let result = fingerprint_many(
433            vec![
434                "SAVEPOINT \"s1234\"",
435                "RELEASE SAVEPOINT \"s1234\"",
436                "SAVEPOINT \"s2345\"",
437                "RELEASE SAVEPOINT \"s2345\"",
438            ],
439            None,
440        );
441        assert_eq!(
442            result,
443            vec![
444                "SAVEPOINT s1",
445                "RELEASE SAVEPOINT s1",
446                "SAVEPOINT s2",
447                "RELEASE SAVEPOINT s2"
448            ]
449        );
450    }
451
452    #[test]
453    fn test_rollback_savepoint() {
454        let result = fingerprint_many(
455            vec!["SAVEPOINT \"s1234\"", "ROLLBACK TO SAVEPOINT \"s1234\""],
456            None,
457        );
458        assert_eq!(result, vec!["SAVEPOINT s1", "ROLLBACK TO SAVEPOINT s1"]);
459    }
460
461    #[test]
462    fn test_select() {
463        let result = fingerprint_many(vec!["SELECT a, b FROM c WHERE a = b"], None);
464        assert_eq!(result, vec!["SELECT ... FROM c WHERE ..."]);
465    }
466
467    #[test]
468    fn test_select_single_value() {
469        let result = fingerprint_many(vec!["SELECT 1"], None);
470        assert_eq!(result, vec!["SELECT ..."]);
471    }
472
473    #[test]
474    fn test_select_distinct_on() {
475        let result = fingerprint_many(vec!["SELECT DISTINCT ON (a, b) c FROM d"], None);
476        assert_eq!(result, vec!["SELECT DISTINCT ON (...) ... FROM d"]);
477    }
478
479    #[test]
480    fn test_select_with_from_quoted() {
481        let result = fingerprint_many(vec!["SELECT a, b FROM \"c\".\"d\""], None);
482        assert_eq!(result, vec!["SELECT ... FROM c.d"]);
483    }
484
485    #[test]
486    fn test_select_with_from_join() {
487        let result = fingerprint_many(vec!["SELECT a, b FROM c JOIN d"], None);
488        assert_eq!(result, vec!["SELECT ... FROM c JOIN d"]);
489    }
490
491    #[test]
492    fn test_select_with_from_inner_join_quoted() {
493        let result = fingerprint_many(
494            vec!["SELECT a, b FROM c INNER JOIN d ON (\"d\".\"a\" = \"c\".\"a\")"],
495            None,
496        );
497        assert_eq!(result, vec!["SELECT ... FROM c INNER JOIN d ON ..."]);
498    }
499
500    #[test]
501    fn test_select_with_from_left_outer_join_quoted() {
502        let result = fingerprint_many(
503            vec!["SELECT a, b FROM c LEFT OUTER JOIN d ON (\"d\".\"a\" = \"c\".\"a\")"],
504            None,
505        );
506        assert_eq!(result, vec!["SELECT ... FROM c LEFT OUTER JOIN d ON ..."]);
507    }
508
509    #[test]
510    fn test_select_with_group_by() {
511        let result = fingerprint_many(vec!["SELECT a, b FROM c GROUP BY a, b"], None);
512        assert_eq!(result, vec!["SELECT ... FROM c GROUP BY ..."]);
513    }
514
515    #[test]
516    fn test_select_with_order_by() {
517        let result = fingerprint_many(vec!["SELECT a, b FROM c ORDER BY a, b DESC"], None);
518        assert_eq!(result, vec!["SELECT ... FROM c ORDER BY ..."]);
519    }
520
521    #[test]
522    fn test_select_with_order_by_more() {
523        let result = fingerprint_many(vec!["SELECT a, b FROM c ORDER BY a ASC, b DESC"], None);
524        assert_eq!(result, vec!["SELECT ... FROM c ORDER BY ... ASC"]);
525    }
526
527    #[test]
528    fn test_select_with_limit_offset() {
529        let result = fingerprint_many(vec!["SELECT a FROM b LIMIT 21 OFFSET 101 ROWS"], None);
530        assert_eq!(result, vec!["SELECT ... FROM b LIMIT ... OFFSET ... ROWS"]);
531    }
532
533    #[test]
534    fn test_clickhouse_select_with_limit_by() {
535        let result = fingerprint_many(vec!["SELECT a FROM b LIMIT 21 BY c"], None);
536        assert_eq!(result, vec!["SELECT ... FROM b LIMIT ... BY ..."]);
537    }
538
539    #[test]
540    fn test_mysql_select_with_limit_comma() {
541        let result = fingerprint_many(vec!["SELECT a FROM b LIMIT 21, 101"], None);
542        assert_eq!(result, vec!["SELECT ... FROM b LIMIT ..., ..."]);
543    }
544
545    #[test]
546    fn test_select_union() {
547        let result = fingerprint_many(vec!["SELECT a, b FROM c UNION SELECT a, b FROM d"], None);
548        assert_eq!(result, vec!["SELECT ... FROM c UNION SELECT ... FROM d"]);
549    }
550
551    #[test]
552    fn test_select_union_parenthesized() {
553        let result = fingerprint_many(
554            vec!["(SELECT a, b FROM c) UNION (SELECT a, b FROM d)"],
555            None,
556        );
557        assert_eq!(
558            result,
559            vec!["(SELECT ... FROM c) UNION (SELECT ... FROM d)"]
560        );
561    }
562
563    #[test]
564    fn test_select_union_all() {
565        let result = fingerprint_many(
566            vec!["SELECT a, b FROM c UNION ALL SELECT a, b FROM d"],
567            None,
568        );
569        assert_eq!(
570            result,
571            vec!["SELECT ... FROM c UNION ALL SELECT ... FROM d"]
572        );
573    }
574
575    #[test]
576    fn test_select_union_all_parenthesized() {
577        let result = fingerprint_many(
578            vec!["(SELECT a, b FROM c) UNION ALL (SELECT a, b FROM d)"],
579            None,
580        );
581        assert_eq!(
582            result,
583            vec!["(SELECT ... FROM c) UNION ALL (SELECT ... FROM d)"]
584        );
585    }
586
587    #[test]
588    fn test_select_except() {
589        let result = fingerprint_many(vec!["SELECT a, b FROM c EXCEPT SELECT a, b FROM d"], None);
590        assert_eq!(result, vec!["SELECT ... FROM c EXCEPT SELECT ... FROM d"]);
591    }
592
593    #[test]
594    fn test_select_except_parenthesized() {
595        let result = fingerprint_many(
596            vec!["(SELECT a, b FROM c) EXCEPT (SELECT a, b FROM d)"],
597            None,
598        );
599        assert_eq!(
600            result,
601            vec!["(SELECT ... FROM c) EXCEPT (SELECT ... FROM d)"]
602        );
603    }
604
605    #[test]
606    fn test_select_intersect() {
607        let result = fingerprint_many(
608            vec!["SELECT a, b FROM c INTERSECT SELECT a, b FROM d"],
609            None,
610        );
611        assert_eq!(
612            result,
613            vec!["SELECT ... FROM c INTERSECT SELECT ... FROM d"]
614        );
615    }
616
617    #[test]
618    fn test_select_intersect_parenthesized() {
619        let result = fingerprint_many(
620            vec!["(SELECT a, b FROM c) INTERSECT (SELECT a, b FROM d)"],
621            None,
622        );
623        assert_eq!(
624            result,
625            vec!["(SELECT ... FROM c) INTERSECT (SELECT ... FROM d)"]
626        );
627    }
628
629    #[test]
630    fn test_select_union_triple() {
631        let result = fingerprint_many(
632            vec!["SELECT a, b FROM c UNION SELECT a, b FROM d UNION SELECT a, b FROM e"],
633            None,
634        );
635        assert_eq!(
636            result,
637            vec!["SELECT ... FROM c UNION SELECT ... FROM d UNION SELECT ... FROM e"]
638        );
639    }
640
641    #[test]
642    fn test_select_union_triple_parenthesized() {
643        let result = fingerprint_many(
644            vec!["(SELECT a, b FROM c) UNION (SELECT a, b FROM d) UNION (SELECT a, b FROM e)"],
645            None,
646        );
647        assert_eq!(
648            result,
649            vec!["(SELECT ... FROM c) UNION (SELECT ... FROM d) UNION (SELECT ... FROM e)"]
650        );
651    }
652
653    #[test]
654    fn test_with_recursive_select() {
655        let result = fingerprint_many(
656            vec!["WITH RECURSIVE t AS (SELECT a, b FROM c WHERE d = 12345) SELECT * FROM t"],
657            None,
658        );
659        assert_eq!(
660            result,
661            vec!["WITH RECURSIVE t AS (SELECT ... FROM c WHERE ...) SELECT * FROM t"]
662        );
663    }
664
665    #[test]
666    fn test_with_recursive_select_union() {
667        let result = fingerprint_many(
668            vec!["WITH RECURSIVE t AS (SELECT a FROM b UNION SELECT a FROM c) SELECT * FROM t"],
669            None,
670        );
671
672        assert_eq!(
673            result,
674            vec!["WITH RECURSIVE t AS (SELECT ... FROM b UNION SELECT ... FROM c) SELECT * FROM t"],
675        );
676    }
677
678    #[test]
679    fn test_declare_cursor() {
680        let result = fingerprint_many(vec!["DECLARE c CURSOR FOR SELECT a, b FROM c join d"], None);
681        assert_eq!(
682            result,
683            vec!["DECLARE ... CURSOR FOR SELECT ... FROM c JOIN d"]
684        );
685    }
686
687    #[test]
688    fn test_insert() {
689        let result = fingerprint_many(
690            vec!["INSERT INTO c (a, b) VALUES (1, 2), (3, 4) RETURNING d"],
691            None,
692        );
693        assert_eq!(
694            result,
695            vec!["INSERT INTO c (...) VALUES (...) RETURNING ..."]
696        );
697    }
698
699    #[test]
700    fn test_insert_select() {
701        let result = fingerprint_many(vec!["INSERT INTO a (b, c) SELECT d FROM e"], None);
702        assert_eq!(result, vec!["INSERT INTO a (...) SELECT ... FROM e"]);
703    }
704
705    #[test]
706    fn test_insert_on_conflict() {
707        let result = fingerprint_many(
708            vec![
709                "INSERT INTO a (b, c) VALUES (1, 2) ON CONFLICT(\"a\", \"b\") DO UPDATE SET \"d\" = EXCLUDED.d WHERE e = f RETURNING b, c",
710            ],
711            None,
712        );
713        assert_eq!(
714            result,
715            vec![
716                "INSERT INTO a (...) VALUES (...) ON CONFLICT(...) DO UPDATE SET ... = ... WHERE ... RETURNING ..."
717            ]
718        );
719    }
720
721    #[test]
722    fn test_update() {
723        let result = fingerprint_many(
724            vec!["UPDATE a SET b = 1, c = 2 WHERE d = 3 RETURNING e"],
725            None,
726        );
727        assert_eq!(
728            result,
729            vec!["UPDATE a SET ... = ... WHERE ... RETURNING ..."]
730        );
731    }
732
733    #[test]
734    fn test_delete() {
735        let result = fingerprint_many(vec!["DELETE FROM a WHERE b = 1 RETURNING c"], None);
736        assert_eq!(result, vec!["DELETE FROM a WHERE ... RETURNING ..."]);
737    }
738
739    #[test]
740    fn test_insert_select_unnest() {
741        let result = fingerprint_many(
742            vec![
743                "INSERT INTO my_table (col1, col2) SELECT * FROM UNNEST(ARRAY[1,2,3,4,5]) ON CONFLICT(col1) DO UPDATE SET col2 = EXCLUDED.col2",
744            ],
745            None,
746        );
747        assert_eq!(
748            result,
749            vec![
750                "INSERT INTO my_table (...) SELECT * FROM UNNEST(...) ON CONFLICT(...) DO UPDATE SET ... = ..."
751            ]
752        );
753    }
754
755    #[test]
756    fn test_select_from_unnest() {
757        let result = fingerprint_many(vec!["SELECT * FROM UNNEST(ARRAY[1,2,3,4,5])"], None);
758        assert_eq!(result, vec!["SELECT * FROM UNNEST(...)"]);
759    }
760
761    #[test]
762    fn test_select_from_unnest_with_alias() {
763        let result = fingerprint_many(
764            vec!["SELECT * FROM UNNEST(ARRAY[1,2,3,4,5]) AS t (value)"],
765            None,
766        );
767        assert_eq!(result, vec!["SELECT * FROM UNNEST(...) AS t (...)"]);
768    }
769}