fix(cassandra): report materialized views and skip db-side sort

This commit is contained in:
dienaso authored and GitHub committed 2026-09-06 10:31:47 +08:00
1 parent eea3faa224
commit 0938b6bee0
11 files changed
+296 -42

No files matched your search

@@ -350,7 +350,7 @@ import { useTheme } from "@/composables/useTheme";
import { useConnectionStore } from "@/stores/connectionStore";
import { useQueryStore } from "@/stores/queryStore";
import { useSettingsStore } from "@/stores/settingsStore";
import { simpleDataGridOrderByMatchesSort, simpleDataGridOrderByReferencesMissingColumn, type DataGridSortDirection, type DataGridSortMode } from "@/lib/dataGrid/dataGridSort";
import { databaseSortSupportedForDatabase, simpleDataGridOrderByMatchesSort, simpleDataGridOrderByReferencesMissingColumn, type DataGridSortDirection, type DataGridSortMode } from "@/lib/dataGrid/dataGridSort";
import { resolveGridFocusRestoreTarget } from "@/lib/dataGrid/dataGridFocusRestore";
import { buildOrderedGridRows, type GridInsertRowPosition, type GridNewRowPlacement } from "@/lib/dataGrid/gridNewRowPlacement";
import {
@@ -848,6 +848,7 @@ function sortMenuItems(column: string, columnIndex: number) {
column,
columnIndex,
state: currentColumnSortState(),
databaseSortEnabled: databaseSortSupportedForDatabase(resolvedDatabaseType.value),
labels: {
databaseAscending: t("grid.sortDatabaseAscending"),
databaseDescending: t("grid.sortDatabaseDescending"),
@@ -12514,6 +12515,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
canFilter: canUseWhereSearch.value,
hasSort: !!sortCol.value,
sortMode: sortMode.value,
databaseSortEnabled: databaseSortSupportedForDatabase(resolvedDatabaseType.value),
frozenColumnCount: frozenColumnCount.value,
contextVisibleColIdx: contextHeaderVisibleColIdx.value ?? undefined,
hasColumnSelection: hasColumnSelection.value,
@@ -18,6 +18,46 @@ describe("dataGridContextMenu", () => {
expect(dataGridSelectedSortMenuValue(state, "id", 0)).toBe("local-desc");
});
it("omits database sort entries from the sort menu when database sort is unsupported", () => {
const state = { column: "id", columnIndex: 0, direction: "desc" as const, mode: "local" as const };
const items = createDataGridSortMenuItems({
column: "id",
columnIndex: 0,
state,
databaseSortEnabled: false,
labels: { databaseAscending: "db asc", databaseDescending: "db desc", currentPageAscending: "page asc", currentPageDescending: "page desc", clear: "clear" },
icons: { database: icon, ascending: icon, descending: icon, clear: icon },
});
expect(items.map((item) => item.value)).toEqual(["local-asc", "local-desc", "clear"]);
expect(items[0]?.separatorBefore).toBeFalsy();
expect(dataGridSelectedSortMenuValue(state, "id", 0)).toBe("local-desc");
});
it("omits database sort entries from the column context menu when database sort is unsupported", () => {
const action = vi.fn();
const filter = createDataGridFilterSubmenu({
label: "filter",
icon,
labels: { equals: "equals", notEquals: "not equals", like: "like", notLike: "not like", lessThan: "less", greaterThan: "greater", isNull: "null", isNotNull: "not null", clear: "clear" },
apply: action,
clear: action,
});
const items = createDataGridColumnContextMenuItems({
headerColumn: false,
contextColumn: true,
canCopyAlterSql: false,
canFilter: true,
hasSort: true,
sortMode: "database",
databaseSortEnabled: false,
labels: { copyName: "copy name", copyNames: "copy names", details: "details", copyAlterSql: "alter", databaseAscending: "db asc", databaseDescending: "db desc", localAscending: "local asc", localDescending: "local desc", clearSort: "clear sort" },
icons: { copy: icon, columnDetails: icon, database: icon, ascending: icon, descending: icon, clearSort: icon },
actions: { copyName: action, copyNames: action, details: action, copyAlterSql: action, sort: action },
filterSubmenu: filter,
});
expect(items.map((item) => item.label)).toEqual(["local asc", "local desc", "clear sort", "", "filter"]);
});
it("omits unavailable server actions and disables unavailable formatter", () => {
const items = createDataGridCompactColumnActionItems({
labels: { formatter: "formatter", clearFormatter: "clear formatter", localFilter: "local", serverFilter: "server" },
@@ -1,5 +1,17 @@
import { describe, expect, it } from "vitest";
import { compareDataGridValues, simpleDataGridOrderByColumn, simpleDataGridOrderByMatchesSort, simpleDataGridOrderByReferencesMissingColumn } from "@/lib/dataGrid/dataGridSort";
import { compareDataGridValues, databaseSortSupportedForDatabase, simpleDataGridOrderByColumn, simpleDataGridOrderByMatchesSort, simpleDataGridOrderByReferencesMissingColumn } from "@/lib/dataGrid/dataGridSort";
describe("databaseSortSupportedForDatabase", () => {
it("keeps database-side sorting for databases that support ORDER BY", () => {
expect(databaseSortSupportedForDatabase("postgres")).toBe(true);
expect(databaseSortSupportedForDatabase("mysql")).toBe(true);
expect(databaseSortSupportedForDatabase(undefined)).toBe(true);
});
it("disables database-side sorting for Cassandra browse queries", () => {
expect(databaseSortSupportedForDatabase("cassandra")).toBe(false);
});
});
describe("simpleDataGridOrderByColumn", () => {
it.each([
@@ -77,6 +77,7 @@ export function createDataGridColumnContextMenuItems(options: {
canFilter: boolean;
hasSort: boolean;
sortMode: "database" | "local";
databaseSortEnabled?: boolean;
frozenColumnCount?: number;
contextVisibleColIdx?: number;
hasColumnSelection?: boolean;
@@ -94,13 +95,14 @@ export function createDataGridColumnContextMenuItems(options: {
}
if (!options.contextColumn && !options.headerColumn) return items;
if (options.contextColumn) {
items.push(
{ label: options.labels.databaseAscending, action: () => options.actions.sort("asc", "database"), icon: options.icons.database },
{ label: options.labels.databaseDescending, action: () => options.actions.sort("desc", "database"), icon: options.icons.database },
{ label: "", separator: true },
{ label: options.labels.localAscending, action: () => options.actions.sort("asc", "local"), icon: options.icons.ascending },
{ label: options.labels.localDescending, action: () => options.actions.sort("desc", "local"), icon: options.icons.descending },
);
if (options.databaseSortEnabled !== false) {
items.push(
{ label: options.labels.databaseAscending, action: () => options.actions.sort("asc", "database"), icon: options.icons.database },
{ label: options.labels.databaseDescending, action: () => options.actions.sort("desc", "database"), icon: options.icons.database },
{ label: "", separator: true },
);
}
items.push({ label: options.labels.localAscending, action: () => options.actions.sort("asc", "local"), icon: options.icons.ascending }, { label: options.labels.localDescending, action: () => options.actions.sort("desc", "local"), icon: options.icons.descending });
if (options.hasSort) items.push({ label: options.labels.clearSort, action: () => options.actions.sort(null, options.sortMode), icon: options.icons.clearSort });
if (options.canFilter) items.push({ label: "", separator: true }, options.filterSubmenu);
}
@@ -210,13 +212,17 @@ export function dataGridSelectedSortMenuValue(state: DataGridColumnSortState, co
return dataGridColumnIsSorted(state, column, columnIndex) ? `${state.mode}-${state.direction}` : undefined;
}
export function createDataGridSortMenuItems(options: { column: string; columnIndex: number; state: DataGridColumnSortState; labels: SortMenuLabels; icons: SortMenuIcons }): DataGridColumnMenuItem[] {
const { column, columnIndex, state, labels, icons } = options;
export function createDataGridSortMenuItems(options: { column: string; columnIndex: number; state: DataGridColumnSortState; labels: SortMenuLabels; icons: SortMenuIcons; databaseSortEnabled?: boolean }): DataGridColumnMenuItem[] {
const { column, columnIndex, state, labels, icons, databaseSortEnabled = true } = options;
const sorted = dataGridColumnIsSorted(state, column, columnIndex);
return [
{ label: labels.databaseAscending, value: "database-asc", icon: icons.database, checked: sorted && state.direction === "asc" && state.mode === "database" },
{ label: labels.databaseDescending, value: "database-desc", icon: icons.database, checked: sorted && state.direction === "desc" && state.mode === "database" },
{ label: labels.currentPageAscending, value: "local-asc", icon: icons.ascending, checked: sorted && state.direction === "asc" && state.mode === "local", separatorBefore: true },
...(databaseSortEnabled
? [
{ label: labels.databaseAscending, value: "database-asc", icon: icons.database, checked: sorted && state.direction === "asc" && state.mode === "database" },
{ label: labels.databaseDescending, value: "database-desc", icon: icons.database, checked: sorted && state.direction === "desc" && state.mode === "database" },
]
: []),
{ label: labels.currentPageAscending, value: "local-asc", icon: icons.ascending, checked: sorted && state.direction === "asc" && state.mode === "local", separatorBefore: databaseSortEnabled },
{ label: labels.currentPageDescending, value: "local-desc", icon: icons.descending, checked: sorted && state.direction === "desc" && state.mode === "local" },
{ label: labels.clear, value: "clear", icon: icons.clear, disabled: !sorted, separatorBefore: true },
];
@@ -1,8 +1,18 @@
import type { DatabaseType } from "@/types/database";
import { isNumericColumnType } from "@/lib/dataGrid/dataGridColumnType";
export type DataGridSortDirection = "asc" | "desc";
export type DataGridSortMode = "database" | "local";
/**
* Cassandra rejects ORDER BY unless the partition key is restricted by an
* equality filter, so a browse query can never be sorted database-side there
* and the sort would keep failing on every refresh or page jump while applied.
*/
export function databaseSortSupportedForDatabase(databaseType?: DatabaseType): boolean {
return databaseType !== "cassandra";
}
export interface DataGridSortState {
column: string | null;
columnIndex: number | null;