Skip to content

Text stored in BLOB columns renders only as hex since 1.6.4 #328

Description

@sykesab

Problem

Since the 1.6.4 release, every byte-valued result cell renders as a hex string
such as 0x7b2261223a20317d. This includes blobs that hold plain text. A common
case is JSON stored in a MySQL mediumblob column.

In earlier versions these cells rendered as readable text, and the V value
viewer detected the JSON and opened tree mode. Now the cell shows hex and the
value viewer shows the same hex string. There is no way inside the TUI to see
the actual content.

Environment

  • sqlit-tui: 1.6.4 (also main at 9db49c2)
  • Provider: MySQL, column type mediumblob containing UTF-8 JSON
  • Also affects any driver that returns bytes for text-like content
    (Postgres bytea, SQLite BLOB, Oracle BLOB, etc.)

Reproduction

CREATE TABLE t (payload MEDIUMBLOB);
INSERT INTO t VALUES ('{"a": 1, "b": [1, 2, 3]}');
SELECT payload FROM t;

Expected cell: {"a": 1, "b": [1, 2, 3]}
Actual cell: 0x7b2261223a20312c202262223a205b312c20322c20335d7d

Press V on the cell: the viewer shows the hex string, not the JSON tree.

Root cause

normalize_arrow_value in sqlit/shared/ui/widgets_tables.py converts all
bytes / bytearray / memoryview values to hex unconditionally before the
Arrow backend is built:

if isinstance(value, (bytes, bytearray, memoryview)):
    return f"0x{bytes(value).hex()}"

This was added to stop a UnicodeDecodeError crash on invalid UTF-8 binary
data (#322). In earlier versions textual-fastdatatable cast the raw binary
Arrow column to string with safe=False, so valid UTF-8 bytes rendered as text.
The hex conversion fixes the crash but also removes that text path.

The value viewer is affected for the same reason. action_view_cell_full in
sqlit/domains/results/ui/mixins/results.py reads the cell back from the table
via get_cell_at, and the table only holds the hex string.

Proposed fix

Decode as UTF-8 first; fall back to hex only when decoding fails:

if isinstance(value, (bytes, bytearray, memoryview)):
    raw = bytes(value)
    try:
        return raw.decode("utf-8")
    except UnicodeDecodeError:
        return f"0x{raw.hex()}"

This restores text rendering for UTF-8 blobs while keeping the crash fix. The
value viewer then receives the decoded text and parse_json_value detects JSON
as before.

Existing tests in tests/ui/test_uuid_fastdatatable.py still pass: the test
payload contains 0x81 and 0xff, which fail a strict UTF-8 decode and still
render as hex.

Related: v tooltip crash on bracketed text

Restoring text rendering exposes a separate pre-existing bug in the lowercase
v cell preview. _show_cell_tooltip assigns the cell string directly to
table.tooltip, and Textual renders string tooltips as Rich markup. JSON that
contains a closing-tag shape such as "[/api/v1]" raises MarkupError when
Textual's hover timer redraws the tooltip, and the app exits. Text such as
"[bold]" is silently stripped instead.

Fix: wrap the value so Rich treats it as literal text:

table.tooltip = Text(tooltip_value)

The uppercase V value viewer already uses markup=False and is not affected.

Test plan

  • Add cases to tests/ui/test_uuid_fastdatatable.py with a UTF-8 JSON
    bytes value and assert get_cell_at returns the decoded string, for both
    the initial table and the incremental add_rows path.
  • Keep the existing invalid-UTF-8 cases asserting hex output.
  • Add a case that displays a JSON blob containing [/api/v1], triggers
    action_view_cell, and asserts the tooltip is a Text with the decoded
    payload.
  • Manual: run the MySQL reproduction above, confirm the cell shows JSON,
    v shows the preview without crashing, and V opens tree mode.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions