summaryrefslogtreecommitdiff
path: root/src/interfaces/odbc/statement.c
diff options
context:
space:
mode:
authorMarc G. Fournier <scrappy@hub.org>1998-04-13 15:02:05 +0000
committerMarc G. Fournier <scrappy@hub.org>1998-04-13 15:02:05 +0000
commit9e3382dfc5affce3b653534714343d5e546e1a3e (patch)
treee5627cb0e135d52c2e4af55a239795d18ba5e7f9 /src/interfaces/odbc/statement.c
parentd4d26f9c33791f7aefd667e3688d9a18400006e1 (diff)
downloadpostgresql-9e3382dfc5affce3b653534714343d5e546e1a3e.tar.gz
Replace old PostODBC driver with new one...
This one is based on an older PostODBC driver, rewritten and maintained by InsightDist(?)
Diffstat (limited to 'src/interfaces/odbc/statement.c')
-rw-r--r--src/interfaces/odbc/statement.c545
1 files changed, 545 insertions, 0 deletions
diff --git a/src/interfaces/odbc/statement.c b/src/interfaces/odbc/statement.c
new file mode 100644
index 0000000000..bea5968dc2
--- /dev/null
+++ b/src/interfaces/odbc/statement.c
@@ -0,0 +1,545 @@
+
+/* 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 <stdio.h>
+
+#include <windows.h>
+#include <sql.h>
+
+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;
+}