Skip to main content

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