/**********************************************************************
* FILENAME : BulkOperations.c
*
* DESCRIPTION :
* Example illustrates bulk reads/updates/deletes/inserts using bookmarks
* with SQLBulkOperations()
*
* Keyset-driven - via SQL_ATTR_CURSOR_TYPE
* Row-wise binding - via SQL_ATTR_ROW_BIND_TYPE
* Record array size of 3 - via SQL_ATTR_ROW_ARRAY_SIZE
* Variable length bookmark - via SQL_ATTR_USE_BOOKMARKS
*
* Example used table with no identity field.
*
* ODBC USAGE :
* Allocates a CustStruct array with 12 elements giving 4 groups of
* 3 records.
*
* 1-3 holds current records selected
* 4-6 holds records to be updated
* 7-9 holds records to be inserted
* 9-12 holds records to be deleted
*
* SQLSetStmtAttr - to set SQL_ATTR_CURSOR_TYPE,
* SQL_ATTR_ROW_BIND_TYPE,
* SQL_ATTR_ROW_ARRAY_SIZE,
* SQL_ATTR_USE_BOOKMARKS,
* SQL_ATTR_ROW_STATUS_PTR,
* SQL_ATTR_ROW_BIND_OFFSET_PTR,
* SQL_ATTR_ROWS_FETCHED_PTR,
* SQL_ATTR_CONCURRENCY.
* SQLBindCol - to bind columns into CustArray
* SQLExecDirect - to execute SELECT
* SQLFetchScroll - to retrieve 1st set of up to 3 records
*
* Loops until exit:
* Requests user to select next action from :
* SQL_FETCH_NEXT, SQL_FETCH_PRIOR, SQL_FETCH_FIRST,
* SQL_FETCH_LAST, SQL_FETCH_ABSOLUTE, SQL_FETCH_RELATIVE
* UPDATE_ROW, DELETE_ROW, ADD_ROW, SHOW_ALL or
* SEND_TO_DATA_SOURCE
*
* For SQL_FETCH_NEXT, SQL_FETCH_PRIOR, SQL_FETCH_FIRST,
* SQL_FETCH_LAST, SQL_FETCH_ABSOLUTE, SQL_FETCH_RELATIVE
* Calls SQLFetchScroll() to get the next/prior/first/last etc
* set of 3 records, replacing the first 3 rows in the array
*
* For UPDATE_ROW, DELETE_ROW, ADD_ROW
* Calls GetNewCustData() to request a row to update/delete with
* new values and bookmark, or to add, saving in the appropriate
* part of array prior to sending to DB.
*
* For SHOW_ALL
* Dumps out the current 12 rows in the CustArray, showing what
* is in place for Current/Update/Delete/Add
*
* For SEND_TO_DATA_SOURCE
* For each of the 3 areas with pending data (Update/Delete/Add)
* calls SQLSetStmtAttr with SQL_ATTR_ROW_ARRAY_SIZE so set the
* number of rows in play and SQLBulkOperations to perform bulk
* update
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <sql.h>
#include <sqlext.h>
#include <string.h>
#include "util.c"
#define NEXT 1
#define FIRST 2
#define LAST 3
#define PRIOR 4
#define ABSOLUTE 5
#define RELATIVE 6
#define UPDATE_ROW 100
#define DELETE_ROW 101
#define ADD_ROW 102
#define SEND_TO_DATA_SOURCE 103
#define SHOW_ALL 104
#define CURRENT_OFFSET 0
#define UPDATE_OFFSET 3
#define INSERT_OFFSET 6
#define DELETE_OFFSET 9
#define QUIT 0
#define TRUE 1
#define FALSE 0
#define BOOKMARK_LEN 10
#define PERSONID_LEN 2
#define LASTNAME_LEN 255
#define FIRSTNAME_LEN 255
#define ADDRESS_LEN 255
#define CITY_LEN 255
#define DATA_ARRAY_SIZE 12
// Define structure for data
typedef struct tagCustStruct {
SQLCHAR Bookmark[BOOKMARK_LEN];
SQLLEN BookmarkLen;
SQLUINTEGER PersonID;
SQLLEN PersonIDInd;
SQLCHAR FirstName[255];
SQLLEN FirstNameLenOrInd;
SQLCHAR LastName[255];
SQLLEN LastNameLenOrInd;
SQLCHAR Address[255];
SQLLEN AddressLenOrInd;
SQLCHAR City[255];
SQLLEN CityLenOrInd;
} CustStruct;
// Allocate 12 of CustStruct.
// elements 0-2 are for the current rowset,
// elements 3-5 are for the buffered updates,
// elements 6-8 are for the buffered inserts,
// elements 9-11 are for the buffered deletes.
CustStruct CustArray[DATA_ARRAY_SIZE];
// RowStatusArray values (for reference)
// SQL_ROW_SUCCESS 0
// SQL_ROW_DELETED 1
// SQL_ROW_UPDATED 2
// SQL_ROW_NOROW 3
// SQL_ROW_ADDED 4
// SQL_ROW_ERROR 5
// (ODBCVER >= 0x0300)
// SQL_ROW_SUCCESS_WITH_INFO 6
// SQL_ROW_PROCEED 0
// SQL_ROW_IGNORE 1
SQLUSMALLINT RowStatusArray[DATA_ARRAY_SIZE], Action, RowNum;
SQLLEN NumUpdates = 0, NumInserts = 0, NumDeletes = 0;
SQLLEN BindOffset = 0;
SQLLEN RowsFetched = 0;
SQLLEN Concurrency = SQL_CONCUR_LOCK;
SQLLEN rowCount;
SQLHENV henv = SQL_NULL_HENV; // Environment
SQLHDBC hdbc = SQL_NULL_HDBC; // Connection handle
SQLHSTMT hstmt = SQL_NULL_HSTMT; // Statement handle
SQLRETURN retcode; // Return status
//
// Display customer data array
//
void DisplayCustData(CustStruct *CustArray,
SQLUSMALLINT start,
SQLUSMALLINT rows) {
int i;
char tmp[256];
if (start==CURRENT_OFFSET) printf ("Current Data:\n");
if (start==UPDATE_OFFSET) printf ("Update Data:\n");
if (start==INSERT_OFFSET) printf ("Insert Data:\n");
if (start==DELETE_OFFSET) printf ("Delete Data:\n");
for (i=0; i<rows; i++) {
printf ("Bookmark %.10s, ",
(char *)CustArray[start+i].Bookmark);
printf ("%i ,", (int)CustArray[start+i].PersonID);
printf ("%.10s ,", (char *)CustArray[start+i].FirstName);
printf ("%.10s ,", (char *)CustArray[start+i].LastName);
printf ("%.10s ,", (char *)CustArray[start+i].Address);
printf ("%.10s\n", (char *)CustArray[start+i].City);
}
return;
}
//
// Function to get Action and Row
// Action = 1 - NEXT,2 - FIRST,3 - LAST,4 - PRIOR,5 - ABSOLUTE,6 - RELATIVE,
// 100 - UPDATE_ROW, 101 - DELETE_ROW, 102 - ADD_ROW,
// 103 - SEND_TO_DATA_SOURCE, 104 - SHOW_ALL
int GetAction(CustStruct *CustArray,
SQLUSMALLINT *StatusArray,
SQLUSMALLINT *Action,
SQLUSMALLINT* RowNum) {
char reply;
// Display cust data array
printf ("Current Array: \n");
DisplayCustData(CustArray, CURRENT_OFFSET, 3);
printf ("Update Array :\n");
DisplayCustData(CustArray, UPDATE_OFFSET, 3);
printf ("Insert Array :\n");
DisplayCustData(CustArray, INSERT_OFFSET, 3);
printf ("Delete Array :\n");
DisplayCustData(CustArray, DELETE_OFFSET, 3);
printf ("Action : QUIT(0) NEXT(1) FIRST(2)");
printf ("LAST(3) PRIOR(4) ABS(5) REL(6)\n");
printf ("........ ROW: UPDATE(100) DELETE(101)");
printf ("ADD(102) SEND(103) SHOW(104)\n");
reply=getInt ("Select Action", (int *) Action, 'N', 0);
if ((*Action == ABSOLUTE || *Action == RELATIVE) && *Action != QUIT) {
printf ("RowNum : ");
reply=getInt ("RowNum", (int *) RowNum, 'N', 0);
}
if (*Action == QUIT) return 0; else return 1;
}
//
// Clear Buffers
//
void clearBuffers (CustStruct *CustArray, int start, int rows) {
int i;
for (i=0;i<rows;i++) {
memset(CustArray[start+i].Bookmark, ' ', BOOKMARK_LEN);
CustArray[start+i].PersonID=0;
memset(CustArray[start+i].FirstName, ' ', FIRSTNAME_LEN);
memset(CustArray[start+i].LastName, ' ', LASTNAME_LEN);
memset(CustArray[start+i].Address, ' ', ADDRESS_LEN);
memset(CustArray[start+i].City, ' ', CITY_LEN);
}
}
//
// Modify customer data in row number of CURRENT
//
SQLUSMALLINT GetNewCustData(CustStruct *CustArray,
SQLUSMALLINT destOffset,
SQLUSMALLINT base ) {
int srcOffset;
SQLCHAR strFirstName[FIRSTNAME_LEN];
SQLCHAR strLastName[LASTNAME_LEN];
SQLCHAR strAddress[ADDRESS_LEN];
SQLCHAR strCity[CITY_LEN];
char reply=' ';
printf ("Dest Offset : %i\n", destOffset);
// Update record
if (base==UPDATE_OFFSET) {
reply=getInt ("Enter Src Offset (0,1,2)", &srcOffset, 'N', 0);
if (srcOffset<3) {
//copy CURRENT record to UPDATE area
memcpy(CustArray[destOffset].FirstName,
CustArray[srcOffset].FirstName, FIRSTNAME_LEN);
memcpy(CustArray[destOffset].LastName,
CustArray[srcOffset].LastName, LASTNAME_LEN);
memcpy(CustArray[destOffset].Address,
CustArray[srcOffset].Address, ADDRESS_LEN);
memcpy(CustArray[destOffset].City,
CustArray[srcOffset].City, CITY_LEN);
// Ask for new FirstName
printf ("Current First Name : %.20s\n",
CustArray[srcOffset].FirstName);
reply=getStr ("New First Name ", strFirstName,
sizeof(strFirstName), 'N');
// Move new FirstName into Update area and remove NULL
memset(CustArray[destOffset].FirstName, ' ', FIRSTNAME_LEN);
strcpy (CustArray[destOffset].FirstName, strFirstName);
CustArray[destOffset].FirstName[strlen(strFirstName)]=' ';
// Set lengths for Update to lengths of fields
CustArray[destOffset].FirstNameLenOrInd=FIRSTNAME_LEN;
CustArray[destOffset].PersonIDInd=SQL_COLUMN_IGNORE;
CustArray[destOffset].LastNameLenOrInd=LASTNAME_LEN;
CustArray[destOffset].AddressLenOrInd=ADDRESS_LEN;
CustArray[destOffset].CityLenOrInd=CITY_LEN;
// Return row number of update record
return (SQLUSMALLINT) srcOffset+1;
}
}
// Insert record
if (base==INSERT_OFFSET) {
// Clear insert buffer
clearBuffers (CustArray, destOffset, 1);
// Ask for new FirstName
reply=getStr ("New First Name ", strFirstName,
sizeof(strFirstName), 'N');
// Move new FirstName into Update area and remove NULL
strcpy (CustArray[destOffset].FirstName, strFirstName);
CustArray[destOffset].FirstName[strlen(strFirstName)]=' ';
// Ask for new LastName
reply=getStr ("New Last Name ", strLastName,
sizeof(strLastName), 'N');
// Move new LastName into Update area and remove NULL
strcpy (CustArray[destOffset].LastName, strLastName);
CustArray[destOffset].LastName[strlen(strLastName)]=' ';
// Ask for new Address
reply=getStr ("New Address ", strAddress,
sizeof(strAddress), 'N');
// Move new Address into Insert area and remove NULL
strcpy (CustArray[destOffset].Address, strAddress);
CustArray[destOffset].Address[strlen(strAddress)]=' ';
// Ask for new City
reply=getStr ("New City ", strCity,
sizeof (strCity), 'N');
// Move new City into Insert area and remove NULL
strcpy (CustArray[destOffset].City, strCity);
memset(CustArray[destOffset].FirstName, ' ', FIRSTNAME_LEN);
CustArray[destOffset].City[strlen(strCity)]=' ';
// Set lengths for Insert to lengths of fields
CustArray[destOffset].PersonIDInd=SQL_COLUMN_IGNORE;
CustArray[destOffset].FirstNameLenOrInd=FIRSTNAME_LEN;
CustArray[destOffset].LastNameLenOrInd=LASTNAME_LEN;
CustArray[destOffset].AddressLenOrInd=ADDRESS_LEN;
CustArray[destOffset].CityLenOrInd=CITY_LEN;
// Return row number of update record
return (SQLUSMALLINT) srcOffset+1;
}
// Delete record
if (base==DELETE_OFFSET) {
reply=getInt ("Enter Src Offset (0,1,2)",
&srcOffset, 'N', 0);
if (srcOffset<3) {
// Return row number of record to delete
printf ("Deleting row %i\n", srcOffset+1);
return (SQLUSMALLINT) srcOffset+1;
}
}
return -1;
}
int main () {
int i;
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
CHECK_ERROR(retcode, "SQLAllocHandle(ENV)",
henv, SQL_HANDLE_ENV);
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER*)SQL_OV_ODBC3, 0);
CHECK_ERROR(retcode, "SQLSetEnvAttr(SQL_ATTR_ODBC_VERSION)",
henv, SQL_HANDLE_ENV);
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
CHECK_ERROR(retcode, "SQLAllocHandle(DBC)",
henv, SQL_HANDLE_ENV);
retcode = SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 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(SQL_HANDLE_DBC)",
hdbc, SQL_HANDLE_DBC);
// Set to autocommit
retcode = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT,
(SQLPOINTER)TRUE, 0);
CHECK_ERROR(retcode, "SQLSetConnectAttr(SQL_ATTR_AUTOCOMMIT)",
hdbc, SQL_HANDLE_DBC);
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_STMT)",
hstmt, SQL_HANDLE_STMT);
// Set the following statement attributes:
// SQL_ATTR_CURSOR_TYPE: Keyset-driven
// SQL_ATTR_ROW_BIND_TYPE: Row-wise
// SQL_ATTR_ROW_ARRAY_SIZE: 3
// SQL_ATTR_USE_BOOKMARKS: Use variable-length bookmarks
// SQL_ATTR_ROW_STATUS_PTR: Points to RowStatusArray
// SQL_ATTR_ROW_BIND_OFFSET_PTR: Points to BindOffset
// SQL_ATTR_CONCURRENCY: Sets Concurrency (because default is READ ONLY)
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_TYPE,
(SQLPOINTER)SQL_CURSOR_KEYSET_DRIVEN, 0);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_TYPE,
(SQLPOINTER)sizeof(CustStruct), 0);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE,
(SQLPOINTER)3, 0);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_USE_BOOKMARKS,
(SQLPOINTER)SQL_UB_VARIABLE, 0);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_STATUS_PTR,
RowStatusArray, 0);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_OFFSET_PTR,
&BindOffset, 0);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_ROWS_FETCHED_PTR,
&RowsFetched,0);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_CONCURRENCY,
(SQLPOINTER)SQL_CONCUR_LOCK ,0);
// Bind arrays to the bookmark, PersonID, FirstName, LastName, Address,
// and City columns.
retcode = SQLBindCol(hstmt, 0, SQL_C_VARBOOKMARK,
CustArray[0].Bookmark, sizeof(CustArray[0].Bookmark),
&CustArray[0].BookmarkLen);
retcode = SQLBindCol(hstmt, 1, SQL_C_ULONG,
&CustArray[0].PersonID,
sizeof(CustArray[0].PersonID),
&CustArray[0].PersonIDInd);
retcode = SQLBindCol(hstmt, 2, SQL_C_CHAR,
CustArray[0].FirstName,
sizeof(CustArray[0].FirstName),
&CustArray[0].FirstNameLenOrInd);
retcode = SQLBindCol(hstmt, 3, SQL_C_CHAR,
CustArray[0].LastName,
sizeof(CustArray[0].LastName),
&CustArray[0].LastNameLenOrInd);
retcode = SQLBindCol(hstmt, 4, SQL_C_CHAR,
CustArray[0].Address,
sizeof(CustArray[0].Address),
&CustArray[0].AddressLenOrInd);
retcode = SQLBindCol(hstmt, 5, SQL_C_CHAR,
CustArray[0].City,
sizeof(CustArray[0].City),
&CustArray[0].CityLenOrInd);
// Execute a statement to retrieve rows from the Persons table.
retcode = SQLExecDirect(hstmt,
(SQLCHAR*)"SELECT PersonID,FirstName,LastName,Address,City FROM TestTBL1", SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect()", hstmt, SQL_HANDLE_STMT);
// Fetch and display the first 3 rows.
retcode = SQLFetchScroll(hstmt, SQL_FETCH_FIRST, 0);
if (retcode == SQL_NO_DATA) {
printf ("SQL_NO_DATA\n");
}
// Call GetAction to get an action and a row number from the user.
while (GetAction(CustArray, RowStatusArray, &Action, &RowNum)) {
switch (Action) {
case SQL_FETCH_NEXT:
case SQL_FETCH_PRIOR:
case SQL_FETCH_FIRST:
case SQL_FETCH_LAST:
case SQL_FETCH_ABSOLUTE:
case SQL_FETCH_RELATIVE:
clearBuffers (CustArray, CURRENT_OFFSET, 3);
// Fetch and display the requested data.
retcode = SQLFetchScroll(hstmt, Action, RowNum);
if (retcode == SQL_SUCCESS ||
retcode == SQL_SUCCESS_WITH_INFO) {
printf ("Rows Fetched (%i)\n", (int) RowsFetched);
for (i=0;i<DATA_ARRAY_SIZE;i++) {
printf ("Row %i, status %i\n", i,
RowStatusArray[i]);
}
if (RowStatusArray[0]==SQL_ROW_DELETED)
clearBuffers (CustArray, CURRENT_OFFSET, 1);
if (RowStatusArray[1]==SQL_ROW_DELETED)
clearBuffers (CustArray, CURRENT_OFFSET+1, 1);
if (RowStatusArray[2]==SQL_ROW_DELETED)
clearBuffers (CustArray, CURRENT_OFFSET+2, 1);
} else {
if (retcode == SQL_NO_DATA) {
printf ("SQL_NO_DATA\n");
} else {
CHECK_ERROR(retcode, "SQLFetchScroll()",
hstmt, SQL_HANDLE_STMT);
}
}
break;
case UPDATE_ROW:
// Check if we have reached the maximum number of
// buffered updates.
if (NumUpdates < 3) {
// Get the new customer data and place it in the next
// available element of the buffered updates section
// of CustArray, copy the bookmark of the row being
// updated to the same element, and increment the update
// counter. Checking to see we have not already buffered
// an update for this row not shown.
RowNum = GetNewCustData(CustArray,
UPDATE_OFFSET + NumUpdates,
UPDATE_OFFSET);
memcpy(CustArray[UPDATE_OFFSET + NumUpdates].Bookmark,
CustArray[RowNum - 1].Bookmark,
CustArray[RowNum - 1].BookmarkLen);
CustArray[UPDATE_OFFSET + NumUpdates].BookmarkLen =
CustArray[RowNum - 1].BookmarkLen;
NumUpdates++;
} else {
printf ("Buffers full.");
printf ("Send buffered changes to the data source.");
}
break;
case DELETE_ROW:
// Check if we have reached the maximum number of buffered
// deletes.
if (NumDeletes < 3) {
// Copy the bookmark of the row being deleted to the next
// available element of the buffered deletes section of
// CustArray and increment the delete counter. Checking
// to see we have not already buffered an update for this
// row not shown.
RowNum = GetNewCustData(CustArray,
DELETE_OFFSET + NumDeletes,
DELETE_OFFSET);
memcpy(CustArray[DELETE_OFFSET + NumDeletes].Bookmark,
CustArray[RowNum - 1].Bookmark,
CustArray[RowNum - 1].BookmarkLen);
CustArray[DELETE_OFFSET + NumDeletes].BookmarkLen =
CustArray[RowNum - 1].BookmarkLen;
NumDeletes++;
} else {
printf ("Buffers full.");
printf ("Send buffered changes to the data source.");
}
break;
case ADD_ROW:
// Reached maximum number of buffered inserts?
if (NumInserts < 3) {
// Get the new customer data and place it in the next
// available element of the buffered inserts section of
// CustArray and increment insert counter.
GetNewCustData (CustArray,
INSERT_OFFSET + NumInserts,
INSERT_OFFSET);
NumInserts++;
} else {
printf ("Buffers full.");
printf ("Send buffered changes to the data source.");
}
break;
case SHOW_ALL:
// Dump the Cust Array Section by Section
// Current Data
printf ("Current Rows (%i)\n", (int) 3);
DisplayCustData(CustArray, CURRENT_OFFSET, 3);
printf ("For Update : (%i)\n", (int) NumUpdates);
DisplayCustData(CustArray, UPDATE_OFFSET, NumUpdates);
printf ("For Insert : (%i)\n", (int) NumInserts);
DisplayCustData(CustArray, INSERT_OFFSET, NumInserts);
printf ("For Delete : (%i)\n", (int) NumDeletes);
DisplayCustData(CustArray, DELETE_OFFSET, NumDeletes);
break;
case SEND_TO_DATA_SOURCE:
// If there are any buffered updates, inserts, or deletes, set
// the array size to that number, set the binding offset to
// use the data in the buffered update, insert, or delete part
// of CustArray, and call SQLBulkOperations to do the updates,
// inserts, or deletes. Because we will never have more than
// 3 updates, inserts, or deletes, we can use the same row
// status array.
if (NumUpdates) {
printf ("Updating %i rows\n", (int)NumUpdates);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE,
(SQLPOINTER) NumUpdates,
(SQLINTEGER) 0);
CHECK_ERROR(retcode, "SQLSetStmtAttr(ROW_ARRAY_SIZE)",
hstmt, SQL_HANDLE_STMT);
// Set start of record (Bind Offset)
BindOffset = UPDATE_OFFSET * sizeof(CustStruct);
retcode = SQLBulkOperations(hstmt, SQL_UPDATE_BY_BOOKMARK);
CHECK_ERROR(retcode, "SQLBulkOperations(hstmt)",
henv, SQL_HANDLE_ENV);
}
if (NumInserts) {
SQLSetStmtAttr (hstmt,
SQL_ATTR_ROW_ARRAY_SIZE,
(SQLPOINTER) NumInserts, 0);
BindOffset = INSERT_OFFSET * sizeof(CustStruct);
SQLBulkOperations(hstmt, SQL_ADD);
}
if (NumDeletes) {
SQLSetStmtAttr (hstmt,
SQL_ATTR_ROW_ARRAY_SIZE,
(SQLPOINTER) NumDeletes, 0);
BindOffset = DELETE_OFFSET * sizeof(CustStruct);
SQLBulkOperations(hstmt, SQL_DELETE_BY_BOOKMARK);
}
// If there were any updates, inserts, or deletes, reset the
// binding offset and array size to their original values.
if (NumUpdates || NumInserts || NumDeletes) {
// SQLRowCount returns the number of rows affected by an
// UPDATE, INSERT, or DELETE statement;
SQLRowCount (hstmt, &rowCount);
printf ("Rows Effected %i\n", (int) rowCount);
SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE,
(SQLPOINTER)3, 0);
// Now buffers updated clean them out
clearBuffers (CustArray, UPDATE_OFFSET, 3);
clearBuffers (CustArray, INSERT_OFFSET, 3);
clearBuffers (CustArray, DELETE_OFFSET, 3);
NumUpdates = 0;
NumInserts = 0;
NumDeletes = 0;
BindOffset = 0;
}
break;
}
}
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