From f64fd09b7aae285832188d9345ef59fb34b608cb Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Wed, 3 Jun 2026 21:04:19 +0800 Subject: [PATCH] fix(grid): keep table filters available without metadata --- .../src/components/layout/ContentArea.vue | 7 +- .../src/composables/useDataGridActions.ts | 20 +++-- apps/desktop/src/lib/tableDataTabMeta.ts | 28 +++++++ packages/app-tests/tableDataTabMeta.test.ts | 81 +++++++++++++++++++ 4 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 apps/desktop/src/lib/tableDataTabMeta.ts create mode 100644 packages/app-tests/tableDataTabMeta.test.ts diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index f848536e37..ae49e18737 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -41,6 +41,7 @@ import { useQueryStore } from "@/stores/queryStore"; import { canCancelQueryExecution, queryExecutionLabelKey } from "@/lib/queryExecutionState"; import { databaseDisplayNameForTab } from "@/lib/tabPresentation"; import { isTableDataEditable } from "@/lib/tableEditing"; +import { tableMetaForDataTab } from "@/lib/tableDataTabMeta"; import type { QueryTab, ConnectionConfig } from "@/types/database"; import type { SqlFormatDialect } from "@/lib/sqlFormatter"; @@ -116,6 +117,8 @@ const columnVisibilityOptions = computed( ); const redisKeyBrowserRef = ref(); const objectBrowserRef = ref(); +const activeTableMeta = computed(() => props.activeTab.tableMeta); +const activeDataTabTableMeta = computed(() => tableMetaForDataTab(props.activeTab)); const activeSqlFormatDialect = computed(() => { switch (props.activeConnection?.db_type) { @@ -766,13 +769,13 @@ defineExpose({ focusSearch, refreshData, handleModRTarget }); :initial-order-by-input="activeTab.orderByInput" :sql="activeTab.sql" :loading="activeTab.isExecuting" - :editable="isTableDataEditable(activeConnection?.db_type, activeTab.tableMeta?.primaryKeys ?? [])" + :editable="isTableDataEditable(activeConnection?.db_type, activeTableMeta?.primaryKeys ?? [])" context="table-data" :initial-where-input="activeTab.whereInput" :database-type="activeConnection?.db_type" :connection-id="activeTab.connectionId" :database="activeTab.database" - :table-meta="activeTab.tableMeta" + :table-meta="activeDataTabTableMeta" :page-offset="activeTab.resultPageOffset" :page-limit="activeTab.resultPageLimit" :on-execute-sql="async (sql: string) => emit('executeSql', sql)" diff --git a/apps/desktop/src/composables/useDataGridActions.ts b/apps/desktop/src/composables/useDataGridActions.ts index ad647a8f91..b292f3b038 100644 --- a/apps/desktop/src/composables/useDataGridActions.ts +++ b/apps/desktop/src/composables/useDataGridActions.ts @@ -5,6 +5,7 @@ import { useQueryStore } from "@/stores/queryStore"; import { useSettingsStore } from "@/stores/settingsStore"; import { buildTableSelectSql, quoteTableIdentifier } from "@/lib/tableSelectSql"; import { editablePrimaryKeys, usesSyntheticRowIdKey } from "@/lib/tableEditing"; +import { tableMetaForDataTab } from "@/lib/tableDataTabMeta"; import * as api from "@/lib/api"; import type { QueryTab } from "@/types/database"; import { useToast } from "@/composables/useToast"; @@ -26,20 +27,23 @@ export function useDataGridActions(activeTab: ComputedRef) options: { orderBy?: string; limit?: number; offset?: number; whereInput?: string } = {}, ): Promise { const config = connectionStore.getConfig(tab.connectionId); - const primaryKeys = tab.tableMeta ? editablePrimaryKeys(config?.db_type, tab.tableMeta.columns) : []; + const tableMeta = tableMetaForDataTab(tab); + const primaryKeys = tab.tableMeta + ? editablePrimaryKeys(config?.db_type, tab.tableMeta.columns) + : (tableMeta?.primaryKeys ?? []); if (tab.tableMeta && primaryKeys.join("\0") !== tab.tableMeta.primaryKeys.join("\0")) { tab.tableMeta.primaryKeys = primaryKeys; } const fallbackOrderColumns = config?.db_type === "sqlserver" && !primaryKeys.length - ? tab.tableMeta?.columns.slice(0, 1).map((column) => column.name) + ? tableMeta?.columns.slice(0, 1).map((column) => column.name) : undefined; const useRowId = usesSyntheticRowIdKey(config?.db_type, primaryKeys); return buildTableSelectSql({ databaseType: config?.db_type, - schema: tab.tableMeta?.schema, - tableName: tab.tableMeta?.tableName ?? "", - columns: tab.tableMeta?.columns.map((column) => column.name), + schema: tableMeta?.schema, + tableName: tableMeta?.tableName ?? "", + columns: tableMeta?.columns.map((column) => column.name), primaryKeys, fallbackOrderColumns, includeRowId: useRowId, @@ -65,7 +69,7 @@ export function useDataGridActions(activeTab: ComputedRef) ) { const tab = activeTab.value; if (!tab) return; - if (tab.mode === "data" && tab.tableMeta) { + if (tab.mode === "data" && tableMetaForDataTab(tab)) { tab.whereInput = whereInput ?? ""; const pageLimit = limit ?? settingsStore.editorSettings.pageSize; const pageOffset = offset ?? 0; @@ -116,7 +120,7 @@ export function useDataGridActions(activeTab: ComputedRef) return; } - if (!tab.tableMeta) return; + if (!tableMetaForDataTab(tab)) return; tab.whereInput = whereInput ?? ""; const sql = await buildTableSql(tab, { limit, offset, whereInput, orderBy }); queryStore.updateSql(tab.id, sql); @@ -134,7 +138,7 @@ export function useDataGridActions(activeTab: ComputedRef) tab.resultSortDirection = direction ?? undefined; if (tab.mode === "data") { - if (!tab.tableMeta) return; + if (!tableMetaForDataTab(tab)) return; tab.whereInput = whereInput ?? ""; const config = connectionStore.getConfig(tab.connectionId); const quotedColumn = quoteIdent(tab, column); diff --git a/apps/desktop/src/lib/tableDataTabMeta.ts b/apps/desktop/src/lib/tableDataTabMeta.ts new file mode 100644 index 0000000000..b9e44cc536 --- /dev/null +++ b/apps/desktop/src/lib/tableDataTabMeta.ts @@ -0,0 +1,28 @@ +import type { QueryTab, ColumnInfo } from "@/types/database"; + +export type DataTabTableMeta = NonNullable; + +function fallbackColumnInfo(name: string): ColumnInfo { + return { + name, + data_type: "", + is_nullable: true, + column_default: null, + is_primary_key: false, + extra: null, + }; +} + +export function tableMetaForDataTab(tab: QueryTab | undefined): DataTabTableMeta | undefined { + if (!tab || tab.mode !== "data") return undefined; + if (tab.tableMeta) return tab.tableMeta; + const tableName = tab.title.trim(); + if (!tableName) return undefined; + + return { + schema: tab.schema, + tableName, + columns: (tab.result?.columns ?? []).map(fallbackColumnInfo), + primaryKeys: [], + }; +} diff --git a/packages/app-tests/tableDataTabMeta.test.ts b/packages/app-tests/tableDataTabMeta.test.ts new file mode 100644 index 0000000000..ec9cc05ab4 --- /dev/null +++ b/packages/app-tests/tableDataTabMeta.test.ts @@ -0,0 +1,81 @@ +import { strict as assert } from "node:assert"; +import test from "node:test"; +import { tableMetaForDataTab } from "../../apps/desktop/src/lib/tableDataTabMeta.ts"; +import type { QueryTab } from "../../apps/desktop/src/types/database.ts"; + +function tab(overrides: Partial = {}): QueryTab { + return { + id: "tab-1", + title: "users", + connectionId: "conn-1", + database: "app", + schema: "public", + sql: "select * from users", + isExecuting: false, + isCancelling: false, + isExplaining: false, + mode: "data", + ...overrides, + }; +} + +test("returns persisted table metadata for a data tab", () => { + const tableMeta = { + schema: "public", + tableName: "users", + columns: [ + { + name: "id", + data_type: "integer", + is_nullable: false, + column_default: null, + is_primary_key: true, + extra: null, + }, + ], + primaryKeys: ["id"], + }; + + assert.equal(tableMetaForDataTab(tab({ tableMeta })), tableMeta); +}); + +test("builds fallback metadata from a data tab when column metadata is unavailable", () => { + const meta = tableMetaForDataTab( + tab({ + result: { + columns: ["id", "name"], + rows: [], + affected_rows: 0, + execution_time_ms: 1, + }, + }), + ); + + assert.deepEqual(meta, { + schema: "public", + tableName: "users", + columns: [ + { + name: "id", + data_type: "", + is_nullable: true, + column_default: null, + is_primary_key: false, + extra: null, + }, + { + name: "name", + data_type: "", + is_nullable: true, + column_default: null, + is_primary_key: false, + extra: null, + }, + ], + primaryKeys: [], + }); +}); + +test("does not infer table metadata for query tabs", () => { + assert.equal(tableMetaForDataTab(tab({ mode: "query" })), undefined); +});