Files
dbx/apps/desktop/src/lib/__tests__/database/postgresServerStatus.spec.ts
T

260 lines
13 KiB
TypeScript
Vendored

import { describe, expect, it } from "vitest";
import type { QueryResult } from "@/types/database";
import {
computePgTps,
computeRate,
connectionSupportsServerDashboard,
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,
type StatusSample,
} from "@/lib/database/postgresServerStatus";
function statusResult(columns: string[], row: (string | number)[]): QueryResult {
return { columns, rows: [row], affected_rows: 0, execution_time_ms: 0 };
}
function sample(at: number, status: Record<string, string>): StatusSample {
return { at, status };
}
describe("parsePgStatusRow", () => {
it("parses the single aggregate row into a map", () => {
const map = parsePgStatusRow(statusResult(["xact_commit", "xact_rollback"], ["1200", "3"]));
expect(map).toEqual({ xact_commit: "1200", xact_rollback: "3" });
});
it("returns empty map for malformed input", () => {
expect(parsePgStatusRow(null)).toEqual({});
expect(parsePgStatusRow({ columns: [], rows: [], affected_rows: 0, execution_time_ms: 0 })).toEqual({});
});
});
describe("statusNumber", () => {
it("reads numeric values and defaults to 0", () => {
expect(statusNumber({ connections: "12" }, "connections")).toBe(12);
expect(statusNumber({}, "missing")).toBe(0);
expect(statusNumber({ x: "abc" }, "x")).toBe(0);
});
});
describe("computeRate", () => {
it("computes per-second delta", () => {
const prev = sample(1000, { tup_inserted: "1000" });
const curr = sample(3000, { tup_inserted: "5000" });
expect(computeRate(prev, curr, "tup_inserted")).toBe(2000); // 4000 rows / 2s
});
it("returns 0 on counter reset (decrease)", () => {
const prev = sample(1000, { xact_commit: "9000" });
const curr = sample(2000, { xact_commit: "10" });
expect(computeRate(prev, curr, "xact_commit")).toBe(0);
});
it("returns 0 for non-positive time delta", () => {
const prev = sample(2000, { xact_commit: "10" });
const curr = sample(2000, { xact_commit: "20" });
expect(computeRate(prev, curr, "xact_commit")).toBe(0);
});
});
describe("computePgTps", () => {
it("sums commit and rollback rates", () => {
const prev = sample(0, { xact_commit: "0", xact_rollback: "0" });
const curr = sample(1000, { xact_commit: "90", xact_rollback: "10" });
expect(computePgTps(prev, curr)).toBe(100);
});
});
describe("pgCacheHitRatio", () => {
it("computes hit ratio as a percentage", () => {
expect(pgCacheHitRatio({ blks_hit: "997", blks_read: "3" })).toBeCloseTo(99.7, 1);
});
it("returns null when no data has accumulated", () => {
expect(pgCacheHitRatio({})).toBeNull();
expect(pgCacheHitRatio({ blks_hit: "0", blks_read: "0" })).toBeNull();
});
});
describe("formatters", () => {
it("formats bytes and bytes/sec", () => {
expect(formatBytes(0)).toBe("0 B");
expect(formatBytes(1024)).toBe("1.0 KB");
expect(formatBytes(1024 * 1024 * 2)).toBe("2.0 MB");
expect(formatBytesPerSec(1024)).toBe("1.0 KB/s");
});
it("formats uptime compactly", () => {
expect(formatUptime(0)).toBe("0s");
expect(formatUptime(45)).toBe("45s");
expect(formatUptime(3661)).toBe("1h 1m");
expect(formatUptime(90061)).toBe("1d 1h 1m");
});
});
describe("PG_STATUS_SQL", () => {
it("does not use FILTER (PG9.4+ only) — this dashboard targets PG9.2+", () => {
expect(PG_STATUS_SQL).not.toContain("FILTER");
expect(PG_STATUS_SQL).toContain("sum(CASE WHEN state IS NOT NULL THEN 1 ELSE 0 END)");
expect(PG_STATUS_SQL).not.toContain("count(*) AS connections");
expect(PG_STATUS_SQL).toContain("sum(CASE WHEN state = 'active' THEN 1 ELSE 0 END)");
expect(PG_STATUS_SQL).toContain("sum(CASE WHEN state = 'idle' THEN 1 ELSE 0 END)");
});
it("is recovery-aware for the WAL metric, never calling pg_current_wal_lsn() unconditionally", () => {
expect(PG_STATUS_SQL).toContain("CASE WHEN pg_is_in_recovery()");
expect(PG_STATUS_SQL).toContain("pg_last_wal_replay_lsn()");
expect(PG_STATUS_SQL).toContain("pg_current_wal_lsn()");
});
});
describe("PG_STATUS_LEGACY_SQL", () => {
it("swaps only the PG10+ WAL functions for their pre-10 equivalents", () => {
expect(PG_STATUS_LEGACY_SQL).not.toContain("pg_current_wal_lsn");
expect(PG_STATUS_LEGACY_SQL).not.toContain("pg_last_wal_replay_lsn");
expect(PG_STATUS_LEGACY_SQL).not.toContain("pg_wal_lsn_diff");
expect(PG_STATUS_LEGACY_SQL).toContain("pg_current_xlog_location");
expect(PG_STATUS_LEGACY_SQL).toContain("pg_last_xlog_replay_location");
expect(PG_STATUS_LEGACY_SQL).toContain("pg_xlog_location_diff");
// Everything else stays byte-for-byte identical to the primary query.
const revertedToPrimary = PG_STATUS_LEGACY_SQL.replace(/\bpg_current_xlog_location\b/g, "pg_current_wal_lsn")
.replace(/\bpg_last_xlog_replay_location\b/g, "pg_last_wal_replay_lsn")
.replace(/\bpg_xlog_location_diff\b/g, "pg_wal_lsn_diff");
expect(revertedToPrimary).toBe(PG_STATUS_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);
expect(isPgStatusCompatibilityError(new Error("function pg_last_wal_replay_lsn() does not exist"))).toBe(true);
expect(isPgStatusCompatibilityError(new Error("function pg_wal_lsn_diff(pg_lsn, unknown) does not exist"))).toBe(true);
});
it("detects the WAL-function-not-found SQLSTATE combined with a matching message", () => {
expect(isPgStatusCompatibilityError(Object.assign(new Error("function pg_current_wal_lsn() does not exist"), { code: "42883" }))).toBe(true);
});
it("does not misclassify unrelated errors", () => {
expect(isPgStatusCompatibilityError(new Error("connection refused"))).toBe(false);
expect(isPgStatusCompatibilityError({ code: "42703" })).toBe(false);
});
it("does not treat every SQLSTATE 42883 as the WAL compatibility issue — only the two specific functions", () => {
// Bare code with no message can't confirm which function is missing.
expect(isPgStatusCompatibilityError({ code: "42883" })).toBe(false);
// A different undefined function under the same SQLSTATE must not trigger the WAL fallback.
expect(isPgStatusCompatibilityError(Object.assign(new Error("function pg_postmaster_start_time() does not exist"), { code: "42883" }))).toBe(false);
expect(isPgStatusCompatibilityError(new Error("function some_other_fn() does not exist"))).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);
});
});