feat(grid): show column data types in result header (#669) (#820)

The data grid header only showed column names, and column types were
available only via tableMeta (open-table view). Arbitrary query results
(e.g. `select * from pg_depend`) therefore showed no type at all, which
is exactly the case the reporter hit.

Backend: add `column_types` to QueryResult (serde-default, backward
compatible) and populate it for the native drivers where the type is
readily available — PostgreSQL, MySQL, SQL Server, ClickHouse. Other
drivers leave it empty for now (no behavior change); schemaless stores
(Mongo/Redis/ES) have no column types.

Frontend: render a type row under each column name in the grid header,
color-coded by type. The type is resolved from tableMeta first (richer,
includes precision) and falls back to the query result's column_types by
index. Add a `showColumnTypesInHeader` setting (default on) and keep the
msgpack tab-result cache compatible. The source-selection logic is
extracted to lib/dataGridColumnType.ts with unit tests.

Co-authored-by: vrustx <vrustx@vrustxdeMac-mini.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-06-07 22:45:46 +08:00
1 parent 3b457f7803
commit 54de28d583
23 files changed
+284 -2

No files matched your search

@@ -116,6 +116,7 @@ const editAppLayout = ref(settingsStore.editorSettings.appLayout);
const editShowTrayIcon = ref(settingsStore.desktopSettings.show_tray_icon);
const editIconTheme = ref<DesktopIconTheme>(settingsStore.desktopSettings.icon_theme);
const editShowColumnCommentsInHeader = ref(settingsStore.editorSettings.showColumnCommentsInHeader);
const editShowColumnTypesInHeader = ref(settingsStore.editorSettings.showColumnTypesInHeader);
const editCompactColumnHeaderActions = ref(settingsStore.editorSettings.compactColumnHeaderActions);
const editRedisScanPageSize = ref(settingsStore.editorSettings.redisScanPageSize);
const editShortcuts = ref(normalizeShortcutSettings(settingsStore.editorSettings.shortcuts));
@@ -273,6 +274,7 @@ watch(
editShowTrayIcon.value = settingsStore.desktopSettings.show_tray_icon;
editIconTheme.value = settingsStore.desktopSettings.icon_theme;
editShowColumnCommentsInHeader.value = settingsStore.editorSettings.showColumnCommentsInHeader;
editShowColumnTypesInHeader.value = settingsStore.editorSettings.showColumnTypesInHeader;
editCompactColumnHeaderActions.value = settingsStore.editorSettings.compactColumnHeaderActions;
editRedisScanPageSize.value = settingsStore.editorSettings.redisScanPageSize;
editShortcuts.value = normalizeShortcutSettings(settingsStore.editorSettings.shortcuts);
@@ -320,6 +322,7 @@ function hasChanges(): boolean {
editShowTrayIcon.value !== settingsStore.desktopSettings.show_tray_icon ||
editIconTheme.value !== settingsStore.desktopSettings.icon_theme ||
editShowColumnCommentsInHeader.value !== settingsStore.editorSettings.showColumnCommentsInHeader ||
editShowColumnTypesInHeader.value !== settingsStore.editorSettings.showColumnTypesInHeader ||
editCompactColumnHeaderActions.value !== settingsStore.editorSettings.compactColumnHeaderActions ||
editRedisScanPageSize.value !== settingsStore.editorSettings.redisScanPageSize ||
JSON.stringify(editShortcuts.value) !== JSON.stringify(settingsStore.editorSettings.shortcuts) ||
@@ -354,6 +357,7 @@ async function persistSettings() {
confirmDangerousSqlExecution: editConfirmDangerousSqlExecution.value,
appLayout: editAppLayout.value,
showColumnCommentsInHeader: editShowColumnCommentsInHeader.value,
showColumnTypesInHeader: editShowColumnTypesInHeader.value,
compactColumnHeaderActions: editCompactColumnHeaderActions.value,
redisScanPageSize: editRedisScanPageSize.value,
shortcuts: editShortcuts.value,
@@ -401,6 +405,7 @@ function resetDefaults() {
editShowTrayIcon.value = DEFAULT_DESKTOP_SETTINGS.show_tray_icon;
editIconTheme.value = DEFAULT_DESKTOP_SETTINGS.icon_theme;
editShowColumnCommentsInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader;
editShowColumnTypesInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnTypesInHeader;
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
editRedisScanPageSize.value = DEFAULT_EDITOR_SETTINGS.redisScanPageSize;
editShortcuts.value = normalizeShortcutSettings(DEFAULT_EDITOR_SETTINGS.shortcuts);
@@ -1569,6 +1574,17 @@ watch(
</div>
<Switch id="show-column-comments-in-header" v-model="editShowColumnCommentsInHeader" />
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="show-column-types-in-header">
{{ t("settings.showColumnTypesInHeader") }}
</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.showColumnTypesInHeaderDescription") }}
</p>
</div>
<Switch id="show-column-types-in-header" v-model="editShowColumnTypesInHeader" />
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="compact-column-header-actions">
@@ -83,6 +83,7 @@ import { createColumnDrafts } from "@/lib/tableStructureEditorState";
import type { BuildSingleColumnAlterSqlOptions } from "@/lib/tableStructureEditorSql";
import { buildTableSelectSql, quoteTableIdentifier } from "@/lib/tableSelectSql";
import { uuid } from "@/lib/utils";
import { resolveHeaderColumnType } from "@/lib/dataGridColumnType";
import {
canEditExistingTableRows,
hiveTablePropertiesIndicateTransactional,
@@ -341,6 +342,7 @@ const columnCommentMap = computed(() => {
return map;
});
const showColumnCommentsInHeader = computed(() => settingsStore.editorSettings.showColumnCommentsInHeader);
const showColumnTypesInHeader = computed(() => settingsStore.editorSettings.showColumnTypesInHeader);
const compactColumnHeaderActions = computed(() => settingsStore.editorSettings.compactColumnHeaderActions);
const dataGridRenderMode = computed(() => settingsStore.editorSettings.dataGridRenderMode);
@@ -349,6 +351,16 @@ function headerColumnComment(column: string): string {
return columnCommentMap.value.get(column) || "";
}
function headerColumnType(column: string, actualColIdx: number): string {
if (!showColumnTypesInHeader.value) return "";
const resolved = resolveHeaderColumnType({
tableColumnType: columnTypeMap.value.get(column),
resultColumnTypes: props.result.column_types,
actualColIdx,
});
return resolved ? shortTypeName(resolved) : "";
}
function shortTypeName(t: string): string {
const s = t.toLowerCase();
if (s === "character varying") return "varchar";
@@ -6505,6 +6517,14 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
<span class="min-w-0 truncate leading-4">
{{ col.name }}
</span>
<span
v-if="headerColumnType(col.name, col.actualColIdx)"
class="min-w-0 truncate text-[10px] font-normal leading-3"
:class="typeColorClass(headerColumnType(col.name, col.actualColIdx))"
:title="headerColumnType(col.name, col.actualColIdx)"
>
{{ headerColumnType(col.name, col.actualColIdx) }}
</span>
<span
v-if="headerColumnComment(col.name)"
class="min-w-0 truncate text-[10px] font-normal leading-3 text-muted-foreground"