fix(query): stop doubling global query timeout

This commit is contained in:
zipg
2026-09-16 23:21:57 +08:00
committed by GitHub
parent e8affe9436
commit 1ed06f270b
4 changed files with 21 additions and 14 deletions
+6 -5
View File
@@ -29,17 +29,18 @@ describe("queryTimeout", () => {
const packageSpec = `CREATE OR REPLACE PACKAGE pkg_utils AS
FUNCTION get_version RETURN VARCHAR2;
END pkg_utils;`;
expect(frontendQueryTimeoutSecsForSql(`${packageSpec}\n/\nSELECT 1;`, "opengauss", 30)).toBe(120);
expect(frontendQueryTimeoutSecsForSql(`${packageSpec}\n/\nSELECT 1;`, "opengauss", 30)).toBe(60);
});
it("keeps the frontend guard for non-row PostgreSQL statements", () => {
expect(frontendQueryTimeoutSecsForSql("UPDATE sample_records SET state = 'ready'", "postgres", 30)).toBe(60);
expect(frontendQueryTimeoutSecsForSql("INSERT INTO sample_records(note) VALUES ('RETURNING is text')", "postgres", 30)).toBe(60);
expect(frontendQueryTimeoutSecsForSql("UPDATE sample_records SET note = 'ready' /* RETURNING */", "postgres", 30)).toBe(60);
expect(frontendQueryTimeoutSecsForSql("UPDATE sample_records SET state = 'ready'", "postgres", 30)).toBe(30);
expect(frontendQueryTimeoutSecsForSql("INSERT INTO sample_records(note) VALUES ('RETURNING is text')", "postgres", 30)).toBe(30);
expect(frontendQueryTimeoutSecsForSql("UPDATE sample_records SET note = 'ready' /* RETURNING */", "postgres", 30)).toBe(30);
});
it("keeps the existing frontend guard for other database types", () => {
expect(frontendQueryTimeoutSecsForSql("SELECT * FROM sample_records LIMIT 2000", "mysql", 30)).toBe(60);
expect(frontendQueryTimeoutSecsForSql("SELECT * FROM sample_records LIMIT 2000", "mysql", 30)).toBe(30);
expect(frontendQueryTimeoutSecsForSql("SELECT SLEEP(30)", "mysql", 10)).toBe(10);
expect(queryTimeoutSecsForConnection({ query_timeout_secs: undefined })).toBe(60);
});
+1 -2
View File
@@ -56,9 +56,8 @@ export function frontendQueryTimeoutSecsForSql(sql: string, databaseType: Databa
// the timeout for row-returning PostgreSQL statements.
if (databaseType === "postgres" && postgresQueryMayReturnRows(sql, databaseType)) return 0;
const baseTimeoutSecs = Math.max(queryTimeoutSecs * 2, 60);
const statementCount = Math.max(splitSqlStatementRanges(sql, databaseType, parameterOptions ?? sqlStatementParameterOptionsForCompatibility(databaseType)).length, 1);
return baseTimeoutSecs * statementCount;
return queryTimeoutSecs * statementCount;
}
export function frontendQueryTimeoutDelayMs(timeoutSecs: number): number | undefined {
+10 -3
View File
@@ -593,7 +593,7 @@ function displayedQueryMetadataSql(tab: QueryTab, fallbackSql: string): string {
return tab.results?.length ? (tab.result?.sourceStatement ?? fallbackSql) : fallbackSql;
}
async function withFrontendQueryTimeout<T>(promise: Promise<T>, timeoutSecs: number, message: string): Promise<T> {
async function withFrontendQueryTimeout<T>(promise: Promise<T>, timeoutSecs: number, message: string, onTimeout?: () => void): Promise<T> {
const timeoutMs = frontendQueryTimeoutDelayMs(timeoutSecs);
if (timeoutMs === undefined) return promise;
@@ -602,7 +602,10 @@ async function withFrontendQueryTimeout<T>(promise: Promise<T>, timeoutSecs: num
return await Promise.race([
promise,
new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(new Error(message)), timeoutMs);
timer = setTimeout(() => {
onTimeout?.();
reject(new Error(message));
}, timeoutMs);
}),
]);
} finally {
@@ -7096,7 +7099,11 @@ export const useQueryStore = defineStore("query", () => {
executionPromise = executeWithoutManualTransaction();
}
const annotatedResults = annotateQueryResultSources(
markQueryResultsRowsRaw(await withFrontendQueryTimeout(executionPromise, frontendTimeoutSecs, t("editor.queryTimeoutError", { seconds: frontendTimeoutSecs }))),
markQueryResultsRowsRaw(
await withFrontendQueryTimeout(executionPromise, frontendTimeoutSecs, t("editor.queryTimeoutError", { seconds: frontendTimeoutSecs }), () => {
void api.cancelQuery(executionId).catch((error) => queryExecutionLog("warn", "frontend-timeout:cancel-failed", { traceId, error }));
}),
),
queryBaseSql,
sourceLabelDatabase,
effectiveDbType,
+4 -4
View File
@@ -14,10 +14,10 @@ test("queryTimeoutSecsForConnection falls back to the default timeout", () => {
});
test("frontend query timeout scales with SQL statement count", () => {
assert.equal(frontendQueryTimeoutSecsForSql("INSERT INTO users VALUES (1)", "mysql", 30), 60);
assert.equal(frontendQueryTimeoutSecsForSql("INSERT INTO users VALUES (1); INSERT INTO users VALUES (2);", "mysql", 30), 120);
assert.equal(frontendQueryTimeoutSecsForSql("INSERT INTO users VALUES (1); INSERT INTO users VALUES (2); INSERT INTO users VALUES (3);", "mysql", 10), 180);
assert.equal(frontendQueryTimeoutSecsForSql("/* prep */\nINSERT INTO users VALUES (1);\n-- keep batching\nINSERT INTO users VALUES (2);", "mysql", 30), 120);
assert.equal(frontendQueryTimeoutSecsForSql("INSERT INTO users VALUES (1)", "mysql", 30), 30);
assert.equal(frontendQueryTimeoutSecsForSql("INSERT INTO users VALUES (1); INSERT INTO users VALUES (2);", "mysql", 30), 60);
assert.equal(frontendQueryTimeoutSecsForSql("INSERT INTO users VALUES (1); INSERT INTO users VALUES (2); INSERT INTO users VALUES (3);", "mysql", 10), 30);
assert.equal(frontendQueryTimeoutSecsForSql("/* prep */\nINSERT INTO users VALUES (1);\n-- keep batching\nINSERT INTO users VALUES (2);", "mysql", 30), 60);
assert.equal(frontendQueryTimeoutSecsForSql("INSERT INTO users VALUES (1); INSERT INTO users VALUES (2);", "mysql", 0), 0);
});