/*------- * Module: execute.c * * Description: This module contains routines related to * preparing and executing an SQL statement. * * Classes: n/a * * API functions: SQLPrepare, SQLExecute, SQLExecDirect, SQLTransact, * SQLCancel, SQLNativeSql, SQLParamData, SQLPutData * * Comments: See "notice.txt" for copyright and license information. *------- */ #include "psqlodbc.h" #include <stdio.h> #include <string.h> #ifndef WIN32 #include "iodbc.h" #include "isqlext.h" #else #include <windows.h> #include <sqlext.h> #endif #include "connection.h" #include "statement.h" #include "qresult.h" #include "convert.h" #include "bind.h" #include "pgtypes.h" #include "lobj.h" #include "pgapifunc.h" /*extern GLOBAL_VALUES globals;*/ /* Perform a Prepare on the SQL statement */ RETCODE SQL_API PGAPI_Prepare(HSTMT hstmt, UCHAR FAR *szSqlStr, SDWORD cbSqlStr) { static char *func = "PGAPI_Prepare"; StatementClass *self = (StatementClass *) hstmt; mylog("%s: entering...\n", func); if (!self) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } /* * According to the ODBC specs it is valid to call SQLPrepare mulitple * times. In that case, the bound SQL statement is replaced by the new * one */ switch (self->status) { case STMT_PREMATURE: mylog("**** PGAPI_Prepare: STMT_PREMATURE, recycle\n"); SC_recycle_statement(self); /* recycle the statement, but do * not remove parameter bindings */ break; case STMT_FINISHED: mylog("**** PGAPI_Prepare: STMT_FINISHED, recycle\n"); SC_recycle_statement(self); /* recycle the statement, but do * not remove parameter bindings */ break; case STMT_ALLOCATED: mylog("**** PGAPI_Prepare: STMT_ALLOCATED, copy\n"); self->status = STMT_READY; break; case STMT_READY: mylog("**** PGAPI_Prepare: STMT_READY, change SQL\n"); break; case STMT_EXECUTING: mylog("**** PGAPI_Prepare: STMT_EXECUTING, error!\n"); self->errornumber = STMT_SEQUENCE_ERROR; self->errormsg = "PGAPI_Prepare(): The handle does not point to a statement that is ready to be executed"; SC_log_error(func, "", self); return SQL_ERROR; default: self->errornumber = STMT_INTERNAL_ERROR; self->errormsg = "An Internal Error has occured -- Unknown statement status."; SC_log_error(func, "", self); return SQL_ERROR; } if (self->statement) free(self->statement); self->statement = make_string(szSqlStr, cbSqlStr, NULL); if (!self->statement) { self->errornumber = STMT_NO_MEMORY_ERROR; self->errormsg = "No memory available to store statement"; SC_log_error(func, "", self); return SQL_ERROR; } self->prepare = TRUE; self->statement_type = statement_type(self->statement); /* Check if connection is onlyread (only selects are allowed) */ if (CC_is_onlyread(self->hdbc) && STMT_UPDATE(self)) { self->errornumber = STMT_EXEC_ERROR; self->errormsg = "Connection is readonly, only select statements are allowed."; SC_log_error(func, "", self); return SQL_ERROR; } return SQL_SUCCESS; } /* Performs the equivalent of SQLPrepare, followed by SQLExecute. */ RETCODE SQL_API PGAPI_ExecDirect( HSTMT hstmt, UCHAR FAR *szSqlStr, SDWORD cbSqlStr) { StatementClass *stmt = (StatementClass *) hstmt; RETCODE result; static char *func = "PGAPI_ExecDirect"; mylog("%s: entering...\n", func); if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } if (stmt->statement) free(stmt->statement); /* * keep a copy of the un-parametrized statement, in case they try to * execute this statement again */ stmt->statement = make_string(szSqlStr, cbSqlStr, NULL); if (!stmt->statement) { stmt->errornumber = STMT_NO_MEMORY_ERROR; stmt->errormsg = "No memory available to store statement"; SC_log_error(func, "", stmt); return SQL_ERROR; } mylog("**** %s: hstmt=%u, statement='%s'\n", func, hstmt, stmt->statement); stmt->prepare = FALSE; /* * If an SQLPrepare was performed prior to this, but was left in the * premature state because an error occurred prior to SQLExecute then * set the statement to finished so it can be recycled. */ if (stmt->status == STMT_PREMATURE) stmt->status = STMT_FINISHED; stmt->statement_type = statement_type(stmt->statement); /* Check if connection is onlyread (only selects are allowed) */ if (CC_is_onlyread(stmt->hdbc) && STMT_UPDATE(stmt)) { stmt->errornumber = STMT_EXEC_ERROR; stmt->errormsg = "Connection is readonly, only select statements are allowed."; SC_log_error(func, "", stmt); return SQL_ERROR; } mylog("%s: calling PGAPI_Execute...\n", func); result = PGAPI_Execute(hstmt); mylog("%s: returned %hd from PGAPI_Execute\n", func, result); return result; } /* Execute a prepared SQL statement */ RETCODE SQL_API PGAPI_Execute( HSTMT hstmt) { static char *func = "PGAPI_Execute"; StatementClass *stmt = (StatementClass *) hstmt; ConnectionClass *conn; int i, retval; mylog("%s: entering...\n", func); if (!stmt) { SC_log_error(func, "", NULL); mylog("%s: NULL statement so return SQL_INVALID_HANDLE\n", func); return SQL_INVALID_HANDLE; } /* * If the statement is premature, it means we already executed it from * an SQLPrepare/SQLDescribeCol type of scenario. So just return * success. */ if (stmt->prepare && stmt->status == STMT_PREMATURE) { if (stmt->inaccurate_result) SC_recycle_statement(stmt); else { stmt->status = STMT_FINISHED; if (stmt->errormsg == NULL) { mylog("%s: premature statement but return SQL_SUCCESS\n", func); return SQL_SUCCESS; } else { SC_log_error(func, "", stmt); mylog("%s: premature statement so return SQL_ERROR\n", func); return SQL_ERROR; } } } mylog("%s: clear errors...\n", func); SC_clear_error(stmt); conn = SC_get_conn(stmt); if (conn->status == CONN_EXECUTING) { stmt->errormsg = "Connection is already in use."; stmt->errornumber = STMT_SEQUENCE_ERROR; SC_log_error(func, "", stmt); mylog("%s: problem with connection\n", func); return SQL_ERROR; } if (!stmt->statement) { stmt->errornumber = STMT_NO_STMTSTRING; stmt->errormsg = "This handle does not have a SQL statement stored in it"; SC_log_error(func, "", stmt); mylog("%s: problem with handle\n", func); return SQL_ERROR; } /* * If SQLExecute is being called again, recycle the statement. Note * this should have been done by the application in a call to * SQLFreeStmt(SQL_CLOSE) or SQLCancel. */ if (stmt->status == STMT_FINISHED) { mylog("%s: recycling statement (should have been done by app)...\n", func); SC_recycle_statement(stmt); } /* Check if the statement is in the correct state */ if ((stmt->prepare && stmt->status != STMT_READY) || (stmt->status != STMT_ALLOCATED && stmt->status != STMT_READY)) { stmt->errornumber = STMT_STATUS_ERROR; stmt->errormsg = "The handle does not point to a statement that is ready to be executed"; SC_log_error(func, "", stmt); mylog("%s: problem with statement\n", func); return SQL_ERROR; } /* * Check if statement has any data-at-execute parameters when it is * not in SC_pre_execute. */ if (!stmt->pre_executing) { /* * The bound parameters could have possibly changed since the last * execute of this statement? Therefore check for params and * re-copy. */ stmt->data_at_exec = -1; for (i = 0; i < stmt->parameters_allocated; i++) { Int4 *pcVal = stmt->parameters[i].used; if (pcVal && (*pcVal == SQL_DATA_AT_EXEC || *pcVal <= SQL_LEN_DATA_AT_EXEC_OFFSET)) stmt->parameters[i].data_at_exec = TRUE; else stmt->parameters[i].data_at_exec = FALSE; /* Check for data at execution parameters */ if (stmt->parameters[i].data_at_exec == TRUE) { if (stmt->data_at_exec < 0) stmt->data_at_exec = 1; else stmt->data_at_exec++; } } /* * If there are some data at execution parameters, return need * data */ /* * SQLParamData and SQLPutData will be used to send params and * execute the statement. */ if (stmt->data_at_exec > 0) return SQL_NEED_DATA; } mylog("%s: copying statement params: trans_status=%d, len=%d, stmt='%s'\n", func, conn->transact_status, strlen(stmt->statement), stmt->statement); /* Create the statement with parameters substituted. */ retval = copy_statement_with_parameters(stmt); if (retval != SQL_SUCCESS) /* error msg passed from above */ return retval; mylog(" stmt_with_params = '%s'\n", stmt->stmt_with_params); /* * Get the field info for the prepared * query using dummy backward fetch. */ if (stmt->inaccurate_result && conn->connInfo.disallow_premature) { if (SC_is_pre_executable(stmt)) { BOOL in_trans = CC_is_in_trans(conn); BOOL issued_begin = FALSE, begin_included = FALSE; QResultClass *res; if (strnicmp(stmt->stmt_with_params, "BEGIN;", 6) == 0) begin_included = TRUE; else if (!in_trans) { res = CC_send_query(conn, "BEGIN", NULL); if (res && !QR_aborted(res)) issued_begin = TRUE; if (res) QR_Destructor(res); if (!issued_begin) { stmt->errornumber = STMT_EXEC_ERROR; stmt->errormsg = "Handle prepare error"; return SQL_ERROR; } } /* we are now in a transaction */ CC_set_in_trans(conn); stmt->result = res = CC_send_query(conn, stmt->stmt_with_params, NULL); if (!res || QR_aborted(res)) { CC_abort(conn); stmt->errornumber = STMT_EXEC_ERROR; stmt->errormsg = "Handle prepare error"; return SQL_ERROR; } else { if (CC_is_in_autocommit(conn)) { if (issued_begin) { res = CC_send_query(conn, "COMMIT", NULL); CC_set_no_trans(conn); if (res) QR_Destructor(res); } else if (!in_trans && begin_included) CC_set_no_trans(conn); } stmt->status =STMT_FINISHED; return SQL_SUCCESS; } } else return SQL_SUCCESS; } return SC_execute(stmt); } RETCODE SQL_API PGAPI_Transact( HENV henv, HDBC hdbc, UWORD fType) { static char *func = "PGAPI_Transact"; extern ConnectionClass *conns[]; ConnectionClass *conn; QResultClass *res; char ok, *stmt_string; int lf; mylog("entering %s: hdbc=%u, henv=%u\n", func, hdbc, henv); if (hdbc == SQL_NULL_HDBC && henv == SQL_NULL_HENV) { CC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } /* * If hdbc is null and henv is valid, it means transact all * connections on that henv. */ if (hdbc == SQL_NULL_HDBC && henv != SQL_NULL_HENV) { for (lf = 0; lf < MAX_CONNECTIONS; lf++) { conn = conns[lf]; if (conn && conn->henv == henv) if (PGAPI_Transact(henv, (HDBC) conn, fType) != SQL_SUCCESS) return SQL_ERROR; } return SQL_SUCCESS; } conn = (ConnectionClass *) hdbc; if (fType == SQL_COMMIT) stmt_string = "COMMIT"; else if (fType == SQL_ROLLBACK) stmt_string = "ROLLBACK"; else { conn->errornumber = CONN_INVALID_ARGUMENT_NO; conn->errormsg = "PGAPI_Transact can only be called with SQL_COMMIT or SQL_ROLLBACK as parameter"; CC_log_error(func, "", conn); return SQL_ERROR; } /* If manual commit and in transaction, then proceed. */ if (!CC_is_in_autocommit(conn) && CC_is_in_trans(conn)) { mylog("PGAPI_Transact: sending on conn %d '%s'\n", conn, stmt_string); res = CC_send_query(conn, stmt_string, NULL); CC_set_no_trans(conn); if (!res) { /* error msg will be in the connection */ CC_log_error(func, "", conn); return SQL_ERROR; } ok = QR_command_successful(res); QR_Destructor(res); if (!ok) { CC_log_error(func, "", conn); return SQL_ERROR; } } return SQL_SUCCESS; } RETCODE SQL_API PGAPI_Cancel( HSTMT hstmt) /* Statement to cancel. */ { static char *func = "PGAPI_Cancel"; StatementClass *stmt = (StatementClass *) hstmt; RETCODE result; ConnInfo *ci; #ifdef WIN32 HMODULE hmodule; FARPROC addr; #endif mylog("%s: entering...\n", func); /* Check if this can handle canceling in the middle of a SQLPutData? */ if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } ci = &(SC_get_conn(stmt)->connInfo); /* * Not in the middle of SQLParamData/SQLPutData so cancel like a * close. */ if (stmt->data_at_exec < 0) { /* * MAJOR HACK for Windows to reset the driver manager's cursor * state: Because of what seems like a bug in the Odbc driver * manager, SQLCancel does not act like a SQLFreeStmt(CLOSE), as * many applications depend on this behavior. So, this brute * force method calls the driver manager's function on behalf of * the application. */ #ifdef WIN32 if (ci->drivers.cancel_as_freestmt) { hmodule = GetModuleHandle("ODBC32"); addr = GetProcAddress(hmodule, "SQLFreeStmt"); result = addr((char *) (stmt->phstmt) - 96, SQL_CLOSE); } else result = PGAPI_FreeStmt(hstmt, SQL_CLOSE); #else result = PGAPI_FreeStmt(hstmt, SQL_CLOSE); #endif mylog("PGAPI_Cancel: PGAPI_FreeStmt returned %d\n", result); SC_clear_error(hstmt); return SQL_SUCCESS; } /* In the middle of SQLParamData/SQLPutData, so cancel that. */ /* * Note, any previous data-at-exec buffers will be freed in the * recycle */ /* if they call SQLExecDirect or SQLExecute again. */ stmt->data_at_exec = -1; stmt->current_exec_param = -1; stmt->put_data = FALSE; return SQL_SUCCESS; } /* * Returns the SQL string as modified by the driver. * Currently, just copy the input string without modification * observing buffer limits and truncation. */ RETCODE SQL_API PGAPI_NativeSql( HDBC hdbc, UCHAR FAR *szSqlStrIn, SDWORD cbSqlStrIn, UCHAR FAR *szSqlStr, SDWORD cbSqlStrMax, SDWORD FAR *pcbSqlStr) { static char *func = "PGAPI_NativeSql"; int len = 0; char *ptr; ConnectionClass *conn = (ConnectionClass *) hdbc; RETCODE result; mylog("%s: entering...cbSqlStrIn=%d\n", func, cbSqlStrIn); ptr = (cbSqlStrIn == 0) ? "" : make_string(szSqlStrIn, cbSqlStrIn, NULL); if (!ptr) { conn->errornumber = CONN_NO_MEMORY_ERROR; conn->errormsg = "No memory available to store native sql string"; CC_log_error(func, "", conn); return SQL_ERROR; } result = SQL_SUCCESS; len = strlen(ptr); if (szSqlStr) { strncpy_null(szSqlStr, ptr, cbSqlStrMax); if (len >= cbSqlStrMax) { result = SQL_SUCCESS_WITH_INFO; conn->errornumber = STMT_TRUNCATED; conn->errormsg = "The buffer was too small for the NativeSQL."; } } if (pcbSqlStr) *pcbSqlStr = len; if (cbSqlStrIn) free(ptr); return result; } /* * Supplies parameter data at execution time. * Used in conjuction with SQLPutData. */ RETCODE SQL_API PGAPI_ParamData( HSTMT hstmt, PTR FAR *prgbValue) { static char *func = "PGAPI_ParamData"; StatementClass *stmt = (StatementClass *) hstmt; int i, retval; ConnInfo *ci; mylog("%s: entering...\n", func); if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } ci = &(SC_get_conn(stmt)->connInfo); mylog("%s: data_at_exec=%d, params_alloc=%d\n", func, stmt->data_at_exec, stmt->parameters_allocated); if (stmt->data_at_exec < 0) { stmt->errornumber = STMT_SEQUENCE_ERROR; stmt->errormsg = "No execution-time parameters for this statement"; SC_log_error(func, "", stmt); return SQL_ERROR; } if (stmt->data_at_exec > stmt->parameters_allocated) { stmt->errornumber = STMT_SEQUENCE_ERROR; stmt->errormsg = "Too many execution-time parameters were present"; SC_log_error(func, "", stmt); return SQL_ERROR; } /* close the large object */ if (stmt->lobj_fd >= 0) { lo_close(stmt->hdbc, stmt->lobj_fd); /* commit transaction if needed */ if (!ci->drivers.use_declarefetch && CC_is_in_autocommit(stmt->hdbc)) { QResultClass *res; char ok; res = CC_send_query(stmt->hdbc, "COMMIT", NULL); if (!res) { stmt->errormsg = "Could not commit (in-line) a transaction"; stmt->errornumber = STMT_EXEC_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } ok = QR_command_successful(res); CC_set_no_trans(stmt->hdbc); QR_Destructor(res); if (!ok) { stmt->errormsg = "Could not commit (in-line) a transaction"; stmt->errornumber = STMT_EXEC_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } } stmt->lobj_fd = -1; } /* Done, now copy the params and then execute the statement */ if (stmt->data_at_exec == 0) { retval = copy_statement_with_parameters(stmt); if (retval != SQL_SUCCESS) return retval; stmt->current_exec_param = -1; return SC_execute(stmt); } /* * Set beginning param; if first time SQLParamData is called , start * at 0. Otherwise, start at the last parameter + 1. */ i = stmt->current_exec_param >= 0 ? stmt->current_exec_param + 1 : 0; /* At least 1 data at execution parameter, so Fill in the token value */ for (; i < stmt->parameters_allocated; i++) { if (stmt->parameters[i].data_at_exec == TRUE) { stmt->data_at_exec--; stmt->current_exec_param = i; stmt->put_data = FALSE; *prgbValue = stmt->parameters[i].buffer; /* token */ break; } } return SQL_NEED_DATA; } /* * Supplies parameter data at execution time. * Used in conjunction with SQLParamData. */ RETCODE SQL_API PGAPI_PutData( HSTMT hstmt, PTR rgbValue, SDWORD cbValue) { static char *func = "PGAPI_PutData"; StatementClass *stmt = (StatementClass *) hstmt; int old_pos, retval; ParameterInfoClass *current_param; char *buffer; mylog("%s: entering...\n", func); if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } if (stmt->current_exec_param < 0) { stmt->errornumber = STMT_SEQUENCE_ERROR; stmt->errormsg = "Previous call was not SQLPutData or SQLParamData"; SC_log_error(func, "", stmt); return SQL_ERROR; } current_param = &(stmt->parameters[stmt->current_exec_param]); if (!stmt->put_data) { /* first call */ mylog("PGAPI_PutData: (1) cbValue = %d\n", cbValue); stmt->put_data = TRUE; current_param->EXEC_used = (SDWORD *) malloc(sizeof(SDWORD)); if (!current_param->EXEC_used) { stmt->errornumber = STMT_NO_MEMORY_ERROR; stmt->errormsg = "Out of memory in PGAPI_PutData (1)"; SC_log_error(func, "", stmt); return SQL_ERROR; } *current_param->EXEC_used = cbValue; if (cbValue == SQL_NULL_DATA) return SQL_SUCCESS; /* Handle Long Var Binary with Large Objects */ if (current_param->SQLType == SQL_LONGVARBINARY) { /* begin transaction if needed */ if (!CC_is_in_trans(stmt->hdbc)) { QResultClass *res; char ok; res = CC_send_query(stmt->hdbc, "BEGIN", NULL); if (!res) { stmt->errormsg = "Could not begin (in-line) a transaction"; stmt->errornumber = STMT_EXEC_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } ok = QR_command_successful(res); QR_Destructor(res); if (!ok) { stmt->errormsg = "Could not begin (in-line) a transaction"; stmt->errornumber = STMT_EXEC_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } CC_set_in_trans(stmt->hdbc); } /* store the oid */ current_param->lobj_oid = lo_creat(stmt->hdbc, INV_READ | INV_WRITE); if (current_param->lobj_oid == 0) { stmt->errornumber = STMT_EXEC_ERROR; stmt->errormsg = "Couldnt create large object."; SC_log_error(func, "", stmt); return SQL_ERROR; } /* * major hack -- to allow convert to see somethings there have * to modify convert to handle this better */ current_param->EXEC_buffer = (char *) ¤t_param->lobj_oid; /* store the fd */ stmt->lobj_fd = lo_open(stmt->hdbc, current_param->lobj_oid, INV_WRITE); if (stmt->lobj_fd < 0) { stmt->errornumber = STMT_EXEC_ERROR; stmt->errormsg = "Couldnt open large object for writing."; SC_log_error(func, "", stmt); return SQL_ERROR; } retval = lo_write(stmt->hdbc, stmt->lobj_fd, rgbValue, cbValue); mylog("lo_write: cbValue=%d, wrote %d bytes\n", cbValue, retval); } else { /* for handling fields */ if (cbValue == SQL_NTS) { current_param->EXEC_buffer = strdup(rgbValue); if (!current_param->EXEC_buffer) { stmt->errornumber = STMT_NO_MEMORY_ERROR; stmt->errormsg = "Out of memory in PGAPI_PutData (2)"; SC_log_error(func, "", stmt); return SQL_ERROR; } } else { Int2 ctype = current_param->CType; if (ctype == SQL_C_DEFAULT) ctype = sqltype_to_default_ctype(current_param->SQLType); if (ctype == SQL_C_CHAR || ctype == SQL_C_BINARY) { current_param->EXEC_buffer = malloc(cbValue + 1); if (!current_param->EXEC_buffer) { stmt->errornumber = STMT_NO_MEMORY_ERROR; stmt->errormsg = "Out of memory in PGAPI_PutData (2)"; SC_log_error(func, "", stmt); return SQL_ERROR; } memcpy(current_param->EXEC_buffer, rgbValue, cbValue); current_param->EXEC_buffer[cbValue] = '\0'; } else { Int4 used = ctype_length(ctype); current_param->EXEC_buffer = malloc(used); if (!current_param->EXEC_buffer) { stmt->errornumber = STMT_NO_MEMORY_ERROR; stmt->errormsg = "Out of memory in PGAPI_PutData (2)"; SC_log_error(func, "", stmt); return SQL_ERROR; } memcpy(current_param->EXEC_buffer, rgbValue, used); } } } } else { /* calling SQLPutData more than once */ mylog("PGAPI_PutData: (>1) cbValue = %d\n", cbValue); if (current_param->SQLType == SQL_LONGVARBINARY) { /* the large object fd is in EXEC_buffer */ retval = lo_write(stmt->hdbc, stmt->lobj_fd, rgbValue, cbValue); mylog("lo_write(2): cbValue = %d, wrote %d bytes\n", cbValue, retval); *current_param->EXEC_used += cbValue; } else { buffer = current_param->EXEC_buffer; if (cbValue == SQL_NTS) { buffer = realloc(buffer, strlen(buffer) + strlen(rgbValue) + 1); if (!buffer) { stmt->errornumber = STMT_NO_MEMORY_ERROR; stmt->errormsg = "Out of memory in PGAPI_PutData (3)"; SC_log_error(func, "", stmt); return SQL_ERROR; } strcat(buffer, rgbValue); mylog(" cbValue = SQL_NTS: strlen(buffer) = %d\n", strlen(buffer)); *current_param->EXEC_used = cbValue; /* reassign buffer incase realloc moved it */ current_param->EXEC_buffer = buffer; } else if (cbValue > 0) { old_pos = *current_param->EXEC_used; *current_param->EXEC_used += cbValue; mylog(" cbValue = %d, old_pos = %d, *used = %d\n", cbValue, old_pos, *current_param->EXEC_used); /* dont lose the old pointer in case out of memory */ buffer = realloc(current_param->EXEC_buffer, *current_param->EXEC_used + 1); if (!buffer) { stmt->errornumber = STMT_NO_MEMORY_ERROR; stmt->errormsg = "Out of memory in PGAPI_PutData (3)"; SC_log_error(func, "", stmt); return SQL_ERROR; } memcpy(&buffer[old_pos], rgbValue, cbValue); buffer[*current_param->EXEC_used] = '\0'; /* reassign buffer incase realloc moved it */ current_param->EXEC_buffer = buffer; } else { SC_log_error(func, "bad cbValue", stmt); return SQL_ERROR; } } } return SQL_SUCCESS; }