feat(settings): configurable query result max rows with unified load limit

This commit is contained in:
zipg authored and GitHub committed 2026-08-11 13:07:35 +08:00
1 parent 89d65e96ed
commit 0c407c2d03
26 files changed
+446 -187

No files matched your search

+31 -12
View File
@@ -47,7 +47,8 @@ import { frontendQueryTimeoutDelayMs, frontendQueryTimeoutSecsForSql, queryTimeo
import { queryResultNameFromPreamble, queryResultSourceLabel } from "@/lib/sql/queryResultSource";
import { beginDataGridNativeSelectionBlock, finishDataGridNativeSelectionBlock } from "@/lib/dataGrid/dataGridNativeSelection";
import { simpleDataGridOrderByReferencesMissingColumn, sortDataGridRowIndexes, type DataGridSortDirection } from "@/lib/dataGrid/dataGridSort";
import { MAX_RESULT_PAGE_SIZE, normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize";
import { normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize";
import { agentProtocolQueryResultMaxRows, capQueryResultTotal, effectiveQueryResultMaxRows, limitQueryPagination, queryResultLimitReached } from "@/lib/dataGrid/queryResultRowLimit";
import { elasticsearchRestRequestRanges, executableStatementRanges, splitSqlStatementRanges } from "@/lib/sql/sqlStatementRanges";
import { externalSqlFileDisplayTitles, normalizeExternalSqlPath } from "@/lib/sql/sqlFileOpen";
import { clearDataGridPendingSnapshotsForTab } from "@/composables/useDataGridEditor";
@@ -3610,6 +3611,7 @@ export const useQueryStore = defineStore("query", () => {
traceId: string;
elapsed: () => string;
timeoutSecs: number;
maxRows?: number;
}) {
const resultRowCount = options.result.rows.length;
if (resultRowCount <= 0) {
@@ -3652,10 +3654,11 @@ export const useQueryStore = defineStore("query", () => {
setQueryTotalRowCountIfCurrent(options.tabId, options.executionId, options.result, undefined);
return;
}
setQueryTotalRowCountIfCurrent(options.tabId, options.executionId, options.result, total);
const cappedTotal = capQueryResultTotal(total, options.maxRows);
setQueryTotalRowCountIfCurrent(options.tabId, options.executionId, options.result, cappedTotal);
queryExecutionLog("info", "count:done", {
traceId: options.traceId,
total,
total: cappedTotal,
elapsed: options.elapsed(),
});
} catch (error) {
@@ -4284,6 +4287,8 @@ export const useQueryStore = defineStore("query", () => {
return producedResult;
}
const queryResultMaxRows = effectiveQueryResultMaxRows(settingsStore.editorSettings.queryResultMaxRowsEnabled, settingsStore.editorSettings.queryResultMaxRows);
if (tab.mode === "query") {
const prepared = await prepareEditableQueryExecution(tab, sqlToExecute, conn, effectiveDbType, executionDatabase, traceId, elapsed);
sqlToExecute = prepared.sql;
@@ -4305,7 +4310,7 @@ export const useQueryStore = defineStore("query", () => {
sqlToExecute = sorted.sql;
resultSortedSql = sorted.sql;
}
const pagination = options?.pagination ?? { limit: settingsStore.editorSettings.pageSize, offset: 0 };
const pagination = limitQueryPagination(options?.pagination ?? { limit: settingsStore.editorSettings.pageSize, offset: 0 }, queryResultMaxRows);
const plan = await api.prepareQueryPaginationExecutionPlan({
sql: sqlToExecute,
queryBaseSql,
@@ -4336,8 +4341,15 @@ export const useQueryStore = defineStore("query", () => {
return false;
}
} else if (tab.mode === "data") {
pageLimit = options?.pagination?.limit ?? tableOpenPageLimit(settingsStore.editorSettings.tableOpenPageSize);
pageOffset = options?.pagination?.offset ?? 0;
const pagination = limitQueryPagination(
{
limit: options?.pagination?.limit ?? tableOpenPageLimit(settingsStore.editorSettings.tableOpenPageSize),
offset: options?.pagination?.offset ?? 0,
},
queryResultMaxRows,
);
pageLimit = pagination.limit;
pageOffset = pagination.offset;
}
const executionSchema = targetContext?.scope === "connection" ? undefined : connectionQueryExecutionSchema(conn, databaseTargetContext?.database ?? executionTarget?.database ?? tab.database, databaseTargetContext?.schema ?? executionTarget?.schema ?? tab.schema, tab.mode === "data");
@@ -4354,7 +4366,7 @@ export const useQueryStore = defineStore("query", () => {
}
queryExecutionLog("info", "execute-in-txn:invoke", { traceId, txnSessionId: tab.txnSessionId, elapsed: elapsed() });
executionDispatched = true;
executionPromise = api.executeInManualTransaction(tab.txnSessionId, sqlToExecute, executionDatabase, executionSchema, pageLimit);
executionPromise = api.executeInManualTransaction(tab.txnSessionId, sqlToExecute, executionDatabase, executionSchema, pageLimit ?? agentProtocolQueryResultMaxRows(queryResultMaxRows));
} else {
queryExecutionLog("info", "execute-multi:start", { traceId, elapsed: elapsed() });
// Query and data tabs use a tab-scoped pool so repeated executions keep
@@ -4364,14 +4376,13 @@ export const useQueryStore = defineStore("query", () => {
? useAgentResultSession
? {
// Agent cursors apply maxRows cumulatively across fetched pages.
// Keep multi-page navigation available without allowing unbounded reads.
maxRows: MAX_RESULT_PAGE_SIZE,
maxRows: agentProtocolQueryResultMaxRows(queryResultMaxRows),
fetchSize: pageLimit,
pageSize: pageLimit,
resultSessionId: options?.pagination?.sessionId,
}
: { maxRows: pageLimit, fetchSize: pageLimit }
: {}),
: { maxRows: agentProtocolQueryResultMaxRows(queryResultMaxRows) }),
...(executionClientSessionId ? { clientSessionId: executionClientSessionId } : {}),
timeoutSecs: queryTimeoutSecs,
catalog: executionCatalog,
@@ -4497,6 +4508,12 @@ export const useQueryStore = defineStore("query", () => {
current.resultTotalRowCount = undefined;
}
const resultRowCount = current.result?.rows.length ?? 0;
const resultLimitReached = !!current.result && queryResultLimitReached(pageOffset, resultRowCount, queryResultMaxRows);
if (resultLimitReached && current.result) {
current.result.has_more = false;
current.result.truncated = true;
current.resultTotalRowCount = queryResultMaxRows;
}
const totalKnownFromIncompletePage = !!current.result && typeof exactTotalFromIncompletePage(current.result, pageLimit, pageOffset, useAgentResultSession) === "number";
const dataCountTarget =
current.mode === "data"
@@ -4514,7 +4531,8 @@ export const useQueryStore = defineStore("query", () => {
};
})()
: undefined;
const canAutoCalculateTotalRows = !options?.appendResult && !!current.result && resultRowCount > 0 && !totalKnownFromIncompletePage && settingsStore.editorSettings.autoCalculateTotalRows && ((current.mode === "query" && !!countSql) || (current.mode === "data" && !!dataCountTarget));
const canAutoCalculateTotalRows =
!options?.appendResult && !!current.result && resultRowCount > 0 && !resultLimitReached && !totalKnownFromIncompletePage && settingsStore.editorSettings.autoCalculateTotalRows && ((current.mode === "query" && !!countSql) || (current.mode === "data" && !!dataCountTarget));
current.resultTotalRowCountLoading = canAutoCalculateTotalRows;
// Server-side pagination without a countSql: the backend (currently
// the Elasticsearch driver) already reports the true match total via
@@ -4528,7 +4546,7 @@ export const useQueryStore = defineStore("query", () => {
}
touchResult(current);
syncDisplayedResultRun(current, queryBaseSql, captureResultRun);
if (!options?.appendResult && !totalRowCountResolved && (current.mode === "query" || current.mode === "data") && current.result) {
if (!options?.appendResult && !resultLimitReached && !totalRowCountResolved && (current.mode === "query" || current.mode === "data") && current.result) {
countQueryTotalRowsInBackground({
tabId: id,
connectionId: executionConnectionId,
@@ -4550,6 +4568,7 @@ export const useQueryStore = defineStore("query", () => {
traceId,
elapsed,
timeoutSecs: queryTimeoutSecs,
maxRows: queryResultMaxRows,
});
}
queryExecutionLog("info", "result:assigned", {
+10
View File
@@ -8,6 +8,7 @@ import { safeLocalStorageGet, safeLocalStorageRemove } from "@/lib/backend/safeS
import { type ColumnFormatterConfig, type CustomColumnFormatterConfig, normalizeColumnFormatter, normalizeCustomColumnFormatter, normalizeGlobalDateTimePattern } from "@/lib/dataGrid/columnFormatter";
import { type DataGridCopyPreference, type DataGridExtractorOptions, DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS, normalizeDataGridCopyPreference, normalizeDataGridExtractorOptions } from "@/lib/dataGrid/dataGridCopyExtractor";
import { normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize";
import { DEFAULT_QUERY_RESULT_MAX_ROWS, normalizeQueryResultMaxRows } from "@/lib/dataGrid/queryResultRowLimit";
import { normalizeShortcutSettings, type ShortcutSettings } from "@/lib/editor/shortcutRegistry";
import type { SavedSqlOpenTargetMode } from "@/lib/savedSql/savedSqlExecutionTarget";
import type { ConnectionListSortMode } from "@/lib/sidebar/connectionListSort";
@@ -515,7 +516,10 @@ export interface EditorSettings {
appLayout: "separated" | "classic";
pageSize: number;
tableOpenPageSize: number;
queryResultMaxRowsEnabled: boolean;
queryResultMaxRows: number;
infiniteScroll: boolean;
/** Preserved for downgrade compatibility; current clients use queryResultMaxRows. */
infiniteScrollMaxRows: number;
regexMaxMatchCount: number;
autoCalculateTotalRows: boolean;
@@ -702,6 +706,8 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
appLayout: "classic",
pageSize: 100,
tableOpenPageSize: 100,
queryResultMaxRowsEnabled: true,
queryResultMaxRows: DEFAULT_QUERY_RESULT_MAX_ROWS,
infiniteScroll: false,
infiniteScrollMaxRows: 5000,
regexMaxMatchCount: 1000,
@@ -1036,6 +1042,8 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
appLayout: settings.appLayout ?? DEFAULT_EDITOR_SETTINGS.appLayout,
pageSize: normalizeResultPageSize(settings.pageSize),
tableOpenPageSize: normalizeResultPageSize(settings.tableOpenPageSize, DEFAULT_EDITOR_SETTINGS.tableOpenPageSize),
queryResultMaxRowsEnabled: settings.queryResultMaxRowsEnabled !== false,
queryResultMaxRows: normalizeQueryResultMaxRows(settings.queryResultMaxRows),
infiniteScroll: settings.infiniteScroll ?? DEFAULT_EDITOR_SETTINGS.infiniteScroll,
infiniteScrollMaxRows: typeof settings.infiniteScrollMaxRows === "number" && settings.infiniteScrollMaxRows >= 1000 && settings.infiniteScrollMaxRows <= 50000 ? Math.round(settings.infiniteScrollMaxRows) : DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows,
regexMaxMatchCount: typeof settings.regexMaxMatchCount === "number" && Number.isFinite(settings.regexMaxMatchCount) && settings.regexMaxMatchCount >= 100 && settings.regexMaxMatchCount <= 10000 ? Math.round(settings.regexMaxMatchCount) : DEFAULT_EDITOR_SETTINGS.regexMaxMatchCount,
@@ -1572,6 +1580,8 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.appLayout !== undefined) editorSettings.value.appLayout = partial.appLayout;
if (partial.pageSize !== undefined) editorSettings.value.pageSize = normalizeResultPageSize(partial.pageSize);
if (partial.tableOpenPageSize !== undefined) editorSettings.value.tableOpenPageSize = normalizeResultPageSize(partial.tableOpenPageSize, DEFAULT_EDITOR_SETTINGS.tableOpenPageSize);
if (partial.queryResultMaxRowsEnabled !== undefined) editorSettings.value.queryResultMaxRowsEnabled = Boolean(partial.queryResultMaxRowsEnabled);
if (partial.queryResultMaxRows !== undefined) editorSettings.value.queryResultMaxRows = normalizeQueryResultMaxRows(partial.queryResultMaxRows, editorSettings.value.queryResultMaxRows);
if (partial.infiniteScroll !== undefined) editorSettings.value.infiniteScroll = partial.infiniteScroll;
if (partial.infiniteScrollMaxRows !== undefined)
editorSettings.value.infiniteScrollMaxRows = typeof partial.infiniteScrollMaxRows === "number" && partial.infiniteScrollMaxRows >= 1000 && partial.infiniteScrollMaxRows <= 50000 ? Math.round(partial.infiniteScrollMaxRows) : DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows;