/** * Database Service * Core business logic for database operations * Shared between MCP and HTTP modes */ import type { DbAdapter, DbConfig, QueryResult, SchemaInfo, TableInfo, EnumValuesResult, SampleDataResult } from '../types/adapter.js'; import { SchemaEnhancerConfig } from '../utils/schema-enhancer.js'; import type { QueryAnalyzer } from './query-analyzer.js'; /** * Schema 缓存配置 */ export interface SchemaCacheConfig { /** 缓存过期时间(毫秒),默认 1 分钟 */ ttl: number; /** 是否启用缓存,默认 true */ enabled: boolean; } /** * Schema 增强配置(导出供外部使用) */ export type { SchemaEnhancerConfig }; /** * Schema 缓存统计信息 */ export interface SchemaCacheStats { /** 缓存是否有效 */ isCached: boolean; /** 缓存时间 */ cachedAt: Date | null; /** 缓存过期时间 */ expiresAt: Date | null; /** 缓存命中次数 */ hitCount: number; /** 缓存未命中次数 */ missCount: number; } /** * Database Service Class * Encapsulates all database operations with validation and error handling */ export declare class DatabaseService { private adapter; private config; private schemaCache; private schemaCacheTime; private cacheConfig; private cacheHitCount; private cacheMissCount; private queryTimeoutMs; private slowQueryThresholdMs; private slowBufferSize; private slowQueries; private queryAnalyzer; private activeProfileProvider; private schemaEnhancer; private dataMasker; constructor(adapter: DbAdapter, config: DbConfig, options?: Partial<{ slowQueryThresholdMs: number; slowBufferSize: number; }> & Partial, enhancerConfig?: Partial); /** * v2.17: attach a QueryAnalyzer (optional). When set, execute_query response * includes a `lint` field, and queries are recorded to history.db. */ setQueryAnalyzer(qa: QueryAnalyzer | null): void; /** * v2.19: register a callback returning the currently active profile name * (or `null` for legacy single-DB mode). The DatabaseService forwards * this to its QueryAnalyzer (when one is registered), so any history * recorded while executing a query automatically gets profile_name * populated. Pass `null` to clear. */ setActiveProfileProvider(fn: (() => string | null) | null): void; /** v2.19: returns the registered active-profile provider (for diagnostics). */ getActiveProfileProvider(): (() => string | null) | null; /** * Execute a query with validation * * 重要: query timeout 为"best-effort"。 * - 调用方的 Promise 会在达到超时时间后立即 reject, * 这是保证 UI/上层不会无限等待的唯一手段。 * - 但是,底层驱动(mysql2 / pg / mssql / oracledb 等)的 SQL 执行 * 在原生层是同步或独立任务的,我们无法用可移植的方式真正取消它。 * - 因此达到超时后,DB 端的语句**可能仍在执行** —— * 写操作尤其需要注意: BEGIN/COMMIT 仍然会落到连接池中。 * - 若某个应用场景需要真正可取消的查询,请使用支持查询取消的专用 API。 */ executeQuery(query: string, params?: unknown[]): Promise; /** * Wrap a promise with a hard timeout. * * Limitations (see executeQuery doc comment): * - Only the **caller's wait** is bounded; the underlying DB query * is NOT cancelled when the timer fires. Drivers like mysql2 / pg / * mssql / oracledb do not expose a portable query cancellation API, * so we accept the trade-off (bounded wait, possibly-continued DB work). * - The "best-effort" timeout is still useful: without it, a hanging * connection would stall the MCP request indefinitely. */ private withTimeout; /** * Execute a multi-statement script or PL block. * Requires 'script' permission. */ executeScript(query: string, options?: { useTransaction?: boolean; maxStatements?: number; }): Promise; /** * Execute a batch DML operation. * Requires 'batch' permission. */ executeBatch(sql: string, paramsList: unknown[][], options?: { useTransaction?: boolean; maxBatchSize?: number; }): Promise<{ affectedRowsPerStatement: number[]; totalAffectedRows: number; executionTime?: number; }>; /** * Generate and insert sample data based on table structure + LLM-provided rules. * Requires 'insert' + 'batch' permissions. */ generateAndInsertSampleData(tableName: string, rowCount: number, options?: { seed?: number; rules?: any[]; columnOverrides?: Record; columns?: string[]; overwrite?: boolean; }): Promise<{ insertedRows: number; tableName: string; columns: string[]; executionTime: number; }>; /** * Execute SQL from a file path. * Requires 'script' permission and file path to be in configured allowlist. */ executeSqlFile(options: { filePath: string; useTransaction?: boolean; maxStatements?: number; }): Promise; /** * Get complete database schema * @param forceRefresh - 是否强制刷新缓存,忽略现有缓存 */ getSchema(forceRefresh?: boolean): Promise; /** * 增强 Schema 信息 * - 为现有外键关系添加 source 标记 * - 推断隐式关系 * - 细化关系类型 */ private enhanceSchema; /** * Get information about a specific table * @param tableName - 表名(支持 schema.table_name 格式) * @param forceRefresh - 是否强制刷新缓存 */ getTableInfo(tableName: string, forceRefresh?: boolean): Promise; /** * List all tables in the database * @param forceRefresh - 是否强制刷新缓存 */ listTables(forceRefresh?: boolean): Promise; /** * Test database connection */ testConnection(): Promise; /** * 清除 Schema 缓存 */ clearSchemaCache(): void; /** * 获取缓存统计信息 */ getCacheStats(): SchemaCacheStats; /** * 获取缓存命中率 */ getCacheHitRate(): string; /** * 更新缓存配置 */ updateCacheConfig(config: Partial): void; /** * 更新 Schema 增强配置 */ updateEnhancerConfig(config: Partial): void; /** * 获取 Schema 增强配置 */ getEnhancerConfig(): SchemaEnhancerConfig; /** * Validate query against write permissions */ private validateQuery; /** * Get the underlying adapter */ getAdapter(): DbAdapter; /** * Get the configuration */ getConfig(): DbConfig; /** * 获取指定列的枚举值 * 用于帮助 LLM 了解 status、type 等枚举列的所有可能值 * * @param tableName - 表名 * @param columnName - 列名 * @param limit - 最大返回数量(默认 50,最大 100) * @param includeCount - 是否包含每个值的出现次数(默认 false) * @returns 枚举值查询结果 */ getEnumValues(tableName: string, columnName: string, limit?: number, includeCount?: boolean): Promise; /** * 获取表的示例数据(已脱敏) * 用于帮助 LLM 理解数据格式(日期格式、ID 格式等) * * @param tableName - 表名 * @param columns - 要查看的列(可选,默认全部) * @param limit - 返回行数(默认 3,最大 10) * @returns 示例数据查询结果 */ getSampleData(tableName: string, columns?: string[], limit?: number): Promise; /** * 构建枚举值查询 SQL(不含计数) * * P1: 使用抽样策略避免对大表做完整的 DISTINCT 扫描。 * 先随机抽样 10000 行(按 RANDOM()/RAND() 排序后取 LIMIT), * 再对这些样本做 DISTINCT 并按值排序,最后取所需 limit。 * * 注意:抽样仅在支持 RAND()/RANDOM() + LIMIT 子查询的方言上启用: * MySQL / TiDB / OceanBase / PolarDB / GoldenDB / PostgreSQL / SQLite。 * Oracle 和 SQL Server 不能在子查询中使用 RANDOM()/RAND() 或 LIMIT, * 因此回退到简单的全表 DISTINCT(稍慢但是语义正确)。 */ private buildEnumValuesQuery; /** * 是否对当前方言启用 get_enum_values 的随机抽样优化。 * 返回 false 时,buildEnumValuesQuery 回退到简单 DISTINCT。 */ private supportsEnumSampling; /** * 判断当前数据库是否使用 MySQL 风格的 RAND()(而不是 RANDOM())。 * MySQL / TiDB / OceanBase / PolarDB / GoldenDB 兼容 MySQL RAND()。 */ private useMySQLRandom; /** * 构建枚举值查询 SQL(含计数) */ private buildEnumValuesQueryWithCount; /** * 构建示例数据查询 SQL */ private buildSampleDataQuery; /** * 引用标识符(表名、列名) * 根据数据库类型使用不同的引号 * 支持 schema.table 格式:自动拆分并分别引用 */ private quoteIdentifier; /** * 引用单个标识符(不含 schema 前缀) */ private quoteSimpleIdentifier; /** * 添加 LIMIT 子句 * 根据数据库类型使用不同的语法 */ private appendLimit; /** * 生成 INSERT 占位符字符串 * * 不同方言的参数占位符语法不同: * - MySQL / Oracle / SQLite / DM / Kingbase / GaussDB / Vastbase / HighGo / * ClickHouse / OceanBase / TiDB / PolarDB / GoldenDB: ? (anonymous) * - PostgreSQL: $1, $2, ... * - SQL Server: @p1, @p2, ... * * 这里生成的占位符串会被直接拼接到 "( ... )" 中,例如 "(?, ?, ?)" / "($1, $2, $3)"。 */ private buildPlaceholderString; } //# sourceMappingURL=database-service.d.ts.map