import { Client } from 'pg'; import { DatabaseConnector, ConnectionConfig, QueryResult } from '../interfaces/DatabaseConnector'; import { ConnectionTestResult, TableInfo } from '../types/externalDbTypes'; export class PostgreSQLConnector implements DatabaseConnector { private client: Client | null = null; private config: ConnectionConfig; constructor(config: ConnectionConfig) { this.config = config; } async connect(): Promise { if (this.client) { await this.disconnect(); } const clientConfig: any = { host: this.config.host, port: this.config.port, database: this.config.database, user: this.config.user, password: this.config.password, }; if (this.config.connectionTimeoutMillis != null) { clientConfig.connectionTimeoutMillis = this.config.connectionTimeoutMillis; } if (this.config.queryTimeoutMillis != null) { clientConfig.query_timeout = this.config.queryTimeoutMillis; } if (this.config.ssl != null) { clientConfig.ssl = this.config.ssl; } this.client = new Client(clientConfig); await this.client.connect(); } async disconnect(): Promise { if (this.client) { await this.client.end(); this.client = null; } } async testConnection(): Promise { const startTime = Date.now(); try { await this.connect(); const result = await this.client!.query("SELECT version(), pg_database_size(current_database()) as size"); const responseTime = Date.now() - startTime; await this.disconnect(); return { success: true, message: "PostgreSQL 연결이 성공했습니다.", details: { response_time: responseTime, server_version: result.rows[0]?.version || "알 수 없음", database_size: this.formatBytes(parseInt(result.rows[0]?.size || "0")), }, }; } catch (error: any) { await this.disconnect(); return { success: false, message: "PostgreSQL 연결에 실패했습니다.", error: { code: "CONNECTION_FAILED", details: error.message || "알 수 없는 오류", }, }; } } async executeQuery(query: string): Promise { try { await this.connect(); const result = await this.client!.query(query); await this.disconnect(); return { rows: result.rows, rowCount: result.rowCount ?? undefined, fields: result.fields ?? undefined, }; } catch (error: any) { await this.disconnect(); throw new Error(`PostgreSQL 쿼리 실행 실패: ${error.message}`); } } async getTables(): Promise { try { await this.connect(); const result = await this.client!.query(` SELECT t.table_name, obj_description(quote_ident(t.table_name)::regclass::oid, 'pg_class') as table_description FROM information_schema.tables t WHERE t.table_schema = 'public' AND t.table_type = 'BASE TABLE' ORDER BY t.table_name; `); await this.disconnect(); return result.rows.map((row) => ({ table_name: row.table_name, description: row.table_description, columns: [], // Columns will be fetched by getColumns })); } catch (error: any) { await this.disconnect(); throw new Error(`PostgreSQL 테이블 목록 조회 실패: ${error.message}`); } } async getColumns(tableName: string): Promise { try { await this.connect(); const result = await this.client!.query(` SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1 ORDER BY ordinal_position; `, [tableName]); await this.disconnect(); return result.rows; } catch (error: any) { await this.disconnect(); throw new Error(`PostgreSQL 컬럼 정보 조회 실패: ${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]; } }