// @ts-ignore import * as oracledb from "oracledb"; import { DatabaseConnector, ConnectionConfig, QueryResult, } from "../interfaces/DatabaseConnector"; import { ConnectionTestResult, TableInfo } from "../types/externalDbTypes"; export class OracleConnector implements DatabaseConnector { private connection: oracledb.Connection | null = null; private config: ConnectionConfig; constructor(config: ConnectionConfig) { this.config = config; // Oracle XE 21c 특화 설정 // oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT; // oracledb.autoCommit = true; } async connect(): Promise { try { // Oracle XE 21c 연결 문자열 구성 const connectionString = this.buildConnectionString(); const connectionConfig: any = { user: this.config.user, password: this.config.password, connectString: connectionString, }; this.connection = await oracledb.getConnection(connectionConfig); console.log("Oracle XE 21c 연결 성공"); } catch (error: any) { console.error("Oracle XE 21c 연결 실패:", error); throw new Error(`Oracle 연결 실패: ${error.message}`); } } private buildConnectionString(): string { const { host, port, database } = this.config; // Oracle XE 21c는 기본적으로 XE 서비스명을 사용 // 다양한 연결 문자열 형식 지원 if (database.includes("/") || database.includes(":")) { // 이미 완전한 연결 문자열인 경우 return database; } // Oracle XE 21c 표준 형식 return `${host}:${port}/${database}`; } async disconnect(): Promise { if (this.connection) { try { await this.connection.close(); this.connection = null; console.log("Oracle 연결 해제됨"); } catch (error: any) { console.error("Oracle 연결 해제 실패:", error); } } } async testConnection(): Promise { try { if (!this.connection) { await this.connect(); } // Oracle XE 21c 버전 확인 쿼리 const result = await this.connection!.execute( "SELECT BANNER FROM V$VERSION WHERE BANNER LIKE 'Oracle%'" ); console.log("Oracle 버전:", result.rows); return { success: true, message: "연결 성공", details: { server_version: (result.rows as any)?.[0]?.BANNER || "Unknown", }, }; } catch (error: any) { console.error("Oracle 연결 테스트 실패:", error); return { success: false, message: "연결 실패", details: { server_version: error.message, }, }; } } async executeQuery(query: string, params: any[] = []): Promise { if (!this.connection) { await this.connect(); } try { const startTime = Date.now(); // 쿼리 타입 확인 const isDML = /^\s*(INSERT|UPDATE|DELETE|MERGE)/i.test(query); const isCOMMIT = /^\s*COMMIT/i.test(query); const isROLLBACK = /^\s*ROLLBACK/i.test(query); // 🔥 COMMIT/ROLLBACK 명령은 직접 실행 if (isCOMMIT || isROLLBACK) { if (isCOMMIT) { await this.connection!.commit(); console.log("✅ Oracle COMMIT 실행됨"); } else { await this.connection!.rollback(); console.log("⚠️ Oracle ROLLBACK 실행됨"); } return { rows: [], rowCount: 0, fields: [], affectedRows: 0, }; } // Oracle XE 21c 쿼리 실행 옵션 const options: any = { outFormat: (oracledb as any).OUT_FORMAT_OBJECT, // OBJECT format maxRows: 10000, // XE 제한 고려 fetchArraySize: 100, autoCommit: false, // 🔥 수동으로 COMMIT 제어하도록 변경 }; console.log("Oracle 쿼리 실행:", { query: query.substring(0, 100) + "...", isDML, autoCommit: options.autoCommit, }); const result = await this.connection!.execute(query, params, options); const executionTime = Date.now() - startTime; console.log("Oracle 쿼리 실행 결과:", { query, rowCount: result.rows?.length || 0, rowsAffected: result.rowsAffected, metaData: result.metaData?.length || 0, executionTime: `${executionTime}ms`, actualRows: result.rows, metaDataInfo: result.metaData, autoCommit: options.autoCommit, }); return { rows: result.rows || [], rowCount: result.rowsAffected || result.rows?.length || 0, fields: this.extractFieldInfo(result.metaData || []), }; } catch (error: any) { console.error("Oracle 쿼리 실행 실패:", error); throw new Error(`쿼리 실행 실패: ${error.message}`); } } private extractFieldInfo(metaData: any[]): any[] { return metaData.map((field) => ({ name: field.name, type: this.mapOracleType(field.dbType), length: field.precision || field.byteSize, nullable: field.nullable, })); } private mapOracleType(oracleType: any): string { // Oracle XE 21c 타입 매핑 (간단한 방식) if (typeof oracleType === "string") { return oracleType; } return "UNKNOWN"; } async getTables(): Promise { try { // 현재 사용자 스키마의 테이블들만 조회 const query = ` SELECT table_name, USER as owner FROM user_tables ORDER BY table_name `; console.log("Oracle 테이블 조회 시작 - 사용자:", this.config.user); const result = await this.executeQuery(query); console.log("사용자 스키마 테이블 조회 결과:", result.rows); const tables = result.rows.map((row: any) => ({ table_name: row.TABLE_NAME, columns: [], description: null, })); console.log(`총 ${tables.length}개의 사용자 테이블을 찾았습니다.`); return tables; } catch (error: any) { console.error("Oracle 테이블 목록 조회 실패:", error); throw new Error(`테이블 목록 조회 실패: ${error.message}`); } } async getColumns(tableName: string): Promise { try { console.log(`[OracleConnector] getColumns 호출: tableName=${tableName}`); const query = ` SELECT column_name, data_type, data_length, data_precision, data_scale, nullable, data_default FROM user_tab_columns WHERE table_name = UPPER(:tableName) ORDER BY column_id `; console.log(`[OracleConnector] 쿼리 실행 시작: ${query}`); const result = await this.executeQuery(query, [tableName]); console.log(`[OracleConnector] 쿼리 결과:`, result.rows); console.log( `[OracleConnector] 결과 개수:`, result.rows ? result.rows.length : "null/undefined" ); const mappedResult = result.rows.map((row: any) => ({ column_name: row.COLUMN_NAME, data_type: this.formatOracleDataType(row), is_nullable: row.NULLABLE === "Y" ? "YES" : "NO", column_default: row.DATA_DEFAULT, })); console.log(`[OracleConnector] 매핑된 결과:`, mappedResult); return mappedResult; } catch (error: any) { console.error("[OracleConnector] getColumns 오류:", error); throw new Error(`테이블 컬럼 조회 실패: ${error.message}`); } } private formatOracleDataType(row: any): string { const { DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE } = row; switch (DATA_TYPE) { case "NUMBER": if (DATA_PRECISION && DATA_SCALE !== null) { return `NUMBER(${DATA_PRECISION},${DATA_SCALE})`; } else if (DATA_PRECISION) { return `NUMBER(${DATA_PRECISION})`; } return "NUMBER"; case "VARCHAR2": case "CHAR": return `${DATA_TYPE}(${DATA_LENGTH})`; default: return DATA_TYPE; } } }