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

@@ -1,6 +1,24 @@
import { describe, expect, it } from "vitest";
import type { QueryResult } from "@/types/database";
import { buildPgKillSql, isPgProcessListCompatibilityError, mapPgProcessRows, pgKillResultError, PG_PROCESS_LIST_LEGACY_SQL, PG_PROCESS_LIST_SQL, supportsPgProcessList } from "@/lib/database/postgresProcessList";
import {
buildKingbaseKillSql,
buildKingbasePgKillSql,
buildPgKillSql,
isKingbaseOwnSessionCatalogCompatibilityError,
isKingbaseProcessListCatalogCompatibilityError,
isKingbaseTerminateCatalogCompatibilityError,
isPgProcessListCompatibilityError,
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_PROCESS_LIST_LEGACY_SQL,
PG_PROCESS_LIST_SQL,
} from "@/lib/database/postgresProcessList";
import { connectionSupportsProcessList, resolveProcessListDriver, resolveProcessListDriverForConnection, supportsProcessList } from "@/lib/database/processListDrivers";
import type { ConnectionConfig } from "@/types/database";
@@ -49,6 +67,55 @@ describe("buildPgKillSql", () => {
expect(() => buildPgKillSql(-1)).toThrow();
expect(() => buildPgKillSql(Number.NaN)).toThrow();
});
it("builds the KingbaseES sys_terminate_backend call with the same PID validation", () => {
expect(buildKingbaseKillSql(4211)).toBe("SELECT sys_terminate_backend(4211)");
expect(() => buildKingbaseKillSql(0)).toThrow();
expect(() => buildKingbaseKillSql(1.5)).toThrow();
});
});
describe("PostgreSQL-family process SQL", () => {
it("uses openGauss's pg catalog and boolean waiting column", () => {
const driver = resolveProcessListDriver("opengauss");
expect(driver?.listSql).toBe(OPENGAUSS_PROCESS_LIST_SQL);
expect(driver?.ownSessionSql).toBe(OPENGAUSS_OWN_SESSION_SQL);
expect(OPENGAUSS_PROCESS_LIST_SQL).toContain("FROM pg_catalog.pg_stat_activity");
expect(OPENGAUSS_PROCESS_LIST_SQL).toContain("CASE WHEN waiting");
expect(OPENGAUSS_PROCESS_LIST_SQL).not.toContain("wait_event_type");
expect(driver?.buildKillSql(7)).toBe("SELECT pg_terminate_backend(7)");
});
it("uses KingbaseES's sys catalog and session functions", () => {
const driver = resolveProcessListDriver("kingbase");
expect(driver?.listSql).toBe(KINGBASE_PROCESS_LIST_SQL);
expect(driver?.ownSessionSql).toBe(KINGBASE_OWN_SESSION_SQL);
expect(KINGBASE_PROCESS_LIST_SQL).toContain("FROM sys_catalog.sys_stat_activity");
expect(KINGBASE_PROCESS_LIST_SQL).toContain("wait_event_type");
expect(KINGBASE_PROCESS_LIST_SQL).toContain("extract(epoch FROM CAST(CURRENT_TIMESTAMP AS TIMESTAMP))");
expect(KINGBASE_PROCESS_LIST_SQL).toContain("extract(epoch FROM CAST(coalesce(query_start, xact_start, backend_start) AS TIMESTAMP))");
expect(KINGBASE_PROCESS_LIST_SQL).not.toContain("CURRENT_TIMESTAMP - coalesce(query_start");
expect(driver?.buildKillSql(7)).toBe("SELECT sys_terminate_backend(7)");
});
it("provides pg_catalog fallbacks for pg-compatible KingbaseES servers", () => {
const driver = resolveProcessListDriver("kingbase");
expect(driver?.fallbackListSql).toBe(KINGBASE_PG_PROCESS_LIST_SQL);
expect(driver?.fallbackOwnSessionSql).toBe(KINGBASE_PG_OWN_SESSION_SQL);
expect(driver?.buildFallbackKillSql?.(7)).toBe("SELECT pg_terminate_backend(7)");
expect(KINGBASE_PG_PROCESS_LIST_SQL).toContain("FROM pg_catalog.pg_stat_activity");
expect(KINGBASE_PG_PROCESS_LIST_SQL).not.toContain("sys_catalog");
expect(buildKingbasePgKillSql(4211)).toBe("SELECT pg_terminate_backend(4211)");
});
it("retries only missing sys relation/function errors", () => {
expect(isKingbaseProcessListCatalogCompatibilityError(new Error('relation "sys_catalog.sys_stat_activity" does not exist (SQLSTATE 42P01)'))).toBe(true);
expect(isKingbaseOwnSessionCatalogCompatibilityError(Object.assign(new Error("function sys_backend_pid() does not exist"), { code: "42883" }))).toBe(true);
expect(isKingbaseTerminateCatalogCompatibilityError(new Error("function sys_terminate_backend(integer) does not exist (SQLSTATE 42883)"))).toBe(true);
expect(isKingbaseProcessListCatalogCompatibilityError(Object.assign(new Error("permission denied for relation sys_catalog.sys_stat_activity"), { code: "42501" }))).toBe(false);
expect(isKingbaseOwnSessionCatalogCompatibilityError(new Error("authentication failed for sys_backend_pid"))).toBe(false);
expect(isKingbaseTerminateCatalogCompatibilityError(new Error("connection refused"))).toBe(false);
});
});
describe("Postgres compatibility", () => {
@@ -69,19 +136,6 @@ describe("Postgres compatibility", () => {
});
});
describe("supportsPgProcessList", () => {
it("covers the Postgres-kernel family and excludes divergent wire-protocol engines", () => {
expect(supportsPgProcessList("postgres")).toBe(true);
// Postgres-kernel forks are unverified for now, so they stay excluded.
expect(supportsPgProcessList("opengauss")).toBe(false);
expect(supportsPgProcessList("kingbase")).toBe(false);
expect(supportsPgProcessList("redshift")).toBe(false);
expect(supportsPgProcessList("questdb")).toBe(false);
expect(supportsPgProcessList("mysql")).toBe(false);
expect(supportsPgProcessList(undefined)).toBe(false);
});
});
describe("resolveProcessListDriver", () => {
it("routes MySQL and Postgres engines to distinct drivers", () => {
const mysql = resolveProcessListDriver("mysql");
@@ -97,6 +151,11 @@ describe("resolveProcessListDriver", () => {
it("unifies process-list support across both families", () => {
expect(supportsProcessList("mysql")).toBe(true);
expect(supportsProcessList("postgres")).toBe(true);
expect(supportsProcessList("opengauss")).toBe(true);
expect(supportsProcessList("kingbase")).toBe(true);
// Postgres wire-protocol lookalikes with divergent catalogs stay excluded.
expect(supportsProcessList("redshift")).toBe(false);
expect(supportsProcessList("questdb")).toBe(false);
expect(supportsProcessList("sqlite")).toBe(false);
expect(supportsProcessList(undefined)).toBe(false);
});
@@ -110,6 +169,10 @@ describe("connectionSupportsProcessList", () => {
it("gates on the real connection profile", () => {
expect(connectionSupportsProcessList(conn({ db_type: "mysql" }))).toBe(true);
expect(connectionSupportsProcessList(conn({ db_type: "postgres" }))).toBe(true);
expect(connectionSupportsProcessList(conn({ db_type: "opengauss" }))).toBe(true);
expect(connectionSupportsProcessList(conn({ db_type: "gaussdb", driver_profile: "opengauss" }))).toBe(true);
expect(connectionSupportsProcessList(conn({ db_type: "gaussdb", driver_profile: "gaussdb" }))).toBe(false);
expect(connectionSupportsProcessList(conn({ db_type: "kingbase" }))).toBe(true);
expect(connectionSupportsProcessList(conn({ db_type: "sqlite" }))).toBe(false);
expect(connectionSupportsProcessList(undefined)).toBe(false);
});
@@ -7,13 +7,21 @@ import {
formatBytes,
formatBytesPerSec,
formatUptime,
isOpenGaussReplayRecordError,
isKingbaseStatusCatalogCompatibilityError,
isPgStatusCompatibilityError,
KINGBASE_PG_STATUS_SQL,
KINGBASE_STATUS_SQL,
KINGBASE_VARIABLES_SQL,
OPENGAUSS_STATUS_FALLBACK_SQL,
OPENGAUSS_STATUS_SQL,
OPENGAUSS_VARIABLES_SQL,
parsePgStatusRow,
pgCacheHitRatio,
PG_STATUS_LEGACY_SQL,
PG_STATUS_SQL,
resolveServerDashboardDriver,
statusNumber,
supportsServerDashboard,
type StatusSample,
} from "@/lib/database/postgresServerStatus";
@@ -132,6 +140,50 @@ describe("PG_STATUS_LEGACY_SQL", () => {
});
});
describe("PostgreSQL-family status drivers", () => {
it("uses openGauss's pg catalog with xlog location functions", () => {
const driver = resolveServerDashboardDriver("opengauss");
expect(driver?.statusSql).toBe(OPENGAUSS_STATUS_SQL);
expect(driver?.variablesSql).toBe(OPENGAUSS_VARIABLES_SQL);
expect(OPENGAUSS_STATUS_SQL).toContain("FROM pg_catalog.pg_stat_database");
expect(OPENGAUSS_STATUS_SQL).toContain("FROM pg_catalog.pg_stat_activity");
expect(OPENGAUSS_STATUS_SQL).toContain("pg_current_xlog_location()");
expect(OPENGAUSS_STATUS_SQL).toContain("(pg_last_xlog_replay_location()).lsn");
expect(OPENGAUSS_STATUS_SQL).not.toContain("pg_xlog_location_diff(pg_last_xlog_replay_location()");
expect(OPENGAUSS_STATUS_SQL).not.toContain("pg_current_wal_lsn()");
// The record-notation replay form has a scalar-text fallback for builds whose
// pg_last_xlog_replay_location() returns text rather than a (term, lsn) record.
expect(driver?.fallbackStatusSql).toBe(OPENGAUSS_STATUS_FALLBACK_SQL);
expect(driver?.shouldUseFallbackStatusSql?.(new Error('could not identify column "lsn" in record data type'))).toBe(true);
expect(OPENGAUSS_STATUS_FALLBACK_SQL).toContain("CAST(pg_last_xlog_replay_location() AS text)");
expect(OPENGAUSS_STATUS_FALLBACK_SQL).toContain("pg_current_xlog_location()");
expect(OPENGAUSS_STATUS_FALLBACK_SQL).not.toContain(".lsn");
});
it("uses KingbaseES's sys catalog and sys backend functions", () => {
const driver = resolveServerDashboardDriver("kingbase");
expect(driver?.statusSql).toBe(KINGBASE_STATUS_SQL);
expect(driver?.variablesSql).toBe(KINGBASE_VARIABLES_SQL);
expect(KINGBASE_STATUS_SQL).toContain("FROM sys_catalog.sys_stat_database");
expect(KINGBASE_STATUS_SQL).toContain("FROM sys_catalog.sys_stat_activity");
expect(KINGBASE_STATUS_SQL).toContain("sys_backend_pid()");
expect(KINGBASE_STATUS_SQL).toContain("sys_current_wal_lsn()");
expect(KINGBASE_STATUS_SQL).toContain("extract(epoch FROM CAST(CURRENT_TIMESTAMP AS TIMESTAMP))");
expect(KINGBASE_STATUS_SQL).toContain("extract(epoch FROM CAST(sys_postmaster_start_time() AS TIMESTAMP))");
expect(KINGBASE_STATUS_SQL).not.toContain("CURRENT_TIMESTAMP - sys_postmaster_start_time()");
});
it("wires each engine-specific status fallback", () => {
// PostgreSQL falls back to the pre-PG10 xlog-named query; openGauss falls back
// to the scalar-text replay form; Kingbase falls back from sys_catalog/sys_*
// to pg_catalog/pg_* when the server only exposes PostgreSQL-compatible names.
expect(resolveServerDashboardDriver("postgres")?.fallbackStatusSql).toBe(PG_STATUS_LEGACY_SQL);
expect(resolveServerDashboardDriver("opengauss")?.fallbackStatusSql).toBe(OPENGAUSS_STATUS_FALLBACK_SQL);
expect(resolveServerDashboardDriver("kingbase")?.fallbackStatusSql).toBe(KINGBASE_PG_STATUS_SQL);
expect(resolveServerDashboardDriver("mysql")).toBeNull();
});
});
describe("isPgStatusCompatibilityError", () => {
it("detects the WAL-function-not-found message on servers without a code field", () => {
expect(isPgStatusCompatibilityError(new Error("function pg_current_wal_lsn() does not exist"))).toBe(true);
@@ -157,17 +209,49 @@ describe("isPgStatusCompatibilityError", () => {
});
});
describe("supportsServerDashboard", () => {
it("is true for postgres only", () => {
expect(supportsServerDashboard("postgres")).toBe(true);
expect(supportsServerDashboard("mysql")).toBe(false);
expect(supportsServerDashboard("opengauss")).toBe(false);
expect(supportsServerDashboard("kingbase")).toBe(false);
expect(supportsServerDashboard(undefined)).toBe(false);
describe("isOpenGaussReplayRecordError", () => {
it("detects the .lsn-on-non-composite failure some openGauss builds raise", () => {
expect(isOpenGaussReplayRecordError(new Error('could not identify column "lsn" in record data type'))).toBe(true);
expect(isOpenGaussReplayRecordError(new Error("column notation .lsn applied to type text, which is not a composite type"))).toBe(true);
expect(isOpenGaussReplayRecordError(Object.assign(new Error("column notation .lsn applied to type text"), { code: "42809" }))).toBe(true);
});
it("does not misclassify unrelated errors", () => {
expect(isOpenGaussReplayRecordError(new Error("connection refused"))).toBe(false);
// Names an LSN function but is the pre-PG10 WAL-rename failure, not the record issue.
expect(isOpenGaussReplayRecordError(new Error("function pg_current_wal_lsn() does not exist"))).toBe(false);
});
});
describe("Kingbase catalog compatibility", () => {
it("keeps the sys_catalog query primary and provides a pg_catalog fallback", () => {
const driver = resolveServerDashboardDriver("kingbase");
expect(driver?.statusSql).toBe(KINGBASE_STATUS_SQL);
expect(driver?.fallbackStatusSql).toBe(KINGBASE_PG_STATUS_SQL);
expect(KINGBASE_STATUS_SQL).toContain("sys_catalog.sys_stat_database");
expect(KINGBASE_STATUS_SQL).toContain("sys_backend_pid()");
expect(KINGBASE_PG_STATUS_SQL).toContain("pg_catalog.pg_stat_database");
expect(KINGBASE_PG_STATUS_SQL).toContain("pg_backend_pid()");
expect(KINGBASE_PG_STATUS_SQL).not.toContain("sys_catalog");
expect(KINGBASE_PG_STATUS_SQL).not.toMatch(/\bsys_(?:backend|is|wal|last|current|postmaster)/);
});
it("falls back only for missing Kingbase sys catalog objects", () => {
expect(isKingbaseStatusCatalogCompatibilityError(Object.assign(new Error('relation "sys_catalog.sys_stat_database" does not exist'), { code: "42P01" }))).toBe(true);
expect(isKingbaseStatusCatalogCompatibilityError(new Error("function sys_current_wal_lsn() does not exist (SQLSTATE 42883)"))).toBe(true);
expect(isKingbaseStatusCatalogCompatibilityError(Object.assign(new Error("permission denied for relation sys_catalog.sys_stat_database"), { code: "42501" }))).toBe(false);
expect(isKingbaseStatusCatalogCompatibilityError(new Error("connection refused"))).toBe(false);
expect(isKingbaseStatusCatalogCompatibilityError(Object.assign(new Error('relation "other_table" does not exist'), { code: "42P01" }))).toBe(false);
});
});
describe("connectionSupportsServerDashboard", () => {
it("gates on the connection's effective db type", () => {
expect(connectionSupportsServerDashboard({ id: "pg", name: "Postgres", db_type: "postgres" } as any)).toBe(true);
expect(connectionSupportsServerDashboard({ id: "og", name: "openGauss", db_type: "opengauss" } as any)).toBe(true);
expect(connectionSupportsServerDashboard({ id: "og-import", name: "Imported openGauss", db_type: "gaussdb", driver_profile: "opengauss" } as any)).toBe(true);
expect(connectionSupportsServerDashboard({ id: "gauss", name: "GaussDB", db_type: "gaussdb", driver_profile: "gaussdb" } as any)).toBe(false);
expect(connectionSupportsServerDashboard({ id: "kb", name: "KingbaseES", db_type: "kingbase" } as any)).toBe(true);
expect(connectionSupportsServerDashboard({ id: "jdbc-pg", name: "JDBC Postgres", db_type: "jdbc", connection_string: "jdbc:postgresql://localhost/db" } as any)).toBe(true);
expect(connectionSupportsServerDashboard({ id: "mysql", name: "MySQL", db_type: "mysql" } as any)).toBe(false);
expect(connectionSupportsServerDashboard(undefined)).toBe(false);