236 lines
7.0 KiB
TypeScript
236 lines
7.0 KiB
TypeScript
// @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<void> {
|
|
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<void> {
|
|
if (this.connection) {
|
|
try {
|
|
await this.connection.close();
|
|
this.connection = null;
|
|
console.log('Oracle 연결 해제됨');
|
|
} catch (error: any) {
|
|
console.error('Oracle 연결 해제 실패:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
async testConnection(): Promise<ConnectionTestResult> {
|
|
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<QueryResult> {
|
|
if (!this.connection) {
|
|
await this.connect();
|
|
}
|
|
|
|
try {
|
|
const startTime = Date.now();
|
|
|
|
// Oracle XE 21c 쿼리 실행 옵션
|
|
const options: any = {
|
|
outFormat: oracledb.OUT_FORMAT_OBJECT, // OBJECT format
|
|
maxRows: 10000, // XE 제한 고려
|
|
fetchArraySize: 100
|
|
};
|
|
|
|
const result = await this.connection!.execute(query, params, options);
|
|
const executionTime = Date.now() - startTime;
|
|
|
|
console.log('Oracle 쿼리 실행 결과:', {
|
|
query,
|
|
rowCount: result.rows?.length || 0,
|
|
metaData: result.metaData?.length || 0,
|
|
executionTime: `${executionTime}ms`,
|
|
actualRows: result.rows,
|
|
metaDataInfo: result.metaData
|
|
});
|
|
|
|
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<TableInfo[]> {
|
|
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<any[]> {
|
|
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;
|
|
}
|
|
}
|
|
}
|