feat(query): support editing without selected primary keys
This commit is contained in:
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,
|
||||
|
||||
Reference in new issue
Block a user