sql_insight/formatter.rs
1//! Basic SQL formatting — round-trips through sqlparser's AST
2//! and emits its `Display`. See [`format()`] as the entry point.
3//!
4//! Output is a pass-through to [`sqlparser::ast::Statement`]'s
5//! `Display` impl (keywords uppercase, single-space separators,
6//! comments dropped). Default is single-line; opt into sqlparser's
7//! multi-line pretty-print by setting [`FormatterOptions::pretty`]
8//! and using [`format_with_options`].
9//!
10//! For value-normalization (collapsing `1` and `42` into the same
11//! literal, etc.) see [`crate::normalizer`].
12
13use crate::error::Error;
14use sqlparser::dialect::Dialect;
15use sqlparser::parser::Parser;
16
17/// Parse `sql` under `dialect` and re-emit one formatted string per
18/// statement.
19///
20/// ## Example
21///
22/// ```rust
23/// use sql_insight::sqlparser::dialect::GenericDialect;
24///
25/// let dialect = GenericDialect {};
26/// let sql = "SELECT a FROM t1 \n WHERE b = 1";
27/// let result = sql_insight::formatter::format(&dialect, sql).unwrap();
28/// assert_eq!(result, ["SELECT a FROM t1 WHERE b = 1"]);
29/// ```
30pub fn format(dialect: &dyn Dialect, sql: &str) -> Result<Vec<String>, Error> {
31 Formatter::format(dialect, sql, FormatterOptions::default())
32}
33
34/// Parse `sql` under `dialect` and re-emit one formatted string per
35/// statement, with formatting controlled by `options`.
36///
37/// ## Example
38///
39/// ```rust
40/// use sql_insight::sqlparser::dialect::GenericDialect;
41/// use sql_insight::formatter::{format_with_options, FormatterOptions};
42///
43/// let dialect = GenericDialect {};
44/// let sql = "SELECT a, b FROM t1";
45/// let result = format_with_options(
46/// &dialect,
47/// sql,
48/// FormatterOptions::new().with_pretty(true),
49/// )
50/// .unwrap();
51/// assert_eq!(result[0], "SELECT\n a,\n b\nFROM\n t1");
52/// ```
53pub fn format_with_options(
54 dialect: &dyn Dialect,
55 sql: &str,
56 options: FormatterOptions,
57) -> Result<Vec<String>, Error> {
58 Formatter::format(dialect, sql, options)
59}
60
61/// Options controlling [`format_with_options()`] / [`Formatter::format`].
62#[derive(Debug, Clone, Default)]
63pub struct FormatterOptions {
64 /// When `true`, emit the multi-line pretty-printed form via
65 /// sqlparser's `{:#}` alternate `Display` (indented, one item
66 /// per line). Defaults to `false` (single-line).
67 pub pretty: bool,
68}
69
70impl FormatterOptions {
71 /// Default options (single-line output — `pretty` off).
72 pub fn new() -> Self {
73 Self::default()
74 }
75
76 /// Set [`pretty`](Self::pretty): `true` for the multi-line pretty-printed
77 /// form, `false` (the default) for single-line.
78 pub fn with_pretty(mut self, pretty: bool) -> Self {
79 self.pretty = pretty;
80 self
81 }
82}
83
84/// Struct-style entry point. Used by both [`format()`] and
85/// [`format_with_options()`].
86#[derive(Debug, Default)]
87pub struct Formatter;
88
89impl Formatter {
90 /// Parse `sql` under `dialect` and re-emit each statement,
91 /// formatted according to `options`. [`format()`] / [`format_with_options()`]
92 /// are thin free-function wrappers around this.
93 pub fn format(
94 dialect: &dyn Dialect,
95 sql: &str,
96 options: FormatterOptions,
97 ) -> Result<Vec<String>, Error> {
98 let statements = Parser::parse_sql(dialect, sql)?;
99 Ok(statements
100 .into_iter()
101 .map(|statement| {
102 if options.pretty {
103 format!("{statement:#}")
104 } else {
105 statement.to_string()
106 }
107 })
108 .collect::<Vec<String>>())
109 }
110}