feat(settings): configurable query result max rows with unified load limit
This commit is contained in:
26 files changed
+446
-187
No files matched your search
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { MAX_QUERY_RESULT_MAX_ROWS, agentProtocolQueryResultMaxRows, capQueryResultTotal, continuousQueryResultMaxRows, effectiveQueryResultMaxRows, limitQueryPagination, normalizeQueryResultMaxRows, queryResultLimitReached } from "../queryResultRowLimit";
|
||||
|
||||
describe("query result row limits", () => {
|
||||
it("preserves the legacy 100,000-row default", () => {
|
||||
expect(effectiveQueryResultMaxRows(undefined, undefined)).toBe(100_000);
|
||||
expect(effectiveQueryResultMaxRows(true, 250_000)).toBe(250_000);
|
||||
});
|
||||
|
||||
it("uses the Java signed-int boundary for unlimited Agent execution", () => {
|
||||
expect(effectiveQueryResultMaxRows(false, 100_000)).toBeUndefined();
|
||||
expect(agentProtocolQueryResultMaxRows(undefined)).toBe(MAX_QUERY_RESULT_MAX_ROWS);
|
||||
expect(continuousQueryResultMaxRows(false, 100_000)).toBe(MAX_QUERY_RESULT_MAX_ROWS);
|
||||
expect(continuousQueryResultMaxRows(true, 250_000)).toBe(250_000);
|
||||
expect(normalizeQueryResultMaxRows(MAX_QUERY_RESULT_MAX_ROWS + 1)).toBe(MAX_QUERY_RESULT_MAX_ROWS);
|
||||
});
|
||||
|
||||
it("limits the final page and caps totals", () => {
|
||||
expect(limitQueryPagination({ limit: 100, offset: 950 }, 1_000)).toEqual({ limit: 50, offset: 950 });
|
||||
expect(limitQueryPagination({ limit: 100, offset: 2_000 }, 1_000)).toEqual({ limit: 1, offset: 999 });
|
||||
expect(queryResultLimitReached(950, 50, 1_000)).toBe(true);
|
||||
expect(queryResultLimitReached(900, 50, 1_000)).toBe(false);
|
||||
expect(capQueryResultTotal(1_500, 1_000)).toBe(1_000);
|
||||
expect(capQueryResultTotal(1_500, undefined)).toBe(1_500);
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
export const RESULT_PAGE_SIZE_OPTIONS = [50, 100, 500, 1000];
|
||||
export const DEFAULT_RESULT_PAGE_SIZE = 100;
|
||||
export const MIN_RESULT_PAGE_SIZE = 1;
|
||||
export const MAX_RESULT_PAGE_SIZE = 100000;
|
||||
export const MAX_RESULT_PAGE_SIZE = 1_000_000;
|
||||
|
||||
export function normalizeResultPageSize(value: unknown, fallback = DEFAULT_RESULT_PAGE_SIZE): number {
|
||||
const fallbackValue = Number.isFinite(fallback) && fallback >= MIN_RESULT_PAGE_SIZE ? Math.min(Math.floor(fallback), MAX_RESULT_PAGE_SIZE) : DEFAULT_RESULT_PAGE_SIZE;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
export const DEFAULT_QUERY_RESULT_MAX_ROWS = 100_000;
|
||||
export const MAX_QUERY_RESULT_MAX_ROWS = 2_147_483_647;
|
||||
|
||||
export function normalizeQueryResultMaxRows(value: unknown, fallback = DEFAULT_QUERY_RESULT_MAX_ROWS): number {
|
||||
const normalizedFallback = Number.isFinite(fallback) ? Math.min(Math.max(1, Math.floor(fallback)), MAX_QUERY_RESULT_MAX_ROWS) : DEFAULT_QUERY_RESULT_MAX_ROWS;
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) return normalizedFallback;
|
||||
return Math.min(Math.max(1, Math.floor(parsed)), MAX_QUERY_RESULT_MAX_ROWS);
|
||||
}
|
||||
|
||||
export function effectiveQueryResultMaxRows(enabled: boolean | undefined, value: unknown): number | undefined {
|
||||
return enabled !== false ? normalizeQueryResultMaxRows(value) : undefined;
|
||||
}
|
||||
|
||||
export function continuousQueryResultMaxRows(enabled: boolean | undefined, value: unknown): number {
|
||||
return effectiveQueryResultMaxRows(enabled, value) ?? MAX_QUERY_RESULT_MAX_ROWS;
|
||||
}
|
||||
|
||||
export function agentProtocolQueryResultMaxRows(maxRows: number | undefined): number {
|
||||
return maxRows ?? MAX_QUERY_RESULT_MAX_ROWS;
|
||||
}
|
||||
|
||||
export function limitQueryPagination<T extends { limit: number; offset: number }>(pagination: T, maxRows: number | undefined): T {
|
||||
if (maxRows === undefined) return pagination;
|
||||
const offset = Math.min(Math.max(0, pagination.offset), maxRows - 1);
|
||||
const remaining = maxRows - offset;
|
||||
return { ...pagination, limit: Math.min(pagination.limit, remaining), offset };
|
||||
}
|
||||
|
||||
export function queryResultLimitReached(offset: number | undefined, rowCount: number, maxRows: number | undefined): boolean {
|
||||
return maxRows !== undefined && Math.max(0, offset ?? 0) + rowCount >= maxRows;
|
||||
}
|
||||
|
||||
export function capQueryResultTotal(total: number, maxRows: number | undefined): number {
|
||||
return maxRows === undefined ? total : Math.min(total, maxRows);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { EDITOR_SETTINGS_DRAFT_KEYS, editorSettingsDraftFromSettings, editorSettingsDraftChanged, editorSettingsPatchFromDraft, normalizeTableOpenPageSizeDraft } from "../editorSettingsDraft";
|
||||
import { EDITOR_SETTINGS_DRAFT_KEYS, editorSettingsDraftFromSettings, editorSettingsDraftChanged, editorSettingsPatchFromDraft, normalizeQueryResultMaxRowsDraft, normalizeTableOpenPageSizeDraft } from "../editorSettingsDraft";
|
||||
import type { EditorSettings } from "@/stores/settingsStore";
|
||||
|
||||
function makeSettings(overrides: Partial<EditorSettings> = {}): EditorSettings {
|
||||
@@ -7,6 +7,8 @@ function makeSettings(overrides: Partial<EditorSettings> = {}): EditorSettings {
|
||||
autoCalculateTotalRows: false,
|
||||
pageSize: 100,
|
||||
tableOpenPageSize: 100,
|
||||
queryResultMaxRowsEnabled: true,
|
||||
queryResultMaxRows: 100000,
|
||||
sqlEngine: "desktop",
|
||||
tabSize: 2,
|
||||
keywordCase: "upper",
|
||||
@@ -35,7 +37,10 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => {
|
||||
});
|
||||
|
||||
it("includes the table-open page size", () => {
|
||||
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("pageSize");
|
||||
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("tableOpenPageSize");
|
||||
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("queryResultMaxRowsEnabled");
|
||||
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("queryResultMaxRows");
|
||||
});
|
||||
|
||||
it("includes the saved SQL open target mode", () => {
|
||||
@@ -89,7 +94,8 @@ describe("editorSettingsDraftFromSettings", () => {
|
||||
|
||||
describe("normalizeTableOpenPageSizeDraft", () => {
|
||||
it.each([
|
||||
[200000, 100000],
|
||||
[200000, 200000],
|
||||
[2000000, 1000000],
|
||||
[0, 100],
|
||||
[-1, 100],
|
||||
["123.9", 123],
|
||||
@@ -102,6 +108,17 @@ describe("normalizeTableOpenPageSizeDraft", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeQueryResultMaxRowsDraft", () => {
|
||||
it.each([
|
||||
[250000, 250000],
|
||||
[0, 1],
|
||||
[2147483648, 2147483647],
|
||||
[Number.NaN, 100000],
|
||||
])("normalizes %s to %s", (value, expected) => {
|
||||
expect(normalizeQueryResultMaxRowsDraft(value)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("editorSettingsDraftChanged", () => {
|
||||
it("detects change in continueOnErrorOnBatch", () => {
|
||||
const settings = makeSettings({ continueOnErrorOnBatch: false });
|
||||
@@ -181,7 +198,7 @@ describe("editorSettingsPatchFromDraft", () => {
|
||||
const draft = editorSettingsDraftFromSettings(settings);
|
||||
const base = editorSettingsDraftFromSettings(settings);
|
||||
draft.tableOpenPageSize = 200000.9;
|
||||
expect(editorSettingsPatchFromDraft(draft, base).tableOpenPageSize).toBe(100000);
|
||||
expect(editorSettingsPatchFromDraft(draft, base).tableOpenPageSize).toBe(200000);
|
||||
});
|
||||
|
||||
it("includes the saved SQL open target when changed", () => {
|
||||
|
||||
@@ -131,7 +131,7 @@ describe("settings search", () => {
|
||||
{ titleKey: "settings.savedSqlOpenTarget", category: "editor", targetId: "editor" },
|
||||
{ titleKey: "settings.confirmDangerousSqlExecution", category: "editor", targetId: "editor" },
|
||||
{ titleKey: "settings.continueOnErrorOnBatch", category: "editor", targetId: "editor" },
|
||||
{ titleKey: "settings.dataGridQuickEntry", category: "appearance", targetId: "appearance" },
|
||||
{ titleKey: "settings.dataGridQuickEntry", category: "data", targetId: "data" },
|
||||
{ titleKey: "transfer.dataTransfer", category: "appearance", targetId: "appearance" },
|
||||
{ titleKey: "toolbar.driverManager", category: "appearance", targetId: "appearance" },
|
||||
{ titleKey: "toolbar.theme", category: "appearance", targetId: "appearance" },
|
||||
@@ -155,8 +155,8 @@ describe("settings search", () => {
|
||||
{ titleKey: "settings.sqlFormatterParamTypes", category: "formatter", targetId: "formatter" },
|
||||
{ titleKey: "settings.routineSourceOpenMode", category: "navigation", targetId: "navigation" },
|
||||
{ titleKey: "settings.disconnectTabHandlingMode", category: "navigation", targetId: "navigation" },
|
||||
{ titleKey: "settings.compactColumnHeaderActions", category: "appearance", targetId: "appearance" },
|
||||
{ titleKey: "settings.infiniteScrollMaxRows", category: "appearance", targetId: "appearance" },
|
||||
{ titleKey: "settings.compactColumnHeaderActions", category: "data", targetId: "data" },
|
||||
{ titleKey: "settings.infiniteScroll", category: "data", targetId: "data" },
|
||||
{ titleKey: "settings.globalDateTimeDisplayFormat", category: "data", targetId: "data" },
|
||||
{ titleKey: "settings.globalDateTimeExportFormat", category: "data", targetId: "data" },
|
||||
{ titleKey: "settings.globalDateTimeImportFormat", category: "data", targetId: "data" },
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { EditorSettings } from "@/stores/settingsStore";
|
||||
import { normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize";
|
||||
import { normalizeQueryResultMaxRows } from "@/lib/dataGrid/queryResultRowLimit";
|
||||
import { normalizeCompletionTriggerMode } from "@/lib/sql/sqlCompletionTriggerPolicy";
|
||||
|
||||
export const EDITOR_SETTINGS_DRAFT_KEYS = [
|
||||
@@ -36,9 +37,11 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
|
||||
"compactColumnHeaderActions",
|
||||
"dataGridQuickEntry",
|
||||
"dataGridAutoTransposeSingleRow",
|
||||
"pageSize",
|
||||
"tableOpenPageSize",
|
||||
"queryResultMaxRowsEnabled",
|
||||
"queryResultMaxRows",
|
||||
"infiniteScroll",
|
||||
"infiniteScrollMaxRows",
|
||||
"regexMaxMatchCount",
|
||||
"autoCalculateTotalRows",
|
||||
"tableColumnTemplateFields",
|
||||
@@ -86,8 +89,13 @@ export function normalizeTableOpenPageSizeDraft(value: unknown): number {
|
||||
return normalizeResultPageSize(value);
|
||||
}
|
||||
|
||||
export function normalizeQueryResultMaxRowsDraft(value: unknown): number {
|
||||
return normalizeQueryResultMaxRows(value);
|
||||
}
|
||||
|
||||
function normalizedDraftValue(key: EditorSettingsDraftKey, value: unknown): unknown {
|
||||
if (key === "tableOpenPageSize") return normalizeTableOpenPageSizeDraft(value);
|
||||
if (key === "pageSize" || key === "tableOpenPageSize") return normalizeTableOpenPageSizeDraft(value);
|
||||
if (key === "queryResultMaxRows") return normalizeQueryResultMaxRowsDraft(value);
|
||||
if (key === "completionTriggerMode") return normalizeCompletionTriggerMode(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -170,16 +170,17 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
|
||||
{ id: "navigation-hidden-tables", category: "navigation", titleKey: "settings.sidebarHiddenTablePrefixes", descriptionKey: "settings.sidebarHiddenTablePrefixesDescription", targetId: "navigation" },
|
||||
{ id: "navigation-table-page-size", category: "navigation", titleKey: "settings.sidebarTablePageSize", descriptionKey: "settings.sidebarTablePageSizeDescription", targetId: "navigation" },
|
||||
{ id: "navigation-disconnect-tabs", category: "navigation", titleKey: "settings.disconnectTabHandlingMode", descriptionKey: "settings.disconnectTabHandlingModeDescription", targetId: "navigation" },
|
||||
{ id: "query-page-size", category: "data", titleKey: "settings.queryPageSize", descriptionKey: "settings.queryPageSizeDescription", targetId: "data" },
|
||||
{ id: "data-page-size", category: "data", titleKey: "settings.tableOpenPageSize", descriptionKey: "settings.tableOpenPageSizeDescription", targetId: "data" },
|
||||
{ id: "appearance-header-comments", category: "appearance", titleKey: "settings.showColumnCommentsInHeader", descriptionKey: "settings.showColumnCommentsInHeaderDescription", targetId: "appearance" },
|
||||
{ id: "appearance-header-types", category: "appearance", titleKey: "settings.showColumnTypesInHeader", descriptionKey: "settings.showColumnTypesInHeaderDescription", targetId: "appearance" },
|
||||
{ id: "appearance-index-indicators", category: "appearance", titleKey: "settings.showIndexIndicatorsInHeader", descriptionKey: "settings.showIndexIndicatorsInHeaderDescription", targetId: "appearance" },
|
||||
{ id: "appearance-compact-header-actions", category: "appearance", titleKey: "settings.compactColumnHeaderActions", descriptionKey: "settings.compactColumnHeaderActionsDescription", targetId: "appearance" },
|
||||
{ id: "appearance-auto-total", category: "appearance", titleKey: "settings.autoCalculateTotalRows", descriptionKey: "settings.autoCalculateTotalRowsDescription", targetId: "appearance" },
|
||||
{ id: "appearance-infinite-scroll", category: "appearance", titleKey: "settings.infiniteScroll", descriptionKey: "settings.infiniteScrollDescription", targetId: "appearance" },
|
||||
{ id: "appearance-infinite-scroll-limit", category: "appearance", titleKey: "settings.infiniteScrollMaxRows", descriptionKey: "settings.infiniteScrollMaxRowsDescription", targetId: "appearance" },
|
||||
{ id: "appearance-auto-transpose", category: "appearance", titleKey: "settings.dataGridAutoTransposeSingleRow", descriptionKey: "settings.dataGridAutoTransposeSingleRowDescription", targetId: "appearance" },
|
||||
{ id: "appearance-quick-entry", category: "appearance", titleKey: "settings.dataGridQuickEntry", descriptionKey: "settings.dataGridQuickEntryDescription", targetId: "appearance" },
|
||||
{ id: "query-result-max-rows", category: "data", titleKey: "settings.queryResultMaxRows", descriptionKey: "settings.queryResultMaxRowsDescription", targetId: "data" },
|
||||
{ id: "data-grid-header-comments", category: "data", titleKey: "settings.showColumnCommentsInHeader", descriptionKey: "settings.showColumnCommentsInHeaderDescription", targetId: "data" },
|
||||
{ id: "data-grid-header-types", category: "data", titleKey: "settings.showColumnTypesInHeader", descriptionKey: "settings.showColumnTypesInHeaderDescription", targetId: "data" },
|
||||
{ id: "data-grid-index-indicators", category: "data", titleKey: "settings.showIndexIndicatorsInHeader", descriptionKey: "settings.showIndexIndicatorsInHeaderDescription", targetId: "data" },
|
||||
{ id: "data-grid-compact-header-actions", category: "data", titleKey: "settings.compactColumnHeaderActions", descriptionKey: "settings.compactColumnHeaderActionsDescription", targetId: "data" },
|
||||
{ id: "data-grid-auto-total", category: "data", titleKey: "settings.autoCalculateTotalRows", descriptionKey: "settings.autoCalculateTotalRowsDescription", targetId: "data" },
|
||||
{ id: "data-grid-infinite-scroll", category: "data", titleKey: "settings.infiniteScroll", descriptionKey: "settings.infiniteScrollDescription", targetId: "data" },
|
||||
{ id: "data-grid-auto-transpose", category: "data", titleKey: "settings.dataGridAutoTransposeSingleRow", descriptionKey: "settings.dataGridAutoTransposeSingleRowDescription", targetId: "data" },
|
||||
{ id: "data-grid-quick-entry", category: "data", titleKey: "settings.dataGridQuickEntry", descriptionKey: "settings.dataGridQuickEntryDescription", targetId: "data" },
|
||||
{ id: "appearance-toolbar", category: "appearance", titleKey: "settings.toolbarTitle", descriptionKey: "settings.toolbarHiddenHint", targetId: "appearance" },
|
||||
{ id: "appearance-exclusive-sidebar-panels", category: "appearance", titleKey: "settings.exclusiveRightSidebarPanels", descriptionKey: "settings.exclusiveRightSidebarPanelsDescription", targetId: "appearance" },
|
||||
...createToolbarVisibilitySettingsSearchDefinitions(),
|
||||
|
||||
Reference in new issue
Block a user