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
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user