Developer tools

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

#sql#databases#logs#debugging

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 error

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.

sql_pretty_with_params(sql: str, params: any) → stror an error

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.

sql_one_line(sql: str) → stror an error

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.

Get it

$ quillo install sql_format               # sql_format.qwer into this folder$ quillo install sql_format --example     # and its example, sql_format.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/sql_format.qwer$ curl -O https://quillolang.org/plugins/files/sql_format.qwe

Then, in your program, all of it or only what you call:

main.qwe
use sql_format.qweruse sql_pretty, sql_pretty_with_params from sql_format      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
sqlformat = "0.2"

Example

sql_format.qwe
// 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_format.qwer
//! 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 &params {        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)}
Thread
95Useful? Like it, or add to its thread below.

Thread 0 posts

On SQL formatting: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.