import { DatabaseConnector, ConnectionConfig, QueryResult, } from "../interfaces/DatabaseConnector"; import { ConnectionTestResult, TableInfo } from "../types/externalDbTypes"; import * as mysql from "mysql2/promise"; export class MariaDBConnector implements DatabaseConnector { private connection: mysql.Connection | null = null; private config: ConnectionConfig; constructor(config: ConnectionConfig) { this.config = config; } async connect(): Promise { if (!this.connection) { this.connection = await mysql.createConnection({ host: this.config.host, port: this.config.port, user: this.config.user, password: this.config.password, database: this.config.database, // πŸ”§ MySQL2μ—μ„œ μ§€μ›ν•˜λŠ” νƒ€μž„μ•„μ›ƒ μ„€μ • connectTimeout: this.config.connectionTimeoutMillis || 30000, // μ—°κ²° νƒ€μž„μ•„μ›ƒ 30초 ssl: typeof this.config.ssl === "boolean" ? undefined : this.config.ssl, // πŸ”§ MySQL2μ—μ„œ μ§€μ›ν•˜λŠ” μΆ”κ°€ μ„€μ • charset: "utf8mb4", timezone: "Z", supportBigNumbers: true, bigNumberStrings: true, // πŸ”§ μ—°κ²° ν’€ μ„€μ • (단일 μ—°κ²°μ΄μ§€λ§Œ μ•ˆμ •μ„±μ„ μœ„ν•΄) dateStrings: true, debug: false, trace: false, }); } } 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 [rows] = await this.connection!.query( "SELECT VERSION() as version" ); const version = (rows as any[])[0]?.version || "Unknown"; const responseTime = Date.now() - startTime; await this.disconnect(); return { success: true, message: "MariaDB/MySQL 연결이 μ„±κ³΅ν–ˆμŠ΅λ‹ˆλ‹€.", details: { response_time: responseTime, server_version: version, }, }; } catch (error: any) { await this.disconnect(); return { success: false, message: "MariaDB/MySQL 연결에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.", error: { code: "CONNECTION_FAILED", details: error.message || "μ•Œ 수 μ—†λŠ” 였λ₯˜", }, }; } } async executeQuery(query: string): Promise { try { await this.connect(); // πŸ”§ 쿼리 νƒ€μž„μ•„μ›ƒ μˆ˜λ™ κ΅¬ν˜„ (60초) const queryTimeout = this.config.queryTimeoutMillis || 60000; const queryPromise = this.connection!.query(query); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error("쿼리 μ‹€ν–‰ νƒ€μž„μ•„μ›ƒ")), queryTimeout); }); const [rows, fields] = (await Promise.race([ queryPromise, timeoutPromise, ])) as any; await this.disconnect(); return { rows: rows as any[], fields: fields as any[], }; } catch (error: any) { await this.disconnect(); throw new Error(`쿼리 μ‹€ν–‰ μ‹€νŒ¨: ${error.message}`); } } async getTables(): Promise { try { await this.connect(); const [rows] = await this.connection!.query(` SELECT TABLE_NAME as table_name, TABLE_COMMENT as description FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() ORDER BY TABLE_NAME; `); const tables: TableInfo[] = []; for (const row of rows as any[]) { const columns = await this.getColumns(row.table_name); tables.push({ table_name: row.table_name, description: row.description || null, columns: columns, }); } await this.disconnect(); return tables; } catch (error: any) { await this.disconnect(); throw new Error(`ν…Œμ΄λΈ” λͺ©λ‘ 쑰회 μ‹€νŒ¨: ${error.message}`); } } async getColumns(tableName: string): Promise { try { console.log(`πŸ” MariaDB 컬럼 쑰회 μ‹œμž‘: ${tableName}`); await this.connect(); // πŸ”§ 컬럼 쑰회 νƒ€μž„μ•„μ›ƒ μˆ˜λ™ κ΅¬ν˜„ (30초) const queryTimeout = this.config.queryTimeoutMillis || 30000; // μŠ€ν‚€λ§ˆλͺ…을 λͺ…μ‹œμ μœΌλ‘œ 확인 const schemaQuery = `SELECT DATABASE() as schema_name`; const [schemaResult] = await this.connection!.query(schemaQuery); const schemaName = (schemaResult as any[])[0]?.schema_name || this.config.database; console.log(`πŸ“‹ μ‚¬μš©ν•  μŠ€ν‚€λ§ˆ: ${schemaName}`); const query = ` SELECT COLUMN_NAME as column_name, DATA_TYPE as data_type, IS_NULLABLE as is_nullable, COLUMN_DEFAULT as column_default, COLUMN_COMMENT as column_comment FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION; `; console.log( `πŸ“‹ μ‹€ν–‰ν•  쿼리: ${query.trim()}, νŒŒλΌλ―Έν„°: [${schemaName}, ${tableName}]` ); const queryPromise = this.connection!.query(query, [ schemaName, tableName, ]); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error("컬럼 쑰회 νƒ€μž„μ•„μ›ƒ")), queryTimeout); }); const [rows] = (await Promise.race([ queryPromise, timeoutPromise, ])) as any; console.log( `βœ… MariaDB 컬럼 쑰회 μ™„λ£Œ: ${tableName}, ${rows ? rows.length : 0}개 컬럼` ); await this.disconnect(); return rows as any[]; } catch (error: any) { await this.disconnect(); throw new Error(`컬럼 정보 쑰회 μ‹€νŒ¨: ${error.message}`); } } }