SQL formatting
SQL pretty-printed with indents and keywords in capitals, placeholders filled in for logs, and squeezed back onto one line.
Version 1.0.0 · 3 functions · 1 crate
Functions
What use sql_format.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use sql_pretty, sql_pretty_with_params from sql_format (Quillo 0.6).
sql_pretty(sql: str, indent: int, uppercase: bool) → stror an errorThe SQL pretty-printed: each clause on its own line, indent spaces per level (0 for tabs), keywords in capitals when uppercase. Several statements are kept apart by a blank line. It only lays out the text, so it also works on SQL that a database would refuse.
sql_pretty_with_params(sql: str, params: any) → stror an errorThe SQL pretty-printed with its ? (or $1, :name given as a map) placeholders filled in from params, for logs and debugging: text is quoted with ' doubled, numbers and booleans as they are, null as NULL. Never run the result: send the params to the database instead.
sql_one_line(sql: str) → stror an errorThe SQL on one line: comments removed and runs of spaces and line breaks made one space, leaving quoted text ('...', "...", ...) untouched. For logs and cache keys.
Get it
$ quillo install sql_format # sql_format.qwer into this folder$ quillo install sql_format --example # and its example, sql_format.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/sql_format.qwer$ curl -O https://quillolang.org/plugins/files/sql_format.qweThen, in your program, all of it or only what you call:
use sql_format.qweruse sql_pretty, sql_pretty_with_params from sql_format // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
sqlformat = "0.2"Example
// A slow-query log made readable: each query pretty-printed, with its parameters filled in.//// quillo run sql_format.qwe then open http://localhost:8080/slowuse "sql_format.qwer"let slow = [ { ms: 840, sql: "select o.id, c.name, sum(l.qty * l.price) as total from orders o join customers c on c.id = o.customer_id join lines l on l.order_id = o.id where o.created_at > ? and c.country = ? group by o.id, c.name having sum(l.qty * l.price) > 100 order by total desc limit 20", params: ["2026-01-01", "NL"] }, { ms: 312, sql: "update stock set qty = qty - 1 where sku = :sku and qty > 0", params: { sku: "B'7" } },]route GET /slow = respond(200, join(slow | map((q) => `-- ${q.ms} ms\n${sql_pretty_with_params(q.sql, q.params)?}\n`), "\n"), { "content-type": "text/plain; charset=utf-8" })route GET /pretty ? { sql: str = "SELECT a, b FROM t WHERE x = 1 AND (y = 2 OR z = 3)" } = respond(200, sql_pretty(sql, 4, true)?, { "content-type": "text/plain; charset=utf-8" })route GET /one-line = { sql: sql_one_line("SELECT id, name -- the visible ones\n FROM users\n /* soft-deleted are hidden */ WHERE note = 'two spaces'\n AND ( deleted IS NULL )")? }$ quillo run sql_format.qwe$ curl 'localhost:8080/slow'$ curl 'localhost:8080/pretty'$ curl 'localhost:8080/one-line'Source
//! SQL made readable: queries pretty-printed with indents and line breaks, keywords upper-cased, placeholders filled in for logs, and squeezed back onto one line.//! version: 1.0.0//!//! [dependencies]//! sqlformat = "0.2"use serde_json::Value;const MAX_SQL: usize = 1 << 20;fn opts(indent: i64, uppercase: bool) -> sqlformat::FormatOptions { sqlformat::FormatOptions { indent: if indent <= 0 { sqlformat::Indent::Tabs } else { sqlformat::Indent::Spaces(indent.min(8) as u8) }, uppercase, lines_between_queries: 2, }}fn size(sql: &str) -> Result<(), String> { if sql.len() > MAX_SQL { return Err("SQL is at most 1 MB here".into()); } Ok(())}/// The SQL pretty-printed: each clause on its own line, `indent` spaces per level (0 for tabs), keywords in capitals when `uppercase`. Several statements are kept apart by a blank line./// It only lays out the text, so it also works on SQL that a database would refuse.pub fn sql_pretty(sql: &str, indent: i64, uppercase: bool) -> Result<String, String> { size(sql)?; Ok(sqlformat::format(sql, &sqlformat::QueryParams::None, opts(indent, uppercase)))}/// The SQL pretty-printed with its `?` (or `$1`, `:name` given as a map) placeholders filled in from `params`, for logs and debugging:/// text is quoted with `'` doubled, numbers and booleans as they are, null as NULL. Never run the result: send the params to the database instead.pub fn sql_pretty_with_params(sql: &str, params: serde_json::Value) -> Result<String, String> { size(sql)?; let lit = |v: &Value| match v { Value::Null => "NULL".to_string(), Value::String(s) => format!("'{}'", s.replace('\'', "''")), Value::Bool(b) => if *b { "TRUE".into() } else { "FALSE".into() }, Value::Number(n) => n.to_string(), other => format!("'{}'", other.to_string().replace('\'', "''")), }; let p = match ¶ms { Value::Array(xs) => sqlformat::QueryParams::Indexed(xs.iter().map(lit).collect()), Value::Object(m) => sqlformat::QueryParams::Named(m.iter().map(|(k, v)| (k.clone(), lit(v))).collect()), Value::Null => sqlformat::QueryParams::None, _ => return Err("params are a list (for ? and $1) or a map (for :name)".into()), }; Ok(sqlformat::format(sql, &p, opts(2, true)))}/// The SQL on one line: comments removed and runs of spaces and line breaks made one space, leaving quoted text ('...', "...", `...`) untouched. For logs and cache keys.pub fn sql_one_line(sql: &str) -> Result<String, String> { size(sql)?; let cs: Vec<char> = sql.chars().collect(); let mut out = String::new(); let mut i = 0; let mut space = false; while i < cs.len() { let c = cs[i]; if c == '\'' || c == '"' || c == '`' { if space && !out.is_empty() { out.push(' '); } space = false; out.push(c); i += 1; while i < cs.len() { out.push(cs[i]); if cs[i] == c { if i + 1 < cs.len() && cs[i + 1] == c { out.push(c); i += 2; continue; } break; } i += 1; } i += 1; } else if c == '-' && cs.get(i + 1) == Some(&'-') { while i < cs.len() && cs[i] != '\n' { i += 1; } space = true; } else if c == '/' && cs.get(i + 1) == Some(&'*') { i += 2; while i + 1 < cs.len() && !(cs[i] == '*' && cs[i + 1] == '/') { i += 1; } i += 2; space = true; } else if c.is_whitespace() { space = true; i += 1; } else { if space && !out.is_empty() && !matches!(c, ',' | ')' | ';') && !out.ends_with('(') { out.push(' '); } space = false; out.push(c); i += 1; } } Ok(out)}Related
sql_parse.qwer · 1.0.0SQL read as a database would: whether it parses in a dialect, its statement kind, the tables it reads and writes, and a fingerprint for grouping logs.9 views · 12 downloadsANSI to HTML ansi_html.qwer · 1.0.0Terminal output with ANSI colors made into HTML: build logs, test runs and CLI output on a page with their colors, bold, links and progress lines as the terminal showed them, or stripped to plain text.8 views · 7 downloadsHTTP Archive files har.qwer · 1.0.0HAR files from a browser's network panel read: pages with their load times, each request flat with timings and sizes (-1 given as null, SSL not counted twice), the slowest and largest requests, totals by host and type. Secrets masked from headers, cookies, query strings, form and JSON bodies before the file goes on a ticket, with a list of what was found and never its value.3 views · 6 downloadsLog parser log_parse.qwer · 1.0.0Log lines into rows: nginx and Apache access logs, syslog, logfmt and JSON lines, the format found by itself, with counts by any field.7 views · 7 downloadsSource maps source_maps.qwer · 1.0.0Minified JavaScript stack traces mapped back to the original files, lines, columns and function names with their source maps, and the code around each place.7 views · 8 downloadsThread 0 posts
On SQL formatting: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.