Files
dbx/src/lib/queryResultSort.ts
T
Bacon2994 3b4b6c1ff7 fix(query): 修复查询结果表头提示与全量排序 (#164)
* fix(grid): 修复查询结果表头提示与排序

修复 MySQL 查询结果列元数据取错库名的问题,并补齐查询结果全量排序链路,避免表头提示与排序行为失效。

* fix(query): 修复结果排序重复列名报错
2026-05-08 18:31:09 +08:00

72 lines
2.3 KiB
TypeScript

import type { DatabaseType } from "../types/database.ts";
import { quoteTableIdentifier } from "./tableSelectSql.ts";
import { findStatementAtCursor } from "./sqlStatementSplit.ts";
export type QuerySortDirection = "asc" | "desc";
export interface SortedQuerySqlResult {
ok: true;
sql: string;
}
export interface SortedQuerySqlError {
ok: false;
reason: "empty" | "multi" | "not_select" | "with";
}
export function buildSortedQuerySql(
originalSql: string,
databaseType: DatabaseType | undefined,
resultColumns: string[],
columnIndex: number,
column: string,
direction: QuerySortDirection,
): SortedQuerySqlResult | SortedQuerySqlError {
const baseSql = originalSql.trim();
if (!baseSql) return { ok: false, reason: "empty" };
const statement = findStatementAtCursor(baseSql, 0)
.trim()
.replace(/;+\s*$/, "")
.trim();
if (!statement) return { ok: false, reason: "empty" };
if (statement.length !== baseSql.replace(/;+\s*$/, "").trim().length) {
return { ok: false, reason: "multi" };
}
if (/^\s*WITH\b/i.test(statement)) {
return { ok: false, reason: "with" };
}
if (!/^\s*SELECT\b/i.test(statement)) {
return { ok: false, reason: "not_select" };
}
const aliases = buildDerivedColumnAliases(resultColumns);
const sortAlias = aliases[columnIndex] ?? aliases[resultColumns.indexOf(column)] ?? fallbackAlias(columnIndex);
const quotedColumn = quoteTableIdentifier(databaseType, sortAlias);
const aliasList = aliases.map((alias) => quoteTableIdentifier(databaseType, alias)).join(", ");
return {
ok: true,
sql: `SELECT * FROM (${statement}) t(${aliasList}) ORDER BY ${quotedColumn} ${direction.toUpperCase()};`,
};
}
function buildDerivedColumnAliases(resultColumns: string[]): string[] {
const seen = new Map<string, number>();
return resultColumns.map((column, index) => {
const base = normalizeAliasBase(column, index);
const count = (seen.get(base) ?? 0) + 1;
seen.set(base, count);
return count === 1 ? base : `${base}_${count}`;
});
}
function normalizeAliasBase(column: string, index: number): string {
const compact = column.trim().replace(/\s+/g, "_");
const safe = compact.replace(/[^\p{L}\p{N}_$]/gu, "_").replace(/^_+|_+$/g, "");
return safe || fallbackAlias(index);
}
function fallbackAlias(index: number): string {
return `column_${index + 1}`;
}