feat(query): support editing without selected primary keys

This commit is contained in:
zipg authored and GitHub committed 2026-07-10 18:57:37 +08:00
1 parent 7b47c81ad7
commit c35e5c560a
11 files changed
+935 -88

No files matched your search

@@ -0,0 +1,88 @@
import { computed } from "vue";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { useDataGridActions } from "@/composables/useDataGridActions";
import type { QueryTab } from "@/types/database";
const mocks = vi.hoisted(() => ({
buildSortedQuerySql: vi.fn(),
executeTabSql: vi.fn(),
getConfig: vi.fn(),
}));
vi.mock("vue-i18n", () => ({
useI18n: () => ({ t: (key: string) => key }),
}));
vi.mock("@/lib/backend/api", () => ({
buildSortedQuerySql: mocks.buildSortedQuerySql,
}));
vi.mock("@/stores/connectionStore", () => ({
useConnectionStore: () => ({
getConfig: mocks.getConfig,
}),
}));
vi.mock("@/stores/queryStore", () => ({
useQueryStore: () => ({
executeTabSql: mocks.executeTabSql,
}),
}));
vi.mock("@/stores/settingsStore", () => ({
useSettingsStore: () => ({
editorSettings: { pageSize: 100 },
}),
}));
vi.mock("@/composables/useToast", () => ({
useToast: () => ({ toast: vi.fn() }),
}));
describe("useDataGridActions", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.getConfig.mockReturnValue({ id: "postgres-1", db_type: "postgres" });
mocks.buildSortedQuerySql.mockResolvedValue({ ok: true, sql: "SELECT sorted" });
});
it("excludes hidden primary keys and remaps the selected column for database sorting", async () => {
const tab = {
id: "tab-1",
connectionId: "postgres-1",
database: "app",
title: "Query",
sql: "SELECT name, email FROM users",
resultBaseSql: "SELECT name, email FROM users",
result: {
columns: ["name", "__DBX_PK_0", "email"],
hidden_column_indexes: [1],
rows: [["Alice", 7, "alice@example.com"]],
affected_rows: 0,
execution_time_ms: 1,
},
mode: "query",
isDirty: false,
isExecuting: false,
isCancelling: false,
isExplaining: false,
} as QueryTab;
const actions = useDataGridActions(computed(() => tab));
await actions.onSort("email", 2, "asc");
expect(mocks.executeTabSql).toHaveBeenCalledWith(
"tab-1",
"SELECT name, email FROM users",
expect.objectContaining({
resultBaseSql: "SELECT name, email FROM users",
querySort: {
resultColumns: ["name", "email"],
columnIndex: 1,
column: "email",
direction: "asc",
},
}),
);
});
});
@@ -17,6 +17,19 @@ import { queryResultBaseSql, queryResultExecutionSql } from "@/lib/tabs/tabPrese
const DATA_TAB_METADATA_TTL_MS = 30_000;
function visibleQuerySortColumns(columns: string[], hiddenColumnIndexes: number[] | undefined, columnIndex: number): { resultColumns: string[]; columnIndex: number } | undefined {
const hiddenIndexes = new Set(hiddenColumnIndexes ?? []);
const resultColumns: string[] = [];
let visibleColumnIndex: number | undefined;
for (const [index, resultColumn] of columns.entries()) {
if (hiddenIndexes.has(index)) continue;
if (index === columnIndex) visibleColumnIndex = resultColumns.length;
resultColumns.push(resultColumn);
}
if (visibleColumnIndex === undefined) return undefined;
return { resultColumns, columnIndex: visibleColumnIndex };
}
export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>) {
const { t } = useI18n();
const { toast } = useToast();
@@ -150,9 +163,20 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
return;
}
if (tab.resultSortedSql) {
await queryStore.executeTabSql(tab.id, tab.resultSortedSql, {
const sortColumns = visibleQuerySortColumns(tab.result?.columns ?? [], tab.result?.hidden_column_indexes, tab.resultSortColumnIndex ?? -1);
const rebuildHiddenKeySort = !!tab.result?.hidden_column_indexes?.length && tab.resultSortMode === "database" && !!tab.resultSortDirection && !!tab.resultSortColumn && !!sortColumns;
await queryStore.executeTabSql(tab.id, rebuildHiddenKeySort ? (tab.resultBaseSql ?? tab.sql) : tab.resultSortedSql, {
resultBaseSql: tab.resultBaseSql ?? tab.sql,
resultSortedSql: tab.resultSortedSql,
...(rebuildHiddenKeySort
? {
querySort: {
resultColumns: sortColumns.resultColumns,
columnIndex: sortColumns.columnIndex,
column: tab.resultSortColumn!,
direction: tab.resultSortDirection!,
},
}
: { resultSortedSql: tab.resultSortedSql }),
preserveResultDuringExecution: true,
preserveTotalRowCountDuringExecution: true,
});
@@ -173,7 +197,9 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
const tab = activeTab.value;
if (!tab) return;
if (tab.mode !== "data") {
const baseSql = queryResultExecutionSql(tab);
const sortColumns = visibleQuerySortColumns(tab.result?.columns ?? [], tab.result?.hidden_column_indexes, tab.resultSortColumnIndex ?? -1);
const hasDatabaseSort = !!tab.result?.hidden_column_indexes?.length && tab.resultSortMode === "database" && !!tab.resultSortDirection && !!tab.resultSortColumn && !!sortColumns;
const baseSql = hasDatabaseSort ? queryResultBaseSql(tab) : queryResultExecutionSql(tab);
if (!baseSql.trim()) return;
const expectedNextOffset = (tab.resultPageOffset ?? 0) + (tab.resultPageLimit ?? limit);
const sessionId = tab.result?.has_more && tab.result?.session_id && offset === expectedNextOffset && limit === tab.resultPageLimit ? tab.result.session_id : undefined;
@@ -181,6 +207,16 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
await queryStore.executeTabSql(tab.id, baseSql, {
resultBaseSql,
resultSortedSql: tab.resultSortedSql,
...(hasDatabaseSort
? {
querySort: {
resultColumns: sortColumns.resultColumns,
columnIndex: sortColumns.columnIndex,
column: tab.resultSortColumn!,
direction: tab.resultSortDirection!,
},
}
: {}),
pagination: { offset, limit, sessionId },
preserveResultDuringExecution: true,
preserveTotalRowCountDuringExecution: true,
@@ -260,22 +296,41 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
return;
}
const built = await api.buildSortedQuerySql({
originalSql: baseSql,
databaseType: effectiveDatabaseTypeForConnection(config),
resultColumns: tab.result?.columns ?? [],
columnIndex,
column,
direction,
});
if (!built.ok || !built.sql) {
const sortColumns = visibleQuerySortColumns(tab.result?.columns ?? [], tab.result?.hidden_column_indexes, columnIndex);
if (!sortColumns) {
toast(t("grid.sortUnsupported"), 5000);
return;
}
await queryStore.executeTabSql(tab.id, built.sql, {
if (!tab.result?.hidden_column_indexes?.length) {
const built = await api.buildSortedQuerySql({
originalSql: baseSql,
databaseType: effectiveDatabaseTypeForConnection(config),
resultColumns: sortColumns.resultColumns,
columnIndex: sortColumns.columnIndex,
column,
direction,
});
if (!built.ok || !built.sql) {
toast(t("grid.sortUnsupported"), 5000);
return;
}
await queryStore.executeTabSql(tab.id, built.sql, {
resultBaseSql: baseSql,
resultSortedSql: built.sql,
preserveResultDuringExecution: true,
preserveTotalRowCountDuringExecution: true,
replaceActiveResultInGroup: true,
});
return;
}
await queryStore.executeTabSql(tab.id, baseSql, {
resultBaseSql: baseSql,
resultSortedSql: built.sql,
querySort: {
resultColumns: sortColumns.resultColumns,
columnIndex: sortColumns.columnIndex,
column,
direction,
},
preserveResultDuringExecution: true,
preserveTotalRowCountDuringExecution: true,
replaceActiveResultInGroup: true,