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

273 lines
8.0 KiB
TypeScript
Raw Normal View History

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