Skip to content

Commit

Permalink
feat: Add an option to consider JOINs top level or plain reserved new…
Browse files Browse the repository at this point in the history
…line
  • Loading branch information
lu-zero committed Feb 3, 2025
1 parent 4d2397d commit d1927e9
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 11 deletions.
4 changes: 0 additions & 4 deletions src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,6 @@ pub(crate) fn format(
formatter.format_newline_reserved_word(token, &mut formatted_query);
formatter.previous_reserved_word = Some(token);
}
TokenKind::Join => {
formatter.format_newline_reserved_word(token, &mut formatted_query);
formatter.previous_reserved_word = Some(token);
}
TokenKind::Reserved => {
formatter.format_with_spaces(token, &mut formatted_query);
formatter.previous_reserved_word = Some(token);
Expand Down
40 changes: 39 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod tokenizer;
pub fn format(query: &str, params: &QueryParams, options: &FormatOptions) -> String {
let named_placeholders = matches!(params, QueryParams::Named(_));

let tokens = tokenizer::tokenize(query, named_placeholders);
let tokens = tokenizer::tokenize(query, named_placeholders, options);
formatter::format(&tokens, params, options)
}

Expand Down Expand Up @@ -61,6 +61,10 @@ pub struct FormatOptions<'a> {
///
/// Default: None
pub max_inline_top_level: Option<usize>,
/// Consider any JOIN statement as a top level keyword instead of a reserved keyword
///
/// Default: false,
pub joins_as_top_level: bool,
}

impl<'a> Default for FormatOptions<'a> {
Expand All @@ -74,6 +78,7 @@ impl<'a> Default for FormatOptions<'a> {
max_inline_block: 50,
max_inline_arguments: None,
max_inline_top_level: None,
joins_as_top_level: false,
}
}
}
Expand Down Expand Up @@ -490,6 +495,39 @@ mod tests {
assert_eq!(format(input, &QueryParams::None, &options), expected);
}

#[test]
fn it_formats_select_query_with_non_standard_join_as_toplevel() {
let input = indoc!(
"
SELECT customer_id.from, COUNT(order_id) AS total FROM customers
INNER ANY JOIN orders ON customers.customer_id = orders.customer_id
LEFT
SEMI JOIN foo ON foo.id = customers.id
PASTE
JOIN bar
;"
);
let options = FormatOptions {
joins_as_top_level: true,
max_inline_top_level: Some(40),
max_inline_arguments: Some(40),
..Default::default()
};
let expected = indoc!(
"
SELECT
customer_id.from,
COUNT(order_id) AS total
FROM customers
INNER ANY JOIN
orders ON customers.customer_id = orders.customer_id
LEFT SEMI JOIN foo ON foo.id = customers.id
PASTE JOIN bar;"
);

assert_eq!(format(input, &QueryParams::None, &options), expected);
}

#[test]
fn it_formats_select_query_with_different_comments() {
let input = indoc!(
Expand Down
29 changes: 23 additions & 6 deletions src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ use winnow::prelude::*;
use winnow::token::{any, one_of, rest, take, take_until, take_while};
use winnow::Result;

pub(crate) fn tokenize(mut input: &str, named_placeholders: bool) -> Vec<Token<'_>> {
use crate::FormatOptions;

pub(crate) fn tokenize<'a>(
mut input: &'a str,
named_placeholders: bool,
options: &FormatOptions,
) -> Vec<Token<'a>> {
let mut tokens: Vec<Token> = Vec::new();

let mut last_reserved_token = None;
Expand All @@ -19,17 +25,28 @@ pub(crate) fn tokenize(mut input: &str, named_placeholders: bool) -> Vec<Token<'
}

// Keep processing the string until it is empty
while let Ok(result) = get_next_token(
while let Ok(mut result) = get_next_token(
&mut input,
tokens.last().cloned(),
last_reserved_token.clone(),
last_reserved_top_level_token.clone(),
named_placeholders,
) {
if result.kind == TokenKind::Reserved {
last_reserved_token = Some(result.clone());
} else if result.kind == TokenKind::ReservedTopLevel {
last_reserved_top_level_token = Some(result.clone());
match result.kind {
TokenKind::Reserved => {
last_reserved_token = Some(result.clone());
}
TokenKind::ReservedTopLevel => {
last_reserved_top_level_token = Some(result.clone());
}
TokenKind::Join => {
if options.joins_as_top_level {
result.kind = TokenKind::ReservedTopLevel;
} else {
result.kind = TokenKind::ReservedNewline;
}
}
_ => {}
}

tokens.push(result);
Expand Down

0 comments on commit d1927e9

Please sign in to comment.