Skip to main content

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}