fix(grid): sort negative values correctly on current page
This commit is contained in:
parent
c3547fcb61
commit
39fcaa15cb
|
|
@ -1,3 +1,5 @@
|
|||
import { isNumericColumnType } from "@/lib/dataGrid/dataGridColumnType";
|
||||
|
||||
export type DataGridSortDirection = "asc" | "desc";
|
||||
export type DataGridSortMode = "database" | "local";
|
||||
|
||||
|
|
@ -60,25 +62,25 @@ type DataGridRow = DataGridCellValue[];
|
|||
|
||||
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
|
||||
|
||||
export function sortDataGridRows<T extends DataGridRow>(rows: readonly T[], columnIndex: number, direction: DataGridSortDirection): T[] {
|
||||
return sortDataGridRowIndexes(rows, columnIndex, direction).map((index) => rows[index]!);
|
||||
export function sortDataGridRows<T extends DataGridRow>(rows: readonly T[], columnIndex: number, direction: DataGridSortDirection, columnType?: string): T[] {
|
||||
return sortDataGridRowIndexes(rows, columnIndex, direction, columnType).map((index) => rows[index]!);
|
||||
}
|
||||
|
||||
export function sortDataGridRowIndexes(rows: readonly DataGridRow[], columnIndex: number, direction: DataGridSortDirection): number[] {
|
||||
export function sortDataGridRowIndexes(rows: readonly DataGridRow[], columnIndex: number, direction: DataGridSortDirection, columnType?: string): number[] {
|
||||
const directionMultiplier = direction === "asc" ? 1 : -1;
|
||||
return rows
|
||||
.map((row, index) => ({ row, index }))
|
||||
.sort((left, right) => {
|
||||
const emptyCompared = compareEmptyValues(left.row[columnIndex], right.row[columnIndex]);
|
||||
if (emptyCompared !== null) return emptyCompared;
|
||||
const compared = compareDataGridValues(left.row[columnIndex], right.row[columnIndex]);
|
||||
const compared = compareDataGridValues(left.row[columnIndex], right.row[columnIndex], columnType);
|
||||
if (compared !== 0) return compared * directionMultiplier;
|
||||
return left.index - right.index;
|
||||
})
|
||||
.map((item) => item.index);
|
||||
}
|
||||
|
||||
export function compareDataGridValues(left: DataGridCellValue, right: DataGridCellValue): number {
|
||||
export function compareDataGridValues(left: DataGridCellValue, right: DataGridCellValue, columnType?: string): number {
|
||||
const leftEmpty = left == null;
|
||||
const rightEmpty = right == null;
|
||||
if (leftEmpty || rightEmpty) {
|
||||
|
|
@ -86,6 +88,11 @@ export function compareDataGridValues(left: DataGridCellValue, right: DataGridCe
|
|||
return leftEmpty ? 1 : -1;
|
||||
}
|
||||
|
||||
if (isNumericColumnType(columnType)) {
|
||||
const numericCompared = compareNumericCellValues(left, right);
|
||||
if (numericCompared !== null) return numericCompared;
|
||||
}
|
||||
|
||||
if (typeof left === "number" && typeof right === "number") {
|
||||
return compareNumbers(left, right);
|
||||
}
|
||||
|
|
@ -102,6 +109,53 @@ export function compareDataGridValues(left: DataGridCellValue, right: DataGridCe
|
|||
return collator.compare(String(left), String(right));
|
||||
}
|
||||
|
||||
interface NumericSortValue {
|
||||
sign: -1 | 0 | 1;
|
||||
magnitude: bigint;
|
||||
digits: string;
|
||||
}
|
||||
|
||||
function compareNumericCellValues(left: DataGridCellValue, right: DataGridCellValue): number | null {
|
||||
const leftNumber = parseNumericSortValue(left);
|
||||
const rightNumber = parseNumericSortValue(right);
|
||||
if (!leftNumber || !rightNumber) return null;
|
||||
if (leftNumber.sign !== rightNumber.sign) return leftNumber.sign - rightNumber.sign;
|
||||
if (leftNumber.sign === 0) return 0;
|
||||
|
||||
let compared = compareBigInts(leftNumber.magnitude, rightNumber.magnitude);
|
||||
if (compared === 0) {
|
||||
const width = Math.max(leftNumber.digits.length, rightNumber.digits.length);
|
||||
compared = leftNumber.digits.padEnd(width, "0").localeCompare(rightNumber.digits.padEnd(width, "0"));
|
||||
}
|
||||
return leftNumber.sign === 1 ? compared : -compared;
|
||||
}
|
||||
|
||||
function parseNumericSortValue(value: DataGridCellValue): NumericSortValue | null {
|
||||
if (typeof value !== "string" && typeof value !== "number") return null;
|
||||
const text = String(value).trim();
|
||||
const negative = text.startsWith("-");
|
||||
const unsigned = /^[+-]/.test(text) ? text.slice(1) : text;
|
||||
const match = unsigned.match(/^(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/);
|
||||
if (!match) return null;
|
||||
|
||||
const integerDigits = match[1] ?? "";
|
||||
const fractionDigits = match[2] ?? "";
|
||||
const allDigits = `${integerDigits}${fractionDigits}`;
|
||||
if (!allDigits) return null;
|
||||
const leadingZeroCount = allDigits.match(/^0*/)?.[0].length ?? 0;
|
||||
const digits = allDigits.slice(leadingZeroCount);
|
||||
if (!digits) return { sign: 0, magnitude: 0n, digits: "0" };
|
||||
|
||||
const exponent = BigInt(match[3] ?? "0");
|
||||
const magnitude = BigInt(integerDigits.length - leadingZeroCount) + exponent;
|
||||
return { sign: negative ? -1 : 1, magnitude, digits };
|
||||
}
|
||||
|
||||
function compareBigInts(left: bigint, right: bigint): number {
|
||||
if (left === right) return 0;
|
||||
return left < right ? -1 : 1;
|
||||
}
|
||||
|
||||
function compareEmptyValues(left: DataGridCellValue, right: DataGridCellValue): number | null {
|
||||
const leftEmpty = left == null;
|
||||
const rightEmpty = right == null;
|
||||
|
|
|
|||
|
|
@ -1159,7 +1159,8 @@ export const useQueryStore = defineStore("query", () => {
|
|||
}
|
||||
|
||||
const originalRows = tab.resultLocalSortOriginalRows;
|
||||
const rowIndexes = direction ? sortDataGridRowIndexes(originalRows, columnIndex, direction) : originalRows.map((_, index) => index);
|
||||
const columnType = tab.result.column_types?.[columnIndex];
|
||||
const rowIndexes = direction ? sortDataGridRowIndexes(originalRows, columnIndex, direction, columnType) : originalRows.map((_, index) => index);
|
||||
const rows = rowIndexes.map((index) => originalRows[index]!);
|
||||
const originalMongoDocuments = tab.resultLocalSortOriginalMongoDocuments;
|
||||
const mongo_documents = originalMongoDocuments ? rowIndexes.map((index) => originalMongoDocuments[index]) : undefined;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,13 @@ test("sortDataGridRows uses natural string order and keeps equal values stable",
|
|||
]);
|
||||
});
|
||||
|
||||
test("sortDataGridRows sorts numeric strings by signed value for numeric columns", () => {
|
||||
const rows = [["-27700"], ["-78800"], ["297500"], ["9007199254740993"], ["9007199254740992"], ["-1.2e3"], ["-1.19e3"], ["1.01"], ["1.001"]];
|
||||
|
||||
assert.deepEqual(sortDataGridRows(rows, 0, "asc", "NUMBER"), [["-78800"], ["-27700"], ["-1.2e3"], ["-1.19e3"], ["1.001"], ["1.01"], ["297500"], ["9007199254740992"], ["9007199254740993"]]);
|
||||
assert.deepEqual(sortDataGridRows([["-27700"], ["-78800"]], 0, "asc", "VARCHAR2"), [["-27700"], ["-78800"]]);
|
||||
});
|
||||
|
||||
test("sortDataGridRows sorts ISO date strings by time", () => {
|
||||
const rows = [["2026-02-01"], ["2025-12-31"], ["2026-01-01"]];
|
||||
|
||||
|
|
|
|||
|
|
@ -1175,6 +1175,26 @@ test("sortTabResultLocally sorts current rows and restores original order", () =
|
|||
assert.equal(tab.resultLocalSortOriginalMongoCopyDocuments, undefined);
|
||||
});
|
||||
|
||||
test("sortTabResultLocally uses result column types for numeric strings", () => {
|
||||
setActivePinia(createPinia());
|
||||
const store = useQueryStore();
|
||||
const tabId = store.createTab("conn-1", "db");
|
||||
const tab = store.tabs.find((item) => item.id === tabId);
|
||||
assert.ok(tab);
|
||||
|
||||
tab.result = {
|
||||
columns: ["QUANTITY_IN_STOCK"],
|
||||
column_types: ["NUMBER"],
|
||||
rows: [["-27700"], ["-78800"], ["297500"]],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: 1,
|
||||
};
|
||||
|
||||
store.sortTabResultLocally(tabId, "QUANTITY_IN_STOCK", 0, "asc");
|
||||
|
||||
assert.deepEqual(tab.result.rows, [["-78800"], ["-27700"], ["297500"]]);
|
||||
});
|
||||
|
||||
test("selecting a result run restores its displayed result without changing SQL draft", async () => {
|
||||
setActivePinia(createPinia());
|
||||
const store = useQueryStore();
|
||||
|
|
@ -4674,7 +4694,10 @@ test("mongo dropIndexes execution exposes partial failures and refreshes loaded
|
|||
assert.equal(indexRefreshRequested, true);
|
||||
const indexGroup = connectionStore.treeNodes[0]?.children?.[0]?.children?.[0]?.children?.[0];
|
||||
assert.equal(indexGroup?.isExpanded, false);
|
||||
assert.deepEqual(indexGroup?.children?.map((node) => node.label), ["_id_ (_id)"]);
|
||||
assert.deepEqual(
|
||||
indexGroup?.children?.map((node) => node.label),
|
||||
["_id_ (_id)"],
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
restoreStorage();
|
||||
|
|
|
|||
Loading…
Reference in New Issue