From 4b869b273c382b97fd693bd43bf47bb61cbbd664 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sat, 10 May 2025 01:56:21 +0200 Subject: [PATCH 1/8] implement pretty-printing with {:#} closes https://github.com/apache/datafusion-sqlparser-rs/issues/1845 --- src/ast/mod.rs | 14 +- src/ast/query.rs | 336 +++++++++++++++++++++++++----------------- src/display_utils.rs | 127 ++++++++++++++++ src/lib.rs | 1 + tests/pretty_print.rs | 114 ++++++++++++++ 5 files changed, 451 insertions(+), 141 deletions(-) create mode 100644 src/display_utils.rs create mode 100644 tests/pretty_print.rs diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 6b7ba12d9c..67f6a162be 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -40,8 +40,11 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "visitor")] use sqlparser_derive::{Visit, VisitMut}; -use crate::keywords::Keyword; use crate::tokenizer::{Span, Token}; +use crate::{ + display_utils::{Indent, NewLine}, + keywords::Keyword, +}; pub use self::data_type::{ ArrayElemTypeDef, BinaryLength, CharLengthUnits, CharacterLength, DataType, EnumMember, @@ -134,9 +137,9 @@ where fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut delim = ""; for t in self.slice { - write!(f, "{delim}")?; + f.write_str(delim)?; delim = self.sep; - write!(f, "{t}")?; + t.fmt(f)?; } Ok(()) } @@ -4219,7 +4222,8 @@ impl fmt::Display for Statement { } => { write!(f, "FLUSH")?; if let Some(location) = location { - write!(f, " {location}")?; + f.write_str(" ")?; + location.fmt(f)?; } write!(f, " {object_type}")?; @@ -4301,7 +4305,7 @@ impl fmt::Display for Statement { write!(f, "{statement}") } - Statement::Query(s) => write!(f, "{s}"), + Statement::Query(s) => s.fmt(f), Statement::Declare { stmts } => { write!(f, "DECLARE ")?; write!(f, "{}", display_separated(stmts, "; ")) diff --git a/src/ast/query.rs b/src/ast/query.rs index a90b616681..e2ba17edee 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -27,6 +27,7 @@ use sqlparser_derive::{Visit, VisitMut}; use crate::{ ast::*, + display_utils::{indented_list, SpaceOrNewline}, tokenizer::{Token, TokenWithSpan}, }; @@ -70,33 +71,41 @@ pub struct Query { impl fmt::Display for Query { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(ref with) = self.with { - write!(f, "{with} ")?; + with.fmt(f)?; + SpaceOrNewline.fmt(f)?; } - write!(f, "{}", self.body)?; + self.body.fmt(f)?; if let Some(ref order_by) = self.order_by { - write!(f, " {order_by}")?; + f.write_str(" ")?; + order_by.fmt(f)?; } if let Some(ref limit_clause) = self.limit_clause { limit_clause.fmt(f)?; } if let Some(ref settings) = self.settings { - write!(f, " SETTINGS {}", display_comma_separated(settings))?; + f.write_str(" SETTINGS ")?; + display_comma_separated(settings).fmt(f)?; } if let Some(ref fetch) = self.fetch { - write!(f, " {fetch}")?; + f.write_str(" ")?; + fetch.fmt(f)?; } if !self.locks.is_empty() { - write!(f, " {}", display_separated(&self.locks, " "))?; + f.write_str(" ")?; + display_separated(&self.locks, " ").fmt(f)?; } if let Some(ref for_clause) = self.for_clause { - write!(f, " {}", for_clause)?; + f.write_str(" ")?; + for_clause.fmt(f)?; } if let Some(ref format) = self.format_clause { - write!(f, " {}", format)?; + f.write_str(" ")?; + format.fmt(f)?; } for pipe_operator in &self.pipe_operators { - write!(f, " |> {}", pipe_operator)?; + f.write_str(" |> ")?; + pipe_operator.fmt(f)?; } Ok(()) } @@ -169,29 +178,39 @@ impl SetExpr { impl fmt::Display for SetExpr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - SetExpr::Select(s) => write!(f, "{s}"), - SetExpr::Query(q) => write!(f, "({q})"), - SetExpr::Values(v) => write!(f, "{v}"), - SetExpr::Insert(v) => write!(f, "{v}"), - SetExpr::Update(v) => write!(f, "{v}"), - SetExpr::Delete(v) => write!(f, "{v}"), - SetExpr::Table(t) => write!(f, "{t}"), + SetExpr::Select(s) => s.fmt(f), + SetExpr::Query(q) => { + f.write_str("(")?; + q.fmt(f)?; + f.write_str(")") + } + SetExpr::Values(v) => v.fmt(f), + SetExpr::Insert(v) => v.fmt(f), + SetExpr::Update(v) => v.fmt(f), + SetExpr::Delete(v) => v.fmt(f), + SetExpr::Table(t) => t.fmt(f), SetExpr::SetOperation { left, right, op, set_quantifier, } => { - write!(f, "{left} {op}")?; + left.fmt(f)?; + SpaceOrNewline.fmt(f)?; + op.fmt(f)?; match set_quantifier { SetQuantifier::All | SetQuantifier::Distinct | SetQuantifier::ByName | SetQuantifier::AllByName - | SetQuantifier::DistinctByName => write!(f, " {set_quantifier}")?, - SetQuantifier::None => write!(f, "{set_quantifier}")?, + | SetQuantifier::DistinctByName => { + f.write_str(" ")?; + set_quantifier.fmt(f)?; + } + SetQuantifier::None => {} } - write!(f, " {right}")?; + SpaceOrNewline.fmt(f)?; + right.fmt(f)?; Ok(()) } } @@ -242,7 +261,7 @@ impl fmt::Display for SetQuantifier { SetQuantifier::ByName => write!(f, "BY NAME"), SetQuantifier::AllByName => write!(f, "ALL BY NAME"), SetQuantifier::DistinctByName => write!(f, "DISTINCT BY NAME"), - SetQuantifier::None => write!(f, ""), + SetQuantifier::None => Ok(()), } } } @@ -357,90 +376,122 @@ impl fmt::Display for Select { } if let Some(value_table_mode) = self.value_table_mode { - write!(f, " {value_table_mode}")?; + f.write_str(" ")?; + value_table_mode.fmt(f)?; } if let Some(ref top) = self.top { if self.top_before_distinct { - write!(f, " {top}")?; + f.write_str(" ")?; + top.fmt(f)?; } } if let Some(ref distinct) = self.distinct { - write!(f, " {distinct}")?; + f.write_str(" ")?; + distinct.fmt(f)?; } if let Some(ref top) = self.top { if !self.top_before_distinct { - write!(f, " {top}")?; + f.write_str(" ")?; + top.fmt(f)?; } } if !self.projection.is_empty() { - write!(f, " {}", display_comma_separated(&self.projection))?; + indented_list(f, &self.projection)?; } if let Some(ref into) = self.into { - write!(f, " {into}")?; + f.write_str(" ")?; + into.fmt(f)?; } if self.flavor == SelectFlavor::Standard && !self.from.is_empty() { - write!(f, " FROM {}", display_comma_separated(&self.from))?; + SpaceOrNewline.fmt(f)?; + f.write_str("FROM")?; + indented_list(f, &self.from)?; } if !self.lateral_views.is_empty() { for lv in &self.lateral_views { - write!(f, "{lv}")?; + lv.fmt(f)?; } } if let Some(ref prewhere) = self.prewhere { - write!(f, " PREWHERE {prewhere}")?; + f.write_str(" PREWHERE ")?; + prewhere.fmt(f)?; } if let Some(ref selection) = self.selection { - write!(f, " WHERE {selection}")?; + SpaceOrNewline.fmt(f)?; + f.write_str("WHERE")?; + SpaceOrNewline.fmt(f)?; + Indent(selection).fmt(f)?; } match &self.group_by { - GroupByExpr::All(_) => write!(f, " {}", self.group_by)?, + GroupByExpr::All(_) => { + SpaceOrNewline.fmt(f)?; + self.group_by.fmt(f)?; + } GroupByExpr::Expressions(exprs, _) => { if !exprs.is_empty() { - write!(f, " {}", self.group_by)? + SpaceOrNewline.fmt(f)?; + self.group_by.fmt(f)?; } } } if !self.cluster_by.is_empty() { - write!( - f, - " CLUSTER BY {}", - display_comma_separated(&self.cluster_by) - )?; + SpaceOrNewline.fmt(f)?; + f.write_str("CLUSTER BY")?; + SpaceOrNewline.fmt(f)?; + Indent(display_comma_separated(&self.cluster_by)).fmt(f)?; } if !self.distribute_by.is_empty() { - write!( - f, - " DISTRIBUTE BY {}", - display_comma_separated(&self.distribute_by) - )?; + SpaceOrNewline.fmt(f)?; + f.write_str("DISTRIBUTE BY")?; + SpaceOrNewline.fmt(f)?; + display_comma_separated(&self.distribute_by).fmt(f)?; } if !self.sort_by.is_empty() { - write!(f, " SORT BY {}", display_comma_separated(&self.sort_by))?; + SpaceOrNewline.fmt(f)?; + f.write_str("SORT BY")?; + SpaceOrNewline.fmt(f)?; + Indent(display_comma_separated(&self.sort_by)).fmt(f)?; } if let Some(ref having) = self.having { - write!(f, " HAVING {having}")?; + SpaceOrNewline.fmt(f)?; + f.write_str("HAVING")?; + SpaceOrNewline.fmt(f)?; + Indent(having).fmt(f)?; } if self.window_before_qualify { if !self.named_window.is_empty() { - write!(f, " WINDOW {}", display_comma_separated(&self.named_window))?; + SpaceOrNewline.fmt(f)?; + f.write_str("WINDOW")?; + SpaceOrNewline.fmt(f)?; + display_comma_separated(&self.named_window).fmt(f)?; } if let Some(ref qualify) = self.qualify { - write!(f, " QUALIFY {qualify}")?; + SpaceOrNewline.fmt(f)?; + f.write_str("QUALIFY")?; + SpaceOrNewline.fmt(f)?; + qualify.fmt(f)?; } } else { if let Some(ref qualify) = self.qualify { - write!(f, " QUALIFY {qualify}")?; + SpaceOrNewline.fmt(f)?; + f.write_str("QUALIFY")?; + SpaceOrNewline.fmt(f)?; + qualify.fmt(f)?; } if !self.named_window.is_empty() { - write!(f, " WINDOW {}", display_comma_separated(&self.named_window))?; + SpaceOrNewline.fmt(f)?; + f.write_str("WINDOW")?; + SpaceOrNewline.fmt(f)?; + display_comma_separated(&self.named_window).fmt(f)?; } } if let Some(ref connect_by) = self.connect_by { - write!(f, " {connect_by}")?; + SpaceOrNewline.fmt(f)?; + connect_by.fmt(f)?; } Ok(()) } @@ -546,12 +597,12 @@ pub struct With { impl fmt::Display for With { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "WITH {}{}", - if self.recursive { "RECURSIVE " } else { "" }, - display_comma_separated(&self.cte_tables) - ) + f.write_str("WITH ")?; + if self.recursive { + f.write_str("RECURSIVE ")?; + } + display_comma_separated(&self.cte_tables).fmt(f)?; + Ok(()) } } @@ -598,8 +649,24 @@ pub struct Cte { impl fmt::Display for Cte { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.materialized.as_ref() { - None => write!(f, "{} AS ({})", self.alias, self.query)?, - Some(materialized) => write!(f, "{} AS {materialized} ({})", self.alias, self.query)?, + None => { + self.alias.fmt(f)?; + f.write_str(" AS (")?; + NewLine.fmt(f)?; + Indent(&self.query).fmt(f)?; + NewLine.fmt(f)?; + f.write_str(")")?; + } + Some(materialized) => { + self.alias.fmt(f)?; + f.write_str(" AS ")?; + materialized.fmt(f)?; + f.write_str(" (")?; + NewLine.fmt(f)?; + Indent(&self.query).fmt(f)?; + NewLine.fmt(f)?; + f.write_str(")")?; + } }; if let Some(ref fr) = self.from { write!(f, " FROM {fr}")?; @@ -912,18 +979,21 @@ impl fmt::Display for ReplaceSelectElement { impl fmt::Display for SelectItem { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use std::fmt::Write; match &self { - SelectItem::UnnamedExpr(expr) => write!(f, "{expr}"), - SelectItem::ExprWithAlias { expr, alias } => write!(f, "{expr} AS {alias}"), + SelectItem::UnnamedExpr(expr) => expr.fmt(f), + SelectItem::ExprWithAlias { expr, alias } => { + expr.fmt(f)?; + f.write_str(" AS ")?; + alias.fmt(f) + } SelectItem::QualifiedWildcard(kind, additional_options) => { - write!(f, "{kind}")?; - write!(f, "{additional_options}")?; - Ok(()) + kind.fmt(f)?; + additional_options.fmt(f) } SelectItem::Wildcard(additional_options) => { - write!(f, "*")?; - write!(f, "{additional_options}")?; - Ok(()) + f.write_char('*')?; + additional_options.fmt(f) } } } @@ -939,9 +1009,10 @@ pub struct TableWithJoins { impl fmt::Display for TableWithJoins { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.relation)?; + self.relation.fmt(f)?; for join in &self.joins { - write!(f, "{join}")?; + SpaceOrNewline.fmt(f)?; + join.fmt(f)?; } Ok(()) } @@ -1769,9 +1840,9 @@ impl fmt::Display for TableFactor { sample, index_hints, } => { - write!(f, "{name}")?; + name.fmt(f)?; if let Some(json_path) = json_path { - write!(f, "{json_path}")?; + json_path.fmt(f)?; } if !partitions.is_empty() { write!(f, "PARTITION ({})", display_comma_separated(partitions))?; @@ -1818,7 +1889,11 @@ impl fmt::Display for TableFactor { if *lateral { write!(f, "LATERAL ")?; } - write!(f, "({subquery})")?; + f.write_str("(")?; + NewLine.fmt(f)?; + Indent(subquery).fmt(f)?; + NewLine.fmt(f)?; + f.write_str(")")?; if let Some(alias) = alias { write!(f, " AS {alias}")?; } @@ -2132,116 +2207,104 @@ impl fmt::Display for Join { Suffix(constraint) } if self.global { - write!(f, " GLOBAL")?; + write!(f, "GLOBAL ")?; } match &self.join_operator { - JoinOperator::Join(constraint) => write!( - f, - " {}JOIN {}{}", + JoinOperator::Join(constraint) => f.write_fmt(format_args!( + "{}JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::Inner(constraint) => write!( - f, - " {}INNER JOIN {}{}", + )), + JoinOperator::Inner(constraint) => f.write_fmt(format_args!( + "{}INNER JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::Left(constraint) => write!( - f, - " {}LEFT JOIN {}{}", + )), + JoinOperator::Left(constraint) => f.write_fmt(format_args!( + "{}LEFT JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::LeftOuter(constraint) => write!( - f, - " {}LEFT OUTER JOIN {}{}", + )), + JoinOperator::LeftOuter(constraint) => f.write_fmt(format_args!( + "{}LEFT OUTER JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::Right(constraint) => write!( - f, - " {}RIGHT JOIN {}{}", + )), + JoinOperator::Right(constraint) => f.write_fmt(format_args!( + "{}RIGHT JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::RightOuter(constraint) => write!( - f, - " {}RIGHT OUTER JOIN {}{}", + )), + JoinOperator::RightOuter(constraint) => f.write_fmt(format_args!( + "{}RIGHT OUTER JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::FullOuter(constraint) => write!( - f, - " {}FULL JOIN {}{}", + )), + JoinOperator::FullOuter(constraint) => f.write_fmt(format_args!( + "{}FULL JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::CrossJoin => write!(f, " CROSS JOIN {}", self.relation), - JoinOperator::Semi(constraint) => write!( - f, - " {}SEMI JOIN {}{}", + )), + JoinOperator::CrossJoin => f.write_fmt(format_args!("CROSS JOIN {}", self.relation)), + JoinOperator::Semi(constraint) => f.write_fmt(format_args!( + "{}SEMI JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::LeftSemi(constraint) => write!( - f, - " {}LEFT SEMI JOIN {}{}", + )), + JoinOperator::LeftSemi(constraint) => f.write_fmt(format_args!( + "{}LEFT SEMI JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::RightSemi(constraint) => write!( - f, - " {}RIGHT SEMI JOIN {}{}", + )), + JoinOperator::RightSemi(constraint) => f.write_fmt(format_args!( + "{}RIGHT SEMI JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::Anti(constraint) => write!( - f, - " {}ANTI JOIN {}{}", + )), + JoinOperator::Anti(constraint) => f.write_fmt(format_args!( + "{}ANTI JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::LeftAnti(constraint) => write!( - f, - " {}LEFT ANTI JOIN {}{}", + )), + JoinOperator::LeftAnti(constraint) => f.write_fmt(format_args!( + "{}LEFT ANTI JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::RightAnti(constraint) => write!( - f, - " {}RIGHT ANTI JOIN {}{}", + )), + JoinOperator::RightAnti(constraint) => f.write_fmt(format_args!( + "{}RIGHT ANTI JOIN {}{}", prefix(constraint), self.relation, suffix(constraint) - ), - JoinOperator::CrossApply => write!(f, " CROSS APPLY {}", self.relation), - JoinOperator::OuterApply => write!(f, " OUTER APPLY {}", self.relation), + )), + JoinOperator::CrossApply => f.write_fmt(format_args!("CROSS APPLY {}", self.relation)), + JoinOperator::OuterApply => f.write_fmt(format_args!("OUTER APPLY {}", self.relation)), JoinOperator::AsOf { match_condition, constraint, - } => write!( - f, - " ASOF JOIN {} MATCH_CONDITION ({match_condition}){}", + } => f.write_fmt(format_args!( + "ASOF JOIN {} MATCH_CONDITION ({match_condition}){}", self.relation, suffix(constraint) - ), - JoinOperator::StraightJoin(constraint) => { - write!(f, " STRAIGHT_JOIN {}{}", self.relation, suffix(constraint)) - } + )), + JoinOperator::StraightJoin(constraint) => f.write_fmt(format_args!( + "STRAIGHT_JOIN {}{}", + self.relation, + suffix(constraint) + )), } } } @@ -2914,8 +2977,9 @@ impl fmt::Display for GroupByExpr { Ok(()) } GroupByExpr::Expressions(col_names, modifiers) => { - let col_names = display_comma_separated(col_names); - write!(f, "GROUP BY {col_names}")?; + f.write_str("GROUP BY")?; + SpaceOrNewline.fmt(f)?; + Indent(display_comma_separated(col_names)).fmt(f)?; if !modifiers.is_empty() { write!(f, " {}", display_separated(modifiers, " "))?; } diff --git a/src/display_utils.rs b/src/display_utils.rs new file mode 100644 index 0000000000..792064bde9 --- /dev/null +++ b/src/display_utils.rs @@ -0,0 +1,127 @@ +use std::fmt::{self, Display, Write}; + +/// A wrapper around a value that adds an indent to the value when displayed with {:#}. +pub(crate) struct Indent(pub T); + +const INDENT: &str = " "; + +impl Display for Indent +where + T: Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + f.write_str(INDENT)?; + write!(Indent(f), "{:#}", self.0) + } else { + self.0.fmt(f) + } + } +} + +/// Adds an indent to the inner writer +impl Write for Indent +where + T: Write, +{ + fn write_str(&mut self, s: &str) -> fmt::Result { + let mut first = true; + for line in s.split('\n') { + if !first { + write!(self.0, "\n{INDENT}")?; + } + self.0.write_str(line)?; + first = false; + } + Ok(()) + } +} + +/// A value that inserts a newline when displayed with {:#}, but not when displayed with {}. +pub(crate) struct NewLine; + +impl Display for NewLine { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + f.write_char('\n') + } else { + Ok(()) + } + } +} + +/// A value that inserts a space when displayed with {}, but a newline when displayed with {:#}. +pub(crate) struct SpaceOrNewline; + +impl Display for SpaceOrNewline { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + f.write_char('\n') + } else { + f.write_char(' ') + } + } +} + +/// A value that displays a comma-separated list of values. +/// When pretty-printed (using {:#}), it displays each value on a new line. +pub struct DisplayCommaSeparated<'a, T: fmt::Display>(&'a [T]); + +impl<'a, T: fmt::Display> fmt::Display for DisplayCommaSeparated<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut first = true; + for t in self.0 { + if !first { + f.write_char(',')?; + SpaceOrNewline.fmt(f)?; + } + first = false; + t.fmt(f)?; + } + Ok(()) + } +} + +/// Displays a whitespace, followed by a comma-separated list that is indented when pretty-printed. +pub(crate) fn indented_list(f: &mut fmt::Formatter, slice: &[T]) -> fmt::Result { + SpaceOrNewline.fmt(f)?; + Indent(DisplayCommaSeparated(slice)).fmt(f) +} + +#[cfg(test)] +mod tests { + use super::*; + + struct DisplayCharByChar(T); + + impl Display for DisplayCharByChar { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for c in self.0.to_string().chars() { + write!(f, "{}", c)?; + } + Ok(()) + } + } + + #[test] + fn test_indent() { + let original = "line 1\nline 2"; + let indent = Indent(original); + assert_eq!( + indent.to_string(), + original, + "Only the alternate form should be indented" + ); + let expected = " line 1\n line 2"; + assert_eq!(format!("{:#}", indent), expected); + let display_char_by_char = DisplayCharByChar(original); + assert_eq!(format!("{:#}", Indent(display_char_by_char)), expected); + } + + #[test] + fn test_space_or_newline() { + let space_or_newline = SpaceOrNewline; + assert_eq!(format!("{}", space_or_newline), " "); + assert_eq!(format!("{:#}", space_or_newline), "\n"); + } +} diff --git a/src/lib.rs b/src/lib.rs index 5d72f9f0e9..fef3b04631 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,6 +142,7 @@ extern crate pretty_assertions; pub mod ast; #[macro_use] pub mod dialect; +mod display_utils; pub mod keywords; pub mod parser; pub mod tokenizer; diff --git a/tests/pretty_print.rs b/tests/pretty_print.rs new file mode 100644 index 0000000000..712df0d8dd --- /dev/null +++ b/tests/pretty_print.rs @@ -0,0 +1,114 @@ +use sqlparser::dialect::GenericDialect; +use sqlparser::parser::Parser; + +#[test] +fn test_pretty_print_select() { + let sql = "SELECT a, b, c FROM my_table WHERE x = 1 AND y = 2"; + let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); + let pretty = format!("{:#}", ast[0]); + assert_eq!( + pretty, + r#"SELECT + a, + b, + c +FROM + my_table +WHERE + x = 1 AND y = 2"# + ); +} + +#[test] +fn test_pretty_print_join() { + let sql = "SELECT a FROM table1 JOIN table2 ON table1.id = table2.id"; + let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); + let pretty = format!("{:#}", ast[0]); + assert_eq!( + pretty, + r#"SELECT + a +FROM + table1 + JOIN table2 ON table1.id = table2.id"# + ); +} + +#[test] +fn test_pretty_print_subquery() { + let sql = "SELECT * FROM (SELECT a, b FROM my_table) AS subquery"; + let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); + let pretty = format!("{:#}", ast[0]); + assert_eq!( + pretty, + r#"SELECT + * +FROM + ( + SELECT + a, + b + FROM + my_table + ) AS subquery"# + ); +} + +#[test] +fn test_pretty_print_union() { + let sql = "SELECT a FROM table1 UNION SELECT b FROM table2"; + let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); + let pretty = format!("{:#}", ast[0]); + assert_eq!( + pretty, + r#"SELECT + a +FROM + table1 +UNION +SELECT + b +FROM + table2"# + ); +} + +#[test] +fn test_pretty_print_group_by() { + let sql = "SELECT a, COUNT(*) FROM my_table GROUP BY a HAVING COUNT(*) > 1"; + let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); + let pretty = format!("{:#}", ast[0]); + assert_eq!( + pretty, + r#"SELECT + a, + COUNT(*) +FROM + my_table +GROUP BY + a +HAVING + COUNT(*) > 1"# + ); +} + +#[test] +fn test_pretty_print_cte() { + let sql = "WITH cte AS (SELECT a, b FROM my_table) SELECT * FROM cte"; + let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); + let pretty = format!("{:#}", ast[0]); + assert_eq!( + pretty, + r#"WITH cte AS ( + SELECT + a, + b + FROM + my_table +) +SELECT + * +FROM + cte"# + ); +} From ae9479d4e8f135e455f3d4334d7398afebd43c72 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sat, 10 May 2025 02:04:10 +0200 Subject: [PATCH 2/8] nostd fixes --- src/ast/query.rs | 2 +- src/display_utils.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index e2ba17edee..488868c510 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -979,7 +979,7 @@ impl fmt::Display for ReplaceSelectElement { impl fmt::Display for SelectItem { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use std::fmt::Write; + use core::fmt::Write; match &self { SelectItem::UnnamedExpr(expr) => expr.fmt(f), SelectItem::ExprWithAlias { expr, alias } => { diff --git a/src/display_utils.rs b/src/display_utils.rs index 792064bde9..e283fb3499 100644 --- a/src/display_utils.rs +++ b/src/display_utils.rs @@ -1,4 +1,4 @@ -use std::fmt::{self, Display, Write}; +use core::fmt::{self, Display, Write}; /// A wrapper around a value that adds an indent to the value when displayed with {:#}. pub(crate) struct Indent(pub T); From b8e9de9abe15da9201e09d254ef585b1a7b0329f Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sat, 10 May 2025 02:07:10 +0200 Subject: [PATCH 3/8] clippy --- src/ast/query.rs | 1 - src/display_utils.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index 488868c510..33168695f7 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -1405,7 +1405,6 @@ pub enum TableFactor { #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] - pub enum TableSampleKind { /// Table sample located before the table alias option BeforeTableAlias(Box), diff --git a/src/display_utils.rs b/src/display_utils.rs index e283fb3499..8f2fd0cd8f 100644 --- a/src/display_utils.rs +++ b/src/display_utils.rs @@ -67,7 +67,7 @@ impl Display for SpaceOrNewline { /// When pretty-printed (using {:#}), it displays each value on a new line. pub struct DisplayCommaSeparated<'a, T: fmt::Display>(&'a [T]); -impl<'a, T: fmt::Display> fmt::Display for DisplayCommaSeparated<'a, T> { +impl fmt::Display for DisplayCommaSeparated<'_, T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut first = true; for t in self.0 { From 5aeb83c225aa27a92684bfa838f8455af738117e Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sat, 10 May 2025 14:32:12 +0200 Subject: [PATCH 4/8] document the new feature --- src/ast/mod.rs | 22 ++++++++++++++++++++++ src/display_utils.rs | 6 ++++++ src/lib.rs | 21 +++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 67f6a162be..6431fa6da9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -4207,6 +4207,28 @@ impl fmt::Display for RaisErrorOption { } impl fmt::Display for Statement { + /// Formats a SQL statement with support for pretty printing. + /// + /// When using the alternate flag (`{:#}`), the statement will be formatted with proper + /// indentation and line breaks. For example: + /// + /// ``` + /// # use sqlparser::dialect::GenericDialect; + /// # use sqlparser::parser::Parser; + /// let sql = "SELECT a, b FROM table_1"; + /// let ast = Parser::parse_sql(&GenericDialect, sql).unwrap(); + /// + /// // Regular formatting + /// assert_eq!(format!("{}", ast[0]), "SELECT a, b FROM table_1"); + /// + /// // Pretty printing + /// assert_eq!(format!("{:#}", ast[0]), + /// r#"SELECT + /// a, + /// b + /// FROM + /// table_1"#); + /// ``` // Clippy thinks this function is too complicated, but it is painful to // split up without extracting structs for each `Statement` variant. #[allow(clippy::cognitive_complexity)] diff --git a/src/display_utils.rs b/src/display_utils.rs index 8f2fd0cd8f..e7e1272fb3 100644 --- a/src/display_utils.rs +++ b/src/display_utils.rs @@ -1,3 +1,9 @@ +//! Utilities for formatting SQL AST nodes with pretty printing support. +//! +//! The module provides formatters that implement the `Display` trait with support +//! for both regular (`{}`) and pretty (`{:#}`) formatting modes. Pretty printing +//! adds proper indentation and line breaks to make SQL statements more readable. + use core::fmt::{self, Display, Write}; /// A wrapper around a value that adds an indent to the value when displayed with {:#}. diff --git a/src/lib.rs b/src/lib.rs index fef3b04631..c81ab50023 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,27 @@ //! // The original SQL text can be generated from the AST //! assert_eq!(ast[0].to_string(), sql); //! ``` +//! +//! # Pretty Printing +//! +//! SQL statements can be pretty-printed with proper indentation and line breaks using the alternate flag (`{:#}`): +//! +//! ``` +//! # use sqlparser::dialect::GenericDialect; +//! # use sqlparser::parser::Parser; +//! let sql = "SELECT a, b FROM table_1"; +//! let ast = Parser::parse_sql(&GenericDialect, sql).unwrap(); +//! +//! // Pretty print with indentation and line breaks +//! let pretty_sql = format!("{:#}", ast[0]); +//! assert_eq!(pretty_sql, r#" +//! SELECT +//! a, +//! b +//! FROM +//! table_1 +//! "#.trim()); +//! ``` //! [sqlparser crates.io page]: https://crates.io/crates/sqlparser //! [`Parser::parse_sql`]: crate::parser::Parser::parse_sql //! [`Parser::new`]: crate::parser::Parser::new From 492331603cffa44fa27b200fc3e12a128e051765 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sat, 10 May 2025 14:50:14 +0200 Subject: [PATCH 5/8] pretty print case statements --- src/ast/mod.rs | 28 +++++++++++++++++++++------- tests/pretty_print.rs | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 6431fa6da9..854abc2759 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -40,7 +40,10 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "visitor")] use sqlparser_derive::{Visit, VisitMut}; -use crate::tokenizer::{Span, Token}; +use crate::{ + display_utils::SpaceOrNewline, + tokenizer::{Span, Token}, +}; use crate::{ display_utils::{Indent, NewLine}, keywords::Keyword, @@ -631,7 +634,12 @@ pub struct CaseWhen { impl fmt::Display for CaseWhen { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "WHEN {} THEN {}", self.condition, self.result) + f.write_str("WHEN ")?; + self.condition.fmt(f)?; + f.write_str(" THEN")?; + SpaceOrNewline.fmt(f)?; + Indent(&self.result).fmt(f)?; + Ok(()) } } @@ -1671,17 +1679,23 @@ impl fmt::Display for Expr { conditions, else_result, } => { - write!(f, "CASE")?; + f.write_str("CASE")?; if let Some(operand) = operand { - write!(f, " {operand}")?; + f.write_str(" ")?; + operand.fmt(f)?; } for when in conditions { - write!(f, " {when}")?; + SpaceOrNewline.fmt(f)?; + Indent(when).fmt(f)?; } if let Some(else_result) = else_result { - write!(f, " ELSE {else_result}")?; + SpaceOrNewline.fmt(f)?; + Indent("ELSE").fmt(f)?; + SpaceOrNewline.fmt(f)?; + Indent(Indent(else_result)).fmt(f)?; } - write!(f, " END") + SpaceOrNewline.fmt(f)?; + f.write_str("END") } Expr::Exists { subquery, negated } => write!( f, diff --git a/tests/pretty_print.rs b/tests/pretty_print.rs index 712df0d8dd..afe0104ba4 100644 --- a/tests/pretty_print.rs +++ b/tests/pretty_print.rs @@ -112,3 +112,24 @@ FROM cte"# ); } + +#[test] +fn test_pretty_print_case_when() { + let sql = "SELECT CASE WHEN x > 0 THEN 'positive' WHEN x < 0 THEN 'negative' ELSE 'zero' END FROM my_table"; + let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); + let pretty = format!("{:#}", ast[0]); + assert_eq!( + pretty, + r#"SELECT + CASE + WHEN x > 0 THEN + 'positive' + WHEN x < 0 THEN + 'negative' + ELSE + 'zero' + END +FROM + my_table"# + ); +} From 5ef60c44ac6783e602c1a4c88dbfabc21bea85f7 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sat, 10 May 2025 14:59:40 +0200 Subject: [PATCH 6/8] pretty print window function calls these can get verbose --- src/ast/mod.rs | 38 +++++++++++++++++++++++++++----------- tests/pretty_print.rs | 19 +++++++++++++++++++ 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 854abc2759..79b0e0d529 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1673,7 +1673,7 @@ impl fmt::Display for Expr { write!(f, "{data_type}")?; write!(f, " {value}") } - Expr::Function(fun) => write!(f, "{fun}"), + Expr::Function(fun) => fun.fmt(f), Expr::Case { operand, conditions, @@ -1884,8 +1884,14 @@ pub enum WindowType { impl Display for WindowType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - WindowType::WindowSpec(spec) => write!(f, "({})", spec), - WindowType::NamedWindow(name) => write!(f, "{}", name), + WindowType::WindowSpec(spec) => { + f.write_str("(")?; + NewLine.fmt(f)?; + Indent(spec).fmt(f)?; + NewLine.fmt(f)?; + f.write_str(")") + } + WindowType::NamedWindow(name) => name.fmt(f), } } } @@ -1913,14 +1919,19 @@ pub struct WindowSpec { impl fmt::Display for WindowSpec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut delim = ""; + let mut is_first = true; if let Some(window_name) = &self.window_name { - delim = " "; + if !is_first { + SpaceOrNewline.fmt(f)?; + } + is_first = false; write!(f, "{window_name}")?; } if !self.partition_by.is_empty() { - f.write_str(delim)?; - delim = " "; + if !is_first { + SpaceOrNewline.fmt(f)?; + } + is_first = false; write!( f, "PARTITION BY {}", @@ -1928,12 +1939,16 @@ impl fmt::Display for WindowSpec { )?; } if !self.order_by.is_empty() { - f.write_str(delim)?; - delim = " "; + if !is_first { + SpaceOrNewline.fmt(f)?; + } + is_first = false; write!(f, "ORDER BY {}", display_comma_separated(&self.order_by))?; } if let Some(window_frame) = &self.window_frame { - f.write_str(delim)?; + if !is_first { + SpaceOrNewline.fmt(f)?; + } if let Some(end_bound) = &window_frame.end_bound { write!( f, @@ -7096,7 +7111,8 @@ impl fmt::Display for Function { } if let Some(o) = &self.over { - write!(f, " OVER {o}")?; + f.write_str(" OVER ")?; + o.fmt(f)?; } if self.uses_odbc_syntax { diff --git a/tests/pretty_print.rs b/tests/pretty_print.rs index afe0104ba4..3098a6aaee 100644 --- a/tests/pretty_print.rs +++ b/tests/pretty_print.rs @@ -133,3 +133,22 @@ FROM my_table"# ); } + +#[test] +fn test_pretty_print_window_function() { + let sql = "SELECT id, value, ROW_NUMBER() OVER (PARTITION BY category ORDER BY value DESC) as rank FROM my_table"; + let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); + let pretty = format!("{:#}", ast[0]); + assert_eq!( + pretty, + r#"SELECT + id, + value, + ROW_NUMBER() OVER ( + PARTITION BY category + ORDER BY value DESC + ) AS rank +FROM + my_table"# + ); +} From b03a05b7b2e280ba2803898ca49aa58311546ae2 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sat, 10 May 2025 15:04:56 +0200 Subject: [PATCH 7/8] more readable tests --- tests/pretty_print.rs | 99 ++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/tests/pretty_print.rs b/tests/pretty_print.rs index 3098a6aaee..63c2a08789 100644 --- a/tests/pretty_print.rs +++ b/tests/pretty_print.rs @@ -1,47 +1,50 @@ use sqlparser::dialect::GenericDialect; use sqlparser::parser::Parser; +fn parse_and_format(sql: &str) -> String { + let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); + format!("{:#}", ast[0]) +} + #[test] fn test_pretty_print_select() { - let sql = "SELECT a, b, c FROM my_table WHERE x = 1 AND y = 2"; - let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); - let pretty = format!("{:#}", ast[0]); assert_eq!( - pretty, - r#"SELECT + parse_and_format("SELECT a, b, c FROM my_table WHERE x = 1 AND y = 2"), + r#" +SELECT a, b, c FROM my_table WHERE - x = 1 AND y = 2"# + x = 1 AND y = 2 +"# + .trim() ); } #[test] fn test_pretty_print_join() { - let sql = "SELECT a FROM table1 JOIN table2 ON table1.id = table2.id"; - let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); - let pretty = format!("{:#}", ast[0]); assert_eq!( - pretty, - r#"SELECT + parse_and_format("SELECT a FROM table1 JOIN table2 ON table1.id = table2.id"), + r#" +SELECT a FROM table1 - JOIN table2 ON table1.id = table2.id"# + JOIN table2 ON table1.id = table2.id +"# + .trim() ); } #[test] fn test_pretty_print_subquery() { - let sql = "SELECT * FROM (SELECT a, b FROM my_table) AS subquery"; - let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); - let pretty = format!("{:#}", ast[0]); assert_eq!( - pretty, - r#"SELECT + parse_and_format("SELECT * FROM (SELECT a, b FROM my_table) AS subquery"), + r#" +SELECT * FROM ( @@ -50,18 +53,18 @@ FROM b FROM my_table - ) AS subquery"# + ) AS subquery +"# + .trim() ); } #[test] fn test_pretty_print_union() { - let sql = "SELECT a FROM table1 UNION SELECT b FROM table2"; - let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); - let pretty = format!("{:#}", ast[0]); assert_eq!( - pretty, - r#"SELECT + parse_and_format("SELECT a FROM table1 UNION SELECT b FROM table2"), + r#" +SELECT a FROM table1 @@ -69,18 +72,18 @@ UNION SELECT b FROM - table2"# + table2 +"# + .trim() ); } #[test] fn test_pretty_print_group_by() { - let sql = "SELECT a, COUNT(*) FROM my_table GROUP BY a HAVING COUNT(*) > 1"; - let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); - let pretty = format!("{:#}", ast[0]); assert_eq!( - pretty, - r#"SELECT + parse_and_format("SELECT a, COUNT(*) FROM my_table GROUP BY a HAVING COUNT(*) > 1"), + r#" +SELECT a, COUNT(*) FROM @@ -88,18 +91,18 @@ FROM GROUP BY a HAVING - COUNT(*) > 1"# + COUNT(*) > 1 +"# + .trim() ); } #[test] fn test_pretty_print_cte() { - let sql = "WITH cte AS (SELECT a, b FROM my_table) SELECT * FROM cte"; - let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); - let pretty = format!("{:#}", ast[0]); assert_eq!( - pretty, - r#"WITH cte AS ( + parse_and_format("WITH cte AS (SELECT a, b FROM my_table) SELECT * FROM cte"), + r#" +WITH cte AS ( SELECT a, b @@ -109,18 +112,18 @@ fn test_pretty_print_cte() { SELECT * FROM - cte"# + cte +"# + .trim() ); } #[test] fn test_pretty_print_case_when() { - let sql = "SELECT CASE WHEN x > 0 THEN 'positive' WHEN x < 0 THEN 'negative' ELSE 'zero' END FROM my_table"; - let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); - let pretty = format!("{:#}", ast[0]); assert_eq!( - pretty, - r#"SELECT + parse_and_format("SELECT CASE WHEN x > 0 THEN 'positive' WHEN x < 0 THEN 'negative' ELSE 'zero' END FROM my_table"), + r#" +SELECT CASE WHEN x > 0 THEN 'positive' @@ -130,18 +133,17 @@ fn test_pretty_print_case_when() { 'zero' END FROM - my_table"# + my_table +"#.trim() ); } #[test] fn test_pretty_print_window_function() { - let sql = "SELECT id, value, ROW_NUMBER() OVER (PARTITION BY category ORDER BY value DESC) as rank FROM my_table"; - let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); - let pretty = format!("{:#}", ast[0]); assert_eq!( - pretty, - r#"SELECT + parse_and_format("SELECT id, value, ROW_NUMBER() OVER (PARTITION BY category ORDER BY value DESC) as rank FROM my_table"), + r#" +SELECT id, value, ROW_NUMBER() OVER ( @@ -149,6 +151,7 @@ fn test_pretty_print_window_function() { ORDER BY value DESC ) AS rank FROM - my_table"# + my_table +"#.trim() ); } From 963873b37fd6041404008325065a37770bec4933 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sat, 10 May 2025 15:11:56 +0200 Subject: [PATCH 8/8] prettify --- tests/pretty_print.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/pretty_print.rs b/tests/pretty_print.rs index 63c2a08789..1eb8ca41ce 100644 --- a/tests/pretty_print.rs +++ b/tests/pretty_print.rs @@ -1,7 +1,7 @@ use sqlparser::dialect::GenericDialect; use sqlparser::parser::Parser; -fn parse_and_format(sql: &str) -> String { +fn prettify(sql: &str) -> String { let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); format!("{:#}", ast[0]) } @@ -9,7 +9,7 @@ fn parse_and_format(sql: &str) -> String { #[test] fn test_pretty_print_select() { assert_eq!( - parse_and_format("SELECT a, b, c FROM my_table WHERE x = 1 AND y = 2"), + prettify("SELECT a, b, c FROM my_table WHERE x = 1 AND y = 2"), r#" SELECT a, @@ -27,7 +27,7 @@ WHERE #[test] fn test_pretty_print_join() { assert_eq!( - parse_and_format("SELECT a FROM table1 JOIN table2 ON table1.id = table2.id"), + prettify("SELECT a FROM table1 JOIN table2 ON table1.id = table2.id"), r#" SELECT a @@ -42,7 +42,7 @@ FROM #[test] fn test_pretty_print_subquery() { assert_eq!( - parse_and_format("SELECT * FROM (SELECT a, b FROM my_table) AS subquery"), + prettify("SELECT * FROM (SELECT a, b FROM my_table) AS subquery"), r#" SELECT * @@ -62,7 +62,7 @@ FROM #[test] fn test_pretty_print_union() { assert_eq!( - parse_and_format("SELECT a FROM table1 UNION SELECT b FROM table2"), + prettify("SELECT a FROM table1 UNION SELECT b FROM table2"), r#" SELECT a @@ -81,7 +81,7 @@ FROM #[test] fn test_pretty_print_group_by() { assert_eq!( - parse_and_format("SELECT a, COUNT(*) FROM my_table GROUP BY a HAVING COUNT(*) > 1"), + prettify("SELECT a, COUNT(*) FROM my_table GROUP BY a HAVING COUNT(*) > 1"), r#" SELECT a, @@ -100,7 +100,7 @@ HAVING #[test] fn test_pretty_print_cte() { assert_eq!( - parse_and_format("WITH cte AS (SELECT a, b FROM my_table) SELECT * FROM cte"), + prettify("WITH cte AS (SELECT a, b FROM my_table) SELECT * FROM cte"), r#" WITH cte AS ( SELECT @@ -121,7 +121,7 @@ FROM #[test] fn test_pretty_print_case_when() { assert_eq!( - parse_and_format("SELECT CASE WHEN x > 0 THEN 'positive' WHEN x < 0 THEN 'negative' ELSE 'zero' END FROM my_table"), + prettify("SELECT CASE WHEN x > 0 THEN 'positive' WHEN x < 0 THEN 'negative' ELSE 'zero' END FROM my_table"), r#" SELECT CASE @@ -141,7 +141,7 @@ FROM #[test] fn test_pretty_print_window_function() { assert_eq!( - parse_and_format("SELECT id, value, ROW_NUMBER() OVER (PARTITION BY category ORDER BY value DESC) as rank FROM my_table"), + prettify("SELECT id, value, ROW_NUMBER() OVER (PARTITION BY category ORDER BY value DESC) as rank FROM my_table"), r#" SELECT id,