메뉴 여닫기
개인 메뉴 토글
로그인하지 않음
만약 지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.

Autoit 과 oracle 이용 테이블 컬럼목록 조회

데브카페


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

  1. 큰 테이블의 경우 통계 정보만 조회

python oracle_table_analyzer.py "conn_string" "large_table" \

   --include-statistics --output json

</source>

== 네트워크 최적화

</source>python

  1. 로컬 네트워크가 아닌 경우 필요한 정보만 조회

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

Comments

목차