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