Files
api_builder/backend/src/services/SqlExecutor.ts
2025-11-29 16:07:01 +03:00

136 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { QueryResult } from '../types';
import { databasePoolManager } from './DatabasePoolManager';
export class SqlExecutor {
private async retryQuery<T>(
fn: () => Promise<T>,
retries: number = 3,
delay: number = 1000
): Promise<T> {
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<QueryResult> {
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);
// Log SQL query and parameters before execution
console.log('\n[SQL DB]', databaseId);
console.log('[SQL Query]', sqlQuery);
console.log('[SQL Query HEX first 100 chars]', Buffer.from(sqlQuery.substring(0, 100)).toString('hex'));
console.log('[SQL Params]', params);
// Execute with retry mechanism
const result = await this.retryQuery(async () => {
const queryResult = await pool.query(sqlQuery, params);
console.log('[SQL Result] rowCount:', queryResult.rowCount, 'rows:', JSON.stringify(queryResult.rows).substring(0, 500));
return queryResult;
}, 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<any[]> {
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<string[]> {
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();