bugfix - fix broken output in expanded aligned format, when data are too short

First seen: 2026-03-23 19:42:35+00:00 · Messages: 8 · Participants: 3

Latest Update

2026-05-27 · claude-opus-4-6

Bugfix: Broken Output in Expanded Aligned Format When Data Are Too Short

Core Problem

The bug exists in src/fe_utils/print.c, specifically in the print_aligned_vertical() function, which handles rendering query results in psql's expanded (\x) display mode. When using border 2 style with unicode line characters, the expanded output becomes visually broken when the actual data columns are narrower than the record header line (e.g., ─[ RECORD 1 ]─).

The Visual Manifestation

With border style 2 and unicode linestyle, a table with short integer values like (10, 20) renders as:

┌─[ RECORD 1 ]─┐
│ a │ 10 │
│ b │ 20 │
└───┴────┘

The closing border (└───┴────┘) is shorter than the opening border (┌─[ RECORD 1 ]─┐), creating a visually broken box. The data area doesn't respect the minimum width established by the header.

Root Cause

The print_aligned_vertical() function has a code block that calculates the available width for data, ensuring the output respects terminal width and the header width. However, this width calculation was gated behind a condition that only checked for PRINT_WRAPPED format:

if (cont->opt->format == PRINT_WRAPPED)

When the format is PRINT_ALIGNED (the default), this width calculation is skipped entirely. The consequence is that in aligned mode, the data area width is never adjusted to at least match the record header width, resulting in the misaligned box-drawing characters.

Why This Matters Architecturally

The print_aligned_vertical() function serves both PRINT_ALIGNED and PRINT_WRAPPED formats when expanded mode is active. The distinction between these formats is:

Both formats share the same vertical (expanded) rendering path, but the minimum-width constraint (ensuring data area is at least as wide as the header) should apply to both. The original code only applied it to wrapped mode, likely an oversight from when this width-calculation logic was initially added (it may have been added specifically for wrapping purposes without considering that minimum-width enforcement is also needed for aligned mode).

The Fix

The fix is minimal — a single condition change:

- if (cont->opt->format == PRINT_WRAPPED)
+ if (cont->opt->format == PRINT_WRAPPED || cont->opt->format == PRINT_ALIGNED)

Subtlety: Avoiding Unwanted Wrapping

The initial v1 patch (mentioned only in the first email's diff) had a subtle issue: the code block that calculates available width also contains logic that can force the format into wrapped mode when data exceeds the terminal width. Simply adding PRINT_ALIGNED to the condition without care could cause aligned-format output to unexpectedly wrap.

The v2 patch (2026-03-24) addresses this by ensuring that the width calculation applies to both modes while the wrapping behavior remains exclusive to PRINT_WRAPPED. The comment was also updated:

/*
 * Calculate available width for data in wrapped mode or minimal width
 * in aligned mode
 */

This distinguishes the two purposes of the code block:

  1. For PRINT_WRAPPED: calculate maximum available width and enable wrapping
  2. For PRINT_ALIGNED: calculate minimum width to ensure consistent box borders

Regression Testing

The patch adds a test to src/test/regress/sql/psql.sql (and corresponding expected output) that:

  1. Sets border 2 and expanded on
  2. Creates a table with short integer columns
  3. Runs a SELECT in both aligned and wrapped formats
  4. Verifies the output boxes are properly formed
  5. Cleans up (drops table, resets pset options)

The final version includes explicit \pset expanded off cleanup after the test to avoid affecting subsequent tests — a defensive practice even though current subsequent tests reset their own settings.

Design Decisions and Tradeoffs

  1. Minimal fix approach: Rather than refactoring the width calculation into a separate function or restructuring the control flow, the fix takes the least-invasive approach of extending the existing condition. This is appropriate for a bugfix targeting backport.

  2. No backport discussion yet: The thread doesn't explicitly discuss which branches this should be backported to, though the bug likely exists in all supported versions since the width calculation code was introduced.

  3. Test hygiene: Minor discussion about whether to reset \pset expanded off after the test. Pavel initially argued it's each test's responsibility to set its own environment, but accepted the suggestion to add the reset for defensive purposes.