### Quick start: format SQL with Style::River Source: https://docs.rs/libpgfmt/latest/libpgfmt Shows the basic usage of the format function with the River style. The example formats a simple SELECT statement and asserts the expected output. ```rust use libpgfmt::{format, style::Style}; let sql = "SELECT id, name FROM users WHERE active = TRUE"; let formatted = format(sql, Style::River).unwrap(); assert_eq!(formatted, "SELECT id,\n name\n FROM users\n WHERE active = TRUE;"); ``` -------------------------------- ### Format PL/pgSQL with format_plpgsql Source: https://docs.rs/libpgfmt/latest/libpgfmt Shows how to format PL/pgSQL function bodies using the format_plpgsql function. The example formats a simple BEGIN...END block with the River style and asserts the output. ```rust use libpgfmt::{format_plpgsql, style::Style}; let body = "BEGIN RETURN 1; END"; let formatted = format_plpgsql(body, Style::River).unwrap(); assert_eq!(formatted, "BEGIN\n RETURN 1;\nEND;"); ``` -------------------------------- ### format_plpgsql function signature and usage example Source: https://docs.rs/libpgfmt/latest/libpgfmt/fn.format_plpgsql.html The function signature shows the public API for formatting PL/pgSQL code. The example demonstrates formatting a simple function body with the River style, producing a formatted string with indentation. ```rust pub fn format_plpgsql(code: &str, style: Style) -> Result ``` ```rust use libpgfmt::{format_plpgsql, style::Style}; let body = "BEGIN RETURN 1; END"; let formatted = format_plpgsql(body, Style::River).unwrap(); assert_eq!(formatted, "BEGIN\n RETURN 1;\nEND;"); ``` -------------------------------- ### Parse style from string Source: https://docs.rs/libpgfmt/latest/libpgfmt Demonstrates parsing a style from a string using the FromStr implementation. The example parses "dbt" and asserts it equals Style::Dbt. ```rust use libpgfmt::style::Style; let style: Style = "dbt".parse().unwrap(); assert_eq!(style, Style::Dbt); ``` -------------------------------- ### Formatting SQL with River and Dbt styles Source: https://docs.rs/libpgfmt/latest/libpgfmt/fn.format.html Examples showing how to use the format function with different styles. The River style formats with specific indentation, while the Dbt style uses lowercase and blank lines. ```rust use libpgfmt::{format, style::Style}; // River style let result = format("SELECT id FROM users WHERE active = TRUE", Style::River).unwrap(); assert_eq!(result, "SELECT id\n FROM users\n WHERE active = TRUE;"); // dbt style (lowercase, blank lines) let result = format("SELECT id FROM users", Style::Dbt).unwrap(); assert_eq!(result, "select id\n\nfrom users;"); ``` -------------------------------- ### Initialize C expression formatting Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/expr.rs.html Starts formatting a C-style expression node, initializing state for tracking block subqueries and parenthesis depth. The snippet is truncated at the point where the match on child kind begins. ```rust fn format_c_expr(&self, node: Node<'a>) -> String { let mut parts = Vec::new(); let mut has_block_subquery = false; let mut paren_depth: u32 = 0; let mut paren_parts: Vec = Vec::new(); let mut cursor = node.walk(); for child in node.children(&mut cursor) { if child.is_named() { let formatted = match child.kind() { ``` -------------------------------- ### format_select_stmt and format_select_no_parens methods Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/select.rs.html Implements the entry points for formatting SELECT statements. format_select_stmt finds the select_no_parens child and delegates, while format_select_no_parens calls the min-width variant with zero width. ```rust 37impl<'a> Formatter<'a> { 38 /// Format a SelectStmt node. 39 pub(crate) fn format_select_stmt(&self, node: Node<'a>) -> String { 40 let snp = node.find_child("select_no_parens").unwrap_or(node); 41 self.format_select_no_parens(snp) 42 } 43 44 /// Format a select_no_parens node. 45 pub(crate) fn format_select_no_parens(&self, node: Node<'a>) -> String { 46 self.format_select_no_parens_with_min_width(node, 0) 47 } 48 49 fn format_select_no_parens_with_min_width( 50 &self, 51 node: Node<'a>, 52 min_river_width: usize, 53 ) -> String { 54 let clauses = self.collect_select_clauses(node); 55 if clauses.values_clause.is_some() { 56 return self.format_values_only(&clauses); 57 } 58 if self.config.river { 59 self.format_select_river_with_min_width(&clauses, min_river_width) 60 } else { 61 self.format_select_left_aligned(&clauses) 62 } 63 } ``` -------------------------------- ### parse_dollar_quoted: Parse dollar-quoted string into tag and body Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/stmt.rs.html Parses a dollar-quoted string into its tag and body. Returns None if the input does not start with '$' or if the closing tag is not found. The tag is the opening delimiter including the surrounding dollar signs, and the body is the content between the tags. ```rust fn parse_dollar_quoted(s: &str) -> Option<(&str, &str)> { if !s.starts_with('$') { return None; } // Find the end of the opening tag. let tag_end = s[1..].find('$')? + 2; // +1 for the inner offset, +1 for the closing $ let tag = &s[..tag_end]; let rest = &s[tag_end..]; // Find the closing tag. let body_end = rest.rfind(tag)?; let body = &rest[..body_end]; Some((tag, body)) } ``` -------------------------------- ### reindent_body: Re-indent multi-line body with given indent Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/stmt.rs.html Re-indents a multi-line body (e.g., a dollar-quoted function body) so that each non-empty line starts with the given indent prefix. Strips leading and trailing blank lines, removes the common leading whitespace from all lines, and returns an empty string if the body has no non-empty lines. ```rust fn reindent_body(s: &str, indent: &str) -> String { let lines: Vec<&str> = s.lines().collect(); // Skip leading/trailing empty lines. let start = lines.iter().position(|l| !l.trim().is_empty()).unwrap_or(0); let end = lines .iter() .rposition(|l| !l.trim().is_empty()) .map(|i| i + 1) .unwrap_or(lines.len()); let body_lines = &lines[start..end]; if body_lines.is_empty() { return String::new(); } // Determine the minimum leading whitespace across non-empty lines. let min_indent = body_lines .iter() .filter(|l| !l.trim().is_empty()) .map(|l| l.len() - l.trim_start().len()) .min() .unwrap_or(0); body_lines .iter() .map(|line| { if line.trim().is_empty() { String::new() } else { format!("{indent}{}", &line[min_indent..]) } }) .collect::>() .join("\n") } ``` -------------------------------- ### Style::ALL Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/style.rs.html A constant array containing all available Style variants. Use this to iterate over all supported styles. ```APIDOC ## Style::ALL ### Description A constant array containing all available `Style` variants. Use this to iterate over all supported styles. ### Signature ```rust pub const ALL: &[Style] ``` ### Usage Example ```rust for s in Style::ALL { println!("{s}"); } ``` ### Returns - `&[Style]` - A slice of all `Style` variants. ``` -------------------------------- ### Parse and list SQL formatting styles Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/style.rs.html Shows how to parse a style name into a Style enum and iterate over all available styles. The parse method is case-insensitive and returns an error for unsupported names. ```rust use libpgfmt::style::Style; let style: Style = "mozilla".parse().unwrap(); assert_eq!(style, Style::Mozilla); // List all available styles for s in Style::ALL { println!("{s}"); } ``` -------------------------------- ### Format RAISE option Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/plpgsql.rs.html Formats a single RAISE ... USING option as a KEYWORD = expression string. ```rust fn format_raise_option(&self, node: Node<'a>) -> String { let mut cursor = node.walk(); let mut opt_kw = String::new(); let mut opt_expr = String::new(); for c in node.named_children(&mut cursor) { if c.kind().starts_with("kw_") { opt_kw = self.kw(self.text(c).trim()); } else if c.kind() == "sql_expression" { opt_expr = self.text(c).trim().to_string(); } } format!("{opt_kw} = {opt_expr}") } ``` -------------------------------- ### Style::fmt (Display) Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/style.rs.html Formats a Style variant as its string representation. This is used for display and string conversion. ```APIDOC ## Style::fmt (Display) ### Description Formats a `Style` variant as its string representation. This is used for display and string conversion. ### Signature ```rust fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result ``` ### Parameters - `f` (Formatter) - Required - The formatter to write the style name to. ### Returns - `fmt::Result` - Ok if formatting succeeded. ### String Representations - `Style::River` -> `"river"` - `Style::Mozilla` -> `"mozilla"` - `Style::Aweber` -> `"aweber"` - `Style::Dbt` -> `"dbt"` - `Style::Gitlab` -> `"gitlab"` - `Style::Kickstarter` -> `"kickstarter"` - `Style::Mattmc3` -> `"mattmc3"` - `Style::PgDump` -> `"pg_dump"` ``` -------------------------------- ### Format PL/pgSQL blocks with tree-sitter in libpgfmt Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/plpgsql.rs.html Implements the PL/pgSQL block formatter for the libpgfmt crate. The main entry point `format_plpgsql_block` walks the tree-sitter node and emits DECLARE, BEGIN, EXCEPTION, and END keywords with proper indentation. Helper methods handle declaration sections, alias declarations, constants, collations, NOT NULL, default values, and procedural statements (IF, LOOP/WHILE, FOR). ```rust /// PL/pgSQL formatting. use crate::node_helpers::NodeExt; use tree_sitter::Node; use super::Formatter; impl<'a> Formatter<'a> { /// Format a PL/pgSQL block. pub(crate) fn format_plpgsql_block(&self, node: Node<'a>, indent_level: usize) -> String { let indent = " ".repeat(indent_level); let mut lines = Vec::new(); let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { match child.kind() { "decl_sect" => { lines.push(format!("{indent}{}", self.kw("DECLARE"))); self.format_decl_sect(child, indent_level + 1, &mut lines); } "kw_begin" => { lines.push(format!("{indent}{}", self.kw("BEGIN"))); } "proc_sect" => { self.format_proc_sect(child, indent_level + 1, &mut lines); } "exception_sect" => { lines.push(format!("{indent}{}", self.kw("EXCEPTION"))); self.format_exception_sect(child, indent_level + 1, &mut lines); } "kw_end" => { lines.push(format!("{indent}{}", self.kw("END"))); } _ => {} } } lines.join("\n") } fn format_decl_sect(&self, node: Node<'a>, indent_level: usize, lines: &mut Vec) { let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if child.kind() == "decl_stmt" { self.format_decl_stmt(child, indent_level, lines); } } } fn format_decl_stmt(&self, node: Node<'a>, indent_level: usize, lines: &mut Vec) { let indent = " ".repeat(indent_level); if let Some(decl) = node.find_child("decl_statement") { let var_name = decl .find_child("decl_varname") .map(|n| self.text(n).trim().to_string()) .unwrap_or_default(); // Alias declaration: name ALIAS FOR target ; if decl.has_child("kw_alias") { let target = decl .named_children(&mut decl.walk()) .last() .map(|n| self.text(n).trim().to_string()) .unwrap_or_default(); lines.push(format!( "{indent}{var_name} {} {} {target};", self.kw("ALIAS"), self.kw("FOR") )); return; } let mut parts = vec![var_name]; // Constant? if decl.has_child("kw_constant") { parts.push(self.kw("CONSTANT")); } // Data type. if let Some(dt) = decl.find_child("decl_datatype") { let type_text = self.text(dt).trim().to_string(); parts.push(type_text); } // Collation. if let Some(coll) = decl.find_child("decl_collate") { let coll_text = self.text(coll).trim().to_string(); parts.push(coll_text); } // NOT NULL. if decl.has_child("kw_not") { parts.push(format!("{} {}", self.kw("NOT"), self.kw("NULL"))); } // Default value. if let Some(defval) = decl.find_child("decl_defval") { let def_text = self.text(defval).trim().to_string(); parts.push(def_text); } lines.push(format!("{indent}{};", parts.join(" "))); } } fn format_proc_sect(&self, node: Node<'a>, indent_level: usize, lines: &mut Vec) { let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if child.kind() == "proc_stmt" { self.format_proc_stmt(child, indent_level, lines); } } } fn format_proc_stmt(&self, node: Node<'a>, indent_level: usize, lines: &mut Vec) { let indent = " ".repeat(indent_level); let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { match child.kind() { "stmt_if" => self.format_stmt_if(child, indent_level, lines), "stmt_loop" | "stmt_while" => self.format_stmt_loop(child, indent_level, lines), "stmt_for" => self.format_stmt_for(child, indent_level, lines), _ => {} } } } } ``` -------------------------------- ### Build Ordered Table Elements Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/stmt.rs.html Constructs an ordered list of table elements, processing primary keys, columns, and constraints sequentially. ```rust // Build ordered list of (rendered item, trailing comments): // PKs first, then columns, then constraints. let mut all_items: Vec<(String, Vec)> = Vec::new(); for (pk, comments) in &pk_elements { all_items.push((pk.clone(), comments.clone())); } for (name, typename, constraints, comments) in &col_elements { all_items.push(( render_aligned_column( name, typename, constraints, max_name_len, max_type_len, ), comments.clone(), )); } // Table constraints: CONSTRAINT name on one line, // CHECK(...) on the next, both aligned with the type column. for (name, body, comments) in &constraint_elements { let constraint_padding = " ".repeat(max_name_len + 1); if let Some(cname) = name { all_items.push(( format!( "{constraint_padding}{} {cname}\n{constraint_padding}{body}", self.kw("CONSTRAINT") ), comments.clone(), )); } else { all_items.push((format!("{constraint_padding}{body}"), comments.clone())); } } ``` -------------------------------- ### Format FETCH FIRST clause Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/select.rs.html Formats a FETCH FIRST n ROWS ONLY clause, returning the text after the leading FETCH keyword (e.g., 'FIRST 5 ROWS ONLY'). It iterates over named children, skipping the kw_fetch keyword and handling first_or_next nodes. ```rust fn format_fetch_first(&self, node: Node<'a>) -> String { let mut parts = Vec::new(); for child in node.named_children_vec() { match child.kind() { "kw_fetch" => {} // rendered as the leading keyword "first_or_next" => { if child.has_child("kw_next") { ``` -------------------------------- ### Format SELECT target list with multi-line handling Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/select.rs.html Formats each target in a SELECT list, indenting continuation lines for multi-line targets and adding a comma suffix except for the last target. ```rust let formatted = self.format_target_el(*target); let padding = " ".repeat(content_col); let suffix = if i < targets.len() - 2 { "," } else { "" }; if formatted.contains('\n') { // Multi-line target: indent continuation lines. let mut target_lines = formatted.lines(); let first_target_line = target_lines.next().unwrap_or(""); lines.push(format!("{padding}{first_target_line}")); for line in target_lines { lines.push(format!("{padding}{line}")); } // Add suffix to last line. if !suffix.is_empty() && let Some(last) = lines.last_mut() { last.push_str(suffix); } } else { lines.push(format!("{padding}{formatted}{suffix}")); } ``` -------------------------------- ### Format PL/pgSQL statements with match arms Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/plpgsql.rs.html Handles formatting for various PL/pgSQL statement types. For RETURN, it builds the statement with optional NEXT/QUERY/EXECUTE keywords. For NULL, it outputs 'NULL;'. For pass-through statements (assign, execsql, perform, call, etc.), it outputs the raw text. Nested blocks are formatted recursively. Unknown statement types are output as raw text if non-empty. ```rust 123 "stmt_foreach_a" => self.format_stmt_foreach(child, indent_level, lines), 124 "stmt_case" => self.format_stmt_case(child, indent_level, lines), 125 "stmt_return" => { 126 let mut parts = vec![self.kw("RETURN")]; 127 // RETURN NEXT [expr] / RETURN QUERY [EXECUTE] ... 128 if child.has_child("kw_next") { 129 parts.push(self.kw("NEXT")); 130 } else if child.has_child("kw_query") { 131 parts.push(self.kw("QUERY")); 132 if child.has_child("kw_execute") { 133 parts.push(self.kw("EXECUTE")); 134 } 135 } 136 let expr = child 137 .find_child("sql_expression") 138 .map(|n| self.text(n).trim()) 139 .unwrap_or(""); 140 if !expr.is_empty() { 141 parts.push(expr.to_string()); 142 } 143 lines.push(format!("{indent}{};", parts.join(" "))); 144 } 145 "stmt_raise" => self.format_stmt_raise(child, indent_level, lines), 146 "stmt_null" => { 147 lines.push(format!("{indent}{};", self.kw("NULL"))); 148 } 149 // Statements whose source text is passed through with indentation. 150 "stmt_assign" | "stmt_execsql" | "stmt_perform" | "stmt_call" 151 | "stmt_dynexecute" | "stmt_exit" | "stmt_continue" | "stmt_open" 152 | "stmt_close" | "stmt_fetch" | "stmt_move" | "stmt_commit" | "stmt_rollback" 153 | "stmt_assert" => { 154 let text = self.text(child).trim(); lines.push(format!("{indent}{text}")); 155 } 156 "pl_block" => { 157 let block_text = self.format_plpgsql_block(child, indent_level); 158 lines.push(block_text); 159 } 160 _ => { 161 let text = self.text(child).trim(); 162 if !text.is_empty() { 163 lines.push(format!("{indent}{text}")); 164 } 165 } 166 } 167 } 168 } 169 170 fn format_stmt_if(&self, node: Node<'a>, indent_level: usize, lines: &mut Vec) { 171 let indent = " ".repeat(indent_level); 172 let inner_indent_level = indent_level + 1; 173 174 let mut cursor = node.walk(); 175 let children: Vec<_> = node.named_children(&mut cursor).collect(); 176 let mut i = 0; while i < children.len() { let child = children[i]; match child.kind() { "kw_if" => { if i == 0 { // Opening IF. let cond = children .get(i + 1) .filter(|c| c.kind() == "sql_expression") .map(|c| self.text(*c).trim()) .unwrap_or(""); lines.push(format!( "{indent}{} {cond} {}", self.kw("IF"), self.kw("THEN") )); i += 3; // skip cond and THEN } else { i += 1; // closing IF (END IF) } } "sql_expression" => { i += 1; // handled with IF/ELSIF } "kw_then" => { i += 1; // handled with IF/ELSIF } "proc_sect" => { self.format_proc_sect(child, inner_indent_level, lines); i += 1; } "elsif_clause" => { self.format_elsif_clause(child, indent_level, lines); i += 1; } "else_clause" => { lines.push(format!("{indent}{}", self.kw("ELSE"))); if let Some(proc) = child.find_child("proc_sect") { self.format_proc_sect(proc, inner_indent_level, lines); } i += 1; } "kw_end" => { i += 1; // END IF handled below } _ => { i += 1; } } } lines.push(format!("{indent}{} {};", self.kw("END"), self.kw("IF"))); } ``` -------------------------------- ### Format interval qualifier and parentheses in expression Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/expr.rs.html Handles formatting of interval qualifiers and parentheses in expression nodes. The first two lines are part of a match arm that delegates to format_opt_interval or format_expr. The third block is a match on trimmed text to output parentheses. ```rust result.push_str(&self.format_opt_interval(child)); ``` ```rust _ => result.push_str(&self.format_expr(child)), ``` ```rust match self.text(child).trim() { "(" => result.push('('), ")" => result.push(')'), _ => {} } ``` -------------------------------- ### Format interval qualifier with keyword casing Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/expr.rs.html Formats an interval qualifier (e.g., DAY or HOUR TO MINUTE) by joining its named children with spaces. If no named children exist, returns the trimmed original text. ```rust fn format_opt_interval(&self, node: Node<'a>) -> String { let mut cursor = node.walk(); let parts: Vec = node .named_children(&mut cursor) .map(|child| self.format_expr(child)) .collect(); if parts.is_empty() { self.text(node).trim().to_string() } else { parts.join(" ") } } ``` -------------------------------- ### format_when_clause method Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/expr.rs.html Formats individual WHEN clauses by processing WHEN/THEN keywords and their associated expressions. ```rust fn format_when_clause(&self, node: Node<'a>) -> String { let mut parts = Vec::new(); let named = node.named_children_vec(); for child in &named { match child.kind() { "kw_when" => parts.push(self.kw("WHEN")), "kw_then" => parts.push(self.kw("THEN")), "a_expr" | "c_expr" | "a_expr_prec" => { parts.push(self.format_expr(*child)); } _ => {} } } parts.join(" ") } ``` -------------------------------- ### Style enum with Display and FromStr implementations Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/style.rs.html Defines the Style enum with eight variants, each implementing a different SQL layout strategy. The default is Aweber. Display outputs lowercase names, and FromStr accepts case-insensitive names with aliases for PgDump. ```rust use std::fmt; use std::str::FromStr; /// SQL formatting style. /// /// Each variant implements a different layout strategy for SQL statements. /// Use [`Style::default()`] for the [AWeber style](https://gist.github.com/gmr/2cceb85bb37be96bc96f05c5b8de9e1b), or parse from a string: /// /// ``` /// use libpgfmt::style::Style; /// /// let style: Style = "mozilla".parse().unwrap(); /// assert_eq!(style, Style::Mozilla); /// /// // List all available styles /// for s in Style::ALL { /// println!("{s}"); /// } /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Style { /// Simon Holywell's river style — keywords right-aligned to form a visual river. River, /// Mozilla style — keywords left-aligned, content indented 4 spaces. Mozilla, /// AWeber style — river style with JOINs participating in keyword alignment. #[default] Aweber, /// dbt style — Mozilla-like with lowercase keywords and blank lines between clauses. Dbt, /// GitLab style — Mozilla-like with 2-space indent and uppercase keywords. Gitlab, /// Kickstarter style — Mozilla-like with 2-space indent and compact JOINs. Kickstarter, /// mattmc3 style — lowercase river with leading commas. Mattmc3, /// PostgreSQL "internal" style — mimics the `ruleutils.c` deparser output /// emitted by `pg_get_viewdef`/`pg_get_functiondef` (i.e. `pg_dump`). The /// correctness bar is byte-identical round-tripping of genuine deparser /// output. PgDump, } impl Style { /// All available styles. pub const ALL: &[Style] = &[ Style::River, Style::Mozilla, Style::Aweber, Style::Dbt, Style::Gitlab, Style::Kickstarter, Style::Mattmc3, Style::PgDump, ]; } impl fmt::Display for Style { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Style::River => write!(f, "river"), Style::Mozilla => write!(f, "mozilla"), Style::Aweber => write!(f, "aweber"), Style::Dbt => write!(f, "dbt"), Style::Gitlab => write!(f, "gitlab"), Style::Kickstarter => write!(f, "kickstarter"), Style::Mattmc3 => write!(f, "mattmc3"), Style::PgDump => write!(f, "pg_dump"), } } } impl FromStr for Style { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "river" => Ok(Style::River), "mozilla" => Ok(Style::Mozilla), "aweber" => Ok(Style::Aweber), "dbt" => Ok(Style::Dbt), "gitlab" => Ok(Style::Gitlab), "kickstarter" => Ok(Style::Kickstarter), "mattmc3" => Ok(Style::Mattmc3), "pg_dump" | "pgdump" | "postgres" => Ok(Style::PgDump), _ => Err(format!("Unsupported style: '{s}'")), } } } ``` -------------------------------- ### format_case_expr method Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/expr.rs.html Formats SQL CASE expressions, supporting optional arguments, multiple WHEN clauses, and ELSE clauses. It includes logic to wrap the output into multiple lines based on configuration or length constraints. ```rust fn format_case_expr(&self, node: Node<'a>) -> String { let case_kw = self.kw("CASE"); let end_kw = self.kw("END"); let mut case_arg: Option = None; let mut when_clauses: Vec = Vec::new(); let mut else_parts: Option = None; let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { match child.kind() { "kw_case" | "kw_end" => {} "case_arg" => { if let Some(expr) = child.find_child_any(&["a_expr", "c_expr"]) { case_arg = Some(self.format_expr(expr)); } } "when_clause_list" => { let clauses = flatten_list(child, "when_clause_list"); for clause in clauses { when_clauses.push(self.format_when_clause(clause)); } } "case_default" => { if let Some(expr) = child.find_child_any(&["a_expr", "c_expr", "a_expr_prec"]) { else_parts = Some(format!("{} {}", self.kw("ELSE"), self.format_expr(expr))); } } _ => {} } } // Build the single-line version first. let mut inline_parts = vec![case_kw.clone()]; if let Some(ref arg) = case_arg { inline_parts.push(arg.clone()); } for wc in &when_clauses { inline_parts.push(wc.clone()); } if let Some(ref ep) = else_parts { inline_parts.push(ep.clone()); } inline_parts.push(end_kw.clone()); let single_line = inline_parts.join(" "); // Wrap CASE when the style wraps CASE+ELSE and there's an ELSE clause, // or when the single-line version is excessively long. let should_wrap = if self.config.wrap_case_else && else_parts.is_some() { true } else { single_line.len() > 120 }; if !should_wrap { return single_line; } // Multi-line: align WHEN/ELSE under the first WHEN, END indented 1 space. // First line: "CASE [arg] WHEN ..." // Continuation: " WHEN ..." (indent = len("CASE ") + len(arg + " ") if present) let prefix = match &case_arg { Some(arg) => format!("{case_kw} {arg} "), None => format!("{case_kw} "), }; let when_indent = " ".repeat(prefix.len()); let end_indent = " "; let mut lines = Vec::new(); for (i, wc) in when_clauses.iter().enumerate() { if i == 0 { lines.push(format!("{prefix}{wc}")); } else { lines.push(format!("{when_indent}{wc}")); } } if let Some(ep) = &else_parts { lines.push(format!("{when_indent}{ep}")); } lines.push(format!("{end_indent}{end_kw}")); lines.join("\n") } ``` -------------------------------- ### Style::from_str Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/style.rs.html Parses a string into a Style variant. The input is case-insensitive and accepts aliases for PgDump ("pg_dump", "pgdump", "postgres"). Returns an error string for unsupported styles. ```APIDOC ## Style::from_str ### Description Parses a string into a `Style` variant. The input is case-insensitive and accepts aliases for `PgDump` ("pg_dump", "pgdump", "postgres"). Returns an error string for unsupported styles. ### Signature ```rust fn from_str(s: &str) -> Result ``` ### Parameters - `s` (string) - Required - The style name to parse. ### Returns - `Result` - `Ok(Style)` if the string matches a known style, otherwise `Err` with an error message. ### Supported Values - `"river"` -> `Style::River` - `"mozilla"` -> `Style::Mozilla` - `"aweber"` -> `Style::Aweber` - `"dbt"` -> `Style::Dbt` - `"gitlab"` -> `Style::Gitlab` - `"kickstarter"` -> `Style::Kickstarter` - `"mattmc3"` -> `Style::Mattmc3` - `"pg_dump"`, `"pgdump"`, `"postgres"` -> `Style::PgDump` ### Example ```rust let style: Style = "mozilla".parse().unwrap(); assert_eq!(style, Style::Mozilla); ``` ``` -------------------------------- ### Format FOREACH Statement Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/plpgsql.rs.html Formats the FOREACH statement by retrieving the raw text of the node. ```rust fn format_stmt_foreach(&self, node: Node<'a>, indent_level: usize, lines: &mut Vec) { let indent = " ".repeat(indent_level); // FOREACH ... SLICE ... IN ARRAY ... LOOP ... END LOOP let text = self.text(node); lines.push(format!("{indent}{text}")); } ``` -------------------------------- ### Format CASE Statement Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/plpgsql.rs.html Formats CASE statements by iterating through WHEN clauses and their associated procedure sections. ```rust fn format_stmt_case(&self, node: Node<'a>, indent_level: usize, lines: &mut Vec) { let indent = " ".repeat(indent_level); let inner_indent_level = indent_level + 1; let inner_indent = " ".repeat(inner_indent_level); let expr = node .find_child("sql_expression") .map(|n| self.text(n).trim()) .unwrap_or(""); lines.push(format!("{indent}{} {expr}", self.kw("CASE"))); let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if child.kind() == "case_when" { let when_expr = child .find_child("sql_expression") .map(|n| self.text(n).trim()) .unwrap_or(""); lines.push(format!( "{inner_indent}{} {when_expr} {}", self.kw("WHEN"), self.kw("THEN") )); if let Some(proc) = child.find_child("proc_sect") { ``` -------------------------------- ### Statement formatting dispatch and INSERT formatting in stmt.rs Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/stmt.rs.html Rust source for statement-level formatting in libpgfmt. Defines TableElementKind enum and implements format_stmt and format_insert_stmt methods on Formatter. Handles semicolon placement after line comments and formats INSERT statements with column lists, OVERRIDING clauses, and VALUES/SELECT. ```rust 1/// Statement-level formatting: dispatches to specific statement formatters. 2use crate::error::FormatError; 3use crate::node_helpers::{NodeExt, flatten_list}; 4use tree_sitter::Node; 5 6use super::Formatter; 7 8/// Classification of table elements for river-style CREATE TABLE. 9enum TableElementKind { 10 /// PRIMARY KEY constraint (should be first). 11 PrimaryKey(String), 12 /// Column definition: (name, typename, constraints_text). 13 Column(String, String, String), 14 /// Table constraint: (optional_name, body). 15 Constraint(Option, String), 16} 17 18impl<'a> Formatter<'a> { 19 /// Format a `stmt` node, dispatching based on the statement type. 20 pub(crate) fn format_stmt(&self, node: Node<'a>) -> Result { 21 let mut cursor = node.walk(); 22 if let Some(child) = node.named_children(&mut cursor).next() { 23 let result = match child.kind() { 24 "SelectStmt" => self.format_select_stmt(child), 25 "InsertStmt" => self.format_insert_stmt(child), 26 "UpdateStmt" => self.format_update_stmt(child), 27 "DeleteStmt" => self.format_delete_stmt(child), 28 "CreateStmt" => self.format_create_table_stmt(child), 29 "ViewStmt" => self.format_view_stmt(child), 30 "CreateFunctionStmt" => self.format_create_function_stmt(child), 31 "CreateDomainStmt" => self.format_create_domain_stmt(child), 32 "CreateForeignTableStmt" => self.format_create_foreign_table_stmt(child), 33 "CreateTableAsStmt" | "CreateMatViewStmt" => { 34 self.format_create_table_as_stmt(child) 35 } 36 "CreatePropGraphStmt" => self.format_create_prop_graph_stmt(child), 37 "AlterPropGraphStmt" => self.format_alter_prop_graph_stmt(child), 38 _ => { 39 let text = self.text(child); 40 normalize_whitespace(text) 41 } 42 }; 43 let trimmed = result.trim_end_matches(';'); 44 // If the last line contains a line comment (--), appending ; 45 // directly would put the semicolon inside the comment. 46 let needs_newline = trimmed 47 .lines() 48 .last() 49 .map(|line| line.contains("--")) 50 .unwrap_or(false); 51 return if needs_newline { 52 Ok(format!("{trimmed}\n;")) 53 } else { 54 Ok(format!("{trimmed};")) }; } Ok(String::new()) } // ── INSERT ────────────────────────────────────────────────────────── pub(crate) fn format_insert_stmt(&self, node: Node<'a>) -> String { let mut parts = Vec::new(); // INSERT INTO target. let target = node .find_child("insert_target") .map(|n| self.format_qualified_name_from(n)) .unwrap_or_default(); parts.push(format!( "{} {} {target}", self.kw("INSERT"), self.kw("INTO") )); // Column list. let insert_rest = node.find_child("insert_rest"); if let Some(rest) = insert_rest { if let Some(col_list) = rest.find_child("insert_column_list") { let cols = flatten_list(col_list, "insert_column_list"); let formatted: Vec<_> = cols.iter().map(|c| self.format_expr(*c)).collect(); parts[0] = format!("{} ({})", parts[0], formatted.join(", ")); } // OVERRIDING {SYSTEM|USER} VALUE (before VALUES/SELECT). if rest.has_child("kw_overriding") { let kind = rest .find_child("override_kind") .map(|ok| { if ok.has_child("kw_system") { self.kw("SYSTEM") } else { self.kw("USER") } }) .unwrap_or_default(); parts[0] = format!( "{} {} {kind} {}", parts[0], self.kw("OVERRIDING"), self.kw("VALUE") ); } // VALUES or SELECT. if let Some(select) = rest.find_child("SelectStmt") { let formatted = self.format_select_stmt(select); // Check if it's VALUES or a sub-SELECT. let select_text = formatted.trim_end_matches(';'); let values_kw = self.kw("VALUES"); ``` -------------------------------- ### format Source: https://docs.rs/libpgfmt/latest/libpgfmt/index.html Formats one or more PostgreSQL SQL statements according to the specified style. Returns a Result containing the formatted SQL string. ```APIDOC ## format ### Description Formats one or more PostgreSQL SQL statements according to the specified style. ### Function Signature `pub fn format(sql: &str, style: Style) -> Result` ### Parameters - **sql** (`&str`) - Required - The SQL statements to format. - **style** (`Style`) - Required - The formatting style to apply. ### Returns - `Result` - The formatted SQL string on success, or an error. ### Example ```rust use libpgfmt::{format, style::Style}; let sql = "SELECT id, name FROM users WHERE active = TRUE"; let formatted = format(sql, Style::River).unwrap(); assert_eq!(formatted, "SELECT id,\n name\n FROM users\n WHERE active = TRUE;"); ``` ``` -------------------------------- ### Format FROM clause with comma-separated tables Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/select.rs.html Formats a FROM list, handling plain tables and tables with JOINs, adding commas between items. ```rust fn format_from_river(&self, from_node: Node<'a>, width: usize, lines: &mut Vec) { if let Some(from_list) = from_node.find_child("from_list") { let tables = flatten_list(from_list, "from_list"); if tables.is_empty() { return; } for (i, table) in tables.iter().enumerate() { let is_last = i == tables.len() - 1; if table.kind() == "table_ref" && table.has_child("joined_table") { // Table with JOINs. let jt = table.find_child("joined_table").unwrap(); self.format_joined_table_river(jt, width, i == 0, lines); } else { let text = self.format_table_ref(*table); if i == 0 { lines.push(self.river_line(&self.kw("FROM"), &text, width)); } else { let content_col = width + 1; let padding = " ".repeat(content_col); lines.push(format!("{padding}{text}")); } } // A comma separates each FROM item from the next, whether the // items are plain tables or JOIN groups. if !is_last && let Some(last) = lines.last_mut() { last.push(','); } } } } ``` -------------------------------- ### Render Items with Trailing Commas Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/stmt.rs.html Iterates through prepared items to render them into physical lines, appending commas where necessary. ```rust // Render each item to its physical line(s), with the trailing // comma on the last line (constraints span two lines). let mut rendered: Vec<(Vec, Vec)> = Vec::new(); let total = all_items.len(); for (i, (item, comments)) in all_items.iter().enumerate() { let comma = if i < total - 1 { "," } else { "" }; let mut phys: Vec = Vec::new(); if item.contains('\n') { let item_lines: Vec<&str> = item.lines().collect(); for (j, line) in item_lines.iter().enumerate() { if j == item_lines.len() - 1 { phys.push(format!("{indent}{line}{comma}")); } else { phys.push(format!("{indent}{line}")); } } } else { phys.push(format!("{indent}{item}{comma}")); } rendered.push((phys, comments.clone())); } ``` -------------------------------- ### Format DELETE statement Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/stmt.rs.html Handles the formatting of DELETE statements, including optional USING, WHERE, and RETURNING clauses with support for river-aligned output. ```rust pub(crate) fn format_delete_stmt(&self, node: Node<'a>) -> String { let table = self.format_update_delete_target(node); let mut lines = Vec::new(); if self.config.river { let delete_kw = self.kw("DELETE"); let mut keywords = vec![delete_kw.clone(), self.kw("FROM")]; if node.has_child("using_clause") { keywords.push(self.kw("USING")); } if node.has_child("where_or_current_clause") { keywords.push(self.kw("WHERE")); } if node.has_child("returning_clause") { keywords.push(self.kw("RETURNING")); } let width = keywords.iter().map(|k| k.len()).max().unwrap_or(6); lines.push(delete_kw); lines.push(self.river_line(&self.kw("FROM"), &table, width)); // USING clause. ``` -------------------------------- ### Format relation list with river alignment Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/stmt.rs.html Formats a list of relations (e.g., for UPDATE ... FROM or DELETE ... USING) by aligning them to a specific column width. ```rust fn format_relation_list_river( &self, from_list: Node<'a>, keyword: &str, width: usize, lines: &mut Vec, ) { let tables = flatten_list(from_list, "from_list"); let last = tables.len().saturating_sub(1); for (i, table) in tables.iter().enumerate() { let mut text = self.format_table_ref(*table); if i < last { text.push(','); } if i == 0 { lines.push(self.river_line(keyword, &text, width)); } else { let padding = " ".repeat(width + 1); lines.push(format!("{padding}{text}")); } } } ``` -------------------------------- ### Format ORDER BY clause river-style Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/select.rs.html Formats an ORDER BY clause in river style. It finds the sortby_list child, flattens it, formats each sortby item, and pushes a single river line with the "ORDER BY" keyword and the joined items. ```rust fn format_order_by_river(&self, node: Node<'a>, width: usize, lines: &mut Vec) { let kw = self.kw_pair("ORDER", "BY"); if let Some(list) = node.find_child("sortby_list") { let items = flatten_list(list, "sortby_list"); let formatted: Vec<_> = items.iter().map(|i| self.format_sortby(*i)).collect(); lines.push(self.river_line(&kw, &formatted.join(", "), width)); } } ``` -------------------------------- ### Format Column Reference Source: https://docs.rs/libpgfmt/latest/src/libpgfmt/formatter/expr.rs.html Formats a column reference node by iterating through its children and dispatching to specific formatters. ```rust fn format_columnref(&self, node: Node<'a>) -> String { let mut parts = Vec::new(); let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { match child.kind() { "ColId" => parts.push(self.format_col_id(child)), "indirection" => parts.push(self.format_indirection(child)), _ => parts.push(self.format_expr(child)), } } parts.join("") } ```