import { QueryResult } from '../types'; import { databasePoolManager } from './DatabasePoolManager'; export class SqlExecutor { private async retryQuery( fn: () => Promise, retries: number = 3, delay: number = 1000 ): Promise { for (let attempt = 1; attempt <= retries; attempt++) { try { return await fn(); } catch (error: any) { // Retry only on connection-related errors const isConnectionError = error.message?.includes('timeout') || error.message?.includes('Connection terminated') || error.message?.includes('ECONNREFUSED') || error.message?.includes('ETIMEDOUT'); if (!isConnectionError || attempt === retries) { throw error; } console.warn(`Query attempt ${attempt} failed, retrying in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); } } throw new Error('Max retries exceeded'); } async executeQuery( databaseId: string, sqlQuery: string, params: any[] = [] ): Promise { const pool = databasePoolManager.getPool(databaseId); if (!pool) { throw new Error(`Database with id ${databaseId} not found or not configured`); } const startTime = Date.now(); try { // Security: Prevent multiple statements and dangerous commands this.validateQuery(sqlQuery); // Execute with retry mechanism const result = await this.retryQuery(async () => { return await pool.query(sqlQuery, params); }, 3, 500); // 3 попытки с задержкой 500ms const executionTime = Date.now() - startTime; return { rows: result.rows, rowCount: result.rowCount || 0, executionTime, }; } catch (error: any) { console.error('SQL Execution Error:', error); throw new Error(`SQL Error: ${error.message}`); } } private validateQuery(sqlQuery: string) { const normalized = sqlQuery.trim().toLowerCase(); // Prevent multiple statements (basic check) if (normalized.includes(';') && normalized.indexOf(';') < normalized.length - 1) { throw new Error('Multiple SQL statements are not allowed'); } // Prevent dangerous commands (you can extend this list) const dangerousCommands = ['drop', 'truncate', 'delete from', 'alter', 'create', 'grant', 'revoke']; const isDangerous = dangerousCommands.some(cmd => normalized.startsWith(cmd)); if (isDangerous && !normalized.startsWith('select')) { // For safety, you might want to allow only SELECT queries // Or implement a whitelist/permission system for write operations console.warn('Potentially dangerous query detected:', sqlQuery); } } async testQuery(databaseId: string, sqlQuery: string): Promise<{ success: boolean; error?: string }> { try { await this.executeQuery(databaseId, sqlQuery); return { success: true }; } catch (error: any) { return { success: false, error: error.message }; } } async getTableSchema(databaseId: string, tableName: string): Promise { const query = ` SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_name = $1 ORDER BY ordinal_position; `; const result = await this.executeQuery(databaseId, query, [tableName]); return result.rows; } async getAllTables(databaseId: string): Promise { const query = ` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name; `; const result = await this.executeQuery(databaseId, query); return result.rows.map(row => row.table_name); } } export const sqlExecutor = new SqlExecutor();