183 lines
5.8 KiB
TypeScript
183 lines
5.8 KiB
TypeScript
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<void> {
|
|
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<void> {
|
|
if (this.pool) {
|
|
await this.pool.close();
|
|
this.pool = null;
|
|
}
|
|
}
|
|
|
|
async testConnection(): Promise<ConnectionTestResult> {
|
|
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<QueryResult> {
|
|
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<TableInfo[]> {
|
|
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<string, TableInfo>();
|
|
|
|
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<any[]> {
|
|
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];
|
|
}
|
|
} |