feat(admin): support openGauss and Kingbase monitoring

This commit is contained in:
amwps290 authored and GitHub committed 2026-07-30 18:40:12 +08:00
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). */