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.user, password: this.config.password, connectTimeout: this.config.connectionTimeoutMillis || 30000, ssl: this.config.ssl ? { 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, params: any[] = []): Promise { if (!this.connection) { await this.connect(); } try { const [rows, fields] = await this.connection!.query(query, params); 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() || "unknown", })), }; } 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< Array<{ name: string; dataType: string; isNullable: boolean; defaultValue?: string; }> > { 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]; } }