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
15pub fn fingerprint_one(input: &str, dialect: Option<&dyn Dialect>) -> String {
27 fingerprint_many(vec![input], dialect).join(" ")
28}
29
30pub 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 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 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 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}