메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

Pro*C를 사용한 임베디드 SQL

한빛미디어

|

2007-01-05

|

by HANBIT

18,276

제공: 한빛 네트워크
저자: Sai Kiran Gummaraj, 최연기 역
원문: Embedded SQL with Pro*C

어떠한 응용 소프트웨어도, 데이터베이스 시스템에 접근하는 것은 거의 필연적이다. 특별한 라이브러리와 함께 사용되는 Java와 C++과 같은 몇몇 프로그래밍 언어는 데이터베이스에 접근하는 좋은 프로그램 인터페이스를 제공한다. 만약 당신이 C, COBOL 또는 FORTRAN을 사용하여 데이터베이스에 접근하려고 한다면 어떤가? 보통의 대형 은행과 같은 고전적인 응용 프로그램들에 사용되는 경우가 많다. 이러한 곳의 시스템은 다른 산업들이 회사들 보다 매우 천천히 변화하고, 시스템들은 극히 크리티컬하게 유지된다: 그들은 전기적인 자금 추적을 한다. 이것은 기술 전도자들의 목마름에 단지 만족할지라도 새로운 기술들을 소개하기 어렵다. 이런 시스템들은 종종 Oracle의 OCI, Sybase Open Client Connect Libraries 와 Pro*C와 같은 낮은 레벨의 데이터베이스 라이브러리를 포함한, 데이터베이스에 접근하는 오래된 길들에 의존한다. 이것은 새로운 세대의 시스템으로 이동하는 계획을 풍부하게 한다; 이것은 오래된 구현들을 단계적으로 제거하기 위해 평행하고 단계적인 개발에 여러 해를 끈다.

Pro*C와 같은 데이터베이스 접근 메커니즘은 상대적으로 약간의 시간 동안 머무를 것이다. Pro*C는 많은 프로그래머(그리고 리뷰어)들이 데이터베이스에 접근하기 위한 코드를 작성하는 친화적인 방법이다. 사전 편집(pre-compilation) 스테이지는 Pro*C 코드를 로우 레벨 구조체와 함수 콜들로 돌아간다. 이러한 페러다임은 꽤 많은 이점들을 제공한다. 첫째, 이것은 프로그래머를 데이터베이스에 접근하는데 사용되는 하부의 API 콜들로부터 보호한다. 둘째, 실제적인 SQL 문장들과 주 프로그래밍 언어 코드는 아주 읽기 좋고 깨끗한 코드를 생산하도록 정말 잘 혼합된다. 이것은 완벽하지는 않지만 인정한다.

임베디드 SQL은 당신을 C와 COBOL과 같은 언어로 작성된 응용 프로그램들 안의 SQL 문장들을 두는 Sybase의 T-SQL 또는 Oracle의 PL/SQL의 상위 집합이다. Pro*C는 C 프로그래머가 데이터베이스 접근 코드를 빠르고 습득하는데 드는 시간을 줄이도록 한다. C와 SQL 모두에 친숙한 사람들에게는 매우 쉬운 일이다. 그것의 무가치는 Pro*C와 맞은편에 다른 데이터베이스 벤더들의 구현 사이는 다르고, 다른 데이터베이스 아키텍처, 데이터타입 등의 사이도 다르게 해야 한다는 것이다. 데이터베이스 각각의 새로운 릴리즈는 그것의 임베디드 SQL 프리컴파일러의 확실한 향상이나 변화를 공고한다. 이것은 벤더의 데이터베이스 웹사이트들을 조회하여 이들 정면의 변화를 추적하는데 최선이다.

이 글은 데이터베이스 시스템에 접근하고 수정하는 Pro*C 코드를 작성하는 방법과 의미를 논의한다. 나의 테스트 데이터베이스들은 Pro*C 처럼 동일한 환경에서 널리 사용되는 Sybase와 Oracle이다. 마지막으로, 임베디드 SQL을 Pro*C라고 부르는 몇몇 사람들에게는 가치가 없다.

준비되었나? 편안한 C API 콜들을 사용하여 Sybase 데이터베이스에 접속하기 위해 무엇을 해야 하는가(Sybase 공개 클라이언트 접속 라이브러리들과 같은)?

주의: dbCon.c는 Sybase의 Sybase 프로그래밍 코드 예제이다.
/************************************************
 *                                              *
 * Filename : dbCon.c                           *
 *                                              *
 * This is function demonstrates the series of  *
 * steps it involves in opening a connection    *
 * to the Sybase database.                      *
 *                                              *
 * NOTE: Not all variables used are declared;   *
 * Such as context, connection etc. as this     *
 * is just a demo snippet.                      *
 *                                              *
 ***********************************************/

void dbConnect ()
{
    CS_INT        rc;
    CS_INT        *outlen;
    CS_INT        buf_len;
    CS_INT        msglimit;
    CS_INT        netdriver;

    /*-----------------------------------------*
     * Allocate a connection to the server     *
     *-----------------------------------------*/
    rc = ct_con_alloc (context, &connection);
    if (rc != CS_SUCCEED)
    {
        strncpy (msgstr, "CT_CONALLOC failed", \
            msg_size);
        no_errors_sw = FALSE ;
        error_out (rc);
    }

    /*-----------------------------------------*
     * Alter properties of the                 *
     * connection for user-id                  *
     *-----------------------------------------*/
    buf_len  = user_size;
    rc = ct_con_props (connection, (long)CS_SET,\
        (long)CS_USERNAME, username, buf_len,\
        outlen);
    if (rc != CS_SUCCEED)
    {
        strncpy (msgstr, "CT_CON_PROPS for \
            user-id failed",msg_size);
        no_errors_sw = FALSE ;
        error_out (rc);
    }

    /*-----------------------------------------*
     * Alter properties of the                 *
     * connection for password                 *
     *-----------------------------------------*/
    buf_len = pwd_size;
    rc = ct_con_props (connection, (long)CS_SET,\
        (long)CS_PASSWORD, pwd, buf_len, outlen);
    if (rc != CS_SUCCEED)
    {
        strncpy (msgstr, "CT_CON_PROPS for \
            password failed", msg_size);
        no_errors_sw = FALSE ;
        error_out (rc);
    }

    /*-----------------------------------------*
     * Alter properties of the                 *
     * connection for transaction              *
     *-----------------------------------------*/
    buf_len = tran_size;
    rc = ct_con_props (connection, (long)CS_SET,\
        (long)CS_TRANSACTION_NAME, tran,\
        buf_len, outlen);
    if (rc != CS_SUCCEED)
    {
        strncpy (msgstr, "CT_CON_PROPS for \
            transaction failed", msg_size);
        no_errors_sw = FALSE ;
        error_out (rc);
    }

    /*-----------------------------------------*
     * Alter properties of the connection      *
     * for network driver                      *
     *-----------------------------------------*/

    /*-----------------------------------------*
     * default value for non-recognized        *
     * driver name                             *
     *-----------------------------------------*/
    netdriver = 9999;

    /*-----------------------------------------*
     * if no netdriver entered,                *
     * default is LU62                         *
     *-----------------------------------------*/

    if (strncmp(driver,"         ",9) == 0  ?? \
        strncmp(driver,"LU62",4) == 0)
            netdriver = CS_LU62;
    else if (strncmp(driver,"INTERLINK",8) == 0)
        netdriver = CS_INTERLINK;
    else if (strncmp(driver,"IBMTCPIP",8) == 0)
        netdriver = CS_TCPIP;
    else if (strncmp(driver,"CPIC",4) == 0)
        netdriver = CS_NCPIC;

    rc = ct_con_props (connection, (long)CS_SET,\
        (long)CS_NET_DRIVER, (long)netdriver,\
        CS_UNUSED, outlen);
    if (rc != CS_SUCCEED)
    {
        strncpy (msgstr, "CT_CON_PROPS for \
            network driver failed",msg_size);
        no_errors_sw = FALSE ;
        error_out (rc);
    }

    /*-----------------------------------------*
     * Setup retrieval of All Messages         *
     *-----------------------------------------*/
    rc = ct_diag (connection, CS_INIT, \
        CS_UNUSED, CS_UNUSED, CS_NULL);
    if (rc != CS_SUCCEED)
    {
        strncpy (msgstr, "CT_DIAG CS_INIT \
            failed", msg_size);
        no_errors_sw = FALSE ;
        error_out (rc);
    }

    /*-----------------------------------------*
     * Set the upper limit of number           *
     * of messages                             *
     *-----------------------------------------*/
    msglimit = 5 ;
    rc = ct_diag (connection, CS_MSGLIMIT, \
        CS_ALLMSG_TYPE, CS_UNUSED, &msglimit);
    if (rc != CS_SUCCEED)
    {
        strncpy (msgstr, "CT_DIAG CS_MSGLIMIT \
            failed", msg_size);
        no_errors_sw = FALSE ;
        error_out (rc);
    }

    /*-----------------------------------------*
     * Open connection to the server           *
     * or CICS region                          *
     *-----------------------------------------*/
    rc = ct_connect (connection, servname, \
        server_size);
    if (rc != CS_SUCCEED)
    {
        strncpy (msgstr, "CT_CONNECT failed",\
            msg_size);
        no_errors_sw = FALSE ;
        error_out (rc);
    }
}
이 코드는 API들을 호출하고 에러-검사가 많다는 것을 알려준다. 얼마 더 간단한 Pro*C가 이것을 만드는지! Pro*C 소스 파일들은 .PC 또는 .pc 확장자를 가진다는 것에 주의하라. Pro*C 프리-컴파일러는 실질적인 .c또는 .cpp 파일들을 생성한다.

다음 Pro*C 프로그램 예제는 단지 데이터베이스에 성공적으로 연결하기 위해 필요한 선언들과 문장들을 보여준다. 나는 이것을 나중에 설명할 것이다. 그저 얼마나 간결한지 보라.
/*----------------------------------------------*
 * This appears in the top of the file in the   *
 * global area. Not inside any function.        *
 *----------------------------------------------*/
EXEC SQL INCLUDE SQLCA;
EXEC SQL WHENEVER SQLERROR CONTINUE;
EXEC SQL WHENEVER SQLWARNING CONTINUE;
EXEC SQL WHENEVER NOT FOUND CONTINUE;

/*----------------------------------------------*
 * This is a simple C or C++ function that      *
 * would make use of Pro*C code to connect      *
 * to the database.                             *
 *----------------------------------------------*/


int database_connect()
{
    EXEC SQL BEGIN DECLARE SECTION;
        char *usr;
        char *pswd;
        char *srvr;
        char *dbase;
        char *cnct;
    EXEC SQL END DECLARE SECTION;

    EXEC SQL CONNECT :usr identified by \
    :pswd AT :cnct using :srvr;
    err_check (&sqlca, __FUNCTION__, \
        __LINE__);

    EXEC SQL AT :cnct USE :dbase;
    err_check (&sqlca, __FUNCTION__, \
        __LINE__);
}


/*---------------------------------------------*
 * Here is the basic error handling            *
 * function snippet.                           *
 *---------------------------------------------*/

int error_check (SQLCA * sqlca_ptr,
    char *func_name, int line_num)
{
    SQLCA *p_sqlca;

    if (p_sqlca -> sqlcode < 0)
    {
        printf ("Error at function: %s at \
            line %d\n", func_name, line_num);
        printf ("\tError Code: %d\n", \
            p_sqlca -> sqlerrm.sqlerrml);
        printf ("\tError Description: %s\n", \
            p_sqlca -> sqlerrm.sqlerrmc);
        return p_sqlca -> sqlcode;
    }
    else if (p_sqlca -> sqlcode == 0)
    {
        printf ("%s operation successful\n", \
            func_name);
        return p_sqlca -> sqlcode;
    }
    return 0;
}
코드의 라인들의 수와 코드의 명확성이 API 콜들을 직접적으로 사용하는 것과 무엇이 다른가!

일반적인 개념

Pro*C 프리-컴파일러는 종종 Sybase 또는 Oracle과 같은 데이터베이스 관리 소프트웨어와 함께 온다. Pro*C 프리-컴파일러는 각각의 Pro*C 콜들을 적당한 C/C++로 대체한다. 그런 후 C/C++ 컴파일러는 다른 보통의 프로그램처럼 컴파일하고 링크하여 C/C++ 프로그램을 생성한다. 링커는 명백히 Sybase 또는 Oracle의 라이브러리의 위치를 필요로 한다. 그림1은 단계의 연관 관계를 보여준다.

그림1
그림 1. Pro*C 프로그램의 컴파일 순서

모든 Pro*C 문장은 일반적인 문법 패턴을 따른다: 이것은 EXEC SQL 키워드들로 시작하고 세미콜론(;)으로 끝난다. EXEC SQL 키워드 뒤의 코드는 데이터베이스의 실제 SQL과 거의 1대1로 일치되고, 물론 호스트 언어 (C/C++) 변수를 데이터베이스에 통과하고 반환값을 만회하는 여분 기능을 제공한다. 예를 들면, SQL 문장은 다음과 같다:
COMMIT tran
를 Pro*C 코드로 바꾸면 다음과 같다.
EXEC SQL COMMIT tran;
"호스트 변수"를 사용하여 호스트 언어와 SQL 연결하기

호스트 변수들은 C/C++과 SQL 데이터베이스 콜들 같은 호스트 언어 사이에 데이터를 전송한다. 호스트 변수들을 선언하기 위해서는 다음과 같이 한다:
    EXEC SQL BEGIN DECLARE SECTION;
        // 여기에 모든 호스트 값들을 선언한다.
    EXEC SQL END DECLARE SECTION;
호스트 변수는 이름이 콜론(:)으로 시작하도록 한다.
    /*-----------------------------------------*
     * For simplicity I have just allocated    *
     * memory statically. They can even be     *
     * pointers and allocations can be managed *
     * later.                                  *
     *-----------------------------------------*/
    EXEC SQL BEGIN DECLARE SECTION;
        char username[128];
        char password[128];
        char db_name[128];
        char connection_name[128];
    EXEC SQL END DECLARE SECTION;

    strcpy (username, "casper");
    strcpy (password, "knock knock");
    strcpy (db_name, "cust_db");
    strcpy (connection_name, "Sai");
    EXEC SQL CONNECT :username IDENTIFIED BY
        :password AT :connection_name
        USING :db_name;
당신의 프로그램에 여러 개의 데이터베이스 연결을 가지길 원한다고 가정해 보자. 당신의 코드는 다음과 같이 될 것이다.
    char custdb[256] = "Cust_Db";
    char invdb[256] = "Inventory_Db";

    EXEC SQL CONNECT :username IDENTIFIED BY
        :password AT :custdb USING :db_string1;
    EXEC SQL CONNECT :username IDENTIFIED BY
        :password AT :invdb USING :db_string2;
그런 후 당신은 SQL 문장들을 다음과 같이 실행한다:
EXEC SQL AT "Cust_Db"
/* or */
EXEC SQL AT :custdb
SELECT ...
그러나, 이 값들에는 다음과 같은 몇몇 경고가 있다.
  1. 호스트 변수들의 데이터타입들은 벤더 의존적으로 허용된다. 자세한 정보는 벤더 매뉴얼을 참고하라. 내가 알고 있는 것은, Pro*C는 대부분의 기본적은 데이터타입들을 허용한다. Oracle과 Sybase 매뉴얼 모두 EXEC SQL (BEGIN/END) DECLARE 섹션에 포인터들을 선언하여 사용할 수 있지만, 호스트 값들처럼 포인터나 포인터 표현식들을 사용할 수 없다고 설명되어 있다.
  2. 모든 호스트 변수는 실제적인 C 값이지만, 데이터베이스 컬럼 이름들과는 달리 대소문자를 구분한다.
  3. 모든 호스트 변수는 실제적인 C 값이고, 이후로는 프로그램의 유효한 주소와 일치해야 한다.
호스트 변수의 데이터타입은 데이터베이스 컬럼의 타입과 호환되어야 한다. 예를 들면, C 데이터타입인 char과 char[](배열)은 VARCHAR 오라클 타입과 매치된다. short/int/long/float 등은 데이터베이스의 NUMBER/NUMBER(P,S) 타입과 매치된다.

호스트 구조체와 배열

당신은 또한 데이터베이스 테이블의 로우(row)를 표현하기 위해 호스트 변수 구조체(structure)들을 선언할 수 있다. 호스트 구조체(structure)들의 배열은 테이블의 로우(row)들의 집합을 표현한다. Pro*C는 개별적인 호스트 변수들의 데이터를 가져오고, 조종하는 대신 한번에 호스트 구조체(struct)를 가져오고, 조종하는 것을 허용한다. 이 향상된 성능과 데이터베이스 데이터를 표현하는 많은 논리적인 방법을 제공한다. 예를 들면 다음과 같다.
    struct part_cust_rec
    {
        char cust_name[256];
        float discount_rate;
    };

    // a set of 50 key customers
    struct part_cust_rec key_custs[50];

    EXEC SQL AT "Cust_Db"
    SELECT Customer_Name,  Customer_Discount
    FROM Customer
    INTO :key_custs.custname, \
         :key_custs.discount_rate
    WHERE Customer_Discount > 25
지시자 변수

모든 호스트 변수는 연관된 지시자 변수을 가질 수 있다. 지시자 변수들은 호스트 변수와 일치하는 값을 가리키는 2 바이트로 된 정수형 값들이다. DECLARE 섹션의 호스트 변수 선언의 일부로 선언하라.

표1은 지시자 값들과 그 의미를 보여준다.

지시자 변수 값 설명
0 연산이 성공적이었다.
-1 insert나 update 시, NULL이 반환되었다.
-2 "long" 타입에서 문자형 호스트 값으로 출력 시 끝이 잘렸으나, 원본 컬럼의 길이를 결정할 수 없다.
>0 SELECT 또는 FETCH의 결과를 문자형 호스트 변수로 변환시 끝이 잘렸다. 이런 경우, 호스트 변수가 멀티바이트 문자형 변수이면, 지시자 값은 문자형들의 원본 컬럼 길이이다. 만약, 호스트 변수가 멀티바이트 문자형 값이 아니면, 지시자의 길이는 바이트의 원본 컬럼 길이이다.


지시자 값은 다음과 같이 사용한다:
    EXEC SQL BEGIN DECLARE SECTION;
        int cust_id;
        char cust_name[256];
        char cust_address[512];
        short indicator_addr;
    EXEC SQL END DECLARE SECTION;

    EXEC SQL AT "Cust_Db"
    SELECT Customer_Name, Customer_Address
    FROM Customer
    INTO :cust_name, :cust_address:indicator_addr

    /*-------------------------------------------*
     * Alternate Declaration of the INTO clause  *
     *                                           *
     * INTO :cust_name,:cust_address             *
     * INDICATOR :indicator_addr                 *
     * WHERE Customer_Id = :cust_id;             *
     *-------------------------------------------*/

    if (indicator_addr == -1)
    {
        /* Address is NULL */
        cout << "Customer"s Contact \
            Address is not registered...";
        ...
    }
통신 지역과 에러 처리

호스트 값을 사용하는데 개별적으로 데이터가 데이터베이스와 호스트 언어 사이를 앞뒤로 통과하기 위해 SQL 통신 지역(SQLCA, SQL Communication Area)은 상태와 런타임 정보를 데이터베이스와 호스트 사이에 전달할 수 있다. SQLCA는 호스트 언어는 특정한 데이터베이스 이벤트들을 위한 일치된 동작을 취하기 위해 데이터베이스의 런타임 정보를 전송한다.

SQLCA의 사용:
        EXEC SQL INCLUDE SQLCA.H;       // In Oracle
그리고,
        EXEC SQL INCLUDE SQLCA;         // In Sybase
이 선언은 가장 최근에 실행된 SQL을 위한 SQL 상태 코드, 에러 메시지, 경고 메시지 등을 가진 sqlca라 불리는 구조체를 포함한다. 당신은 sqlca 구조체의 직접적인 출력을 통해 상세한 정보를 읽은 수 있다.
    struct sqlca
    {
        /*-------------------------------------*
         * sqlcaid : Holds the hardcoded       *
         * string "SQLCA"                      *
         *-------------------------------------*/
        char sqlcaid[8];

        /*-------------------------------------*
         * sqlabc : Holds the length of the    *
         * structure                           *
         *-------------------------------------*/
        long sqlabc;

        /*-------------------------------------*
         * sqlcode : Holds the status code     *
         * of the most recently executed       *
         * SQL statement.                      *
         *-------------------------------------*/
        long sqlcode;

        /*-------------------------------------*
         * sqlerrm is a structure to hold      *
         * error description.                  *
         *                                     *
         * sqlerrmc : contains error           *
         * description of the status code      *
         * in sqlerrmc (upto 70 chars).        *
         *-------------------------------------*/
        struct
        {
            unsigned short sqlerrml;
            char sqlerrmc[70];
        } sqlerrm;

        /*-------------------------------------*
         * sqlerrp : Un-used                   *
         *                                     *
         * The array element sqlerrd[2] :      *
         * Holds number of Rows                *
         * processed by the most recent        *
         * SQL statement                       *
         *                                     *
         * The array element sqlerrd[4] :      *
         * Holds offset of the most recent     *
         * parse error in SQL.                 *
         *                                     *
         * The array elements                  *
         * sqlerrd[0,1,3,5] : Are Un-used      *
         *-------------------------------------*/
        char sqlerrp[8];
        long sqlerrd[6];

        /*-------------------------------------*
         * sqlwarn : Holds warning information *
         * sqlext : Un-used.                   *
         *-------------------------------------*/
        char sqlwarn[8];
        char sqlext[8];
};
예외 처리

sqlca 구조체 또는 WHENEVER절을 이용하여 에러 코드들과 이유들을 시험하여 에러들을 다루어 보자:
    EXEC SQL WHENEVER  
CONDITION 은 다음과 같은 것을 할 수 있다:
  1. SQLERROR, 이전 SQL 명령을 실행하는 동안 에러가 발생하였음을 가리킨다.
  2. SQLWARNING, 이전 SQL 명령을 실행한 결과가 경고를 발생하였음을 가리킨다.
  3. NOT FOUND, 이전 SQL 명령을 실행한 결과로 데이터베이스로부터 데이터를 발견하지 못하였음을 가리킨다.
ACTION 은 다음과 같은 것을 할 수 있다:
  1. STOP, 모든 커밋되지 않은 트랜잭션을 롤백(roll back)하고, 프로그램을 exit()를 한다.
  2. CONTINUE, 에러에도 불구하고 프로그램을 계속 실행하도록 노력한다.
  3. DO , while 함수, 에러-처리 함수, 해당 함수를 호출한다.
  4. GOTO
트랜잭션

데이터베이스 트랜잭션들은 SQL 문장들의 집합을 하나의 단일체로 다룰 수 있도록 한다. 이들 SQL 문장들이 데이터베이스에 영구히 커밋하게 하거나, 롤백하여 변경을 하여, 데이터 호환성을 항상 유지한다. Pro*C는 SQL이나 프로시듀어 SQL안에서와 같이 begin과 end를 정의할 수 있도록 허용한다. 트랜잭션들의 SQL 문장을 감싸지 않으면, 각 SQL 문장은 개별적인 트랜잭션을 이르킨다. 이 코드는 Pro*C 상에서 트랜잭션 문장들을 사용하는 것을 설명한다:
    EXEC SQL BEGIN TRANSACTION
        cust_transaction_insert;
    EXEC SQL AT "Cust_Db"
        INSERT INTO Customer
        VALUES (:cust_id, :cust_name,
            :cust_address, :cust_discount);

    if (ptr_sqlca -> sqlcode < 0)
    {
        printf ("Your Transaction Will be \
            rolled back.\n");
        printf ("Error Code: %d\n", \
            ptr_sqlca->sqlerrm.sqlerrml);
        printf ("Error Description: %s\n", \
            ptr_sqlca->sqlerrm->sqlerrmc);
        EXEC SQL ROLLBACK TRANSACTION
            cust_transaction_insert;
    }

    /*-----------------------------------------*
     * This marks a Save point in your         *
     * transaction. At any time ahead in your  *
     * program if there is a roll back, you    *
     * have an option of rolling back          *
     * completely or to this point.            *
     *-----------------------------------------*/

    EXEC SQL SAVEPOINT start_update;

    /* ... Some Program Logic goes here ... */

    EXEC SQL AT "Cust_Db"
        UPDATE Customer SET Customer_Discount
            = :cust_discount
            WHERE Customer_Id = :cust_id;

    if (ptr_sqlca -> sqlcode < 0)
    {
        printf ("Unable to update discount
            rates for the customer. %d\n",
                cust_id);
        printf ("Error Code: %d\n",
            ptr_sqlca->sqlerrm.sqlerrml);
        printf ("Error Description: %s\n",
            ptr_sqlca->sqlerrm->sqlerrmc);

        /*-------------------------------------*
         * Your transaction will be rolled back*
         * to the most recent save point       *
         *-------------------------------------*/
        EXEC SQL ROLLBACK TRANSACTION
            start_update;

        /*-------------------------------------*
         * If you had just said                *
         *     EXEC SQL ROLLBACK TRANSACTION   *
         * The entire transaction would have   *
         * been rolled back. Not just          *
         * upto the save point.                *
         *-------------------------------------*/
    }
    EXEC SQL COMMIT cust_transaction_insert;
커서

쿼리가 여러 줄(row)을 반환하면, "커서" 또는 "호스트 배열"을 사용하여 데이터의 각 줄(row)을 처리해야 한다. 커서는 쿼리에 의해 반환된 줄(row)들의 집합에서 현재 줄(row)를 가리킨다. 커서는 프로그램에서 한번에 하나의 데이터 줄(row)을 처리할 수 있도록 한다.

커서 조작하는데는 다음과 같은 문장들을 사용한다:
  1. 쿼리와 연관된 커서의 이름을 지시하기 위해 DECLARE CURSOR을 사용한다.
  2. 커서의 "Active Set"과 같은 쿼리의 반환된 줄(row)들의 집합을 구분하고, 쿼리를 실행하기 위해 OPEN을 사용한다.
  3. 집합으로부터 다음 줄(row)를 가져와 개별적인 줄(row)들로 조작하기 위해 FETCH를 사용한다.
  4. 커서를 사용하지 못하게 하고 "Atcive Set"을 닫기위해 CLOSE를 사용한다.
다음 예제가 도움이 될 것이다:
    EXEC SQL BEGIN DECLARE SECTION;
        char customer_name[256];
        long cust_id;
        char customer_address[512];
        char customer_email[128];
        float customer_discount;
    EXEC SQL END DECLARE SECTION;

    EXEC SQL DECLARE cust_cursor CURSOR FOR
        SELECT cust_id, cust_name, cust_address,
        cust_email, cust_discount
        FROM customer
        WHERE cust_name = :customer_name;

    EXEC SQL OPEN cust_cursor;
    EXEC SQL WHENEVER NOT FOUND DO break;
    while (1)
    {
        EXEC SQL FETCH cust_cursor INTO
            :customer_id, :customer_name,
            :customer_address,
            :customer_email, :customer_discout;
        /*-------------------------------------*
         * Logic to process retrieved          *
         * values go here;                     *
         *-------------------------------------*/
    }
    EXEC SQL CLOSE cust_cursor;
커서의 SELECT의 부분으로 INTO 절을 사용할 수 없다는 것을 주의하라. 대신, 선택된 컬럼들을 호스트 값들로 가져오기 위해 FETCH를 사용한다. 또한 하나의 소스 파일에서 커서를 선언하고 다른 소스 파일에서 열거나 사용할 수 없다.

여기 SELECT와 INSERT 조작들의 설명하기 위한 간단한 예제가 있다:
#include 
#include 
#include 

EXEC SQL INCLUDE SQLCA;
EXEC SQL WHENEVER SQLERROR CONTINUE;
EXEC SQL WHENEVER SQLWARNING CONTINUE;
EXEC SQL WHENEVER NOT FOUND CONTINUE;

int connect (char *connection_name,
    char *db_server, char *db_instance,
    char *uname, char *passwd);
int error_check (char *func_name,
    int line_num);
void finish_proc ();

int main (int argc, char *argv[])
{
    char dataserver[128];
    char database[128];
    char username[128];
    char password[128];
    char connection[128];
    int status = 0;
        int choice = 0;
    SQLCA *p_sqlca;

    EXEC SQL BEGIN DECLARE SECTION;
        CS_INT emp_id;
        CS_CHAR emp_name[50];
        CS_CHAR addr[50];
        CS_CHAR phno[50];
        CS_CHAR mgr_name[50];
        short ind_mgr;
        float salary;
        CS_CHAR join_date[50];
    EXEC SQL END DECLARE SECTION;

    printf ("Enter the database \
        server name: ");
    scanf ("%s", dataserver);

    printf ("Enter the database \
        instance name: ");
    scanf ("%s", database);

    printf ("Enter the username: ");
    scanf ("%s", username);

    printf ("Enter the password: ");
    scanf ("%s", password);

    printf ("Enter a name for your \
        connection: ");
    scanf ("%s", connection);

    status = connect (connection,
        dataserver, database, username,
        password);

    if (status < 0)
    {
        printf ("Unable to proceed.
            Exiting...\n");
        exit (1);
    }

    atexit(finish_proc);

    while (1)
    {
            printf ("\n\n");
            printf ("\tEnter 1 for SELECT, 2 for\
                INSERT and 0 to QUIT: ");
            scanf ("%d", &choice);

            switch (choice)
            {
            case 1:

            EXEC SQL DECLARE emp_cursor CURSOR \
                FOR SELECT employee_id, \
                employee_name, address, phno,\
                manager_name, salary, \
                joining_date FROM Emp;

            EXEC SQL OPEN emp_cursor;

            printf("employee_id\temployee_name\t\
            address\tphno\\tmanager_name\t\
            salary\t\joining_date\n");

            printf("-----------\t\-------------\t
            -------\t\----\t------------\t------\
            \t------------\n");

            while (1)
            {
                EXEC SQL FETCH emp_cursor INTO
                :emp_id, :emp_name,:addr, :phno,
                :mgr_name:ind_mgr,:salary,
                :join_date;
                printf ("%d\t%s\t%s\t%s\t%s \
                    \t%f\t%s\n", emp_id, \
                    emp_name, addr, phno, \
                    ((ind_mgr == -1) ? \
                    "NULL" : mgr_name),
                    salary, join_date);
            }
            break;

            case 2:

            printf ("Enter the employee id");
            scanf ("%d", &emp_id);
            printf ("Enter the employee name:");
            scanf ("%s", emp_name);
            printf ("Enter the employee \
                address: ");
            scanf ("%s", addr);
            printf ("Enter the employee \
                phno: ");
            scanf ("%s", phno);
            printf ("Enter the employee\
                manager name: ");
            scanf ("%s", mgr_name);
            printf ("Enter the employee \
                salary: ");
            scanf ("%f", &salary);
            printf ("Enter the employee \
                joining date (MM/DD/YYYY):");
            scanf ("%s", join_date);

            EXEC SQL BEGIN
                TRANSACTION emp_insert;

            EXEC SQL INSERT INTO Emp
                VALUES (:emp_id, :emp_name,
                :addr, :phno, :mgr_name,
                :salary, :join_date);

            p_sqlca = &sqlca;
            if (p_sqlca -> sqlcode < 0)
            {
                printf ("Error Code: %d\n",\
                    p_sqlca -> \
                    sqlerrm.sqlerrml);
                printf ("Error Description:
                    %s\n",p_sqlca -> \
                    sqlerrm.sqlerrmc);
                printf ("Your transaction will be
                    rolled back.");
                EXEC SQL ROLLBACK
                    TRANSACTION emp_insert;
            }

            EXEC SQL COMMIT
                TRANSACTION emp_insert;
            break;

            case 0:
            default:

                EXEC SQL DISCONNECT DEFAULT;
                exit(0);
        }
    }
}

int connect (char *c_name, char *db_svr,
    char *db_inst, char *uname, char *passwd)
{
    int status = 0;

    EXEC SQL BEGIN DECLARE SECTION;
        char *connection_name;
        char *db_server;
        char *username;
        char *password;
        char *db_instance;
    EXEC SQL END DECLARE SECTION;

    connection_name = c_name;
    db_server = db_svr;
    db_instance = db_inst;
    username = uname;
    password = passwd;

    EXEC SQL CONNECT :username identified
        by :password AT :connection_name
        using :db_server;
    status = error_check("connect",
        __LINE__);

    if (status < 0) return status;

    EXEC SQL AT :connection_name
        USE :db_instance;
    status = error_check("connect",
        __LINE__);

    if (status < 0)
        return status;

    return status;
}

int error_check (char *func_name,
    int line_num)
{
    SQLCA *p_sqlca;

    p_sqlca     = &sqlca;

    if (p_sqlca -> sqlcode < 0)
    {
        printf ("Error at function: %s \
            at line %d\n", func_name,
            line_num);
        printf ("\tError Code: %d\n",
            p_sqlca -> sqlerrm.sqlerrml);
        printf ("\tError Description: %s\n",
            p_sqlca -> sqlerrm.sqlerrmc);
        return -1;
    }
    else if (p_sqlca -> sqlcode == 0)
    {
        printf ("%s operation successful\n",
            func_name);
        return 0;
    }
    return 0;
}
나는 Sun 솔라리스 머신 상에서 Sybase 10.0.1 프리-컴파일러를 사용하여 이 프로그램을 작성하고 컴파일하였다.
#
# Note: You will need to compile sybesql.c
# and link the sybesql.o with your object file.
# You can find sybesql.c under
# $SYBASE_HOME/include
#

$ cc -g -c -I/opt/sybase_10.0.1/sybembsql/include
  -I/opt/sybase_10.0.1/include sybesql.c

$ /opt/sybase_10.0.1/sybembsql/bin/cpre -a -r \
  -m -C ANSI_C db_delegate.PC

$ cc -g -c \
  -I/opt/sybase_10.0.1/sybembsql/include \
  -I/opt/sybase_10.0.1/include db_delegate.c

$ cc -g -mt sybesql.o db_delegate.o \
        /opt/sybase_10.0.1/lib/libct.a \
        /opt/sybase_10.0.1/lib/libcs.a \
        /opt/sybase_10.0.1/lib/libcomn.a \
        /opt/sybase_10.0.1/lib/libtcl.a \
        /opt/sybase_10.0.1/lib/libintl.a \
        /opt/sybase_10.0.1/lib/libtli.a \
        -L/opt/sybase_10.0.1/lib \
        -L/opt/sybase_10.0.1/sybembsql/lib \
        -lsocket -lnsl -ldl -lm -o db_delegate
Sai Kiran Gummaraj는 소프트웨어 산업에 소프트웨어 엔지니어로 약 8년 가까이 종사하고 있다.

최연기역자 최연기님은 2006년 6월 LG CNS에 입사하여 시스템 아키텍트로 새로운 삶을 살아가고 있으며, 이전에는 나인포유㈜, ㈜이씨오에서 웹 프로그래밍을 4년간 하였다. 금오공과대학교 컴퓨터공학부를 졸업하고, 나인포유㈜에서 인터넷 지불 대행(PG) 관련 프로그램 개발을 담당하였다.
TAG :
댓글 입력
자료실

최근 본 책0