/* Module: statement.c * * Description: This module contains functions related to creating * and manipulating a statement. * * Classes: StatementClass (Functions prefix: "SC_") * * API functions: SQLAllocStmt, SQLFreeStmt * * Comments: See "notice.txt" for copyright and license information. * */ #include "statement.h" #include "bind.h" #include "connection.h" #include "qresult.h" #include "convert.h" #include "environ.h" #include #include #include extern GLOBAL_VALUES globals; RETCODE SQL_API SQLAllocStmt(HDBC hdbc, HSTMT FAR *phstmt) { ConnectionClass *conn = (ConnectionClass *) hdbc; StatementClass *stmt; if( ! conn) return SQL_INVALID_HANDLE; stmt = SC_Constructor(); mylog("**** SQLAllocStmt: hdbc = %u, stmt = %u\n", hdbc, stmt); if ( ! stmt) { conn->errornumber = CONN_STMT_ALLOC_ERROR; conn->errormsg = "No more memory to allocate a further SQL-statement"; *phstmt = SQL_NULL_HSTMT; return SQL_ERROR; } if ( ! CC_add_statement(conn, stmt)) { conn->errormsg = "Maximum number of connections exceeded."; conn->errornumber = CONN_STMT_ALLOC_ERROR; SC_Destructor(stmt); *phstmt = SQL_NULL_HSTMT; return SQL_ERROR; } *phstmt = (HSTMT) stmt; return SQL_SUCCESS; } RETCODE SQL_API SQLFreeStmt(HSTMT hstmt, UWORD fOption) { StatementClass *stmt = (StatementClass *) hstmt; mylog("**** enter SQLFreeStmt: hstmt=%u, fOption=%d\n", hstmt, fOption); if ( ! stmt) return SQL_INVALID_HANDLE; if (fOption == SQL_DROP) { ConnectionClass *conn = stmt->hdbc; /* Remove the statement from the connection's statement list */ if ( conn) { if ( ! CC_remove_statement(conn, stmt)) { stmt->errornumber = STMT_SEQUENCE_ERROR; stmt->errormsg = "Statement is currently executing a transaction."; return SQL_ERROR; /* stmt may be executing a transaction */ } /* Free any cursors and discard any result info */ if (stmt->result) { QR_Destructor(stmt->result); stmt->result = NULL; } } /* Destroy the statement and free any results, cursors, etc. */ SC_Destructor(stmt); } else if (fOption == SQL_UNBIND) { SC_unbind_cols(stmt); } else if (fOption == SQL_CLOSE) { ConnectionClass *conn = stmt->hdbc; /* this should discard all the results, but leave the statement */ /* itself in place (it can be executed again) */ if (!SC_recycle_statement(stmt)) // errormsg passed in above return SQL_ERROR; } else if(fOption == SQL_RESET_PARAMS) { SC_free_params(stmt, STMT_FREE_PARAMS_ALL); } else { stmt->errormsg = "Invalid option passed to SQLFreeStmt."; stmt->errornumber = STMT_OPTION_OUT_OF_RANGE_ERROR; return SQL_ERROR; } return SQL_SUCCESS; } /********************************************************************** * StatementClass implementation */ StatementClass * SC_Constructor() { StatementClass *rv; rv = (StatementClass *) malloc(sizeof(StatementClass)); if (rv) { rv->hdbc = NULL; /* no connection associated yet */ rv->result = NULL; rv->manual_result = FALSE; rv->prepare = FALSE; rv->status = STMT_ALLOCATED; rv->maxRows = 0; // driver returns all rows rv->errormsg = NULL; rv->errornumber = 0; rv->errormsg_created = FALSE; rv->statement = NULL; rv->stmt_with_params[0] = '\0'; rv->statement_type = STMT_TYPE_UNKNOWN; rv->bindings = NULL; rv->bindings_allocated = 0; rv->parameters_allocated = 0; rv->parameters = 0; rv->currTuple = -1; rv->result = 0; rv->data_at_exec = -1; rv->current_exec_param = -1; rv->put_data = FALSE; } return rv; } char SC_Destructor(StatementClass *self) { mylog("SC_Destructor: self=%u, self->result=%u, self->hdbc=%u\n", self, self->result, self->hdbc); if (STMT_EXECUTING == self->status) { self->errornumber = STMT_SEQUENCE_ERROR; self->errormsg = "Statement is currently executing a transaction."; return FALSE; } if (self->result) { if ( ! self->hdbc) self->result->conn = NULL; /* prevent any dbase activity */ QR_Destructor(self->result); } if (self->statement) free(self->statement); SC_free_params(self, STMT_FREE_PARAMS_ALL); /* the memory pointed to by the bindings is not deallocated by the driver */ /* by by the application that uses that driver, so we don't have to care */ /* about that here. */ if (self->bindings) free(self->bindings); free(self); return TRUE; } /* Free parameters and free the memory from the data-at-execution parameters that was allocated in SQLPutData. */ void SC_free_params(StatementClass *self, char option) { int i; if( ! self->parameters) return; for (i = 0; i < self->parameters_allocated; i++) { if (self->parameters[i].data_at_exec == TRUE) { if (self->parameters[i].EXEC_used) { free(self->parameters[i].EXEC_used); self->parameters[i].EXEC_used = NULL; } if (self->parameters[i].EXEC_buffer) { free(self->parameters[i].EXEC_buffer); self->parameters[i].EXEC_buffer = NULL; } } } self->data_at_exec = -1; self->current_exec_param = -1; self->put_data = FALSE; if (option == STMT_FREE_PARAMS_ALL) { free(self->parameters); self->parameters = NULL; self->parameters_allocated = 0; } } int statement_type(char *statement) { if(strnicmp(statement, "SELECT", 6) == 0) return STMT_TYPE_SELECT; else if(strnicmp(statement, "INSERT", 6) == 0) return STMT_TYPE_INSERT; else if(strnicmp(statement, "UPDATE", 6) == 0) return STMT_TYPE_UPDATE; else if(strnicmp(statement, "DELETE", 6) == 0) return STMT_TYPE_DELETE; else return STMT_TYPE_OTHER; } /* Called from SQLPrepare if STMT_PREMATURE, or from SQLExecute if STMT_FINISHED, or from SQLFreeStmt(SQL_CLOSE) */ char SC_recycle_statement(StatementClass *self) { ConnectionClass *conn; /* This would not happen */ if (self->status == STMT_EXECUTING) { self->errornumber = STMT_SEQUENCE_ERROR; self->errormsg = "Statement is currently executing a transaction."; return FALSE; } self->errormsg = NULL; self->errornumber = 0; self->errormsg_created = FALSE; switch (self->status) { case STMT_ALLOCATED: /* this statement does not need to be recycled */ return TRUE; case STMT_READY: break; case STMT_PREMATURE: /* Premature execution of the statement might have caused the start of a transaction. If so, we have to rollback that transaction. */ conn = SC_get_conn(self); if ( ! CC_is_in_autocommit(conn) && CC_is_in_trans(conn)) { CC_send_query(conn, "ABORT", NULL, NULL); CC_set_no_trans(conn); } break; case STMT_FINISHED: break; default: self->errormsg = "An internal error occured while recycling statements"; self->errornumber = STMT_INTERNAL_ERROR; return FALSE; } /* Free any cursors */ if (self->result) { QR_Destructor(self->result); self->result = NULL; } self->status = STMT_READY; self->currTuple = -1; self->errormsg = NULL; self->errornumber = 0; self->errormsg_created = FALSE; // Free any data at exec params before the statement is executed // again. If not, then there will be a memory leak when // the next SQLParamData/SQLPutData is called. SC_free_params(self, STMT_FREE_PARAMS_DATA_AT_EXEC_ONLY); return TRUE; } /* Pre-execute a statement (SQLPrepare/SQLDescribeCol) */ void SC_pre_execute(StatementClass *self) { mylog("SC_pre_execute: status = %d\n", self->status); if (self->status == STMT_READY) { mylog(" preprocess: status = READY\n"); SQLExecute(self); if (self->status == STMT_FINISHED) { mylog(" preprocess: after status = FINISHED, so set PREMATURE\n"); self->status = STMT_PREMATURE; } } } /* This is only called from SQLFreeStmt(SQL_UNBIND) */ char SC_unbind_cols(StatementClass *self) { Int2 lf; for(lf = 0; lf < self->bindings_allocated; lf++) { self->bindings[lf].buflen = 0; self->bindings[lf].buffer = NULL; self->bindings[lf].used = NULL; self->bindings[lf].returntype = SQL_C_CHAR; } return 1; } void SC_clear_error(StatementClass *self) { self->errornumber = 0; self->errormsg = NULL; self->errormsg_created = FALSE; } // This function creates an error msg which is the concatenation // of the result, statement, connection, and socket messages. char * SC_create_errormsg(StatementClass *self) { QResultClass *res = self->result; ConnectionClass *conn = self->hdbc; int pos; static char msg[4096]; msg[0] = '\0'; if (res && res->message) strcpy(msg, res->message); else if (self->errormsg) strcpy(msg, self->errormsg); if (conn) { SocketClass *sock = conn->sock; if (conn->errormsg && conn->errormsg[0] != '\0') { pos = strlen(msg); sprintf(&msg[pos], ";\n%s", conn->errormsg); } if (sock && sock->errormsg && sock->errormsg[0] != '\0') { pos = strlen(msg); sprintf(&msg[pos], ";\n%s", sock->errormsg); } } return msg; } char SC_get_error(StatementClass *self, int *number, char **message) { char rv; // Create a very informative errormsg if it hasn't been done yet. if ( ! self->errormsg_created) { self->errormsg = SC_create_errormsg(self); self->errormsg_created = TRUE; } if ( self->errornumber) { *number = self->errornumber; *message = self->errormsg; self->errormsg = NULL; } rv = (self->errornumber != 0); self->errornumber = 0; return rv; } RETCODE SC_execute(StatementClass *self) { ConnectionClass *conn; QResultClass *res; char ok, was_ok, was_nonfatal; Int2 oldstatus, numcols; conn = SC_get_conn(self); /* Begin a transaction if one is not already in progress */ /* The reason is because we can't use declare/fetch cursors without starting a transaction first. */ if ( ! CC_is_in_trans(conn)) { mylog(" about to begin a transaction on statement = %u\n", self); res = CC_send_query(conn, "BEGIN", NULL, NULL); if ( ! res) { self->errormsg = "Could not begin a transaction"; self->errornumber = STMT_EXEC_ERROR; return SQL_ERROR; } ok = QR_command_successful(res); mylog("SQLExecute: ok = %d, status = %d\n", ok, QR_get_status(res)); QR_Destructor(res); if (!ok) { self->errormsg = "Could not begin a transaction"; self->errornumber = STMT_EXEC_ERROR; return SQL_ERROR; } else CC_set_in_trans(conn); } oldstatus = conn->status; conn->status = CONN_EXECUTING; self->status = STMT_EXECUTING; // If its a SELECT statement, use a cursor. // Note that the declare cursor has already been prepended to the statement // in copy_statement... if (self->statement_type == STMT_TYPE_SELECT) { char cursor[32]; char fetch[64]; sprintf(cursor, "C%u", self); mylog(" Sending SELECT statement on stmt=%u\n", self); /* send the declare/select */ self->result = CC_send_query(conn, self->stmt_with_params, NULL, NULL); if (self->result != NULL) { /* That worked, so now send the fetch to start getting data back */ sprintf(fetch, "fetch %d in %s", globals.fetch_max, cursor); // Save the cursor in the result for later use self->result = CC_send_query( conn, fetch, NULL, cursor); } mylog(" done sending the query:\n"); } else { // not a SELECT statement so don't use a cursor mylog(" its NOT a select statement: stmt=%u\n", self); self->result = CC_send_query(conn, self->stmt_with_params, NULL, NULL); // If we are in autocommit, we must send the commit. if (CC_is_in_autocommit(conn)) { CC_send_query(conn, "COMMIT", NULL, NULL); CC_set_no_trans(conn); } } conn->status = oldstatus; self->status = STMT_FINISHED; /* Check the status of the result */ if (self->result) { was_ok = QR_command_successful(self->result); was_nonfatal = QR_command_nonfatal(self->result); if ( was_ok) self->errornumber = STMT_OK; else self->errornumber = was_nonfatal ? STMT_INFO_ONLY : STMT_ERROR_TAKEN_FROM_BACKEND; self->currTuple = -1; /* set cursor before the first tuple in the list */ /* see if the query did return any result columns */ numcols = QR_NumResultCols(self->result); /* now allocate the array to hold the binding info */ if (numcols > 0) { extend_bindings(self, numcols); if (self->bindings == NULL) { self->errornumber = STMT_NO_MEMORY_ERROR; self->errormsg = "Could not get enough free memory to store the binding information"; return SQL_ERROR; } } } else { /* Bad Error -- The error message will be in the Connection */ self->errornumber = STMT_EXEC_ERROR; self->errormsg = "Error while executing the query"; CC_abort(conn); } if (self->errornumber == STMT_OK) return SQL_SUCCESS; else if (self->errornumber == STMT_INFO_ONLY) return SQL_SUCCESS_WITH_INFO; else return SQL_ERROR; }