2025-09-26 01:28:51 +09:00
|
|
|
import {
|
|
|
|
|
DatabaseConnector,
|
|
|
|
|
ConnectionConfig,
|
|
|
|
|
QueryResult,
|
|
|
|
|
} from "../interfaces/DatabaseConnector";
|
|
|
|
|
import { ConnectionTestResult, TableInfo } from "../types/externalDbTypes";
|
|
|
|
|
import * as mysql from "mysql2/promise";
|
2025-09-24 10:04:25 +09:00
|
|
|
|
|
|
|
|
export class MariaDBConnector implements DatabaseConnector {
|
|
|
|
|
private connection: mysql.Connection | null = null;
|
|
|
|
|
private config: ConnectionConfig;
|
|
|
|
|
|
|
|
|
|
constructor(config: ConnectionConfig) {
|
|
|
|
|
this.config = config;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async connect(): Promise<void> {
|
|
|
|
|
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,
|
2025-09-26 01:28:51 +09:00
|
|
|
// 🔧 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,
|
2025-09-24 10:04:25 +09:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async disconnect(): Promise<void> {
|
|
|
|
|
if (this.connection) {
|
|
|
|
|
await this.connection.end();
|
|
|
|
|
this.connection = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async testConnection(): Promise<ConnectionTestResult> {
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
try {
|
|
|
|
|
await this.connect();
|
2025-09-26 01:28:51 +09:00
|
|
|
const [rows] = await this.connection!.query(
|
|
|
|
|
"SELECT VERSION() as version"
|
|
|
|
|
);
|
2025-09-24 10:04:25 +09:00
|
|
|
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<QueryResult> {
|
|
|
|
|
try {
|
|
|
|
|
await this.connect();
|
2025-09-26 01:28:51 +09:00
|
|
|
|
|
|
|
|
// 🔧 쿼리 타임아웃 수동 구현 (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;
|
2025-09-24 10:04:25 +09:00
|
|
|
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<TableInfo[]> {
|
|
|
|
|
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<any[]> {
|
|
|
|
|
try {
|
2025-09-26 01:28:51 +09:00
|
|
|
console.log(`🔍 MariaDB 컬럼 조회 시작: ${tableName}`);
|
2025-09-24 10:04:25 +09:00
|
|
|
await this.connect();
|
2025-09-26 01:28:51 +09:00
|
|
|
|
|
|
|
|
// 🔧 컬럼 조회 타임아웃 수동 구현 (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 = `
|
2025-09-24 10:04:25 +09:00
|
|
|
SELECT
|
|
|
|
|
COLUMN_NAME as column_name,
|
|
|
|
|
DATA_TYPE as data_type,
|
|
|
|
|
IS_NULLABLE as is_nullable,
|
2025-09-26 01:28:51 +09:00
|
|
|
COLUMN_DEFAULT as column_default,
|
|
|
|
|
COLUMN_COMMENT as column_comment
|
2025-09-24 10:04:25 +09:00
|
|
|
FROM information_schema.COLUMNS
|
2025-09-26 01:28:51 +09:00
|
|
|
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
2025-09-24 10:04:25 +09:00
|
|
|
ORDER BY ORDINAL_POSITION;
|
2025-09-26 01:28:51 +09:00
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
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}개 컬럼`
|
|
|
|
|
);
|
2025-09-24 10:04:25 +09:00
|
|
|
await this.disconnect();
|
|
|
|
|
return rows as any[];
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
await this.disconnect();
|
|
|
|
|
throw new Error(`컬럼 정보 조회 실패: ${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-26 01:28:51 +09:00
|
|
|
}
|