import { DatabaseConnector, ConnectionConfig, QueryResult } from '../interfaces/DatabaseConnector'; import { ConnectionTestResult, TableInfo } from '../types/externalDbTypes'; // @ts-ignore import * as mssql from 'mssql'; export class MSSQLConnector implements DatabaseConnector { private pool: mssql.ConnectionPool | null = null; private config: ConnectionConfig; constructor(config: ConnectionConfig) { this.config = config; } async connect(): Promise { if (!this.pool) { const config: mssql.config = { server: this.config.host, port: this.config.port, user: this.config.user, password: this.config.password, database: this.config.database, options: { encrypt: this.config.ssl === true, trustServerCertificate: true }, connectionTimeout: this.config.connectionTimeoutMillis || 15000, requestTimeout: this.config.queryTimeoutMillis || 15000 }; this.pool = await new mssql.ConnectionPool(config).connect(); } } async disconnect(): Promise { if (this.pool) { await this.pool.close(); this.pool = null; } } async testConnection(): Promise { const startTime = Date.now(); try { await this.connect(); // 버전 정보 조회 const versionResult = await this.pool!.request().query('SELECT @@VERSION as version'); // 데이터베이스 크기 조회 const sizeResult = await this.pool!.request() .input('dbName', mssql.VarChar, this.config.database) .query(` SELECT SUM(size * 8 * 1024) as size FROM sys.master_files WHERE database_id = DB_ID(@dbName) `); const responseTime = Date.now() - startTime; return { success: true, message: "MSSQL 연결이 성공했습니다.", details: { response_time: responseTime, server_version: versionResult.recordset[0]?.version || "알 수 없음", database_size: this.formatBytes(parseInt(sizeResult.recordset[0]?.size || "0")), }, }; } catch (error: any) { return { success: false, message: "MSSQL 연결에 실패했습니다.", error: { code: "CONNECTION_FAILED", details: error.message || "알 수 없는 오류", }, }; } } async executeQuery(query: string): Promise { try { await this.connect(); const result = await this.pool!.request().query(query); return { rows: result.recordset, rowCount: result.rowsAffected[0], }; } catch (error: any) { throw new Error(`쿼리 실행 오류: ${error.message}`); } } async getTables(): Promise { try { await this.connect(); const result = await this.pool!.request().query(` SELECT t.TABLE_NAME as table_name, c.COLUMN_NAME as column_name, c.DATA_TYPE as data_type, c.IS_NULLABLE as is_nullable, c.COLUMN_DEFAULT as column_default, CAST(p.value AS NVARCHAR(MAX)) as description FROM INFORMATION_SCHEMA.TABLES t LEFT JOIN INFORMATION_SCHEMA.COLUMNS c ON c.TABLE_NAME = t.TABLE_NAME LEFT JOIN sys.extended_properties p ON p.major_id = OBJECT_ID(t.TABLE_NAME) AND p.minor_id = 0 AND p.name = 'MS_Description' WHERE t.TABLE_TYPE = 'BASE TABLE' ORDER BY t.TABLE_NAME, c.ORDINAL_POSITION `); // 결과를 TableInfo[] 형식으로 변환 const tables = new Map(); result.recordset.forEach((row: any) => { if (!tables.has(row.table_name)) { tables.set(row.table_name, { table_name: row.table_name, columns: [], description: row.description || null }); } if (row.column_name) { tables.get(row.table_name)!.columns.push({ column_name: row.column_name, data_type: row.data_type, is_nullable: row.is_nullable === 'YES' ? 'Y' : 'N', column_default: row.column_default }); } }); return Array.from(tables.values()); } catch (error: any) { throw new Error(`테이블 목록 조회 오류: ${error.message}`); } } async getColumns(tableName: string): Promise { try { await this.connect(); const result = await this.pool!.request() .input('tableName', mssql.VarChar, tableName) .query(` SELECT c.COLUMN_NAME as name, c.DATA_TYPE as type, c.IS_NULLABLE as nullable, c.COLUMN_DEFAULT as default_value, c.CHARACTER_MAXIMUM_LENGTH as max_length, c.NUMERIC_PRECISION as precision, c.NUMERIC_SCALE as scale, CAST(p.value AS NVARCHAR(MAX)) as description FROM INFORMATION_SCHEMA.COLUMNS c LEFT JOIN sys.columns sc ON sc.object_id = OBJECT_ID(@tableName) AND sc.name = c.COLUMN_NAME LEFT JOIN sys.extended_properties p ON p.major_id = sc.object_id AND p.minor_id = sc.column_id AND p.name = 'MS_Description' WHERE c.TABLE_NAME = @tableName ORDER BY c.ORDINAL_POSITION `); return result.recordset; } catch (error: any) { throw new Error(`컬럼 정보 조회 오류: ${error.message}`); } } 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]; } }