Skip to content

feat(format): give parenthesised groups that span lines their own lines - #184

Merged
kvlonge merged 2 commits into
mainfrom
fix/unhug-nested-casts
Nov 11, 2026
Merged

kvlonge merged 2 commits into
mainfrom
fix/unhug-nested-casts

Conversation

@kvlonge

@kvlonge kvlonge commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Why

Nested CASTs that span lines printed hugged together, as CAST(CAST(( on one line, with two groups closing at once.

Changes

  • feat(format): a parenthesised group that spans lines opens its inside on its own line and closes on its own line. VALUES rows, DECODE and OVER keep their layout.
  • A long line breaks at || first, then + and -, then *, /, // and %. An operator that continues a group's only operand keeps the group's indentation. A tighter operator under a looser one, such as * d under + c, keeps the extra indent so the layout reads with the right precedence.
  • 15 of the 1,066 format corpus entries change layout.
  • Lines between a CASE and its END no longer break before AND, OR or an arithmetic operator.
  • docs: the landing page and policy page examples show the new layout.

Converted SQL

The convert layout still keeps case and refuses any change other than whitespace. This diff compares origin/main with this branch using the playground's analyze at its default settings, over 2,962 inputs:

Corpus Inputs Changed
basics 1,842 473
everyday 615 189
everyday-models 499 263
examples 6 1

All 926 changes are whitespace only. Outcomes, diagnostics, raises and columns are identical, with 0 panics. Examples are in the first comment.

Verification

  • make verify-recorded: 12,236 cases, 0 failed, 0 unproven. translation-results shows 0 changes, and sql-sizes is unchanged at 13,903,263 B.
  • The crate tests for format, blocks and the convert layout pass. The playground tests pass 14 of 14.
  • The docs CLI samples match the new layout.

A group such as a nested CAST that spans lines no longer opens or closes on a line it shares
with another group: CAST(CAST((... now puts each opener's inside on its own line, closed on
its own line. A long line breaks at its loosest operators first (|| before + and - before *,
/, // and %), and an operator that continues the one operand of a group keeps the group's
indentation instead of one level more. Converted SQL gets the same layout; only whitespace
changes, and every token keeps its case.

15 of the 1,066 format corpus entries change layout.

kvlonge commented Nov 11, 2026

Copy link
Copy Markdown
Contributor Author

Converted SQL before (origin/main) and after (this branch), from the harness diff:

The playground's decimal example (example:decimal):

--- old
+++ new
@@ -1,14 +1,20 @@
 SELECT
     "ORDERS"."ORDER_ID" AS "ORDER_ID",
-    CAST(CAST((
-        sign(CAST(CAST("ORDERS"."TOTAL" AS DECIMAL(38, 2)) * 100000000 AS HUGEINT))
-        * (
+    CAST(
+        CAST(
             (
-                2 * abs(CAST(CAST("ORDERS"."TOTAL" AS DECIMAL(38, 2)) * 100000000 AS HUGEINT)) + 3
-            )
-            // 6
+                sign(CAST(CAST("ORDERS"."TOTAL" AS DECIMAL(38, 2)) * 100000000 AS HUGEINT))
+                * (
+                    (
+                        2
+                        * abs(CAST(CAST("ORDERS"."TOTAL" AS DECIMAL(38, 2)) * 100000000 AS HUGEINT))
+                        + 3
+                    )
+                    // 6
+                )
+            ) AS DECIMAL(38, 0)
         )
-    ) AS DECIMAL(38, 0))
-    * 0.00000001 AS DECIMAL(18, 8)) AS "THIRD"
+        * 0.00000001 AS DECIMAL(18, 8)
+    ) AS "THIRD"
 FROM "ORDERS" AS "ORDERS"
 ORDER BY "ORDERS"."ORDER_ID" ASC NULLS LAST

everyday: a sum of two columns cast to a decimal (everyday#183):

--- old
+++ new
@@ -6,6 +6,8 @@
 
 SELECT
-    CAST((
-        CAST("T"."A" AS DECIMAL(38, 0)) + CAST("T"."B" AS DECIMAL(38, 0))
-    ) AS DECIMAL(2, 0)) AS "S"
+    CAST(
+        (
+            CAST("T"."A" AS DECIMAL(38, 0)) + CAST("T"."B" AS DECIMAL(38, 0))
+        ) AS DECIMAL(2, 0)
+    ) AS "S"
 FROM "T" AS "T"

basics: a literal added to a column, cast to a decimal (basics#481):

--- old
+++ new
@@ -1,5 +1,7 @@
 SELECT
-    CAST((
-        CAST(1 AS DECIMAL(38, 0)) + CAST("ORDERS"."TOTAL" AS DECIMAL(38, 2))
-    ) AS DECIMAL(13, 2)) AS "V"
+    CAST(
+        (
+            CAST(1 AS DECIMAL(38, 0)) + CAST("ORDERS"."TOTAL" AS DECIMAL(38, 2))
+        ) AS DECIMAL(13, 2)
+    ) AS "V"
 FROM "ORDERS" AS "ORDERS"

Not in this change: An operator placed by the earlier pass that leads multi-line operands still indents one level more than one this change places. One everyday-models query shows both, for example || '-' and || COALESCE( in a dbt surrogate key. This is cosmetic, and fixing it changes that older rule, so it is left for a separate change.

…er one

A line inside a group led by an operator of another tier, such as + c, kept the group's
indentation when it broke again, so * d lined up under + c and read as (... + c) * d. Such a
line now keeps the usual extra indent. A subquery's clauses and a window's PARTITION BY are no
longer taken for a group's only operand. VALUES row openers are looked up in a mask instead of
a list, so a long VALUES list no longer slows the layout down quadratically.

The first commit also stops a long line from breaking before AND, OR or an arithmetic operator
that sits between a CASE and its END on that line.
@kvlonge
kvlonge enabled auto-merge November 11, 2026 21:22
@kvlonge
kvlonge added this pull request to the merge queue Nov 11, 2026
Merged via the queue into main with commit bab166c Nov 11, 2026
14 checks passed
@kvlonge
kvlonge deleted the fix/unhug-nested-casts branch November 11, 2026 23:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant