feat(oceanbase-oracle): split execution time from client wait
This commit is contained in:
1 parent
1294708eea
commit
33afb0b432
58 files changed
+843
-11
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { isActiveResultLoading } from "@/lib/sql/queryExecutionState";
|
||||
import { BackendErrorException } from "@/lib/backend/errorUtils";
|
||||
import type { QueryResult } from "@/types/database";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
@@ -157,6 +158,35 @@ describe("queryStore multi-statement errors", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["oceanbase-oracle", "mysql"] as const)("measures complete result wait only for a single %s query result", async (databaseType) => {
|
||||
mocks.getConnectionConfig.mockReturnValue({
|
||||
id: "timing-1",
|
||||
name: "Timing",
|
||||
db_type: databaseType,
|
||||
database: "APP",
|
||||
query_timeout_secs: 30,
|
||||
});
|
||||
let clock = 100;
|
||||
vi.spyOn(performance, "now").mockImplementation(() => clock);
|
||||
const pending = deferred<QueryResult[]>();
|
||||
mocks.executeMulti.mockReturnValue(pending.promise);
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
const tabId = store.createTab("timing-1", "APP", "Query", "query", "APP");
|
||||
try {
|
||||
const execution = store.executeTabSql(tabId, "SELECT VALUE FROM T");
|
||||
await vi.waitFor(() => expect(mocks.executeMulti).toHaveBeenCalledTimes(1));
|
||||
clock = 145;
|
||||
pending.resolve([{ columns: ["VALUE"], rows: [[1]], affected_rows: 0, execution_time_ms: 12 }]);
|
||||
await execution;
|
||||
const result = store.tabs.find((item) => item.id === tabId)?.result;
|
||||
expect(result?.execution_time_ms).toBe(12);
|
||||
expect(result?.client_request_wait_ms).toBe(databaseType === "oceanbase-oracle" ? 45 : undefined);
|
||||
} finally {
|
||||
vi.restoreAllMocks();
|
||||
}
|
||||
});
|
||||
|
||||
it("opens the first error result from a mixed result batch", async () => {
|
||||
const structuredError = structuredTimeoutError();
|
||||
mocks.executeMulti.mockResolvedValue([
|
||||
|
||||
@@ -14,6 +14,18 @@ function make(rows: number, spatial?: QueryResult["spatial_columns"]): QueryResu
|
||||
}
|
||||
|
||||
describe("appendQueryResultSegment spatial merge", () => {
|
||||
it("sums client request wait across loaded pages", () => {
|
||||
const previous = { ...make(1), client_request_wait_ms: 45 };
|
||||
const segment = { ...make(1), client_request_wait_ms: 30 };
|
||||
const merged = appendQueryResultSegment(previous, segment, 100);
|
||||
expect(merged.client_request_wait_ms).toBe(75);
|
||||
expect(merged.rows).toHaveLength(2);
|
||||
});
|
||||
it("does not report a partial wait when either loaded page lacks a sample", () => {
|
||||
const sampled = { ...make(1), client_request_wait_ms: 40 };
|
||||
expect(appendQueryResultSegment(make(1), sampled, 100).client_request_wait_ms).toBeUndefined();
|
||||
expect(appendQueryResultSegment(sampled, make(1), 100).client_request_wait_ms).toBeUndefined();
|
||||
});
|
||||
it("keeps the first non-null SRID per column across pages", () => {
|
||||
const previous = make(2, [{ column_index: 0, srid: 4326 }]);
|
||||
const segment = make(2, [{ column_index: 0, srid: 3857 }]);
|
||||
@@ -60,3 +72,19 @@ describe("appendQueryResultSegment spatial merge", () => {
|
||||
expect(() => appendQueryResultSegment(previous, segment, 100)).toThrow("relation missing_table does not exist");
|
||||
});
|
||||
});
|
||||
|
||||
describe("appendQueryResultSegment OceanBase audit timing", () => {
|
||||
it("uses the terminal audit for the whole JDBC cursor without summing page samples", () => {
|
||||
const first = { ...make(100), session_id: "cursor-1", has_more: true };
|
||||
const last = { ...make(50), server_execute_time_us: 370, has_more: false };
|
||||
expect(appendQueryResultSegment(first, last, 1000).server_execute_time_us).toBe(370);
|
||||
});
|
||||
|
||||
it("sums independent SQL pages only when each page has an audit sample", () => {
|
||||
const first = { ...make(100), server_execute_time_us: 200 };
|
||||
const sampledNext = { ...make(100), server_execute_time_us: 150 };
|
||||
expect(appendQueryResultSegment(first, sampledNext, 1000).server_execute_time_us).toBe(350);
|
||||
expect(appendQueryResultSegment(make(100), sampledNext, 1000).server_execute_time_us).toBeUndefined();
|
||||
expect(appendQueryResultSegment(first, make(100), 1000).server_execute_time_us).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -312,6 +312,10 @@ export function appendQueryResultSegment(previous: QueryResult, segment: QueryRe
|
||||
mongo_documents: appendParallelValues(previous.mongo_documents, segment.mongo_documents),
|
||||
mongo_copy_documents: appendParallelValues(previous.mongo_copy_documents, segment.mongo_copy_documents),
|
||||
execution_time_ms: (previous.execution_time_ms ?? 0) + (segment.execution_time_ms ?? 0),
|
||||
// A JDBC cursor's terminal page audits the entire original statement.
|
||||
// Independent page SQLs need every page sampled before a total is shown.
|
||||
server_execute_time_us: previous.session_id ? segment.server_execute_time_us : previous.server_execute_time_us !== undefined && segment.server_execute_time_us !== undefined ? previous.server_execute_time_us + segment.server_execute_time_us : undefined,
|
||||
client_request_wait_ms: previous.client_request_wait_ms !== undefined && segment.client_request_wait_ms !== undefined ? previous.client_request_wait_ms + segment.client_request_wait_ms : undefined,
|
||||
has_more: previous.rows.length + appendedRowCount >= maxRows ? false : segment.has_more,
|
||||
});
|
||||
}
|
||||
@@ -6433,6 +6437,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
let useAgentResultSession = false;
|
||||
let paginationRowNumberColumn: string | undefined;
|
||||
let executionDispatched = false;
|
||||
let clientRequestStartedAt: number | undefined;
|
||||
let producedResult = false;
|
||||
const resumedExecutionTarget = batchResume?.batch.executionTarget;
|
||||
const executionConnectionId = resumedExecutionTarget?.connectionId ?? options?.executionTarget?.connectionId ?? tab.connectionId;
|
||||
@@ -7266,6 +7271,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
clientSession: Boolean(executionClientSessionId),
|
||||
});
|
||||
executionDispatched = true;
|
||||
if (effectiveDbType === "oceanbase-oracle" && tab.mode === "query") clientRequestStartedAt = performance.now();
|
||||
if (useAgentResultSession && tab.mode === "query" && typeof pageOffset === "number" && pageOffset > 0 && !options?.pagination?.sessionId && !(tab.batchSqlExecution && tab.batchSqlExecution.total > 1)) {
|
||||
return (async () => {
|
||||
let sessionId: string | undefined;
|
||||
@@ -7338,6 +7344,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
} else {
|
||||
queryExecutionLog("info", "execute-in-txn:invoke", { traceId, txnSessionId: tab.txnSessionId, elapsed: elapsed() });
|
||||
executionDispatched = true;
|
||||
if (effectiveDbType === "oceanbase-oracle" && tab.mode === "query") clientRequestStartedAt = performance.now();
|
||||
// Only an initial manual execution classifies the user SQL (sticky
|
||||
// proven-read-only dialects). A later cursor-page fetch must neither
|
||||
// set nor clear the sticky bit.
|
||||
@@ -7386,6 +7393,11 @@ export const useQueryStore = defineStore("query", () => {
|
||||
void api.cancelQuery(executionId).catch((error) => queryExecutionLog("warn", "frontend-timeout:cancel-failed", { traceId, error }));
|
||||
});
|
||||
if (findExecutionTab(id) !== tab || tab.executionId !== executionId || manualTransactionTargetEpoch(tab) !== executionTargetEpoch) return false;
|
||||
// A single result has an unambiguous request boundary. This includes fetch and
|
||||
// transport, but excludes SQL preparation and the grid's later render work.
|
||||
if (clientRequestStartedAt !== undefined && responseResults.length === 1 && !responseResults[0]?.execution_error) {
|
||||
responseResults[0]!.client_request_wait_ms = Math.max(0, Math.round(performance.now() - clientRequestStartedAt));
|
||||
}
|
||||
const annotatedResults = annotateQueryResultSources(markQueryResultsRowsRaw(responseResults), queryBaseSql, sourceLabelDatabase, effectiveDbType, options?.sourceOffset, sqlStatementParameterOptions, sqlToExecute, options?.sourceOffset === undefined ? undefined : tab.sql);
|
||||
const results = offsetBatchQueryResultIndexes(annotatedResults.results, batchResume?.startStatementIndex ?? 0);
|
||||
if (paginationRowNumberColumn && results.length === 1) {
|
||||
|
||||
Reference in new issue
Block a user