mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-07 17:04:59 +00:00
implement pretty-printing with {:#}
(#1847)
This commit is contained in:
parent
052ad4a759
commit
6120bb59cc
5 changed files with 591 additions and 160 deletions
102
src/ast/mod.rs
102
src/ast/mod.rs
|
@ -40,8 +40,14 @@ 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::SpaceOrNewline,
|
||||
tokenizer::{Span, Token},
|
||||
};
|
||||
use crate::{
|
||||
display_utils::{Indent, NewLine},
|
||||
keywords::Keyword,
|
||||
};
|
||||
|
||||
pub use self::data_type::{
|
||||
ArrayElemTypeDef, BinaryLength, CharLengthUnits, CharacterLength, DataType, EnumMember,
|
||||
|
@ -134,9 +140,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(())
|
||||
}
|
||||
|
@ -628,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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1662,23 +1673,29 @@ 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,
|
||||
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,
|
||||
|
@ -1867,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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1896,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 {}",
|
||||
|
@ -1911,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,
|
||||
|
@ -4204,6 +4236,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)]
|
||||
|
@ -4219,7 +4273,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 +4356,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, "; "))
|
||||
|
@ -7056,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 {
|
||||
|
|
337
src/ast/query.rs
337
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 core::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(())
|
||||
}
|
||||
|
@ -1334,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<TableSample>),
|
||||
|
@ -1769,9 +1839,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 +1888,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 +2206,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 +2976,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, " "))?;
|
||||
}
|
||||
|
|
133
src/display_utils.rs
Normal file
133
src/display_utils.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
//! 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 {:#}.
|
||||
pub(crate) struct Indent<T>(pub T);
|
||||
|
||||
const INDENT: &str = " ";
|
||||
|
||||
impl<T> Display for Indent<T>
|
||||
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<T> Write for Indent<T>
|
||||
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<T: fmt::Display> fmt::Display for DisplayCommaSeparated<'_, 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<T: fmt::Display>(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: Display>(T);
|
||||
|
||||
impl<T: Display> Display for DisplayCharByChar<T> {
|
||||
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");
|
||||
}
|
||||
}
|
22
src/lib.rs
22
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
|
||||
|
@ -142,6 +163,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;
|
||||
|
|
157
tests/pretty_print.rs
Normal file
157
tests/pretty_print.rs
Normal file
|
@ -0,0 +1,157 @@
|
|||
use sqlparser::dialect::GenericDialect;
|
||||
use sqlparser::parser::Parser;
|
||||
|
||||
fn prettify(sql: &str) -> String {
|
||||
let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap();
|
||||
format!("{:#}", ast[0])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_select() {
|
||||
assert_eq!(
|
||||
prettify("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
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_join() {
|
||||
assert_eq!(
|
||||
prettify("SELECT a FROM table1 JOIN table2 ON table1.id = table2.id"),
|
||||
r#"
|
||||
SELECT
|
||||
a
|
||||
FROM
|
||||
table1
|
||||
JOIN table2 ON table1.id = table2.id
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_subquery() {
|
||||
assert_eq!(
|
||||
prettify("SELECT * FROM (SELECT a, b FROM my_table) AS subquery"),
|
||||
r#"
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
a,
|
||||
b
|
||||
FROM
|
||||
my_table
|
||||
) AS subquery
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_union() {
|
||||
assert_eq!(
|
||||
prettify("SELECT a FROM table1 UNION SELECT b FROM table2"),
|
||||
r#"
|
||||
SELECT
|
||||
a
|
||||
FROM
|
||||
table1
|
||||
UNION
|
||||
SELECT
|
||||
b
|
||||
FROM
|
||||
table2
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_group_by() {
|
||||
assert_eq!(
|
||||
prettify("SELECT a, COUNT(*) FROM my_table GROUP BY a HAVING COUNT(*) > 1"),
|
||||
r#"
|
||||
SELECT
|
||||
a,
|
||||
COUNT(*)
|
||||
FROM
|
||||
my_table
|
||||
GROUP BY
|
||||
a
|
||||
HAVING
|
||||
COUNT(*) > 1
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_cte() {
|
||||
assert_eq!(
|
||||
prettify("WITH cte AS (SELECT a, b FROM my_table) SELECT * FROM cte"),
|
||||
r#"
|
||||
WITH cte AS (
|
||||
SELECT
|
||||
a,
|
||||
b
|
||||
FROM
|
||||
my_table
|
||||
)
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
cte
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_case_when() {
|
||||
assert_eq!(
|
||||
prettify("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'
|
||||
WHEN x < 0 THEN
|
||||
'negative'
|
||||
ELSE
|
||||
'zero'
|
||||
END
|
||||
FROM
|
||||
my_table
|
||||
"#.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_window_function() {
|
||||
assert_eq!(
|
||||
prettify("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 (
|
||||
PARTITION BY category
|
||||
ORDER BY value DESC
|
||||
) AS rank
|
||||
FROM
|
||||
my_table
|
||||
"#.trim()
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue