Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions src/lib/db/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ export interface DatabaseStats {
cacheSize: number;
}

export function getDatabaseStats(): DatabaseStats {
const db = getDbInstance();

export function getDatabaseStats(db: SqliteAdapter = getDbInstance()): DatabaseStats {
const pageSize = db.pragma("page_size", { simple: true }) as number;
const pageCount = db.pragma("page_count", { simple: true }) as number;
const cacheSize = db.pragma("cache_size", { simple: true }) as number;
Expand All @@ -39,17 +37,26 @@ export function getDatabaseStats(): DatabaseStats {
.all() as Array<{ name: string }>;

const tableStats = tables.map((table) => {
const rowCount = db.prepare(`SELECT COUNT(*) as count FROM ${table.name}`).get() as {
count: number;
};
let rowCount = 0;
try {
const quotedName = `"${table.name.replaceAll('"', '""')}"`;
const row = db.prepare(`SELECT COUNT(*) as count FROM ${quotedName}`).get() as
{ count: number } | undefined;
rowCount = row?.count ?? 0;
} catch (error) {
if (!(error instanceof Error) || !error.message.startsWith("no such module:")) {
throw error;
}
// Optional virtual-table modules may be unavailable on this connection.
}

const tableSize = db
.prepare(`SELECT SUM(pgsize) as size FROM dbstat WHERE name = ?`)
.get(table.name) as { size: number | null };

return {
name: table.name,
rowCount: rowCount.count,
rowCount,
size: tableSize?.size || 0,
};
});
Expand Down
68 changes: 68 additions & 0 deletions tests/unit/db/stats-virtual-table.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import assert from "node:assert/strict";
import test from "node:test";

import type { SqliteAdapter } from "../../../src/lib/db/adapters/types.ts";
import { getDatabaseStats } from "../../../src/lib/db/stats.ts";

function createAdapter(
virtualTableError = "no such module: vec0",
regularRow: { count: number } | undefined = { count: 3 },
returnUndefinedRegularRow = false
): SqliteAdapter {
return {
driver: "better-sqlite3",
open: true,
name: ":memory:",
raw: {},
pragma(name: string) {
return name === "page_size" ? 4096 : name === "page_count" ? 2 : -2000;
},
prepare(sql: string) {
if (sql.includes("FROM sqlite_master WHERE type='table'")) {
return { all: () => [{ name: "regular" }, { name: "vec_memories" }] } as never;
}
if (sql.includes('COUNT(*) as count FROM "regular"')) {
return { get: () => (returnUndefinedRegularRow ? undefined : regularRow) } as never;
}
if (sql.includes('COUNT(*) as count FROM "vec_memories"')) {
throw new Error(virtualTableError);
}
if (sql.includes("FROM dbstat")) {
return { get: () => ({ size: 1024 }) } as never;
}
if (sql.includes("FROM sqlite_master WHERE type='index'")) {
return { all: () => [] } as never;
}
throw new Error(`Unexpected SQL: ${sql}`);
},
exec() {},
transaction: (fn) => fn,
immediate(fn) {
fn();
},
async backup() {},
checkpoint() {},
close() {},
};
}

test("database stats tolerate virtual tables whose module is unavailable", () => {
const stats = getDatabaseStats(createAdapter());

assert.deepEqual(stats.tables, [
{ name: "regular", rowCount: 3, size: 1024 },
{ name: "vec_memories", rowCount: 0, size: 1024 },
]);
});

test("database stats do not mask unrelated table errors", () => {
assert.throws(() => getDatabaseStats(createAdapter("database disk image is malformed")), {
message: "database disk image is malformed",
});
});

test("database stats tolerate an undefined COUNT result", () => {
const stats = getDatabaseStats(createAdapter("no such module: vec0", { count: 3 }, true));

assert.equal(stats.tables[0]?.rowCount, 0);
});
Loading