/**********************************************************************
* FILENAME :        ReadingDataWithExtendedFetch.c
*
* DESCRIPTION :
*       Example uses SQLExtendedFetch() to retrieve data using both row
*           and column wise binding. SQLExtendedFetch is the ODBC version
*           2 way of fetching data, now deprecated by SQLFetch and
*           SQLFetchScroll, but needs supporting. Example is repeated
*           to illustrate how to read data using both column-wise and
*           row-wise binding.
*
* ODBC USAGE :
*       Uses fixed SQL SELECT statement on TestTBL1
*
*       Read records using column-wise binding
*           SQLSetStmtAttr() with SQL_ROWSET_SIZE to indicate a rowset
*                            size of ROWSET_SIZE
*           SQLExecDirect()  to execute the SELECT
*           SQLBindCol()     to bind data to the 4 columns by giving the
*                            address of the start of each column data and
*                            len array of each column
*           SQLExtendedFetch with SQL_FETCH_NEXT to get record sets in
*                            blocks of ROWSET_SIZE until SQL_NO_DATA_FOUND
*                            returned
*                            display results in blocks of ROWSET_SIZE
*
*       Read records using row-wise binding
*           SQLSetStmtAttr()   with SQL_ROWSET_SIZE to set number of rows
*                              to fetch
*           SQLSetStmtAttr()   with SQL_BIND_TYPE to set row size (sizeof
*                              row structure/no of rows)
*           SQLBindCol()       to bind data to the 4 columns by giving the
*                              address of element zero of each column and
*                              length in the row array
*           SQLExtendedFetch() with SQL_FETCH_NEXT to get record sets in
*                              blocks of ROWSET_SIZE until SQL_NO_DATA_FOUND
*                              returned
*                              display results in blocks of ROWSET_SIZE
*/
#include <stdio.h>
#include <stdlib.h>
#include <sql.h>
#include <sqlext.h>
#include <string.h>

#include "util.c"

#define ROWSET_SIZE 3  // How many rows at a time

int main () {

    SQLHENV  henv  = SQL_NULL_HENV;   // Environment
    SQLHDBC  hdbc  = SQL_NULL_HDBC;   // Connection handle
    SQLHSTMT hstmt = SQL_NULL_HSTMT;  // Statement handle

    SQLRETURN retcode;

    SQLCHAR statement[]="SELECT FirstName,LastName,Address,City FROM TestTBL1";

    // Column-wise binding data areas
    // Uses Arrays of values and lengths
    // Column 1    Column2     ... Column n
    // |value|len| |value|len| ... |value|len|
    // |value|len| |value|len| ... |value|len|
    // |.....|...| |.....|...| ... |.....|...|
    // |.....|...| |.....|...| ... |.....|...|
    // |.....|...| |.....|...| ... |.....|...|
    // |value|len| |value|len| ... |value|len|

    SQLCHAR         FirstName[ROWSET_SIZE][255]; // value/length columns
    SQLLEN          FirstName_l[ROWSET_SIZE];
    SQLCHAR         LastName[ROWSET_SIZE][255];
    SQLLEN          LastName_l[ROWSET_SIZE];
    SQLCHAR         Address[ROWSET_SIZE][255];
    SQLLEN          Address_l[ROWSET_SIZE];
    SQLCHAR         City[ROWSET_SIZE][255];
    SQLLEN          City_l[ROWSET_SIZE];

    // Row-wise binding
    // Uses an array of length/value pairs
    //        Column 1  Column 2                      Column n
    // Row 1 -> |Len|Value|Len|Value|...|.....|...|.....|Len|Value|
    // Row 2 -> |Len|Value|Len|Value|...|.....|...|.....|Len|Value|
    // ...   -> |Len|Value|Len|Value|...|.....|...|.....|Len|Value|
    // ...   -> |Len|Value|Len|Value|...|.....|...|.....|Len|Value|
    // Row n -> |Len|Value|Len|Value|...|.....|...|.....|Len|Value|

    struct {
        SQLLEN          FirstName_l;          // length/value rows
        SQLCHAR         FirstName[255];
        SQLLEN          LastName_l;
        SQLCHAR         LastName[255];
        SQLLEN          Address_l;
        SQLCHAR         Address[255];
        SQLLEN          City_l;
        SQLCHAR         City[255];
    } Row[ROWSET_SIZE];

    SQLUSMALLINT     Row_Stat[ROWSET_SIZE];         // array of length/value pairs

    // working vars
    SQLULEN          pcrow;
    int              i, row, max;
    char padding[] = "          ";
    char * val;
    char plural[] = " ";

    //
    // Column-wise binding
    //
    retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_ENV)",
                henv, SQL_HANDLE_ENV);

    retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
                                        (SQLCHAR *)(void*)SQL_OV_ODBC3, -1);
    CHECK_ERROR(retcode, "SQLSetEnvAttr(SQL_ATTR_ODBC_VERSION)",
                henv, SQL_HANDLE_ENV);

    retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQLAllocHandle)",
                hdbc, SQL_HANDLE_DBC);

    retcode = SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)10, 0);
    CHECK_ERROR(retcode, "SQLSetConnectAttr(SQL_LOGIN_TIMEOUT)",
                hdbc, SQL_HANDLE_DBC);

    retcode = SQLConnect(hdbc, (SQLCHAR*) "DATASOURCE", SQL_NTS,
                                        (SQLCHAR*) NULL, 0, NULL, 0);
    CHECK_ERROR(retcode, "SQLConnect(DATASOURCE)",
                hdbc, SQL_HANDLE_DBC);

    retcode = SQLAllocHandle( SQL_HANDLE_STMT, hdbc, &hstmt);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_ENV)",
                hstmt, SQL_HANDLE_STMT);

    ///
    // NOTE: THIS IS THE ODBC VERSION 2 WAY
    // Retrieve record using Column-wise binding.
    // Uses SQL_ROWSET_SIZE to do this. Must assume
    // column binding unless told otherwise(?)
    //
    // Tell SQLExtendedFetch how many rows to fetch
    //
    retcode = SQLSetStmtAttr(hstmt, SQL_ROWSET_SIZE, (void*) ROWSET_SIZE, 0);
    CHECK_ERROR(retcode, "SQLSetStmtAttr(SQL_ROWSET_SIZE)",
                hstmt, SQL_HANDLE_STMT);

    retcode = SQLExecDirect(hstmt, statement, SQL_NTS);
    CHECK_ERROR(retcode, "SQLExecDirect()",
                hstmt, SQL_HANDLE_STMT);

    retcode = SQLBindCol(hstmt, 1, SQL_C_CHAR,
                         (SQLPOINTER) FirstName, 255, (SQLLEN *) &FirstName_l);
    CHECK_ERROR(retcode, "SQLBindCol(1)", hstmt, SQL_HANDLE_STMT);

    retcode = SQLBindCol(hstmt, 2, SQL_C_CHAR,
                         (SQLPOINTER) LastName, 255, (SQLLEN *) &LastName_l);
    CHECK_ERROR(retcode, "SQLBindCol(2)", hstmt, SQL_HANDLE_STMT);

    retcode = SQLBindCol(hstmt, 3, SQL_C_CHAR,
                         (SQLPOINTER) Address, 255, (SQLLEN *) &Address_l);
    CHECK_ERROR(retcode, "SQLBindCol(3)", hstmt, SQL_HANDLE_STMT);

    retcode = SQLBindCol(hstmt, 4, SQL_C_CHAR,
                         (SQLPOINTER) City, 255, (SQLLEN *) &City_l);
    CHECK_ERROR(retcode, "SQLBindCol(4)", hstmt, SQL_HANDLE_STMT);

    /* Fetch ROWSET_SIZE rows at a time, and display */

    max=strlen(padding);

    printf("\nColumn Based Results ... \n");
    printf("\nRow FirstName LastName  Address   City");
    printf("\n--- --------- --------- --------- -------\n");
    row=1;
    while ((retcode = SQLExtendedFetch(hstmt,
                    SQL_FETCH_NEXT, 0, &pcrow, Row_Stat)) == SQL_SUCCESS) {

        strcpy(plural, "s");
        if (pcrow==1) strcpy(plural, "");
        if (pcrow<ROWSET_SIZE) {
            printf("Last %i Row%s \n", (int)pcrow, plural);
        }
        else {
            printf("Next %i Row%s \n", (int)pcrow, plural);
        }

        for (i = 0; i < pcrow; i++) {
            val = itoa(row++);
            padOut(val, padding, max-6);
            printf("%s%s", val, padding);
            free (val);
            padOut (rtrim(FirstName[i], ' '), padding, max);
            printf("%s%s", FirstName[i], padding);
            padOut (rtrim(LastName[i], ' '), padding, max);
            printf("%s%s", LastName[i],padding);
            padOut (rtrim(Address[i], ' '), padding, max);
            printf("%s%s", Address[i], padding);
            padOut (rtrim(City[i], ' '), padding, max);
            printf("%s%s\n", City[i],padding);
        }

        //
        // If we read fewer rows than the ROWSET, we've finished
        //
        if (pcrow < ROWSET_SIZE)
            break;
    }                           /* endwhile */

    // Last status should be SQL_NO_DATA_FOUND
    if (retcode != SQL_NO_DATA_FOUND) {
        CHECK_ERROR(retcode, "SQLExtendedFetch(SQL_FETCH_NEXT)",
                    hstmt, SQL_HANDLE_STMT);
    }

    retcode = SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

    //
    // NOTE: THIS IS THE ODBC VERSION 2 WAY
    // Retrieve same data using Row-wise binding
    // Uses SQL_ROWSET_SIZE and SQL_BIND_TYPE to
    // do this.
    //

    //
    // Row-wise binding
    //
    retcode = SQLAllocHandle( SQL_HANDLE_STMT, hdbc, &hstmt);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_STMT)",
                hstmt, SQL_HANDLE_STMT);

    // Set maximum number of rows to receive with each extended fetch
    retcode = SQLSetStmtAttr(hstmt, SQL_ROWSET_SIZE, (void*) ROWSET_SIZE, 0);
    CHECK_ERROR(retcode, "SQLSetStmtAttr(SQL_ROWSET_SIZE)",
                hstmt, SQL_HANDLE_STMT);

    // Set SQL_BIND_TYPE to size of one row, used as offset for
    // each bound column
    retcode = SQLSetStmtAttr(hstmt, SQL_BIND_TYPE,
                                    (void*) (sizeof(Row) / ROWSET_SIZE), 0);
    CHECK_ERROR(retcode, "SQLSetStmtAttr(SQL_BIND_TYPE)",
                hstmt, SQL_HANDLE_STMT);

    retcode = SQLExecDirect(hstmt, statement, SQL_NTS);
    CHECK_ERROR(retcode, "SQLExecDirect()", hstmt, SQL_HANDLE_STMT);

    retcode = SQLBindCol(hstmt, 1, SQL_C_CHAR,
                                  (SQLPOINTER) &Row[0].FirstName, 255,
                                  &Row[0].FirstName_l);
    CHECK_ERROR(retcode, "SQLBindCol(1)", hstmt, SQL_HANDLE_STMT);

    retcode = SQLBindCol(hstmt, 2, SQL_C_CHAR,
                                  (SQLPOINTER) Row[0].LastName, 255,
                                  &Row[0].LastName_l);
    CHECK_ERROR(retcode, "SQLBindCol(2)", hstmt, SQL_HANDLE_STMT);

    retcode = SQLBindCol(hstmt, 3, SQL_C_CHAR,
                                  (SQLPOINTER) &Row[0].Address, 255,
                                  &Row[0].Address_l);
    CHECK_ERROR(retcode, "SQLBindCol(3)", hstmt, SQL_HANDLE_STMT);

    retcode = SQLBindCol(hstmt, 4, SQL_C_CHAR,
                                  (SQLPOINTER) Row[0].City, 255,
                                  &Row[0].City_l);
    CHECK_ERROR(retcode, "SQLBindCol(4)", hstmt, SQL_HANDLE_STMT);

    /* Fetch ROWSET_SIZE rows at a time, and display */
    printf("\nRow Based Results ... \n");
    printf("\nRow FirstName LastName  Address   City");
    printf("\n--- --------- --------- --------- -------\n");
    row=1;
    while ((retcode = SQLExtendedFetch(hstmt,
                    SQL_FETCH_NEXT, 0, &pcrow, Row_Stat)) == SQL_SUCCESS) {

        strcpy(plural, "s");
        if (pcrow==1) strcpy(plural, "");
        if (pcrow<ROWSET_SIZE) {
            printf("Last %i Row%s \n", (int)pcrow, plural);
        }
        else {
            printf("Next %i Row%s \n", (int)pcrow, plural);
        }

        for (i = 0; i < pcrow; i++) {
            val = itoa(row++);
            padOut(val, padding, max-6);
            printf("%s%s", val, padding);
            free (val);
            padOut (rtrim(Row[i].FirstName, ' '), padding, max);
            printf("%s%s", Row[i].FirstName, padding);
            padOut (rtrim(Row[i].LastName, ' '), padding, max);
            printf("%s%s", Row[i].LastName,padding);
            padOut (rtrim(Row[i].Address, ' '), padding, max);
            printf("%s%s", Row[i].Address, padding);
            padOut (rtrim(Row[i].City, ' '), padding, max);
            printf("%s%s\n", Row[i].City,padding);
        }

        //
        // If we read fewer rows than the ROWSET, we've finished
        //
        if (pcrow < ROWSET_SIZE)
            break;
    }

    // Last status should be SQL_NO_DATA_FOUND
    if (retcode != SQL_NO_DATA_FOUND) {
        CHECK_ERROR(retcode, "SQLExtendedFetch(SQL_FETCH_NEXT)", hstmt, SQL_HANDLE_STMT);
    }

exit:

    printf ("\nComplete.\n");

    // Free handles
    // Statement
    if (hstmt != SQL_NULL_HSTMT)
        SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

    // Connection
    if (hdbc != SQL_NULL_HDBC) {
        SQLDisconnect(hdbc);
        SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
    }

    // Environment
    if (henv != SQL_NULL_HENV)
        SQLFreeHandle(SQL_HANDLE_ENV, henv);

    return 0;
}

Further information