feat(admin): support openGauss and Kingbase monitoring
This commit is contained in:
1 parent
cd3163d737
commit
51315e549f
9 files changed
+558
-73
No files matched your search
@@ -0,0 +1,44 @@
|
||||
type KingbaseCatalogObjectKind = "relation" | "function";
|
||||
|
||||
function errorCode(error: unknown): string {
|
||||
if (typeof error !== "object" || error === null || !("code" in error)) return "";
|
||||
return String((error as { code?: unknown }).code ?? "").toUpperCase();
|
||||
}
|
||||
|
||||
function errorMessage(error: unknown): string {
|
||||
if (error instanceof Error) return error.message;
|
||||
if (typeof error === "object" && error !== null && "message" in error) return String((error as { message?: unknown }).message ?? "");
|
||||
return String(error);
|
||||
}
|
||||
|
||||
function referencesCatalogObject(message: string, names: readonly string[]): boolean {
|
||||
const normalized = message.toLowerCase();
|
||||
return names.some((name) => {
|
||||
const lowerName = name.toLowerCase();
|
||||
const shortName = lowerName.slice(lowerName.lastIndexOf(".") + 1);
|
||||
return normalized.includes(lowerName) || normalized.includes(shortName);
|
||||
});
|
||||
}
|
||||
|
||||
function isMissingKingbaseCatalogObject(error: unknown, kind: KingbaseCatalogObjectKind, names: readonly string[]): boolean {
|
||||
const message = errorMessage(error);
|
||||
if (!referencesCatalogObject(message, names)) return false;
|
||||
|
||||
const expectedCode = kind === "relation" ? "42P01" : "42883";
|
||||
const directCode = errorCode(error);
|
||||
if (directCode) return directCode === expectedCode;
|
||||
|
||||
const messageCode = message.match(/\b(?:SQLSTATE\s*[:=]?\s*)?(42P01|42883)\b/i)?.[1]?.toUpperCase();
|
||||
if (messageCode) return messageCode === expectedCode;
|
||||
|
||||
const missingPattern = kind === "relation" ? /(?:relation|table)[\s\S]*?(?:does not exist|not found|undefined)|(?:does not exist|not found|undefined)[\s\S]*?(?:relation|table)/i : /function[\s\S]*?(?:does not exist|not found|undefined)|(?:does not exist|not found|undefined)[\s\S]*?function/i;
|
||||
return missingPattern.test(message);
|
||||
}
|
||||
|
||||
export function isMissingKingbaseSysRelation(error: unknown, names: readonly string[]): boolean {
|
||||
return isMissingKingbaseCatalogObject(error, "relation", names);
|
||||
}
|
||||
|
||||
export function isMissingKingbaseSysFunction(error: unknown, names: readonly string[]): boolean {
|
||||
return isMissingKingbaseCatalogObject(error, "function", names);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { DatabaseType, QueryResult } from "@/types/database";
|
||||
import type { QueryResult } from "@/types/database";
|
||||
import { isMissingKingbaseSysFunction, isMissingKingbaseSysRelation } from "@/lib/database/kingbaseCatalogCompatibility";
|
||||
|
||||
/**
|
||||
* PostgreSQL "current activity / process list" helpers. Pure and framework-free
|
||||
@@ -9,8 +10,6 @@ import type { DatabaseType, QueryResult } from "@/types/database";
|
||||
* bits (coordinator, interval clamping, session counting) are shared from there.
|
||||
*/
|
||||
|
||||
const PG_PROCESS_LIST_DB_TYPES = new Set<DatabaseType>(["postgres"]);
|
||||
|
||||
/**
|
||||
* One row per server-side backend. `now() - query_start` gives the age of the
|
||||
* currently running (or last) statement; we fall back to the transaction and
|
||||
@@ -42,8 +41,45 @@ export const PG_PROCESS_LIST_LEGACY_SQL = `SELECT pid,
|
||||
FROM pg_stat_activity
|
||||
ORDER BY time DESC NULLS LAST`;
|
||||
|
||||
/** openGauss exposes the legacy boolean `waiting` column rather than wait-event detail. */
|
||||
export const OPENGAUSS_PROCESS_LIST_SQL = `SELECT pid,
|
||||
usename AS "user",
|
||||
datname AS db,
|
||||
coalesce(host(client_addr), client_hostname, 'local') AS client,
|
||||
application_name AS app,
|
||||
state,
|
||||
CASE WHEN waiting THEN 'Lock' ELSE '' END AS wait,
|
||||
CAST(floor(extract(epoch FROM (CURRENT_TIMESTAMP - coalesce(query_start, xact_start, backend_start)))) AS BIGINT) AS time,
|
||||
query
|
||||
FROM pg_catalog.pg_stat_activity
|
||||
ORDER BY time DESC NULLS LAST`;
|
||||
|
||||
/**
|
||||
* KingbaseES uses sys_catalog. Numeric epoch subtraction works in both MySQL
|
||||
* and Oracle modes despite their different datetime subtraction rules.
|
||||
*/
|
||||
export const KINGBASE_PROCESS_LIST_SQL = `SELECT pid,
|
||||
usename AS "user",
|
||||
datname AS db,
|
||||
coalesce(CAST(client_addr AS VARCHAR), client_hostname, 'local') AS client,
|
||||
application_name AS app,
|
||||
state,
|
||||
coalesce(nullif(concat_ws(':', wait_event_type, wait_event), ''), '') AS wait,
|
||||
CAST(floor(
|
||||
extract(epoch FROM CAST(CURRENT_TIMESTAMP AS TIMESTAMP))
|
||||
- extract(epoch FROM CAST(coalesce(query_start, xact_start, backend_start) AS TIMESTAMP))
|
||||
) AS BIGINT) AS time,
|
||||
query
|
||||
FROM sys_catalog.sys_stat_activity
|
||||
ORDER BY time DESC NULLS LAST`;
|
||||
|
||||
export const KINGBASE_PG_PROCESS_LIST_SQL = KINGBASE_PROCESS_LIST_SQL.replace("sys_catalog.sys_stat_activity", "pg_catalog.pg_stat_activity");
|
||||
|
||||
/** Scalar query that returns the viewer's own backend pid. */
|
||||
export const PG_OWN_SESSION_SQL = "SELECT pg_backend_pid()";
|
||||
export const OPENGAUSS_OWN_SESSION_SQL = "SELECT pg_backend_pid()";
|
||||
export const KINGBASE_OWN_SESSION_SQL = "SELECT sys_backend_pid()";
|
||||
export const KINGBASE_PG_OWN_SESSION_SQL = "SELECT pg_backend_pid()";
|
||||
|
||||
export interface PgProcessRow {
|
||||
id: number;
|
||||
@@ -117,19 +153,45 @@ export function mapPgProcessRows(result: QueryResult | null | undefined): PgProc
|
||||
* finite positive integer (never interpolated as free text) so there is no
|
||||
* injection path.
|
||||
*/
|
||||
export function buildPgKillSql(pid: number): string {
|
||||
function validateBackendPid(pid: number): void {
|
||||
if (!Number.isInteger(pid) || pid <= 0) {
|
||||
throw new Error(`Invalid backend pid: ${pid}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function buildPgKillSql(pid: number): string {
|
||||
validateBackendPid(pid);
|
||||
return `SELECT pg_terminate_backend(${pid})`;
|
||||
}
|
||||
|
||||
export function buildKingbaseKillSql(pid: number): string {
|
||||
validateBackendPid(pid);
|
||||
return `SELECT sys_terminate_backend(${pid})`;
|
||||
}
|
||||
|
||||
export function buildKingbasePgKillSql(pid: number): string {
|
||||
validateBackendPid(pid);
|
||||
return `SELECT pg_terminate_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");
|
||||
}
|
||||
|
||||
export function kingbaseKillResultError(results: QueryResult[]): string | null {
|
||||
return backendKillResultError(results, "sys_terminate_backend");
|
||||
}
|
||||
|
||||
export function kingbasePgKillResultError(results: QueryResult[]): string | null {
|
||||
return backendKillResultError(results, "pg_terminate_backend");
|
||||
}
|
||||
|
||||
function backendKillResultError(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 "pg_terminate_backend did not terminate the backend";
|
||||
return `${functionName} did not terminate the backend`;
|
||||
}
|
||||
|
||||
/** Detect the undefined-column failure produced by pre-9.6 pg_stat_activity. */
|
||||
@@ -140,7 +202,14 @@ export function isPgProcessListCompatibilityError(error: unknown): boolean {
|
||||
return /(?:wait_event_type|wait_event).*(?:does not exist|42703)|(?:does not exist|42703).*(?:wait_event_type|wait_event)/i.test(message);
|
||||
}
|
||||
|
||||
/** Whether the given database type exposes the Postgres process-list viewer. */
|
||||
export function supportsPgProcessList(dbType: DatabaseType | undefined): boolean {
|
||||
return !!dbType && PG_PROCESS_LIST_DB_TYPES.has(dbType);
|
||||
export function isKingbaseProcessListCatalogCompatibilityError(error: unknown): boolean {
|
||||
return isMissingKingbaseSysRelation(error, ["sys_catalog.sys_stat_activity"]);
|
||||
}
|
||||
|
||||
export function isKingbaseOwnSessionCatalogCompatibilityError(error: unknown): boolean {
|
||||
return isMissingKingbaseSysFunction(error, ["sys_backend_pid"]);
|
||||
}
|
||||
|
||||
export function isKingbaseTerminateCatalogCompatibilityError(error: unknown): boolean {
|
||||
return isMissingKingbaseSysFunction(error, ["sys_terminate_backend"]);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ConnectionConfig, DatabaseType, QueryResult } from "@/types/database";
|
||||
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { isMissingKingbaseSysFunction, isMissingKingbaseSysRelation } from "@/lib/database/kingbaseCatalogCompatibility";
|
||||
import { computeRate, formatBytes, formatBytesPerSec, formatNumber, formatRate, formatUptime, statusEntries, statusNumber, type StatusEntry, type StatusMap, type StatusSample } from "@/lib/database/serverMetrics";
|
||||
|
||||
/**
|
||||
@@ -92,12 +93,113 @@ export const PG_STATUS_LEGACY_SQL = PG_STATUS_SQL.replace(/\bpg_current_wal_lsn\
|
||||
|
||||
export const PG_VARIABLES_SQL = "SELECT current_setting('max_connections') AS max_connections, current_setting('server_version') AS version";
|
||||
|
||||
/**
|
||||
* openGauss keeps the xlog names. Its current-location function returns text,
|
||||
* while the replay-location function returns a `(term, lsn)` record.
|
||||
*/
|
||||
export const OPENGAUSS_STATUS_SQL = `WITH db_stats AS (
|
||||
SELECT
|
||||
coalesce(sum(xact_commit),0) AS xact_commit,
|
||||
coalesce(sum(xact_rollback),0) AS xact_rollback,
|
||||
coalesce(sum(blks_hit),0) AS blks_hit,
|
||||
coalesce(sum(blks_read),0) AS blks_read,
|
||||
coalesce(sum(tup_returned),0) AS tup_returned,
|
||||
coalesce(sum(tup_fetched),0) AS tup_fetched,
|
||||
coalesce(sum(tup_inserted),0) AS tup_inserted,
|
||||
coalesce(sum(tup_updated),0) AS tup_updated,
|
||||
coalesce(sum(tup_deleted),0) AS tup_deleted,
|
||||
coalesce(sum(deadlocks),0) AS deadlocks,
|
||||
coalesce(sum(temp_files),0) AS temp_files
|
||||
FROM pg_catalog.pg_stat_database
|
||||
), activity_stats AS (
|
||||
SELECT
|
||||
coalesce(sum(CASE WHEN state IS NOT NULL THEN 1 ELSE 0 END),0) AS connections,
|
||||
coalesce(sum(CASE WHEN state = 'active' THEN 1 ELSE 0 END),0) AS active_connections,
|
||||
coalesce(sum(CASE WHEN state = 'idle' THEN 1 ELSE 0 END),0) AS idle_connections
|
||||
FROM pg_catalog.pg_stat_activity
|
||||
WHERE pid <> pg_backend_pid()
|
||||
)
|
||||
SELECT
|
||||
db_stats.*,
|
||||
activity_stats.*,
|
||||
coalesce(CASE WHEN pg_is_in_recovery()
|
||||
THEN pg_xlog_location_diff(CAST((pg_last_xlog_replay_location()).lsn AS text), CAST('0/0' AS text))
|
||||
ELSE pg_xlog_location_diff(CAST(pg_current_xlog_location() AS text), CAST('0/0' AS text))
|
||||
END, 0) AS wal_bytes,
|
||||
CAST(floor(extract(epoch FROM (CURRENT_TIMESTAMP - pg_postmaster_start_time()))) AS BIGINT) AS uptime_seconds
|
||||
FROM db_stats
|
||||
CROSS JOIN activity_stats`;
|
||||
|
||||
/**
|
||||
* Some openGauss builds return `text` (not a `(term, lsn)` record) from
|
||||
* `pg_last_xlog_replay_location()`, which makes the `.lsn` field access in
|
||||
* `OPENGAUSS_STATUS_SQL` fail to *parse* — and both CASE branches are planned,
|
||||
* so it fails even on a primary. This fallback drops the record notation and
|
||||
* reads the replay location as scalar text; the driver retries with it only
|
||||
* after the primary query raises `isOpenGaussReplayRecordError`.
|
||||
*/
|
||||
export const OPENGAUSS_STATUS_FALLBACK_SQL = OPENGAUSS_STATUS_SQL.replace("(pg_last_xlog_replay_location()).lsn", "pg_last_xlog_replay_location()");
|
||||
|
||||
export const OPENGAUSS_VARIABLES_SQL = "SELECT current_setting('max_connections') AS max_connections, version() AS version";
|
||||
|
||||
/**
|
||||
* KingbaseES exposes monitoring under sys_* names. Epoch values are subtracted
|
||||
* numerically because MySQL mode types CURRENT_TIMESTAMP as datetime while the
|
||||
* monitoring functions return timestamp with time zone.
|
||||
*/
|
||||
function buildKingbaseStatusSql(catalog: "sys_catalog" | "pg_catalog", prefix: "sys" | "pg"): string {
|
||||
return `WITH db_stats AS (
|
||||
SELECT
|
||||
coalesce(sum(xact_commit),0) AS xact_commit,
|
||||
coalesce(sum(xact_rollback),0) AS xact_rollback,
|
||||
coalesce(sum(blks_hit),0) AS blks_hit,
|
||||
coalesce(sum(blks_read),0) AS blks_read,
|
||||
coalesce(sum(tup_returned),0) AS tup_returned,
|
||||
coalesce(sum(tup_fetched),0) AS tup_fetched,
|
||||
coalesce(sum(tup_inserted),0) AS tup_inserted,
|
||||
coalesce(sum(tup_updated),0) AS tup_updated,
|
||||
coalesce(sum(tup_deleted),0) AS tup_deleted,
|
||||
coalesce(sum(deadlocks),0) AS deadlocks,
|
||||
coalesce(sum(temp_files),0) AS temp_files
|
||||
FROM ${catalog}.${prefix}_stat_database
|
||||
), activity_stats AS (
|
||||
SELECT
|
||||
coalesce(sum(CASE WHEN state IS NOT NULL THEN 1 ELSE 0 END),0) AS connections,
|
||||
coalesce(sum(CASE WHEN state = 'active' THEN 1 ELSE 0 END),0) AS active_connections,
|
||||
coalesce(sum(CASE WHEN state = 'idle' THEN 1 ELSE 0 END),0) AS idle_connections
|
||||
FROM ${catalog}.${prefix}_stat_activity
|
||||
WHERE pid <> ${prefix}_backend_pid()
|
||||
)
|
||||
SELECT
|
||||
db_stats.*,
|
||||
activity_stats.*,
|
||||
coalesce(CASE WHEN ${prefix}_is_in_recovery()
|
||||
THEN ${prefix}_wal_lsn_diff(${prefix}_last_wal_replay_lsn(), '0/0')
|
||||
ELSE ${prefix}_wal_lsn_diff(${prefix}_current_wal_lsn(), '0/0')
|
||||
END, 0) AS wal_bytes,
|
||||
CAST(floor(
|
||||
extract(epoch FROM CAST(CURRENT_TIMESTAMP AS TIMESTAMP))
|
||||
- extract(epoch FROM CAST(${prefix}_postmaster_start_time() AS TIMESTAMP))
|
||||
) AS BIGINT) AS uptime_seconds
|
||||
FROM db_stats
|
||||
CROSS JOIN activity_stats`;
|
||||
}
|
||||
|
||||
export const KINGBASE_STATUS_SQL = buildKingbaseStatusSql("sys_catalog", "sys");
|
||||
export const KINGBASE_PG_STATUS_SQL = buildKingbaseStatusSql("pg_catalog", "pg");
|
||||
|
||||
export const KINGBASE_VARIABLES_SQL = "SELECT current_setting('max_connections') AS max_connections, version() AS version";
|
||||
|
||||
export interface PgServerStatusDriver {
|
||||
statusSql: string;
|
||||
variablesSql: string;
|
||||
fallbackStatusSql?: string;
|
||||
shouldUseFallbackStatusSql?(error: unknown): boolean;
|
||||
}
|
||||
|
||||
/** Max samples retained for the live charts (~ a few minutes at 5s cadence). */
|
||||
export const MAX_SAMPLES = 60;
|
||||
|
||||
/** Engines exposing `pg_stat_database`/`pg_stat_activity` in the shape this dashboard expects. */
|
||||
const SERVER_DASHBOARD_DB_TYPES = new Set<DatabaseType>(["postgres"]);
|
||||
|
||||
/** Detect the undefined-function failure produced by pre-10 servers lacking `pg_current_wal_lsn`/`pg_last_wal_replay_lsn`/`pg_wal_lsn_diff`. */
|
||||
export function isPgStatusCompatibilityError(error: unknown): boolean {
|
||||
const code = typeof error === "object" && error !== null && "code" in error ? String((error as { code?: unknown }).code ?? "") : "";
|
||||
@@ -111,6 +213,61 @@ export function isPgStatusCompatibilityError(error: unknown): boolean {
|
||||
return /(?:pg_current_wal_lsn|pg_last_wal_replay_lsn|pg_wal_lsn_diff)/i.test(message) && /does not exist/i.test(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the failure raised when an openGauss build's `pg_last_xlog_replay_location()`
|
||||
* returns `text` instead of a `(term, lsn)` record, so the `.lsn` field access in
|
||||
* `OPENGAUSS_STATUS_SQL` is invalid. SQLSTATE 42809 is `wrong_object_type` (column
|
||||
* notation on a non-composite value); some builds report it as 42703 with an
|
||||
* "identify column" message instead. Gates the retry with `OPENGAUSS_STATUS_FALLBACK_SQL`.
|
||||
* NOTE: confirm the exact message/SQLSTATE on the target openGauss build.
|
||||
*/
|
||||
export function isOpenGaussReplayRecordError(error: unknown): boolean {
|
||||
const code = typeof error === "object" && error !== null && "code" in error ? String((error as { code?: unknown }).code ?? "") : "";
|
||||
if (code === "42809") return true;
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return /\blsn\b/i.test(message) && /(?:composite|record data type|column notation|identify column)/i.test(message);
|
||||
}
|
||||
|
||||
export function isKingbaseStatusCatalogCompatibilityError(error: unknown): boolean {
|
||||
return isMissingKingbaseSysRelation(error, ["sys_catalog.sys_stat_database", "sys_catalog.sys_stat_activity"]) || isMissingKingbaseSysFunction(error, ["sys_backend_pid", "sys_is_in_recovery", "sys_wal_lsn_diff", "sys_last_wal_replay_lsn", "sys_current_wal_lsn", "sys_postmaster_start_time"]);
|
||||
}
|
||||
|
||||
const POSTGRES_STATUS_DRIVER: PgServerStatusDriver = {
|
||||
statusSql: PG_STATUS_SQL,
|
||||
variablesSql: PG_VARIABLES_SQL,
|
||||
fallbackStatusSql: PG_STATUS_LEGACY_SQL,
|
||||
shouldUseFallbackStatusSql: isPgStatusCompatibilityError,
|
||||
};
|
||||
|
||||
const OPENGAUSS_STATUS_DRIVER: PgServerStatusDriver = {
|
||||
statusSql: OPENGAUSS_STATUS_SQL,
|
||||
variablesSql: OPENGAUSS_VARIABLES_SQL,
|
||||
fallbackStatusSql: OPENGAUSS_STATUS_FALLBACK_SQL,
|
||||
shouldUseFallbackStatusSql: isOpenGaussReplayRecordError,
|
||||
};
|
||||
|
||||
const KINGBASE_STATUS_DRIVER: PgServerStatusDriver = {
|
||||
statusSql: KINGBASE_STATUS_SQL,
|
||||
variablesSql: KINGBASE_VARIABLES_SQL,
|
||||
fallbackStatusSql: KINGBASE_PG_STATUS_SQL,
|
||||
shouldUseFallbackStatusSql: isKingbaseStatusCatalogCompatibilityError,
|
||||
};
|
||||
|
||||
/** Resolve the SQL contract used by the shared PostgreSQL-family dashboard. */
|
||||
export function resolveServerDashboardDriver(dbType: DatabaseType | undefined): PgServerStatusDriver | null {
|
||||
if (dbType === "postgres") return POSTGRES_STATUS_DRIVER;
|
||||
if (dbType === "opengauss") return OPENGAUSS_STATUS_DRIVER;
|
||||
if (dbType === "kingbase") return KINGBASE_STATUS_DRIVER;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveServerDashboardDriverForConnection(connection: ConnectionConfig | undefined): PgServerStatusDriver | null {
|
||||
if (!connection) return null;
|
||||
const dbType = effectiveDatabaseTypeForConnection(connection);
|
||||
if (dbType === "gaussdb" && connection.driver_profile?.toLowerCase() === "opengauss") return OPENGAUSS_STATUS_DRIVER;
|
||||
return resolveServerDashboardDriver(dbType);
|
||||
}
|
||||
|
||||
/** Parse the single-row `PG_STATUS_SQL` / `PG_VARIABLES_SQL` result into a name/value map. */
|
||||
export function parsePgStatusRow(result: QueryResult | null | undefined): StatusMap {
|
||||
const map: StatusMap = {};
|
||||
@@ -142,12 +299,7 @@ export function pgCacheHitRatio(status: StatusMap): number | null {
|
||||
return Math.max(0, Math.min(100, ratio));
|
||||
}
|
||||
|
||||
/** Whether the given database type exposes the Postgres server dashboard. */
|
||||
export function supportsServerDashboard(dbType: DatabaseType | undefined): boolean {
|
||||
return !!dbType && SERVER_DASHBOARD_DB_TYPES.has(dbType);
|
||||
}
|
||||
|
||||
/** Connection-aware gate (mirrors the MySQL server-dashboard gate). */
|
||||
export function connectionSupportsServerDashboard(connection: ConnectionConfig | undefined): boolean {
|
||||
return !!connection && supportsServerDashboard(effectiveDatabaseTypeForConnection(connection));
|
||||
return resolveServerDashboardDriverForConnection(connection) !== null;
|
||||
}
|
||||
@@ -1,7 +1,28 @@
|
||||
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 { buildPgKillSql, isPgProcessListCompatibilityError, mapPgProcessRows, pgKillResultError, PG_OWN_SESSION_SQL, PG_PROCESS_LIST_LEGACY_SQL, PG_PROCESS_LIST_SQL, supportsPgProcessList } from "./postgresProcessList";
|
||||
import {
|
||||
buildKingbaseKillSql,
|
||||
buildKingbasePgKillSql,
|
||||
buildPgKillSql,
|
||||
isKingbaseOwnSessionCatalogCompatibilityError,
|
||||
isKingbaseProcessListCatalogCompatibilityError,
|
||||
isKingbaseTerminateCatalogCompatibilityError,
|
||||
isPgProcessListCompatibilityError,
|
||||
kingbaseKillResultError,
|
||||
kingbasePgKillResultError,
|
||||
KINGBASE_OWN_SESSION_SQL,
|
||||
KINGBASE_PG_OWN_SESSION_SQL,
|
||||
KINGBASE_PG_PROCESS_LIST_SQL,
|
||||
KINGBASE_PROCESS_LIST_SQL,
|
||||
mapPgProcessRows,
|
||||
OPENGAUSS_OWN_SESSION_SQL,
|
||||
OPENGAUSS_PROCESS_LIST_SQL,
|
||||
pgKillResultError,
|
||||
PG_OWN_SESSION_SQL,
|
||||
PG_PROCESS_LIST_LEGACY_SQL,
|
||||
PG_PROCESS_LIST_SQL,
|
||||
} from "./postgresProcessList";
|
||||
|
||||
/**
|
||||
* Engine-agnostic process-list model. Each supported engine contributes a driver
|
||||
@@ -34,6 +55,10 @@ export interface ProcessListDriver {
|
||||
shouldUseFallbackListSql?(error: unknown): boolean;
|
||||
/** Scalar SQL returning the caller's own session id (nullable path tolerated). */
|
||||
ownSessionSql: string;
|
||||
/** Compatibility query used when the primary own-session function is unavailable. */
|
||||
fallbackOwnSessionSql?: string;
|
||||
/** Restrict own-session fallback attempts to known compatibility failures. */
|
||||
shouldUseFallbackOwnSessionSql?(error: unknown): boolean;
|
||||
/** Columns to render, in display order. */
|
||||
columns: ProcessColumn[];
|
||||
/** Column key used for the initial sort. */
|
||||
@@ -44,8 +69,14 @@ export interface ProcessListDriver {
|
||||
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;
|
||||
}
|
||||
|
||||
const MYSQL_COLUMNS: ProcessColumn[] = [
|
||||
@@ -95,10 +126,41 @@ const POSTGRES_DRIVER: ProcessListDriver = {
|
||||
killResultError: pgKillResultError,
|
||||
};
|
||||
|
||||
const OPENGAUSS_DRIVER: ProcessListDriver = {
|
||||
listSql: OPENGAUSS_PROCESS_LIST_SQL,
|
||||
ownSessionSql: OPENGAUSS_OWN_SESSION_SQL,
|
||||
columns: POSTGRES_COLUMNS,
|
||||
defaultSortKey: "time",
|
||||
maxRows: 5000,
|
||||
mapRows: (result) => mapPgProcessRows(result) as unknown as ProcessRow[],
|
||||
buildKillSql: buildPgKillSql,
|
||||
killResultError: pgKillResultError,
|
||||
};
|
||||
|
||||
const KINGBASE_DRIVER: ProcessListDriver = {
|
||||
listSql: KINGBASE_PROCESS_LIST_SQL,
|
||||
fallbackListSql: KINGBASE_PG_PROCESS_LIST_SQL,
|
||||
shouldUseFallbackListSql: isKingbaseProcessListCatalogCompatibilityError,
|
||||
ownSessionSql: KINGBASE_OWN_SESSION_SQL,
|
||||
fallbackOwnSessionSql: KINGBASE_PG_OWN_SESSION_SQL,
|
||||
shouldUseFallbackOwnSessionSql: isKingbaseOwnSessionCatalogCompatibilityError,
|
||||
columns: POSTGRES_COLUMNS,
|
||||
defaultSortKey: "time",
|
||||
maxRows: 5000,
|
||||
mapRows: (result) => mapPgProcessRows(result) as unknown as ProcessRow[],
|
||||
buildKillSql: buildKingbaseKillSql,
|
||||
buildFallbackKillSql: buildKingbasePgKillSql,
|
||||
shouldUseFallbackKillSql: isKingbaseTerminateCatalogCompatibilityError,
|
||||
killResultError: kingbaseKillResultError,
|
||||
fallbackKillResultError: kingbasePgKillResultError,
|
||||
};
|
||||
|
||||
/** Resolve the process-list driver for a connection, or null if unsupported. */
|
||||
export function resolveProcessListDriver(dbType: DatabaseType | undefined): ProcessListDriver | null {
|
||||
if (supportsMysqlProcessList(dbType)) return MYSQL_DRIVER;
|
||||
if (supportsPgProcessList(dbType)) return POSTGRES_DRIVER;
|
||||
if (dbType === "postgres") return POSTGRES_DRIVER;
|
||||
if (dbType === "opengauss") return OPENGAUSS_DRIVER;
|
||||
if (dbType === "kingbase") return KINGBASE_DRIVER;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -124,7 +186,9 @@ export function resolveProcessListDriverForConnection(connection: ConnectionConf
|
||||
const profile = [connection.driver_profile, connection.connection_string, connection.jdbc_driver_class, ...(connection.jdbc_driver_paths ?? [])].filter(Boolean).join("\n");
|
||||
if (MYSQL_LOOKALIKE_JDBC.test(profile)) return null;
|
||||
}
|
||||
return resolveProcessListDriver(effectiveDatabaseTypeForConnection(connection));
|
||||
const dbType = effectiveDatabaseTypeForConnection(connection);
|
||||
if (dbType === "gaussdb" && connection.driver_profile?.toLowerCase() === "opengauss") return OPENGAUSS_DRIVER;
|
||||
return resolveProcessListDriver(dbType);
|
||||
}
|
||||
|
||||
/** Connection-aware process-list gate (mirrors the server-dashboard gate). */
|
||||
|
||||
Reference in new issue
Block a user