1use std::borrow::Cow;
3use std::ops::ControlFlow;
4
5use itertools::Itertools;
6use sqlparser::ast::{
7 AlterTableOperation, Assignment, BinaryOperator, CloseCursor, ColumnDef, CopySource, Declare,
8 Expr, FunctionArg, Ident, LockTable, ObjectName, Query, Select, SelectItem, SetExpr,
9 ShowStatementFilter, Statement, TableAlias, TableConstraint, TableFactor, UnaryOperator, Value,
10 VisitMut, VisitorMut,
11};
12use sqlparser::dialect::{Dialect, GenericDialect};
13
14use crate::span::TABLE_NAME_REGEX;
15
16const MAX_EXPRESSION_DEPTH: usize = 64;
22
23pub fn parse_query(
26 db_system: Option<&str>,
27 query: &str,
28) -> Result<Vec<Statement>, sqlparser::parser::ParserError> {
29 relay_log::with_scope(
30 |scope| {
31 scope.set_tag("db_system", db_system.unwrap_or_default());
32 scope.set_extra("query", query.into());
33 },
34 || match std::panic::catch_unwind(|| parse_query_inner(db_system, query)) {
35 Ok(res) => res,
36 Err(_) => Err(sqlparser::parser::ParserError::ParserError(
37 "panicked".to_string(),
38 )),
39 },
40 )
41}
42
43fn parse_query_inner(
44 db_system: Option<&str>,
45 query: &str,
46) -> Result<Vec<Statement>, sqlparser::parser::ParserError> {
47 let dialect = db_system
50 .and_then(sqlparser::dialect::dialect_from_str)
51 .unwrap_or_else(|| Box::new(GenericDialect {}));
52 let dialect = DialectWithParameters(dialect);
53
54 sqlparser::parser::Parser::parse_sql(&dialect, query)
55}
56
57pub fn normalize_parsed_queries(
59 db_system: Option<&str>,
60 string: &str,
61) -> Result<(String, Vec<Statement>), ()> {
62 let mut parsed = parse_query(db_system, string).map_err(|_| ())?;
63 parsed.visit(&mut NormalizeVisitor);
64 parsed.visit(&mut MaxDepthVisitor::new());
65
66 let concatenated = parsed
67 .iter()
68 .map(|statement| statement.to_string())
69 .join("; ");
70
71 let replaced = concatenated.replace("___UPDATE_LHS___ = NULL", "..");
73
74 Ok((replaced, parsed))
75}
76
77struct NormalizeVisitor;
81
82impl NormalizeVisitor {
83 fn placeholder() -> Value {
85 Value::Number("%s".into(), false)
86 }
87
88 fn ellipsis() -> Ident {
90 Ident::new("..")
91 }
92
93 fn is_collapsible(item: &SelectItem) -> bool {
96 match item {
97 SelectItem::UnnamedExpr(expr) | SelectItem::ExprWithAlias { expr, .. } => {
98 matches!(
99 expr,
100 Expr::Value(_) | Expr::Identifier(_) | Expr::CompoundIdentifier(_)
101 )
102 }
103 _ => false,
104 }
105 }
106
107 fn collapse_items(collapse: &mut Vec<SelectItem>, output: &mut Vec<SelectItem>) {
109 match collapse.len() {
110 0 => {}
111 1 => {
112 output.append(collapse);
113 }
114 _ => {
115 output.push(SelectItem::UnnamedExpr(Expr::Identifier(Self::ellipsis())));
116 }
117 }
118 }
119
120 fn transform_query(&mut self, query: &mut Query) {
122 if let SetExpr::Select(select) = &mut *query.body {
123 self.transform_select(&mut *select);
124 }
125 }
126
127 fn transform_select(&mut self, select: &mut Select) {
128 let mut collapse = vec![];
130
131 for item in std::mem::take(&mut select.projection) {
133 let item = match item {
135 SelectItem::ExprWithAlias { expr, .. } => SelectItem::UnnamedExpr(expr),
137 SelectItem::QualifiedWildcard(_, options) => SelectItem::Wildcard(options),
139 _ => item,
140 };
141 if Self::is_collapsible(&item) {
142 collapse.push(item);
143 } else {
144 Self::collapse_items(&mut collapse, &mut select.projection);
145 collapse.clear();
146 select.projection.push(item);
147 }
148 }
149 Self::collapse_items(&mut collapse, &mut select.projection);
150 }
151
152 fn simplify_table_alias(alias: &mut Option<TableAlias>) {
153 if let Some(TableAlias { name, columns }) = alias {
154 Self::scrub_name(name);
155 for column in columns {
156 Self::scrub_name(column);
157 }
158 }
159 }
160
161 fn simplify_compound_identifier(parts: &mut Vec<Ident>) {
162 if let Some(mut last) = parts.pop() {
163 Self::scrub_name(&mut last);
164 *parts = vec![last];
165 }
166 }
167
168 fn scrub_name(name: &mut Ident) {
169 name.quote_style = None;
170 if let Cow::Owned(s) = TABLE_NAME_REGEX.replace_all(&name.value, "{%s}") {
171 name.value = s
172 };
173 }
174
175 fn erase_name(name: &mut Ident) {
176 name.quote_style = None;
177 name.value = "%s".into()
178 }
179
180 fn scrub_statement_filter(filter: &mut Option<ShowStatementFilter>) {
181 if let Some(s) = filter {
182 match s {
183 sqlparser::ast::ShowStatementFilter::Like(s)
184 | sqlparser::ast::ShowStatementFilter::ILike(s) => "%s".clone_into(s),
185 sqlparser::ast::ShowStatementFilter::Where(_) => {}
186 }
187 }
188 }
189}
190
191impl VisitorMut for NormalizeVisitor {
192 type Break = ();
193
194 fn pre_visit_relation(&mut self, relation: &mut ObjectName) -> ControlFlow<Self::Break> {
195 Self::simplify_compound_identifier(&mut relation.0);
196 ControlFlow::Continue(())
197 }
198
199 fn pre_visit_table_factor(
200 &mut self,
201 table_factor: &mut TableFactor,
202 ) -> ControlFlow<Self::Break> {
203 match table_factor {
204 TableFactor::Table { alias, .. } => {
205 Self::simplify_table_alias(alias);
206 }
207 TableFactor::Derived {
208 subquery, alias, ..
209 } => {
210 self.transform_query(subquery);
211 Self::simplify_table_alias(alias);
212 }
213 TableFactor::TableFunction { alias, .. } => {
214 Self::simplify_table_alias(alias);
215 }
216 TableFactor::UNNEST {
217 alias,
218 with_offset_alias,
219 ..
220 } => {
221 Self::simplify_table_alias(alias);
222 if let Some(ident) = with_offset_alias {
223 Self::scrub_name(ident);
224 }
225 }
226 TableFactor::NestedJoin { alias, .. } => {
227 Self::simplify_table_alias(alias);
228 }
229 TableFactor::Pivot {
230 value_column,
231 alias,
232 ..
233 } => {
234 Self::simplify_compound_identifier(value_column);
235 Self::simplify_table_alias(alias);
236 }
237 TableFactor::Function {
238 name, args, alias, ..
239 } => {
240 Self::simplify_compound_identifier(&mut name.0);
241 for arg in args {
242 if let FunctionArg::Named { name, .. } = arg {
243 Self::scrub_name(name);
244 }
245 }
246 Self::simplify_table_alias(alias);
247 }
248 TableFactor::JsonTable { columns, alias, .. } => {
249 for column in columns {
250 Self::scrub_name(&mut column.name);
251 }
252 Self::simplify_table_alias(alias);
253 }
254 TableFactor::Unpivot {
255 value,
256 name,
257 columns,
258 alias,
259 ..
260 } => {
261 Self::scrub_name(value);
262 Self::scrub_name(name);
263 Self::simplify_compound_identifier(columns);
264 Self::simplify_table_alias(alias);
265 }
266 }
267 ControlFlow::Continue(())
268 }
269
270 fn pre_visit_expr(&mut self, expr: &mut Expr) -> ControlFlow<Self::Break> {
271 match expr {
272 Expr::Value(x) => *x = Self::placeholder(),
274 Expr::InList { list, .. } => *list = vec![Expr::Value(Self::placeholder())],
276 Expr::CompoundIdentifier(parts) => {
278 Self::simplify_compound_identifier(parts);
279 }
280 Expr::Identifier(ident) => {
282 Self::scrub_name(ident);
283 }
284 Expr::Subquery(query) => self.transform_query(query),
286 Expr::UnaryOp {
288 op: UnaryOperator::Minus,
289 expr: inner,
290 } => {
291 if let Expr::Value(_) = **inner {
292 *expr = Expr::Value(Self::placeholder())
293 }
294 }
295 Expr::Case {
297 operand,
298 conditions,
299 results,
300 else_result,
301 } => {
302 operand.take();
303 *conditions = vec![Expr::Identifier(Self::ellipsis())];
304 *results = vec![Expr::Identifier(Self::ellipsis())];
305 else_result.take();
306 }
307 _ => {}
308 }
309 ControlFlow::Continue(())
310 }
311
312 fn post_visit_expr(&mut self, expr: &mut Expr) -> ControlFlow<Self::Break> {
313 match expr {
316 Expr::Cast { expr: inner, .. } => {
317 *expr = take_expr(inner);
318 }
319 Expr::BinaryOp {
320 left,
321 op: op @ (BinaryOperator::Or | BinaryOperator::And),
322 right,
323 } => {
324 remove_redundant_parentheses(op, left);
325 remove_redundant_parentheses(op, right);
326 if left == right {
327 *expr = take_expr(left);
333 } else {
334 if let Expr::BinaryOp {
340 left: left_left,
341 op: left_op,
342 right: left_right,
343 } = left.as_mut()
344 {
345 if left_op == op && left_right == right {
346 *left = Box::new(take_expr(left_left));
347 }
348 }
349 }
350 }
351 Expr::Nested(inner) if matches!(inner.as_ref(), &Expr::Nested(_)) => {
352 *expr = take_expr(inner);
355 }
356 _ => (),
357 }
358
359 ControlFlow::Continue(())
360 }
361
362 fn post_visit_statement(&mut self, statement: &mut Statement) -> ControlFlow<Self::Break> {
363 match statement {
364 Statement::Query(query) => {
365 self.transform_query(query);
366 }
367 Statement::Insert {
369 columns, source, ..
370 } => {
371 *columns = vec![Self::ellipsis()];
372 if let Some(source) = source.as_mut() {
373 if let SetExpr::Values(v) = &mut *source.body {
374 v.rows = vec![vec![Expr::Value(Self::placeholder())]]
375 }
376 }
377 }
378 Statement::Update { assignments, .. } => {
380 if assignments.len() > 1
381 && assignments
382 .iter()
383 .all(|a| matches!(a.value, Expr::Value(_)))
384 {
385 *assignments = vec![Assignment {
386 id: vec![Ident::new("___UPDATE_LHS___")],
387 value: Expr::Value(Value::Null),
388 }]
389 } else {
390 for assignment in assignments.iter_mut() {
391 Self::simplify_compound_identifier(&mut assignment.id);
392 }
393 }
394 }
395 Statement::Savepoint { name } => Self::erase_name(name),
397 Statement::ReleaseSavepoint { name } => Self::erase_name(name),
398 Statement::Declare { stmts } => {
399 for Declare {
400 names, for_query, ..
401 } in stmts
402 {
403 for name in names {
404 Self::erase_name(name);
405 }
406 if let Some(for_query) = for_query {
407 self.transform_query(for_query.as_mut());
408 }
409 }
410 }
411 Statement::Fetch { name, into, .. } => {
412 Self::erase_name(name);
413 if let Some(into) = into {
414 into.0 = vec![Ident {
415 value: "%s".into(),
416 quote_style: None,
417 }];
418 }
419 }
420 Statement::Close { cursor } => match cursor {
421 CloseCursor::All => {}
422 CloseCursor::Specific { name } => Self::erase_name(name),
423 },
424 Statement::AlterTable {
425 name, operations, ..
426 } => {
427 Self::simplify_compound_identifier(&mut name.0);
428 for operation in operations {
429 match operation {
430 AlterTableOperation::AddConstraint(c) => match c {
431 TableConstraint::Unique { name, columns, .. } => {
432 if let Some(name) = name {
433 Self::scrub_name(name);
434 }
435 for column in columns {
436 Self::scrub_name(column);
437 }
438 }
439 TableConstraint::ForeignKey {
440 name,
441 columns,
442 referred_columns,
443 ..
444 } => {
445 if let Some(name) = name {
446 Self::scrub_name(name);
447 }
448 for column in columns {
449 Self::scrub_name(column);
450 }
451 for column in referred_columns {
452 Self::scrub_name(column);
453 }
454 }
455 TableConstraint::Check { name, .. } => {
456 if let Some(name) = name {
457 Self::scrub_name(name);
458 }
459 }
460 TableConstraint::Index { name, columns, .. } => {
461 if let Some(name) = name {
462 Self::scrub_name(name);
463 }
464 for column in columns {
465 Self::scrub_name(column);
466 }
467 }
468 TableConstraint::FulltextOrSpatial {
469 opt_index_name,
470 columns,
471 ..
472 } => {
473 if let Some(name) = opt_index_name {
474 Self::scrub_name(name);
475 }
476 for column in columns {
477 Self::scrub_name(column);
478 }
479 }
480 },
481 AlterTableOperation::AddColumn { column_def, .. } => {
482 let ColumnDef { name, .. } = column_def;
483 Self::scrub_name(name);
484 }
485 AlterTableOperation::DropConstraint { name, .. } => Self::scrub_name(name),
486 AlterTableOperation::DropColumn { column_name, .. } => {
487 Self::scrub_name(column_name)
488 }
489 AlterTableOperation::RenameColumn {
490 old_column_name,
491 new_column_name,
492 } => {
493 Self::scrub_name(old_column_name);
494 Self::scrub_name(new_column_name);
495 }
496 AlterTableOperation::ChangeColumn {
497 old_name, new_name, ..
498 } => {
499 Self::scrub_name(old_name);
500 Self::scrub_name(new_name);
501 }
502 AlterTableOperation::RenameConstraint { old_name, new_name } => {
503 Self::scrub_name(old_name);
504 Self::scrub_name(new_name);
505 }
506 AlterTableOperation::AlterColumn { column_name, .. } => {
507 Self::scrub_name(column_name);
508 }
509 _ => {}
510 }
511 }
512 }
513 Statement::Analyze { columns, .. } => {
514 Self::simplify_compound_identifier(columns);
515 }
516 Statement::Truncate { .. } => {}
517 Statement::Msck { .. } => {}
518 Statement::Directory { path, .. } => {
519 "%s".clone_into(path);
520 }
521 Statement::Call(_) => {}
522 Statement::Copy { source, values, .. } => {
523 if let CopySource::Table { columns, .. } = source {
524 Self::simplify_compound_identifier(columns);
525 }
526 *values = vec![Some("..".into())];
527 }
528 Statement::CopyIntoSnowflake {
529 from_stage_alias,
530 files,
531 pattern,
532 validation_mode,
533 ..
534 } => {
535 if let Some(from_stage_alias) = from_stage_alias {
536 Self::scrub_name(from_stage_alias);
537 }
538 *files = None;
539 *pattern = None;
540 *validation_mode = None;
541 }
542 Statement::Delete { .. } => {}
543 Statement::CreateView {
544 columns,
545 cluster_by,
546 ..
547 } => {
548 for column in columns {
549 Self::scrub_name(&mut column.name);
550 }
551 Self::simplify_compound_identifier(cluster_by);
552 }
553 Statement::CreateTable { .. } => {}
554 Statement::CreateVirtualTable {
555 module_name,
556 module_args,
557 ..
558 } => {
559 Self::scrub_name(module_name);
560 Self::simplify_compound_identifier(module_args);
561 }
562 Statement::CreateIndex {
563 name,
564 using,
565 include,
566 ..
567 } => {
568 if let Some(name) = name {
570 Self::simplify_compound_identifier(&mut name.0);
571 }
572 if let Some(using) = using {
573 Self::scrub_name(using);
574 }
575 Self::simplify_compound_identifier(include);
576 }
577 Statement::CreateRole { .. } => {}
578 Statement::AlterIndex { name, operation } => {
579 Self::simplify_compound_identifier(&mut name.0);
581 match operation {
582 sqlparser::ast::AlterIndexOperation::RenameIndex { index_name } => {
583 Self::simplify_compound_identifier(&mut index_name.0);
584 }
585 }
586 }
587 Statement::AlterView { .. } => {}
588 Statement::AlterRole { name, .. } => {
589 Self::scrub_name(name);
590 }
591 Statement::AttachDatabase { schema_name, .. } => Self::scrub_name(schema_name),
592 Statement::Drop { .. } => {}
593 Statement::DropFunction { .. } => {}
594 Statement::CreateExtension { name, .. } => Self::scrub_name(name),
595 Statement::Flush { channel, .. } => *channel = None,
596 Statement::Discard { .. } => {}
597 Statement::SetRole { role_name, .. } => {
598 if let Some(role_name) = role_name {
599 Self::scrub_name(role_name);
600 }
601 }
602 Statement::SetVariable { .. } => {}
603 Statement::SetTimeZone { .. } => {}
604 Statement::SetNames {
605 charset_name,
606 collation_name,
607 } => {
608 *charset_name = "%s".into();
609 *collation_name = None;
610 }
611 Statement::SetNamesDefault {} => {}
612 Statement::ShowFunctions { filter } => Self::scrub_statement_filter(filter),
613 Statement::ShowVariable { variable } => Self::simplify_compound_identifier(variable),
614 Statement::ShowVariables { filter, .. } => Self::scrub_statement_filter(filter),
615 Statement::ShowCreate { .. } => {}
616 Statement::ShowColumns { filter, .. } => Self::scrub_statement_filter(filter),
617 Statement::ShowTables {
618 db_name, filter, ..
619 } => {
620 if let Some(db_name) = db_name {
621 Self::scrub_name(db_name);
622 }
623 Self::scrub_statement_filter(filter);
624 }
625 Statement::ShowCollation { filter } => Self::scrub_statement_filter(filter),
626 Statement::Use { db_name } => Self::scrub_name(db_name),
627 Statement::StartTransaction { .. } => {}
628 Statement::SetTransaction { .. } => {}
629 Statement::Comment { comment, .. } => *comment = None,
630 Statement::Commit { .. } => {}
631 Statement::Rollback { savepoint, .. } => {
632 if let Some(savepoint) = savepoint {
633 Self::erase_name(savepoint);
634 }
635 }
636 Statement::CreateSchema { .. } => {}
637 Statement::CreateDatabase {
638 location,
639 managed_location,
640 ..
641 } => {
642 *location = None;
643 *managed_location = None;
644 }
645 Statement::CreateFunction { .. } => {}
646 Statement::CreateProcedure { .. } => {}
647 Statement::CreateMacro { .. } => {}
648 Statement::CreateStage { comment, .. } => *comment = None,
649 Statement::Assert { .. } => {}
650 Statement::Grant {
651 grantees,
652 granted_by,
653 ..
654 } => {
655 Self::simplify_compound_identifier(grantees);
656 *granted_by = None;
657 }
658 Statement::Revoke {
659 grantees,
660 granted_by,
661 ..
662 } => {
663 Self::simplify_compound_identifier(grantees);
664 *granted_by = None;
665 }
666 Statement::Deallocate { name, .. } => {
667 Self::scrub_name(name);
668 }
669 Statement::Execute { name, .. } => Self::scrub_name(name),
670 Statement::Prepare { name, .. } => Self::scrub_name(name),
671 Statement::Kill { id, .. } => *id = 0,
672 Statement::ExplainTable { .. } => {}
673 Statement::Explain { .. } => {}
674 Statement::Merge { .. } => {}
675 Statement::Cache { .. } => {}
676 Statement::UNCache { .. } => {}
677 Statement::CreateSequence { .. } => {}
678 Statement::CreateType { .. } => {}
679 Statement::Pragma { .. } => {}
680 Statement::LockTables { tables } => {
681 for table in tables {
682 let LockTable {
683 table,
684 alias,
685 lock_type: _,
686 } = table;
687 Self::scrub_name(table);
688 if let Some(alias) = alias {
689 Self::scrub_name(alias);
690 }
691 }
692 }
693 Statement::UnlockTables => {}
694 Statement::Install { extension_name } | Statement::Load { extension_name } => {
695 Self::scrub_name(extension_name)
696 }
697 Statement::ShowStatus {
698 filter: _,
699 global: _,
700 session: _,
701 } => {}
702 Statement::Unload {
703 query: _,
704 to,
705 with: _,
706 } => {
707 Self::scrub_name(to);
708 }
709 }
710
711 ControlFlow::Continue(())
712 }
713}
714
715fn take_expr(expr: &mut Expr) -> Expr {
719 let mut swapped = Expr::Value(Value::Null);
720 std::mem::swap(&mut swapped, expr);
721 swapped
722}
723
724fn remove_redundant_parentheses(outer_op: &BinaryOperator, expr: &mut Expr) {
729 if let Expr::Nested(inner) = expr {
730 if let Expr::BinaryOp { op, .. } = inner.as_ref() {
731 if op == outer_op {
732 *expr = take_expr(inner.as_mut());
733 }
734 }
735 }
736}
737
738struct MaxDepthVisitor {
742 current_expr_depth: usize,
744}
745
746impl MaxDepthVisitor {
747 pub fn new() -> Self {
748 Self {
749 current_expr_depth: 0,
750 }
751 }
752}
753
754impl VisitorMut for MaxDepthVisitor {
755 type Break = ();
756
757 fn pre_visit_expr(&mut self, expr: &mut Expr) -> ControlFlow<Self::Break> {
758 if self.current_expr_depth > MAX_EXPRESSION_DEPTH {
759 *expr = Expr::Value(Value::Placeholder("..".to_owned()));
760 return ControlFlow::Continue(());
761 }
762 self.current_expr_depth += 1;
763 ControlFlow::Continue(())
764 }
765
766 fn post_visit_expr(&mut self, _expr: &mut Expr) -> ControlFlow<Self::Break> {
767 self.current_expr_depth = self.current_expr_depth.saturating_sub(1);
768 ControlFlow::Continue(())
769 }
770}
771
772#[derive(Debug)]
774struct DialectWithParameters(Box<dyn Dialect>);
775
776impl DialectWithParameters {
777 const PARAMETERS: &'static str = "?%:";
778}
779
780impl Dialect for DialectWithParameters {
781 fn dialect(&self) -> std::any::TypeId {
782 self.0.dialect()
783 }
784
785 fn is_identifier_start(&self, ch: char) -> bool {
786 Self::PARAMETERS.contains(ch) || self.0.is_identifier_start(ch)
787 }
788
789 fn is_identifier_part(&self, ch: char) -> bool {
790 self.0.is_identifier_part(ch)
791 }
792
793 fn is_delimited_identifier_start(&self, ch: char) -> bool {
794 self.0.is_delimited_identifier_start(ch)
795 }
796
797 fn is_proper_identifier_inside_quotes(
798 &self,
799 chars: std::iter::Peekable<std::str::Chars<'_>>,
800 ) -> bool {
801 self.0.is_proper_identifier_inside_quotes(chars)
802 }
803
804 fn supports_filter_during_aggregation(&self) -> bool {
805 self.0.supports_filter_during_aggregation()
806 }
807
808 fn supports_within_after_array_aggregation(&self) -> bool {
809 self.0.supports_within_after_array_aggregation()
810 }
811
812 fn supports_group_by_expr(&self) -> bool {
813 self.0.supports_group_by_expr()
814 }
815
816 fn supports_substring_from_for_expr(&self) -> bool {
817 self.0.supports_substring_from_for_expr()
818 }
819
820 fn parse_prefix(
821 &self,
822 parser: &mut sqlparser::parser::Parser,
823 ) -> Option<Result<Expr, sqlparser::parser::ParserError>> {
824 self.0.parse_prefix(parser)
825 }
826
827 fn parse_infix(
828 &self,
829 parser: &mut sqlparser::parser::Parser,
830 expr: &Expr,
831 precedence: u8,
832 ) -> Option<Result<Expr, sqlparser::parser::ParserError>> {
833 self.0.parse_infix(parser, expr, precedence)
834 }
835
836 fn get_next_precedence(
837 &self,
838 parser: &sqlparser::parser::Parser,
839 ) -> Option<Result<u8, sqlparser::parser::ParserError>> {
840 self.0.get_next_precedence(parser)
841 }
842
843 fn parse_statement(
844 &self,
845 parser: &mut sqlparser::parser::Parser,
846 ) -> Option<Result<Statement, sqlparser::parser::ParserError>> {
847 self.0.parse_statement(parser)
848 }
849}
850
851#[cfg(test)]
852mod tests {
853 use super::*;
854
855 #[test]
856 fn parse_deep_expression() {
857 let query = "SELECT 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1";
858 assert_eq!(
859 normalize_parsed_queries(None, query).unwrap().0,
860 "SELECT .. + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s"
861 );
862 }
863
864 #[test]
865 fn parse_dont_panic() {
866 assert!(parse_query_inner(None, "REPLACE g;'341234c").is_err());
867 }
868}