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