feat(datagrid): per-element JSON editing for PostgreSQL jsonb[] columns - #2903
Merged
Merged
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PostgreSQL
jsonb[]andjson[]cells now open on their elements, each element in the same JSON viewer ajsonbcell gets, on the data grid chevron and in the row inspector.Fixes #2897
Root cause
Two booleans in
ColumnTypekept an array of JSON out of every structured editor:supportsElementEditingreturned false when the array element was.json.isJsonTypeis true only for a scalar.json, never for.array(element: .json).So a
jsonb[]cell matched no branch inDataGridView+Click.handleChevronActionand fell through to the plain inline text editor, andFieldEditorResolver.resolvefell through to.multiLine. Nothing decoded the value wrongly: the escaping in the report is PostgreSQL's own array quoting, arriving intact.The exclusion rested on a claim that is false
PR #2110 excluded
jsonb[]because "its additional escaping layer cannot round-trip through a per-element list". Measured against a live PostgreSQL 17.11, with aswiftcharness compiled over the shippingPostgresArrayLiteralCodecand fed the exact literalsarray_outproduced: every single-dimension case parses to the right elements and re-serializes byte for byte. That covers nested objects containing commas and braces, escaped quotes, doubled backslashes,\n, non-ASCII, the empty string,{}and[]elements, and the SQL-NULL versus JSON-null distinction. Only[0:1]={…}and{{1,2},{3,4}}return nil, which is the correct degrade and already falls back to raw text editing.So the codec did not need rewriting, and TableProPluginKit's public API does not move: no
currentPluginKitVersionbump, no plugin re-release.What changed
ColumnType.arrayElementEditorreplaces the boolean's overloaded job: it answers which element editor an array gets (.scalar,.json, or none), andsupportsElementEditingis now derived from it, so the three existing call sites are unchanged.ArrayValueEditorViewgains a JSON mode. The elements are listed above and the selected one opens inJSONViewerViewin its existing live-binding mode, so an element has the Text and Tree modes, the tree search and the invalid-JSON reporting the rest of the app already has. Add, remove and reorder act on the selection from the footer, because a list of documents is a master list rather than a stack of one-line fields.An element the user only read writes back the bytes the server sent. An element whose JSON actually changed writes back compact, which is what the scalar JSON cell editor does. Without that rule, opening a
json[]cell and pressing OK would rewrite every element as the pretty-printed form, and PostgreSQL storesjsontext verbatim. This is the bug DataGrip shipped in the same feature (YouTrack DBE-26425, where the per-element editor stringified object elements on save).FieldEditorKind.arrayElementsgives the row inspector a route it had for no array type at all: #2110 shipped grid-only, and the inspector is the surface in the report's screenshot. It followsSetPickerViewexactly, a summary label and a menu carrying Edit Elements…, Set NULL and Set DEFAULT, opening the same editor in a popover that commits once. That inherits pending NULL and DEFAULT and the multi-row "Multiple values" state instead of reinventing them.FieldEditorResolverresolves the array editor from the value, not from the declared type alone.jsonb[]andjsonb[][]are one type in PostgreSQL's catalog and any array column may carry an explicit lower bound, so the type cannot rule either out on a given row. Gating on a successful parse mirrors what the grid already does, and it is also why another engine's list literal, such as DuckDB's[a, b], cannot reach this editor. That parse is also what scopes the editor to the engines whose arrays are written this way: a document store'sobject[]classifies identically, so a field with no literal to read keeps the plain editor rather than being offered a list that would commit PostgreSQL{…}syntax. A stored NULL and a multi-row selection therefore stay on the plain editor, which is where they already were.ArrayValueEditorViewtakes the stored literal rather than parsed elements, which makes it total over what a column can hold: a literal the list cannot read opens in its raw text mode over that same text. Without that, the editor kind cached on the field made a real data-loss path reachable, because committing a bounds-prefixed literal through Edit as Text and reopening the popover would have shown an empty list and written{}over it on OK.ColumnType.withAllowedValuescarries a column's declared labels down into an array's element. They arrive separately inTableRows.columnEnumValuesand were injected on the scalar enum and set cases alone, so anENUM[]column would have reached the inspector with no vocabulary and offered free-form text where the grid offers the declared labels.One collateral fix folded in
PostgresArrayLiteralCodectrimmed whitespace with Swift'sCharacter.isWhitespace, the whole Unicode set, where PostgreSQL'sarray_intrims six characters. Measured on the same server:array_outwrites U+00A0 and U+3000 into atext[]literal unquoted andarray_inreads them back as part of the value, while the codec parsed{<U+00A0>abc,def}toabcand re-serialized to{abc,def}. Editing any other element in the same cell then deleted that character silently, because committing re-serializes every row.Fixing that exposed the general form of the bug, so the codec now scans Unicode scalars rather than
Characters, which is what the server does. A Swift grapheme can carry a structural scalar and a combining mark together, andCharactercomparison then misses it:,followed by U+0301 is oneCharacterthat is not",", so a grapheme scan keeps an element the server splits in two, and a space followed by U+0301 is oneCharacterthat is not whitespace, so it goes out unquoted for the server to trim. CR LF is the same shape, one grapheme the server reads as two of its six.It ships here because the new inspector route gives
text[]a surface it did not have before, which makes an existing silent-data-loss path materially more likely to bite. The public API does not move:parseandserializekeep their signatures and theirCharacterdelimiter.Not in scope
[0:2]={a,b,c}keep degrading to raw text editing.bytea[]and composite arrays stay excluded.json[]badge the report noticed isColumnType.badgeLabel, a semantic vocabulary (string,number,bool,json,date) in which scalarjsonbalso badges asjson. The raw type name is intact throughout, so nothing is losing ab.Verification
verify.sh buildverify.sh testover the five touched suitesverify.sh lintover 19 changed Swift filesverify.sh docsverify.sh plugins(AllPlugins)unknown attribute 'usableFromInlinenonisolated'from a@TaskLocalmacro expansion inoracle-nio. Same error on unrelated branches.Suites:
PostgresArrayLiteralCodecTests,ArrayValueEditorModelTests,FieldEditorResolverTests,InspectorFieldLayoutTests,ColumnTypeClassifierTests. New coverage pins the measured PostgreSQL 17.11 literals as byte-identical round trips, the SQL-NULL versus JSON-null distinction, the six characters PostgreSQL does trim against the two it does not, the write-back rule in all four of its cases, and the resolver's parse gate including the multi-dimensional and foreign-literal fallbacks.ColumnTypeClassifierTestspreviously asserted!classify("jsonb[]").supportsElementEditing; that assertion is now the opposite, which is the point of the change.A second-model review (Codex) read the diff over two rounds and raised ten defects. Fixed here: the grapheme-versus-scalar scan, the discarded raw edit on reopen, the missing enum-array labels, unbounded JSON parsing in the element list's render path, engine scoping for a
NULLobject[]cell, and six icon-only buttons plus an invalid-JSON warning that VoiceOver read as generic symbols. One suggestion could not be taken:ForEach(rows.enumerated(), id:)is the project's own rule in.claude/skills/swiftui/references/api.md, andEnumeratedSequence'sRandomAccessCollectionconformance is macOS 26 against a macOS 14 target, so the array copy stays with the reason recorded beside it.End to end against a live PostgreSQL 17.11: the column opens on its elements in the row inspector with the
json[]badge, andtext[]gets the same list.No
TableProUITestscoverage. The flow needs a live PostgreSQL server, which CI does not have, and the data grid takes no synthetic input since #2381 replaced its cells with CoreText drawing.The Arrays of JSON docs section ships without a screenshot and needs one. The editor opens from a SwiftUI
Menu, which does not respond toSystem Eventssynthetic clicks, so the popover could not be driven from a script. A placeholder was written and then removed rather than shipped:docs/STYLE.mdrequires alt text to describe what is actually in the image, and a title card under "Array element list above a JSON document editor" would not. The surrounding inspector field was captured and is below.After
The row inspector's
itemsfield, with thejson[]badge and the element editor on it, captured against a live PostgreSQL 17.11. The before is the plain text field over the escaped literal in the report's own screenshot.