fix(process-list): cancel queries without closing sessions
Closes #3967
This commit is contained in:
1 parent
396ebf8393
commit
4106a8ce22
15 files changed
+189
-165
No files matched your search
@@ -2,7 +2,7 @@ import type { DatabaseType, QueryResult } from "@/types/database";
|
||||
|
||||
/**
|
||||
* Engines that speak the MySQL protocol and support `SHOW FULL PROCESSLIST` /
|
||||
* `KILL CONNECTION`. MariaDB, TiDB, and OceanBase ride the `mysql` dbType via a
|
||||
* `KILL QUERY`. MariaDB, TiDB, and OceanBase ride the `mysql` dbType via a
|
||||
* driver profile, so they are covered by the `"mysql"` entry.
|
||||
*/
|
||||
const PROCESS_LIST_DB_TYPES = new Set<DatabaseType>(["mysql"]);
|
||||
@@ -127,14 +127,14 @@ export function mapProcessRows(result: QueryResult | null | undefined): ProcessR
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the `KILL CONNECTION <id>` statement. `id` must be a finite integer; it
|
||||
* Build the `KILL QUERY <id>` statement. `id` must be a finite positive integer; it
|
||||
* is validated (never interpolated as free text) so there is no injection path.
|
||||
*/
|
||||
export function buildKillSql(id: number): string {
|
||||
if (!Number.isInteger(id) || id < 0) {
|
||||
export function buildCancelQuerySql(id: number): string {
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
throw new Error(`Invalid session id: ${id}`);
|
||||
}
|
||||
return `KILL CONNECTION ${id}`;
|
||||
return `KILL QUERY ${id}`;
|
||||
}
|
||||
|
||||
/** Clamp a user-entered refresh interval to a safe integer range of seconds. */
|
||||
|
||||
@@ -149,7 +149,7 @@ export function mapPgProcessRows(result: QueryResult | null | undefined): PgProc
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a `SELECT pg_terminate_backend(<pid>)` statement. `pid` is validated as a
|
||||
* Build a `SELECT pg_cancel_backend(<pid>)` statement. `pid` is validated as a
|
||||
* finite positive integer (never interpolated as free text) so there is no
|
||||
* injection path.
|
||||
*/
|
||||
@@ -159,39 +159,39 @@ function validateBackendPid(pid: number): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function buildPgKillSql(pid: number): string {
|
||||
export function buildPgCancelQuerySql(pid: number): string {
|
||||
validateBackendPid(pid);
|
||||
return `SELECT pg_terminate_backend(${pid})`;
|
||||
return `SELECT pg_cancel_backend(${pid})`;
|
||||
}
|
||||
|
||||
export function buildKingbaseKillSql(pid: number): string {
|
||||
export function buildKingbaseCancelQuerySql(pid: number): string {
|
||||
validateBackendPid(pid);
|
||||
return `SELECT sys_terminate_backend(${pid})`;
|
||||
return `SELECT sys_cancel_backend(${pid})`;
|
||||
}
|
||||
|
||||
export function buildKingbasePgKillSql(pid: number): string {
|
||||
export function buildKingbasePgCancelQuerySql(pid: number): string {
|
||||
validateBackendPid(pid);
|
||||
return `SELECT pg_terminate_backend(${pid})`;
|
||||
return `SELECT pg_cancel_backend(${pid})`;
|
||||
}
|
||||
|
||||
/** Return an error when PostgreSQL declines to terminate the target backend. */
|
||||
export function pgKillResultError(results: QueryResult[]): string | null {
|
||||
return backendKillResultError(results, "pg_terminate_backend");
|
||||
/** Return an error when PostgreSQL reports that no running query was canceled. */
|
||||
export function pgCancelQueryResultError(results: QueryResult[]): string | null {
|
||||
return backendCancelQueryResultError(results, "pg_cancel_backend");
|
||||
}
|
||||
|
||||
export function kingbaseKillResultError(results: QueryResult[]): string | null {
|
||||
return backendKillResultError(results, "sys_terminate_backend");
|
||||
export function kingbaseCancelQueryResultError(results: QueryResult[]): string | null {
|
||||
return backendCancelQueryResultError(results, "sys_cancel_backend");
|
||||
}
|
||||
|
||||
export function kingbasePgKillResultError(results: QueryResult[]): string | null {
|
||||
return backendKillResultError(results, "pg_terminate_backend");
|
||||
export function kingbasePgCancelQueryResultError(results: QueryResult[]): string | null {
|
||||
return backendCancelQueryResultError(results, "pg_cancel_backend");
|
||||
}
|
||||
|
||||
function backendKillResultError(results: QueryResult[], functionName: string): string | null {
|
||||
function backendCancelQueryResultError(results: QueryResult[], functionName: string): string | null {
|
||||
const result = results.find((item) => item.execution_error !== true);
|
||||
const value = result?.rows?.[0]?.[0];
|
||||
if (value === true || value === 1 || String(value).toLowerCase() === "t" || String(value).toLowerCase() === "true") return null;
|
||||
return `${functionName} did not terminate the backend`;
|
||||
return `${functionName} did not cancel a running query`;
|
||||
}
|
||||
|
||||
/** Detect the undefined-column failure produced by pre-9.6 pg_stat_activity. */
|
||||
@@ -210,6 +210,6 @@ export function isKingbaseOwnSessionCatalogCompatibilityError(error: unknown): b
|
||||
return isMissingKingbaseSysFunction(error, ["sys_backend_pid"]);
|
||||
}
|
||||
|
||||
export function isKingbaseTerminateCatalogCompatibilityError(error: unknown): boolean {
|
||||
return isMissingKingbaseSysFunction(error, ["sys_terminate_backend"]);
|
||||
export function isKingbaseCancelCatalogCompatibilityError(error: unknown): boolean {
|
||||
return isMissingKingbaseSysFunction(error, ["sys_cancel_backend"]);
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import type { ConnectionConfig, DatabaseType, QueryResult } from "@/types/database";
|
||||
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { buildKillSql as buildMysqlKillSql, mapProcessRows as mapMysqlProcessRows, PROCESS_LIST_SQL as MYSQL_PROCESS_LIST_SQL, supportsProcessList as supportsMysqlProcessList } from "./mysqlProcessList";
|
||||
import { buildCancelQuerySql as buildMysqlCancelQuerySql, mapProcessRows as mapMysqlProcessRows, PROCESS_LIST_SQL as MYSQL_PROCESS_LIST_SQL, supportsProcessList as supportsMysqlProcessList } from "./mysqlProcessList";
|
||||
import {
|
||||
buildKingbaseKillSql,
|
||||
buildKingbasePgKillSql,
|
||||
buildPgKillSql,
|
||||
buildKingbaseCancelQuerySql,
|
||||
buildKingbasePgCancelQuerySql,
|
||||
buildPgCancelQuerySql,
|
||||
isKingbaseOwnSessionCatalogCompatibilityError,
|
||||
isKingbaseProcessListCatalogCompatibilityError,
|
||||
isKingbaseTerminateCatalogCompatibilityError,
|
||||
isKingbaseCancelCatalogCompatibilityError,
|
||||
isPgProcessListCompatibilityError,
|
||||
kingbaseKillResultError,
|
||||
kingbasePgKillResultError,
|
||||
kingbaseCancelQueryResultError,
|
||||
kingbasePgCancelQueryResultError,
|
||||
KINGBASE_OWN_SESSION_SQL,
|
||||
KINGBASE_PG_OWN_SESSION_SQL,
|
||||
KINGBASE_PG_PROCESS_LIST_SQL,
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
mapPgProcessRows,
|
||||
OPENGAUSS_OWN_SESSION_SQL,
|
||||
OPENGAUSS_PROCESS_LIST_SQL,
|
||||
pgKillResultError,
|
||||
pgCancelQueryResultError,
|
||||
PG_OWN_SESSION_SQL,
|
||||
PG_PROCESS_LIST_LEGACY_SQL,
|
||||
PG_PROCESS_LIST_SQL,
|
||||
@@ -67,16 +67,16 @@ export interface ProcessListDriver {
|
||||
maxRows: number;
|
||||
/** Map a raw list result into typed rows. */
|
||||
mapRows(result: QueryResult | null | undefined): ProcessRow[];
|
||||
/** Build the validated statement that kills the given session id. */
|
||||
buildKillSql(id: number): string;
|
||||
/** Build the compatibility statement used when the primary kill function is unavailable. */
|
||||
buildFallbackKillSql?(id: number): string;
|
||||
/** Restrict kill fallback attempts to known compatibility failures. */
|
||||
shouldUseFallbackKillSql?(error: unknown): boolean;
|
||||
/** Validate any engine-specific success value returned by the kill statement. */
|
||||
killResultError?(results: QueryResult[]): string | null;
|
||||
/** Validate the success value returned by the compatibility kill statement. */
|
||||
fallbackKillResultError?(results: QueryResult[]): string | null;
|
||||
/** Build the validated statement that cancels the selected session's running query. */
|
||||
buildCancelQuerySql(id: number): string;
|
||||
/** Build the compatibility statement used when the primary cancellation function is unavailable. */
|
||||
buildFallbackCancelQuerySql?(id: number): string;
|
||||
/** Restrict cancellation fallback attempts to known compatibility failures. */
|
||||
shouldUseFallbackCancelQuerySql?(error: unknown): boolean;
|
||||
/** Validate any engine-specific success value returned by the cancellation statement. */
|
||||
cancelQueryResultError?(results: QueryResult[]): string | null;
|
||||
/** Validate the success value returned by the compatibility cancellation statement. */
|
||||
fallbackCancelQueryResultError?(results: QueryResult[]): string | null;
|
||||
}
|
||||
|
||||
const MYSQL_COLUMNS: ProcessColumn[] = [
|
||||
@@ -110,7 +110,7 @@ const MYSQL_DRIVER: ProcessListDriver = {
|
||||
maxRows: 5000,
|
||||
// Typed structs carry no index signature; they are plain string-keyed objects at runtime.
|
||||
mapRows: (result) => mapMysqlProcessRows(result) as unknown as ProcessRow[],
|
||||
buildKillSql: buildMysqlKillSql,
|
||||
buildCancelQuerySql: buildMysqlCancelQuerySql,
|
||||
};
|
||||
|
||||
const POSTGRES_DRIVER: ProcessListDriver = {
|
||||
@@ -122,8 +122,8 @@ const POSTGRES_DRIVER: ProcessListDriver = {
|
||||
defaultSortKey: "time",
|
||||
maxRows: 5000,
|
||||
mapRows: (result) => mapPgProcessRows(result) as unknown as ProcessRow[],
|
||||
buildKillSql: buildPgKillSql,
|
||||
killResultError: pgKillResultError,
|
||||
buildCancelQuerySql: buildPgCancelQuerySql,
|
||||
cancelQueryResultError: pgCancelQueryResultError,
|
||||
};
|
||||
|
||||
const OPENGAUSS_DRIVER: ProcessListDriver = {
|
||||
@@ -133,8 +133,8 @@ const OPENGAUSS_DRIVER: ProcessListDriver = {
|
||||
defaultSortKey: "time",
|
||||
maxRows: 5000,
|
||||
mapRows: (result) => mapPgProcessRows(result) as unknown as ProcessRow[],
|
||||
buildKillSql: buildPgKillSql,
|
||||
killResultError: pgKillResultError,
|
||||
buildCancelQuerySql: buildPgCancelQuerySql,
|
||||
cancelQueryResultError: pgCancelQueryResultError,
|
||||
};
|
||||
|
||||
const KINGBASE_DRIVER: ProcessListDriver = {
|
||||
@@ -148,11 +148,11 @@ const KINGBASE_DRIVER: ProcessListDriver = {
|
||||
defaultSortKey: "time",
|
||||
maxRows: 5000,
|
||||
mapRows: (result) => mapPgProcessRows(result) as unknown as ProcessRow[],
|
||||
buildKillSql: buildKingbaseKillSql,
|
||||
buildFallbackKillSql: buildKingbasePgKillSql,
|
||||
shouldUseFallbackKillSql: isKingbaseTerminateCatalogCompatibilityError,
|
||||
killResultError: kingbaseKillResultError,
|
||||
fallbackKillResultError: kingbasePgKillResultError,
|
||||
buildCancelQuerySql: buildKingbaseCancelQuerySql,
|
||||
buildFallbackCancelQuerySql: buildKingbasePgCancelQuerySql,
|
||||
shouldUseFallbackCancelQuerySql: isKingbaseCancelCatalogCompatibilityError,
|
||||
cancelQueryResultError: kingbaseCancelQueryResultError,
|
||||
fallbackCancelQueryResultError: kingbasePgCancelQueryResultError,
|
||||
};
|
||||
|
||||
/** Resolve the process-list driver for a connection, or null if unsupported. */
|
||||
|
||||
Reference in new issue
Block a user