UNPKG

cyber-mysql-openai

Version:

Intelligent natural language to SQL translator with self-correction capabilities using OpenAI and MySQL

226 lines 7.19 kB
"use strict"; /** * Sistema de cache en memoria para consultas SQL * Optimiza el rendimiento almacenando resultados de consultas frecuentes */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MemoryCache = void 0; class MemoryCache { constructor(maxSize = 1000, cleanupIntervalMs = 300000) { this.cache = new Map(); this.stats = { hits: 0, misses: 0 }; this.cleanupInterval = null; this.maxSize = maxSize; this.enabled = true; // Cleanup automático cada 5 minutos por defecto if (cleanupIntervalMs > 0) { this.cleanupInterval = setInterval(() => this.cleanup(), cleanupIntervalMs); } } /** * Obtiene la instancia singleton del cache */ static getInstance(maxSize = 1000, cleanupIntervalMs = 300000) { if (!MemoryCache.instance) { MemoryCache.instance = new MemoryCache(maxSize, cleanupIntervalMs); } return MemoryCache.instance; } /** * Genera una clave de cache normalizada */ generateKey(prompt, language, schemaHash) { // Normalizar la consulta para mejorar hit rate const normalizedPrompt = prompt .toLowerCase() .trim() .replace(/\s+/g, ' ') // Múltiples espacios → un espacio .replace(/[¿?¡!\.]/g, '') // Remover signos de puntuación .replace(/\d{4}-\d{2}-\d{2}/g, 'DATE') // Normalizar fechas .replace(/\d+/g, 'NUM'); // Normalizar números return `${this.hashString(normalizedPrompt)}_${language}_${schemaHash}`; } /** * Genera un hash simple para strings */ hashString(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convertir a 32bit integer } return Math.abs(hash).toString(36); } /** * Determina el TTL basado en el tipo de consulta SQL */ getTTLByQueryType(sql) { const upperSQL = sql.toUpperCase(); // Consultas de esquema y metadatos → 1 hora if (upperSQL.includes('SHOW TABLES') || upperSQL.includes('DESCRIBE') || upperSQL.includes('INFORMATION_SCHEMA')) { return 3600000; // 1 hora } // Consultas agregadas y reportes → 15 minutos if (upperSQL.includes('COUNT(') || upperSQL.includes('SUM(') || upperSQL.includes('AVG(') || upperSQL.includes('GROUP BY')) { return 900000; // 15 minutos } // Consultas simples → 5 minutos return 300000; // 5 minutos por defecto } /** * Obtiene una entrada del cache */ get(prompt, language, schemaHash) { if (!this.enabled) return null; const key = this.generateKey(prompt, language, schemaHash); const entry = this.cache.get(key); if (!entry) { this.stats.misses++; return null; } // Verificar TTL if (Date.now() - entry.timestamp > entry.ttl) { this.cache.delete(key); this.stats.misses++; return null; } this.stats.hits++; return entry; } /** * Almacena una entrada en el cache */ set(prompt, language, schemaHash, sql, results, naturalResponse, executionTime) { if (!this.enabled) return; const key = this.generateKey(prompt, language, schemaHash); const ttl = this.getTTLByQueryType(sql); // Limpiar cache si está lleno if (this.cache.size >= this.maxSize) { this.evictOldest(); } const entry = { sql, results, naturalResponse, timestamp: Date.now(), ttl, language, executionTime }; this.cache.set(key, entry); } /** * Elimina las entradas más antiguas cuando el cache está lleno */ evictOldest() { let oldestKey = null; let oldestTime = Date.now(); for (const [key, entry] of this.cache.entries()) { if (entry.timestamp < oldestTime) { oldestTime = entry.timestamp; oldestKey = key; } } if (oldestKey) { this.cache.delete(oldestKey); } } /** * Limpia entradas expiradas */ cleanup() { const now = Date.now(); const keysToDelete = []; for (const [key, entry] of this.cache.entries()) { if (now - entry.timestamp > entry.ttl) { keysToDelete.push(key); } } keysToDelete.forEach(key => this.cache.delete(key)); if (keysToDelete.length > 0) { console.log(`[MemoryCache] Cleaned up ${keysToDelete.length} expired entries`); } } /** * Invalida entradas relacionadas con una tabla específica */ invalidateByTable(tableName) { const keysToDelete = []; for (const [key, entry] of this.cache.entries()) { if (entry.sql.toLowerCase().includes(tableName.toLowerCase())) { keysToDelete.push(key); } } keysToDelete.forEach(key => this.cache.delete(key)); return keysToDelete.length; } /** * Obtiene estadísticas del cache */ getStats() { const total = this.stats.hits + this.stats.misses; const entries = Array.from(this.cache.values()); return { totalEntries: this.cache.size, hits: this.stats.hits, misses: this.stats.misses, hitRate: total > 0 ? (this.stats.hits / total) * 100 : 0, memoryUsage: this.estimateMemoryUsage(), oldestEntry: entries.length > 0 ? Math.min(...entries.map(e => e.timestamp)) : 0, newestEntry: entries.length > 0 ? Math.max(...entries.map(e => e.timestamp)) : 0 }; } /** * Estima el uso de memoria del cache */ estimateMemoryUsage() { let totalSize = 0; for (const entry of this.cache.values()) { // Estimación aproximada del tamaño en bytes totalSize += JSON.stringify(entry).length * 2; // 2 bytes por carácter en UTF-16 } return totalSize; } /** * Limpia todo el cache */ clear() { this.cache.clear(); this.stats = { hits: 0, misses: 0 }; } /** * Habilita o deshabilita el cache */ setEnabled(enabled) { this.enabled = enabled; if (!enabled) { this.clear(); } } /** * Verifica si el cache está habilitado */ isEnabled() { return this.enabled; } /** * Limpia los recursos del cache */ destroy() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = null; } this.clear(); } } exports.MemoryCache = MemoryCache; //# sourceMappingURL=memoryCache.js.map