cyber-mysql-openai
Version:
Intelligent natural language to SQL translator with self-correction capabilities using OpenAI and MySQL
226 lines • 7.19 kB
JavaScript
"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