fix(mongodb): route find result sorting through shell sort clauses

Use find().sort() for MongoDB query grid sorting instead of SQL ORDER BY,
and normalize document browser sort input the same way as filters.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
gggdsg 2026-07-03 15:47:38 +08:00 committed by GitHub
parent 06231d512f
commit e7ec2ecaf2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 71 additions and 2 deletions

View File

@ -16,7 +16,18 @@ import * as api from "@/lib/api";
import { useConnectionStore } from "@/stores/connectionStore";
import { clampSearchSplitWidth } from "@/lib/dataGridSearchSplit";
import { documentViewerFontStyle } from "@/lib/documentViewerFontStyle";
import { buildDocumentFilterCondition, combineDocumentFilterConditions, currentDocumentFilterJson, defaultDocumentFilterRule, documentFilterModeNeedsValue, documentFilterModeOptions, documentStoreProviderFor, type DocumentFilterMode, type DocumentFilterRule } from "@/lib/documentStoreProvider";
import {
buildDocumentFilterCondition,
combineDocumentFilterConditions,
currentDocumentFilterJson,
currentDocumentSortJson,
defaultDocumentFilterRule,
documentFilterModeNeedsValue,
documentFilterModeOptions,
documentStoreProviderFor,
type DocumentFilterMode,
type DocumentFilterRule,
} from "@/lib/documentStoreProvider";
import { buildMongoInsertDocument, buildMongoUpdateDocument, formatMongoShellLiteral, parseMongoDocumentInputValue, type MongoInputValue } from "@/lib/mongoDocumentValues";
import { normalizeResultPageSize } from "@/lib/paginationPageSize";
import { useSettingsStore } from "@/stores/settingsStore";
@ -433,7 +444,7 @@ async function load() {
const previousSelectedId = previousSelectedIdx === null ? null : documentIdentity(documents.value[previousSelectedIdx]);
try {
const filter = currentDocumentFilter();
const sort = sortInput.value.trim() || undefined;
const sort = currentDocumentSortJson(sortInput.value);
const result = await api.documentFindDocuments(props.connectionId, props.database, props.collection, page.value * pageSize.value, pageSize.value, filter, undefined, sort, executionId);
if (documentLoadExecutionId.value !== executionId) return;
const nextDocuments = result.documents.map(asRecord);

View File

@ -10,6 +10,7 @@ import * as api from "@/lib/api";
import type { QueryTab } from "@/types/database";
import { useToast } from "@/composables/useToast";
import { effectiveDatabaseTypeForConnection, metadataSchemaForConnection } from "@/lib/jdbcDialect";
import { applyMongoFindSort } from "@/lib/mongoShellCommand";
import { uuid } from "@/lib/utils";
import type { DataGridSortMode } from "@/lib/dataGridSort";
@ -235,6 +236,22 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
}
const config = connectionStore.getConfig(tab.connectionId);
if (effectiveDatabaseTypeForConnection(config) === "mongodb") {
const sortedSql = applyMongoFindSort(baseSql, column, direction);
if (!sortedSql) {
toast(t("grid.sortUnsupported"), 5000);
return;
}
queryStore.updateSql(tab.id, sortedSql);
await queryStore.executeTabSql(tab.id, sortedSql, {
resultBaseSql: baseSql,
resultSortedSql: sortedSql,
preserveResultDuringExecution: true,
preserveTotalRowCountDuringExecution: true,
});
return;
}
const built = await api.buildSortedQuerySql({
originalSql: baseSql,
databaseType: effectiveDatabaseTypeForConnection(config),

View File

@ -151,6 +151,11 @@ export function currentDocumentFilterJson(input: string, structured: Record<stri
return Object.keys(filter).length ? JSON.stringify(filter) : undefined;
}
export function currentDocumentSortJson(input: string): string | undefined {
const sort = parseDocumentFilterInput(input);
return Object.keys(sort).length ? JSON.stringify(sort) : undefined;
}
function parseDocumentFilterValue(raw: string): unknown {
const trimmed = raw.trim();
if (!trimmed) return "";

View File

@ -112,6 +112,27 @@ export function parseMongoFindCommand(input: string): MongoFindCommand | null {
};
}
export function applyMongoFindSort(input: string, column: string, direction: "asc" | "desc"): string | null {
const source = input.trim().replace(/;$/, "").trim();
const parsed = parseMongoFindCommand(source);
if (!parsed) return null;
const target = parseFindTarget(source);
if (!target) return null;
const findOpenIndex = source.indexOf("(", target.findCallIndex);
const findCloseIndex = findMatchingParen(source, findOpenIndex);
if (findCloseIndex < 0) return null;
const prefix = source.slice(0, findCloseIndex + 1);
const chainSource = source.slice(findCloseIndex + 1).trim();
if (chainSource && !chainSource.startsWith(".")) return null;
const chain = removeChainedMethodCall(chainSource, "sort");
const sortCall = `.sort(${JSON.stringify({ [column]: direction === "asc" ? 1 : -1 })})`;
return `${prefix}${sortCall}${chain}`;
}
export function parseMongoCountDocumentsCommand(input: string): MongoCountDocumentsCommand | null {
const source = input.trim().replace(/;$/, "").trim();
// Accept deprecated Mongo shell count helpers for old server workflows, but
@ -934,6 +955,21 @@ function readChainedIntegerArgument(source: string, name: string, fallback: numb
return value;
}
function removeChainedMethodCall(chain: string, name: string): string {
if (!chain.trim()) return "";
let result = chain.trim();
const pattern = chainedMethodCallPattern(name);
let match: RegExpExecArray | null;
while ((match = pattern.exec(result)) !== null) {
const openIndex = result.indexOf("(", match.index);
const closeIndex = findMatchingParen(result, openIndex);
if (closeIndex < 0) break;
result = `${result.slice(0, match.index)}${result.slice(closeIndex + 1)}`.trim();
pattern.lastIndex = 0;
}
return result;
}
function readChainedCallArgument(source: string, name: string): string | undefined {
const pattern = chainedMethodCallPattern(name);
let match = pattern.exec(source);