/**********************************************************************
* FILENAME : UsingKeysetCursors.c
*
* DESCRIPTION :
* Shows use of SQLSetPos with keyset driven cursor type and
* SQLFetchScroll() to read and update data in a rowset.
*
* Illustrates the use of SQL_FETCH_NEXT, SQL_FETCH_FIRST,
* SQL_FETCH_LAST, SQL_FETCH_PRIOR, SQL_FETCH_ABSOLUTE and
* SQL_FETCH_RELATIVE to retrieve data with UPDATE, DELETE and INSERT
* operations to change data in the rowset.
*
* A rowset array size of 10 is used (+1 extra for an insert record)
*
* ODBC USAGE :
* Sets the statement handle attributes ...
*
* SQLSetStmtAttr() with - SQL_ATTR_CURSOR_TYPE = SQL_CURSOR_KEYSET_DRIVEN
* - SQL_ATTR_ROW_BIND_TYPE = SQL_BIND_BY_COLUMN
* - SQL_ATTR_ROW_ARRAY_SIZE = 10
* - SQL_ATTR_ROW_STATUS_PTR = pointer to row status
* array
* - SQL_ATTR_CONCURRENCY = SQL_CONCUR_LOCK
* SQLBindCol() - to bind data array for current rowset
* SQLExecuteDirect() - execute SELECT from TestTBL1
* SQLFetchScroll() with - to retrieve rowset according to action requested
* (SQL_FETCH_FIRST, etc)
* SQLSetPos() - to set the cursor position in the rowset
* (for update, delete and insert)
*/
#include <stdio.h>
#include <stdlib.h>
#include <sql.h>
#include <sqlext.h>
#include <string.h>
#include "util.c"
#define QUIT 0
#define INSERT 0
#define UPDATE 1
#define DELETE 2
#define LAST 3
#define UPDATE_ROW 100
#define DELETE_ROW 101
#define INSERT_ROW 102
#define REFRESH_ROW 103
#define QUIT 0
#define TRUE 1
#define FALSE 0
#define PERSONID_LEN 2
#define LASTNAME_LEN 255
#define FIRSTNAME_LEN 255
#define ADDRESS_LEN 255
#define CITY_LEN 255
#define DATA_ARRAY_SIZE 11
// Data and size arrays NOTE difference with Bulk Operations
// where structure is used
SQLUINTEGER PersonID[DATA_ARRAY_SIZE];
SQLCHAR FirstName[DATA_ARRAY_SIZE][255];
SQLCHAR LastName[DATA_ARRAY_SIZE][255];
SQLCHAR Address[DATA_ARRAY_SIZE][255];
SQLCHAR City[DATA_ARRAY_SIZE][255];
SQLLEN PersonIDLenArr[DATA_ARRAY_SIZE];
SQLLEN FirstNameLenArr[DATA_ARRAY_SIZE];
SQLLEN LastNameLenArr[DATA_ARRAY_SIZE];
SQLLEN AddressLenArr[DATA_ARRAY_SIZE];
SQLLEN CityLenArr[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
// SQLFetchScroll() actions (for reference)
//#define SQL_FETCH_NEXT 1 - fetch next rowset
//#define SQL_FETCH_FIRST 2 - fetch first rowset
//#define SQL_FETCH_LAST 3 - fetch last rowset
//#define SQL_FETCH_PRIOR 4 - fetch rowset prior to current rowset
//#define SQL_FETCH_ABSOLUTE 5 - fetch rowset starting at row within
// the table
//#define SQL_FETCH_RELATIVE 6 - fetch rowset starting at row within
// current rowset
SQLUSMALLINT RowStatusArray[DATA_ARRAY_SIZE], Action, RowNum;
SQLUSMALLINT NumUpdates = 0, NumInserts = 0, NumDeletes = 0;
SQLLEN BindOffset = 0;
SQLLEN RowsFetched = 0;
SQLLEN Concurrency = SQL_CONCUR_LOCK;
SQLHENV henv = SQL_NULL_HENV; // Environment handle
SQLHDBC hdbc = SQL_NULL_HDBC; // Connection handle
SQLHSTMT hstmt = SQL_NULL_HSTMT; // Statement handle
SQLRETURN retcode;
SQLLEN rowCount;
//
// Display row status array and rowset data array
//
void DisplayCustData() {
int i;
// Display RowStatusArray
for (i=0; i<DATA_ARRAY_SIZE; i++) {
printf ("%i, ", (int)RowStatusArray[i]);
}
printf ("\n");
for (i=0; i<DATA_ARRAY_SIZE; i++) {
printf ("Record %i - ", i);
printf ("%i ,", (int)PersonID[i]);
printf ("%.10s ,", (char *)FirstName[i]);
printf ("%.10s ,", (char *)LastName[i]);
printf ("%.10s ,", (char *)Address[i]);
printf ("%.10s\n", (char *)City[i]);
}
return;
}
//
// Function to get next action
//
int GetAction(SQLUSMALLINT *pStatusArray,
SQLUSMALLINT *pAction,
SQLUSMALLINT* pRowNum) {
// Display cust data array
printf ("Current Data: \n");
DisplayCustData();
printf ("QUIT(0), \n");
printf ("LIST : NEXT(1), FIRST(2), LAST(3), PRIOR(4), ABS(5), REL(6)\n");
printf ("OR : UPDATE(100) DELETE(101) INSERT(102) REFRESH(103)\n");
getInt ("Select Action", (int *) pAction, 'N', 0);
// Row number only used for SQL_FETCH_ABSOLUTE and SQL_FETCH_RELATIVE
if (*pAction == SQL_FETCH_ABSOLUTE || *pAction == SQL_FETCH_RELATIVE) {
getInt ("Select ABS/REL Row Number", (int *) pRowNum, 'N', 0);
} else {
*pRowNum=0;
}
return 1;
}
//
// Clear a number of rows in the rowset data array
//
void clearBuffers (int index, int rows) {
int i;
for (i=index;i<index+rows;i++) {
RowStatusArray[i]=SQL_ROW_NOROW;
}
printf ("Clear Buffers : %i, %i\n", index, rows);
for (i=index;i<index+rows;i++) {
PersonID[i]=0;
memset(FirstName[i], ' ', FIRSTNAME_LEN);
memset(LastName[i], ' ', LASTNAME_LEN);
memset(Address[i], ' ', ADDRESS_LEN);
memset(City[i], ' ', CITY_LEN);
}
}
//
// If performing an Update or Delete, prompts for record number/index of
// record to process in the data array. Requests new data for update or
// insert. Data for inserts is returned in an extra row at the end of
// the rowset data array.
//
SQLUSMALLINT GetNewCustData(base) {
int srcOffset;
SQLCHAR strFirstName[FIRSTNAME_LEN];
SQLCHAR strLastName[LASTNAME_LEN];
SQLCHAR strAddress[ADDRESS_LEN];
SQLCHAR strCity[CITY_LEN];
char reply=' ';
// Update record
if (base==UPDATE) {
getInt ("Enter Record No (0 - 9)", &srcOffset, 'N', 0);
if (srcOffset<DATA_ARRAY_SIZE) {
// Ask for new FirstName
printf ("Current First Name : %.20s\n", FirstName[srcOffset]);
memset(strFirstName, ' ', FIRSTNAME_LEN);
reply=getStr ("New First Name ", strFirstName, FIRSTNAME_LEN, 'N');
// Move new FirstName into Update area and remove NULL
memset(FirstName[srcOffset], ' ', FIRSTNAME_LEN);
strFirstName[strlen(strFirstName)]= ' ';
memcpy(FirstName[srcOffset], strFirstName, FIRSTNAME_LEN);
// Set lengths for Update to lengths of fields
PersonIDLenArr[srcOffset]=SQL_COLUMN_IGNORE;
FirstNameLenArr[srcOffset]=FIRSTNAME_LEN;
LastNameLenArr[srcOffset]=LASTNAME_LEN;
AddressLenArr[srcOffset]=ADDRESS_LEN;
CityLenArr[srcOffset]=CITY_LEN;
// Return row number of update record
return srcOffset+1;
}
}
// Insert record
if (base==INSERT) {
// Clear insert buffer
clearBuffers (DATA_ARRAY_SIZE-1, 1);
// Ask for new FirstName
memset(strFirstName, ' ', FIRSTNAME_LEN);
reply=getStr ("New First Name ", strFirstName, FIRSTNAME_LEN, 'N');
memset(FirstName[DATA_ARRAY_SIZE-1], ' ', FIRSTNAME_LEN);
strFirstName[strlen(strFirstName)]= ' ';
memcpy(FirstName[DATA_ARRAY_SIZE-1], strFirstName, FIRSTNAME_LEN);
// Ask for new LastName
memset(strLastName, ' ', LASTNAME_LEN);
reply=getStr ("New Last Name ", strLastName, LASTNAME_LEN, 'N');
memset(LastName[DATA_ARRAY_SIZE-1], ' ', LASTNAME_LEN);
strLastName[strlen(strLastName)]= ' ';
memcpy(LastName[DATA_ARRAY_SIZE-1], strLastName, LASTNAME_LEN);
// Ask for new Address
memset(strAddress, ' ', ADDRESS_LEN);
reply=getStr ("New Address ", strAddress, ADDRESS_LEN, 'N');
memset(Address[DATA_ARRAY_SIZE-1], ' ', ADDRESS_LEN);
strAddress[strlen(strAddress)]= ' ';
memcpy(Address[DATA_ARRAY_SIZE-1], strAddress, ADDRESS_LEN);
// Ask for new City
memset(strCity, ' ', CITY_LEN);
reply=getStr ("New City ", strCity, CITY_LEN, 'N');
memset(City[DATA_ARRAY_SIZE-1], ' ', CITY_LEN);
strCity[strlen(strCity)]= ' ';
memcpy(City[DATA_ARRAY_SIZE-1], strCity, CITY_LEN);
// Set lengths for Insert to lengths of fields
PersonIDLenArr[DATA_ARRAY_SIZE-1]=SQL_COLUMN_IGNORE;
FirstNameLenArr[DATA_ARRAY_SIZE-1]=FIRSTNAME_LEN;
LastNameLenArr[DATA_ARRAY_SIZE-1]=LASTNAME_LEN;
AddressLenArr[DATA_ARRAY_SIZE-1]=ADDRESS_LEN;
CityLenArr[DATA_ARRAY_SIZE-1]=CITY_LEN;
// Return row number of update record
return DATA_ARRAY_SIZE;
}
// Delete record
if (base==DELETE) {
getInt ("Enter Record No (0 - 9)", &srcOffset, 'N', 0);
if (srcOffset<DATA_ARRAY_SIZE) {
// return row number of record to delete
printf ("Deleting row %i\n", srcOffset+1);
return srcOffset+1;
}
}
return -1;
}
// Display row being inserted, updated or deleted
void DisplayRow(int action, int RowNum) {
if (action==UPDATE) {
printf ("Updating Row %i\n", RowNum);
}
if (action==INSERT) {
printf ("Inserting Row %i\n", RowNum);
}
if (action==DELETE) {
printf ("Deleting Row %i\n", RowNum);
}
printf ("Record : %.10s,", FirstName[RowNum-1]);
printf ("'%.10s',", FirstName[RowNum-1]);
printf ("'%.10s',", LastName[RowNum-1]);
printf ("'%.10s',", Address[RowNum-1]);
printf ("'%.10s'\n", City[RowNum-1]);
}
int main () {
int i;
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,
(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(SQL_HANDLE_DBC)",
hdbc, SQL_HANDLE_DBC);
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(DATASOURCE)", 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: SQL_BIND_BY_COLUMN
// SQL_ATTR_ROW_ARRAY_SIZE: 10
// SQL_ATTR_ROW_STATUS_PTR: Points to RowStatusArray
// 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,
SQL_BIND_BY_COLUMN, 0);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE,
(SQLPOINTER)10, 0);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_STATUS_PTR,
RowStatusArray, 0);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_CONCURRENCY,
(SQLPOINTER)SQL_CONCUR_LOCK ,0); // not in Microsoft example
// Bind arrays to the PersonID, FirstName, LastName, Address,
// and City columns.
retcode = SQLBindCol(hstmt, 1, SQL_C_ULONG,
&PersonID, 0,
PersonIDLenArr);
retcode = SQLBindCol(hstmt, 2, SQL_C_CHAR,
FirstName, sizeof(FirstName[0]),
FirstNameLenArr);
retcode = SQLBindCol(hstmt, 3, SQL_C_CHAR,
LastName, sizeof(LastName[0]),
LastNameLenArr);
retcode = SQLBindCol(hstmt, 4, SQL_C_CHAR,
Address, sizeof(Address[0]),
AddressLenArr);
retcode = SQLBindCol(hstmt, 5, SQL_C_CHAR,
City, sizeof(City[0]),
CityLenArr);
// Execute a statement to retrieve rows from the Persons table.
retcode = SQLExecDirect(hstmt, (SQLCHAR*)
"SELECT PersonID, FirstName, LastName, Address, City "
"FROM TestTBL1", SQL_NTS);
// 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(RowStatusArray, &Action, &RowNum)) {
if (Action==0) break;
if (Action==REFRESH_ROW) {
Action=SQL_FETCH_FIRST;
}
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 (0, DATA_ARRAY_SIZE);
// Fetch and display the requested data.
retcode = SQLFetchScroll(hstmt, Action, RowNum);
if (retcode == SQL_NO_DATA) {
printf ("SQL_NO_DATA\n");
}
break;
// SQLRowCount returns the number of rows affected by an
// UPDATE, INSERT, or DELETE statement;
// an SQL_ADD, SQL_UPDATE_BY_BOOKMARK, or SQL_
// DELETE_BY_BOOKMARK operation in SQLBulkOperations;
// or an SQL_UPDATE or SQL_DELETE operation in SQLSetPos.
case UPDATE_ROW:
RowNum = GetNewCustData(UPDATE);
DisplayRow(UPDATE, RowNum);
SQLSetPos(hstmt, RowNum, SQL_UPDATE, SQL_LOCK_NO_CHANGE);
SQLRowCount (hstmt, &rowCount);
printf ("Rows Effected %i\n", (int) rowCount);
break;
case DELETE_ROW:
RowNum = GetNewCustData(DELETE);
DisplayRow(DELETE, RowNum);
SQLSetPos(hstmt, RowNum, SQL_DELETE, SQL_LOCK_NO_CHANGE);
SQLRowCount (hstmt, &rowCount);
printf ("Rows Effected %i\n", (int) rowCount);
clearBuffers (RowNum-1, 1);
break;
case INSERT_ROW:
RowNum = GetNewCustData(INSERT);
DisplayRow(INSERT, 11);
retcode = SQLSetPos(hstmt, 11, SQL_ADD, SQL_LOCK_NO_CHANGE);
if (retcode != SQL_SUCCESS &&
retcode != SQL_SUCCESS_WITH_INFO) {
extract_error("SQLSetPos - INSERT ROW",
hstmt, SQL_HANDLE_STMT);
} else {
SQLRowCount (hstmt, &rowCount);
printf ("Rows Effected %i\n", (int) rowCount);
}
clearBuffers (10, 1);
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