feat(xugu): add read-only server monitoring dashboard
This commit is contained in:
9 files changed
+1153
-1
No files matched your search
@@ -0,0 +1,108 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
XUGU_ACTIVE_SESSION_SQL,
|
||||
XUGU_CLUSTER_NODES_SQL,
|
||||
XUGU_LOCK_MODE_SUMMARY_SQL,
|
||||
XUGU_LOCK_WAITS_SQL,
|
||||
XUGU_MEMORY_STATUS_SQL,
|
||||
XUGU_RUN_INFO_SQL,
|
||||
XUGU_SESSION_SUMMARY_SQL,
|
||||
XUGU_SESSION_STATUS_SUMMARY_SQL,
|
||||
XUGU_TABLESPACE_SUMMARY_SQL,
|
||||
XUGU_TRANSACTION_SUMMARY_SQL,
|
||||
XUGU_VERSION_SQL,
|
||||
connectionSupportsXuguServerDashboard,
|
||||
xuguClusterNodeStateLabel,
|
||||
xuguClusterNodeTypeLabels,
|
||||
xuguClusterNodesFromResult,
|
||||
xuguLockModeSummaryFromResult,
|
||||
xuguMemoryStatusFromResult,
|
||||
xuguRunInfoFromResult,
|
||||
xuguSessionSummaryFromResult,
|
||||
xuguSessionStatusSummaryFromResult,
|
||||
xuguTablespaceSummaryFromResult,
|
||||
xuguTransactionSummaryFromResult,
|
||||
xuguVersionFromResult,
|
||||
} from "@/lib/database/xuguServerStatus";
|
||||
|
||||
describe("Xugu server status helpers", () => {
|
||||
it("keeps the first-phase catalog queries scoped to Xugu system views", () => {
|
||||
expect(XUGU_VERSION_SQL).toContain("VERSION()");
|
||||
expect(XUGU_CLUSTER_NODES_SQL).toContain("FROM SYS_CLUSTERS");
|
||||
expect(XUGU_RUN_INFO_SQL).toContain("FROM SYS_ALL_RUN_INFO");
|
||||
expect(XUGU_SESSION_SUMMARY_SQL).toContain("FROM SYS_ALL_SESSIONS");
|
||||
expect(XUGU_ACTIVE_SESSION_SQL).toContain("FROM SYS_ALL_THD_SESSION");
|
||||
expect(XUGU_ACTIVE_SESSION_SQL).toContain("JOIN SYS_ALL_SESSIONS");
|
||||
expect(XUGU_ACTIVE_SESSION_SQL).not.toMatch(/WHERE\s+STATE\s*=\s*1/i);
|
||||
expect(XUGU_TRANSACTION_SUMMARY_SQL).toContain("FROM SYS_ALL_TRANS");
|
||||
expect(XUGU_LOCK_WAITS_SQL).toContain("SYS_ALL_LWAITERS");
|
||||
expect(XUGU_LOCK_MODE_SUMMARY_SQL).toContain("SYS_ALL_LOWNERS");
|
||||
expect(XUGU_SESSION_STATUS_SUMMARY_SQL).toContain("FROM SYS_ALL_SESSIONS");
|
||||
expect(XUGU_MEMORY_STATUS_SQL).toContain("SYS_ALL_MEM_STATUS");
|
||||
expect(XUGU_TABLESPACE_SUMMARY_SQL).toContain("SYS_ALL_TABLESPACES");
|
||||
});
|
||||
|
||||
it("recognizes native and JDBC Xugu connections only", () => {
|
||||
expect(connectionSupportsXuguServerDashboard({ id: "x", name: "Xugu", db_type: "xugu" } as any)).toBe(true);
|
||||
expect(connectionSupportsXuguServerDashboard({ id: "pg", name: "Postgres", db_type: "postgres" } as any)).toBe(false);
|
||||
expect(connectionSupportsXuguServerDashboard(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it("maps documented node states and roles", () => {
|
||||
expect(xuguClusterNodeStateLabel("2")).toBe("running");
|
||||
expect(xuguClusterNodeStateLabel("4")).toBe("offline");
|
||||
expect(xuguClusterNodeTypeLabels("1")).toEqual(["master"]);
|
||||
expect(xuguClusterNodeTypeLabels("29")).toEqual(["master", "storage", "query", "worker"]);
|
||||
expect(xuguClusterNodeTypeLabels("0")).toEqual([]);
|
||||
expect(xuguClusterNodeStateLabel("999")).toBe("unknown");
|
||||
});
|
||||
|
||||
it("parses status rows by column name instead of column order", () => {
|
||||
const nodes = xuguClusterNodesFromResult({
|
||||
columns: ["NODE_IP", "NODE_ID", "NODE_STATE", "NODE_TYPE", "NODE_PORT", "RACK_NO", "CPU_LOAD", "BOOT_TIME", "STORE_NUM", "MAJOR_NUM"],
|
||||
rows: [["127.0.0.1", 1, 2, 1, 5138, 0, 4, "2026-01-01", 10, 8]],
|
||||
});
|
||||
const runInfo = xuguRunInfoFromResult({
|
||||
columns: ["FREE_STO_N", "NODEID", "ACT_TRANS_NUM", "DISK_R_N", "DISK_R_BYTES", "DISK_W_N", "DISK_W_BYTES", "MAX_TRANS_ID", "MIN_TRANS_ID", "XLOG_WPOS", "XLOG_CKPT"],
|
||||
rows: [[11, 1, 2, 3, 4, 5, 6, 100, 99, 1000, 900]],
|
||||
});
|
||||
const sessions = xuguSessionSummaryFromResult({
|
||||
columns: ["MEMORY_BYTES", "SESSIONS", "NODE_ID", "ACTIVE_SESSIONS", "OLDEST_STATEMENT"],
|
||||
rows: [[1024, 4, 1, 2, "2026-01-01 00:00:00"]],
|
||||
});
|
||||
const transactions = xuguTransactionSummaryFromResult({
|
||||
columns: ["ACTIVE_TRANSACTIONS", "NODE_ID", "OLDEST_TRANSACTION"],
|
||||
rows: [[3, 1, "2026-01-01 00:00:00"]],
|
||||
});
|
||||
const memory = xuguMemoryStatusFromResult({
|
||||
columns: ["SGA_FREE_BYTES", "NODEID", "SGA_TOTAL_BYTES", "BUFFER_FREE_BYTES", "BUFFER_TOTAL_BYTES"],
|
||||
rows: [[20, 1, 100, 40, 200]],
|
||||
});
|
||||
const sessionStatuses = xuguSessionStatusSummaryFromResult({
|
||||
columns: ["SESSIONS", "STATUS", "NODE_ID"],
|
||||
rows: [[3, 112, 1]],
|
||||
});
|
||||
const lockModes = xuguLockModeSummaryFromResult({
|
||||
columns: ["LOCKS", "LOCK_MODE", "NODE_ID"],
|
||||
rows: [[3, "IS", 1]],
|
||||
});
|
||||
const tablespaces = xuguTablespaceSummaryFromResult({
|
||||
columns: ["FREE_BYTES", "TOTAL_BYTES", "MEDIA_ERROR", "DATAFILES", "SPACE_TYPE", "SPACE_NAME", "NODE_ID"],
|
||||
rows: [[10, 100, false, 1, "DATA_SPACE", "DATA1", 1]],
|
||||
});
|
||||
|
||||
expect(nodes[0]).toMatchObject({ nodeId: "1", host: "127.0.0.1", state: "2", majorCount: "8" });
|
||||
expect(runInfo[0]).toMatchObject({ nodeId: "1", activeTransactions: "2", diskReadCount: "3", freeStores: "11", xlogWritePosition: "1000" });
|
||||
expect(sessions[0]).toMatchObject({ nodeId: "1", sessions: "4", activeSessions: "2", memoryBytes: "1024", oldestStatement: "2026-01-01 00:00:00" });
|
||||
expect(transactions[0]).toMatchObject({ nodeId: "1", activeTransactions: "3", oldestTransaction: "2026-01-01 00:00:00" });
|
||||
expect(memory[0]).toMatchObject({ nodeId: "1", sgaTotalBytes: "100", sgaFreeBytes: "20", bufferTotalBytes: "200" });
|
||||
expect(sessionStatuses[0]).toMatchObject({ nodeId: "1", status: "112", sessions: "3" });
|
||||
expect(lockModes[0]).toMatchObject({ nodeId: "1", lockMode: "IS", locks: "3" });
|
||||
expect(tablespaces[0]).toMatchObject({ nodeId: "1", spaceName: "DATA1", spaceType: "DATA_SPACE", totalBytes: "100" });
|
||||
});
|
||||
|
||||
it("returns an empty value for missing or null values", () => {
|
||||
expect(xuguVersionFromResult({ columns: ["VERSION"], rows: [[null]] })).toBe("");
|
||||
expect(xuguVersionFromResult({ columns: ["OTHER"], rows: [["x"]] })).toBe("");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,329 @@
|
||||
import type { ConnectionConfig, QueryResult } from "@/types/database";
|
||||
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
|
||||
export interface XuguClusterNode {
|
||||
nodeId: string;
|
||||
rackNo: string;
|
||||
host: string;
|
||||
port: string;
|
||||
nodeType: string;
|
||||
state: string;
|
||||
cpuLoad: string;
|
||||
bootTime: string;
|
||||
storeCount: string;
|
||||
majorCount: string;
|
||||
}
|
||||
|
||||
export interface XuguRunInfo {
|
||||
nodeId: string;
|
||||
activeTransactions: string;
|
||||
diskReadCount: string;
|
||||
diskReadBytes: string;
|
||||
diskWriteCount: string;
|
||||
diskWriteBytes: string;
|
||||
maxTransactionId: string;
|
||||
minTransactionId: string;
|
||||
xlogWritePosition: string;
|
||||
xlogCheckpointPosition: string;
|
||||
freeStores: string;
|
||||
}
|
||||
|
||||
export interface XuguSessionSummary {
|
||||
nodeId: string;
|
||||
sessions: string;
|
||||
activeSessions: string;
|
||||
memoryBytes: string;
|
||||
oldestStatement: string;
|
||||
}
|
||||
|
||||
export interface XuguTransactionSummary {
|
||||
nodeId: string;
|
||||
activeTransactions: string;
|
||||
oldestTransaction: string;
|
||||
}
|
||||
|
||||
export interface XuguMemoryStatus {
|
||||
nodeId: string;
|
||||
bufferTotalBytes: string;
|
||||
bufferFreeBytes: string;
|
||||
bufferDirtyBytes: string;
|
||||
bufferLruBytes: string;
|
||||
sgaTotalBytes: string;
|
||||
sgaFreeBytes: string;
|
||||
sgaPeakBytes: string;
|
||||
swapTotalBytes: string;
|
||||
swapFreeBytes: string;
|
||||
}
|
||||
|
||||
export interface XuguSessionStatusSummary {
|
||||
nodeId: string;
|
||||
status: string;
|
||||
sessions: string;
|
||||
}
|
||||
|
||||
export interface XuguLockModeSummary {
|
||||
nodeId: string;
|
||||
lockMode: string;
|
||||
locks: string;
|
||||
}
|
||||
|
||||
export interface XuguTablespaceSummary {
|
||||
nodeId: string;
|
||||
spaceName: string;
|
||||
spaceType: string;
|
||||
datafiles: string;
|
||||
mediaError: string;
|
||||
totalBytes: string;
|
||||
freeBytes: string;
|
||||
}
|
||||
|
||||
export const XUGU_VERSION_SQL = "SELECT VERSION() AS VERSION FROM DUAL;";
|
||||
|
||||
export const XUGU_CLUSTER_NODES_SQL = `
|
||||
SELECT
|
||||
NODE_ID,
|
||||
RACK_NO,
|
||||
NODE_IP,
|
||||
NODE_PORT,
|
||||
NODE_TYPE,
|
||||
NODE_STATE,
|
||||
CPU_LOAD,
|
||||
BOOT_TIME,
|
||||
STORE_NUM,
|
||||
MAJOR_NUM
|
||||
FROM SYS_CLUSTERS
|
||||
ORDER BY NODE_ID;`.trim();
|
||||
|
||||
export const XUGU_RUN_INFO_SQL = `
|
||||
SELECT
|
||||
NODEID,
|
||||
ACT_TRANS_NUM,
|
||||
DISK_R_N,
|
||||
DISK_R_BYTES,
|
||||
DISK_W_N,
|
||||
DISK_W_BYTES,
|
||||
MAX_TRANS_ID,
|
||||
MIN_TRANS_ID,
|
||||
XLOG_WPOS,
|
||||
XLOG_CKPT,
|
||||
FREE_STO_N
|
||||
FROM SYS_ALL_RUN_INFO
|
||||
ORDER BY NODEID;`.trim();
|
||||
|
||||
/** Per-node session totals. This is optional because restricted users may not see SYS_ALL_SESSIONS. */
|
||||
export const XUGU_SESSION_SUMMARY_SQL = `
|
||||
SELECT NODEID AS NODE_ID,
|
||||
COUNT(*) AS SESSIONS,
|
||||
SUM(MEM_SIZE) AS MEMORY_BYTES
|
||||
FROM SYS_ALL_SESSIONS
|
||||
GROUP BY NODEID
|
||||
ORDER BY NODEID;`.trim();
|
||||
|
||||
/** The thread-session view is the Xugu-specific definition of an active statement. */
|
||||
export const XUGU_ACTIVE_SESSION_SQL = `
|
||||
SELECT T.NODEID AS NODE_ID,
|
||||
COUNT(*) AS ACTIVE_SESSIONS,
|
||||
MIN(S.CMD_START_T) AS OLDEST_STATEMENT
|
||||
FROM SYS_ALL_THD_SESSION T
|
||||
JOIN SYS_ALL_SESSIONS S
|
||||
ON T.NODEID = S.NODEID AND T.SESSION_ID = S.SESSION_ID
|
||||
GROUP BY T.NODEID
|
||||
ORDER BY T.NODEID;`.trim();
|
||||
|
||||
export const XUGU_TRANSACTION_SUMMARY_SQL = `
|
||||
SELECT NODEID AS NODE_ID,
|
||||
COUNT(*) AS ACTIVE_TRANSACTIONS,
|
||||
MIN(START_T) AS OLDEST_TRANSACTION
|
||||
FROM SYS_ALL_TRANS
|
||||
GROUP BY NODEID
|
||||
ORDER BY NODEID;`.trim();
|
||||
|
||||
/** Only table-level lock waiters are exposed by SYS_ALL_LWAITERS. */
|
||||
export const XUGU_LOCK_WAITS_SQL = `
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM SYS_ALL_LWAITERS) AS LOCK_WAITS,
|
||||
(SELECT COUNT(*) FROM SYS_ALL_LOWNERS) AS LOCK_OWNERS
|
||||
FROM DUAL;`.trim();
|
||||
|
||||
export const XUGU_LOCK_MODE_SUMMARY_SQL = `
|
||||
SELECT NODEID AS NODE_ID,
|
||||
LOCK_LEVEL AS LOCK_MODE,
|
||||
COUNT(*) AS LOCKS
|
||||
FROM SYS_ALL_LOWNERS
|
||||
GROUP BY NODEID, LOCK_LEVEL
|
||||
ORDER BY NODEID, LOCK_LEVEL;`.trim();
|
||||
|
||||
export const XUGU_SESSION_STATUS_SUMMARY_SQL = `
|
||||
SELECT NODEID AS NODE_ID,
|
||||
STATUS,
|
||||
COUNT(*) AS SESSIONS
|
||||
FROM SYS_ALL_SESSIONS
|
||||
GROUP BY NODEID, STATUS
|
||||
ORDER BY NODEID, STATUS;`.trim();
|
||||
|
||||
export const XUGU_MEMORY_STATUS_SQL = `
|
||||
SELECT
|
||||
NODEID,
|
||||
BUFF_SIZE * TOTAL_BUFF_NUM AS BUFFER_TOTAL_BYTES,
|
||||
BUFF_SIZE * FREE_BUFF_NUM AS BUFFER_FREE_BYTES,
|
||||
BUFF_SIZE * DIRTY_BUFF_NUM AS BUFFER_DIRTY_BYTES,
|
||||
BUFF_SIZE * LRU_BUFF_NUM AS BUFFER_LRU_BYTES,
|
||||
SGA_BLK_SIZE * TOTAL_SGA_MEM AS SGA_TOTAL_BYTES,
|
||||
SGA_BLK_SIZE * FREE_SGA_MEM AS SGA_FREE_BYTES,
|
||||
SGA_BLK_SIZE * PEAK_SGA_MEM AS SGA_PEAK_BYTES,
|
||||
SWAP_BLK_SIZE * TOTAL_SWAP_MEM AS SWAP_TOTAL_BYTES,
|
||||
SWAP_BLK_SIZE * FREE_SWAP_MEM AS SWAP_FREE_BYTES
|
||||
FROM SYS_ALL_MEM_STATUS
|
||||
ORDER BY NODEID;`.trim();
|
||||
|
||||
export const XUGU_TABLESPACE_SUMMARY_SQL = `
|
||||
SELECT
|
||||
T.NODEID AS NODE_ID,
|
||||
T.SPACE_NAME,
|
||||
T.SPACE_TYPE,
|
||||
T.DATAFILE_NUM AS DATAFILES,
|
||||
T.MEDIA_ERROR,
|
||||
T.TOTAL_CHUNK_NUM * C.CHUNK_SIZE AS TOTAL_BYTES,
|
||||
T.FREE_CHUNK_NUM * C.CHUNK_SIZE AS FREE_BYTES
|
||||
FROM SYS_ALL_TABLESPACES T, SYS_CTL_VARS C
|
||||
ORDER BY T.NODEID, T.SPACE_NAME;`.trim();
|
||||
|
||||
export function connectionSupportsXuguServerDashboard(connection: ConnectionConfig | undefined): boolean {
|
||||
return !!connection && effectiveDatabaseTypeForConnection(connection) === "xugu";
|
||||
}
|
||||
|
||||
export function xuguClusterNodeStateLabel(state: string): "joining" | "running" | "error" | "offline" | "unknown" {
|
||||
switch (Number(state)) {
|
||||
case 1:
|
||||
return "joining";
|
||||
case 2:
|
||||
return "running";
|
||||
case 3:
|
||||
return "error";
|
||||
case 4:
|
||||
return "offline";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
export type XuguClusterNodeRole = "master" | "standby" | "storage" | "query" | "worker" | "change";
|
||||
|
||||
export function xuguClusterNodeTypeLabels(type: string): XuguClusterNodeRole[] {
|
||||
const value = Number(type);
|
||||
if (!Number.isFinite(value) || value <= 0) return [];
|
||||
const roles: Array<[number, XuguClusterNodeRole]> = [
|
||||
[1, "master"],
|
||||
[2, "standby"],
|
||||
[4, "storage"],
|
||||
[8, "query"],
|
||||
[16, "worker"],
|
||||
[32, "change"],
|
||||
];
|
||||
return roles.filter(([bit]) => (value & bit) === bit).map(([, role]) => role);
|
||||
}
|
||||
|
||||
export function xuguClusterNodesFromResult(result: QueryResult): XuguClusterNode[] {
|
||||
return result.rows.map((row) => ({
|
||||
nodeId: valueAt(result, row, "NODE_ID"),
|
||||
rackNo: valueAt(result, row, "RACK_NO"),
|
||||
host: valueAt(result, row, "NODE_IP"),
|
||||
port: valueAt(result, row, "NODE_PORT"),
|
||||
nodeType: valueAt(result, row, "NODE_TYPE"),
|
||||
state: valueAt(result, row, "NODE_STATE"),
|
||||
cpuLoad: valueAt(result, row, "CPU_LOAD"),
|
||||
bootTime: valueAt(result, row, "BOOT_TIME"),
|
||||
storeCount: valueAt(result, row, "STORE_NUM"),
|
||||
majorCount: valueAt(result, row, "MAJOR_NUM"),
|
||||
}));
|
||||
}
|
||||
|
||||
export function xuguRunInfoFromResult(result: QueryResult): XuguRunInfo[] {
|
||||
return result.rows.map((row) => ({
|
||||
nodeId: valueAt(result, row, "NODEID"),
|
||||
activeTransactions: valueAt(result, row, "ACT_TRANS_NUM"),
|
||||
diskReadCount: valueAt(result, row, "DISK_R_N"),
|
||||
diskReadBytes: valueAt(result, row, "DISK_R_BYTES"),
|
||||
diskWriteCount: valueAt(result, row, "DISK_W_N"),
|
||||
diskWriteBytes: valueAt(result, row, "DISK_W_BYTES"),
|
||||
maxTransactionId: valueAt(result, row, "MAX_TRANS_ID"),
|
||||
minTransactionId: valueAt(result, row, "MIN_TRANS_ID"),
|
||||
xlogWritePosition: valueAt(result, row, "XLOG_WPOS"),
|
||||
xlogCheckpointPosition: valueAt(result, row, "XLOG_CKPT"),
|
||||
freeStores: valueAt(result, row, "FREE_STO_N"),
|
||||
}));
|
||||
}
|
||||
|
||||
export function xuguSessionSummaryFromResult(result: QueryResult): XuguSessionSummary[] {
|
||||
return result.rows.map((row) => ({
|
||||
nodeId: valueAt(result, row, "NODE_ID"),
|
||||
sessions: valueAt(result, row, "SESSIONS"),
|
||||
activeSessions: valueAt(result, row, "ACTIVE_SESSIONS"),
|
||||
memoryBytes: valueAt(result, row, "MEMORY_BYTES"),
|
||||
oldestStatement: valueAt(result, row, "OLDEST_STATEMENT"),
|
||||
}));
|
||||
}
|
||||
|
||||
export function xuguTransactionSummaryFromResult(result: QueryResult): XuguTransactionSummary[] {
|
||||
return result.rows.map((row) => ({
|
||||
nodeId: valueAt(result, row, "NODE_ID"),
|
||||
activeTransactions: valueAt(result, row, "ACTIVE_TRANSACTIONS"),
|
||||
oldestTransaction: valueAt(result, row, "OLDEST_TRANSACTION"),
|
||||
}));
|
||||
}
|
||||
|
||||
export function xuguMemoryStatusFromResult(result: QueryResult): XuguMemoryStatus[] {
|
||||
return result.rows.map((row) => ({
|
||||
nodeId: valueAt(result, row, "NODEID"),
|
||||
bufferTotalBytes: valueAt(result, row, "BUFFER_TOTAL_BYTES"),
|
||||
bufferFreeBytes: valueAt(result, row, "BUFFER_FREE_BYTES"),
|
||||
bufferDirtyBytes: valueAt(result, row, "BUFFER_DIRTY_BYTES"),
|
||||
bufferLruBytes: valueAt(result, row, "BUFFER_LRU_BYTES"),
|
||||
sgaTotalBytes: valueAt(result, row, "SGA_TOTAL_BYTES"),
|
||||
sgaFreeBytes: valueAt(result, row, "SGA_FREE_BYTES"),
|
||||
sgaPeakBytes: valueAt(result, row, "SGA_PEAK_BYTES"),
|
||||
swapTotalBytes: valueAt(result, row, "SWAP_TOTAL_BYTES"),
|
||||
swapFreeBytes: valueAt(result, row, "SWAP_FREE_BYTES"),
|
||||
}));
|
||||
}
|
||||
|
||||
export function xuguSessionStatusSummaryFromResult(result: QueryResult): XuguSessionStatusSummary[] {
|
||||
return result.rows.map((row) => ({
|
||||
nodeId: valueAt(result, row, "NODE_ID"),
|
||||
status: valueAt(result, row, "STATUS"),
|
||||
sessions: valueAt(result, row, "SESSIONS"),
|
||||
}));
|
||||
}
|
||||
|
||||
export function xuguLockModeSummaryFromResult(result: QueryResult): XuguLockModeSummary[] {
|
||||
return result.rows.map((row) => ({
|
||||
nodeId: valueAt(result, row, "NODE_ID"),
|
||||
lockMode: valueAt(result, row, "LOCK_MODE"),
|
||||
locks: valueAt(result, row, "LOCKS"),
|
||||
}));
|
||||
}
|
||||
|
||||
export function xuguTablespaceSummaryFromResult(result: QueryResult): XuguTablespaceSummary[] {
|
||||
return result.rows.map((row) => ({
|
||||
nodeId: valueAt(result, row, "NODE_ID"),
|
||||
spaceName: valueAt(result, row, "SPACE_NAME"),
|
||||
spaceType: valueAt(result, row, "SPACE_TYPE"),
|
||||
datafiles: valueAt(result, row, "DATAFILES"),
|
||||
mediaError: valueAt(result, row, "MEDIA_ERROR"),
|
||||
totalBytes: valueAt(result, row, "TOTAL_BYTES"),
|
||||
freeBytes: valueAt(result, row, "FREE_BYTES"),
|
||||
}));
|
||||
}
|
||||
|
||||
export function xuguScalarFromResult(result: QueryResult, column: string): string {
|
||||
return result.rows.length > 0 ? valueAt(result, result.rows[0], column) : "";
|
||||
}
|
||||
|
||||
export function xuguVersionFromResult(result: QueryResult): string {
|
||||
return xuguScalarFromResult(result, "VERSION");
|
||||
}
|
||||
|
||||
function valueAt(result: QueryResult, row: unknown[], column: string): string {
|
||||
const index = result.columns.findIndex((candidate) => candidate.toUpperCase() === column.toUpperCase());
|
||||
return index < 0 || row[index] == null ? "" : String(row[index]);
|
||||
}
|
||||
Reference in new issue
Block a user