import type { Db, ExecResult, Row } from "./driver.ts"; import type { Model } from "./schema.ts"; export interface QueryRecord { sql: string; paramsCount: number; durationMs: number; rowCount?: number; operation: "all" | "one" | "exec"; duplicateCount: number; } export interface QueryIssue { code: string; severity: "error" | "warning" | "info"; message: string; sql: string; } export interface QueryPolicy { slowQueryMs?: number; timeoutMs?: number; maxRows?: number; duplicateWarningCount?: number; warnSelectStar?: boolean; warnUnboundedSelect?: boolean; onQuery?: (record: QueryRecord) => void | Promise; onIssue?: (issue: QueryIssue) => void | Promise; } const recentQueries: QueryRecord[] = []; const recentQueryIssues: QueryIssue[] = []; const TELEMETRY_LIMIT = 200; export function getDbPerformanceSnapshot(): { queries: QueryRecord[]; issues: QueryIssue[] } { return { queries: structuredClone(recentQueries), issues: structuredClone(recentQueryIssues) }; } export function resetDbPerformanceSnapshot(): void { recentQueries.length = 0; recentQueryIssues.length = 0; } function remember(items: T[], value: T): void { items.push(structuredClone(value)); if (items.length > TELEMETRY_LIMIT) items.splice(0, items.length - TELEMETRY_LIMIT); } function normalizedSql(sql: string): string { return sql .replace(/--.*$/gm, " ") .replace(/\/\*[\s\S]*?\*\//g, " ") .replace(/\s+/g, " ") .trim(); } function operationName(sql: string): string { return normalizedSql(sql).split(" ")[0]?.toUpperCase() ?? "UNKNOWN"; } async function withTimeout(promise: Promise, timeoutMs: number, sql: string): Promise { let timer: ReturnType | undefined; try { return await Promise.race([ promise, new Promise((_resolve, reject) => { timer = setTimeout( () => reject( new Error(`WRN-DB-TIMEOUT: query exceeded ${timeoutMs} ms: ${sql.slice(0, 120)}`), ), timeoutMs, ); }), ]); } finally { if (timer) clearTimeout(timer); } } export function instrumentDb(db: Db, policy: QueryPolicy = {}): Db { const counts = new Map(); const slowQueryMs = policy.slowQueryMs ?? 100; const timeoutMs = policy.timeoutMs ?? 30_000; const maxRows = policy.maxRows ?? 10_000; const duplicateWarningCount = policy.duplicateWarningCount ?? 5; const inspect = async (sql: string): Promise => { const normalized = normalizedSql(sql); if (policy.warnSelectStar !== false && /^SELECT\s+\*/i.test(normalized)) { const issue: QueryIssue = { code: "WRN-DB-SELECT-STAR", severity: "warning", message: "Avoid SELECT * in production queries.", sql, }; remember(recentQueryIssues, issue); await policy.onIssue?.(issue); } if ( policy.warnUnboundedSelect !== false && /^SELECT\b/i.test(normalized) && !/\bLIMIT\b/i.test(normalized) && !/\bCOUNT\s*\(/i.test(normalized) ) { const issue: QueryIssue = { code: "WRN-DB-UNBOUNDED-SELECT", severity: "warning", message: "SELECT query has no LIMIT. Prefer cursor pagination for large datasets.", sql, }; remember(recentQueryIssues, issue); await policy.onIssue?.(issue); } }; const record = async ( sql: string, params: unknown[], operation: QueryRecord["operation"], durationMs: number, rowCount?: number, ): Promise => { const key = `${operation}:${normalizedSql(sql)}:${JSON.stringify(params)}`; const duplicateCount = (counts.get(key) ?? 0) + 1; counts.set(key, duplicateCount); const queryRecord: QueryRecord = { sql, paramsCount: params.length, durationMs, ...(rowCount === undefined ? {} : { rowCount }), operation, duplicateCount, }; remember(recentQueries, queryRecord); await policy.onQuery?.(queryRecord); if (durationMs >= slowQueryMs) { const issue: QueryIssue = { code: "WRN-DB-SLOW-QUERY", severity: durationMs >= slowQueryMs * 5 ? "error" : "warning", message: `Query took ${durationMs.toFixed(2)} ms.`, sql, }; remember(recentQueryIssues, issue); await policy.onIssue?.(issue); } if (duplicateCount === duplicateWarningCount) { const issue: QueryIssue = { code: "WRN-DB-DUPLICATE-QUERY", severity: "warning", message: `The same query ran ${duplicateCount} times in one request scope (possible N+1).`, sql, }; remember(recentQueryIssues, issue); await policy.onIssue?.(issue); } }; const wrapped: Db = { driver: db.driver, async all(sql: string, params: unknown[] = [], model?: Model): Promise { await inspect(sql); const started = performance.now(); const rows = await withTimeout(db.all(sql, params, model), timeoutMs, sql); const duration = performance.now() - started; if (rows.length > maxRows) { await policy.onIssue?.({ code: "WRN-DB-MAX-ROWS", severity: "error", message: `Query returned ${rows.length} rows; maximum is ${maxRows}.`, sql, }); throw new Error(`WRN-DB-MAX-ROWS: query returned ${rows.length} rows.`); } await record(sql, params, "all", duration, rows.length); return rows; }, async one(sql: string, params: unknown[] = [], model?: Model): Promise { await inspect(sql); const started = performance.now(); const row = await withTimeout(db.one(sql, params, model), timeoutMs, sql); await record(sql, params, "one", performance.now() - started, row ? 1 : 0); return row; }, async exec(sql: string, params: unknown[] = []): Promise { const started = performance.now(); const result = await withTimeout(db.exec(sql, params), timeoutMs, sql); await record(sql, params, "exec", performance.now() - started, result.changes); return result; }, tx(fn: (transaction: Db) => Promise): Promise { return db.tx((transaction) => fn(instrumentDb(transaction, policy))); }, createTable(model) { return db.createTable(model); }, close() { return db.close(); }, }; return wrapped; } export function queryOperation(sql: string): string { return operationName(sql); }