ERP-node/backend-node/src/database/MySQLConnector.ts

192 lines
5.1 KiB
TypeScript
Raw Normal View History

2025-09-25 10:13:43 +09:00
import * as mysql from "mysql2/promise";
import {
DatabaseConnector,
ConnectionConfig,
QueryResult,
} from "../interfaces/DatabaseConnector";
import { ConnectionTestResult, TableInfo } from "../types/externalDbTypes";
2025-09-23 10:45:53 +09:00
export class MySQLConnector implements DatabaseConnector {
private connection: mysql.Connection | null = null;
private config: ConnectionConfig;
constructor(config: ConnectionConfig) {
this.config = config;
}
async connect(): Promise<void> {
if (this.connection) {
2025-09-25 10:13:43 +09:00
throw new Error("이미 연결되어 있습니다.");
2025-09-23 10:45:53 +09:00
}
this.connection = await mysql.createConnection({
host: this.config.host,
port: this.config.port,
database: this.config.database,
2025-09-25 10:13:43 +09:00
user: this.config.user,
2025-09-23 10:45:53 +09:00
password: this.config.password,
2025-09-25 10:13:43 +09:00
connectTimeout: this.config.connectionTimeoutMillis || 30000,
ssl: this.config.ssl ? { rejectUnauthorized: false } : undefined,
2025-09-23 10:45:53 +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-25 10:13:43 +09:00
const [versionResult] = await this.connection!.query(
"SELECT VERSION() as version"
);
2025-09-23 10:45:53 +09:00
const [sizeResult] = await this.connection!.query(
2025-09-25 10:13:43 +09:00
"SELECT SUM(data_length + index_length) as size FROM information_schema.tables WHERE table_schema = DATABASE()"
2025-09-23 10:45:53 +09:00
);
2025-09-25 10:13:43 +09:00
2025-09-23 10:45:53 +09:00
const responseTime = Date.now() - startTime;
return {
success: true,
2025-09-25 10:13:43 +09:00
message: "MySQL 연결이 성공했습니다.",
2025-09-23 10:45:53 +09:00
details: {
response_time: responseTime,
2025-09-25 10:13:43 +09:00
server_version: (versionResult as any)[0]?.version || "알 수 없음",
database_size: this.formatBytes(
parseInt((sizeResult as any)[0]?.size || "0")
),
2025-09-23 10:45:53 +09:00
},
};
} catch (error) {
return {
success: false,
2025-09-25 10:13:43 +09:00
message: "MySQL 연결에 실패했습니다.",
2025-09-23 10:45:53 +09:00
error: {
2025-09-25 10:13:43 +09:00
code: "CONNECTION_FAILED",
details: error instanceof Error ? error.message : "알 수 없는 오류",
2025-09-23 10:45:53 +09:00
},
};
} finally {
await this.disconnect();
}
}
2025-09-29 15:21:14 +09:00
async executeQuery(query: string, params: any[] = []): Promise<QueryResult> {
2025-09-23 10:45:53 +09:00
if (!this.connection) {
await this.connect();
}
try {
2025-09-29 15:21:14 +09:00
const [rows, fields] = await this.connection!.query(query, params);
2025-09-23 10:45:53 +09:00
return {
rows: rows as any[],
rowCount: Array.isArray(rows) ? rows.length : 0,
2025-09-25 10:13:43 +09:00
fields: (fields as mysql.FieldPacket[]).map((field) => ({
2025-09-23 10:45:53 +09:00
name: field.name,
2025-09-25 10:13:43 +09:00
dataType: field.type?.toString() || "unknown",
2025-09-23 10:45:53 +09:00
})),
};
} finally {
await this.disconnect();
}
}
async getTables(): Promise<TableInfo[]> {
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[]) {
2025-09-25 10:13:43 +09:00
const [columns] = await this.connection!.query(
`
2025-09-23 10:45:53 +09:00
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
2025-09-25 10:13:43 +09:00
`,
[table.table_name]
);
2025-09-23 10:45:53 +09:00
result.push({
table_name: table.table_name,
description: table.table_description || null,
columns: columns as any[],
});
}
return result;
} finally {
await this.disconnect();
}
}
2025-09-25 10:13:43 +09:00
async getColumns(tableName: string): Promise<
Array<{
name: string;
dataType: string;
isNullable: boolean;
defaultValue?: string;
}>
> {
2025-09-23 10:45:53 +09:00
if (!this.connection) {
await this.connect();
}
try {
2025-09-25 10:13:43 +09:00
const [columns] = await this.connection!.query(
`
2025-09-23 10:45:53 +09:00
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
2025-09-25 10:13:43 +09:00
`,
[tableName]
);
2025-09-23 10:45:53 +09:00
2025-09-25 10:13:43 +09:00
return (columns as any[]).map((col) => ({
2025-09-23 10:45:53 +09:00
name: col.name,
dataType: col.dataType,
isNullable: col.isNullable,
defaultValue: col.defaultValue,
}));
} finally {
await this.disconnect();
}
}
private formatBytes(bytes: number): string {
2025-09-25 10:13:43 +09:00
if (bytes === 0) return "0 Bytes";
2025-09-23 10:45:53 +09:00
const k = 1024;
2025-09-25 10:13:43 +09:00
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
2025-09-23 10:45:53 +09:00
const i = Math.floor(Math.log(bytes) / Math.log(k));
2025-09-25 10:13:43 +09:00
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
2025-09-23 10:45:53 +09:00
}
}