From 2c3d9cca5aa32c2b6506df6c5940e7b8d360146a Mon Sep 17 00:00:00 2001 From: edjubert Date: Mon, 7 Sep 2026 15:05:37 +0200 Subject: [PATCH] Add --join-on-indent option to indent multi-keyword join ON clauses Add a new option --join-on-indent that indents the ON clause continuation lines (AND/OR) of a multi-keyword join (e.g. LEFT OUTER JOIN) one level below the join keyword. By default the option is off and the existing behavior is preserved. Without the option (default): LEFT OUTER JOIN ab_frap appel ON appel.frap_tyap_code = tyap.tyap_code AND appel.frap_imme_no = tyap.tyap_imme_no With --join-on-indent: LEFT OUTER JOIN ab_frap appel ON appel.frap_tyap_code = tyap.tyap_code AND appel.frap_imme_no = tyap.tyap_imme_no --- lib/pgFormatter/Beautify.pm | 16 ++++++++++++++-- lib/pgFormatter/CLI.pm | 6 +++++- t/02_regress.t | 3 ++- t/test-files/ex85.sql | 25 +++++++++++++++++++++++++ t/test-files/expected/ex85.sql | 26 ++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 t/test-files/ex85.sql create mode 100644 t/test-files/expected/ex85.sql diff --git a/lib/pgFormatter/Beautify.pm b/lib/pgFormatter/Beautify.pm index 8e0bc254..bc222f06 100755 --- a/lib/pgFormatter/Beautify.pm +++ b/lib/pgFormatter/Beautify.pm @@ -168,6 +168,8 @@ Takes options as hash. Following options are recognized: =item * compact_clause_body - keep the first element of a clause on the keyword line +=item * join_on_indent - indent the ON clause continuation lines (AND/OR) of a multi-keyword join (e.g. LEFT OUTER JOIN) one level below the join keyword + =item * redundant_parenthesis - do not eliminate redundant parenthesis in DML queries =item * vertical_align - vertically align CREATE TABLE column definitions @@ -186,7 +188,7 @@ sub new { $self->set_defaults(); for my $key ( - qw( query spaces space break wrap keywords functions rules uc_keywords uc_functions uc_types uc_identifiers no_comments no_grouping placeholder multiline separator comma comma_break format colorize format_type wrap_limit wrap_after wrap_comment numbering redshift no_extra_line keep_newline no_space_function compact_clause_body redundant_parenthesis vertical_align) + qw( query spaces space break wrap keywords functions rules uc_keywords uc_functions uc_types uc_identifiers no_comments no_grouping placeholder multiline separator comma comma_break format colorize format_type wrap_limit wrap_after wrap_comment numbering redshift no_extra_line keep_newline no_space_function compact_clause_body join_on_indent redundant_parenthesis vertical_align) ) { $self->{$key} = $options{$key} if defined $options{$key}; @@ -4075,7 +4077,14 @@ sub beautify { and ( not defined $last or uc($last) ne 'MATCH' ) ) { $self->{'no_break'} = 0; - if ( !$self->{'_is_in_join'} and ( defined $last and $last ne ')' ) ) { + # When join_on_indent is enabled, only step back once per join clause: + # a join spelled with several keywords, such as LEFT OUTER JOIN, must + # not step back once per keyword or it loses one indentation level. + if ( !$self->{'_is_in_join'} + and ( defined $last and $last ne ')' ) + and ( !$self->{'join_on_indent'} + or $last !~ /^(?:LEFT|RIGHT|FULL|INNER|OUTER|CROSS|NATURAL)$/i ) ) + { $self->_back( $token, $last ) if ($#{ $self->{'_level_stack'} } < 0 or $self->{'_level'} > $self->{'_level_stack'}[-1]+1); } if ( $self->{'_has_over_in_join'} ) { @@ -5691,6 +5700,8 @@ Currently defined defaults: =item compact_clause_body => 0 +=item join_on_indent => 0 + =item redundant_parenthesis => 0 =item vertical_align => 0 @@ -5738,6 +5749,7 @@ sub set_defaults { $self->{'keep_newline'} = 0; $self->{'no_space_function'} = 0; $self->{'compact_clause_body'} = 0; + $self->{'join_on_indent'} = 0; $self->{'redundant_parenthesis'} = 0; $self->{'vertical_align'} = 0; diff --git a/lib/pgFormatter/CLI.pm b/lib/pgFormatter/CLI.pm index 24a987eb..b7ae80a9 100755 --- a/lib/pgFormatter/CLI.pm +++ b/lib/pgFormatter/CLI.pm @@ -131,6 +131,7 @@ sub beautify { $args{'extra_keyword'} = $self->{'cfg'}->{'extra-keyword'}; $args{'no_space_function'} = $self->{'cfg'}->{'no-space-function'}; $args{'compact_clause_body'} = $self->{'cfg'}->{'compact-clause-body'}; + $args{'join_on_indent'} = $self->{'cfg'}->{'join-on-indent'}; $args{'redundant_parenthesis'} = $self->{'cfg'}->{'redundant-parenthesis'}; $args{'vertical_align'} = $self->{'cfg'}->{'vertical-align'}; @@ -333,6 +334,9 @@ Options: --compact-clause-body : keep the first element of a FROM, WHERE, SET, RETURNING, HAVING or VALUES clause on the same line as the keyword, and the body of a CASE ... THEN on the same line as THEN. + --join-on-indent : indent the ON clause continuation lines (AND/OR) of a + multi-keyword join (e.g. LEFT OUTER JOIN) one level + below the join keyword. --redundant-parenthesis: do not remove redundant parenthesis in DML. --vertical-align : vertically align CREATE TABLE column definitions and trailing comments. @@ -403,7 +407,7 @@ sub get_command_line_args { 'wrap-limit|w=i', 'wrap-after|W=i', 'inplace|i!', 'extra-function=s', 'extra-keyword=s', 'no-space-function!', - 'compact-clause-body!', 'ident-case|I=i', + 'compact-clause-body!', 'join-on-indent!', 'ident-case|I=i', 'redundant-parenthesis!', 'vertical-align!', ); diff --git a/t/02_regress.t b/t/02_regress.t index 29bc6092..5655fcf9 100755 --- a/t/02_regress.t +++ b/t/02_regress.t @@ -1,4 +1,4 @@ -use Test::Simple tests => 86; +use Test::Simple tests => 87; use File::Temp qw/ tempfile /; my $pg_format = $ENV{PG_FORMAT} // './pg_format'; # set to the full path to 'pg_format' to test installed binary in /usr/bin @@ -32,6 +32,7 @@ foreach my $f (@files) $opt = "--compact-clause-body" if ($f =~ m#/ex82.sql$#); $opt = "--no-space-function" if ($f =~ m#/ex83.sql$#); $opt = "--ident-case 2 -f 1 -U 1" if ($f =~ m#/ex84\.sql$#); + $opt = "--join-on-indent" if ($f =~ m#/ex85.sql$#); if ($f =~ m#/ex61.sql$#) { my ($fh, $tmpfile) = tempfile('tmp_pgformatXXXX', SUFFIX => '.lst', TMPDIR => 1, O_TEMPORARY => 1, UNLINK => 1 ); diff --git a/t/test-files/ex85.sql b/t/test-files/ex85.sql new file mode 100644 index 00000000..763c3f4e --- /dev/null +++ b/t/test-files/ex85.sql @@ -0,0 +1,25 @@ +SELECT + tyap0.tyap_code + , appel.frap_tyap_code +FROM + tyap + LEFT OUTER JOIN ab_frap@link_serveur appel ON appel.frap_tyap_code = tyap.tyap_code + AND appel.frap_imme_no = tyap.tyap_imme_no + LEFT OUTER JOIN bud ON bud.budg_tyap_direct = tyap.tyap_direct; + +SELECT + a.id +FROM + a + INNER JOIN b ON b.id = a.id + CROSS JOIN c ON true + NATURAL JOIN d ON true + RIGHT JOIN e ON e.id = a.id + FULL JOIN f ON f.id = a.id; + +SELECT + x.v +FROM + x + JOIN y ON y.k = x.k + LEFT JOIN z ON z.k = x.k; diff --git a/t/test-files/expected/ex85.sql b/t/test-files/expected/ex85.sql new file mode 100644 index 00000000..7ed465b5 --- /dev/null +++ b/t/test-files/expected/ex85.sql @@ -0,0 +1,26 @@ +SELECT + tyap0.tyap_code, + appel.frap_tyap_code +FROM + tyap + LEFT OUTER JOIN ab_frap@link_serveur appel ON appel.frap_tyap_code = tyap.tyap_code + AND appel.frap_imme_no = tyap.tyap_imme_no + LEFT OUTER JOIN bud ON bud.budg_tyap_direct = tyap.tyap_direct; + +SELECT + a.id +FROM + a + INNER JOIN b ON b.id = a.id + CROSS JOIN c ON TRUE + NATURAL JOIN d ON TRUE + RIGHT JOIN e ON e.id = a.id + FULL JOIN f ON f.id = a.id; + +SELECT + x.v +FROM + x + JOIN y ON y.k = x.k + LEFT JOIN z ON z.k = x.k; +