'programming'에 해당되는 글 387건

  1. 2014.10.01 OTL (Oracle Template Library) - C++ 에서 손쉽게 오라클과 RDBMS 다루기

반응형

http://lesstif.com/pages/viewpage.action?pageId=20775476



 OTL (Oracle Template Library) - C++ 에서 손쉽게 오라클과 RDBMS 다루기

 
하드를 정리하다가 예전에 프로젝트했던 문서에서 OTL 관련 내용을 발견했다. C++ 을 써 본지도 오래 됐고 앞으로 쓰게 될지도 모르겠지만 당시에 고민했던 내용을 해결하도록 도와준 고마운 라이브러리라 개인적인 기록 차원에서 정리해 본다.

OTL을 처음 써 본게 2000년이고 마지막으로 쓴게 2007년이고 예전 자료와 기억을 토대로 기술하므로 현재의 상황과는 많이 다를수 있다.

C 언어로 오라클에서 데이타를 가져오고 넣고 했던 업무를 처음했던때가 1999년 이었던 것 같다. 지금이야 Java와 JDBC 가 있고 더 나아가 Mybatis나 Hibernate 같은 ORM 을 사용하지만 당시에 DB를 다루는 작업은 녹녹치가 않았다.

 

DBMS 를 오라클을 사용한다면 Pro*C 는 어쩔수 없이 선택해야 하는 방안이었다. 당시의 Pro*c 는 아주 형편없는 물건이라 .pc 파일을 전처리할때 C++ 형식의 주석인 // 가 있으면 아무 에러 메시지없이 segmentation fault 를 내뱉고 죽어버릴 정도였다.

또 전처리 방식이므로 .pc 를 변환해서 .c 를 만들고 이것을 컴파일하는 복잡한 과정을 거쳐야 했고 디버깅도 번거로웠다.

 Pro*C 사용예제

당시에는 저런 삽질을 해야 했었다..



EXEC SQL BEGIN DECLARE SECTION;
sql_context ctx1[CONTEXT_MAX_COUNT];            /* declare global context ctx1     */
EXEC SQL END DECLARE SECTION;
 
EXEC SQL  TYPE my_long IS LONG VARCHAR(1000000) REFERENCE;
 
 
int oracleConnect(char* szAlias, char* szUser, char* szPass, int nContextCount)
{
    EXEC SQL BEGIN DECLARE SECTION;
        VARCHAR  username[128];
        VARCHAR  password[32];
        VARCHAR  alias[128];
        int nCount = 0;
    EXEC SQL END DECLARE SECTION;
        memset(username.arr, 0x00, sizeof(username.arr));
        username.len = 0;
        memset(password.arr, 0x00, sizeof(password.arr));
        password.len = 0;
        memset(alias.arr, 0x00, sizeof(alias.arr));
        alias.len = 0;
    strcpy((char*)username.arr, szUser);
    username.len = (unsigned short)strlen(szUser);
    strcpy((char*)password.arr, szPass);
    password.len = (unsigned short)strlen(szPass);
 
    EXEC SQL WHENEVER SQLERROR GOTO connect_error;
    if(strlen(szAlias) > 0)
    {
        strcpy((char*)alias.arr, szAlias);
        alias.len = (unsigned short)strlen(szAlias);
     
        EXEC SQL CONTEXT USE :ctx1[nContextCount];
        EXEC SQL CONNECT :username IDENTIFIED BY :password USING :alias;   
    }
    else
    {
        EXEC SQL CONTEXT USE :ctx1[nContextCount];
        EXEC SQL CONNECT :username IDENTIFIED BY :password ;
 
    }  
         
    return STAT_OK;
connect_error:
 
    return(sqlca.sqlcode);
}
 
int oracleClose()
{
    EXEC SQL WHENEVER SQLERROR CONTINUE;
    int nCount = 0;
    for (nCount=0; nCount<g_nContextCount; nCount++)
    {
        EXEC SQL CONTEXT USE :ctx1[nCount];
        EXEC SQL COMMIT WORK RELEASE;
    }
 
    return STAT_OK;
}
 
int oracleSelectPolicyList(char* sqlText, PolicyList **head, int nContextNumber)
{
    EXEC SQL BEGIN DECLARE SECTION;
        int  result;
        int  general = 0;
        VARCHAR PolicyID[50];
        VARCHAR PolicyCode[10];
        VARCHAR Type[10];
        VARCHAR UsageType[10];
    EXEC SQL END DECLARE SECTION; 
    PolicyList *first, *second;
        memset(PolicyID.arr, 0x00, sizeof(PolicyID.arr));
        PolicyID.len = 0;
        memset(PolicyCode.arr, 0x00, sizeof(PolicyCode.arr));
        PolicyCode.len = 0;
        memset(Type.arr, 0x00, sizeof(Type.arr));
        Type.len = 0;
        memset(UsageType.arr, 0x00, sizeof(UsageType.arr));
        UsageType.len = 0;
    EXEC SQL WHENEVER SQLERROR CONTINUE;
#ifdef _USER_THREAD
    EXEC SQL CONTEXT USE :ctx1[nContextNumber];
#endif
    EXEC SQL PREPARE ORA_STMT FROM :sqlText;
    EXEC SQL DECLARE ORA_CUR_SELECT CURSOR  FOR ORA_STMT;
    EXEC SQL OPEN ORA_CUR_SELECT ;
    while(1)
    {
        EXEC SQL FETCH ORA_CUR_SELECT INTO :PolicyID, :PolicyCode, :Type, :UsageType;
        result = sqlca.sqlcode;
        if(result == 1403)
        {
            break;
        }
        else if((result < 0) && (result != ORACLE_STAT_NULL_RETURN))
        {
            EXEC SQL CLOSE ORA_CUR_SELECT;
            return result;
        }
         
        if(general == 0)
        {
            *head = (PolicyList *)calloc(1, sizeof(PolicyList));
            strncpy((*head)->PolicyID,   (char *)PolicyID.arr,   PolicyID.len);
            strncpy((*head)->PolicyCode, (char *)PolicyCode.arr, PolicyCode.len);
            strncpy((*head)->Type,       (char *)Type.arr,       Type.len);
            strncpy((*head)->UsageType,  (char *)UsageType.arr,       UsageType.len);
            (*head)->next = NULL;
            second        = *head;
            general++;
            continue;
        
        first = (PolicyList *)calloc(1, sizeof(recordPolicyificateList));
        strncpy(first->PolicyID,   (char *)PolicyID.arr,   PolicyID.len);
        strncpy(first->PolicyCode, (char *)PolicyCode.arr, PolicyCode.len);
        strncpy(first->Type,       (char *)Type.arr,       Type.len);
        strncpy(first->UsageType,  (char *)UsageType.arr,  UsageType.len);
        first->next  = NULL;
        second->next = first;
        second       = second->next;
        first        = NULL;
        general++;
    }
    EXEC SQL CLOSE ORA_CUR_SELECT;
    if(general > 0)
        return(0);
    return(result);
}

 

 

C 언어를 써야 했다면 ODBC 를 쓰면 되지 않냐는 궁금증도 있을 것이다. 당시는 99년이었고 윈도 NT의 위상은 형편없어서 대부분의 서버는 Solaris, AIX, HP-UX 등의 상용 유닉스를 쓰던 시절이었다.

ODBC 는 MS 가 제정한 규격이었고 오라클같은 DBMS 제조사에서는 윈도외에는 ODBC 용 드라이버를 제공하지 않았다. 

그래서 유닉스용 ODBC 를 제공하는 외국의 솔루션 업체가 있었는데 가격이 매우 비쌌고 UnixODBC 같은 오픈소스 프로젝트가 있었지만 안정성이 검증되지 않고 프로젝트에 도입할 수가 없었다.

 

Pro*C 를 사용해서 프로젝트를 몇 개 하고 슬슬 대안을 찾던 중에 OCI(Oracle Call Interface) 라는 걸 알게 되었다.

이건 전처리를 통과하지 않아도 되는 순수 C 로 된 라이브러리였고 막강한 기능을 제공했었다. 다만 한 가지 큰 문제는 API 가 너무 어렵고 난잡했다.

Oracle 에 연결하기 위해 OCI 관련 변수와 환경을 초기화하면 코드만 해도 다음과 같이 엄청난 양을 코딩해야 했다.


 OCIInitialize 예제



OCIEnv *envhp; // OCI environment handle
OCIServer *srvhp; // OCI Server handle
OCIError *errhp; // OCI Error handle
OCISvcCtx *svchp; // OCI Service context handle
OCISession *authp; // OCI Session handle
static int initialize(const int threaded_mode=0)
{
 int status;
 int mode;
 if(threaded_mode)
   mode=OCI_THREADED;
 else
   mode=OCI_DEFAULT;
 status=OCIInitialize
  (static_cast<ub4>(mode),
   reinterpret_cast<dvoid *>(0),
   0,
   0,
   0
  );
 if(status!=OCI_SUCCESS)
  return 0;
 else
  return 1;
}

SQL 구문의 placeholder와 프로그램내 변수를 매핑시키는 OCIBindByName  같은 함수는 다음과 같은 엄청난 매개 변수를 넘겨주고 호출해야 했다.


OCIBindByName interface

sword OCIBindByName ( OCIStmt       *stmtp,
                      OCIBind       **bindpp,
                      OCIError      *errhp,
                      CONST text    *placeholder,
                      sb4           placeh_len,
                      dvoid         *valuep,
                      sb4           value_sz,
                      ub2           dty,
                      dvoid         *indp,
                      ub2           *alenp,
                      ub2           *rcodep,
                      ub4           maxarr_len,
                      ub4           *curelep,
                      ub4           mode );

 

 

 

Ref

 



반응형
Posted by 공간사랑
,