Repository navigation
fix(pretty_print): keep = ( together for scalar subqueries - #896
Merged
Merged
Conversation
psteinroe
changed the base branch from
fix/pretty-print-case-alignment
to
main
October 26, 2026 07:53
psteinroe
added a commit
that referenced
this pull request
Oct 26, 2026
…functions break sensibly (#898) Stacked on #896. Three places where a call-like expression broke in the wrong place or not at all. **`count(*) filter (...)` keeps `count(*)` together** A function call put its name and arguments in the same group as a following FILTER or WITHIN GROUP. When that clause didn't fit, the arguments broke too: ```sql -- before count( dl.id ) filter (where dl.created_at > now() - cast('180 days' as interval)) -- after count(dl.id) filter (where dl.created_at > now() - cast('180 days' as interval)) ``` The name and arguments now have their own group whenever FILTER, WITHIN GROUP or OVER follows. OVER already had one. The line breaks before the clause first. A clause that moves to its own line is indented one level, so it reads as part of the call: `percentile_cont(0.5)\n within group (order by x)`. **A FILTER clause that fits stays on one line** `filter (where` was followed by a soft line in the call's group, so a FILTER that fit still broke as `filter (where\n cond)` once the call broke, with `)` attached to the condition. FILTER is now a group of its own: `filter (where cond)` when it fits, and otherwise `filter (`, `where cond` indented on its own lines, and `)` on its own line, like a policy's `using (...)`. **Long `array[...]` literals fill indented lines** `array[...]` had no indent and no break after `[`. Once it didn't fit, every element after the first went on its own line at the enclosing indent, and `]` stuck to the last element: ```sql -- before oid <> all (cast(array['regproc', 'regprocedure', 'regoper', ... 'regcollation'] as regtype[])) -- after oid <> all (cast(array[ 'regproc', 'regprocedure', 'regoper', 'regoperator', 'regconfig', 'regdictionary', 'regnamespace', 'regcollation' ] as regtype[])) ``` Arrays now use the same layout as an `IN (...)` list. On one line when they fit; otherwise a break after `[`, the elements filling indented lines, and `]` back on the line the array started on. **`json_value`, `json_query` and `json_exists` respect the line width** These had no indent and no break after `(`, so a long context item ran past the width, and the clauses that wrapped did so at the enclosing indent with `)` attached to the last one. #882 left this out of scope. They now use the function-call layout that `json_table` already uses: on one line when the call fits, otherwise the context item, the path and each clause (`passing`, `returning`, wrapper, quotes, `on empty`, `on error`) on their own lines, one level in, and `)` on its own line: ```sql -- before (lineWidth 60) select json_value(jsonb_column_with_long_name, '$.some.very.long.path.expression.here' returning int default 0 on empty error on error) from t; -- after select json_value( jsonb_column_with_long_name, '$.some.very.long.path.expression.here' returning int default 0 on empty error on error ) from t; ``` The `passing` arguments are a group of their own, so `passing 0 as x, 2 as y` stays on one line. The three functions shared most of their emitter code, which is now written once. **Snapshot changes** All reviewed: - `fn(\n arg\n)\nfilter (where ...` / `within group (...)` → `fn(arg)` with the clause indented on the next line (`aggregates`, `ordered_set_filter`, `tuplesort`, `func_call_within_group_filter_0`, `20230405163940_download_metrics`, `window`) - array elements one per line at the enclosing indent → filled, indented lines between `array[` and `]` (`amutils`, `arrays`, `type_sanity`, `stats_ext`, `rowtypes`, `rangefuncs`, `multirangetypes`, `jsonpath`) - broken `json_value`/`json_query`/`json_exists` calls → function-call layout (`sqljson_queryfuncs`, most of the diff, and `json_returning`) New `function_call_layout.rs` tests cover `count(*) filter`, `within group`, long and short arrays, and the three JSON functions, broken and flat. **Not covered** - `json_arrayagg`/`json_objectagg`, `xmlserialize`/`xmlexists`, `position(... in ...)`, `extract(... from ...)` and a few statement headers listed in the release review can still go past the width. **Follow-ups:** The remaining width offenders are fixed in #946.
psteinroe
added a commit
that referenced
this pull request
Oct 26, 2026
…der the keyword (#938) Stacked on #935. Follow-up to #896, which listed this as not covered. A condition that stays on its keyword line (WHERE and HAVING with `clauseBodyStyle: compact`, and ON in every config) was indented for its continuation lines. When the condition only breaks inside the parentheses of a subquery, it has no continuation lines of its own. The indent then put the subquery two levels in and the closing parenthesis one level in, under nothing: ```sql -- layout: expanded, clauseBodyStyle: compact -- before select * from public.permissions where exists ( select 1 from public.permission_set_permissions as rp where rp.permission_name = permissions.name ); -- after select * from public.permissions where exists ( select 1 from public.permission_set_permissions as rp where rp.permission_name = permissions.name ); ``` The same happened to `on exists (` in a join with the default config. **What changed** `emit_clause_condition` (compact) and the ON of a join leave out the continuation indent when the condition breaks only inside a subquery. That covers: - a subquery link: `exists (...)`, `x in (...)`, `x = any (...)`, `(...)`, `array(...)`; - `x <op> (subquery)` or `x <op> array(subquery)`, where `x` is a column, parameter or constant; - `not` in front of either. Everything else keeps the indent, including AND/OR chains, whose operands still continue one level in, with a subquery in them closing at that level. **Snapshot changes** All reviewed; one category: the body and `)` of a subquery opened on a compact `where`/`having` line, or on an `on` line, move one level left, so `)` closes under the keyword. This is in `compact_select_list*`, `scalar_subquery_expanded`, `sublink_bool_groups_expanded` and, for ON with the default config, `predicate`. New tests in `subquery_layout.rs` cover `where exists (`, `where x = (`, a chain with an `exists` operand, and `on x = (`. **Not covered** A lone select item in compact layout has the same pattern: `select coalesce(` indents its arguments two levels and closes one level in. That is the select-list indent from #865 and would need the same rule there.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #894.
Follow-up to #864 for the comparison case, plus a nested-subquery case from #857.
= (subquery)stays together#864 kept
exists (,in (,= any (and> all (on one line, but a scalar subquery compared with an operator still broke between the operator and(. With the config reported in #857 (layout: expanded,clauseBodyStyle: compact,logicalOperatorPlacement: leading):The default config did the same,
t.owner_id =followed by(select …)on its own line. A binary operator now puts a plain space before a right-hand(subquery)orarray(subquery), and the subquery breaks inside its parentheses as it does afterin (. Other right-hand operands still break after the operator when they don't fit.Subqueries with clauses expand inside a select-list-only subquery
Since #857, a subquery made of a select list alone, such as
(select auth.uid()), uses fit layout in expanded mode, because there are no clauses to break between. That fit layout applied to everything inside it, so a subquery with FROM/WHERE nested in it stayed on one line in expanded mode:The emitter now remembers the configured layout, and a nested subquery with clauses switches back to it, so the inner
select max(x)/from y/where ...expands again.Snapshot changes
All reviewed; one category:
x =\n(/x <>\n(/x <\n(/<>\narray(→x = (,x <> (,x < (,<> array(. When the subquery is short enough to fit on the next line, it now sits indented inside the parentheses instead (relnamespace = (\n select oid from pg_namespace where ...\n)). Affected:create_cast,create_index,create_view,namespace,opr_sanity,rangefuncs,reloptions,tablespace,type_sanity,with.New fixtures
scalar_subquery.sqlandscalar_subquery_expanded.sql(the #857 reporter's config) cover a policy,=with a multi-clause subquery,= array(subquery), the nested-coalesce case and a short= (select 1). Newsubquery_layout.rstests cover both findings.Not covered
With
clauseBodyStyle: compact, a WHERE condition keeps its indent for its own continuation lines, so a subquery that opens on thewhereline is indented two levels and closes one level in:That is the same for
where exists (today and is a separate change to the compact layout.Follow-ups: The compact WHERE indentation is fixed in #938.