feat(structure): persist local column order

This commit is contained in:
zipg 2026-07-12 12:06:49 +08:00 committed by GitHub
parent c3ec7fceef
commit 0650428d1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 278 additions and 9 deletions

View File

@ -159,7 +159,19 @@ import { MAX_RESULT_PAGE_SIZE, MIN_RESULT_PAGE_SIZE, normalizeResultPageSize, re
import { allNullColumnIndexes, filterColumnVisibilityOptions, hiddenColumnIndexesWithAllNullColumns, invertedHiddenColumnIndexes, nextHiddenColumnIndexes, removeAutoHiddenColumnIndexes, visibleColumnIndexesForFilter } from "@/lib/dataGrid/dataGridColumnVisibility";
import { buildDataGridColumnLookupItems, filterDataGridColumnLookupItems } from "@/lib/dataGrid/dataGridColumnLookup";
import { columnOrderKeysForIndexes, isDefaultColumnOrder, moveVisibleColumnIndex, orderedColumnIndexes, uniqueDataGridColumnOrderKeys } from "@/lib/dataGrid/dataGridColumnOrder";
import { dataGridColumnLayoutScopeKey, loadDataGridColumnOrder, removeDataGridColumnOrder, saveDataGridColumnOrder } from "@/lib/dataGrid/dataGridColumnLayoutStorage";
import {
dataGridColumnLayoutScopeKey,
loadDataGridColumnOrder,
loadTableDataGridColumnOrder,
notifyTableDataGridColumnOrderChanged,
removeDataGridColumnOrder,
removeTableDataGridColumnOrder,
saveDataGridColumnOrder,
saveTableDataGridColumnOrder,
TABLE_DATA_GRID_COLUMN_ORDER_CHANGED_EVENT,
tableDataGridColumnOrderScopeKey,
type TableDataGridColumnOrderChangedDetail,
} from "@/lib/dataGrid/dataGridColumnLayoutStorage";
import { parseClipboardTable, summarizeSelection } from "@/lib/dataGrid/gridSelection";
import { columnHeaderCanvasPointerDisabled, columnHeaderClickShouldBeSuppressed, columnHeaderPreviewOffsetForColumn, columnHeaderTooltipDisabled } from "@/lib/dataGrid/dataGridColumnHeaderInteraction";
@ -2554,6 +2566,15 @@ const columnLayoutScopeKey = computed(() =>
sourceColumns: props.sourceColumns,
}),
);
const tableColumnOrderScopeKey = computed(() => {
if (props.context !== "table-data" || !props.connectionId || !props.database || !props.tableMeta?.tableName) return "";
return tableDataGridColumnOrderScopeKey({
connectionId: props.connectionId,
database: props.database,
schema: props.tableMeta.schema,
tableName: props.tableMeta.tableName,
});
});
const persistedColumnOrderKeys = ref<string[]>([]);
const displayableColumnIndexes = computed(() =>
props.result.columns
@ -2633,20 +2654,42 @@ function invertColumnVisibility() {
hiddenColumnIndexes.value = invertedHiddenColumnIndexes(displayableColumnIndexes.value, hiddenColumnIndexes.value);
}
function loadColumnOrder() {
persistedColumnOrderKeys.value = loadDataGridColumnOrder(columnLayoutScopeKey.value, columnOrderKeys.value);
const tableOrder = tableColumnOrderScopeKey.value ? loadTableDataGridColumnOrder(tableColumnOrderScopeKey.value) : [];
persistedColumnOrderKeys.value = tableOrder.length ? tableOrder : loadDataGridColumnOrder(columnLayoutScopeKey.value, columnOrderKeys.value);
}
function onTableDataGridColumnOrderChanged(event: Event) {
if (!(event instanceof CustomEvent)) return;
const detail = event.detail as TableDataGridColumnOrderChangedDetail | undefined;
if (!detail || detail.scopeKey !== tableColumnOrderScopeKey.value) return;
loadColumnOrder();
nextTick(refreshGridScrollerMetrics);
}
function persistColumnOrder(indexes: number[]) {
const tableScopeKey = tableColumnOrderScopeKey.value;
if (isDefaultColumnOrder(displayableColumnIndexes.value, indexes)) {
removeDataGridColumnOrder(columnLayoutScopeKey.value);
if (tableScopeKey) {
removeTableDataGridColumnOrder(tableScopeKey);
notifyTableDataGridColumnOrderChanged(tableScopeKey);
}
persistedColumnOrderKeys.value = [];
return;
}
const keys = columnOrderKeysForIndexes(indexes, columnOrderKeys.value);
persistedColumnOrderKeys.value = keys;
saveDataGridColumnOrder(columnLayoutScopeKey.value, columnOrderKeys.value, keys);
if (tableScopeKey) {
saveTableDataGridColumnOrder(tableScopeKey, keys);
notifyTableDataGridColumnOrderChanged(tableScopeKey);
}
}
function resetColumnOrder() {
removeDataGridColumnOrder(columnLayoutScopeKey.value);
const tableScopeKey = tableColumnOrderScopeKey.value;
if (tableScopeKey) {
removeTableDataGridColumnOrder(tableScopeKey);
notifyTableDataGridColumnOrderChanged(tableScopeKey);
}
persistedColumnOrderKeys.value = [];
nextTick(refreshGridScrollerMetrics);
}
@ -2678,7 +2721,7 @@ watch(allNullColumnIndexesForResult, () => {
autoHiddenNullColumnIndexes.value = new Set();
hideAllNullColumns();
});
watch(() => columnLayoutScopeKey.value, loadColumnOrder, { immediate: true });
watch([columnLayoutScopeKey, tableColumnOrderScopeKey], loadColumnOrder, { immediate: true });
const firstVisibleColumnIndex = computed(() => visibleColumnIndexes.value[0] ?? 0);
function actualColumnIndex(visibleColumnIndex: number): number {
return visibleColumnIndexes.value[visibleColumnIndex] ?? visibleColumnIndex;
@ -6266,6 +6309,7 @@ onMounted(() => {
window.visualViewport?.addEventListener("resize", resizeFocusedConditionInputs);
window.addEventListener("dbx:ui-scale-applied", scheduleCanvasPixelRatioRefresh);
window.addEventListener("dbx:ui-scale-applied", resizeFocusedConditionInputs);
window.addEventListener(TABLE_DATA_GRID_COLUMN_ORDER_CHANGED_EVENT, onTableDataGridColumnOrderChanged);
document.addEventListener("pointerdown", onConditionSuggestionDocumentPointerDown, true);
});
onDeactivated(pauseCanvasGridWork);
@ -6292,6 +6336,7 @@ onUnmounted(() => {
window.visualViewport?.removeEventListener("resize", resizeFocusedConditionInputs);
window.removeEventListener("dbx:ui-scale-applied", scheduleCanvasPixelRatioRefresh);
window.removeEventListener("dbx:ui-scale-applied", resizeFocusedConditionInputs);
window.removeEventListener(TABLE_DATA_GRID_COLUMN_ORDER_CHANGED_EVENT, onTableDataGridColumnOrderChanged);
document.removeEventListener("pointerdown", onConditionSuggestionDocumentPointerDown, true);
});

View File

@ -28,7 +28,9 @@ import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStor
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 { getTableMetadataCapabilities, firstStructureMetadataTab, isStructureMetadataTabSupported } from "@/lib/table/tableMetadataCapabilities";
import { canAddTableStructureColumn, getTableStructureCapabilities } from "@/lib/table/tableStructureCapabilities";
import { canAddTableStructureColumn, getTableStructureCapabilities, hasLocalTableColumnOrderChange, isPhysicalTableColumnOrderChange, supportsLocalTableColumnReorder } from "@/lib/table/tableStructureCapabilities";
import { orderedColumnIndexes, uniqueDataGridColumnOrderKeys } from "@/lib/dataGrid/dataGridColumnOrder";
import { loadTableDataGridColumnOrder, notifyTableDataGridColumnOrderChanged, removeTableDataGridColumnOrder, saveTableDataGridColumnOrder, tableDataGridColumnOrderScopeKey } from "@/lib/dataGrid/dataGridColumnLayoutStorage";
import { connectionObjectTreeQuerySchema, tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import type { TableInfoTab, TableStructureEditorDraft, TableStructureEditorTarget, TableStructureEditorViewport } from "@/types/database";
import {
@ -191,7 +193,7 @@ function columnChanged(column: EditableStructureColumn, index: number): boolean
if (!column.original || column.markedForDrop) return true;
const original = column.original;
return (
column.originalPosition !== index ||
isPhysicalTableColumnOrderChange(databaseType.value, connection.value?.db_type, column.originalPosition, index) ||
column.name !== original.name ||
column.dataType !== original.data_type ||
column.isNullable !== original.is_nullable ||
@ -1152,6 +1154,7 @@ function resetState() {
highlightedIndexId.value = null;
appliedInitialTargetSearchKey = "";
appliedInitialTargetScrollKey = "";
localColumnOrderNoticeShown.value = false;
}
async function reloadStructureFromDatabase() {
@ -1216,7 +1219,7 @@ async function loadStructure(silent = false, scope: StructureRefreshScope = FULL
// Load live charset/collation metadata from the MySQL server so the column
// editor shows the correct options for the server version.
void loadCharsetMetadata();
columns.value = createColumnDrafts(nextColumns, databaseType.value);
columns.value = applyStoredLocalColumnOrder(createColumnDrafts(nextColumns, databaseType.value));
}
const nextTableComment = await tableCommentPromise;
@ -1325,6 +1328,7 @@ type ColumnDragState = {
};
const columnDragState = ref<ColumnDragState | null>(null);
const localColumnOrderNoticeShown = ref(false);
let columnDragPreviousBodyUserSelect = "";
let columnDragPreviousBodyCursor = "";
let columnDragTracking = false;
@ -1350,7 +1354,53 @@ function canDropColumnAt(sourceIndex: number, insertionIndex: number): boolean {
return crossedColumns.every((column) => !column.original);
}
const canShowColumnDragControls = computed(() => isCreateMode.value || structureCapabilities.value.reorderColumn);
const usesLocalTableColumnOrder = computed(() => !isCreateMode.value && supportsLocalTableColumnReorder(databaseType.value, connection.value?.db_type));
const canShowColumnDragControls = computed(() => isCreateMode.value || structureCapabilities.value.reorderColumn || usesLocalTableColumnOrder.value);
function localTableColumnOrderScopeKey(): string {
return tableDataGridColumnOrderScopeKey({
connectionId: props.connectionId,
database: props.database,
schema: props.schema,
tableName: props.tableName,
});
}
function localColumnOrderKeys(items: readonly EditableStructureColumn[]): string[] {
return uniqueDataGridColumnOrderKeys(items.map((column) => column.name));
}
const hasLocalColumnOrderChange = computed(() => {
if (!usesLocalTableColumnOrder.value) return false;
return hasLocalTableColumnOrderChange(columns.value);
});
function applyStoredLocalColumnOrder(items: EditableStructureColumn[]): EditableStructureColumn[] {
if (!usesLocalTableColumnOrder.value) return items;
const orderedKeys = loadTableDataGridColumnOrder(localTableColumnOrderScopeKey());
if (!orderedKeys.length) return items;
const columnKeys = uniqueDataGridColumnOrderKeys(items.map((column) => column.name));
const indexes = orderedColumnIndexes({
availableIndexes: items.map((_, index) => index),
columnKeys,
orderedKeys,
});
return indexes.map((index) => items[index]).filter((column): column is EditableStructureColumn => !!column);
}
function persistLocalColumnOrder(showNotice = true) {
if (!usesLocalTableColumnOrder.value) return;
const scopeKey = localTableColumnOrderScopeKey();
if (hasLocalColumnOrderChange.value) {
saveTableDataGridColumnOrder(scopeKey, localColumnOrderKeys(columns.value));
} else {
removeTableDataGridColumnOrder(scopeKey);
}
notifyTableDataGridColumnOrderChanged(scopeKey);
if (!showNotice || localColumnOrderNoticeShown.value) return;
localColumnOrderNoticeShown.value = true;
toast(t("structureEditor.localColumnOrderNotice"), 4000);
}
function isSqlServerIdentityChecked(column: EditableStructureColumn): boolean {
return !!column.extra.autoIncrement || !!column.extra.identity;
@ -1510,6 +1560,7 @@ function moveColumnTo(index: number, insertionIndex: number) {
const adjustedInsertionIndex = insertionIndex > index ? insertionIndex - 1 : insertionIndex;
nextColumns.splice(adjustedInsertionIndex, 0, column);
columns.value = nextColumns;
persistLocalColumnOrder();
}
function onColumnDragPointerDown(index: number, event: PointerEvent) {
@ -2097,6 +2148,8 @@ async function applyChanges() {
emit("saved", tableComment.value !== originalTableComment.value);
emit("close");
} else {
// Refresh persisted keys after successful renames/additions before metadata reloads.
persistLocalColumnOrder(false);
saving.value = false;
postSaveRefreshing.value = true;
skipNextRefreshVersion = true;
@ -2788,7 +2841,7 @@ watch([activeTab, ddlLoading], ([tab, loading]) => {
type="button"
variant="ghost"
size="icon"
:class="[structureActionButtonClass, canDragColumn(index) ? 'cursor-grab active:cursor-grabbing' : 'cursor-not-allowed']"
:class="[structureActionButtonClass, canDragColumn(index) ? 'cursor-grab active:cursor-grabbing' : 'cursor-not-allowed', hasLocalColumnOrderChange ? 'border-primary/30 bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary' : '']"
:disabled="!canDragColumn(index)"
:title="t('structureEditor.dragColumn')"
:aria-label="t('structureEditor.dragColumn')"

View File

@ -1892,6 +1892,7 @@ export default {
moveColumnUp: "Move column up",
moveColumnDown: "Move column down",
dragColumn: "Drag to reorder column",
localColumnOrderNotice: "Column order is saved locally and does not change the database table structure.",
yes: "Yes",
no: "No",
emptyReadonly: "No records",

View File

@ -1817,6 +1817,7 @@ export default withEnglishFallback({
moveColumnUp: "Mover columna arriba",
moveColumnDown: "Mover columna abajo",
dragColumn: "Arrastra para reordenar columnas",
localColumnOrderNotice: "El orden de las columnas se guarda solo localmente y no modifica la estructura de la base de datos.",
yes: "Sí",
no: "No",
emptyReadonly: "Sin registros",

View File

@ -1815,6 +1815,7 @@ export default withEnglishFallback({
moveColumnUp: "Sposta colonna su",
moveColumnDown: "Sposta colonna giù",
dragColumn: "Trascina per riordinare la colonna",
localColumnOrderNotice: "L'ordine delle colonne viene salvato solo localmente e non modifica la struttura del database.",
yes: "Sì",
no: "No",
emptyReadonly: "Nessun record",

View File

@ -1850,6 +1850,7 @@ export default withEnglishFallback({
moveColumnUp: "列を上に移動",
moveColumnDown: "列を下に移動",
dragColumn: "ドラッグして列の順序を変更",
localColumnOrderNotice: "列の順序はローカルにのみ保存され、データベースのテーブル構造は変更されません。",
yes: "はい",
no: "いいえ",
emptyReadonly: "レコードなし",

View File

@ -1817,6 +1817,7 @@ export default withEnglishFallback({
moveColumnUp: "Mover coluna para cima",
moveColumnDown: "Mover coluna para baixo",
dragColumn: "Arraste para reordenar colunas",
localColumnOrderNotice: "A ordem das colunas é salva apenas localmente e não altera a estrutura do banco de dados.",
yes: "Sim",
no: "Não",
emptyReadonly: "Nenhum registro",

View File

@ -1891,6 +1891,7 @@ export default withEnglishFallback({
moveColumnUp: "上移字段",
moveColumnDown: "下移字段",
dragColumn: "拖拽调整字段顺序",
localColumnOrderNotice: "字段顺序仅保存在本地,不会修改数据库表结构。",
yes: "是",
no: "否",
emptyReadonly: "暂无记录",

View File

@ -1721,6 +1721,7 @@ export default withEnglishFallback({
moveColumnUp: "上移欄位",
moveColumnDown: "下移欄位",
dragColumn: "拖曳調整欄位順序",
localColumnOrderNotice: "欄位順序僅儲存在本機,不會修改資料庫資料表結構。",
yes: "是",
no: "否",
emptyReadonly: "暫無記錄",

View File

@ -0,0 +1,59 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { loadTableDataGridColumnOrder, notifyTableDataGridColumnOrderChanged, removeTableDataGridColumnOrder, saveTableDataGridColumnOrder, TABLE_DATA_GRID_COLUMN_ORDER_CHANGED_EVENT, tableDataGridColumnOrderScopeKey } from "@/lib/dataGrid/dataGridColumnLayoutStorage";
function installLocalStorage() {
const data = new Map<string, string>();
vi.stubGlobal("localStorage", {
getItem: (key: string) => data.get(key) ?? null,
setItem: (key: string, value: string) => data.set(key, value),
removeItem: (key: string) => data.delete(key),
});
}
describe("table data grid column order storage", () => {
beforeEach(installLocalStorage);
afterEach(() => vi.unstubAllGlobals());
it("stores an order independently from the current result column signature", () => {
const scopeKey = tableDataGridColumnOrderScopeKey({
connectionId: "sqlserver-1",
database: "sales",
schema: "core",
tableName: "products",
});
const order = ["name\u00000", "id\u00000"];
saveTableDataGridColumnOrder(scopeKey, order);
expect(loadTableDataGridColumnOrder(scopeKey)).toEqual(order);
});
it("isolates tables and removes a saved order", () => {
const products = tableDataGridColumnOrderScopeKey({ connectionId: "sqlserver-1", database: "sales", schema: "core", tableName: "products" });
const orders = tableDataGridColumnOrderScopeKey({ connectionId: "sqlserver-1", database: "sales", schema: "core", tableName: "orders" });
saveTableDataGridColumnOrder(products, ["name\u00000", "id\u00000"]);
expect(loadTableDataGridColumnOrder(orders)).toEqual([]);
removeTableDataGridColumnOrder(products);
expect(loadTableDataGridColumnOrder(products)).toEqual([]);
});
it("normalizes a missing schema to the database namespace", () => {
const withoutSchema = tableDataGridColumnOrderScopeKey({ connectionId: "sqlite-1", database: "main", tableName: "products" });
const explicitMainSchema = tableDataGridColumnOrderScopeKey({ connectionId: "sqlite-1", database: "main", schema: "main", tableName: "products" });
expect(withoutSchema).toBe(explicitMainSchema);
});
it("notifies other open views when a table order changes", () => {
const dispatchEvent = vi.fn();
vi.stubGlobal("window", { dispatchEvent });
notifyTableDataGridColumnOrderChanged("table-scope");
expect(dispatchEvent).toHaveBeenCalledOnce();
const event = dispatchEvent.mock.calls[0]?.[0] as CustomEvent;
expect(event.type).toBe(TABLE_DATA_GRID_COLUMN_ORDER_CHANGED_EVENT);
expect(event.detail).toEqual({ scopeKey: "table-scope" });
});
});

View File

@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { getTableStructureCapabilities } from "@/lib/table/tableStructureCapabilities";
import { getTableStructureCapabilities, hasLocalTableColumnOrderChange, isPhysicalTableColumnOrderChange, supportsLocalTableColumnReorder } from "@/lib/table/tableStructureCapabilities";
describe("tableStructureCapabilities", () => {
it("uses table rebuilds only for native SQLite connections", () => {
@ -27,4 +27,39 @@ describe("tableStructureCapabilities", () => {
expect(getTableStructureCapabilities("mysql", "mysql").alterStrategy).toBe("direct");
expect(getTableStructureCapabilities("postgres", "postgres").alterStrategy).toBe("direct");
});
it("uses local-only column reordering for editable databases without physical reorder support", () => {
for (const databaseType of ["sqlserver", "postgres", "sqlite", "oracle", "dameng", "duckdb", "informix"] as const) {
expect(supportsLocalTableColumnReorder(databaseType, databaseType)).toBe(true);
}
for (const databaseType of ["mysql", "gbase", "clickhouse"] as const) {
expect(supportsLocalTableColumnReorder(databaseType, databaseType)).toBe(false);
}
expect(supportsLocalTableColumnReorder("influxdb", "influxdb")).toBe(false);
});
it("does not treat local-only reordering as a database structure change", () => {
expect(isPhysicalTableColumnOrderChange("sqlserver", "sqlserver", 0, 2)).toBe(false);
expect(isPhysicalTableColumnOrderChange("postgres", "postgres", 0, 2)).toBe(false);
expect(isPhysicalTableColumnOrderChange("mysql", "mysql", 0, 2)).toBe(true);
});
it("detects local order changes including newly added columns", () => {
const first = { original: {}, originalPosition: 0 };
const second = { original: {}, originalPosition: 1 };
const added = {};
expect(hasLocalTableColumnOrderChange([first, second, added])).toBe(false);
expect(hasLocalTableColumnOrderChange([first, added, second])).toBe(true);
expect(hasLocalTableColumnOrderChange([second, first, added])).toBe(true);
});
it("ignores dropped columns when comparing local order", () => {
const first = { original: {}, originalPosition: 0 };
const dropped = { original: {}, originalPosition: 1, markedForDrop: true };
const third = { original: {}, originalPosition: 2 };
expect(hasLocalTableColumnOrderChange([first, dropped, third])).toBe(false);
});
});

View File

@ -1,8 +1,15 @@
import { safeLocalStorageGet, safeLocalStorageRemove, safeLocalStorageSet } from "@/lib/backend/safeStorage";
const STORAGE_PREFIX = "dbx-data-grid-column-layout:";
const TABLE_STORAGE_PREFIX = "dbx-data-grid-table-column-order:";
const STORAGE_VERSION = 1;
export const TABLE_DATA_GRID_COLUMN_ORDER_CHANGED_EVENT = "dbx:table-data-grid-column-order-changed";
export interface TableDataGridColumnOrderChangedDetail {
scopeKey: string;
}
export interface DataGridColumnLayoutScope {
connectionId?: string;
database?: string;
@ -21,6 +28,18 @@ interface StoredDataGridColumnLayout {
order: string[];
}
export interface TableDataGridColumnOrderScope {
connectionId: string;
database: string;
schema?: string;
tableName: string;
}
interface StoredTableDataGridColumnOrder {
version: number;
order: string[];
}
function normalizeSql(sql?: string): string {
return (sql ?? "").replace(/\s+/g, " ").trim();
}
@ -56,3 +75,37 @@ export function saveDataGridColumnOrder(scopeKey: string, columnKeys: readonly s
export function removeDataGridColumnOrder(scopeKey: string) {
safeLocalStorageRemove(`${STORAGE_PREFIX}${scopeKey}`);
}
export function tableDataGridColumnOrderScopeKey(scope: TableDataGridColumnOrderScope): string {
const namespace = scope.schema?.trim() || scope.database;
return [scope.connectionId, scope.database, namespace, scope.tableName].join("\u0001");
}
export function loadTableDataGridColumnOrder(scopeKey: string): string[] {
const raw = safeLocalStorageGet(`${TABLE_STORAGE_PREFIX}${scopeKey}`);
if (!raw) return [];
try {
const parsed = JSON.parse(raw) as Partial<StoredTableDataGridColumnOrder>;
if (parsed.version !== STORAGE_VERSION || !Array.isArray(parsed.order)) return [];
return parsed.order.filter((key): key is string => typeof key === "string");
} catch {
return [];
}
}
export function saveTableDataGridColumnOrder(scopeKey: string, order: readonly string[]) {
const payload: StoredTableDataGridColumnOrder = {
version: STORAGE_VERSION,
order: [...order],
};
safeLocalStorageSet(`${TABLE_STORAGE_PREFIX}${scopeKey}`, JSON.stringify(payload));
}
export function removeTableDataGridColumnOrder(scopeKey: string) {
safeLocalStorageRemove(`${TABLE_STORAGE_PREFIX}${scopeKey}`);
}
export function notifyTableDataGridColumnOrderChanged(scopeKey: string) {
if (typeof window === "undefined") return;
window.dispatchEvent(new CustomEvent<TableDataGridColumnOrderChangedDetail>(TABLE_DATA_GRID_COLUMN_ORDER_CHANGED_EVENT, { detail: { scopeKey } }));
}

View File

@ -339,6 +339,23 @@ export function canEditTableStructure(dbType?: DatabaseType): boolean {
return caps.createTable || caps.addColumn || caps.alterExistingColumn || caps.createIndex || caps.dropIndex;
}
export function supportsLocalTableColumnReorder(dbType?: DatabaseType, connectionDbType?: DatabaseType): boolean {
const caps = getTableStructureCapabilities(dbType, connectionDbType);
return canEditTableStructure(dbType) && !caps.reorderColumn;
}
export function isPhysicalTableColumnOrderChange(dbType: DatabaseType | undefined, connectionDbType: DatabaseType | undefined, originalPosition: number | undefined, currentPosition: number): boolean {
return getTableStructureCapabilities(dbType, connectionDbType).reorderColumn && originalPosition !== currentPosition;
}
export function hasLocalTableColumnOrderChange(columns: readonly { originalPosition?: number; original?: unknown; markedForDrop?: boolean }[]): boolean {
const activeColumns = columns.filter((column) => !column.markedForDrop);
// Databases without physical reorder support keep existing columns in ordinal order
// and append newly added columns, so compare against that post-save layout.
const databaseOrder = [...activeColumns.filter((column) => column.original).sort((left, right) => (left.originalPosition ?? Number.MAX_SAFE_INTEGER) - (right.originalPosition ?? Number.MAX_SAFE_INTEGER)), ...activeColumns.filter((column) => !column.original)];
return activeColumns.some((column, index) => column !== databaseOrder[index]);
}
export function canAddTableStructureColumn(dbType: DatabaseType | undefined, isCreateMode: boolean): boolean {
const caps = getTableStructureCapabilities(dbType);
return isCreateMode ? caps.createTable : caps.addColumn;