Repository navigation
feat(grid): client-side sorting for primitive result columns - #171
Merged
Merged
Conversation
Result grids delegated sorting to the backend, which re-ran the user's SQL wrapped in `SELECT * FROM (…) ORDER BY …`. That only worked for plain parsable SELECTs, re-ran expensive queries on every header click, and the table browser had no sort support at all. Sorting now happens in the frontend over the rows already returned, and is limited to primitive columns (text/number/boolean/date/NULL) because JSON, arrays and BLOBs have no meaningful client-side total ordering. - add src/utils/gridSort.ts: sortability detection, type-aware comparator, stable multi-column sort, sort summary - extend useDataGridSort with setSort/pruneSort; drop the now-unused buildOrderByClause and hasActiveSort - DataGrid: sortedRows drives rendering, row actions, copy/export and the status bar; unsortable headers and menu entries explain why - QueryResultPanel: filters still round-trip, sorting no longer re-executes - DataTableView: header sorting with indicators, scoped to the fetched page - cover the comparator and sort state with unit tests Backend `execute_sorted_query` keeps its sort field; the frontend now always sends an empty list, so no Rust change is required.
Blankll
added a commit
that referenced
this pull request
Oct 8, 2026
Integrate the kind-precedence design into the #171 implementation. Per-cell kind resolution (resolveSortKind on every comparison) breaks two ways on columns holding mixed primitives — routine on SQLite: - the comparator flips kind between passes, so order is unstable - resolveSortKind inspected only the LEFT value, making compare(a,b) !== -compare(b,a) — an asymmetric comparator Kind is now resolved once per column: the declared type wins outright; without one, the highest-precedence kind present is used (number → boolean → date → string; strings are never date-sniffed). NULL ordering, non-primitive column exclusion and the coercers from #171 are unchanged.
2 tasks done
Blankll
added a commit
that referenced
this pull request
Oct 8, 2026
Integrate the kind-precedence design into the #171 implementation. Per-cell kind resolution (resolveSortKind on every comparison) breaks two ways on columns holding mixed primitives — routine on SQLite: - the comparator flips kind between passes, so order is unstable - resolveSortKind inspected only the LEFT value, making compare(a,b) !== -compare(b,a) — an asymmetric comparator Kind is now resolved once per column: the declared type wins outright; without one, the highest-precedence kind present is used (number → boolean → date → string; strings are never date-sniffed). NULL ordering, non-primitive column exclusion and the coercers from #171 are unchanged.
Blankll
added a commit
that referenced
this pull request
Oct 8, 2026
Integrate the kind-precedence design into the #171 implementation. Per-cell kind resolution (resolveSortKind on every comparison) breaks two ways on columns holding mixed primitives — routine on SQLite: - the comparator flips kind between passes, so order is unstable - resolveSortKind inspected only the LEFT value, making compare(a,b) !== -compare(b,a) — an asymmetric comparator Kind is now resolved once per column: the declared type wins outright; without one, the highest-precedence kind present is used (number → boolean → date → string; strings are never date-sniffed). NULL ordering, non-primitive column exclusion and the coercers from #171 are unchanged.
Blankll
added a commit
that referenced
this pull request
Oct 8, 2026
Integrate the kind-precedence design into the #171 implementation. Per-cell kind resolution (resolveSortKind on every comparison) breaks two ways on columns holding mixed primitives — routine on SQLite: - the comparator flips kind between passes, so order is unstable - resolveSortKind inspected only the LEFT value, making compare(a,b) !== -compare(b,a) — an asymmetric comparator Kind is now resolved once per column: the declared type wins outright; without one, the highest-precedence kind present is used (number → boolean → date → string; strings are never date-sniffed). NULL ordering, non-primitive column exclusion and the coercers from #171 are unchanged.
Blankll
added a commit
that referenced
this pull request
Oct 8, 2026
Integrate the kind-precedence design into the #171 implementation. Per-cell kind resolution (resolveSortKind on every comparison) breaks two ways on columns holding mixed primitives — routine on SQLite: - the comparator flips kind between passes, so order is unstable - resolveSortKind inspected only the LEFT value, making compare(a,b) !== -compare(b,a) — an asymmetric comparator Kind is now resolved once per column: the declared type wins outright; without one, the highest-precedence kind present is used (number → boolean → date → string; strings are never date-sniffed). NULL ordering, non-primitive column exclusion and the coercers from #171 are unchanged.
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.
Problem
Result grids could not reliably sort by a column:
DataGrid(query results) rendered sort UI, but a header click emittedsortChangeandQueryResultPanelre-ran the user's SQL throughexecute_sorted_query, wrapping it asSELECT * FROM (…) ORDER BY …. That only works for plain parsableSELECTs, re-runs potentially expensive queries on every click, and fails outright on anything sqlparser cannot parse.DataTableView(table data browser) had no sort support at all.Solution
Sorting now happens in the frontend, over the rows already returned, and is limited to primitive columns (text / number / boolean / date / NULL). JSON documents, arrays and BLOBs stay unsortable — they have no meaningful client-side total ordering.
Sortability — a column qualifies only if its declared type is primitive (not
JSON/ARRAY/BYTEA/BLOB/BINARY/…) and every returned cell is primitive. One object/array cell disqualifies the whole column. Unsortable headers render as plain labels with a tooltip, and the header context menu disables its sort entries and explains why.Comparison — number / boolean / date / string, preferring the declared column type (so
bigintreturned as a string still compares numerically). Dates compare by parsed timestamp. Strings uselocaleCompare(…, { numeric: true, sensitivity: 'base' }). NULLs sort last ascending; descending is a pure reversal, matching PostgreSQL.Multi-column sort — plain click cycles ASC → DESC → cleared; shift-click appends (ASC → DESC → removed). Equal rows keep their original order and the input array is never mutated.
Changes
src/utils/gridSort.tssortRowsByState,formatSortStateuseDataGridSortsetSort/pruneSort; removes now-unusedbuildOrderByClauseandhasActiveSortDataGrid.vuesortedRowsis the single source of truth for rendering, row actions, copy/export and the status bar; selection is cleared when rows/columns/sort change (selection is positional)QueryResultPanel.vueDataTableView.vuesortedRows; sort resets on table/connection changeColumnHeaderContextMenu.vuesortableprop disables sort entries for non-primitive columnscomponents.dataGrid.sort.unsortableinenUS/zhCNdocs/GRID_SORT_DESIGN.mdKnown limitations
get_table_datapaginates server-side (100/500/1000 rows per page), so only the fetched page is reordered. Cross-page sorting would needORDER BYpushed into that command — intentionally out of scope here.execute_sorted_querycommand keeps itssortfield (the frontend now always sends an empty list), so no backend change is required and the API stays compatible.Testing
src/__tests__/gridSort.test.ts— sortability detection, per-kind comparison, NULL placement, multi-column priority, stability, immutability.src/__tests__/useDataGridSort.test.ts— click/shift cycling,setSort,pruneSort.vue-tsc --noEmitandeslint ./clean.