Skip to content

Add JSON columns content highlighting - #347

Open
danvergara wants to merge 4 commits into
mainfrom
feature-json-handling
Open

Add JSON columns content highlighting#347
danvergara wants to merge 4 commits into
mainfrom
feature-json-handling

Conversation

@danvergara

@danvergara danvergara commented Jul 30, 2026

Copy link
Copy Markdown
Owner

JSON column viewer with syntax highlighting

Description

Adds a way to inspect JSON columns as pretty-printed, syntax-highlighted documents instead of squeezing them into a single table cell.

Appending | json to a read query switches the result panel from the usual table view to a text panel that pretty-prints the JSON payload and colorizes it with chroma.

SELECT metadata FROM users WHERE id = 1 | json

Also included: errors rendered into text panels are now styled (red, bold, padded) instead of being printed as plain text.

Screenshot From 2026-08-06 13-13-03 Screenshot From 2026-08-08 22-06-24 Screenshot From 2026-08-08 23-49-41

Changes

pkg/client/client.go

  • New QueryType type with NormalQuery / JSONQuery values, plus a JSONSuffix (| json) constant.
  • QueryResult gains two fields: QueryType and JSONData []byte.
  • New trimJSONSuffix(query string) (string, bool) helper: reports whether a query requests the JSON view and returns the query with the suffix removed. Matching is case-insensitive (strings.EqualFold) and tolerant of surrounding whitespace, so | json, | JSON, | Json and | json are all recognized. Keeping this as a standalone function makes the parsing unit-testable without a database.
  • AsyncQuery calls the helper on the read path, executes the stripped statement, and marks the result as a JSON query. QueryResult.Query keeps the original text including the suffix, so query history still shows what the user typed.
  • JSON path validation, so failures are explained rather than producing garbage output:
    • the query must select exactly one column, otherwise the result carries an error saying how many columns were selected;
    • the column's database type must be JSON-compatible for the active driver;
    • an empty result set reports no data returned.
  • New Client.isValidJSONColumn helper maps drivers to acceptable column types:
    • PostgreSQL: JSON, JSONB, TEXT, VARCHAR
    • MySQL: JSON
    • Oracle: JSON, CLOB, BLOB, VARCHAR2
    • SQL Server: NVARCHAR, VARCHAR, TEXT (no native JSON type)
    • SQLite: everything — it is dynamically typed, so json.Indent acts as the final validator
    • default: JSON, JSONB
  • The existing row-scanning logic (BLOB handling, []bytestring conversion, etc.) is unchanged; it now lives under the default branch of a switch alongside the JSON branch.
  • The read and exec paths now execute query (the goroutine's parameter, i.e. the suffix-stripped statement) instead of the q range variable. This is load-bearing rather than cosmetic: without it the | json suffix would reach the driver and every JSON query would fail with a syntax error.

pkg/bubbletui/resultset.go

  • Registers a dblab-cyberpunk chroma style matching the app's palette: magenta keys, neon-green strings, purple numbers and literals, light-grey punctuation, red errors.
  • On results, switches on qr.QueryType:
    • JSONQueryjson.Indent the raw bytes, highlight them with quick.Highlight(..., "json", "terminal256", dblab-cyberpunk), and render into a text panel.
    • NormalQuery → the existing table panel.
  • Indent/highlight failures fall back to a styled error inside the same panel, so a malformed payload can't blow up the view.
  • Query errors are now rendered through errorStyle before being set on the panel.

pkg/client/client_test.go

Seven cases added to ClientTestSuite, all driver-parameterized through DB_DRIVER like the rest of the suite. They rely on a jsonExpr helper that builds a driver-specific expression yielding a JSON-typed column (::jsonb on Postgres, CAST(... AS JSON) on MySQL, a plain literal on SQLite), so no schema change is required to exercise the feature.

Test Covers
TestAsyncQueryJSONView happy path, suffix stripped before execution, Query retains the suffix for history
TestAsyncQueryJSONViewSuffixVariants case-insensitive and whitespace-tolerant matching in trimJSONSuffix
TestAsyncQueryJSONViewMultipleColumns the single-column guard
TestAsyncQueryJSONViewNonJSONColumn isValidJSONColumn rejection (skipped on SQLite, which accepts any type)
TestAsyncQueryJSONViewNoRows the empty result set guard
TestAsyncQueryWithoutJSONSuffix the NormalQuery path is unaffected
TestAsyncQueryJSONViewMixedBatch per-result QueryType routing when JSON and normal queries run concurrently

Payloads are compared after json.Unmarshal rather than byte-for-byte, since jsonb reorders keys and MySQL compacts whitespace.

go.mod / go.sum

  • Adds github.com/alecthomas/chroma/v2 v2.27.0 (and its github.com/dlclark/regexp2/v2 indirect dependency).

Fixes #251

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

The suite requires Docker (testcontainers):

make test                              # postgres
make test DB_DRIVER=mysql DB_USER=root

Both drivers pass.

Manual check in the TUI — no schema needed, this one literal exercises every token type in the style at once:

SELECT '{"name":"dblab","tags":["sql","tui"],"count":42,"ok":true,"extra":null}' AS doc | json

Verify:

  • keys render magenta, strings green, numbers and true/null purple, punctuation grey;
  • selecting more than one column reports the column-count error;
  • selecting a non-JSON column reports the type error;
  • a query matching no rows reports no data returned;
  • queries without the suffix still render as tables.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have checked my code and corrected any misspellings

@danvergara
danvergara marked this pull request as ready for review August 9, 2026 04:36
@danvergara danvergara self-assigned this Aug 9, 2026
@danvergara
danvergara requested a review from rkgarcia August 9, 2026 04:37
Comment thread pkg/client/client.go
return dbTypeName == "JSON" || dbTypeName == "CLOB" || dbTypeName == "BLOB" || dbTypeName == "VARCHAR2"
case drivers.SQLServer:
// SQL Server lacks a native JSON type, so we must allow text.
return dbTypeName == "NVARCHAR" || dbTypeName == "VARCHAR" || dbTypeName == "TEXT"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider "SQL Server 2025 introduces a native JSON data type, shifting away from the older requirement of storing JSON documents as standard VARCHAR or NVARCHAR strings. Instead of treating JSON as a string, SQL Server 2025 parses it upon input and stores it internally in an optimized, native binary UTF-8 format (referred to as the MSJSON format)"

@rkgarcia rkgarcia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only review the MSSQL Json Data Type

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.

[FEATURE] present result vertical

2 participants