mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(grid): disambiguate duplicate column names in headers
This commit is contained in:
@@ -119,6 +119,7 @@ import { uuid } from "@/lib/common/utils";
|
||||
import { generateCellValues, type CellValueGenerationKind, type CellValueGenerationOptions } from "@/lib/dataGrid/cellValueGeneration";
|
||||
import { MONGO_DOCUMENT_GRID_NULL, mongoDocumentGridClipboardText, mongoDocumentGridDisplayText, mongoDocumentGridEditorText, mongoDocumentGridExternalValue, mongoDocumentGridInputValue, mongoDocumentGridNumericValue, mongoDocumentRelaxedExtendedJson } from "@/lib/mongo/mongoDocumentValues";
|
||||
import { compactHeaderColumnType, formatMetadataColumnTypeLabel, isNumericColumnType, resolveDataGridTypeVisualKind, resolveHeaderColumnType, resolveResultColumnType, shortHeaderTypeName } from "@/lib/dataGrid/dataGridColumnType";
|
||||
import { dedupeResultColumnNames } from "@/lib/sql/resultColumnNames";
|
||||
import { dataGridCellTextClass, dataGridTypeVisualClass } from "@/lib/dataGrid/dataGridCellTextVisual";
|
||||
import { DATA_GRID_TYPE_COLOR_KEYS, resolveActiveDataGridTypeColors } from "@/lib/dataGrid/dataGridTypeColorScheme";
|
||||
import {
|
||||
@@ -2419,6 +2420,10 @@ const filteredGoToColumns = computed(() => {
|
||||
return filterDataGridColumnLookupItems(goToColumnItems.value, goToColumnSearch.value, goToColumnMatchMode.value);
|
||||
});
|
||||
const visibleColumns = computed(() => visibleColumnIndexes.value.map((index) => props.result.columns[index]));
|
||||
// Header labels only: SQL generation, sorting, transfer and export keep the raw
|
||||
// result columns so duplicate-label handling (#12214) and INSERT targets stay
|
||||
// correct. Duplicate labels are disambiguated for display only (#12452).
|
||||
const displayColumnNames = computed(() => dedupeResultColumnNames(props.result.columns));
|
||||
const visibleColumnIndexIndicators = computed(() =>
|
||||
visibleColumns.value.map((column) => {
|
||||
if (!showIndexIndicatorsInHeader.value) return false;
|
||||
@@ -14823,7 +14828,7 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
<DataGridColumnHeader
|
||||
v-for="col in renderedGridColumns"
|
||||
:key="`${col.name}-${col.actualColIdx}`"
|
||||
:name="col.name"
|
||||
:name="displayColumnNames[col.actualColIdx] ?? col.name"
|
||||
:actual-column-index="col.actualColIdx"
|
||||
:visible-column-index="col.visibleColIdx"
|
||||
:class="{ 'crosshair-column': !!crosshairTarget?.columnCrosshair && crosshairTarget.visibleColIdx === col.visibleColIdx }"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { dedupeResultColumnNames } from "@/lib/sql/resultColumnNames";
|
||||
|
||||
describe("dedupeResultColumnNames", () => {
|
||||
it("renames repeated labels with numeric suffixes", () => {
|
||||
expect(dedupeResultColumnNames(["id", "name", "name", "name"])).toEqual(["id", "name", "name_1", "name_2"]);
|
||||
});
|
||||
|
||||
it("never collides with a label that already uses the suffix", () => {
|
||||
expect(dedupeResultColumnNames(["name", "name_1", "name", "name"])).toEqual(["name", "name_1", "name_2", "name_3"]);
|
||||
});
|
||||
|
||||
it("keeps unique and empty labels untouched", () => {
|
||||
expect(dedupeResultColumnNames(["", "a", "b"])).toEqual(["", "a", "b"]);
|
||||
expect(dedupeResultColumnNames([])).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Query results can contain duplicate column labels, e.g.
|
||||
* `SELECT a.name, b.name, c.name ...` (#12452). The grid, sorting, filtering
|
||||
* and copy paths all look columns up by name, so duplicates make them address
|
||||
* the first match only.
|
||||
*
|
||||
* Rename duplicates once at the result boundary, Navicat-style:
|
||||
* `name, name, name` -> `name, name_1, name_2`. Existing names are never
|
||||
* reused, so a result that already contains `name_1` stays unambiguous.
|
||||
*/
|
||||
export function dedupeResultColumnNames(columns: readonly string[]): string[] {
|
||||
const used = new Set<string>();
|
||||
return columns.map((column) => {
|
||||
if (!used.has(column)) {
|
||||
used.add(column);
|
||||
return column;
|
||||
}
|
||||
let suffix = 1;
|
||||
let candidate = `${column}_${suffix}`;
|
||||
while (used.has(candidate)) {
|
||||
suffix += 1;
|
||||
candidate = `${column}_${suffix}`;
|
||||
}
|
||||
used.add(candidate);
|
||||
return candidate;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user