다른 명령
Oracle 테이블 분석기 설치 및 사용 가이드
프로그램 구성
파일 구조
OracleTableAnalyzer/ ├── oracle_table_analyzer.py # Python 백엔드 스크립트 ├── oracle_analyzer.au3 # AutoIt GUI 프로그램 ├── oracle_analyzer_config.ini # 설정 파일 (자동 생성) ├── requirements.txt # Python 라이브러리 의존성 └── README.md # 사용 설명서
2. 설치 및 설정
2.1 Python 환경 설정
필수 라이브러리 설치
a # cx_Oracle 라이브러리 설치 pip install cx_Oracle # 추가 라이브러리 설치 pip install pandas numpy openpyxl
requirements.txt 파일 생성
cx_Oracle>=8.3.0 pandas>=1.5.0 numpy>=1.21.0 openpyxl>=3.0.0
Oracle Instant Client 설치
1. [Oracle Instant Client 다운로드](https://www.oracle.com/database/technologies/instant-client/downloads.html) 2. 적절한 디렉토리에 압축 해제 3. 환경 변수 설정:
PATH에 Instant Client 경로 추가 예: C:\oracle\instantclient_21_8
2.2 AutoIt 설정
AutoIt 설치
1. [AutoIt 공식 사이트](https://www.autoitscript.com/)에서 AutoIt 다운로드 2. SciTE 에디터와 함께 설치 3. 필요한 Include 파일 확인:
- Json.au3 (JSON 처리용) - 기본 GUI 라이브러리들
JSON 처리 라이브러리 (Json.au3)
; Json.au3 파일을 AutoIt Include 디렉토리에 복사 ; 또는 다음 링크에서 다운로드: ; https://www.autoitscript.com/forum/topic/148114-a-non-strict-json-udf/
3. 프로그램 실행 방법
3.1 GUI 프로그램 실행
1. **AutoIt 스크립트 컴파일** (선택사항)
Aut2Exe.exe를 사용하여 .au3 파일을 .exe로 컴파일
2. **직접 실행**
oracle_analyzer.au3 파일을 더블클릭하여 실행
3.2 Python 스크립트 독립 실행
# 기본 실행
python oracle_table_analyzer.py "user/password@host:1521/service" "table_name"
# 옵션 포함 실행
python oracle_table_analyzer.py "hr/password@localhost:1521/ORCL" "employees" \
--owner HR \
--include-constraints \
--include-indexes \
--include-statistics \
--output json \
--save-to-file employees_analysis.json
4. 사용법 상세 가이드
4.1 GUI 프로그램 사용
연결 정보 입력
1. **사용자명**: Oracle 사용자 계정 2. **비밀번호**: 해당 계정의 비밀번호 3. **호스트**: Oracle 서버 주소 (기본값: localhost) 4. **포트**: Oracle 포트 번호 (기본값: 1521) 5. **서비스명**: Oracle 서비스명 또는 SID
== 테이블 분석 설정
1. **스키마**: 조회할 스키마명 (비워두면 현재 사용자) 2. **테이블명**: 분석할 테이블명 (필수) 3. **포함 정보**:
- 제약조건: Primary Key, Foreign Key, Check 제약조건 - 인덱스: 테이블의 모든 인덱스 정보 - 통계정보: 테이블 통계 (행 수, 블록 수 등)
== 실행 순서
1. 연결 정보 입력 2. "연결 테스트" 클릭하여 DB 연결 확인 3. 테이블명 및 옵션 설정 4. "테이블 분석 실행" 클릭 5. 결과를 탭별로 확인 6. 필요시 CSV/JSON으로 내보내기
4.2 Python 명령행 사용
== 기본 문법
python oracle_table_analyzer.py [연결문자열] [테이블명] [옵션들]
== 주요 옵션들
- `--owner SCHEMA`: 스키마명 지정 - `--output {json,csv,table}`: 출력 형식 - `--include-constraints`: 제약조건 포함 - `--include-indexes`: 인덱스 정보 포함 - `--include-statistics`: 통계정보 포함 - `--save-to-file FILE`: 결과를 파일로 저장
== 실행 예제들
# 1. 기본 컬럼 정보만 조회
python oracle_table_analyzer.py "hr/password@localhost:1521/ORCL" "employees"
# 2. 모든 정보 포함하여 JSON으로 저장
python oracle_table_analyzer.py "hr/password@localhost:1521/ORCL" "employees" \
--include-constraints --include-indexes --include-statistics \
--output json --save-to-file employees_full.json
# 3. 다른 스키마의 테이블 분석
python oracle_table_analyzer.py "system/password@localhost:1521/ORCL" "employees" \
--owner HR --output csv
# 4. 테이블 형식으로 출력 (AutoIt에서 파싱하기 쉬운 형태)
python oracle_table_analyzer.py "hr/password@localhost:1521/ORCL" "departments" \
--output table
5. 실무 활용 시나리오
5.1 일상적인 테이블 구조 확인
# 새로 추가된 테이블의 구조 빠른 확인
python oracle_table_analyzer.py "dba/password@prod:1521/PROD" "new_customer_table" \
--include-constraints --output table
5.2 데이터 모델링 문서 작성용
# 전체 스키마의 주요 테이블들 문서화
for table in employees departments locations jobs; do
python oracle_table_analyzer.py "hr/password@localhost:1521/ORCL" "$table" \
--include-constraints --include-indexes --include-statistics \
--save-to-file "${table}_structure.json"
done
5.3 성능 분석용 통계 수집
# 큰 테이블들의 통계 정보 확인
python oracle_table_analyzer.py "dba/password@prod:1521/PROD" "large_transaction_table" \
--include-statistics --output json
5.4 마이그레이션 준비
# 마이그레이션 대상 테이블들의 구조 백업
python oracle_table_analyzer.py "old_system/password@old_db:1521/OLDSYS" "customer_master" \
--include-constraints --include-indexes \
--save-to-file migration_customer_structure.json
6. 고급 활용법
6.1 배치 스크립트 작성
== Windows 배치 파일 (analyze_tables.bat)
</source>batch @echo off set DB_CONN=hr/password@localhost:1521/ORCL set PYTHON_SCRIPT=oracle_table_analyzer.py
echo 테이블 구조 분석 시작...
for %%T in (employees departments locations jobs job_history) do (
echo 분석 중: %%T python %PYTHON_SCRIPT% "%DB_CONN%" "%%T" --include-constraints --include-indexes --save-to-file "%%T_structure.json"
)
echo 모든 테이블 분석 완료! pause </source>
== Linux/Unix 쉘 스크립트 (analyze_tables.sh)
#!/bin/bash
DB_CONN="hr/password@localhost:1521/ORCL"
PYTHON_SCRIPT="oracle_table_analyzer.py"
OUTPUT_DIR="./table_structures"
mkdir -p $OUTPUT_DIR
tables=("employees" "departments" "locations" "jobs" "job_history")
for table in "${tables[@]}"; do
echo "분석 중: $table"
python $PYTHON_SCRIPT "$DB_CONN" "$table" \
--include-constraints \
--include-indexes \
--include-statistics \
--save-to-file "$OUTPUT_DIR/${table}_structure.json"
done
echo "모든 테이블 분석 완료!"
6.2 AutoIt 자동화 스크립트
== 여러 테이블 자동 분석
</source>autoit
- 여러 테이블을 순차적으로 분석하는 함수
Func AnalyzeMultipleTables()
Local $aTables = StringSplit("EMPLOYEES,DEPARTMENTS,LOCATIONS,JOBS", ",")
For $i = 1 To $aTables[0]
GUICtrlSetData($idTableName, $aTables[$i])
AnalyzeTable()
Sleep(2000) ; 2초 대기
; 결과를 개별 파일로 저장
ExportToJSON(@ScriptDir & "\" & $aTables[$i] & "_structure.json")
Next
EndFunc </source>
7. 문제 해결 가이드
7.1 자주 발생하는 오류들
== cx_Oracle 설치 문제
</source> 오류: ModuleNotFoundError: No module named 'cx_Oracle' 해결: pip install cx_Oracle </source>
== Oracle Client 문제
</source> 오류: DPI-1047: Cannot locate a 64-bit Oracle Client library 해결: Oracle Instant Client 설치 및 PATH 환경변수 설정 </source>
== 인코딩 문제
</source> 오류: UnicodeDecodeError 해결: 환경변수 설정 set NLS_LANG=KOREAN_KOREA.AL32UTF8 </source>
== 권한 문제
</source> 오류: ORA-00942: table or view does not exist 해결: 적절한 권한 부여 GRANT SELECT ON dba_tab_columns TO username; GRANT SELECT ON dba_constraints TO username; </source>
7.2 성능 최적화
== 대용량 테이블 처리
</source>python
- 큰 테이블의 경우 통계 정보만 조회
python oracle_table_analyzer.py "conn_string" "large_table" \
--include-statistics --output json
</source>
== 네트워크 최적화
</source>python
- 로컬 네트워크가 아닌 경우 필요한 정보만 조회
python oracle_table_analyzer.py "conn_string" "remote_table" \
--output csv # JSON보다 가벼움
</source>
8. 확장 및 커스터마이징
8.1 추가 기능 구현 아이디어
1. **DDL 생성 기능** 2. **테이블 비교 기능** 3. **데이터 프로파일링** 4. **성능 메트릭 수집** 5. **웹 인터페이스 제공**
8.2 설정 파일 커스터마이징
== oracle_analyzer_config.ini 예제
[Connection] User=hr Host=localhost Port=1521 Service=ORCL [Settings] PythonPath=C:\Python39\python.exe DefaultTimeout=30 MaxResults=1000 [Display] FontName=Consolas FontSize=10 GridLines=1
파이썬 프로그램 (오라클 컬럼 정보 조회)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Oracle 테이블 컬럼 정보 조회 프로그램
AutoIt에서 호출되어 테이블 구조를 분석하고 결과를 반환
"""
import cx_Oracle
import sys
import json
import csv
import argparse
from datetime import datetime
from typing import List, Dict, Optional
import os
class OracleTableAnalyzer:
"""Oracle 테이블 구조 분석 클래스"""
def __init__(self, connection_string: str):
"""
초기화
Args:
connection_string: Oracle 연결 문자열
"""
self.connection_string = connection_string
self.connection = None
self.cursor = None
def connect(self):
"""Oracle 데이터베이스 연결"""
try:
self.connection = cx_Oracle.connect(self.connection_string)
self.cursor = self.connection.cursor()
return True
except cx_Oracle.Error as e:
print(f"ERROR|연결 실패: {str(e)}")
return False
def disconnect(self):
"""데이터베이스 연결 해제"""
if self.cursor:
self.cursor.close()
if self.connection:
self.connection.close()
def get_table_columns(self, table_name: str, owner: str = None) -> List[Dict]:
"""
테이블 컬럼 정보 조회
Args:
table_name: 테이블명
owner: 스키마명 (None이면 현재 사용자)
Returns:
컬럼 정보 리스트
"""
try:
if owner:
# 다른 스키마의 테이블 조회
sql = """
SELECT
column_name,
data_type,
data_length,
data_precision,
data_scale,
nullable,
data_default,
column_id,
hidden_column,
virtual_column,
char_length
FROM all_tab_columns
WHERE table_name = UPPER(:table_name)
AND owner = UPPER(:owner)
ORDER BY column_id
"""
params = {'table_name': table_name, 'owner': owner}
else:
# 현재 사용자 스키마의 테이블 조회
sql = """
SELECT
column_name,
data_type,
data_length,
data_precision,
data_scale,
nullable,
data_default,
column_id,
hidden_column,
virtual_column,
char_length
FROM user_tab_columns
WHERE table_name = UPPER(:table_name)
ORDER BY column_id
"""
params = {'table_name': table_name}
self.cursor.execute(sql, params)
rows = self.cursor.fetchall()
if not rows:
return []
columns = []
for row in rows:
column_info = {
'column_name': row[0],
'data_type': row[1],
'data_length': row[2],
'data_precision': row[3],
'data_scale': row[4],
'nullable': row[5],
'data_default': row[6].read() if row[6] else None,
'column_id': row[7],
'hidden_column': row[8],
'virtual_column': row[9],
'char_length': row[10] if len(row) > 10 else None
}
# 데이터 타입 포맷팅
column_info['formatted_type'] = self._format_data_type(column_info)
columns.append(column_info)
return columns
except cx_Oracle.Error as e:
print(f"ERROR|쿼리 실행 실패: {str(e)}")
return []
def get_table_constraints(self, table_name: str, owner: str = None) -> List[Dict]:
"""테이블 제약조건 정보 조회"""
try:
if owner:
sql = """
SELECT
c.constraint_name,
c.constraint_type,
c.status,
cc.column_name,
cc.position,
c.r_constraint_name,
rc.table_name as ref_table,
rcc.column_name as ref_column
FROM all_constraints c
LEFT JOIN all_cons_columns cc ON c.constraint_name = cc.constraint_name
AND c.owner = cc.owner
LEFT JOIN all_constraints rc ON c.r_constraint_name = rc.constraint_name
LEFT JOIN all_cons_columns rcc ON rc.constraint_name = rcc.constraint_name
AND rc.owner = rcc.owner
WHERE c.table_name = UPPER(:table_name)
AND c.owner = UPPER(:owner)
ORDER BY c.constraint_type, c.constraint_name, cc.position
"""
params = {'table_name': table_name, 'owner': owner}
else:
sql = """
SELECT
c.constraint_name,
c.constraint_type,
c.status,
cc.column_name,
cc.position,
c.r_constraint_name,
rc.table_name as ref_table,
rcc.column_name as ref_column
FROM user_constraints c
LEFT JOIN user_cons_columns cc ON c.constraint_name = cc.constraint_name
LEFT JOIN user_constraints rc ON c.r_constraint_name = rc.constraint_name
LEFT JOIN user_cons_columns rcc ON rc.constraint_name = rcc.constraint_name
WHERE c.table_name = UPPER(:table_name)
ORDER BY c.constraint_type, c.constraint_name, cc.position
"""
params = {'table_name': table_name}
self.cursor.execute(sql, params)
rows = self.cursor.fetchall()
constraints = []
for row in rows:
constraint_info = {
'constraint_name': row[0],
'constraint_type': row[1],
'constraint_type_desc': self._get_constraint_type_desc(row[1]),
'status': row[2],
'column_name': row[3],
'position': row[4],
'ref_constraint': row[5],
'ref_table': row[6],
'ref_column': row[7]
}
constraints.append(constraint_info)
return constraints
except cx_Oracle.Error as e:
print(f"ERROR|제약조건 조회 실패: {str(e)}")
return []
def get_table_indexes(self, table_name: str, owner: str = None) -> List[Dict]:
"""테이블 인덱스 정보 조회"""
try:
if owner:
sql = """
SELECT
i.index_name,
i.index_type,
i.uniqueness,
i.status,
ic.column_name,
ic.column_position,
ic.descend
FROM all_indexes i
JOIN all_ind_columns ic ON i.index_name = ic.index_name
AND i.owner = ic.index_owner
WHERE i.table_name = UPPER(:table_name)
AND i.owner = UPPER(:owner)
ORDER BY i.index_name, ic.column_position
"""
params = {'table_name': table_name, 'owner': owner}
else:
sql = """
SELECT
i.index_name,
i.index_type,
i.uniqueness,
i.status,
ic.column_name,
ic.column_position,
ic.descend
FROM user_indexes i
JOIN user_ind_columns ic ON i.index_name = ic.index_name
WHERE i.table_name = UPPER(:table_name)
ORDER BY i.index_name, ic.column_position
"""
params = {'table_name': table_name}
self.cursor.execute(sql, params)
rows = self.cursor.fetchall()
indexes = []
for row in rows:
index_info = {
'index_name': row[0],
'index_type': row[1],
'uniqueness': row[2],
'status': row[3],
'column_name': row[4],
'column_position': row[5],
'descend': row[6]
}
indexes.append(index_info)
return indexes
except cx_Oracle.Error as e:
print(f"ERROR|인덱스 조회 실패: {str(e)}")
return []
def get_table_statistics(self, table_name: str, owner: str = None) -> Dict:
"""테이블 통계 정보 조회"""
try:
if owner:
sql = """
SELECT
num_rows,
blocks,
empty_blocks,
avg_space,
chain_cnt,
avg_row_len,
sample_size,
last_analyzed,
table_name
FROM all_tables
WHERE table_name = UPPER(:table_name)
AND owner = UPPER(:owner)
"""
params = {'table_name': table_name, 'owner': owner}
else:
sql = """
SELECT
num_rows,
blocks,
empty_blocks,
avg_space,
chain_cnt,
avg_row_len,
sample_size,
last_analyzed,
table_name
FROM user_tables
WHERE table_name = UPPER(:table_name)
"""
params = {'table_name': table_name}
self.cursor.execute(sql, params)
row = self.cursor.fetchone()
if row:
return {
'num_rows': row[0],
'blocks': row[1],
'empty_blocks': row[2],
'avg_space': row[3],
'chain_cnt': row[4],
'avg_row_len': row[5],
'sample_size': row[6],
'last_analyzed': row[7].strftime('%Y-%m-%d %H:%M:%S') if row[7] else None,
'table_name': row[8]
}
else:
return {}
except cx_Oracle.Error as e:
print(f"ERROR|통계 정보 조회 실패: {str(e)}")
return {}
def _format_data_type(self, column_info: Dict) -> str:
"""데이터 타입 포맷팅"""
data_type = column_info['data_type']
if data_type in ['VARCHAR2', 'CHAR', 'NVARCHAR2', 'NCHAR']:
if column_info['char_length']:
return f"{data_type}({column_info['char_length']})"
else:
return f"{data_type}({column_info['data_length']})"
elif data_type == 'NUMBER':
if column_info['data_precision'] and column_info['data_scale'] is not None:
return f"NUMBER({column_info['data_precision']},{column_info['data_scale']})"
elif column_info['data_precision']:
return f"NUMBER({column_info['data_precision']})"
else:
return "NUMBER"
elif data_type in ['RAW', 'LONG RAW']:
return f"{data_type}({column_info['data_length']})"
else:
return data_type
def _get_constraint_type_desc(self, constraint_type: str) -> str:
"""제약조건 타입 설명"""
type_map = {
'P': 'Primary Key',
'U': 'Unique',
'R': 'Foreign Key',
'C': 'Check',
'V': 'View Check',
'O': 'Read Only'
}
return type_map.get(constraint_type, constraint_type)
def main():
"""메인 함수"""
parser = argparse.ArgumentParser(description='Oracle 테이블 컬럼 정보 조회')
parser.add_argument('connection_string', help='Oracle 연결 문자열 (user/password@host:port/service)')
parser.add_argument('table_name', help='조회할 테이블명')
parser.add_argument('--owner', help='스키마명 (선택사항)')
parser.add_argument('--output', choices=['json', 'csv', 'table'], default='json', help='출력 형식')
parser.add_argument('--include-constraints', action='store_true', help='제약조건 정보 포함')
parser.add_argument('--include-indexes', action='store_true', help='인덱스 정보 포함')
parser.add_argument('--include-statistics', action='store_true', help='테이블 통계 포함')
parser.add_argument('--save-to-file', help='결과를 파일로 저장')
args = parser.parse_args()
try:
# Oracle 연결
analyzer = OracleTableAnalyzer(args.connection_string)
if not analyzer.connect():
sys.exit(1)
# 테이블 컬럼 정보 조회
columns = analyzer.get_table_columns(args.table_name, args.owner)
if not columns:
print(f"ERROR|테이블 '{args.table_name}'을 찾을 수 없습니다.")
sys.exit(1)
result = {
'table_name': args.table_name.upper(),
'owner': args.owner.upper() if args.owner else None,
'columns': columns,
'query_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
# 추가 정보 조회
if args.include_constraints:
result['constraints'] = analyzer.get_table_constraints(args.table_name, args.owner)
if args.include_indexes:
result['indexes'] = analyzer.get_table_indexes(args.table_name, args.owner)
if args.include_statistics:
result['statistics'] = analyzer.get_table_statistics(args.table_name, args.owner)
# 결과 출력
if args.output == 'json':
output = json.dumps(result, indent=2, ensure_ascii=False, default=str)
print(f"SUCCESS|{output}")
elif args.output == 'csv':
output_lines = []
output_lines.append("COLUMN_NAME,DATA_TYPE,NULLABLE,DEFAULT_VALUE,COLUMN_ID")
for col in columns:
line = f"{col['column_name']},{col['formatted_type']},{col['nullable']},{col['data_default'] or ''},{col['column_id']}"
output_lines.append(line)
print(f"SUCCESS|{chr(10).join(output_lines)}")
elif args.output == 'table':
# 테이블 형태 출력 (AutoIt ListView용)
output_lines = []
output_lines.append("COLUMN_NAME|DATA_TYPE|NULLABLE|DEFAULT|POSITION")
for col in columns:
line = f"{col['column_name']}|{col['formatted_type']}|{col['nullable']}|{col['data_default'] or ''}|{col['column_id']}"
output_lines.append(line)
print(f"SUCCESS|{chr(10).join(output_lines)}")
# 파일 저장
if args.save_to_file:
with open(args.save_to_file, 'w', encoding='utf-8') as f:
if args.output == 'json':
json.dump(result, f, indent=2, ensure_ascii=False, default=str)
else:
f.write(output)
print(f"INFO|결과를 {args.save_to_file}에 저장했습니다.")
except Exception as e:
print(f"ERROR|예기치 않은 오류: {str(e)}")
sys.exit(1)
finally:
if 'analyzer' in locals():
analyzer.disconnect()
if __name__ == "__main__":
main()
autoit - 파이썬 호출용 GUI화면
#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Icon=oracle.ico
#AutoIt3Wrapper_Res_Comment=Oracle Table Analyzer
#AutoIt3Wrapper_Res_Description=Oracle 테이블 구조 분석 도구
#AutoIt3Wrapper_Res_Fileversion=1.0.0.0
#AutoIt3Wrapper_Res_ProductVersion=1.0.0.0
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
#include <ButtonConstants.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <ListViewConstants.au3>
#include <StaticConstants.au3>
#include <TabConstants.au3>
#include <WindowsConstants.au3>
#include <GuiListView.au3>
#include <File.au3>
#include <Array.au3>
#include <Json.au3>
; ================================================================
; Oracle 테이블 분석기 - AutoIt GUI 프로그램
; ================================================================
; 전역 변수
Global $hMainGUI, $hStatusBar
Global $g_sConnectionString = ""
Global $g_sPythonPath = "python.exe" ; Python 실행 파일 경로
Global $g_sScriptPath = @ScriptDir & "\oracle_table_analyzer.py" ; Python 스크립트 경로
; GUI 컨트롤 변수들
Global $idUser, $idPassword, $idHost, $idPort, $idService, $idOwner, $idTableName
Global $idBtnConnect, $idBtnAnalyze, $idBtnExportCSV, $idBtnExportJSON, $idBtnClear
Global $idChkConstraints, $idChkIndexes, $idChkStatistics
Global $idTabMain, $idTabColumns, $idTabConstraints, $idTabIndexes, $idTabStatistics
Global $idListColumns, $idListConstraints, $idListIndexes, $idEditStatistics
Global $idProgressBar
; 메인 함수
Main()
Func Main()
; GUI 생성
CreateMainGUI()
; 설정 파일 로드
LoadConfig()
; GUI 표시
GUISetState(@SW_SHOW, $hMainGUI)
; 메인 루프
While 1
Local $nMsg = GUIGetMsg()
Switch $nMsg
Case $GUI_EVENT_CLOSE
ExitProgram()
Case $idBtnConnect
TestConnection()
Case $idBtnAnalyze
AnalyzeTable()
Case $idBtnExportCSV
ExportToCSV()
Case $idBtnExportJSON
ExportToJSON()
Case $idBtnClear
ClearResults()
EndSwitch
WEnd
EndFunc
Func CreateMainGUI()
; 메인 윈도우 생성
$hMainGUI = GUICreate("Oracle 테이블 분석기 v1.0", 1000, 700, -1, -1, $WS_OVERLAPPEDWINDOW)
; 연결 정보 그룹
GUICtrlCreateGroup("데이터베이스 연결 정보", 10, 10, 480, 150)
GUICtrlCreateLabel("사용자명:", 20, 35, 60, 20)
$idUser = GUICtrlCreateInput("", 85, 32, 100, 20)
GUICtrlCreateLabel("비밀번호:", 200, 35, 60, 20)
$idPassword = GUICtrlCreateInput("", 265, 32, 100, 20, $ES_PASSWORD)
GUICtrlCreateLabel("호스트:", 380, 35, 40, 20)
$idHost = GUICtrlCreateInput("localhost", 425, 32, 60, 20)
GUICtrlCreateLabel("포트:", 20, 65, 30, 20)
$idPort = GUICtrlCreateInput("1521", 55, 62, 50, 20)
GUICtrlCreateLabel("서비스명:", 120, 65, 60, 20)
$idService = GUICtrlCreateInput("ORCL", 180, 62, 80, 20)
$idBtnConnect = GUICtrlCreateButton("연결 테스트", 280, 62, 80, 25)
GUICtrlCreateLabel("스키마:", 20, 95, 50, 20)
$idOwner = GUICtrlCreateInput("", 75, 92, 100, 20)
GUICtrlCreateLabel("테이블명:", 190, 95, 60, 20)
$idTableName = GUICtrlCreateInput("", 255, 92, 120, 20)
; 옵션 체크박스
GUICtrlCreateLabel("포함 정보:", 20, 125, 60, 20)
$idChkConstraints = GUICtrlCreateCheckbox("제약조건", 85, 122, 70, 20)
$idChkIndexes = GUICtrlCreateCheckbox("인덱스", 160, 122, 60, 20)
$idChkStatistics = GUICtrlCreateCheckbox("통계정보", 225, 122, 70, 20)
GUICtrlCreateGroup("", -99, -99, 1, 1) ; 그룹 종료
; 실행 버튼들
GUICtrlCreateGroup("작업", 500, 10, 480, 150)
$idBtnAnalyze = GUICtrlCreateButton("테이블 분석 실행", 520, 40, 120, 35)
GUICtrlSetFont($idBtnAnalyze, 10, 600)
$idBtnExportCSV = GUICtrlCreateButton("CSV 내보내기", 660, 40, 100, 25)
$idBtnExportJSON = GUICtrlCreateButton("JSON 내보내기", 770, 40, 100, 25)
$idBtnClear = GUICtrlCreateButton("결과 지우기", 880, 40, 80, 25)
; 진행률 표시
GUICtrlCreateLabel("진행 상황:", 520, 85, 60, 20)
$idProgressBar = GUICtrlCreateProgress(585, 82, 300, 20)
; 상태 정보
GUICtrlCreateLabel("상태:", 520, 115, 30, 20)
$hStatusBar = GUICtrlCreateLabel("준비", 555, 115, 400, 20)
GUICtrlSetColor($hStatusBar, 0x0000FF)
GUICtrlCreateGroup("", -99, -99, 1, 1) ; 그룹 종료
; 탭 컨트롤 생성
$idTabMain = GUICtrlCreateTab(10, 170, 980, 500)
; 컬럼 정보 탭
$idTabColumns = GUICtrlCreateTabItem("컬럼 정보")
$idListColumns = GUICtrlCreateListView("순서|컬럼명|데이터타입|Null허용|기본값|가상컬럼|숨김컬럼", 20, 200, 960, 450, $LVS_REPORT + $LVS_GRIDLINES + $LVS_FULLROWSELECT)
_GUICtrlListView_SetExtendedListViewStyle($idListColumns, $LVS_EX_GRIDLINES + $LVS_EX_FULLROWSELECT + $LVS_EX_SUBITEMIMAGES)
; 컬럼 너비 설정
_GUICtrlListView_SetColumnWidth($idListColumns, 0, 50) ; 순서
_GUICtrlListView_SetColumnWidth($idListColumns, 1, 150) ; 컬럼명
_GUICtrlListView_SetColumnWidth($idListColumns, 2, 120) ; 데이터타입
_GUICtrlListView_SetColumnWidth($idListColumns, 3, 80) ; Null허용
_GUICtrlListView_SetColumnWidth($idListColumns, 4, 150) ; 기본값
_GUICtrlListView_SetColumnWidth($idListColumns, 5, 80) ; 가상컬럼
_GUICtrlListView_SetColumnWidth($idListColumns, 6, 80) ; 숨김컬럼
; 제약조건 탭
$idTabConstraints = GUICtrlCreateTabItem("제약조건")
$idListConstraints = GUICtrlCreateListView("제약조건명|타입|상태|컬럼명|참조테이블|참조컬럼", 20, 200, 960, 450, $LVS_REPORT + $LVS_GRIDLINES + $LVS_FULLROWSELECT)
_GUICtrlListView_SetExtendedListViewStyle($idListConstraints, $LVS_EX_GRIDLINES + $LVS_EX_FULLROWSELECT)
_GUICtrlListView_SetColumnWidth($idListConstraints, 0, 200) ; 제약조건명
_GUICtrlListView_SetColumnWidth($idListConstraints, 1, 120) ; 타입
_GUICtrlListView_SetColumnWidth($idListConstraints, 2, 80) ; 상태
_GUICtrlListView_SetColumnWidth($idListConstraints, 3, 150) ; 컬럼명
_GUICtrlListView_SetColumnWidth($idListConstraints, 4, 150) ; 참조테이블
_GUICtrlListView_SetColumnWidth($idListConstraints, 5, 150) ; 참조컬럼
; 인덱스 탭
$idTabIndexes = GUICtrlCreateTabItem("인덱스")
$idListIndexes = GUICtrlCreateListView("인덱스명|타입|유일성|상태|컬럼명|순서|정렬", 20, 200, 960, 450, $LVS_REPORT + $LVS_GRIDLINES + $LVS_FULLROWSELECT)
_GUICtrlListView_SetExtendedListViewStyle($idListIndexes, $LVS_EX_GRIDLINES + $LVS_EX_FULLROWSELECT)
_GUICtrlListView_SetColumnWidth($idListIndexes, 0, 200) ; 인덱스명
_GUICtrlListView_SetColumnWidth($idListIndexes, 1, 100) ; 타입
_GUICtrlListView_SetColumnWidth($idListIndexes, 2, 80) ; 유일성
_GUICtrlListView_SetColumnWidth($idListIndexes, 3, 80) ; 상태
_GUICtrlListView_SetColumnWidth($idListIndexes, 4, 150) ; 컬럼명
_GUICtrlListView_SetColumnWidth($idListIndexes, 5, 60) ; 순서
_GUICtrlListView_SetColumnWidth($idListIndexes, 6, 60) ; 정렬
; 통계정보 탭
$idTabStatistics = GUICtrlCreateTabItem("통계정보")
$idEditStatistics = GUICtrlCreateEdit("", 20, 200, 960, 450, $ES_MULTILINE + $ES_READONLY + $WS_VSCROLL)
GUICtrlSetFont($idEditStatistics, 10, 400, 0, "Consolas")
GUICtrlCreateTabItem("") ; 탭 종료
EndFunc
Func TestConnection()
UpdateStatus("데이터베이스 연결을 테스트하고 있습니다...")
GUICtrlSetData($idProgressBar, 30)
Local $sConnectionString = BuildConnectionString()
; 간단한 연결 테스트를 위한 Python 스크립트 실행
Local $sCommand = '"' & $g_sPythonPath & '" -c "' & _
'import cx_Oracle; ' & _
'conn = cx_Oracle.connect(""' & $sConnectionString & '""); ' & _
'print(""SUCCESS""); ' & _
'conn.close()"'
Local $iPID = Run($sCommand, @WorkingDir, @SW_HIDE, $STDIN_CHILD + $STDOUT_CHILD + $STDERR_CHILD)
; 실행 결과 읽기
Local $sOutput = ""
While ProcessExists($iPID)
$sOutput &= StdoutRead($iPID)
Sleep(100)
WEnd
$sOutput &= StdoutRead($iPID)
GUICtrlSetData($idProgressBar, 100)
If StringInStr($sOutput, "SUCCESS") Then
UpdateStatus("데이터베이스 연결 성공!")
GUICtrlSetColor($hStatusBar, 0x008000) ; 녹색
$g_sConnectionString = $sConnectionString
; 설정 저장
SaveConfig()
Else
UpdateStatus("데이터베이스 연결 실패: " & StringStripWS($sOutput, 3))
GUICtrlSetColor($hStatusBar, 0xFF0000) ; 빨간색
EndIf
Sleep(2000)
GUICtrlSetData($idProgressBar, 0)
EndFunc
Func AnalyzeTable()
Local $sTableName = GUICtrlRead($idTableName)
Local $sOwner = GUICtrlRead($idOwner)
If $sTableName = "" Then
MsgBox(64, "알림", "테이블명을 입력해주세요.")
Return
EndIf
If $g_sConnectionString = "" Then
MsgBox(64, "알림", "먼저 데이터베이스 연결을 확인해주세요.")
Return
EndIf
UpdateStatus("테이블 '" & $sTableName & "' 분석 중...")
GUICtrlSetData($idProgressBar, 20)
; Python 스크립트 실행 명령 구성
Local $sCommand = '"' & $g_sPythonPath & '" "' & $g_sScriptPath & '" "' & $g_sConnectionString & '" "' & $sTableName & '"'
; 옵션 추가
If $sOwner <> "" Then
$sCommand &= " --owner " & $sOwner
EndIf
If GUICtrlRead($idChkConstraints) = 1 Then
$sCommand &= " --include-constraints"
EndIf
If GUICtrlRead($idChkIndexes) = 1 Then
$sCommand &= " --include-indexes"
EndIf
If GUICtrlRead($idChkStatistics) = 1 Then
$sCommand &= " --include-statistics"
EndIf
$sCommand &= " --output json"
GUICtrlSetData($idProgressBar, 50)
; Python 스크립트 실행
Local $iPID = Run($sCommand, @WorkingDir, @SW_HIDE, $STDIN_CHILD + $STDOUT_CHILD + $STDERR_CHILD)
Local $sOutput = ""
While ProcessExists($iPID)
$sOutput &= StdoutRead($iPID)
Sleep(100)
WEnd
$sOutput &= StdoutRead($iPID)
GUICtrlSetData($idProgressBar, 80)
; 결과 처리
If StringLeft($sOutput, 7) = "SUCCESS" Then
Local $sJsonData = StringTrimLeft($sOutput, 8) ; "SUCCESS|" 제거
ProcessResults($sJsonData)
UpdateStatus("테이블 분석 완료!")
GUICtrlSetColor($hStatusBar, 0x008000)
ElseIf StringLeft($sOutput, 5) = "ERROR" Then
Local $sError = StringTrimLeft($sOutput, 6) ; "ERROR|" 제거
UpdateStatus("오류: " & $sError)
GUICtrlSetColor($hStatusBar, 0xFF0000)
MsgBox(16, "오류", $sError)
Else
UpdateStatus("알 수 없는 오류가 발생했습니다.")
GUICtrlSetColor($hStatusBar, 0xFF0000)
MsgBox(16, "오류", "Python 스크립트 실행 결과를 해석할 수 없습니다:" & @CRLF & $sOutput)
EndIf
GUICtrlSetData($idProgressBar, 100)
Sleep(1000)
GUICtrlSetData($idProgressBar, 0)
EndFunc
Func ProcessResults($sJsonData)
; JSON 파싱 (간단한 방법 - 실제로는 JSON 라이브러리 사용 권장)
Local $oJson
; JSON 파싱 시도
Try
$oJson = Json_Decode($sJsonData)
Catch
; JSON 파싱 실패 시 수동 파싱
MsgBox(16, "오류", "JSON 결과를 파싱할 수 없습니다.")
Return
EndTry
; 컬럼 정보 표시
DisplayColumns($sJsonData)
; 제약조건 정보 표시 (옵션이 체크되어 있고 데이터가 있는 경우)
If GUICtrlRead($idChkConstraints) = 1 Then
DisplayConstraints($sJsonData)
EndIf
; 인덱스 정보 표시
If GUICtrlRead($idChkIndexes) = 1 Then
DisplayIndexes($sJsonData)
EndIf
; 통계정보 표시
If GUICtrlRead($idChkStatistics) = 1 Then
DisplayStatistics($sJsonData)
EndIf
EndFunc
Func DisplayColumns($sJsonData)
; 기존 데이터 지우기
_GUICtrlListView_DeleteAllItems($idListColumns)
; JSON에서 컬럼 정보 추출 (간단한 문자열 파싱 방법)
Local $aColumns = StringRegExp($sJsonData, '"column_name":\s*"([^"]+)"', 3)
Local $aDataTypes = StringRegExp($sJsonData, '"formatted_type":\s*"([^"]+)"', 3)
Local $aNullable = StringRegExp($sJsonData, '"nullable":\s*"([^"]+)"', 3)
Local $aDefaults = StringRegExp($sJsonData, '"data_default":\s*"?([^",}]+)"?', 3)
Local $aColumnIds = StringRegExp($sJsonData, '"column_id":\s*([0-9]+)', 3)
; ListView에 데이터 추가
For $i = 0 To UBound($aColumns) - 1
Local $sDefault = ""
If $i < UBound($aDefaults) Then $sDefault = $aDefaults[$i]
Local $sNullable = ""
If $i < UBound($aNullable) Then $sNullable = $aNullable[$i]
Local $sColumnId = ""
If $i < UBound($aColumnIds) Then $sColumnId = $aColumnIds[$i]
Local $sDataType = ""
If $i < UBound($aDataTypes) Then $sDataType = $aDataTypes[$i]
_GUICtrlListView_AddItem($idListColumns, $sColumnId & "|" & $aColumns[$i] & "|" & $sDataType & "|" & $sNullable & "|" & $sDefault & "| | ")
Next
; 컬럼 탭을 활성화
GUICtrlSetState($idTabColumns, $GUI_SHOW)
EndFunc
Func DisplayConstraints($sJsonData)
_GUICtrlListView_DeleteAllItems($idListConstraints)
; 제약조건 정보 파싱 및 표시 (간단한 구현)
; 실제로는 더 정교한 JSON 파싱이 필요
Local $aConstraints = StringRegExp($sJsonData, '"constraint_name":\s*"([^"]+)"', 3)
If UBound($aConstraints) > 0 Then
For $i = 0 To UBound($aConstraints) - 1
_GUICtrlListView_AddItem($idListConstraints, $aConstraints[$i] & "|||||")
Next
EndIf
EndFunc
Func DisplayIndexes($sJsonData)
_GUICtrlListView_DeleteAllItems($idListIndexes)
; 인덱스 정보 파싱 및 표시
Local $aIndexes = StringRegExp($sJsonData, '"index_name":\s*"([^"]+)"', 3)
If UBound($aIndexes) > 0 Then
For $i = 0 To UBound($aIndexes) - 1
_GUICtrlListView_AddItem($idListIndexes, $aIndexes[$i] & "||||||")
Next
EndIf
EndFunc
Func DisplayStatistics($sJsonData)
; 통계정보를 텍스트 형태로 표시
Local $sStats = "테이블 통계 정보:" & @CRLF & @CRLF
; JSON에서 통계 정보 추출
Local $aStats = StringRegExp($sJsonData, '"statistics":\s*{([^}]+)}', 3)
If UBound($aStats) > 0 Then
$sStats &= StringReplace($aStats[0], '","', @CRLF)
$sStats = StringReplace($sStats, '"', "")
$sStats = StringReplace($sStats, ":", ": ")
Else
$sStats &= "통계 정보가 없습니다."
EndIf
GUICtrlSetData($idEditStatistics, $sStats)
EndFunc
Func ExportToCSV()
Local $sFileName = FileSaveDialog("CSV로 저장", @DesktopDir, "CSV 파일 (*.csv)", $FD_PATHMUSTEXIST)
If $sFileName <> "" Then
; 현재 표시된 컬럼 정보를 CSV로 저장
Local $sCSV = "순서,컬럼명,데이터타입,Null허용,기본값" & @CRLF
Local $iItemCount = _GUICtrlListView_GetItemCount($idListColumns)
For $i = 0 To $iItemCount - 1
Local $sLine = ""
For $j = 0 To 4 ; 처음 5개 컬럼만
If $j > 0 Then $sLine &= ","
$sLine &= '"' & _GUICtrlListView_GetItemText($idListColumns, $i, $j) & '"'
Next
$sCSV &= $sLine & @CRLF
Next
FileWrite($sFileName, $sCSV)
UpdateStatus("CSV 파일로 저장 완료: " & $sFileName)
EndIf
EndFunc
Func ExportToJSON()
Local $sFileName = FileSaveDialog("JSON으로 저장", @DesktopDir, "JSON 파일 (*.json)", $FD_PATHMUSTEXIST)
If $sFileName <> "" Then
; Python 스크립트를 다시 실행하여 JSON 파일로 직접 저장
Local $sTableName = GUICtrlRead($idTableName)
Local $sOwner = GUICtrlRead($idOwner)
Local $sCommand = '"' & $g_sPythonPath & '" "' & $g_sScriptPath & '" "' & $g_sConnectionString & '" "' & $sTableName & '" --save-to-file "' & $sFileName & '"'
If $sOwner <> "" Then $sCommand &= " --owner " & $sOwner
RunWait($sCommand, @WorkingDir, @SW_HIDE)
UpdateStatus("JSON 파일로 저장 완료: " & $sFileName)
EndIf
EndFunc
Func ClearResults()
_GUICtrlListView_DeleteAllItems($idListColumns)
_GUICtrlListView_DeleteAllItems($idListConstraints)
_GUICtrlListView_DeleteAllItems($idListIndexes)
GUICtrlSetData($idEditStatistics, "")
UpdateStatus("결과를 지웠습니다.")
EndFunc
Func BuildConnectionString()
Local $sUser = GUICtrlRead($idUser)
Local $sPassword = GUICtrlRead($idPassword)
Local $sHost = GUICtrlRead($idHost)
Local $sPort = GUICtrlRead($idPort)
Local $sService = GUICtrlRead($idService)
Return $sUser & "/" & $sPassword & "@" & $sHost & ":" & $sPort & "/" & $sService
EndFunc
Func UpdateStatus($sMessage)
GUICtrlSetData($hStatusBar, $sMessage)
GUICtrlSetColor($hStatusBar, 0x000000) ; 검은색으로 리셋
EndFunc
Func SaveConfig()
; 연결 정보를 설정 파일에 저장 (비밀번호 제외)
Local $sConfigFile = @ScriptDir & "\oracle_analyzer_config.ini"
IniWrite($sConfigFile, "Connection", "User", GUICtrlRead($idUser))
IniWrite($sConfigFile, "Connection", "Host", GUICtrlRead($idHost))
IniWrite($sConfigFile, "Connection", "Port", GUICtrlRead($idPort))
IniWrite($sConfigFile, "Connection", "Service", GUICtrlRead($idService))
IniWrite($sConfigFile, "Settings", "PythonPath", $g_sPythonPath)
EndFunc
Func LoadConfig()
; 설정 파일에서 연결 정보 로드
Local $sConfigFile = @ScriptDir & "\oracle_analyzer_config.ini"
If FileExists($sConfigFile) Then
GUICtrlSetData($idUser, IniRead($sConfigFile, "Connection", "User", ""))
GUICtrlSetData($idHost, IniRead($sConfigFile, "Connection", "Host", "localhost"))
GUICtrlSetData($idPort, IniRead($sConfigFile, "Connection", "Port", "1521"))
GUICtrlSetData($idService, IniRead($sConfigFile, "Connection", "Service", "ORCL"))
$g_sPythonPath = IniRead($sConfigFile, "Settings", "PythonPath", "python.exe")
EndIf
EndFunc
Func ExitProgram()
; 프로그램 종료 전 설정 저장
SaveConfig()
Exit
EndFunc
; 추가 유틸리티 함수들
Func GenerateDDL()
; DDL 생성 기능 (향후 구현)
Local $sTableName = GUICtrlRead($idTableName)
MsgBox(64, "정보", "DDL 생성 기능은 향후 버전에서 제공될 예정입니다." & @CRLF & "테이블: " & $sTableName)
EndFunc
Func SearchTables()
; 테이블 검색 기능 (향후 구현)
Local $sSearchTerm = InputBox("테이블 검색", "검색할 테이블명 패턴을 입력하세요:", "")
If $sSearchTerm <> "" Then
MsgBox(64, "정보", "테이블 검색 기능은 향후 버전에서 제공될 예정입니다." & @CRLF & "검색어: " & $sSearchTerm)
EndIf
EndFunc
; 메뉴 추가 함수 (선택사항)
Func CreateMenu()
Local $hMenu = GUICtrlCreateMenu("파일")
GUICtrlCreateMenuItem("설정 저장", $hMenu)
GUICtrlCreateMenuItem("설정 불러오기", $hMenu)
GUICtrlCreateMenuItem("-", $hMenu)
GUICtrlCreateMenuItem("종료", $hMenu)
Local $hToolMenu = GUICtrlCreateMenu("도구")
GUICtrlCreateMenuItem("DDL 생성", $hToolMenu)
GUICtrlCreateMenuItem("테이블 검색", $hToolMenu)
Local $hHelpMenu = GUICtrlCreateMenu("도움말")
GUICtrlCreateMenuItem("사용법", $hHelpMenu)
GUICtrlCreateMenuItem("정보", $hHelpMenu)
EndFunc