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