/**********************************************************************
* FILENAME :    CallSPEditRecord.c
*
* DESCRIPTION :
*               Illustrates using a stored procedures to perform several
*               actions relating to updating a record into a table. It
*               shows how to set up input parameters and how to return a
*               status value from the procedure.
*
*               Calls a stored procedure as follows:
*               {? = CALL Edit_Record (?, ?, ?, ?, ?, ?)}
*
* ODBC USAGE :
*               Drops and recreates a procedure Edit_Record().
*               Confirms the procedures exist via SQLProcedures ()
*               Uses SQLBindParameter() to bind data to the 7
*               parameters used in the stored procedure.
*
*	            7 Parameters - 1   - Proc return value           - RETURNED
*	                           2   - Action - READ, UPDT         - INPUT
*	                           3   - Record ID                   - INPUT
*	                           4-7 - if action is READ
*	                               - old column values           - OUTPUT
*	                           4-7 - if action is UPDT
*	                               - new column values           - INPUT
*
*	            Illustrates SELECT, INSERT, UPDATE and DELETE actions  via
*	            a stored procedure Edit_Record
*	 			Uses SQLBindParameter() to bind the 7 parameters used with
*	 			the stored procedure Edit_Record() and calls SQLPrepare() to
*	 			make ready for execution.
*
*	 			Uses the notion of current Person ID as current record
*	 			(a value of -1 indicates none). Based on current record and
*	 			action, the program loops executing the stored procedure to
*	 			either Select a specific record, select all records, Update
*	 			or Delete the current record or Insert a new one.
*
*				Selects are either for a single record, or all records.
*				Following a single record select, the record becomes the
*				current record. Following an all records select, there is no
*				current record.
*	 			An insert does not rely on having a current record, but the
*	 			stored procedure returns the record id of the new record, which
*	 			becomes the current record.
*	 			Update relies on having a current record which is kept the same
*	 			during and after the update.
*	 			Delete relies on having a current record but after the delete,
*	 			there is no current record.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sql.h>
#include <sqlext.h>
#include <string.h>

#include "util.c"
#include "CallSPUtil.c"

#define SEL 0 // Action passed to Process_Record() to SELECT single record
#define UPD 1 // Action passed to Process_Record() to UPDATE a record
#define INS 2 // Action passed to Process_Record() to INSERT record
#define DEL 3 // Action passed to Process_Record() to DELETE a record
#define ALL 4 // Action passed to Process_Record() to SELECT ALL records

//
// Function to prompt user for action or record ID. currPID is the current
// record ID, which, if not available (i.e. currPID = -1) removes UPDATE and
// DELETE options (which are dependent on a current record ID) but allows
// SELECT, SELECT all and INSERT (which are not).
//
int getAction (int * currPID) {

    int action=SEL;
    char reply[5];

    printf ("\nNext Action: QUIT (Q)\n"
			"SELECT (Rec No), SELECT All (A), INSERT (I)");
    if (*currPID!=-1) {
        printf (", UPDATE (U), DELETE (D)");
    }
    printf (" ?\n  : ");

    reply[0] = ' ';
    fgets(reply, 5, stdin);

    action=SEL;
    if (atoi (reply)==0) {		// Number not entered
        switch (reply[0]) {
            case 'A':
            case 'a':
                action=ALL;		// SELECT all Records
                break;
            case 'I':
            case 'i':
                action=INS;		// INSERT new record
                break;
            case 'U':
            case 'u':
                action=UPD;		// UPDATE current record
                break;
            case 'D':
            case 'd':
                action=DEL;		// DELETE current record
                break;
            case 'Q':
            case 'q':
            default:
                *currPID=0;		// Quit application
                break;
        }
    } else {
        *currPID=(int) atoi (reply);
    }

    return action;
}

int main () {

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

    // Declare call to stored proc with 7 parameter markers
    // Uses 7 Parameters - 1   - Proc return value   	- RETURNED
    //                     2   - Action - See below  	- INPUT
    //                     3   - RecordID             	- INPUT/OUTOUT
    //                     4-7 - if action is SELECT/SELECT ALL
    //                         - old column values   	- UNUSED
    //							  SELECT is via WHERE RecordID
    //							  SELECT ALL uses no WHERE
    //                     4-7 - if action is UPDATE or INSERT
    //                         - new column values      - INPUT
    //							  UPDATE/INSERT is via WHERE RecordID
    //		  	   4-7 - if action is DELETE    - UNUSED
    //		              				- Delete is via WHERE RecordID
    SQLCHAR * strCallSP   = "{? = CALL Edit_Record (?, ?, ?, ?, ?, ?)}";
    char * strProcName = "Edit_Record";
    char reply[5];

    SQLSMALLINT columns; 				// Number of columns in result-set
    int i, count;

    // Stored proc parameters, input/output variables and return variable.
    SQLCHAR     strFirstName[FIRSTNAME_LEN], strLastName[LASTNAME_LEN];
    SQLCHAR     strAddress[ADDRESS_LEN], strCity[CITY_LEN];
    SQLLEN      lFirstName, lLastName, lAddress, lCity;
    SQLINTEGER	retParam, action, pPersonID=-1, currPID=-1;
    SQLLEN      laction, lretParam;

    SQLLEN  indicator, rowCount;
    char *buf=malloc (255);

    // Allocate an environment handle
    retcode=SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
    CHECK_ERROR(retcode, "SQLAllocHandle(ENV)", henv, SQL_HANDLE_ENV);

    // Set ODBC Version
    retcode=SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
                          (void *) SQL_OV_ODBC3, 0);
    CHECK_ERROR(retcode, "SQLSetEnvAttr(SQL_ATTR_ODBC_VERSION)",
                henv, SQL_HANDLE_ENV);

    // Allocate a connection handle
    retcode=SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_DBC)",
                hdbc, SQL_HANDLE_DBC);

    // Connect to data source
    retcode=SQLDriverConnect(hdbc, NULL, "DSN=SQLSRV;", SQL_NTS, NULL,
                             0, NULL, SQL_DRIVER_COMPLETE);
    CHECK_ERROR(retcode, "SQLDriverConnect(SQLSRV)",
                hdbc, SQL_HANDLE_DBC);

    // Drop previous procedure called Edit_Record
    retcode = DropProcedure (hdbc, strProcName);

    // Create new procedure called Edit_Record
    retcode = CreateProcedure (hdbc, strProcName);

    // Use SQLPRocedures() to show Edit_Record it now available
    retcode = ListProcedure (hdbc, strProcName);

    // Allocate a statement handle
    retcode=SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
    CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_STMT)",
                hstmt, SQL_HANDLE_STMT);

    // Bind parameters. The order is the order in which they appear
    // 1st Parameter marker is stored procedure return parameter
    retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_OUTPUT, SQL_C_LONG,
                               SQL_INTEGER, 0, 0, &retParam, 0, &lretParam);
    CHECK_ERROR(retcode, "SQLBindParameter(1)", hstmt, SQL_HANDLE_STMT);

    // 2nd Parameter marker is action, passed as INPUT
    retcode = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_LONG,
                               SQL_INTEGER, 0, 0, &action, 0, &laction);
    CHECK_ERROR(retcode, "SQLBindParameter(2)", hstmt, SQL_HANDLE_STMT);

    // 3rd Parameter marker is pPersonID, passed as INPUT/OUTPUT
    // Input for SELECT, UPDATE, and DELETE
    // Output for INSERT (returns record inserted)
    retcode = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT_OUTPUT, SQL_C_LONG,
                               SQL_INTEGER, 0, 0, &pPersonID, 0, NULL);
    CHECK_ERROR(retcode, "SQLBindParameter(3)", hstmt, SQL_HANDLE_STMT);

    // 4th Parameter marker is FirstName passed as INPUT
    // Input for INSERT and UPDATE
    retcode = SQLBindParameter(hstmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR,
                               SQL_CHAR, FIRSTNAME_LEN, 0, strFirstName,
                               FIRSTNAME_LEN, &lFirstName);
    CHECK_ERROR(retcode, "SQLBindParameter(4)", hstmt, SQL_HANDLE_STMT);

    // 5th Parameter marker is LastName passed as INPUT
    // Input for INSERT and UPDATE
    retcode = SQLBindParameter(hstmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR,
                               SQL_CHAR, LASTNAME_LEN, 0, strLastName,
                               LASTNAME_LEN, &lLastName);
    CHECK_ERROR(retcode, "SQLBindParameter(5)", hstmt, SQL_HANDLE_STMT);

    // 6th Parameter marker is Address passed as INPUT
    // Input for INSERT and UPDATE
    retcode = SQLBindParameter(hstmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR,
                               SQL_CHAR, ADDRESS_LEN, 0, strAddress,
                               ADDRESS_LEN, &lAddress);
    CHECK_ERROR(retcode, "SQLBindParameter(6)", hstmt, SQL_HANDLE_STMT);

    // 7th Parameter marker is City passed as INPUT
    // Input for INSERT and UPDATE
    retcode = SQLBindParameter(hstmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR,
                               SQL_CHAR, CITY_LEN, 0, strCity,
                               CITY_LEN, &lCity);
    CHECK_ERROR(retcode, "SQLBindParameter(7)", hstmt, SQL_HANDLE_STMT);


    // Prepare statement with procedure call to Edit_Record()
    retcode = SQLPrepare (hstmt, strCallSP, SQL_NTS);
    CHECK_ERROR(retcode, "SQLPrepare(SQL_HANDLE_STMT)",
                hstmt, SQL_HANDLE_STMT);

    // Get initial action. Returns:
    // 1. Record number to SELECT or 0 to quit
    // 2. 'A' to SELECT all records
    // 3. 'I' to INSERT new record
    action=getAction ((int *) &currPID);

    // Loop while we have a current record.
    while (currPID != 0) {

        pPersonID=currPID;
        if (action != SEL && action != ALL) {
    	    // Not selecting so action is either:
            // 			UPD - Update current record
            //          INS - INSERT new record
            //          DEL - DELETE current record

            if (action==UPD) {
                // Updating current record
    		// Prompt for replacment column values
                printf ("\nPersonID  : %i", pPersonID);
                printf ("\nFirstName : %.10s", strFirstName);
                getStr (" - ", strFirstName, FIRSTNAME_LEN, 'N');
                printf ("LastName  : %.10s", strLastName);
                getStr (" - ", strLastName, LASTNAME_LEN, 'N');
                printf ("Address   : %.10s", strAddress);
                getStr (" - ", strAddress, ADDRESS_LEN, 'N');
                printf ("City      : %.10s", strCity);
                getStr (" - ", strCity, CITY_LEN, 'N');
            }

            if (action==INS) {
    		// Inserting new record
    		// prompt for new record column value
                getStr ("\nFirstName ", strFirstName, FIRSTNAME_LEN, 'N');
                getStr ("LastName  ", strLastName, LASTNAME_LEN, 'N');
                getStr ("Address   ", strAddress, ADDRESS_LEN, 'N');
                getStr ("City      ", strCity, CITY_LEN, 'N');
            }

            if (action==UPD || action==INS) {
    		// If updating or insert, set SQLBindParamater()
    		// StrLen_or_IndPtr values
                lFirstName=strlen(strFirstName);
                lLastName=strlen(strLastName);
                lAddress=strlen(strAddress);
                lCity=strlen(strCity);
            } else {
    		// If deleting current record, prompt to confirm
                printf ("\nDelete Record %i, confirm Y/N ? : ", (int) currPID);
                reply[0] = ' ';
                fgets(reply, 3, stdin);
                if (reply[0]=='Y' || reply[0]=='y') {
                    action=DEL;
    		}
            }

    	    // Execute SP for Update, Insert or Delete
            retcode = SQLExecute (hstmt);
            CHECK_ERROR(retcode, "SQLExecute(SQL_HANDLE_STMT)",
                       hstmt, SQL_HANDLE_STMT);

            retcode = SQLMoreResults(hstmt);
	    if (retcode != SQL_NO_DATA) {
		CHECK_ERROR(retcode, "SQLMoreResults(SQL_HANDLE_STMT)",
                           hstmt, SQL_HANDLE_STMT);
		goto exit;
	    }

            if (action==INS) {
                currPID=pPersonID;
    	    }
            action=SEL;
        }

    	// Clear buffers
        memset (strFirstName, ' ', FIRSTNAME_LEN);
        memset (strLastName, ' ', LASTNAME_LEN);
        memset (strAddress, ' ', ADDRESS_LEN);
        memset (strCity, ' ', CITY_LEN);

    	// Reset buffer lengths
        lFirstName=0;
        lLastName=0;
        lAddress=0;
        lCity=0;

    	// Execute SP for SELECT or SELECT ALL
        retcode = SQLExecute (hstmt);
        CHECK_ERROR(retcode, "SQLExecute(SQL_HANDLE_STMT)",
                    hstmt, SQL_HANDLE_STMT);

    	// Loop until SQLMoreResults() says SQL_NO_DATA
        do {

    	    // Get number of columns in results set
            retcode=SQLNumResultCols(hstmt, &columns);
            CHECK_ERROR(retcode, "SQLNumResultCols()", hstmt, SQL_HANDLE_STMT);
            if (columns > 0) {
    			rowCount=0;
    		// Fetch records in results set
                while (SQLFetch(hstmt) != SQL_NO_DATA) {
            	    // Loop through the columns
    		    rowCount++;
            	    memset (buf, ' ', 255);
                    printf ("\n");

    		    // Get the column data
            	    for (i = 1; i <= columns; i++) {
            		retcode = SQLGetData(hstmt, i, SQL_C_CHAR,
                                             buf, 255, &indicator);
            		if (SQL_SUCCEEDED(retcode)) {
                            switch (i) {
                                case 1:
                                    pPersonID=atoi(buf);
                                break;
                                case 2:
                                    strcpy (strFirstName, buf);
                                break;

                                case 3:
                                    strcpy (strLastName, buf);
                                break;

                                case 4:
                                    strcpy (strAddress, buf);
                                break;

                                case 5:
                                    strcpy (strCity, buf);
                                break;
                            }
            		}
            	    }

    		    // Display column data
                    printf ("\nPersonID  : %i", pPersonID);
                    printf ("\nFirstName : %.20s", strFirstName);
                    printf ("\nLastName  : %.20s", strLastName);
                    printf ("\nAddress   : %.20s", strAddress);
                    printf ("\nCity      : %.20s", strCity);
                }
            }

            // Check we have a current person ID. If no records found or
    	    // action is ALL records, we don't have a current record so
    	    // set Person ID to -1 which will remove the INSERT and UPDATE
    	    // options from getAction().
            if (rowCount==0) {
    		if (action==SEL) {
                    printf ("\nNo Records Matching : %i", (int) pPersonID);
    		}
                // Reset current Person ID
                currPID=-1;
            }
            action=getAction ((int *) &currPID);
    	    if (currPID==0) goto exit;
        } while ((retcode=SQLMoreResults(hstmt)) == SQL_SUCCESS);
    }

exit:

    // Free temporary buffer used for formatting record data
    free (buf);

    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