/**********************************************************************
* FILENAME :        CopyDescriptor.c
*
* DESCRIPTION :
*       Example illustrates use of SQLCopyDesc to copy one table to another.
*       This is done by copying the descriptors of a SELECT statement to the
*       descriptors of an INSERT statement, with a loop which performs
*       SELECT and INSERT for all the records.
*
*       Basically the rows of the SELECT become the PARAMS of the INSERT
*
* ODBC USAGE :
*       SQLAllocHandle  to allocate two statement handles,
                        one a SELECT and one an INSERT
*       SQLGetStmtAttr  to obtain the ARD and IRD handles of the SELECT
*       SQLGetStmtAttr  to obtain the APD and IPD handles of the SELECT
*       SQLSetStmtAttr  with SQL_ATTR_ROW_BIND_TYPE to define ROW binding on
*                       the SELECT
*       SQLSetStmtAttr  with SQL_ATTR_ROW_ARRAY_SIZE to define the no of records
*                       on the SELECT
*       SQLSetStmtAttr  with SQL_ATTR_ROWS_FETCHED_PTR to define a variable into
*                       which the num of rows fetched are returned
*       SQLExecDirect   to execute a SELECT without need to be prepared
*       SQLBindCol      to bind the 4 columns in the SELECT
*       SQLCopyDesc     to copy the SELECT ARD descriptor to the INSERT APD
*                       descriptor
*       SQLCopyDesc     to copy the SELECT IRD descriptor to the INSERT IPD
*                       descriptor
*       SQLSetStmtAttr  to define the same status array for the
*                       SELECT and INSERT
*       SQLSetDescField to set the parameter type to SQL_PARAM_INPUT on each
*                       of the fields for the INSERT
*       SQLPrepare      to prepare the INSERT
*       SQLFetchScroll  on the SELECT followed by SQLExecute on the INSERT and
*                       SQLFetch until SQL_NO_DATA returned
*
*       The example (based on SQL SERVER) uses two active statements at the
*       same time and needs the Mars_Connection setting in the odbc.ini file
*       set to yes. This allowes Multiple Active Result Sets to work (for
*       concurrent SELECTs and INSERTs). MARS was new in SQLServer 2005.
*/

#include <stdio.h>
#include <stdlib.h>
#include <sql.h>
#include <sqlext.h>
#include <string.h>

#include "util.c"

#define ROWS 10

#define BOOKMARK_LEN  10
#define PERSONID_LEN  2
#define LASTNAME_LEN  255
#define FIRSTNAME_LEN 255
#define ADDRESS_LEN 255
#define CITY_LEN  255

SQLRETURN retcode;

// Person row (same as TestTBL1) minus PersonID identity field
typedef struct tagCustStruct {
    SQLCHAR         FirstName[255];
    SQLLEN          lenFirstName;
    SQLCHAR         LastName[255];
    SQLLEN          lenLastName;
    SQLCHAR         Address[255];
    SQLLEN          lenAddress;
    SQLCHAR         City[255];
    SQLLEN          lenCity;
} CustStruct;

int main () {

    CustStruct      CustArray[ROWS];            // rowset buffer
    SQLUSMALLINT    sts_ptr[ROWS];              // status pointer

    SQLHDESC        hArd0, hIrd0, hApd1, hIpd1;

    SQLHENV  henv   = SQL_NULL_HENV;     // Environment
    SQLHDBC  hdbc   = SQL_NULL_HDBC;     // Connection handle
    SQLHSTMT hstmt0 = SQL_NULL_HSTMT;    // Statement handle
    SQLHSTMT hstmt1 = SQL_NULL_HSTMT;    // Statement handle
    SQLRETURN retcode;

    SQLLEN RowsFetched = 0, params_processed = 0;
    int i;

    // Allocate Environment Handle
    retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_ENV)",
                henv, SQL_HANDLE_ENV);

    // Set ODBC version
    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);

    // Allocate Connection Handle
    retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_DBC)",
                hdbc, SQL_HANDLE_DBC);

    // Set Login Timeout
    retcode = SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)10, 0);
    CHECK_ERROR(retcode, "SQLSetConnectAttr(SQL_LOGIN_TIMEOUT)",
                hdbc, SQL_HANDLE_DBC);

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

    // Allocate Statement 0 Handle (For Select)
    retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt0);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_STMT0)",
                hstmt0, SQL_HANDLE_STMT);

    // Allocate Statement 1 Handle (For Insert)
    retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt1);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_STMT1)",
                hstmt1, SQL_HANDLE_STMT);

    // Get the ARD and IRD row descriptors for hstmt0
    retcode = SQLGetStmtAttr(hstmt0, SQL_ATTR_APP_ROW_DESC, &hArd0, 0, NULL);
    CHECK_ERROR(retcode, "SQLGetStmtAttr(SQL_ATTR_APP_ROW_DESC)",
                hstmt0, SQL_HANDLE_STMT);

    retcode = SQLGetStmtAttr(hstmt0, SQL_ATTR_IMP_ROW_DESC, &hIrd0, 0, NULL);
    CHECK_ERROR(retcode, "SQLGetStmtAttr(SQL_ATTR_IMP_ROW_DESC)",
                hstmt0, SQL_HANDLE_STMT);

    // Get the APD and IPD param descriptors for hstmt1
    retcode = SQLGetStmtAttr(hstmt1, SQL_ATTR_APP_PARAM_DESC, &hApd1, 0, NULL);
    CHECK_ERROR(retcode, "SQLGetStmtAttr(SQL_ATTR_APP_PARAM_DESC)",
                hstmt1, SQL_HANDLE_STMT);

    retcode = SQLGetStmtAttr(hstmt1, SQL_ATTR_IMP_PARAM_DESC, &hIpd1, 0, NULL);
    CHECK_ERROR(retcode, "SQLGetStmtAttr(SQL_ATTR_IMP_PARAM_DESC)",
                hstmt1, SQL_HANDLE_STMT);

    // Use row-wise binding on hstmt0 to fetch rows
    retcode = SQLSetStmtAttr(hstmt0, SQL_ATTR_ROW_BIND_TYPE,
                            (SQLPOINTER) sizeof(CustStruct), 0);
    CHECK_ERROR(retcode, "SQLSetStmtAttr(SQL_ATTR_ROW_BIND_TYPE)",
                hstmt0, SQL_HANDLE_STMT);

    // Set rowset size for hstmt0
    retcode = SQLSetStmtAttr(hstmt0, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) ROWS, 0);
    CHECK_ERROR(retcode, "SQLSetStmtAttr(SQL_ATTR_ROW_ARRAY_SIZE)",
                hstmt0, SQL_HANDLE_STMT);

    // Rows fetched
    retcode = SQLSetStmtAttr(hstmt0, SQL_ATTR_ROWS_FETCHED_PTR, &RowsFetched,0);
    CHECK_ERROR(retcode, "SQLSetStmtAttr(SQL_ATTR_ROWS_FETCHED_PTR)",
                hstmt0, SQL_HANDLE_STMT);

    // Execute a SELECT statement
    retcode = SQLExecDirect(hstmt0,
                            "SELECT FirstName, LastName, Address, City FROM TestTBL1 order by FirstName", SQL_NTS);
    CHECK_ERROR(retcode, "SQLExecDirect(SQL_HANDLE_STMT0)",
                hstmt0, SQL_HANDLE_STMT);

    // Bind columns for reading records
    // Bind COL 1 - FirstName
    retcode = SQLBindCol(hstmt0, 1, SQL_C_CHAR,
                         &CustArray[0].FirstName,
                         sizeof(CustArray[0].FirstName),
                         &CustArray[0].lenFirstName);
    CHECK_ERROR(retcode, "SQLBindCol(1)",
                hstmt0, SQL_HANDLE_STMT);

    // Bind COL 2 - LastName
    retcode = SQLBindCol(hstmt0, 2, SQL_C_CHAR,
                         &CustArray[0].LastName,
                         sizeof(CustArray[0].LastName),
                         &CustArray[0].lenLastName);
    CHECK_ERROR(retcode, "SQLBindCol(2)",
                hstmt0, SQL_HANDLE_STMT);

    // Bind Col 3- Address
    retcode = SQLBindCol(hstmt0, 3, SQL_C_CHAR,
                         &CustArray[0].Address,
                         sizeof(CustArray[0].Address),
                         &CustArray[0].lenAddress);
    CHECK_ERROR(retcode, "SQLBindCol(3)",
                        hstmt0, SQL_HANDLE_STMT);

    // Bind Col 4 - City
    retcode = SQLBindCol(hstmt0, 4, SQL_C_CHAR,
                         &CustArray[0].City,
                         sizeof(CustArray[0].City),
                         &CustArray[0].lenCity);
    CHECK_ERROR(retcode, "SQLBindCol(4)",
                hstmt0, SQL_HANDLE_STMT);

    // Perform parameter bindings on hstmt1.
    // Copy SELECT ARD to INSERT APD (App RECORD descriptor
    // becomes App PARAM descriptor)
    retcode = SQLCopyDesc(hArd0, hApd1);
    if ( (retcode != SQL_SUCCESS) &&
         (retcode != SQL_SUCCESS_WITH_INFO) ) {
        extract_error("SQLCopyDesc(hArd0, hApd1, 0)", hArd0,
                      SQL_HANDLE_DESC);
        extract_error("SQLCopyDesc(hArd0, hApd1, 1)", hApd1,
                      SQL_HANDLE_DESC);
        goto exit;
    }

    // Copy SELECT IRD to INSERT IPD
    // (Imp RECORD descriptor becomes Imp PARAM descriptor)
    retcode = SQLCopyDesc(hIrd0, hIpd1);
    if ( (retcode != SQL_SUCCESS) &&
         (retcode != SQL_SUCCESS_WITH_INFO) ) {
        extract_error("SQLCopyDesc(hIrd0, hIpd1, 0)", hIrd0,
                      SQL_HANDLE_DESC);
        extract_error("SQLCopyDesc(hIrd0, hIpd1, 1)", hIpd1,
                     SQL_HANDLE_DESC);
        goto exit;
    }

    // Set the ARRAY_STATUS_PTR field of IRD (ROW STATUS Array for SELECT)
    retcode = SQLSetStmtAttr(hstmt0, SQL_ATTR_ROW_STATUS_PTR, sts_ptr,
                             SQL_IS_POINTER);
    CHECK_ERROR(retcode, "SQLSetStmtAttr(SQL_ATTR_ROW_STATUS_PTR)",
                hstmt0, SQL_HANDLE_STMT);

    // Set the ARRAY_STATUS_PTR field of APD to be the same as that in IRD
    // (ROW STATUS Array for INSERT)
    retcode = SQLSetStmtAttr(hstmt1, SQL_ATTR_PARAM_OPERATION_PTR, sts_ptr,
                             SQL_IS_POINTER);
    CHECK_ERROR(retcode, "SQLSetStmtAttr(PARAM_OPERATION_PTR)",
                hstmt1, SQL_HANDLE_STMT);

    // Set the hIpd1 record input parameters
    // Set Desc Field 1 as INPUT in INSERT IPD
    retcode = SQLSetDescField(hIpd1, 1, SQL_DESC_PARAMETER_TYPE,
                              (SQLPOINTER)SQL_PARAM_INPUT, SQL_IS_INTEGER);
    CHECK_ERROR(retcode, "SQLSetDescField(DESC_PARAMETER_TYPE 1)",
                hstmt1, SQL_HANDLE_STMT);

    // Set Desc Field 2 as INPUT in INSERT IPD
    retcode = SQLSetDescField(hIpd1, 2, SQL_DESC_PARAMETER_TYPE,
                              (SQLPOINTER)SQL_PARAM_INPUT, SQL_IS_INTEGER);
    CHECK_ERROR(retcode, "SQLSetDescField(DESC_PARAMETER_TYPE 2)",
                hstmt1, SQL_HANDLE_STMT);

    // Set Desc Field 3 as INPUT in INSERT IPD
    retcode = SQLSetDescField(hIpd1, 3, SQL_DESC_PARAMETER_TYPE,
                              (SQLPOINTER)SQL_PARAM_INPUT, SQL_IS_INTEGER);
    CHECK_ERROR(retcode, "SQLSetDescField(DESC_PARAMETER_TYPE 3)",
                hstmt1, SQL_HANDLE_STMT);

    // Set Desc Field 4 as INPUT in INSERT IPD
    retcode = SQLSetDescField(hIpd1, 4, SQL_DESC_PARAMETER_TYPE,
                              (SQLPOINTER)SQL_PARAM_INPUT, SQL_IS_INTEGER);
    CHECK_ERROR(retcode, "SQLSetDescField(DESC_PARAMETER_TYPE 4)",
                hstmt1, SQL_HANDLE_STMT);

    // Prepare an INSERT statement on hstmt1.
    // TestTBL1Copy is a copy of TestTBL1
    retcode = SQLPrepare(hstmt1, "INSERT INTO TestTBL1Copy (FirstName, LastName, Address, City) VALUES (?, ?, ?, ?)", SQL_NTS);
    CHECK_ERROR(retcode, "SQLPrepare(hstmt1)",
                hstmt1, SQL_HANDLE_STMT);

    // In a loop, fetch a rowset, and copy the fetched rowset to TestTBL1Copy
    // Fetch initial rowset from SELECT
    retcode = SQLFetchScroll(hstmt0, SQL_FETCH_NEXT, 0);

    // Loop while data and success
    while (SQL_SUCCEEDED(retcode)) {

        printf ("Rows Fetched : %i\n", (int) RowsFetched);

        for (i=0;i<ROWS;i++) {
            printf ("%i", sts_ptr[i]);
            if (i!=ROWS-1)
                printf (", ");
            else
                printf ("\n");
        }

        for (i=0;i<RowsFetched;i++) {
            printf ("Record %i, Status %i, First Field - %.10s\n",
                    i+1, sts_ptr[i], CustArray[i].FirstName);
        }

        // The row status array (returned by the FetchScroll of hstmt0)
        // is used as input status in the APD of hstmt1 and hence determines
        // which elements of the rowset buffer are inserted.

        // Execute INSERT
        retcode = SQLExecute(hstmt1);
        CHECK_ERROR(retcode, "SQLExecute(hstmt1)",
                    hstmt1, SQL_HANDLE_STMT);
        printf ("SQLExecute(hstmt1) OK\n");

        // Fetch Scroll next rowset from SELECT
        retcode = SQLFetchScroll(hstmt0, SQL_FETCH_NEXT, 0);
        if (retcode == SQL_NO_DATA) {
            printf ("SQL_NO_DATA\n");
        } else {
            CHECK_ERROR(retcode, "SQLFetchScroll(SQL_HANDLE_STMT 0)",
                        hstmt0, SQL_HANDLE_STMT);
            printf ("SQLFetchScroll(hstmt0) OK\n");
        }
    }

exit:

    printf ("\nComplete.\n");

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

    // Statement 1
    if (hstmt1 != SQL_NULL_HSTMT)
        SQLFreeHandle(SQL_HANDLE_STMT, hstmt1);

    // 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