import * as mysql from 'mysql2/promise'; import { DatabaseConnector, ConnectionConfig, QueryResult } from '../interfaces/DatabaseConnector'; import { ConnectionTestResult, TableInfo } from '../types/externalDbTypes'; export class MySQLConnector implements DatabaseConnector { private connection: mysql.Connection | null = null; private config: ConnectionConfig; constructor(config: ConnectionConfig) { this.config = config; } async connect(): Promise { if (this.connection) { throw new Error('이미 연결되어 있습니다.'); } this.connection = await mysql.createConnection({ host: this.config.host, port: this.config.port, database: this.config.database, user: this.config.username, password: this.config.password, connectTimeout: this.config.connectionTimeout || 30000, ssl: this.config.sslEnabled ? { rejectUnauthorized: false } : undefined, }); } async disconnect(): Promise { if (this.connection) { await this.connection.end(); this.connection = null; } } async testConnection(): Promise { const startTime = Date.now(); try { await this.connect(); const [versionResult] = await this.connection!.query('SELECT VERSION() as version'); const [sizeResult] = await this.connection!.query( 'SELECT SUM(data_length + index_length) as size FROM information_schema.tables WHERE table_schema = DATABASE()' ); const responseTime = Date.now() - startTime; return { success: true, message: 'MySQL 연결이 성공했습니다.', details: { response_time: responseTime, server_version: (versionResult as any)[0]?.version || '알 수 없음', database_size: this.formatBytes(parseInt((sizeResult as any)[0]?.size || '0')), }, }; } catch (error) { return { success: false, message: 'MySQL 연결에 실패했습니다.', error: { code: 'CONNECTION_FAILED', details: error instanceof Error ? error.message : '알 수 없는 오류', }, }; } finally { await this.disconnect(); } } async executeQuery(query: string): Promise { if (!this.connection) { await this.connect(); } try { const [rows, fields] = await this.connection!.query(query); return { rows: rows as any[], rowCount: Array.isArray(rows) ? rows.length : 0, fields: (fields as mysql.FieldPacket[]).map(field => ({ name: field.name, dataType: field.type.toString(), })), }; } finally { await this.disconnect(); } } async getTables(): Promise { if (!this.connection) { await this.connect(); } try { const [tables] = await this.connection!.query(` SELECT t.TABLE_NAME as table_name, t.TABLE_COMMENT as table_description FROM information_schema.TABLES t WHERE t.TABLE_SCHEMA = DATABASE() ORDER BY t.TABLE_NAME `); const result: TableInfo[] = []; for (const table of tables as any[]) { const [columns] = await this.connection!.query(` SELECT COLUMN_NAME as column_name, DATA_TYPE as data_type, IS_NULLABLE as is_nullable, COLUMN_DEFAULT as column_default FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION `, [table.table_name]); result.push({ table_name: table.table_name, description: table.table_description || null, columns: columns as any[], }); } return result; } finally { await this.disconnect(); } } async getColumns(tableName: string): Promise> { if (!this.connection) { await this.connect(); } try { const [columns] = await this.connection!.query(` SELECT COLUMN_NAME as name, DATA_TYPE as dataType, IS_NULLABLE = 'YES' as isNullable, COLUMN_DEFAULT as defaultValue FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION `, [tableName]); return (columns as any[]).map(col => ({ name: col.name, dataType: col.dataType, isNullable: col.isNullable, defaultValue: col.defaultValue, })); } finally { await this.disconnect(); } } private formatBytes(bytes: number): string { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } }