fix(settings): centralize draft normalization and use 60s query timeout
This commit is contained in:
16 files changed
+239
-360
No files matched your search
@@ -21,7 +21,7 @@ describe("ConnectionDialog timeout controls", () => {
|
||||
|
||||
it("restores the default when the connection timeout input is cleared", () => {
|
||||
expect(normalizeConnectTimeoutSecs("")).toBe(DEFAULT_CONNECT_TIMEOUT_SECS);
|
||||
expect(dialogSource).toContain("normalizeGlobalConnectTimeoutSecs(config.connect_timeout_secs)");
|
||||
expect(dialogSource).toContain("normalizeConnectionTimeouts(config");
|
||||
expect(dialogSource).not.toContain("Number(config.connect_timeout_secs)");
|
||||
});
|
||||
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { CONCURRENT_INDEX_QUERY_TIMEOUT_SECS, frontendQueryTimeoutDelayMs, frontendQueryTimeoutSecsForSql, queryTimeoutSecsForConcurrentIndex, queryTimeoutSecsForConnection } from "@/lib/sql/queryTimeout";
|
||||
|
||||
describe("queryTimeout", () => {
|
||||
it("gives CREATE INDEX CONCURRENTLY a dedicated long budget instead of the 30s default", () => {
|
||||
it("gives CREATE INDEX CONCURRENTLY a dedicated long budget instead of the 60s default", () => {
|
||||
expect(CONCURRENT_INDEX_QUERY_TIMEOUT_SECS).toBe(1800);
|
||||
expect(CONCURRENT_INDEX_QUERY_TIMEOUT_SECS).toBeGreaterThan(30);
|
||||
expect(frontendQueryTimeoutDelayMs(CONCURRENT_INDEX_QUERY_TIMEOUT_SECS)).toBe(1_800_000);
|
||||
@@ -33,7 +33,7 @@ describe("queryTimeout", () => {
|
||||
|
||||
it("keeps the existing frontend guard for other database types", () => {
|
||||
expect(frontendQueryTimeoutSecsForSql("SELECT * FROM sample_records LIMIT 2000", "mysql", 30)).toBe(60);
|
||||
expect(queryTimeoutSecsForConnection({ query_timeout_secs: undefined })).toBe(30);
|
||||
expect(queryTimeoutSecsForConnection({ query_timeout_secs: undefined })).toBe(60);
|
||||
});
|
||||
|
||||
it("does not schedule frontend timeouts beyond the browser timer limit", () => {
|
||||
@@ -49,6 +49,6 @@ describe("queryTimeout", () => {
|
||||
});
|
||||
|
||||
it("falls back safely when an inherited global timeout is invalid", () => {
|
||||
expect(queryTimeoutSecsForConnection({ query_timeout_inherit: true }, Number.NaN)).toBe(30);
|
||||
expect(queryTimeoutSecsForConnection({ query_timeout_inherit: true }, Number.NaN)).toBe(60);
|
||||
});
|
||||
});
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeConnectionScope, normalizeConnectionTimeouts } from "../connectionSubmitNormalization";
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
|
||||
function config(overrides: Partial<ConnectionConfig> = {}): ConnectionConfig {
|
||||
return {
|
||||
id: "test",
|
||||
name: "Test",
|
||||
db_type: "postgres",
|
||||
host: "localhost",
|
||||
port: 5432,
|
||||
username: "user",
|
||||
password: "password",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("connection submit normalization", () => {
|
||||
it("normalizes invalid timeout values and inherited global values", () => {
|
||||
const inherited = config({ connect_timeout_inherit: true, query_timeout_inherit: true, idle_timeout_secs: -1, keepalive_interval_secs: Number.NaN });
|
||||
normalizeConnectionTimeouts(inherited, 12, 34);
|
||||
expect(inherited).toMatchObject({ connect_timeout_secs: 12, query_timeout_secs: 34, idle_timeout_secs: 60, keepalive_interval_secs: 30 });
|
||||
|
||||
const invalid = config({ query_timeout_secs: Number.NaN, idle_timeout_secs: -1, keepalive_interval_secs: -2 });
|
||||
normalizeConnectionTimeouts(invalid, 12, 34);
|
||||
expect(invalid).toMatchObject({ query_timeout_secs: 60, idle_timeout_secs: 60, keepalive_interval_secs: 30 });
|
||||
});
|
||||
|
||||
it("canonicalizes scope flags and production database names", () => {
|
||||
const value = config({ db_type: "postgres", one_time: false, read_only: false, save_password: undefined, production_databases: [" app ", "app", ""] });
|
||||
normalizeConnectionScope(value);
|
||||
expect(value).toMatchObject({ one_time: undefined, read_only: undefined, save_password: true, production_databases: ["app"] });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { isSingleDatabase } from "@/lib/database/databaseFeatureSupport";
|
||||
import { normalizeGlobalConnectTimeoutSecs, normalizeGlobalQueryTimeoutSecs } from "@/stores/settingsStore";
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
|
||||
export function normalizeConnectionTimeouts(config: ConnectionConfig, connectTimeoutSecs: number, queryTimeoutSecs: number): void {
|
||||
config.connect_timeout_secs = config.connect_timeout_inherit === true ? normalizeGlobalConnectTimeoutSecs(connectTimeoutSecs) : normalizeGlobalConnectTimeoutSecs(config.connect_timeout_secs);
|
||||
const queryTimeout = Number(config.query_timeout_secs);
|
||||
config.query_timeout_secs = config.query_timeout_inherit === true ? normalizeGlobalQueryTimeoutSecs(queryTimeoutSecs) : normalizeGlobalQueryTimeoutSecs(queryTimeout);
|
||||
const idleTimeout = Number(config.idle_timeout_secs);
|
||||
config.idle_timeout_secs = Number.isFinite(idleTimeout) && idleTimeout >= 0 ? idleTimeout : 60;
|
||||
const keepaliveInterval = Number(config.keepalive_interval_secs);
|
||||
config.keepalive_interval_secs = Number.isFinite(keepaliveInterval) && keepaliveInterval >= 0 ? keepaliveInterval : 30;
|
||||
}
|
||||
|
||||
export function normalizeConnectionScope(config: ConnectionConfig): void {
|
||||
if (!config.one_time) config.one_time = undefined;
|
||||
if (!config.read_only) config.read_only = undefined;
|
||||
// Persist only an explicit opt-out; absent/true keeps the existing save-password behavior.
|
||||
config.save_password = config.save_password !== false;
|
||||
if ((isSingleDatabase(config.db_type) || config.db_type === "mq" || config.db_type === "mqtt") && config.production_databases?.length) {
|
||||
// These connection types do not expose independently selectable production databases.
|
||||
config.is_production = true;
|
||||
config.production_databases = [];
|
||||
}
|
||||
if (!config.is_production) config.is_production = undefined;
|
||||
config.production_databases = [...new Set((config.production_databases || []).map((database) => database.trim()).filter(Boolean))];
|
||||
if (!config.production_databases.length) config.production_databases = undefined;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
export const DEFAULT_CONNECT_TIMEOUT_SECS = 10;
|
||||
export const MAX_CONNECT_TIMEOUT_SECS = 300;
|
||||
export const DEFAULT_QUERY_TIMEOUT_SECS = 30;
|
||||
export const DEFAULT_QUERY_TIMEOUT_SECS = 60;
|
||||
export const MAX_QUERY_TIMEOUT_SECS = 3600;
|
||||
|
||||
export function normalizeConnectTimeoutSecs(value: unknown): number {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ConnectionConfig, DatabaseType, SidebarLayout } from "@/types/database";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
import { buildSidebarLayoutFromFolderPaths } from "@/lib/sidebar/sidebarLayout";
|
||||
import { DEFAULT_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits";
|
||||
|
||||
declare var process: { env: Record<string, string | undefined> } | undefined;
|
||||
|
||||
@@ -462,7 +463,7 @@ function buildConnection(fragment: DataSourceFragment): ConnectionConfig {
|
||||
color: "",
|
||||
transport_layers: [],
|
||||
connect_timeout_secs: 10,
|
||||
query_timeout_secs: 30,
|
||||
query_timeout_secs: DEFAULT_QUERY_TIMEOUT_SECS,
|
||||
ssl: false,
|
||||
oracle_connection_type: profile.dbType === "oracle" ? parsed.oracleConnectionType || "service_name" : undefined,
|
||||
connection_string: profile.dbType === "jdbc" || profile.dbType === "mongodb" ? fragment.jdbcUrl.replace(/^jdbc:/i, "") : undefined,
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ConnectionConfig, DatabaseType, SidebarLayout } from "@/types/data
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
import { JDBCX_JDBC_DRIVER_CLASS } from "@/lib/database/jdbcxBuiltinDriver";
|
||||
import { buildSidebarLayoutFromFolderPaths } from "@/lib/sidebar/sidebarLayout";
|
||||
import { DEFAULT_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits";
|
||||
|
||||
type PartialConnection = Omit<ConnectionConfig, "id">;
|
||||
|
||||
@@ -264,7 +265,7 @@ function buildConnection(entry: DbeaverConnectionEntry, credentials: ReturnType<
|
||||
color: getString(config.color || config["connection-color"]),
|
||||
transport_layers: [],
|
||||
connect_timeout_secs: 10,
|
||||
query_timeout_secs: 30,
|
||||
query_timeout_secs: DEFAULT_QUERY_TIMEOUT_SECS,
|
||||
ssl: false,
|
||||
oracle_connection_type: profile.dbType === "oracle" ? parsedUrl.oracleConnectionType || "service_name" : undefined,
|
||||
connection_string: profile.dbType === "jdbc" || profile.dbType === "mongodb" ? url || undefined : undefined,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ConnectionConfig, DatabaseType, SshTunnelConfig } from "@/types/database";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
import { DEFAULT_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits";
|
||||
|
||||
type PartialConnection = Omit<ConnectionConfig, "id">;
|
||||
|
||||
@@ -336,7 +337,7 @@ async function parseConnection(node: ParsedNode): Promise<ConnectionConfig | nul
|
||||
color: "",
|
||||
transport_layers: sshTunnel ? [sshTunnel] : [],
|
||||
connect_timeout_secs: 10,
|
||||
query_timeout_secs: 30,
|
||||
query_timeout_secs: DEFAULT_QUERY_TIMEOUT_SECS,
|
||||
keepalive_interval_secs: keepaliveInterval,
|
||||
ssl: false,
|
||||
oracle_connection_type: oracleConnectionType,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ref } from "vue";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { applyEditorSettingsDraftToRefs } from "../applyEditorSettingsDraft";
|
||||
import type { EditorSettingsDraft } from "../editorSettingsDraft";
|
||||
|
||||
describe("applyEditorSettingsDraftToRefs", () => {
|
||||
it("applies only the requested keys and preserves custom transforms", () => {
|
||||
const fontSize = ref(12);
|
||||
const theme = ref("light");
|
||||
const draft = { fontSize: 16, theme: "dark" } as EditorSettingsDraft;
|
||||
|
||||
applyEditorSettingsDraftToRefs(draft, ["fontSize"], { fontSize, theme }, { fontSize: (value) => Number(value) + 1 });
|
||||
|
||||
expect(fontSize.value).toBe(17);
|
||||
expect(theme.value).toBe("light");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { Ref } from "vue";
|
||||
import type { EditorSettingsDraft, EditorSettingsDraftKey } from "./editorSettingsDraft";
|
||||
|
||||
export type EditorSettingsDraftRefMap = Record<EditorSettingsDraftKey, Ref>;
|
||||
|
||||
export function applyEditorSettingsDraftToRefs(draft: EditorSettingsDraft, keys: readonly EditorSettingsDraftKey[], refs: EditorSettingsDraftRefMap, transform: Partial<Record<EditorSettingsDraftKey, (value: unknown) => unknown>> = {}) {
|
||||
for (const key of keys) {
|
||||
const target = refs[key];
|
||||
const value = draft[key] as unknown;
|
||||
target.value = transform[key] ? transform[key]!(value) : value;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
import { splitSqlStatementRanges } from "@/lib/sql/sqlStatementRanges";
|
||||
import { tokenizeSqlSemantic } from "@/lib/sql/semantic/tokens";
|
||||
import { DEFAULT_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits";
|
||||
import type { ConnectionConfig, DatabaseType } from "@/types/database";
|
||||
|
||||
export const DEFAULT_QUERY_TIMEOUT_SECS = 30;
|
||||
export { DEFAULT_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits";
|
||||
|
||||
/**
|
||||
* Query timeout (seconds) the table structure editor applies when the change
|
||||
* script contains a PostgreSQL `CREATE INDEX CONCURRENTLY`.
|
||||
*
|
||||
* Concurrent builds scan the whole table while only taking `ShareUpdateExclusiveLock`,
|
||||
* so they routinely run far beyond the default 30s query timeout — the default
|
||||
* so they routinely run far beyond the default 60s query timeout — the default
|
||||
* budget would cancel a legitimate build and leave an INVALID index behind.
|
||||
* PostgreSQL itself does not impose a build deadline
|
||||
* (https://www.postgresql.org/docs/current/sql-createindex.html). This is the
|
||||
|
||||
Reference in new issue
Block a user