From 26bb10dad02640cc8d079be1af99dfbe70fdc82a Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 12 Aug 2026 02:05:54 +0800 Subject: [PATCH] fix(gaussdb): support M-mode type metadata --- .../com/dbx/agent/ConfiguredJdbcAgent.java | 2 +- .../com/dbx/agent/StandardJdbcMetadata.java | 20 +- .../dbx/agent/StandardJdbcMetadataTest.java | 76 +- agents/drivers/tdengine/tests/live.rs | 2 +- agents/metadata-constraint-coverage.tsv | 1 + .../src/components/editor/ColumnInfoPanel.vue | 18 +- apps/desktop/src/components/grid/DataGrid.vue | 858 +++++++++++++++--- .../src/components/layout/ContentArea.vue | 10 +- .../src/components/objects/ObjectBrowser.vue | 11 +- .../structure/TableStructureEditor.vue | 15 +- .../useSidebarTreeExportRuntime.ts | 9 +- .../src/lib/table/postgresDataTypeHelp.ts | 23 + apps/desktop/src/stores/connectionStore.ts | 5 +- 13 files changed, 904 insertions(+), 146 deletions(-) diff --git a/agents/common/src/main/java/com/dbx/agent/ConfiguredJdbcAgent.java b/agents/common/src/main/java/com/dbx/agent/ConfiguredJdbcAgent.java index fbf1f5eca3..dfbb04824b 100644 --- a/agents/common/src/main/java/com/dbx/agent/ConfiguredJdbcAgent.java +++ b/agents/common/src/main/java/com/dbx/agent/ConfiguredJdbcAgent.java @@ -115,7 +115,7 @@ public abstract class ConfiguredJdbcAgent extends AbstractJdbcAgent { @Override public List listIndexes(String schema, String table) { - return StandardJdbcMetadata.INSTANCE.listIndexes(requireConnection(), schema, table); + return StandardJdbcMetadata.INSTANCE.listIndexes(requireConnection(), profile, configuredDatabase, schema, table); } @Override diff --git a/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java b/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java index bbf7887615..8edf4598c9 100644 --- a/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java +++ b/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java @@ -207,12 +207,28 @@ public final class StandardJdbcMetadata { }); } - public List listIndexes(Connection conn, String schema, String table) { + public List listIndexes( + Connection conn, + JdbcAgentProfile profile, + String configuredDatabase, + String schema, + String table + ) { + return unchecked(() -> { + List result = listIndexesInternal(conn, null, schema, table); + if (result.isEmpty() && profile.getCatalogFallbackEnabled() && hasConfiguredDatabase(configuredDatabase)) { + result = listIndexesInternal(conn, configuredDatabase, schema, table); + } + return result; + }); + } + + private List listIndexesInternal(Connection conn, String catalog, String schema, String table) { return unchecked(() -> { DatabaseMetaData meta = conn.getMetaData(); Map> indexes = new LinkedHashMap<>(); Map unique = new LinkedHashMap<>(); - try (ResultSet rs = meta.getIndexInfo(null, blankToNull(schema), table, false, false)) { + try (ResultSet rs = meta.getIndexInfo(blankToNull(catalog), blankToNull(schema), table, false, false)) { while (rs.next()) { String name = rs.getString("INDEX_NAME"); String column = rs.getString("COLUMN_NAME"); diff --git a/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java b/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java index f3fdb090c2..15e19506ab 100644 --- a/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java +++ b/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java @@ -419,13 +419,62 @@ class StandardJdbcMetadataTest { rows() ); - List indexes = StandardJdbcMetadata.INSTANCE.listIndexes(conn, "APP", "ORDERS"); + List indexes = StandardJdbcMetadata.INSTANCE.listIndexes(conn, profile, null, "APP", "ORDERS"); assertEquals(1, indexes.size()); assertEquals(Arrays.asList("A", "B"), indexes.get(0).getColumns()); assertFalse(indexes.get(0).getIs_unique()); } + @Test + void listIndexesUsesConfiguredCatalogOnlyWhenProfileAllowsFallback() { + Connection enabledConn = catalogIndexFallbackConnection( + "TENANTDB", + rows(), + rows(row("INDEX_NAME", "IDX_ORDERS", "COLUMN_NAME", "ID", "ORDINAL_POSITION", (short) 1, "NON_UNIQUE", false)) + ); + + List enabledIndexes = StandardJdbcMetadata.INSTANCE.listIndexes( + enabledConn, + profile, + "TENANTDB", + "APP", + "ORDERS" + ); + + assertEquals(1, enabledIndexes.size()); + + JdbcAgentProfile fallbackDisabledProfile = new JdbcAgentProfile( + "example.Driver", + "jdbc:example://{host}:{port}/{database}", + 0, + false, + Collections.emptySet(), + Collections.singletonList("TABLE"), + "\"", + "SET SCHEMA", + false, + false, + false, + false + ); + Connection disabledConn = catalogIndexFallbackConnection( + "TENANTDB", + rows(), + rows(row("INDEX_NAME", "IDX_ORDERS", "COLUMN_NAME", "ID", "ORDINAL_POSITION", (short) 1, "NON_UNIQUE", false)) + ); + + List disabledIndexes = StandardJdbcMetadata.INSTANCE.listIndexes( + disabledConn, + fallbackDisabledProfile, + "TENANTDB", + "APP", + "ORDERS" + ); + + assertTrue(disabledIndexes.isEmpty()); + } + @Test void mapsForeignKeysAndEmptyTriggers() { Connection conn = connection( @@ -667,6 +716,31 @@ class StandardJdbcMetadataTest { }); } + private static Connection catalogIndexFallbackConnection( + String fallbackCatalog, + ResultSet defaultIndexes, + ResultSet fallbackIndexes + ) { + DatabaseMetaData meta = proxy(DatabaseMetaData.class, new MethodHandler() { + @Override + public Object handle(Method method, Object[] args) { + if ("getIndexInfo".equals(method.getName())) { + return fallbackCatalog.equals(args[0]) ? fallbackIndexes : defaultIndexes; + } + return defaultValue(method.getReturnType()); + } + }); + return proxy(Connection.class, new MethodHandler() { + @Override + public Object handle(Method method, Object[] args) { + if ("getMetaData".equals(method.getName())) { + return meta; + } + return defaultValue(method.getReturnType()); + } + }); + } + private static Connection catalogPrimaryKeyFallbackConnection( String fallbackCatalog, ResultSet defaultPrimaryKeys, diff --git a/agents/drivers/tdengine/tests/live.rs b/agents/drivers/tdengine/tests/live.rs index 7e91d660e7..7d6acf4b6b 100644 --- a/agents/drivers/tdengine/tests/live.rs +++ b/agents/drivers/tdengine/tests/live.rs @@ -90,7 +90,7 @@ fn tdengine_websocket_live_compatibility() { format!("CREATE DATABASE IF NOT EXISTS {database}"), format!("CREATE STABLE IF NOT EXISTS {database}.meters (ts TIMESTAMP, current FLOAT, voltage INT, active BOOL, note NCHAR(32)) TAGS (location NCHAR(64), group_id INT)"), format!("CREATE TABLE IF NOT EXISTS {database}.d1001 USING {database}.meters TAGS ('California.SanFrancisco', 2)"), - format!("INSERT INTO {database}.d1001 VALUES ('2026-08-04 10:00:00.001', 10.1, 220, true, 'first') ('2026-08-04 10:00:00.002', 10.2, 221, false, 'second') ('2026-08-04 10:00:00.003', 10.3, 222, true, 'third')") + format!("INSERT INTO {database}.d1001 VALUES (1785808800001, 10.1, 220, true, 'first') (1785808800002, 10.2, 221, false, 'second') (1785808800003, 10.3, 222, true, 'third')") ] }), ); diff --git a/agents/metadata-constraint-coverage.tsv b/agents/metadata-constraint-coverage.tsv index 8c13e2d854..2010fb3838 100644 --- a/agents/metadata-constraint-coverage.tsv +++ b/agents/metadata-constraint-coverage.tsv @@ -10,6 +10,7 @@ gbase8s native-pushdown java-sql Uses systables with type, filter, stable order, goldendb native-pushdown java-sql Uses information_schema with type, filter, stable order, and LIMIT/OFFSET. h2 native-pushdown java-sql Uses INFORMATION_SCHEMA with type, filter, stable order, and LIMIT/OFFSET. hive intentional-fallback java-sql Uses Hive metadata paths where stable server-side fuzzy paging is not portable, common constraints filter locally. +hive-go shared-fallback native-go Uses HiveServer2 metadata APIs, then applies stable filtering and paging in the native agent with a SHOW TABLES fallback. informix native-pushdown java-sql Uses systables/sysprocedures with type, filter, stable order, and SKIP/FIRST. iotdb shared-fallback native-go Uses SHOW DEVICES or SHOW TABLES DETAILS for Tree/Table metadata, then applies stable filtering and paging in the native agent. kingbase-go shared-fallback native-go Uses sys_catalog/information_schema for compatibility-aware metadata, then applies stable filtering and paging in the native agent. diff --git a/apps/desktop/src/components/editor/ColumnInfoPanel.vue b/apps/desktop/src/components/editor/ColumnInfoPanel.vue index 1c271ef0d6..0d5463f67e 100644 --- a/apps/desktop/src/components/editor/ColumnInfoPanel.vue +++ b/apps/desktop/src/components/editor/ColumnInfoPanel.vue @@ -5,6 +5,8 @@ import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { useToast } from "@/composables/useToast"; import { copyToClipboard } from "@/lib/common/clipboard"; +import { gaussdbMTypeDisplayName } from "@/lib/table/postgresDataTypeHelp"; +import type { DatabaseType } from "@/types/database"; export interface ColumnInfo { name: string; @@ -17,10 +19,12 @@ export interface ColumnInfo { extra?: string | null; } -defineProps<{ +const props = defineProps<{ columns: ColumnInfo[]; loading: boolean; error?: string; + databaseType?: DatabaseType; + isGaussdbM?: boolean; }>(); const emit = defineEmits<{ @@ -30,6 +34,14 @@ const emit = defineEmits<{ const { t } = useI18n(); const { toast } = useToast(); +function displayDataType(dataType: string | undefined): string { + if (!dataType) return "—"; + if (props.databaseType === "gaussdb" && props.isGaussdbM) { + return gaussdbMTypeDisplayName(dataType); + } + return dataType; +} + async function copyText(text: string) { try { await copyToClipboard(text); @@ -81,8 +93,8 @@ async function copyText(text: string) {
{{ t("grid.columnType") }} - - {{ col.dataType || "—" }} + + {{ displayDataType(col.dataType) }}
diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index a464c6deb3..26f441eda6 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -241,6 +241,7 @@ import { import { getTableMetadataCapabilities } from "@/lib/table/tableMetadataCapabilities"; import { getTableStructureCapabilities } from "@/lib/table/tableStructureCapabilities"; import { filterObjectBrowserTableColumns } from "@/lib/table/objectBrowserTableInfo"; +import { gaussdbMTypeDisplayName } from "@/lib/table/postgresDataTypeHelp"; import { reserveDataGridHeaderLine } from "@/lib/dataGrid/dataGridHeaderLayout"; import { buildColumnIndexMap, columnIndexMetadataRequestCurrent, columnIndexNameKey, columnIndexTableIdentity } from "@/lib/dataGrid/dataGridColumnIndexIcon"; import { supportsTableStructureEditing } from "@/lib/database/databaseCapabilities"; @@ -357,7 +358,11 @@ interface DataGridProps { onExecuteSql?: (sql: string) => Promise; fullExportResult?: (onProgress?: (info: { rowsExported: number; totalRows: number | null }) => void) => Promise; queryResultExportRequest?: (options: { exportId: string; filePath: string; format: "csv" | "xlsx" | "txt" | "sql"; includeSqlSheet?: boolean; exportTableName?: string; exportColumnTypes?: Array }) => Promise; - allExportResults?: Array<{ sheetName: string; result: QueryResult; sql?: string }>; + allExportResults?: Array<{ + sheetName: string; + result: QueryResult; + sql?: string; + }>; exportFileBaseName?: string; customSaveHandler?: import("@/composables/useDataGridEditor").CustomSaveHandler; mongoUpdateTarget?: MongoCopyUpdateTarget; @@ -403,7 +408,10 @@ const emit = defineEmits<{ "local-column-filters-change": [value: Record]; }>(); -const autoRefresh = useDataGridAutoRefresh({ canRefresh: computed(() => !isSaving.value && !props.loading), refresh: onToolbarRefresh }); +const autoRefresh = useDataGridAutoRefresh({ + canRefresh: computed(() => !isSaving.value && !props.loading), + refresh: onToolbarRefresh, +}); const autoRefreshIntervalSeconds = autoRefresh.intervalSeconds; const autoRefreshEnabled = autoRefresh.enabled; const autoRefreshLabel = computed(() => (autoRefreshEnabled.value ? t("tabs.autoRefreshEvery", { seconds: autoRefreshIntervalSeconds.value }) : t("tabs.autoRefresh"))); @@ -422,7 +430,10 @@ if (isDebugLoggingEnabled()) { const transposeRowIndex = ref(null); const showTranspose = ref(false); const preserveTransposeOnNextResult = ref(false); -let preservedSelectionOnNextResult: { selection: PersistedDataGridSelection; sourceResult: QueryResult } | null = null; +let preservedSelectionOnNextResult: { + selection: PersistedDataGridSelection; + sourceResult: QueryResult; +} | null = null; watch( () => props.result, @@ -462,7 +473,15 @@ watch( watch( () => props.result, (result) => { - if (!shouldAutoTransposeSingleRow({ enabled: !!props.autoTransposeSingleRow, preserveTranspose: preserveTransposeOnNextResult.value, rowCount: result.rows.length, columnCount: result.columns.length })) return; + if ( + !shouldAutoTransposeSingleRow({ + enabled: !!props.autoTransposeSingleRow, + preserveTranspose: preserveTransposeOnNextResult.value, + rowCount: result.rows.length, + columnCount: result.columns.length, + }) + ) + return; nextTick(() => { if (props.result !== result || !props.autoTransposeSingleRow || result.rows.length !== 1 || result.columns.length <= 1) return; applyTransposeState({ showTranspose: true, transposeRowIndex: 0 }, "start"); @@ -568,9 +587,26 @@ function shortTypeName(t: string): string { if (s === "integer") return "int"; if (s === "smallint") return "int2"; if (s === "real") return "float4"; + // GaussDB M mode uses MySQL-style type names + if (resolvedDatabaseType.value === "gaussdb") { + const conn = resolvedConnectionConfig.value; + if (conn?.driver_profile?.toLowerCase() === "gaussdb-m") { + return gaussdbMTypeDisplayName(t); + } + } return t; } +function gaussdbMColumnType(dataType: string): string { + if (resolvedDatabaseType.value === "gaussdb") { + const conn = resolvedConnectionConfig.value; + if (conn?.driver_profile?.toLowerCase() === "gaussdb-m") { + return gaussdbMTypeDisplayName(dataType); + } + } + return dataType; +} + function headerColumnSortable(actualColIdx: number): boolean { const resolved = props.result.column_sortables?.[actualColIdx]; return resolved !== undefined ? resolved : true; @@ -581,7 +617,12 @@ function columnIsSorted(column: string, columnIndex: number): boolean { } function currentColumnSortState(): DataGridColumnSortState { - return { column: sortCol.value, columnIndex: sortColIndex.value, direction: sortDir.value, mode: sortMode.value }; + return { + column: sortCol.value, + columnIndex: sortColIndex.value, + direction: sortDir.value, + mode: sortMode.value, + }; } function sortMenuItems(column: string, columnIndex: number) { @@ -596,7 +637,12 @@ function sortMenuItems(column: string, columnIndex: number) { currentPageDescending: t("grid.sortCurrentPageDescending"), clear: t("grid.clearSort"), }, - icons: { database: Database, ascending: ArrowUp, descending: ArrowDown, clear: Eraser }, + icons: { + database: Database, + ascending: ArrowUp, + descending: ArrowDown, + clear: Eraser, + }, }); } @@ -623,7 +669,11 @@ function typeColorClass(t: string): string { if (["geometry", "geography", "sdo_geometry"].includes(base)) return "text-emerald-500"; return "text-muted-foreground"; } -const contextCell = ref<{ rowId: number; rowIndex: number; col: number } | null>(null); +const contextCell = ref<{ + rowId: number; + rowIndex: number; + col: number; +} | null>(null); // True when onCellContext created a synthetic single-cell selection (the cell // was not previously selected). Lets buildRequest distinguish a genuine 1×1 // selection (user Ctrl+click) from a synthetic one (right-click on unselected). @@ -672,7 +722,10 @@ const cellDetailDialogMounted = useDataGridAsyncSurface(cellDetailDialogOpen); const detailDialogsMounted = useDataGridAsyncSurface(computed(() => rowDetailDialogOpen.value || columnDetailDialogOpen.value)); const imagePreviewMounted = useDataGridAsyncSurface(imagePreviewOpen); const previewDialogOpen = ref(false); -const previewDialogConfig = shallowRef<{ component: any; props: Record } | null>(null); +const previewDialogConfig = shallowRef<{ + component: any; + props: Record; +} | null>(null); const transposeScrollRef = ref(); const transposeScrollLeft = ref(0); const transposeViewportWidth = ref(0); @@ -742,9 +795,15 @@ const allFilterModeOptions: Array<{ value: FilterMode; labelKey: string }> = [ { value: "like", labelKey: "grid.filterBuilderContains" }, { value: "not-like", labelKey: "grid.filterBuilderNotContains" }, { value: "greater-than", labelKey: "grid.filterBuilderGreaterThan" }, - { value: "greater-than-or-equal", labelKey: "grid.filterBuilderGreaterThanOrEqual" }, + { + value: "greater-than-or-equal", + labelKey: "grid.filterBuilderGreaterThanOrEqual", + }, { value: "less-than", labelKey: "grid.filterBuilderLessThan" }, - { value: "less-than-or-equal", labelKey: "grid.filterBuilderLessThanOrEqual" }, + { + value: "less-than-or-equal", + labelKey: "grid.filterBuilderLessThanOrEqual", + }, { value: "in", labelKey: "grid.filterBuilderIn" }, { value: "not-in", labelKey: "grid.filterBuilderNotIn" }, { value: "between", labelKey: "grid.filterBuilderBetween" }, @@ -832,7 +891,10 @@ const localFilterSummaries = computed(() => .map(([columnIndexText, selected]) => { const columnIndex = Number(columnIndexText); const labelByKey = new Map(buildLocalFilterOptions(columnIndex).map((option) => [option.key, option.label])); - return { columnIndex, values: [...selected].map((key) => labelByKey.get(key) ?? key) }; + return { + columnIndex, + values: [...selected].map((key) => labelByKey.get(key) ?? key), + }; }), ...Object.entries(serverColumnFilters.value).map(([columnIndexText, filter]) => ({ columnIndex: Number(columnIndexText), @@ -875,7 +937,12 @@ function buildLocalFilterOptions(columnIndex: number): LocalFilterOption[] { if (current) { current.count = (current.count ?? 0) + 1; } else { - byKey.set(key, { key, label: localFilterLabel(value, columnIndex), count: 1, value }); + byKey.set(key, { + key, + label: localFilterLabel(value, columnIndex), + count: 1, + value, + }); } }; @@ -889,7 +956,10 @@ function buildLocalFilterOptions(columnIndex: number): LocalFilterOption[] { return [...byKey.values()].sort((a, b) => { if (a.value === null && b.value !== null) return -1; if (a.value !== null && b.value === null) return 1; - return a.label.localeCompare(b.label, undefined, { numeric: true, sensitivity: "base" }); + return a.label.localeCompare(b.label, undefined, { + numeric: true, + sensitivity: "base", + }); }); } @@ -1070,7 +1140,11 @@ function openCompactLocalFilter(colIdx: number, mode: LocalFilterMode = "local") function compactColumnActionMenuItems(columnName: string) { return createDataGridCompactColumnActionItems({ - labels: { formatter: t("grid.columnFormatter"), localFilter: t("grid.localFilter"), serverFilter: t("grid.databaseValueFilter") }, + labels: { + formatter: t("grid.columnFormatter"), + localFilter: t("grid.localFilter"), + serverFilter: t("grid.databaseValueFilter"), + }, icons: { formatter: Code2, filter: Filter, database: Database }, formatterAvailable: !!formatterKeyForColumn(columnName), serverFilterAvailable: canUseServerColumnFilter.value, @@ -1150,13 +1224,26 @@ function currentFormatterDraft(): ColumnFormatterConfig { }; } if (formatterKind.value === "custom-template") { - return { kind: "custom-template", template: formatterCustomTemplate.value.trim() || "${value}" }; + return { + kind: "custom-template", + template: formatterCustomTemplate.value.trim() || "${value}", + }; } - return { kind: "datetime", unit: formatterDateUnit.value, pattern: formatterDatetimePattern.value, timezone: formatterDateTimezone.value }; + return { + kind: "datetime", + unit: formatterDateUnit.value, + pattern: formatterDatetimePattern.value, + timezone: formatterDateTimezone.value, + }; } function loadFormatterDraft(formatter: ColumnFormatterConfig | undefined) { - const draft = formatter ?? { kind: "datetime", unit: "auto" as const, pattern: "YYYY-MM-DD HH:mm:ss" as const, timezone: dayjs.tz.guess() }; + const draft = formatter ?? { + kind: "datetime", + unit: "auto" as const, + pattern: "YYYY-MM-DD HH:mm:ss" as const, + timezone: dayjs.tz.guess(), + }; formatterKind.value = draft.kind === "custom-ref" ? "custom-template" : draft.kind; if (draft.kind === "datetime") { formatterDateUnit.value = draft.unit; @@ -2375,7 +2462,11 @@ let infiniteScrollPositions = new WeakMap() function updateDomGridVisibleItemsDuringScroll(scroller: HTMLElement) { if (useCanvasGridRows.value || scroller !== gridScrollerElement()) return; - (scrollerRef.value as { updateVisibleItems?: (itemsChanged: boolean, checkPositionDiff?: boolean) => void } | null)?.updateVisibleItems?.(false, true); + ( + scrollerRef.value as { + updateVisibleItems?: (itemsChanged: boolean, checkPositionDiff?: boolean) => void; + } | null + )?.updateVisibleItems?.(false, true); } function markGridScrolling() { @@ -2876,7 +2967,12 @@ async function lastPage() { // Keep the busy overlay until the parent query loading flag can take over. await nextTick(); } catch (e: any) { - toast(t("grid.calculateTotalRowsFailed", { message: e?.message || String(e) }), 5000); + toast( + t("grid.calculateTotalRowsFailed", { + message: e?.message || String(e), + }), + 5000, + ); } finally { manualTotalRowCountLoading.value = false; } @@ -2918,7 +3014,10 @@ async function buildCurrentCountTarget(): Promise<{ sql: string; schema?: string tableName: props.tableMeta.tableName, whereInput: currentWhereInput(), }); - return { sql, schema: props.context === "table-data" ? undefined : (props.tableMeta.schema ?? props.schema) }; + return { + sql, + schema: props.context === "table-data" ? undefined : (props.tableMeta.schema ?? props.schema), + }; } return undefined; } @@ -3074,7 +3173,10 @@ const { isPreviewLoading, previewChanges, } = editor; -const pendingQuickEntryDraftCellFocus = ref<{ rowId: number; col: number } | null>(null); +const pendingQuickEntryDraftCellFocus = ref<{ + rowId: number; + col: number; +} | null>(null); const batchAppendPasteRowId = ref(null); const showSqlPreview = ref(false); @@ -3207,7 +3309,15 @@ function startCellEdit(rowId: number, columnIndex: number, expanded: boolean) { function startDomCellEdit(rowId: number, columnIndex: number, displayText: string, event: MouseEvent) { const item = getRowItem(rowId); const editText = item ? cellEditorTextForValue(item.data[columnIndex], columnIndex) : displayText; - startCellEdit(rowId, columnIndex, cellEditContentNeedsExpandedEditor({ displayText, editText, target: event.currentTarget })); + startCellEdit( + rowId, + columnIndex, + cellEditContentNeedsExpandedEditor({ + displayText, + editText, + target: event.currentTarget, + }), + ); } function showReadonlyCellDetailsOnDblClick(item: RowItem, rowIndex: number, visibleColIdx: number, actualColIdx: number): boolean { @@ -3511,9 +3621,24 @@ const addRowToolbarCapability = computed(() => visible: canInsertRows.value, items: [ { value: "insert-multiple", label: t("grid.insertMultipleRows") }, - { value: "position-above", label: t("grid.insertPositionAbove"), separatorBefore: true, selected: insertPosition.value === "above", disabled: !canPlaceInsertAtSelection.value }, - { value: "position-below", label: t("grid.insertPositionBelow"), selected: insertPosition.value === "below", disabled: !canPlaceInsertAtSelection.value }, - { value: "position-end", label: t("grid.insertPositionEnd"), selected: insertPosition.value === "end" }, + { + value: "position-above", + label: t("grid.insertPositionAbove"), + separatorBefore: true, + selected: insertPosition.value === "above", + disabled: !canPlaceInsertAtSelection.value, + }, + { + value: "position-below", + label: t("grid.insertPositionBelow"), + selected: insertPosition.value === "below", + disabled: !canPlaceInsertAtSelection.value, + }, + { + value: "position-end", + label: t("grid.insertPositionEnd"), + selected: insertPosition.value === "end", + }, ], onTrigger: addRow, onSelect: handleAddRowMenuSelect, @@ -3546,7 +3671,9 @@ const previewToolbarCapability = computed(() => })); const saveToolbarCapability = computed(() => ({ label: t(saveActionMode.value.labelKey, { count: pendingChangeCount.value }), - tooltip: t(saveActionMode.value.tooltipKey, { count: pendingChangeCount.value }), + tooltip: t(saveActionMode.value.tooltipKey, { + count: pendingChangeCount.value, + }), visible: saveToolbarState.value.showActions, disabled: saveToolbarState.value.actionsDisabled, loading: isSaving.value, @@ -3598,7 +3725,14 @@ const displayRowRefs = computed(() => { const status: RowStatus = isDeleted ? "deleted" : dirty?.size ? "edited" : "clean"; const isActiveEditingRow = quickEntryEnabled.value && editingCell.value?.rowId === sourceIndex; if (matchesRowStatusFilter(status, rowStatusFilter.value) || isActiveEditingRow) { - refs.push({ id: sourceIndex, displayIndex: refs.length, sourceIndex, isNew: false, isDeleted, status }); + refs.push({ + id: sourceIndex, + displayIndex: refs.length, + sourceIndex, + isNew: false, + isDeleted, + status, + }); } } else { const newIndex = entry.newIndex; @@ -4192,7 +4326,10 @@ function cellDetailFor(rowIndex: number, columnIndex: number): DataGridCellDetai resultColumnTypes: props.result.column_types, commentByColumn: columnCommentMap.value, displayValue: (value, index) => formatCellCached(value, index), - isEditable: canEditGridCellDetail({ canEditCell: canEditCellItem(item, columnIndex), isDraft: !!item.isDraft }), + isEditable: canEditGridCellDetail({ + canEditCell: canEditCellItem(item, columnIndex), + isDraft: !!item.isDraft, + }), }); } @@ -4260,7 +4397,11 @@ const rowDetail = computed(() => { resultColumnTypes: props.result.column_types, commentByColumn: columnCommentMap.value, displayValue: (value, index) => formatCellCached(value, index), - isEditableColumn: (columnIndex) => canEditGridCellDetail({ canEditCell: canEditCellItem(item, columnIndex), isDraft: !!item.isDraft }), + isEditableColumn: (columnIndex) => + canEditGridCellDetail({ + canEditCell: canEditCellItem(item, columnIndex), + isDraft: !!item.isDraft, + }), }); }); @@ -4274,7 +4415,10 @@ const columnDetail = computed(() => { rowIndex: item.displayIndex, rowId: item.id, row: item.data, - isEditable: canEditGridCellDetail({ canEditCell: canEditCellItem(item, columnIndex), isDraft: !!item.isDraft }), + isEditable: canEditGridCellDetail({ + canEditCell: canEditCellItem(item, columnIndex), + isDraft: !!item.isDraft, + }), })), columns: props.result.columns, columnIndex, @@ -4363,7 +4507,10 @@ function syncPendingDataEditorDraft(pending: boolean) { if (tab?.mode === "data") tab.hasPendingDataEditorDraft = pending || undefined; } -watch(hasPendingDataEditorDraft, syncPendingDataEditorDraft, { immediate: true, flush: "sync" }); +watch(hasPendingDataEditorDraft, syncPendingDataEditorDraft, { + immediate: true, + flush: "sync", +}); const activeValueEditorActions = computed(() => { const detail = activeCellDetail.value; @@ -4568,11 +4715,15 @@ function warnFormattedJsonEditIfNeeded(detail: DataGridCellDetail, force = false } function toggleCellDetailJsonFormatted() { - settingsStore.updateEditorSettings({ cellDetailJsonFormatted: !cellDetailJsonFormatted.value }); + settingsStore.updateEditorSettings({ + cellDetailJsonFormatted: !cellDetailJsonFormatted.value, + }); } function toggleCellDetailMetadataCollapsed() { - settingsStore.updateEditorSettings({ cellDetailMetadataCollapsed: !cellDetailMetadataCollapsed.value }); + settingsStore.updateEditorSettings({ + cellDetailMetadataCollapsed: !cellDetailMetadataCollapsed.value, + }); } function startDetailEdit() { @@ -4888,7 +5039,13 @@ function formatCell(value: CellValue, columnIndex?: number): string { const columnName = columnIndex === undefined ? undefined : props.result.columns[columnIndex]; const columnInfo = columnIndex === undefined ? undefined : tableColumnForGridColumn(columnIndex); const displayColumnInfo = columnInfo ?? (columnIndex === undefined ? undefined : resultColumnInfoForGridColumn(columnIndex)); - const arrayDisplay = formatter ? undefined : dataGridCellDisplayText({ value, databaseType: props.databaseType, columnInfo: displayColumnInfo }); + const arrayDisplay = formatter + ? undefined + : dataGridCellDisplayText({ + value, + databaseType: props.databaseType, + columnInfo: displayColumnInfo, + }); if (arrayDisplay !== undefined) return arrayDisplay; const binaryDisplay = formatter ? null : binaryCellDisplayText(value, columnInfo?.data_type ?? (columnName ? columnTypeMap.value.get(columnName) : undefined)); if (binaryDisplay) return binaryDisplay; @@ -5340,7 +5497,13 @@ function onCanvasMouseMove(event: MouseEvent) { } const hit = canvasHitTest(event); const hitItem = hit ? displayItemAt(hit.rowIndex) : undefined; - const next = hit && hitItem ? { rowIndex: hitItem.displayIndex, visibleColIdx: hit.rowNumber ? -1 : hit.visibleColIdx } : null; + const next = + hit && hitItem + ? { + rowIndex: hitItem.displayIndex, + visibleColIdx: hit.rowNumber ? -1 : hit.visibleColIdx, + } + : null; const actualColIdx = next ? visibleColumnIndexes.value[next.visibleColIdx] : undefined; if (canvasRef.value) { const overBooleanInteractive = @@ -5375,7 +5538,10 @@ function onCanvasMouseLeave(event?: MouseEvent) { function keepCanvasDetailHover() { const cell = canvasDetailButtonCell.value; if (!cell) return; - canvasHoverCell.value = { rowIndex: cell.rowIndex, visibleColIdx: cell.visibleColIdx }; + canvasHoverCell.value = { + rowIndex: cell.rowIndex, + visibleColIdx: cell.visibleColIdx, + }; hoveredDetailCell.value = { rowIndex: cell.rowIndex, col: cell.actualColIdx }; scheduleCanvasDraw(); } @@ -5394,7 +5560,15 @@ function booleanCheckboxHitFromCanvasEvent(item: RowItem, hit: { rowIndex: numbe const canvasRect = canvas?.getBoundingClientRect(); const cellRect = canvasCellViewportRect(hit.rowIndex, hit.visibleColIdx); if (!canvasRect || !cellRect) return false; - return isPointInBooleanCheckbox({ x: event.clientX - canvasRect.left, y: event.clientY - canvasRect.top }, { left: cellRect.left, top: cellRect.top, width: cellRect.width, height: cellRect.height }); + return isPointInBooleanCheckbox( + { x: event.clientX - canvasRect.left, y: event.clientY - canvasRect.top }, + { + left: cellRect.left, + top: cellRect.top, + width: cellRect.width, + height: cellRect.height, + }, + ); } function booleanNullTextHitFromCanvasEvent(item: RowItem, hit: { rowIndex: number; visibleColIdx: number }, actualColIdx: number, event: MouseEvent): boolean { @@ -5536,7 +5710,13 @@ const canvasEditingCell = computed(() => { if (rowIndex < 0 || visibleColIdx < 0) return null; const rect = canvasCellViewportRect(rowIndex, visibleColIdx); if (!rect) return null; - return { rowId: editing.rowId, rowIndex, visibleColIdx, actualColIdx: editing.col, rect }; + return { + rowId: editing.rowId, + rowIndex, + visibleColIdx, + actualColIdx: editing.col, + rect, + }; }); function canvasEffectiveViewportWidth(): number { @@ -5588,7 +5768,14 @@ const canvasDetailButtonCell = computed(() => { const foreignKey = canvasCellForeignKey(target.rowIndex, target.col); const minWidth = canvasDataGridActionOverlayWidth(canQuickDownload, !!foreignKey) + 2; if (rect.top < 0 || rect.top > viewportHeight - 1 || visibleRight - visibleLeft < minWidth) return null; - return { rowIndex: target.rowIndex, visibleColIdx, actualColIdx: target.col, rect, canQuickDownload, foreignKey }; + return { + rowIndex: target.rowIndex, + visibleColIdx, + actualColIdx: target.col, + rect, + canQuickDownload, + foreignKey, + }; }); const canvasDetailButtonStyle = computed(() => { @@ -5665,7 +5852,9 @@ watch( }, { immediate: true }, ); -watch(showDataGridTopbar, () => nextTick(observeDataGridTopbarWidth), { immediate: true }); +watch(showDataGridTopbar, () => nextTick(observeDataGridTopbarWidth), { + immediate: true, +}); watch(columnAligns, () => scheduleCanvasDraw()); watch(booleanDisplayMode, () => scheduleCanvasDraw()); watch( @@ -5946,7 +6135,9 @@ function openExtractorConfiguration() { function setDefaultCopyPreference(value: string) { const item = copyPreferenceMenuItems.value.find((candidate) => candidate.value === value); if (!item || item.disabled) return; - settingsStore.updateEditorSettings({ dataGridCopyExtractor: item.value as DataGridCopyPreference }); + settingsStore.updateEditorSettings({ + dataGridCopyExtractor: item.value as DataGridCopyPreference, + }); } function sqlInsertExtractorOptions(excludePrimaryKeysFromInsert: boolean) { @@ -5958,7 +6149,10 @@ function sqlInsertExtractorOptions(excludePrimaryKeysFromInsert: boolean) { } function saveExtractorConfiguration(value: { preference: DataGridCopyPreference; options: typeof settingsStore.editorSettings.dataGridExtractorOptions }) { - settingsStore.updateEditorSettings({ dataGridCopyExtractor: value.preference, dataGridExtractorOptions: value.options }); + settingsStore.updateEditorSettings({ + dataGridCopyExtractor: value.preference, + dataGridExtractorOptions: value.options, + }); } const pageSizeMenuItems = computed(() => @@ -5971,14 +6165,45 @@ const pageSizeMenuItems = computed(() => const exportMenuItems = computed(() => { const hasFullResultExport = !!props.fullExportResult; const canIncludeSql = props.context === "results" && !!(props.exportSql || props.sql)?.trim(); - const allResultItems = (props.allExportResults?.length ?? 0) > 1 ? [{ value: "all-results-xlsx", label: t("grid.exportAllResultsXlsx"), separatorBefore: true }, ...(canIncludeSql ? [{ value: "all-results-xlsx-with-sql", label: t("grid.exportAllResultsXlsxWithSql") }] : [])] : []; + const allResultItems = + (props.allExportResults?.length ?? 0) > 1 + ? [ + { + value: "all-results-xlsx", + label: t("grid.exportAllResultsXlsx"), + separatorBefore: true, + }, + ...(canIncludeSql + ? [ + { + value: "all-results-xlsx-with-sql", + label: t("grid.exportAllResultsXlsxWithSql"), + }, + ] + : []), + ] + : []; const selectedItems = isMultiRow.value ? [ - { value: "selected-csv", label: t("grid.exportSelectedRowsCsv"), separatorBefore: true }, + { + value: "selected-csv", + label: t("grid.exportSelectedRowsCsv"), + separatorBefore: true, + }, { value: "selected-xlsx", label: t("grid.exportSelectedRowsXlsx") }, - ...(canIncludeSql ? [{ value: "selected-xlsx-with-sql", label: t("grid.exportSelectedRowsXlsxWithSql") }] : []), + ...(canIncludeSql + ? [ + { + value: "selected-xlsx-with-sql", + label: t("grid.exportSelectedRowsXlsxWithSql"), + }, + ] + : []), { value: "selected-json", label: t("grid.exportSelectedRowsJson") }, - { value: "selected-markdown", label: t("grid.exportSelectedRowsMarkdown") }, + { + value: "selected-markdown", + label: t("grid.exportSelectedRowsMarkdown"), + }, { value: "selected-sql", label: t("grid.exportSelectedRowsSql") }, { value: "selected-txt", label: t("grid.exportSelectedRowsTxt") }, ] @@ -6001,14 +6226,32 @@ const exportMenuItems = computed(() => { return [ { value: "page-csv", label: t("grid.exportCurrentPageCsv") }, { value: "page-xlsx", label: t("grid.exportCurrentPageXlsx") }, - ...(canIncludeSql ? [{ value: "page-xlsx-with-sql", label: t("grid.exportCurrentPageXlsxWithSql") }] : []), + ...(canIncludeSql + ? [ + { + value: "page-xlsx-with-sql", + label: t("grid.exportCurrentPageXlsxWithSql"), + }, + ] + : []), { value: "page-json", label: t("grid.exportCurrentPageJson") }, { value: "page-markdown", label: t("grid.exportCurrentPageMarkdown") }, { value: "page-sql", label: t("grid.exportCurrentPageSql") }, { value: "page-txt", label: t("grid.exportCurrentPageTxt") }, - { value: "csv", label: t("grid.exportCurrentResultCsv"), separatorBefore: true }, + { + value: "csv", + label: t("grid.exportCurrentResultCsv"), + separatorBefore: true, + }, { value: "xlsx", label: t("grid.exportCurrentResultXlsx") }, - ...(canIncludeSql ? [{ value: "xlsx-with-sql", label: t("grid.exportCurrentResultXlsxWithSql") }] : []), + ...(canIncludeSql + ? [ + { + value: "xlsx-with-sql", + label: t("grid.exportCurrentResultXlsxWithSql"), + }, + ] + : []), { value: "json", label: t("grid.exportCurrentResultJson") }, { value: "markdown", label: t("grid.exportCurrentResultMarkdown") }, { value: "sql", label: t("grid.exportCurrentResultSql") }, @@ -6196,7 +6439,12 @@ watch([selectedRange, showCellDetail, isEditingDetail, isSelectingCells], () => const target = linkedCellDetailTarget({ isOpen: showCellDetail.value, isEditing: isEditingDetail.value && activeCellDetailTab.value !== "valueEditor", - selectedCell: selectedCell ? { rowIndex: selectedCell.rowIndex, visibleColIndex: selectedCell.colIndex } : null, + selectedCell: selectedCell + ? { + rowIndex: selectedCell.rowIndex, + visibleColIndex: selectedCell.colIndex, + } + : null, actualColumnIndex, }); if (!target) return; @@ -6466,7 +6714,9 @@ function applyGeneratedDetailValue(kind: CellValueGenerationKind, startValue = 1 const detail = activeCellDetail.value; if (!detail?.isEditable) return false; const value = generateCellValues(kind, 1, { startValue })[0] ?? null; - applyCellValue(detail.rowId, detail.colIndex, value, { preserveEmptyString: kind === "empty" }); + applyCellValue(detail.rowId, detail.colIndex, value, { + preserveEmptyString: kind === "empty", + }); detailEditValue.value = cellDetailEditorText(value); syncEditorFromDetailEdit(); isEditingDetail.value = activeCellDetailTab.value === "valueEditor"; @@ -6496,13 +6746,41 @@ function applyGenerateIncrementValue() { function generateSelectionMenuItems(disabled: boolean): ContextMenuItem[] { return [ - { label: t("grid.generateEmptyString"), action: () => applyGeneratedSelectionValue("empty"), disabled }, - { label: t("grid.generateNull"), action: () => applyGeneratedSelectionValue("null"), disabled }, - { label: t("grid.generateCurrentDatetime"), action: () => applyGeneratedSelectionValue("datetime"), disabled }, - { label: t("grid.generateCurrentDate"), action: () => applyGeneratedSelectionValue("date"), disabled }, - { label: t("grid.generateUuid"), action: () => applyGeneratedSelectionValue("uuid"), disabled }, - { label: t("grid.generateSnowflakeId"), action: () => applyGeneratedSelectionValue("snowflake"), disabled }, - { label: t("grid.generateIncrementId"), action: () => openGenerateIncrementDialog("selection"), disabled }, + { + label: t("grid.generateEmptyString"), + action: () => applyGeneratedSelectionValue("empty"), + disabled, + }, + { + label: t("grid.generateNull"), + action: () => applyGeneratedSelectionValue("null"), + disabled, + }, + { + label: t("grid.generateCurrentDatetime"), + action: () => applyGeneratedSelectionValue("datetime"), + disabled, + }, + { + label: t("grid.generateCurrentDate"), + action: () => applyGeneratedSelectionValue("date"), + disabled, + }, + { + label: t("grid.generateUuid"), + action: () => applyGeneratedSelectionValue("uuid"), + disabled, + }, + { + label: t("grid.generateSnowflakeId"), + action: () => applyGeneratedSelectionValue("snowflake"), + disabled, + }, + { + label: t("grid.generateIncrementId"), + action: () => openGenerateIncrementDialog("selection"), + disabled, + }, ]; } @@ -6557,7 +6835,10 @@ function scrollCellIntoView(rowIndex: number, colIndex: number, block: DataGridS requestAnimationFrame(() => { const rowEl = gridRef.value?.querySelector(`[data-row-index="${rowIndex}"]`); const cellEl = rowEl?.querySelector(`[data-visible-col-index="${colIndex}"]`); - (cellEl ?? rowEl)?.scrollIntoView({ block: previousPageRowIndex === undefined ? block : "nearest", inline: "nearest" }); + (cellEl ?? rowEl)?.scrollIntoView({ + block: previousPageRowIndex === undefined ? block : "nearest", + inline: "nearest", + }); }); }); } @@ -6837,7 +7118,10 @@ function prepareDataCellMouseDown(item: RowItem, actualColIdx: number) { batchAppendPasteRowId.value = null; const editing = editingCell.value; if (editing?.rowId === quickEntryDraftRowId && item.isDraft && item.id === quickEntryDraftRowId && editing.col !== actualColIdx) { - pendingQuickEntryDraftCellFocus.value = { rowId: item.id, col: actualColIdx }; + pendingQuickEntryDraftCellFocus.value = { + rowId: item.id, + col: actualColIdx, + }; } else { pendingQuickEntryDraftCellFocus.value = null; if (editing && (editing.rowId !== item.id || editing.col !== actualColIdx)) { @@ -7252,7 +7536,13 @@ watch(columnWidthDensity, () => { nextTick(updateTransposeViewport); }); const transposePinnedWidthOverride = ref(null); -const transposePinnedWidth = computed(() => transposePinnedWidthOverride.value ?? transposeFieldWidth(visibleColumns.value, { density: columnWidthDensity.value })); +const transposePinnedWidth = computed( + () => + transposePinnedWidthOverride.value ?? + transposeFieldWidth(visibleColumns.value, { + density: columnWidthDensity.value, + }), +); const transposeEndSpacerWidth = computed(() => { if (!multiRowTranspose.value || displayRowCount.value <= 0) return 0; return transposeEndAlignmentSpacerWidth({ @@ -7775,14 +8065,20 @@ function resizeCellEditTextareaElement(textarea: HTMLTextAreaElement | null) { } } -function cellEditTextMetrics(textarea: HTMLTextAreaElement): { lineHeight: number; verticalChrome: number } { +function cellEditTextMetrics(textarea: HTMLTextAreaElement): { + lineHeight: number; + verticalChrome: number; +} { const computedStyle = window.getComputedStyle(textarea); const lineHeight = Number.parseFloat(computedStyle.lineHeight) || 18; const paddingTop = Number.parseFloat(computedStyle.paddingTop) || 0; const paddingBottom = Number.parseFloat(computedStyle.paddingBottom) || 0; const borderTop = Number.parseFloat(computedStyle.borderTopWidth) || 0; const borderBottom = Number.parseFloat(computedStyle.borderBottomWidth) || 0; - return { lineHeight, verticalChrome: paddingTop + paddingBottom + borderTop + borderBottom }; + return { + lineHeight, + verticalChrome: paddingTop + paddingBottom + borderTop + borderBottom, + }; } function cellEditNaturalHeight(textarea: HTMLTextAreaElement, metrics = cellEditTextMetrics(textarea)): number { @@ -7879,7 +8175,12 @@ function onRowContext(rowId: number, rowIndex: number) { const sqlOneLiner = computed(() => props.sql?.replace(/\s+/g, " ").trim() || ""); -type TableInfoTabItem = { id: TableInfoTab; label: string; icon: Component; count?: number }; +type TableInfoTabItem = { + id: TableInfoTab; + label: string; + icon: Component; + count?: number; +}; const TABLE_INFO_DRAWER_MIN_WIDTH = 240; const CELL_DETAIL_PANEL_MIN_HEIGHT = 180; @@ -8115,7 +8416,12 @@ const tableInfoTabs = computed(() => { }); } if (canShowTableIndexes.value) { - tabs.push({ id: "indexes", label: t("grid.tableInfoIndexes"), icon: KeyRound, count: indexes.value.length }); + tabs.push({ + id: "indexes", + label: t("grid.tableInfoIndexes"), + icon: KeyRound, + count: indexes.value.length, + }); } if (tableMetadataCapabilities.value.foreignKeys) { tabs.push({ @@ -8126,7 +8432,12 @@ const tableInfoTabs = computed(() => { }); } if (tableMetadataCapabilities.value.triggers) { - tabs.push({ id: "triggers", label: t("grid.tableInfoTriggers"), icon: RotateCcw, count: triggers.value.length }); + tabs.push({ + id: "triggers", + label: t("grid.tableInfoTriggers"), + icon: RotateCcw, + count: triggers.value.length, + }); } return tabs; }); @@ -8193,14 +8504,38 @@ async function fetchIndexes() { indexesError.value = ""; try { const nextIndexes = await api.listIndexes(connectionId, database, schema, tableName, catalog); - if (!columnIndexMetadataRequestCurrent({ requestGeneration, currentGeneration: indexesRequestGeneration, requestIdentity, currentIdentity: currentIndexTableIdentity.value })) return; + if ( + !columnIndexMetadataRequestCurrent({ + requestGeneration, + currentGeneration: indexesRequestGeneration, + requestIdentity, + currentIdentity: currentIndexTableIdentity.value, + }) + ) + return; indexes.value = nextIndexes; indexesLoaded.value = true; } catch (e: any) { - if (!columnIndexMetadataRequestCurrent({ requestGeneration, currentGeneration: indexesRequestGeneration, requestIdentity, currentIdentity: currentIndexTableIdentity.value })) return; + if ( + !columnIndexMetadataRequestCurrent({ + requestGeneration, + currentGeneration: indexesRequestGeneration, + requestIdentity, + currentIdentity: currentIndexTableIdentity.value, + }) + ) + return; indexesError.value = String(e?.message || e); } finally { - if (columnIndexMetadataRequestCurrent({ requestGeneration, currentGeneration: indexesRequestGeneration, requestIdentity, currentIdentity: currentIndexTableIdentity.value })) indexesLoading.value = false; + if ( + columnIndexMetadataRequestCurrent({ + requestGeneration, + currentGeneration: indexesRequestGeneration, + requestIdentity, + currentIdentity: currentIndexTableIdentity.value, + }) + ) + indexesLoading.value = false; } } @@ -8216,9 +8551,18 @@ async function refreshMongoIndexMetadataAfterMutation() { const collection = props.tableMeta?.tableName; if (!connectionId || !database || !collection) return; try { - await refreshLoadedMongoIndexes(connectionStore, { connectionId, database, collection }); + await refreshLoadedMongoIndexes(connectionStore, { + connectionId, + database, + collection, + }); } catch (e: any) { - toast(t("contextMenu.mongoIndexRefreshFailed", { message: String(e?.message || e) }), 5000); + toast( + t("contextMenu.mongoIndexRefreshFailed", { + message: String(e?.message || e), + }), + 5000, + ); } } @@ -8235,14 +8579,38 @@ async function fetchForeignKeys() { foreignKeysError.value = ""; try { const nextForeignKeys = await api.listForeignKeys(connectionId, database, schema, tableName, catalog); - if (!foreignKeyMetadataRequestCurrent({ requestGeneration, currentGeneration: foreignKeysRequestGeneration, requestIdentity, currentIdentity: currentForeignKeyTableIdentity.value })) return; + if ( + !foreignKeyMetadataRequestCurrent({ + requestGeneration, + currentGeneration: foreignKeysRequestGeneration, + requestIdentity, + currentIdentity: currentForeignKeyTableIdentity.value, + }) + ) + return; foreignKeys.value = nextForeignKeys; foreignKeysLoaded.value = true; } catch (e: any) { - if (!foreignKeyMetadataRequestCurrent({ requestGeneration, currentGeneration: foreignKeysRequestGeneration, requestIdentity, currentIdentity: currentForeignKeyTableIdentity.value })) return; + if ( + !foreignKeyMetadataRequestCurrent({ + requestGeneration, + currentGeneration: foreignKeysRequestGeneration, + requestIdentity, + currentIdentity: currentForeignKeyTableIdentity.value, + }) + ) + return; foreignKeysError.value = String(e?.message || e); } finally { - if (foreignKeyMetadataRequestCurrent({ requestGeneration, currentGeneration: foreignKeysRequestGeneration, requestIdentity, currentIdentity: currentForeignKeyTableIdentity.value })) foreignKeysLoading.value = false; + if ( + foreignKeyMetadataRequestCurrent({ + requestGeneration, + currentGeneration: foreignKeysRequestGeneration, + requestIdentity, + currentIdentity: currentForeignKeyTableIdentity.value, + }) + ) + foreignKeysLoading.value = false; } } @@ -8441,7 +8809,9 @@ function onDdlResizeMove(event: MouseEvent) { function onDdlResizeEnd() { if (isResizingDdl.value) { - settingsStore.updateEditorSettings({ tableInfoDrawerWidth: ddlWidth.value }); + settingsStore.updateEditorSettings({ + tableInfoDrawerWidth: ddlWidth.value, + }); } isResizingDdl.value = false; document.body.classList.remove("select-none", "cursor-col-resize"); @@ -8468,7 +8838,9 @@ function onDetailResizeMove(event: MouseEvent) { function onDetailResizeEnd() { if (isResizingDetail.value) { - settingsStore.updateEditorSettings({ cellDetailDrawerWidth: detailPanelHeight.value }); + settingsStore.updateEditorSettings({ + cellDetailDrawerWidth: detailPanelHeight.value, + }); } isResizingDetail.value = false; document.body.classList.remove("select-none", "cursor-row-resize", "cursor-col-resize"); @@ -8596,7 +8968,11 @@ const dropMongoIndexConfirmMessage = computed(() => : "", ); const dropMongoIndexPreview = computed(() => (pendingDropMongoIndex.value ? mongoDropIndexPreview(props.database || "", props.tableMeta?.tableName || "", pendingDropMongoIndex.value.name) : "")); -const dropAllMongoIndexesConfirmMessage = computed(() => t("contextMenu.confirmDropMongoAllIndexesMessage", { name: props.tableMeta?.tableName || "" })); +const dropAllMongoIndexesConfirmMessage = computed(() => + t("contextMenu.confirmDropMongoAllIndexesMessage", { + name: props.tableMeta?.tableName || "", + }), +); const dropAllMongoIndexesConfirmDetails = computed(() => t("contextMenu.confirmDropMongoAllIndexesDetails")); const dropAllMongoIndexesPreview = computed(() => mongoDropAllIndexesPreview(props.database || "", props.tableMeta?.tableName || "")); @@ -8634,14 +9010,26 @@ async function confirmDropMongoIndex() { onSuccess: (result) => { const failed = mongoDropIndexFailureCount(result); if (failed > 0) { - toast(t("contextMenu.dropIndexesPartialFailure", { success: result.dropped_names.length, failed }), 5000); + toast( + t("contextMenu.dropIndexesPartialFailure", { + success: result.dropped_names.length, + failed, + }), + 5000, + ); } else { toast(t("contextMenu.dropTableChildObjectSuccess", { name: index.name }), 3000); } showDropMongoIndexConfirm.value = false; pendingDropMongoIndex.value = null; }, - onError: (e: any) => toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000), + onError: (e: any) => + toast( + t("contextMenu.tableOperationFailed", { + message: e?.message || String(e), + }), + 5000, + ), }); } @@ -8667,13 +9055,31 @@ async function confirmDropAllMongoIndexes() { onSuccess: (result) => { const failed = mongoDropIndexFailureCount(result); if (failed > 0) { - toast(t("contextMenu.dropIndexesPartialFailure", { success: result.dropped_names.length, failed }), 5000); + toast( + t("contextMenu.dropIndexesPartialFailure", { + success: result.dropped_names.length, + failed, + }), + 5000, + ); } else { - toast(t("contextMenu.dropAllIndexesSuccess", { count: result.dropped_names.length, name: tableName }), 3000); + toast( + t("contextMenu.dropAllIndexesSuccess", { + count: result.dropped_names.length, + name: tableName, + }), + 3000, + ); } showDropAllMongoIndexesConfirm.value = false; }, - onError: (e: any) => toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000), + onError: (e: any) => + toast( + t("contextMenu.tableOperationFailed", { + message: e?.message || String(e), + }), + 5000, + ), }); } @@ -8862,7 +9268,10 @@ function copySubmenu(): ContextMenuItem { items.push(...buildExtractorContextItems()); items.push({ label: "", separator: true }); items.push({ label: t("grid.copyAll"), action: copyAll }); - items.push({ label: t("grid.copyColumnNames"), action: openCopyAllColumnNamesDialog }); + items.push({ + label: t("grid.copyColumnNames"), + action: openCopyAllColumnNamesDialog, + }); return { label: t("grid.copy"), icon: Copy, children: items }; } @@ -8876,16 +9285,35 @@ function exportSubmenu(): ContextMenuItem { { label: t("grid.exportTxt"), action: exportTxt }, ]; if (props.context === "results" && !!(props.exportSql || props.sql)?.trim()) { - items.splice(2, 0, { label: t("grid.exportXlsxWithSql"), action: exportXlsxWithSql }); + items.splice(2, 0, { + label: t("grid.exportXlsxWithSql"), + action: exportXlsxWithSql, + }); } if (isMultiRow.value) { items.push( { label: "", separator: true }, { label: t("grid.exportSelectedRowsCsv"), action: exportSelectedRowsCsv }, - { label: t("grid.exportSelectedRowsXlsx"), action: exportSelectedRowsXlsx }, - ...(props.context === "results" && !!(props.exportSql || props.sql)?.trim() ? [{ label: t("grid.exportSelectedRowsXlsxWithSql"), action: exportSelectedRowsXlsxWithSql }] : []), - { label: t("grid.exportSelectedRowsJson"), action: exportSelectedRowsJson }, - { label: t("grid.exportSelectedRowsMarkdown"), action: exportSelectedRowsMarkdown }, + { + label: t("grid.exportSelectedRowsXlsx"), + action: exportSelectedRowsXlsx, + }, + ...(props.context === "results" && !!(props.exportSql || props.sql)?.trim() + ? [ + { + label: t("grid.exportSelectedRowsXlsxWithSql"), + action: exportSelectedRowsXlsxWithSql, + }, + ] + : []), + { + label: t("grid.exportSelectedRowsJson"), + action: exportSelectedRowsJson, + }, + { + label: t("grid.exportSelectedRowsMarkdown"), + action: exportSelectedRowsMarkdown, + }, { label: t("grid.exportSelectedRowsSql"), action: exportSelectedRowsSql }, { label: t("grid.exportSelectedRowsTxt"), action: exportSelectedRowsTxt }, ); @@ -8927,7 +9355,12 @@ const gridContextMenuItems = computed(() => { contextVisibleColIdx: contextHeaderVisibleColIdx.value ?? undefined, hasColumnSelection: hasColumnSelection.value, labels: { - copyName: selectedColumnNamesForCopy.value.length > 1 ? t("grid.copyColumnNamesSelected", { count: selectedColumnNamesForCopy.value.length }) : t("grid.copyColumnName"), + copyName: + selectedColumnNamesForCopy.value.length > 1 + ? t("grid.copyColumnNamesSelected", { + count: selectedColumnNamesForCopy.value.length, + }) + : t("grid.copyColumnName"), copyNames: t("grid.copyColumnNames"), details: t("grid.openColumnDetailsDialog"), copyAlterSql: t("grid.copyAlterColumnSql"), @@ -8940,7 +9373,14 @@ const gridContextMenuItems = computed(() => { freezeSelectedColumns: t("grid.freezeSelectedColumns"), unfreezeColumns: t("grid.unfreezeColumns"), }, - icons: { copy: Copy, columnDetails: TableProperties, database: Database, ascending: ArrowUp, descending: ArrowDown, clearSort: Eraser }, + icons: { + copy: Copy, + columnDetails: TableProperties, + database: Database, + ascending: ArrowUp, + descending: ArrowDown, + clearSort: Eraser, + }, actions: { copyName: copyHeaderColumnOrSelected, copyNames: openCopyAllColumnNamesDialog, @@ -8981,13 +9421,31 @@ const gridContextMenuItems = computed(() => { bulkEdit: t("grid.bulkEditSelection"), transpose: t("grid.transpose"), }, - icons: { cellDetails: Maximize2, columnDetails: TableProperties, rowDetails: ListTree, setNull: X, bulkEdit: Pencil, transpose: Rows3 }, - actions: { cellDetails: openContextCellDetailDialog, columnDetails: openContextColumnDetailDialog, rowDetails: openContextRowDetailDialog, setNull: setSelectionNull, bulkEdit: openBulkEditDialog, transpose: openContextTranspose }, + icons: { + cellDetails: Maximize2, + columnDetails: TableProperties, + rowDetails: ListTree, + setNull: X, + bulkEdit: Pencil, + transpose: Rows3, + }, + actions: { + cellDetails: openContextCellDetailDialog, + columnDetails: openContextColumnDetailDialog, + rowDetails: openContextRowDetailDialog, + setNull: setSelectionNull, + bulkEdit: openBulkEditDialog, + transpose: openContextTranspose, + }, importItem: binaryImportItem(contextCellDetail.value), downloadItem: binaryDownloadSubmenu(contextCellDetail.value), foreignKeyItem: contextForeignKeyMenuItem(), copySubmenu: copySubmenu(), - clearSelectionItem: { label: t("grid.clearSelection"), action: clearCellSelection, icon: SquareDashed }, + clearSelectionItem: { + label: t("grid.clearSelection"), + action: clearCellSelection, + icon: SquareDashed, + }, generateSubmenu: { label: t("grid.generateValue"), icon: WandSparkles, @@ -9021,7 +9479,11 @@ const gridContextMenuItems = computed(() => { data-grid-root data-grid-active="true" class="h-full flex flex-col overflow-hidden outline-none" - :class="{ 'data-grid--editing-cell': !!editingCell, 'data-grid--dark': isDark, 'data-grid--has-save-error': !!saveError }" + :class="{ + 'data-grid--editing-cell': !!editingCell, + 'data-grid--dark': isDark, + 'data-grid--has-save-error': !!saveError, + }" :style="gridStyle" tabindex="0" @keydown="onGridKeydown" @@ -9147,7 +9609,13 @@ const gridContextMenuItems = computed(() => { @click="toggleMongoJsonPreview" > - {{ t("grid.mongoJsonPreview") }} + {{ t("grid.mongoJsonPreview") }} {{ t("grid.mongoJsonPreview") }} @@ -9161,7 +9629,13 @@ const gridContextMenuItems = computed(() => { @@ -9178,7 +9652,9 @@ const gridContextMenuItems = computed(() => { #{{ column.index + 1 }} {{ column.comment }} -
{{ t("grid.noColumnsFound") }}
+
+ {{ t("grid.noColumnsFound") }} +
@@ -9269,7 +9745,9 @@ const gridContextMenuItems = computed(() => { 'transpose-record-header-active text-primary': transposeRecordUsesActiveHighlight(recordIndex) && !transposeRecordUsesFramedHeader(recordIndex), 'data-grid-header-cell': !transposeRecordUsesActiveHighlight(recordIndex) && !transposeRecordUsesFramedHeader(recordIndex), }" - :style="{ width: `${getTransposeRecordWidth(recordIndex)}px` }" + :style="{ + width: `${getTransposeRecordWidth(recordIndex)}px`, + }" @click="selectTransposeRecord(recordIndex, $event)" @contextmenu="selectTransposeRecord(recordIndex, $event)" > @@ -9280,7 +9758,13 @@ const gridContextMenuItems = computed(() => {
@@ -10259,10 +10806,18 @@ const gridContextMenuItems = computed(() => { # - {{ t("grid.columnName") }} - {{ t("grid.columnType") }} - {{ t("grid.tableInfoNullable") }} - {{ t("structureEditor.defaultValue") }} + + {{ t("grid.columnName") }} + + + {{ t("grid.columnType") }} + + + {{ t("grid.tableInfoNullable") }} + + + {{ t("structureEditor.defaultValue") }} + @@ -10277,7 +10832,9 @@ const gridContextMenuItems = computed(() => { @keydown.enter.prevent="scrollToTableInfoColumn(column.name)" @keydown.space.prevent="scrollToTableInfoColumn(column.name)" > - {{ index + 1 }} + + {{ index + 1 }} + @@ -10287,9 +10844,20 @@ const gridContextMenuItems = computed(() => { {{ column.comment }} - {{ column.data_type }} - {{ column.is_nullable ? "YES" : "NO" }} - + + {{ gaussdbMColumnType(column.data_type) }} + + + {{ column.is_nullable ? "YES" : "NO" }} + + {{ tableColumnDefaultDisplayValue(column.column_default) }} @@ -10301,7 +10869,9 @@ const gridContextMenuItems = computed(() => {
-
{{ indexesError }}
+
+ {{ indexesError }} +
{{ t("grid.tableInfoNoResults") }}
@@ -10335,7 +10905,9 @@ const gridContextMenuItems = computed(() => {
-
{{ foreignKeysError }}
+
+ {{ foreignKeysError }} +
{{ t("grid.tableInfoNoResults") }}
@@ -10354,7 +10926,9 @@ const gridContextMenuItems = computed(() => {
-
{{ triggersError }}
+
+ {{ triggersError }} +
{{ t("grid.tableInfoNoResults") }}
@@ -10470,7 +11044,13 @@ const gridContextMenuItems = computed(() => {
{{ t("grid.hexViewer") }}
-
{{ t("grid.hexViewerByteCount", { count: activeBinaryHexByteCount }) }}
+
+ {{ + t("grid.hexViewerByteCount", { + count: activeBinaryHexByteCount, + }) + }} +
@@ -10479,7 +11059,9 @@ const gridContextMenuItems = computed(() => {
{{ t("grid.hexViewerAscii") }}
-
{{ row.offset }}
+
+ {{ row.offset }} +
{{ row.hex }}
{{ row.ascii }}
@@ -10562,7 +11144,11 @@ const gridContextMenuItems = computed(() => {
- {{ t(showTruncationWarning ? "grid.loadedRows" : "grid.totalRows", { count: result.rows.length }) }} + {{ + t(showTruncationWarning ? "grid.loadedRows" : "grid.totalRows", { + count: result.rows.length, + }) + }} {{ t(totalRowCountLabelKey, { count: displayedTotalRowCount }) }} {{ t("grid.totalRowCountLoading") }} @@ -10664,7 +11250,11 @@ const gridContextMenuItems = computed(() => {

- {{ t("grid.generateSequenceDescription", { count: generateIncrementTarget === "detail" ? 1 : editableSelectionCells().length }) }} + {{ + t("grid.generateSequenceDescription", { + count: generateIncrementTarget === "detail" ? 1 : editableSelectionCells().length, + }) + }}

@@ -10682,7 +11272,13 @@ const gridContextMenuItems = computed(() => { - +
- {{ column.data_type }} + {{ gaussdbMColumnType(column.data_type) }} {{ column.is_nullable ? "YES" : "NO" }} {{ tableColumnDefaultDisplayValue(column.column_default) }} diff --git a/apps/desktop/src/components/structure/TableStructureEditor.vue b/apps/desktop/src/components/structure/TableStructureEditor.vue index d4df49db3b..2e84c54944 100644 --- a/apps/desktop/src/components/structure/TableStructureEditor.vue +++ b/apps/desktop/src/components/structure/TableStructureEditor.vue @@ -32,7 +32,7 @@ import { invalidateTableMetadataCache } from "@/lib/metadata/tableMetadataCache" import { type BuildTableStructureChangeSqlOptions, type EditableStructureColumn, type EditableStructureForeignKey, type EditableStructureIndex, type EditableStructureTrigger } from "@/lib/table/tableStructureEditorSql"; import { PRESET_FIELDS_TEMPLATE_ID, createTableColumnTemplateDrafts } from "@/lib/table/tableColumnTemplates"; import { getMysqlDataTypeHelp } from "@/lib/table/mysqlDataTypeHelp"; -import { getPostgresDataTypeHelp } from "@/lib/table/postgresDataTypeHelp"; +import { getPostgresDataTypeHelp, gaussdbMTypeDisplayName } from "@/lib/table/postgresDataTypeHelp"; import { getSqliteDataTypeHelp } from "@/lib/table/sqliteDataTypeHelp"; import { getTableMetadataCapabilities, firstStructureMetadataTab, isStructureMetadataTabSupported } from "@/lib/table/tableMetadataCapabilities"; import { hasTableStructureRefreshWork, unloadedTableStructureRefreshScope, visibleTableStructureRefreshScope, type TableStructureRefreshScope } from "@/lib/table/tableStructureMetadataLoading"; @@ -1079,6 +1079,16 @@ function dataTypeTooltip(option: string): string | undefined { return undefined; } +function gaussdbMDataTypeDisplayName(option: string): string { + if (databaseType.value === "gaussdb") { + const conn = connection.value; + if (conn?.driver_profile?.toLowerCase() === "gaussdb-m") { + return gaussdbMTypeDisplayName(option); + } + } + return option; +} + async function loadDynamicDataTypeOptions() { const requestId = ++dataTypeOptionsRequestId; const connectionId = props.connectionId; @@ -2801,10 +2811,11 @@ watch([activeTab, ddlLoading], ([tab, loading]) => { :loading-text="t('common.loading')" :allow-custom="true" :option-tooltip="dataTypeTooltip" + :display-name="gaussdbMDataTypeDisplayName" :trigger-class="[structureMonoControlClass, 'w-full']" @update:model-value="(v: string) => updateColumnDataType(column, v)" /> - + diff --git a/apps/desktop/src/composables/useSidebarTreeExportRuntime.ts b/apps/desktop/src/composables/useSidebarTreeExportRuntime.ts index 27a9ffb421..94f532ec50 100644 --- a/apps/desktop/src/composables/useSidebarTreeExportRuntime.ts +++ b/apps/desktop/src/composables/useSidebarTreeExportRuntime.ts @@ -9,6 +9,7 @@ import * as api from "@/lib/backend/api"; import { isTauriRuntime } from "@/lib/backend/tauriRuntime"; import { copyToClipboard } from "@/lib/common/clipboard"; import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect"; +import { gaussdbMTypeDisplayName } from "@/lib/table/postgresDataTypeHelp"; import { joinExportedDdls } from "@/lib/export/ddlExport"; import { translateBackendError } from "@/i18n/backend-errors"; import { sidebarStructureExportTargets } from "@/lib/sidebar/sidebarExportRuntime"; @@ -107,8 +108,12 @@ export function useSidebarTreeExportRuntime(options: SidebarTreeExportRuntimeOpt return includeTable ? [t("contextMenu.structureDocTable"), ...headers] : headers; } - function columnDocCells(target: TreeNode, column: ColumnInfo, includeTable: boolean): unknown[] { - const cells = [column.name, column.data_type, column.is_primary_key ? t("contextMenu.structureDocYes") : t("contextMenu.structureDocNo"), column.is_nullable ? t("contextMenu.structureDocYes") : t("contextMenu.structureDocNo"), column.column_default, column.comment]; + function columnDocCells(target: TreeNode & { connectionId: string }, column: ColumnInfo, includeTable: boolean): unknown[] { + const config = connectionStore.getConfig(target.connectionId); + const isGaussdbM = effectiveDatabaseTypeForConnection(config) === "gaussdb" && config?.driver_profile?.toLowerCase() === "gaussdb-m"; + const sourceDataType = column.data_type; + const dataType = isGaussdbM && sourceDataType ? gaussdbMTypeDisplayName(sourceDataType) : sourceDataType; + const cells = [column.name, dataType, column.is_primary_key ? t("contextMenu.structureDocYes") : t("contextMenu.structureDocNo"), column.is_nullable ? t("contextMenu.structureDocYes") : t("contextMenu.structureDocNo"), column.column_default, column.comment]; return includeTable ? [structureTargetName(target), ...cells] : cells; } diff --git a/apps/desktop/src/lib/table/postgresDataTypeHelp.ts b/apps/desktop/src/lib/table/postgresDataTypeHelp.ts index d4218c5a5b..d55c6f8d86 100644 --- a/apps/desktop/src/lib/table/postgresDataTypeHelp.ts +++ b/apps/desktop/src/lib/table/postgresDataTypeHelp.ts @@ -153,3 +153,26 @@ function floatHelpKey(normalizedType: string): "real" | "double" | undefined { if (value >= 1 && value <= 24) return "real"; return value >= 25 && value <= 53 ? "double" : undefined; } + +/** + * Converts a GaussDB M-mode PostgreSQL internal type name to its MySQL-style + * display name. Returns the original name if no conversion applies. + * + * GaussDB M mode returns PostgreSQL internal type names (e.g. bpchar, int4, + * numeric, int1) but should display MySQL-compatible names. + */ +export function gaussdbMTypeDisplayName(typeName: string): string { + const s = typeName.trim().toLowerCase(); + if (s === "bpchar") return "char"; + if (s === "int4" || s === "integer") return "int"; + if (s === "int2" || s === "smallint") return "smallint"; + if (s === "int8" || s === "bigint") return "bigint"; + if (s === "int1") return "tinyint"; + if (s === "numeric") return "decimal"; + if (s === "float4" || s === "real") return "float"; + if (s === "float8" || s === "double precision") return "double"; + if (s === "bool" || s === "boolean") return "boolean"; + if (s === "character varying") return "varchar"; + if (s === "character") return "char"; + return typeName; +} diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index 3e31a73591..1365c063f9 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -73,6 +73,7 @@ import { shouldMarkDisconnected } from "@/lib/connection/connectionHealth"; import { connectionAttemptOriginalErrorMessage, connectionAttemptTimeoutMessage, connectionAttemptTimeoutMs } from "@/lib/connection/connectionAttemptTimeout"; import { loadTimeoutInheritanceBackup, saveTimeoutInheritanceBackup } from "@/lib/connection/timeoutInheritanceBackup"; import { migrateSqlServerLegacyCompatibilityConfig, requiresSqlServerLegacyCompatibilityComponent, SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY } from "@/lib/connection/sqlServerLegacyCompatibility"; +import { gaussdbMTypeDisplayName } from "@/lib/table/postgresDataTypeHelp"; import { deleteTabResultSnapshotsForOwner } from "@/lib/tabs/tabResultCache"; import { connectionUsesVisibleSchemaFilter, filterDatabaseNamesForConnection, filterSchemaNamesForConnection, filterVisibleDatabaseNames, normalizeVisibleDatabaseSelection } from "@/lib/database/visibleDatabases"; import { @@ -5066,11 +5067,13 @@ export const useConnectionStore = defineStore("connection", () => { const columns = await api.getColumns(connectionId, database, querySchema, table, catalog); const targetNode = treeNodeLoadTarget(load); if (!targetNode) return; + const connConfig = getConfig(connectionId); + const isGaussdbM = effectiveDatabaseTypeForConnection(connConfig) === "gaussdb" && connConfig?.driver_profile?.toLowerCase() === "gaussdb-m"; setChildren( targetNode, columns.map((col) => ({ id: `${parentId}:${col.name}`, - label: `${col.name} (${col.data_type})`, + label: `${col.name} (${isGaussdbM ? gaussdbMTypeDisplayName(col.data_type) : col.data_type})`, type: "column" as const, connectionId, database,