Skip to content

fix(output): truncate table cells to the column width - #71

Merged
TimelordUK merged 2 commits into
mainfrom
fix/table-output-column-truncation
Sep 9, 2026
Merged

fix(output): truncate table cells to the column width#71
TimelordUK merged 2 commits into
mainfrom
fix/table-output-column-truncation

Conversation

@TimelordUK

Copy link
Copy Markdown
Owner

The -o table renderer measures each column, caps the width at --max-col-width (default 50, not unlimited), draws the border from the capped widths — and then printed the cell values untruncated. Any value longer than the cap overflowed its cell, so that row ran past the border and the table came out ragged. A TeamCity project path like "xTrader2 / Trading Services / Pricing / Analytics / master" is 58 chars, giving a 75-column row inside a 67-column frame.

Headers had the same hole: {:^width$} pads but never truncates, and it pads by byte length, so a wide or accented header misaligned too. The comfy-table styles did truncate, but by byte slicing (&s[..max-3]), which panics on a multi-byte boundary.

Add truncate_to_width to string_utils: ANSI-aware (escape codes pass through uncharged, so colours survive), unicode-width-aware (never splits a multi-byte char or a wide glyph), appending "..." within the budget. Use it for cells and headers in both copies of the renderer (non_interactive::output_table_old_style and its duplicate main::output_table_helper), centre headers by display width, and replace the byte-slicing in the comfy path.

--max-col-width 0 remains the way to get untruncated values; it was already aligned and stays so.

Tests: 6 unit tests plus a doctest for the helper, including a loop asserting the result never exceeds the budget for widths 0..=60; and tests/python_tests/test_table_output_alignment.py, which asserts every line of a rendered table has identical display width across caps 1/2/3/4/5/12/30/57/58/59, long headers, accented and wide characters, uncapped, and markdown style. 11 of its 15 cases fail against the pre-fix binary.

Claude-Session: https://claude.ai/code/session_01NBuWNEbq5TNWsF2YR9wYVH

TimelordUK and others added 2 commits September 9, 2026 18:59
The `-o table` renderer measures each column, caps the width at
--max-col-width (default 50, not unlimited), draws the border from the
capped widths — and then printed the cell values untruncated. Any value
longer than the cap overflowed its cell, so that row ran past the border
and the table came out ragged. A TeamCity project path like
"xTrader2 / Trading Services / Pricing / Analytics / master" is 58 chars,
giving a 75-column row inside a 67-column frame.

Headers had the same hole: `{:^width$}` pads but never truncates, and it
pads by byte length, so a wide or accented header misaligned too. The
comfy-table styles did truncate, but by byte slicing (`&s[..max-3]`),
which panics on a multi-byte boundary.

Add `truncate_to_width` to string_utils: ANSI-aware (escape codes pass
through uncharged, so colours survive), unicode-width-aware (never splits
a multi-byte char or a wide glyph), appending "..." within the budget.
Use it for cells and headers in both copies of the renderer
(non_interactive::output_table_old_style and its duplicate
main::output_table_helper), centre headers by display width, and replace
the byte-slicing in the comfy path.

`--max-col-width 0` remains the way to get untruncated values; it was
already aligned and stays so.

Tests: 6 unit tests plus a doctest for the helper, including a loop
asserting the result never exceeds the budget for widths 0..=60; and
tests/python_tests/test_table_output_alignment.py, which asserts every
line of a rendered table has identical display width across caps
1/2/3/4/5/12/30/57/58/59, long headers, accented and wide characters,
uncapped, and markdown style. 11 of its 15 cases fail against the
pre-fix binary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBuWNEbq5TNWsF2YR9wYVH
Adds a skeleton for the question "would more build agents actually help?",
correlating TeamCity queue time with Metricbeat host CPU.

  examples/teamcity_agent_capacity.sql       live TeamCity + Elasticsearch,
                                             marked [TEST:SKIP] (credentials)
  examples/teamcity_agent_capacity_demo.sql  same logic over sample data,
                                             runs today, passes as a smoke test
  data/teamcity_{builds,cpu}_sample.csv      6 hosts x 5 agents, built to
                                             produce all three verdicts

The analysis classifies each queued build rather than reporting queue time,
because queue time alone does not say agents are short:

  hardware_bound  agents busy AND CPU pegged -> more agents make it worse
  add_agents      agents busy AND CPU idle   -> more agents per server help
  pool_mismatch   agents were free           -> not a capacity problem

Two things worth recording from building it:

sql-cli has no ASOF JOIN (kdb `aj`); join types are Cross/Full/Inner/Left/Right.
It is expressible by hand — inequality join to every earlier sample, then
ROW_NUMBER() ... ORDER BY ts DESC filtered to rn = 1 — and the examples carry
that pattern with the operand-order constraint spelled out (the planner requires
the left table's column as the left operand).

P44: PARSE_DATETIME's explicit-format path parses `%z` and then discards the
offset, so `+0000`, `+0100` and `-0500` all yield the same instant, while the
one-arg auto-detect path on ISO-8601 input honours it. DuckDB applies the offset
in all three cases. This is the shape of bug that does not announce itself: it
would have shifted every TeamCity timestamp an hour off UTC through BST while
Metricbeat stayed correct, quietly scrambling the correlation for half the year.
Logged in docs/SQL_PARITY.md; the skeleton works around it by rebuilding the
stamp as ISO-8601 for the one-arg parser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBuWNEbq5TNWsF2YR9wYVH
@TimelordUK
TimelordUK merged commit af89d27 into main Sep 9, 2026
8 checks passed
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