perf(mysql): batch adjacent DML into multi-statement COM_QUERY for large scripts
This commit is contained in:
9 files changed
+683
-82
No files matched your search
+7
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { frontendQueryTimeoutSecsForSql, queryTimeoutSecsForConnection } from "@/lib/sql/queryTimeout";
|
||||
import { frontendQueryTimeoutDelayMs, frontendQueryTimeoutSecsForSql, queryTimeoutSecsForConnection } from "@/lib/sql/queryTimeout";
|
||||
|
||||
describe("queryTimeout", () => {
|
||||
it("lets PostgreSQL row queries use the backend inactivity timeout", () => {
|
||||
@@ -19,6 +19,12 @@ describe("queryTimeout", () => {
|
||||
expect(queryTimeoutSecsForConnection({ query_timeout_secs: undefined })).toBe(30);
|
||||
});
|
||||
|
||||
it("does not schedule frontend timeouts beyond the browser timer limit", () => {
|
||||
expect(frontendQueryTimeoutDelayMs(60)).toBe(60_000);
|
||||
expect(frontendQueryTimeoutDelayMs(11_401_200)).toBeUndefined();
|
||||
expect(frontendQueryTimeoutDelayMs(0)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("uses the global timeout only for inheriting connections", () => {
|
||||
expect(queryTimeoutSecsForConnection({ query_timeout_secs: 30, query_timeout_inherit: true }, 12)).toBe(12);
|
||||
expect(queryTimeoutSecsForConnection({ query_timeout_secs: 30, query_timeout_inherit: false }, 12)).toBe(30);
|
||||
|
||||
@@ -234,6 +234,35 @@ describe("execution summary", () => {
|
||||
{ status: "error", isError: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it("maps out-of-order results to their explicit statement indexes", () => {
|
||||
const items = executionSummaryItems({
|
||||
results: [
|
||||
{ columns: ["value"], rows: [["third"]], affected_rows: 0, execution_time_ms: 3, statement_index: 2 },
|
||||
{ columns: ["value"], rows: [["first"]], affected_rows: 0, execution_time_ms: 1, statement_index: 0 },
|
||||
],
|
||||
batchSqlExecution: {
|
||||
executionId: "run-out-of-order",
|
||||
submittedSql: "SELECT 'first'; SELECT 'second'; SELECT 'third'",
|
||||
editorFingerprint: "fingerprint",
|
||||
sourceOffset: 0,
|
||||
completed: 2,
|
||||
total: 3,
|
||||
startedAt: 1,
|
||||
items: [
|
||||
{ statementIndex: 0, sql: "SELECT 'first'", from: 0, to: 14, status: "success" },
|
||||
{ statementIndex: 1, sql: "SELECT 'second'", from: 16, to: 31, status: "skipped" },
|
||||
{ statementIndex: 2, sql: "SELECT 'third'", from: 33, to: 47, status: "success" },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(items.map((item) => [item.statementIndex, item.result?.rows[0]?.[0]])).toEqual([
|
||||
[0, "first"],
|
||||
[1, undefined],
|
||||
[2, "third"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("statement execution markers", () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { tokenizeSqlSemantic } from "@/lib/sql/semantic/tokens";
|
||||
import type { ConnectionConfig, DatabaseType } from "@/types/database";
|
||||
|
||||
export const DEFAULT_QUERY_TIMEOUT_SECS = 30;
|
||||
const MAX_BROWSER_TIMER_DELAY_MS = 2_147_483_647;
|
||||
|
||||
const POSTGRES_ROW_STATEMENT_KEYWORDS = new Set(["select", "show", "explain", "table", "with"]);
|
||||
const POSTGRES_RETURNING_STATEMENT_KEYWORDS = new Set(["insert", "update", "delete", "merge"]);
|
||||
@@ -30,6 +31,12 @@ export function frontendQueryTimeoutSecsForSql(sql: string, databaseType: Databa
|
||||
return baseTimeoutSecs * statementCount;
|
||||
}
|
||||
|
||||
export function frontendQueryTimeoutDelayMs(timeoutSecs: number): number | undefined {
|
||||
const timeoutMs = timeoutSecs * 1000;
|
||||
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_BROWSER_TIMER_DELAY_MS) return undefined;
|
||||
return timeoutMs;
|
||||
}
|
||||
|
||||
function postgresQueryMayReturnRows(sql: string, databaseType: DatabaseType): boolean {
|
||||
return splitSqlStatementRanges(sql, databaseType).some(({ sql: statement }) => {
|
||||
const tokens = tokenizeSqlSemantic(statement, "postgres");
|
||||
|
||||
@@ -368,8 +368,12 @@ export interface ExecutionSummaryItem {
|
||||
export function executionSummaryItems(tab: Pick<QueryTab, "result" | "results" | "batchSqlExecution">): ExecutionSummaryItem[] {
|
||||
const results = tab.results?.length ? tab.results : tab.result ? [tab.result] : [];
|
||||
if (tab.batchSqlExecution?.items.length) {
|
||||
const resultsByStatementIndex = new Map<number, QueryResult>();
|
||||
results.forEach((result, resultIndex) => {
|
||||
resultsByStatementIndex.set(result.statement_index ?? resultIndex, result);
|
||||
});
|
||||
return tab.batchSqlExecution.items.map((item, index) => {
|
||||
const result = results.find((candidate, resultIndex) => (candidate.statement_index ?? resultIndex) === item.statementIndex);
|
||||
const result = resultsByStatementIndex.get(item.statementIndex);
|
||||
const returnedRows = result?.rows.length ?? 0;
|
||||
const affectedRows = item.affectedRows ?? result?.affected_rows ?? 0;
|
||||
const hasTabularResult = (result?.columns.length ?? 0) > 0;
|
||||
|
||||
Reference in new issue
Block a user