+ * converting parameters and columns into requested data types.\r
+ * data type.\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ PG_TYPE_DATE SQL_C_TIMESTAMP SQL_C_TIMESTAMP (time = 0 (midnight))
+ PG_TYPE_TIME SQL_C_TIMESTAMP SQL_C_TIMESTAMP (date = current date)
+ PG_TYPE_ABSTIME SQL_C_DATE SQL_C_DATE (time is truncated)
+ PG_TYPE_ABSTIME SQL_C_TIME SQL_C_TIME (date is truncated)
+copy_and_convert_field_bindinfo(Int4 field_type, void *value, BindInfoClass *bic)
+ return copy_and_convert_field(field_type, value, (Int2)bic->returntype, (PTR)bic->buffer,
+copy_and_convert_field(Int4 field_type, void *value, Int2 fCType, PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue)
+ mylog("copy_and_convert: field_type = %d, fctype = %d, value = '%s', cbValueMax=%d\n", field_type, fCType, value, cbValueMax);
+ useable data.
+ /* $$$ need to add parsing for date/time/timestamp strings in PG_TYPE_CHAR,VARCHAR $$$ */
+ nf = sscanf(value, "%3s %3s %2d %2d:%2d:%2d %4d %3s", &day, &mon, &st.d, &st.hh, &st.mm, &st.ss, &st.y, &tz);\r
+ } else { /* The timestamp is invalid so set something conspicuous, like the epoch */\r
+ return COPY_OK; /* dont go any further or the data will be trashed */
+ sprintf((char *)rgbValue, "%.4d-%.2d-%.2d", st.y, st.m, st.d);
+ sprintf((char *)rgbValue, "%.2d:%.2d:%.2d", st.hh, st.mm, st.ss);
+ case PG_TYPE_BYTEA: // convert binary data to hex strings (i.e, 255 = "FF")\r
+ mylog(" SQL_C_CHAR, default: len = %d, cbValueMax = %d, rgbValue = '%s'\n", len, cbValueMax, rgbValue);
+ /* for SQL_C_CHAR, its probably ok to leave currency symbols in. But
+ to convert to numeric types, it is necessary to get rid of those.
+ mylog("SQL_C_BIT: val = %d, cb = %d, rgb=%d\n", atoi(value), cbValueMax, *((UCHAR *)rgbValue));
+ // truncate the data.
+/* This function inserts parameters into an SQL statements.
+ It will also modify a SELECT statement for use with declare/fetch cursors.
--- /dev/null
+\r
+/* Module: execute.c\r
+ *\r
+ * Description: This module contains routines related to \r
+ * preparing and executing an SQL statement.\r
+ *\r
+ * Classes: n/a\r
+ *\r
+ * API functions: SQLPrepare, SQLExecute, SQLExecDirect, SQLTransact,\r
+ * SQLCancel, SQLNativeSql, SQLParamData, SQLPutData\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "psqlodbc.h"
+#include
+#include
+#include
+#include
+
+#include "connection.h"
+#include "statement.h"
+#include "qresult.h"
+#include "convert.h"
+#include "bind.h"
+
+
+// Perform a Prepare on the SQL statement
+RETCODE SQL_API SQLPrepare(HSTMT hstmt,
+ UCHAR FAR *szSqlStr,
+ SDWORD cbSqlStr)
+{
+StatementClass *self = (StatementClass *) hstmt;
+
+ if ( ! self)
+ return SQL_INVALID_HANDLE;
+
+ /* CC: 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("**** SQLPrepare: STMT_PREMATURE, recycle\n");
+
+ SC_recycle_statement(self); /* recycle the statement, but do not remove parameter bindings */
+
+ /* NO Break! -- Contiue the same way as with a newly allocated statement ! */
+
+ case STMT_ALLOCATED:
+ // it is not really necessary to do any conversion of the statement
+ // here--just copy it, and deal with it when it's ready to be
+ // executed.
+ mylog("**** SQLPrepare: STMT_ALLOCATED, copy\n");
+
+ self->statement = make_string(szSqlStr, cbSqlStr, NULL);
+ if ( ! self->statement) {
+ self->errornumber = STMT_NO_MEMORY_ERROR;
+ self->errormsg = "No memory available to store statement";
+ return SQL_ERROR;
+ }
+
+ self->statement_type = statement_type(self->statement);
+
+ // Check if connection is readonly (only selects are allowed)
+ if ( CC_is_readonly(self->hdbc) && self->statement_type != STMT_TYPE_SELECT ) {
+ self->errornumber = STMT_EXEC_ERROR;
+ self->errormsg = "Connection is readonly, only select statements are allowed.";
+ return SQL_ERROR;
+ }
+
+ self->prepare = TRUE;
+ self->status = STMT_READY;
+
+ return SQL_SUCCESS;
+
+ case STMT_READY: /* SQLPrepare has already been called -- Just changed the SQL statement that is assigned to the handle */
+ mylog("**** SQLPrepare: STMT_READY, change SQL\n");
+
+ 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";
+ return SQL_ERROR;
+ }
+
+ self->prepare = TRUE;
+ self->statement_type = statement_type(self->statement);
+
+ // Check if connection is readonly (only selects are allowed)
+ if ( CC_is_readonly(self->hdbc) && self->statement_type != STMT_TYPE_SELECT ) {
+ self->errornumber = STMT_EXEC_ERROR;
+ self->errormsg = "Connection is readonly, only select statements are allowed.";
+ return SQL_ERROR;
+ }
+
+ return SQL_SUCCESS;
+
+ case STMT_FINISHED:
+ mylog("**** SQLPrepare: STMT_FINISHED\n");
+ /* No BREAK: continue as with STMT_EXECUTING */
+
+ case STMT_EXECUTING:
+ mylog("**** SQLPrepare: STMT_EXECUTING, error!\n");
+
+ self->errornumber = STMT_SEQUENCE_ERROR;
+ self->errormsg = "SQLPrepare(): The handle does not point to a statement that is ready to be executed";
+
+ return SQL_ERROR;
+
+ default:
+ self->errornumber = STMT_INTERNAL_ERROR;
+ self->errormsg = "An Internal Error has occured -- Unknown statement status.";
+ return SQL_ERROR;
+ }
+}
+
+// - - - - - - - - -
+
+// Performs the equivalent of SQLPrepare, followed by SQLExecute.
+
+RETCODE SQL_API SQLExecDirect(
+ HSTMT hstmt,
+ UCHAR FAR *szSqlStr,
+ SDWORD cbSqlStr)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+
+ if ( ! stmt)
+ 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";
+ return SQL_ERROR;
+ }
+
+ mylog("**** SQLExecDirect: hstmt=%u, statement='%s'\n", hstmt, stmt->statement);
+
+ stmt->prepare = FALSE;
+ stmt->statement_type = statement_type(stmt->statement);
+
+ // Check if connection is readonly (only selects are allowed)
+ if ( CC_is_readonly(stmt->hdbc) && stmt->statement_type != STMT_TYPE_SELECT ) {
+ stmt->errornumber = STMT_EXEC_ERROR;
+ stmt->errormsg = "Connection is readonly, only select statements are allowed.";
+ return SQL_ERROR;
+ }
+
+ mylog("SQLExecDirect: calling SQLExecute\n");
+
+ return SQLExecute(hstmt);
+}
+
+// Execute a prepared SQL statement
+RETCODE SQL_API SQLExecute(
+ HSTMT hstmt)
+{
+StatementClass *stmt = (StatementClass *) hstmt;\r
+ConnectionClass *conn;
+int i, retval;\r
+\r
+
+ if ( ! stmt)
+ 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) {
+ stmt->status = STMT_FINISHED;
+ return stmt->errormsg == NULL ? SQL_SUCCESS : SQL_ERROR;
+ }
+
+ 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;
+ return SQL_ERROR;
+ }
+
+ if ( ! stmt->statement) {
+ stmt->errornumber = STMT_NO_STMTSTRING;
+ stmt->errormsg = "This handle does not have a SQL statement stored in it";
+ 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) {
+ 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";
+ return SQL_ERROR;
+ }\r
+\r
+\r
+ /* The bound parameters could have possibly changed since the last execute\r
+ of this statement? Therefore check for params and re-copy.\r
+ */\r
+ stmt->data_at_exec = -1;\r
+ for (i = 0; i < stmt->parameters_allocated; i++) {\r
+ /* Check for data at execution parameters */\r
+ if ( stmt->parameters[i].data_at_exec == TRUE) {\r
+ if (stmt->data_at_exec < 0)\r
+ stmt->data_at_exec = 1;\r
+ else\r
+ stmt->data_at_exec++;\r
+ }\r
+ }\r
+ // If there are some data at execution parameters, return need data\r
+ // SQLParamData and SQLPutData will be used to send params and execute the statement.\r
+ if (stmt->data_at_exec > 0)\r
+ return SQL_NEED_DATA;\r
+\r
+\r
+ mylog("SQLExecute: copying statement params: trans_status=%d, len=%d, stmt='%s'\n", conn->transact_status, strlen(stmt->statement), stmt->statement);\r
+\r
+ // Create the statement with parameters substituted.\r
+ retval = copy_statement_with_parameters(stmt);\r
+ if( retval != SQL_SUCCESS)\r
+ /* error msg passed from above */\r
+ return retval;\r
+\r
+ mylog(" stmt_with_params = '%s'\n", stmt->stmt_with_params);\r
+\r
+\r
+ return SC_execute(stmt);\r
+
+}
+\r
+\r
+\r
+
+// - - - - - - - - -
+RETCODE SQL_API SQLTransact(
+ HENV henv,
+ HDBC hdbc,
+ UWORD fType)
+{
+extern ConnectionClass *conns[];
+ConnectionClass *conn;
+QResultClass *res;
+char ok, *stmt_string;
+int lf;
+
+mylog("**** SQLTransact: hdbc=%u, henv=%u\n", hdbc, henv);
+
+ if (hdbc == SQL_NULL_HDBC && henv == SQL_NULL_HENV)
+ 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
+ conn = conns[lf];
+
+ if (conn && conn->henv == henv)
+ if ( SQLTransact(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 ="SQLTransact can only be called with SQL_COMMIT or SQL_ROLLBACK as parameter";
+ return SQL_ERROR;
+ }
+
+ /* If manual commit and in transaction, then proceed. */
+ if ( ! CC_is_in_autocommit(conn) && CC_is_in_trans(conn)) {
+
+ mylog("SQLTransact: sending on conn %d '%s'\n", conn, stmt_string);
+
+ res = CC_send_query(conn, stmt_string, NULL, NULL);
+ CC_set_no_trans(conn);
+
+ if ( ! res)
+ // error msg will be in the connection
+ return SQL_ERROR;
+
+ ok = QR_command_successful(res);
+ QR_Destructor(res);
+
+ if (!ok)
+ return SQL_ERROR;
+ }
+ return SQL_SUCCESS;
+}
+
+// - - - - - - - - -
+
+
+RETCODE SQL_API SQLCancel(
+ HSTMT hstmt) // Statement to cancel.
+{
+StatementClass *stmt = (StatementClass *) hstmt;\r
+\r
+ // Check if this can handle canceling in the middle of a SQLPutData?\r
+ if ( ! stmt)\r
+ return SQL_INVALID_HANDLE;\r
+\r
+ // Not in the middle of SQLParamData/SQLPutData so cancel like a close.\r
+ if (stmt->data_at_exec < 0)\r
+ return SQLFreeStmt(hstmt, SQL_CLOSE);\r
+\r
+ // In the middle of SQLParamData/SQLPutData, so cancel that.\r
+ // Note, any previous data-at-exec buffers will be freed in the recycle\r
+ // if they call SQLExecDirect or SQLExecute again.\r
+\r
+ stmt->data_at_exec = -1;\r
+ stmt->current_exec_param = -1;\r
+ stmt->put_data = FALSE;\r
+\r
+}
+
+// - - - - - - - - -
+
+// Returns the SQL string as modified by the driver.
+
+RETCODE SQL_API SQLNativeSql(
+ HDBC hdbc,
+ UCHAR FAR *szSqlStrIn,
+ SDWORD cbSqlStrIn,
+ UCHAR FAR *szSqlStr,
+ SDWORD cbSqlStrMax,
+ SDWORD FAR *pcbSqlStr)
+{
+
+ strncpy_null(szSqlStr, szSqlStrIn, cbSqlStrMax);
+
+ return SQL_SUCCESS;
+}
+
+// - - - - - - - - -
+
+// Supplies parameter data at execution time. Used in conjuction with
+// SQLPutData.
+
+RETCODE SQL_API SQLParamData(
+ HSTMT hstmt,
+ PTR FAR *prgbValue)
+{\r
+StatementClass *stmt = (StatementClass *) hstmt;\r
+int i, retval;\r
+\r
+ if ( ! stmt)\r
+ return SQL_INVALID_HANDLE;\r
+\r
+ if (stmt->data_at_exec < 0) {\r
+ stmt->errornumber = STMT_SEQUENCE_ERROR;\r
+ stmt->errormsg = "No execution-time parameters for this statement";\r
+ return SQL_ERROR;\r
+ }\r
+\r
+ if (stmt->data_at_exec > stmt->parameters_allocated) {\r
+ stmt->errornumber = STMT_SEQUENCE_ERROR;\r
+ stmt->errormsg = "Too many execution-time parameters were present";\r
+ return SQL_ERROR;\r
+ }\r
+\r
+ /* Done, now copy the params and then execute the statement */\r
+ if (stmt->data_at_exec == 0) {\r
+ retval = copy_statement_with_parameters(stmt);\r
+ if (retval != SQL_SUCCESS)\r
+ return retval;\r
+\r
+ return SC_execute(stmt);\r
+ }\r
+\r
+ /* At least 1 data at execution parameter, so Fill in the token value */\r
+ for (i = 0; i < stmt->parameters_allocated; i++) {\r
+ if (stmt->parameters[i].data_at_exec == TRUE) {\r
+ stmt->data_at_exec--;\r
+ stmt->current_exec_param = i;\r
+ stmt->put_data = FALSE;\r
+ *prgbValue = stmt->parameters[i].buffer; /* token */\r
+ }\r
+ }\r
+\r
+ return SQL_NEED_DATA;\r
+}
+
+// - - - - - - - - -
+
+// Supplies parameter data at execution time. Used in conjunction with
+// SQLParamData.
+
+RETCODE SQL_API SQLPutData(
+ HSTMT hstmt,
+ PTR rgbValue,
+ SDWORD cbValue)
+{
+StatementClass *stmt = (StatementClass *) hstmt;\r
+char *buffer;\r
+SDWORD *used;\r
+int old_pos;\r
+\r
+\r
+ if ( ! stmt)\r
+ return SQL_INVALID_HANDLE;\r
+\r
+ \r
+ if (stmt->current_exec_param < 0) {\r
+ stmt->errornumber = STMT_SEQUENCE_ERROR;\r
+ stmt->errormsg = "Previous call was not SQLPutData or SQLParamData";\r
+ return SQL_ERROR;\r
+ }\r
+\r
+ if ( ! stmt->put_data) { /* first call */\r
+\r
+ mylog("SQLPutData: (1) cbValue = %d\n", cbValue);\r
+\r
+ stmt->put_data = TRUE;\r
+\r
+ used = (SDWORD *) malloc(sizeof(SDWORD));\r
+ if ( ! used) {\r
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;\r
+ stmt->errormsg = "Out of memory in SQLPutData (1)";\r
+ return SQL_ERROR;\r
+ }\r
+\r
+ *used = cbValue;\r
+ stmt->parameters[stmt->current_exec_param].EXEC_used = used;\r
+\r
+ if (cbValue == SQL_NULL_DATA)\r
+ return SQL_SUCCESS;\r
+\r
+ if (cbValue == SQL_NTS) {\r
+ buffer = strdup(rgbValue);\r
+ if ( ! buffer) {\r
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;\r
+ stmt->errormsg = "Out of memory in SQLPutData (2)";\r
+ return SQL_ERROR;\r
+ }\r
+ }\r
+ else {\r
+ buffer = malloc(cbValue + 1);\r
+ if ( ! buffer) {\r
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;\r
+ stmt->errormsg = "Out of memory in SQLPutData (2)";\r
+ return SQL_ERROR;\r
+ }\r
+ memcpy(buffer, rgbValue, cbValue);\r
+ buffer[cbValue] = '\0';\r
+ }\r
+\r
+ stmt->parameters[stmt->current_exec_param].EXEC_buffer = buffer;\r
+ }\r
+\r
+ else { /* calling SQLPutData more than once */\r
+\r
+ mylog("SQLPutData: (>1) cbValue = %d\n", cbValue);\r
+\r
+ used = stmt->parameters[stmt->current_exec_param].EXEC_used;\r
+ buffer = stmt->parameters[stmt->current_exec_param].EXEC_buffer;\r
+\r
+ if (cbValue == SQL_NTS) {\r
+ buffer = realloc(buffer, strlen(buffer) + strlen(rgbValue) + 1);\r
+ if ( ! buffer) {\r
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;\r
+ stmt->errormsg = "Out of memory in SQLPutData (3)";\r
+ return SQL_ERROR;\r
+ }\r
+ strcat(buffer, rgbValue);\r
+\r
+ mylog(" cbValue = SQL_NTS: strlen(buffer) = %d\n", strlen(buffer));\r
+\r
+ *used = cbValue;\r
+\r
+ }\r
+ else if (cbValue > 0) {\r
+\r
+ old_pos = *used;\r
+\r
+ *used += cbValue;\r
+\r
+ mylog(" cbValue = %d, old_pos = %d, *used = %d\n", cbValue, old_pos, *used);\r
+\r
+ buffer = realloc(buffer, *used + 1);\r
+ if ( ! buffer) {\r
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;\r
+ stmt->errormsg = "Out of memory in SQLPutData (3)";\r
+ return SQL_ERROR;\r
+ }\r
+\r
+ memcpy(&buffer[old_pos], rgbValue, cbValue);\r
+ buffer[*used] = '\0';\r
+\r
+ }\r
+ else\r
+ return SQL_ERROR;\r
+ \r
+\r
+ /* reassign buffer incase realloc moved it */\r
+ stmt->parameters[stmt->current_exec_param].EXEC_buffer = buffer;\r
+\r
+ }\r
+\r
+\r
+ return SQL_SUCCESS;\r
+}
+
+
--- /dev/null
+\r
+/* Module: info.c\r
+ *\r
+ * Description: This module contains routines related to\r
+ * ODBC informational functions.\r
+ *\r
+ * Classes: n/a\r
+ *\r
+ * API functions: SQLGetInfo, SQLGetTypeInfo, SQLGetFunctions, \r
+ * SQLTables, SQLColumns, SQLStatistics, SQLSpecialColumns,\r
+ * SQLPrimaryKeys, SQLForeignKeys, \r
+ * SQLProcedureColumns(NI), SQLProcedures(NI), \r
+ * SQLTablePrivileges(NI), SQLColumnPrivileges(NI)\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include
+#include
+#include "psqlodbc.h"
+#include
+#include
+#include
+#include "tuple.h"
+#include "pgtypes.h"
+
+#include "environ.h"
+#include "connection.h"
+#include "statement.h"
+#include "qresult.h"
+#include "bind.h"
+#include "misc.h"
+#include "pgtypes.h"
+
+// - - - - - - - - -
+
+RETCODE SQL_API SQLGetInfo(
+ HDBC hdbc,
+ UWORD fInfoType,
+ PTR rgbInfoValue,
+ SWORD cbInfoValueMax,
+ SWORD FAR *pcbInfoValue)
+{
+ConnectionClass *conn = (ConnectionClass *) hdbc;
+char *p;
+
+ if ( ! conn)
+ return SQL_INVALID_HANDLE;
+
+ /* CC: Some sanity checks */
+ if ((NULL == (char *)rgbInfoValue) ||
+ (cbInfoValueMax == 0))
+
+ /* removed: */
+ /* || (NULL == pcbInfoValue) */
+
+ /* pcbInfoValue is ignored for non-character output. */
+ /* some programs (at least Microsoft Query) seem to just send a NULL, */
+ /* so let them get away with it... */
+
+ return SQL_INVALID_HANDLE;
+
+
+ switch (fInfoType) {
+ case SQL_ACCESSIBLE_PROCEDURES: /* ODBC 1.0 */
+ // can the user call all functions returned by SQLProcedures?
+ // I assume access permissions could prevent this in some cases(?)
+ // anyway, SQLProcedures doesn't exist yet.
+ *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_ACCESSIBLE_TABLES: /* ODBC 1.0 */
+ // is the user guaranteed "SELECT" on every table?
+ *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_ACTIVE_CONNECTIONS: /* ODBC 1.0 */
+ // how many simultaneous connections do we support?
+ *((WORD *)rgbInfoValue) = MAX_CONNECTIONS;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_ACTIVE_STATEMENTS: /* ODBC 1.0 */
+ // no limit on the number of active statements.
+ *((WORD *)rgbInfoValue) = (WORD)0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_ALTER_TABLE: /* ODBC 2.0 */
+ // what does 'alter table' support? (bitmask)
+ // postgres doesn't seem to let you drop columns.
+ *((DWORD *)rgbInfoValue) = SQL_AT_ADD_COLUMN;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_BOOKMARK_PERSISTENCE: /* ODBC 2.0 */
+ // through what operations do bookmarks persist? (bitmask)
+ // bookmarks don't exist yet, so they're not very persistent.
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_COLUMN_ALIAS: /* ODBC 2.0 */
+ // do we support column aliases? guess not.
+ *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_CONCAT_NULL_BEHAVIOR: /* ODBC 1.0 */
+ // how does concatenation work with NULL columns?
+ // not sure how you do concatentation, but this way seems
+ // more reasonable
+ *((WORD *)rgbInfoValue) = SQL_CB_NON_NULL;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ // which types of data-conversion do we support?
+ // currently we don't support any, except converting a type
+ // to itself.
+ case SQL_CONVERT_BIGINT:
+ case SQL_CONVERT_BINARY:
+ case SQL_CONVERT_BIT:
+ case SQL_CONVERT_CHAR:
+ case SQL_CONVERT_DATE:
+ case SQL_CONVERT_DECIMAL:
+ case SQL_CONVERT_DOUBLE:
+ case SQL_CONVERT_FLOAT:
+ case SQL_CONVERT_INTEGER:
+ case SQL_CONVERT_LONGVARBINARY:
+ case SQL_CONVERT_LONGVARCHAR:
+ case SQL_CONVERT_NUMERIC:
+ case SQL_CONVERT_REAL:
+ case SQL_CONVERT_SMALLINT:
+ case SQL_CONVERT_TIME:
+ case SQL_CONVERT_TIMESTAMP:
+ case SQL_CONVERT_TINYINT:
+ case SQL_CONVERT_VARBINARY:
+ case SQL_CONVERT_VARCHAR: /* ODBC 1.0 */
+ // only return the type we were called with (bitmask)
+ *((DWORD *)rgbInfoValue) = fInfoType;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_CONVERT_FUNCTIONS: /* ODBC 1.0 */
+ // which conversion functions do we support? (bitmask)
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_CORRELATION_NAME: /* ODBC 1.0 */
+ // I don't know what a correlation name is, so I guess we don't
+ // support them.
+
+ // *((WORD *)rgbInfoValue) = (WORD)SQL_CN_NONE;
+
+ // well, let's just say we do--otherwise Query won't work.
+ *((WORD *)rgbInfoValue) = (WORD)SQL_CN_ANY;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+
+ break;
+
+ case SQL_CURSOR_COMMIT_BEHAVIOR: /* ODBC 1.0 */
+ // postgres definitely closes cursors when a transaction ends,
+ // but you shouldn't have to re-prepare a statement after
+ // commiting a transaction (I don't think)
+ *((WORD *)rgbInfoValue) = (WORD)SQL_CB_CLOSE;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_CURSOR_ROLLBACK_BEHAVIOR: /* ODBC 1.0 */
+ // see above
+ *((WORD *)rgbInfoValue) = (WORD)SQL_CB_CLOSE;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_DATA_SOURCE_NAME: /* ODBC 1.0 */
+ p = CC_get_DSN(conn);
+ if (pcbInfoValue) *pcbInfoValue = strlen(p);
+ strncpy_null((char *)rgbInfoValue, p, (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_DATA_SOURCE_READ_ONLY: /* ODBC 1.0 */
+ if (pcbInfoValue) *pcbInfoValue = 1;
+ sprintf((char *)rgbInfoValue, "%c", CC_is_readonly(conn) ? 'Y' : 'N');
+ break;
+
+ case SQL_DATABASE_NAME: /* Support for old ODBC 1.0 Apps */
+ // case SQL_CURRENT_QUALIFIER:
+ // this tag doesn't seem to be in ODBC 2.0, and it conflicts
+ // with a valid tag (SQL_TIMEDATE_ADD_INTERVALS).
+
+ p = CC_get_database(conn);
+ if (pcbInfoValue) *pcbInfoValue = strlen(p);
+ strncpy_null((char *)rgbInfoValue, p, (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_DBMS_NAME: /* ODBC 1.0 */
+ if (pcbInfoValue) *pcbInfoValue = 10;
+ strncpy_null((char *)rgbInfoValue, DBMS_NAME, (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_DBMS_VER: /* ODBC 1.0 */
+ if (pcbInfoValue) *pcbInfoValue = 25;
+ strncpy_null((char *)rgbInfoValue, DBMS_VERSION, (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_DEFAULT_TXN_ISOLATION: /* ODBC 1.0 */
+ // are dirty reads, non-repeatable reads, and phantoms possible? (bitmask)
+ // by direct experimentation they are not. postgres forces
+ // the newer transaction to wait before doing something that
+ // would cause one of these problems.
+ *((DWORD *)rgbInfoValue) = SQL_TXN_SERIALIZABLE;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_DRIVER_NAME: /* ODBC 1.0 */
+ // this should be the actual filename of the driver
+ p = DRIVER_FILE_NAME;
+ if (pcbInfoValue) *pcbInfoValue = strlen(p);
+ strncpy_null((char *)rgbInfoValue, p, (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_DRIVER_ODBC_VER:
+ /* I think we should return 02.00--at least, that is the version of the */
+ /* spec I'm currently referring to. */
+ if (pcbInfoValue) *pcbInfoValue = 5;
+ strncpy_null((char *)rgbInfoValue, "02.00", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_DRIVER_VER: /* ODBC 1.0 */
+ p = POSTGRESDRIVERVERSION;
+ if (pcbInfoValue) *pcbInfoValue = strlen(p);
+ strncpy_null((char *)rgbInfoValue, p, (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_EXPRESSIONS_IN_ORDERBY: /* ODBC 1.0 */
+ // can you have expressions in an 'order by' clause?
+ // not sure about this. say no for now.
+ if (pcbInfoValue) *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_FETCH_DIRECTION: /* ODBC 1.0 */
+ // which fetch directions are supported? (bitmask)
+ // I guess these apply to SQLExtendedFetch?
+ *((DWORD *)rgbInfoValue) = SQL_FETCH_NEXT ||
+ SQL_FETCH_FIRST ||
+ SQL_FETCH_LAST ||
+ SQL_FETCH_PRIOR ||
+ SQL_FETCH_ABSOLUTE;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_FILE_USAGE: /* ODBC 2.0 */
+ // we are a two-tier driver, not a file-based one.
+ *((WORD *)rgbInfoValue) = (WORD)SQL_FILE_NOT_SUPPORTED;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_GETDATA_EXTENSIONS: /* ODBC 2.0 */
+ // (bitmask)
+ *((DWORD *)rgbInfoValue) = (SQL_GD_ANY_COLUMN | SQL_GD_ANY_ORDER | SQL_GD_BOUND);
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_GROUP_BY: /* ODBC 2.0 */
+ // how do the columns selected affect the columns you can group by?
+ *((WORD *)rgbInfoValue) = SQL_GB_GROUP_BY_EQUALS_SELECT;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_IDENTIFIER_CASE: /* ODBC 1.0 */
+ // are identifiers case-sensitive (yes)
+ *((WORD *)rgbInfoValue) = SQL_IC_SENSITIVE;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_IDENTIFIER_QUOTE_CHAR: /* ODBC 1.0 */
+ // the character used to quote "identifiers" (what are they?)
+ // the manual index lists 'owner names' and 'qualifiers' as
+ // examples of identifiers. it says return a blank for no
+ // quote character, we'll try that...
+ if (pcbInfoValue) *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, " ", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_KEYWORDS: /* ODBC 2.0 */
+ // do this later
+ conn->errormsg = "SQL_KEYWORDS parameter to SQLGetInfo not implemented.";
+ conn->errornumber = CONN_NOT_IMPLEMENTED_ERROR;
+ return SQL_ERROR;
+ break;
+
+ case SQL_LIKE_ESCAPE_CLAUSE: /* ODBC 2.0 */
+ // is there a character that escapes '%' and '_' in a LIKE clause?
+ // not as far as I can tell
+ if (pcbInfoValue) *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_LOCK_TYPES: /* ODBC 2.0 */
+ // which lock types does SQLSetPos support? (bitmask)
+ // SQLSetPos doesn't exist yet
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_MAX_BINARY_LITERAL_LEN: /* ODBC 2.0 */
+ // the maximum length of a query is 2k, so maybe we should
+ // set the maximum length of all these literals to that value?
+ // for now just use zero for 'unknown or no limit'
+
+ // maximum length of a binary literal
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_MAX_CHAR_LITERAL_LEN: /* ODBC 2.0 */
+ // maximum length of a character literal
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_MAX_COLUMN_NAME_LEN: /* ODBC 1.0 */
+ // maximum length of a column name
+ *((WORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MAX_COLUMNS_IN_GROUP_BY: /* ODBC 2.0 */
+ // maximum number of columns in a 'group by' clause
+ *((WORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MAX_COLUMNS_IN_INDEX: /* ODBC 2.0 */
+ // maximum number of columns in an index
+ *((WORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MAX_COLUMNS_IN_ORDER_BY: /* ODBC 2.0 */
+ // maximum number of columns in an ORDER BY statement
+ *((WORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MAX_COLUMNS_IN_SELECT: /* ODBC 2.0 */
+ // I think you get the idea by now
+ *((WORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MAX_COLUMNS_IN_TABLE: /* ODBC 2.0 */
+ *((WORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MAX_CURSOR_NAME_LEN: /* ODBC 1.0 */
+ *((WORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MAX_INDEX_SIZE: /* ODBC 2.0 */
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_MAX_OWNER_NAME_LEN: /* ODBC 1.0 */
+ // the maximum length of a table owner's name. (0 == none)
+ // (maybe this should be 8)
+ *((WORD *)rgbInfoValue) = (WORD)0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MAX_PROCEDURE_NAME_LEN: /* ODBC 1.0 */
+ *((WORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MAX_QUALIFIER_NAME_LEN: /* ODBC 1.0 */
+ *((WORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MAX_ROW_SIZE: /* ODBC 2.0 */
+ // the maximum size of one row
+ // here I do know a definite value
+ *((DWORD *)rgbInfoValue) = 8192;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_MAX_ROW_SIZE_INCLUDES_LONG: /* ODBC 2.0 */
+ // does the preceding value include LONGVARCHAR and LONGVARBINARY
+ // fields?
+ *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_MAX_STATEMENT_LEN: /* ODBC 2.0 */
+ // there should be a definite value here (2k?)
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_MAX_TABLE_NAME_LEN: /* ODBC 1.0 */
+ *((WORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MAX_TABLES_IN_SELECT: /* ODBC 2.0 */
+ *((WORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MAX_USER_NAME_LEN:
+ *(SWORD FAR *)rgbInfoValue = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_MULT_RESULT_SETS: /* ODBC 1.0 */
+ // do we support multiple result sets?
+ if (pcbInfoValue) *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_MULTIPLE_ACTIVE_TXN: /* ODBC 1.0 */
+ // do we support multiple simultaneous transactions?
+ if (pcbInfoValue) *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_NEED_LONG_DATA_LEN: /* ODBC 2.0 */
+ if (pcbInfoValue) *pcbInfoValue = 1;\r
+ /* Dont need the length, SQLPutData can handle any size and multiple calls */
+ strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_NON_NULLABLE_COLUMNS: /* ODBC 1.0 */
+ // I think you can have NOT NULL columns with one of dal Zotto's
+ // patches, but for now we'll say no.
+ *((WORD *)rgbInfoValue) = (WORD)SQL_NNC_NULL;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_NULL_COLLATION: /* ODBC 2.0 */
+ // where are nulls sorted?
+ *((WORD *)rgbInfoValue) = (WORD)SQL_NC_END;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_NUMERIC_FUNCTIONS: /* ODBC 1.0 */
+ // what numeric functions are supported? (bitmask)
+ // I'm not sure if any of these are actually supported
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_ODBC_API_CONFORMANCE: /* ODBC 1.0 */
+ *((WORD *)rgbInfoValue) = SQL_OAC_LEVEL1; /* well, almost */
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_ODBC_SAG_CLI_CONFORMANCE: /* ODBC 1.0 */
+ // can't find any reference to SAG in the ODBC reference manual
+ // (although it's in the index, it doesn't actually appear on
+ // the pages referenced)
+ *((WORD *)rgbInfoValue) = SQL_OSCC_NOT_COMPLIANT;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_ODBC_SQL_CONFORMANCE: /* ODBC 1.0 */
+ *((WORD *)rgbInfoValue) = SQL_OSC_CORE;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_ODBC_SQL_OPT_IEF: /* ODBC 1.0 */
+ // do we support the "Integrity Enhancement Facility" (?)
+ // (something to do with referential integrity?)
+ if (pcbInfoValue) *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_ORDER_BY_COLUMNS_IN_SELECT: /* ODBC 2.0 */
+ // do the columns sorted by have to be in the list of
+ // columns selected?
+ if (pcbInfoValue) *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_OUTER_JOINS: /* ODBC 1.0 */
+ // do we support outer joins?
+ if (pcbInfoValue) *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "N", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_OWNER_TERM: /* ODBC 1.0 */
+ // what we call an owner
+ if (pcbInfoValue) *pcbInfoValue = 5;
+ strncpy_null((char *)rgbInfoValue, "owner", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_OWNER_USAGE: /* ODBC 2.0 */
+ // in which statements can "owners be used"? (what does that mean?
+ // specifying 'owner.table' instead of just 'table' or something?)
+ // (bitmask)
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_POS_OPERATIONS: /* ODBC 2.0 */
+ // what functions does SQLSetPos support? (bitmask)
+ // SQLSetPos does not exist yet
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_POSITIONED_STATEMENTS: /* ODBC 2.0 */
+ // what 'positioned' functions are supported? (bitmask)
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_PROCEDURE_TERM: /* ODBC 1.0 */
+ // what do we call a procedure?
+ if (pcbInfoValue) *pcbInfoValue = 9;
+ strncpy_null((char *)rgbInfoValue, "procedure", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_PROCEDURES: /* ODBC 1.0 */
+ // do we support procedures?
+ if (pcbInfoValue) *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_QUALIFIER_LOCATION: /* ODBC 2.0 */
+ // where does the qualifier go (before or after the table name?)
+ // we don't really use qualifiers, so...
+ *((WORD *)rgbInfoValue) = SQL_QL_START;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_QUALIFIER_NAME_SEPARATOR: /* ODBC 1.0 */
+ // not really too sure what a qualifier is supposed to do either
+ // (specify the name of a database in certain cases?), so nix
+ // on that, too.
+ if (pcbInfoValue) *pcbInfoValue = 0;
+ strncpy_null((char *)rgbInfoValue, "", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_QUALIFIER_TERM: /* ODBC 1.0 */
+ // what we call a qualifier
+ if (pcbInfoValue) *pcbInfoValue = 0;
+ strncpy_null((char *)rgbInfoValue, "", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_QUALIFIER_USAGE: /* ODBC 2.0 */
+ // where can qualifiers be used? (bitmask)
+ // nowhere
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_QUOTED_IDENTIFIER_CASE: /* ODBC 2.0 */
+ // are "quoted" identifiers case-sensitive?
+ // well, we don't really let you quote identifiers, so...
+ *((WORD *)rgbInfoValue) = SQL_IC_SENSITIVE;
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_ROW_UPDATES: /* ODBC 1.0 */
+ // not quite sure what this means
+ if (pcbInfoValue) *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "Y", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_SCROLL_CONCURRENCY: /* ODBC 1.0 */
+ // what concurrency options are supported? (bitmask)
+ // taking a guess here
+ *((DWORD *)rgbInfoValue) = SQL_SCCO_OPT_ROWVER;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_SCROLL_OPTIONS: /* ODBC 1.0 */
+ // what options are supported for scrollable cursors? (bitmask)
+ // not too sure about this one, either...
+ *((DWORD *)rgbInfoValue) = SQL_SO_KEYSET_DRIVEN;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_SEARCH_PATTERN_ESCAPE: /* ODBC 1.0 */
+ // this is supposed to be the character that escapes '_' or '%'
+ // in LIKE clauses. as far as I can tell postgres doesn't have one
+ // (backslash generates an error). returning an empty string means
+ // no escape character is supported.
+ if (pcbInfoValue) *pcbInfoValue = 0;
+ strncpy_null((char *)rgbInfoValue, "", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_SERVER_NAME: /* ODBC 1.0 */
+ p = CC_get_server(conn);
+ if (pcbInfoValue) *pcbInfoValue = strlen(p);
+ strncpy_null((char *)rgbInfoValue, p, (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_SPECIAL_CHARACTERS: /* ODBC 2.0 */
+ // what special characters can be used in table and column names, etc.?
+ // probably more than just this...
+ if (pcbInfoValue) *pcbInfoValue = 1;
+ strncpy_null((char *)rgbInfoValue, "_", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_STATIC_SENSITIVITY: /* ODBC 2.0 */
+ // can changes made inside a cursor be detected? (or something like that)
+ // (bitmask)
+ // only applies to SQLSetPos, which doesn't exist yet.
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_STRING_FUNCTIONS: /* ODBC 1.0 */
+ // what string functions exist? (bitmask)
+ // not sure if any of these exist, either
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_SUBQUERIES: /* ODBC 2.0 */
+ /* postgres 6.3 supports subqueries */
+ *((DWORD *)rgbInfoValue) = (SQL_SQ_QUANTIFIED |
+ SQL_SQ_IN |
+ SQL_SQ_EXISTS |
+ SQL_SQ_COMPARISON);
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_SYSTEM_FUNCTIONS: /* ODBC 1.0 */
+ // what system functions are supported? (bitmask)
+ // none of these seem to be supported, either
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_TABLE_TERM: /* ODBC 1.0 */
+ // what we call a table
+ if (pcbInfoValue) *pcbInfoValue = 5;
+ strncpy_null((char *)rgbInfoValue, "table", (size_t)cbInfoValueMax);
+ break;
+
+ case SQL_TIMEDATE_ADD_INTERVALS: /* ODBC 2.0 */
+ // what resolutions are supported by the "TIMESTAMPADD scalar
+ // function" (whatever that is)? (bitmask)
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_TIMEDATE_DIFF_INTERVALS: /* ODBC 2.0 */
+ // what resolutions are supported by the "TIMESTAMPDIFF scalar
+ // function" (whatever that is)? (bitmask)
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_TIMEDATE_FUNCTIONS: /* ODBC 1.0 */
+ // what time and date functions are supported? (bitmask)
+ *((DWORD *)rgbInfoValue) = 0;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_TXN_CAPABLE: /* ODBC 1.0 */
+ *((WORD *)rgbInfoValue) = (WORD)SQL_TC_ALL;
+ // Postgres can deal with create or drop table statements in a transaction
+ if(pcbInfoValue) { *pcbInfoValue = 2; }
+ break;
+
+ case SQL_TXN_ISOLATION_OPTION: /* ODBC 1.0 */
+ // what transaction isolation options are available? (bitmask)
+ // only the default--serializable transactions.
+ *((DWORD *)rgbInfoValue) = SQL_TXN_SERIALIZABLE;
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_UNION: /* ODBC 2.0 */
+ /* unions with all supported in postgres 6.3 */
+ *((DWORD *)rgbInfoValue) = (SQL_U_UNION | SQL_U_UNION_ALL);
+ if(pcbInfoValue) { *pcbInfoValue = 4; }
+ break;
+
+ case SQL_USER_NAME: /* ODBC 1.0 */
+ p = CC_get_username(conn);
+ if (pcbInfoValue) *pcbInfoValue = strlen(p);
+ strncpy_null((char *)rgbInfoValue, p, (size_t)cbInfoValueMax);
+ break;
+
+ default:
+ /* unrecognized key */
+ conn->errormsg = "Unrecognized key passed to SQLGetInfo.";
+ conn->errornumber = CONN_NOT_IMPLEMENTED_ERROR;
+ return SQL_ERROR;
+ }
+
+ return SQL_SUCCESS;
+}
+
+// - - - - - - - - -
+
+
+RETCODE SQL_API SQLGetTypeInfo(
+ HSTMT hstmt,
+ SWORD fSqlType)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+TupleNode *row;
+int i;
+Int4 type;
+
+ mylog("**** in SQLGetTypeInfo: fSqlType = %d\n", fSqlType);
+
+ if( ! stmt) {
+ return SQL_INVALID_HANDLE;
+ }
+
+ stmt->manual_result = TRUE;
+ stmt->result = QR_Constructor();
+ if( ! stmt->result) {
+ return SQL_ERROR;
+ }
+
+ extend_bindings(stmt, 15);
+
+ QR_set_num_fields(stmt->result, 15);
+ QR_set_field_info(stmt->result, 0, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 1, "DATA_TYPE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 2, "PRECISION", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 3, "LITERAL_PREFIX", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 4, "LITERAL_SUFFIX", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 5, "CREATE_PARAMS", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 6, "NULLABLE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 7, "CASE_SENSITIVE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 8, "SEARCHABLE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 9, "UNSIGNED_ATTRIBUTE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 10, "MONEY", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 11, "AUTO_INCREMENT", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 12, "LOCAL_TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 13, "MINIMUM_SCALE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 14, "MAXIMUM_SCALE", PG_TYPE_INT2, 2);
+
+ // cycle through the types
+ for(i=0, type = pgtypes_defined[0]; type; type = pgtypes_defined[++i]) {
+
+ if(fSqlType == SQL_ALL_TYPES || fSqlType == pgtype_to_sqltype(type)) {
+
+ row = (TupleNode *)malloc(sizeof(TupleNode) + (15 - 1)*sizeof(TupleField));
+
+ /* These values can't be NULL */
+ set_tuplefield_string(&row->tuple[0], pgtype_to_name(type));
+ set_tuplefield_int2(&row->tuple[1], pgtype_to_sqltype(type));
+ set_tuplefield_int2(&row->tuple[6], pgtype_nullable(type));
+ set_tuplefield_int2(&row->tuple[7], pgtype_case_sensitive(type));
+ set_tuplefield_int2(&row->tuple[8], pgtype_searchable(type));
+ set_tuplefield_int2(&row->tuple[10], pgtype_money(type));
+
+ /* Localized data-source dependent data type name (always NULL) */
+ set_tuplefield_null(&row->tuple[12]);
+
+ /* These values can be NULL */
+ set_nullfield_int4(&row->tuple[2], pgtype_precision(type));
+ set_nullfield_string(&row->tuple[3], pgtype_literal_prefix(type));
+ set_nullfield_string(&row->tuple[4], pgtype_literal_suffix(type));
+ set_nullfield_string(&row->tuple[5], pgtype_create_params(type));
+ set_nullfield_int2(&row->tuple[9], pgtype_unsigned(type));
+ set_nullfield_int2(&row->tuple[11], pgtype_auto_increment(type));
+ set_nullfield_int2(&row->tuple[13], pgtype_scale(type));
+ set_nullfield_int2(&row->tuple[14], pgtype_scale(type));
+
+ QR_add_tuple(stmt->result, row);
+ }
+ }
+
+ stmt->status = STMT_FINISHED;
+ stmt->currTuple = -1;
+
+ return SQL_SUCCESS;
+}
+
+// - - - - - - - - -
+
+RETCODE SQL_API SQLGetFunctions(
+ HDBC hdbc,
+ UWORD fFunction,
+ UWORD FAR *pfExists)
+{
+ if (fFunction == SQL_API_ALL_FUNCTIONS) {
+
+
+#ifdef GETINFO_LIE
+ int i;
+ memset(pfExists, 0, sizeof(UWORD)*100);
+
+ pfExists[SQL_API_SQLALLOCENV] = TRUE;
+ pfExists[SQL_API_SQLFREEENV] = TRUE;
+ for (i = SQL_API_SQLALLOCCONNECT; i <= SQL_NUM_FUNCTIONS; i++)
+ pfExists[i] = TRUE;
+ for (i = SQL_EXT_API_START; i <= SQL_EXT_API_LAST; i++)
+ pfExists[i] = TRUE;
+#else
+ memset(pfExists, 0, sizeof(UWORD)*100);
+
+ // ODBC core functions
+ pfExists[SQL_API_SQLALLOCCONNECT] = TRUE;
+ pfExists[SQL_API_SQLALLOCENV] = TRUE;
+ pfExists[SQL_API_SQLALLOCSTMT] = TRUE;
+ pfExists[SQL_API_SQLBINDCOL] = TRUE;
+ pfExists[SQL_API_SQLCANCEL] = TRUE;
+ pfExists[SQL_API_SQLCOLATTRIBUTES] = TRUE;
+ pfExists[SQL_API_SQLCONNECT] = TRUE;
+ pfExists[SQL_API_SQLDESCRIBECOL] = TRUE; // partial
+ pfExists[SQL_API_SQLDISCONNECT] = TRUE;
+ pfExists[SQL_API_SQLERROR] = TRUE;
+ pfExists[SQL_API_SQLEXECDIRECT] = TRUE;
+ pfExists[SQL_API_SQLEXECUTE] = TRUE;
+ pfExists[SQL_API_SQLFETCH] = TRUE;
+ pfExists[SQL_API_SQLFREECONNECT] = TRUE;
+ pfExists[SQL_API_SQLFREEENV] = TRUE;
+ pfExists[SQL_API_SQLFREESTMT] = TRUE;
+ pfExists[SQL_API_SQLGETCURSORNAME] = FALSE;
+ pfExists[SQL_API_SQLNUMRESULTCOLS] = TRUE;
+ pfExists[SQL_API_SQLPREPARE] = TRUE; // complete?
+ pfExists[SQL_API_SQLROWCOUNT] = TRUE;
+ pfExists[SQL_API_SQLSETCURSORNAME] = FALSE;
+ pfExists[SQL_API_SQLSETPARAM] = FALSE;
+ pfExists[SQL_API_SQLTRANSACT] = TRUE;
+
+ // ODBC level 1 functions
+ pfExists[SQL_API_SQLBINDPARAMETER] = TRUE;
+ pfExists[SQL_API_SQLCOLUMNS] = TRUE;
+ pfExists[SQL_API_SQLDRIVERCONNECT] = TRUE;
+ pfExists[SQL_API_SQLGETCONNECTOPTION] = TRUE; // partial
+ pfExists[SQL_API_SQLGETDATA] = TRUE;
+ pfExists[SQL_API_SQLGETFUNCTIONS] = TRUE; // sadly, I still
+ // had to think about
+ // this one
+ pfExists[SQL_API_SQLGETINFO] = TRUE;
+ pfExists[SQL_API_SQLGETSTMTOPTION] = TRUE; // very partial
+ pfExists[SQL_API_SQLGETTYPEINFO] = TRUE;
+ pfExists[SQL_API_SQLPARAMDATA] = TRUE;
+ pfExists[SQL_API_SQLPUTDATA] = TRUE;
+ pfExists[SQL_API_SQLSETCONNECTOPTION] = TRUE; // partial
+ pfExists[SQL_API_SQLSETSTMTOPTION] = TRUE;
+ pfExists[SQL_API_SQLSPECIALCOLUMNS] = TRUE;
+ pfExists[SQL_API_SQLSTATISTICS] = TRUE;
+ pfExists[SQL_API_SQLTABLES] = TRUE;
+
+ // ODBC level 2 functions
+ pfExists[SQL_API_SQLBROWSECONNECT] = FALSE;
+ pfExists[SQL_API_SQLCOLUMNPRIVILEGES] = FALSE;
+ pfExists[SQL_API_SQLDATASOURCES] = FALSE; // only implemented by DM
+ pfExists[SQL_API_SQLDESCRIBEPARAM] = FALSE;
+ pfExists[SQL_API_SQLDRIVERS] = FALSE;
+ pfExists[SQL_API_SQLEXTENDEDFETCH] = TRUE; // partial?
+ pfExists[SQL_API_SQLFOREIGNKEYS] = TRUE;
+ pfExists[SQL_API_SQLMORERESULTS] = TRUE;
+ pfExists[SQL_API_SQLNATIVESQL] = TRUE;
+ pfExists[SQL_API_SQLNUMPARAMS] = TRUE;
+ pfExists[SQL_API_SQLPARAMOPTIONS] = FALSE;
+ pfExists[SQL_API_SQLPRIMARYKEYS] = TRUE;
+ pfExists[SQL_API_SQLPROCEDURECOLUMNS] = FALSE;
+ pfExists[SQL_API_SQLPROCEDURES] = FALSE;
+ pfExists[SQL_API_SQLSETPOS] = FALSE;
+ pfExists[SQL_API_SQLSETSCROLLOPTIONS] = FALSE;
+ pfExists[SQL_API_SQLTABLEPRIVILEGES] = FALSE;
+#endif
+ } else {
+#ifdef GETINFO_LIE
+ *pfExists = TRUE;
+#else
+ switch(fFunction) {
+ case SQL_API_SQLALLOCCONNECT: *pfExists = TRUE; break;
+ case SQL_API_SQLALLOCENV: *pfExists = TRUE; break;
+ case SQL_API_SQLALLOCSTMT: *pfExists = TRUE; break;
+ case SQL_API_SQLBINDCOL: *pfExists = TRUE; break;
+ case SQL_API_SQLCANCEL: *pfExists = TRUE; break;
+ case SQL_API_SQLCOLATTRIBUTES: *pfExists = TRUE; break;
+ case SQL_API_SQLCONNECT: *pfExists = TRUE; break;
+ case SQL_API_SQLDESCRIBECOL: *pfExists = TRUE; break; // partial
+ case SQL_API_SQLDISCONNECT: *pfExists = TRUE; break;
+ case SQL_API_SQLERROR: *pfExists = TRUE; break;
+ case SQL_API_SQLEXECDIRECT: *pfExists = TRUE; break;
+ case SQL_API_SQLEXECUTE: *pfExists = TRUE; break;
+ case SQL_API_SQLFETCH: *pfExists = TRUE; break;
+ case SQL_API_SQLFREECONNECT: *pfExists = TRUE; break;
+ case SQL_API_SQLFREEENV: *pfExists = TRUE; break;
+ case SQL_API_SQLFREESTMT: *pfExists = TRUE; break;
+ case SQL_API_SQLGETCURSORNAME: *pfExists = FALSE; break;
+ case SQL_API_SQLNUMRESULTCOLS: *pfExists = TRUE; break;
+ case SQL_API_SQLPREPARE: *pfExists = TRUE; break;
+ case SQL_API_SQLROWCOUNT: *pfExists = TRUE; break;
+ case SQL_API_SQLSETCURSORNAME: *pfExists = FALSE; break;
+ case SQL_API_SQLSETPARAM: *pfExists = FALSE; break;
+ case SQL_API_SQLTRANSACT: *pfExists = TRUE; break;
+
+ // ODBC level 1 functions
+ case SQL_API_SQLBINDPARAMETER: *pfExists = TRUE; break;
+ case SQL_API_SQLCOLUMNS: *pfExists = TRUE; break;
+ case SQL_API_SQLDRIVERCONNECT: *pfExists = TRUE; break;
+ case SQL_API_SQLGETCONNECTOPTION: *pfExists = TRUE; break; // partial
+ case SQL_API_SQLGETDATA: *pfExists = TRUE; break;
+ case SQL_API_SQLGETFUNCTIONS: *pfExists = TRUE; break;
+ case SQL_API_SQLGETINFO: *pfExists = TRUE; break;
+ case SQL_API_SQLGETSTMTOPTION: *pfExists = TRUE; break; // very partial
+ case SQL_API_SQLGETTYPEINFO: *pfExists = TRUE; break;
+ case SQL_API_SQLPARAMDATA: *pfExists = TRUE; break;
+ case SQL_API_SQLPUTDATA: *pfExists = TRUE; break;
+ case SQL_API_SQLSETCONNECTOPTION: *pfExists = TRUE; break; // partial
+ case SQL_API_SQLSETSTMTOPTION: *pfExists = TRUE; break;
+ case SQL_API_SQLSPECIALCOLUMNS: *pfExists = TRUE; break;
+ case SQL_API_SQLSTATISTICS: *pfExists = TRUE; break;
+ case SQL_API_SQLTABLES: *pfExists = TRUE; break;
+
+ // ODBC level 2 functions
+ case SQL_API_SQLBROWSECONNECT: *pfExists = FALSE; break;
+ case SQL_API_SQLCOLUMNPRIVILEGES: *pfExists = FALSE; break;
+ case SQL_API_SQLDATASOURCES: *pfExists = FALSE; break; // only implemented by DM
+ case SQL_API_SQLDESCRIBEPARAM: *pfExists = FALSE; break;
+ case SQL_API_SQLDRIVERS: *pfExists = FALSE; break;
+ case SQL_API_SQLEXTENDEDFETCH: *pfExists = TRUE; break; // partial?
+ case SQL_API_SQLFOREIGNKEYS: *pfExists = TRUE; break;
+ case SQL_API_SQLMORERESULTS: *pfExists = TRUE; break;
+ case SQL_API_SQLNATIVESQL: *pfExists = TRUE; break;
+ case SQL_API_SQLNUMPARAMS: *pfExists = TRUE; break;
+ case SQL_API_SQLPARAMOPTIONS: *pfExists = FALSE; break;
+ case SQL_API_SQLPRIMARYKEYS: *pfExists = TRUE; break;
+ case SQL_API_SQLPROCEDURECOLUMNS: *pfExists = FALSE; break;
+ case SQL_API_SQLPROCEDURES: *pfExists = FALSE; break;
+ case SQL_API_SQLSETPOS: *pfExists = FALSE; break;
+ case SQL_API_SQLSETSCROLLOPTIONS: *pfExists = FALSE; break;
+ case SQL_API_SQLTABLEPRIVILEGES: *pfExists = FALSE; break;
+ }
+#endif
+ }
+
+ return SQL_SUCCESS;
+}
+
+
+
+RETCODE SQL_API SQLTables(
+ HSTMT hstmt,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName,
+ UCHAR FAR * szTableType,
+ SWORD cbTableType)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+StatementClass *tbl_stmt;
+TupleNode *row;
+HSTMT htbl_stmt;
+RETCODE result;
+char *tableType;
+char tables_query[MAX_STATEMENT_LEN];
+char table_name[MAX_INFO_STRING], table_owner[MAX_INFO_STRING];
+SDWORD table_name_len, table_owner_len;
+
+mylog("**** SQLTables(): ENTER, stmt=%u\n", stmt);
+
+ if( ! stmt)
+ return SQL_INVALID_HANDLE;
+
+ stmt->manual_result = TRUE;
+ stmt->errormsg_created = TRUE;
+
+ result = SQLAllocStmt( stmt->hdbc, &htbl_stmt);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Couldn't allocate statement for SQLTables result.";
+ return SQL_ERROR;
+ }
+ tbl_stmt = (StatementClass *) htbl_stmt;
+
+ // **********************************************************************
+ // Create the query to find out the tables
+ // **********************************************************************
+
+ strcpy(tables_query, "select relname, usename from pg_class, pg_user where relkind = 'r' ");
+
+ my_strcat(tables_query, " and usename like '%.*s'", szTableOwner, cbTableOwner);
+ my_strcat(tables_query, " and relname like '%.*s'", szTableName, cbTableName);
+
+ // make_string mallocs memory
+ tableType = make_string(szTableType, cbTableType, NULL);
+ if (tableType && ! strstr(tableType, "SYSTEM TABLE")) // is SYSTEM TABLE not present?
+ strcat(tables_query, " and relname not like '" POSTGRES_SYS_PREFIX "%' and relname not like '" INSIGHT_SYS_PREFIX "%'");
+
+ if (tableType)
+ free(tableType);
+
+ strcat(tables_query, " and relname !~ '^Inv[0-9]+' and int4out(usesysid) = int4out(relowner) order by relname");
+
+ // **********************************************************************
+
+ result = SQLExecDirect(htbl_stmt, tables_query, strlen(tables_query));
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = SC_create_errormsg(htbl_stmt);
+ stmt->errornumber = tbl_stmt->errornumber;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(htbl_stmt, 1, SQL_C_CHAR,
+ table_name, MAX_INFO_STRING, &table_name_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(htbl_stmt, 2, SQL_C_CHAR,
+ table_owner, MAX_INFO_STRING, &table_owner_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ stmt->result = QR_Constructor();
+ if(!stmt->result) {
+ stmt->errormsg = "Couldn't allocate memory for SQLTables result.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ // the binding structure for a statement is not set up until
+ // a statement is actually executed, so we'll have to do this ourselves.
+ extend_bindings(stmt, 5);
+
+ // set the field names
+ QR_set_num_fields(stmt->result, 5);
+ QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 3, "TABLE_TYPE", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 4, "REMARKS", PG_TYPE_TEXT, 254);
+
+ // add the tuples
+ result = SQLFetch(htbl_stmt);
+ while((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) {
+ row = (TupleNode *)malloc(sizeof(TupleNode) + (5 - 1) * sizeof(TupleField));
+
+ set_tuplefield_string(&row->tuple[0], "");
+
+ // I have to hide the table owner from Access, otherwise it
+ // insists on referring to the table as 'owner.table'.
+ // (this is valid according to the ODBC SQL grammar, but
+ // Postgres won't support it.)
+ // set_tuplefield_string(&row->tuple[1], table_owner);
+
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], table_name);
+
+ mylog("SQLTables: table_name = '%s'\n", table_name);
+
+ // careful: this is case-sensitive
+ if(strncmp(table_name, POSTGRES_SYS_PREFIX, strlen(POSTGRES_SYS_PREFIX)) == 0 ||
+ strncmp(table_name, INSIGHT_SYS_PREFIX, strlen(INSIGHT_SYS_PREFIX)) == 0) {
+ set_tuplefield_string(&row->tuple[3], "SYSTEM TABLE");
+ } else {
+ set_tuplefield_string(&row->tuple[3], "TABLE");
+ }
+
+ set_tuplefield_string(&row->tuple[4], "");
+
+ QR_add_tuple(stmt->result, row);
+
+ result = SQLFetch(htbl_stmt);
+ }
+ if(result != SQL_NO_DATA_FOUND) {
+ stmt->errormsg = SC_create_errormsg(htbl_stmt);
+ stmt->errornumber = tbl_stmt->errornumber;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ // also, things need to think that this statement is finished so
+ // the results can be retrieved.
+ stmt->status = STMT_FINISHED;
+
+ // set up the current tuple pointer for SQLFetch
+ stmt->currTuple = -1;
+
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ mylog("SQLTables(): EXIT, stmt=%u\n", stmt);
+ return SQL_SUCCESS;
+}
+
+RETCODE SQL_API SQLColumns(
+ HSTMT hstmt,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName,
+ UCHAR FAR * szColumnName,
+ SWORD cbColumnName)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+TupleNode *row;
+HSTMT hcol_stmt;
+StatementClass *col_stmt;
+char columns_query[MAX_STATEMENT_LEN];
+RETCODE result;
+char table_owner[MAX_INFO_STRING], table_name[MAX_INFO_STRING], field_name[MAX_INFO_STRING], field_type_name[MAX_INFO_STRING];
+Int2 field_number, field_length, mod_length;
+Int4 field_type;
+SDWORD table_owner_len, table_name_len, field_name_len,
+ field_type_len, field_type_name_len, field_number_len,
+ field_length_len, mod_length_len;
+
+mylog("**** SQLColumns(): ENTER, stmt=%u\n", stmt);
+
+ if( ! stmt)
+ return SQL_INVALID_HANDLE;
+
+ stmt->manual_result = TRUE;
+ stmt->errormsg_created = TRUE;
+\r
+ // **********************************************************************
+ // Create the query to find out the columns (Note: pre 6.3 did not have the atttypmod field)
+ // **********************************************************************
+ sprintf(columns_query, "select u.usename, c.relname, a.attname, a.atttypid,t.typname, a.attnum, a.attlen, %s from pg_user u, pg_class c, pg_attribute a, pg_type t where "
+ "int4out(u.usesysid) = int4out(c.relowner) and c.oid= a.attrelid and a.atttypid = t.oid and (a.attnum > 0)",\r
+ PROTOCOL_62(&(stmt->hdbc->connInfo)) ? "a.attlen" : "a.atttypmod");
+
+ my_strcat(columns_query, " and c.relname like '%.*s'", szTableName, cbTableName);
+ my_strcat(columns_query, " and u.usename like '%.*s'", szTableOwner, cbTableOwner);
+ my_strcat(columns_query, " and a.attname like '%.*s'", szColumnName, cbColumnName);
+
+ // give the output in the order the columns were defined
+ // when the table was created
+ strcat(columns_query, " order by attnum");
+ // **********************************************************************
+
+ result = SQLAllocStmt( stmt->hdbc, &hcol_stmt);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Couldn't allocate statement for SQLColumns result.";
+ return SQL_ERROR;
+ }
+ col_stmt = (StatementClass *) hcol_stmt;
+
+ result = SQLExecDirect(hcol_stmt, columns_query,
+ strlen(columns_query));
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = SC_create_errormsg(hcol_stmt);
+ stmt->errornumber = col_stmt->errornumber;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(hcol_stmt, 1, SQL_C_CHAR,
+ table_owner, MAX_INFO_STRING, &table_owner_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(hcol_stmt, 2, SQL_C_CHAR,
+ table_name, MAX_INFO_STRING, &table_name_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(hcol_stmt, 3, SQL_C_CHAR,
+ field_name, MAX_INFO_STRING, &field_name_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(hcol_stmt, 4, SQL_C_DEFAULT,
+ &field_type, 4, &field_type_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(hcol_stmt, 5, SQL_C_CHAR,
+ field_type_name, MAX_INFO_STRING, &field_type_name_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(hcol_stmt, 6, SQL_C_DEFAULT,
+ &field_number, MAX_INFO_STRING, &field_number_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(hcol_stmt, 7, SQL_C_DEFAULT,
+ &field_length, MAX_INFO_STRING, &field_length_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(hcol_stmt, 8, SQL_C_DEFAULT,
+ &mod_length, MAX_INFO_STRING, &mod_length_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ stmt->result = QR_Constructor();
+ if(!stmt->result) {
+ stmt->errormsg = "Couldn't allocate memory for SQLColumns result.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ // the binding structure for a statement is not set up until
+ // a statement is actually executed, so we'll have to do this ourselves.
+ extend_bindings(stmt, 12);
+
+ // set the field names
+ QR_set_num_fields(stmt->result, 12);
+ QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 3, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 4, "DATA_TYPE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 5, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 6, "PRECISION", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 7, "LENGTH", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 8, "SCALE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 9, "RADIX", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 10, "NULLABLE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 11, "REMARKS", PG_TYPE_TEXT, 254);
+
+ result = SQLFetch(hcol_stmt);
+ while((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) {
+ row = (TupleNode *)malloc(sizeof(TupleNode) +
+ (12 - 1) * sizeof(TupleField));
+
+ set_tuplefield_string(&row->tuple[0], "");
+ // see note in SQLTables()
+ // set_tuplefield_string(&row->tuple[1], table_owner);
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], table_name);
+ set_tuplefield_string(&row->tuple[3], field_name);
+
+ /* Replace an unknown postgres type with SQL_CHAR type */
+ /* Leave the field_type_name with "unknown" */
+ if (pgtype_to_sqltype(field_type) == PG_UNKNOWN)
+ set_tuplefield_int2(&row->tuple[4], SQL_CHAR);
+ else
+ set_tuplefield_int2(&row->tuple[4], pgtype_to_sqltype(field_type));
+
+ set_tuplefield_string(&row->tuple[5], field_type_name);
+\r
+\r
+ /* Some Notes about Postgres Data Types:\r
+\r
+ VARCHAR - the length is stored in the pg_attribute.atttypmod field\r
+ BPCHAR - the length is also stored as varchar is\r
+ NAME - the length is fixed and stored in pg_attribute.attlen field (32 on my system)\r
+\r
+ */
+ if((field_type == PG_TYPE_VARCHAR) ||
+ (field_type == PG_TYPE_NAME) ||\r
+ (field_type == PG_TYPE_BPCHAR)) {
+
+ if (field_type == PG_TYPE_NAME)
+ mod_length = field_length; // the length is in attlen
+ else if (mod_length >= 4)
+ mod_length -= 4; // the length is in atttypmod - 4
+
+ if (mod_length > MAX_VARCHAR_SIZE || mod_length <= 0)
+ mod_length = MAX_VARCHAR_SIZE;
+
+ mylog("SQLColumns: field type is VARCHAR,NAME: field_type = %d, mod_length = %d\n", field_type, mod_length);
+
+ set_tuplefield_int4(&row->tuple[7], mod_length);
+ set_tuplefield_int4(&row->tuple[6], mod_length);
+ } else {
+ mylog("SQLColumns: field type is OTHER: field_type = %d, pgtype_length = %d\n", field_type, pgtype_length(field_type));
+
+ set_tuplefield_int4(&row->tuple[7], pgtype_length(field_type));
+ set_tuplefield_int4(&row->tuple[6], pgtype_precision(field_type));
+
+ }
+
+ set_nullfield_int2(&row->tuple[8], pgtype_scale(field_type));
+ set_nullfield_int2(&row->tuple[9], pgtype_radix(field_type));
+ set_tuplefield_int2(&row->tuple[10], pgtype_nullable(field_type));
+ set_tuplefield_string(&row->tuple[11], "");
+
+ QR_add_tuple(stmt->result, row);
+
+ result = SQLFetch(hcol_stmt);
+ }
+ if(result != SQL_NO_DATA_FOUND) {
+ stmt->errormsg = SC_create_errormsg(hcol_stmt);
+ stmt->errornumber = col_stmt->errornumber;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ // also, things need to think that this statement is finished so
+ // the results can be retrieved.
+ stmt->status = STMT_FINISHED;
+
+ // set up the current tuple pointer for SQLFetch
+ stmt->currTuple = -1;
+
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ mylog("SQLColumns(): EXIT, stmt=%u\n", stmt);
+ return SQL_SUCCESS;
+}
+
+RETCODE SQL_API SQLSpecialColumns(
+ HSTMT hstmt,
+ UWORD fColType,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName,
+ UWORD fScope,
+ UWORD fNullable)
+{
+TupleNode *row;
+StatementClass *stmt = (StatementClass *) hstmt;
+
+mylog("**** SQLSpecialColumns(): ENTER, stmt=%u\n", stmt);
+
+ if( ! stmt) {
+ return SQL_INVALID_HANDLE;
+ }
+ stmt->manual_result = TRUE;
+ stmt->result = QR_Constructor();
+ extend_bindings(stmt, 8);
+
+ QR_set_num_fields(stmt->result, 8);
+ QR_set_field_info(stmt->result, 0, "SCOPE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 1, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 2, "DATA_TYPE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 3, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 4, "PRECISION", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 5, "LENGTH", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 6, "SCALE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 7, "PSEUDO_COLUMN", PG_TYPE_INT2, 2);
+
+ /* use the oid value for the rowid */
+ if(fColType == SQL_BEST_ROWID) {
+
+ row = (TupleNode *)malloc(sizeof(TupleNode) + (8 - 1) * sizeof(TupleField));
+
+ set_tuplefield_int2(&row->tuple[0], SQL_SCOPE_SESSION);
+ set_tuplefield_string(&row->tuple[1], "oid");
+ set_tuplefield_int2(&row->tuple[2], pgtype_to_sqltype(PG_TYPE_OID));
+ set_tuplefield_string(&row->tuple[3], "OID");
+ set_tuplefield_int4(&row->tuple[4], pgtype_precision(PG_TYPE_OID));
+ set_tuplefield_int4(&row->tuple[5], pgtype_length(PG_TYPE_OID));
+ set_tuplefield_int2(&row->tuple[6], pgtype_scale(PG_TYPE_OID));
+ set_tuplefield_int2(&row->tuple[7], SQL_PC_PSEUDO);
+
+ QR_add_tuple(stmt->result, row);
+
+ } else if(fColType == SQL_ROWVER) {
+ /* can columns automatically update? */
+ /* for now assume no. */
+ /* return an empty result. */
+ }
+
+ stmt->status = STMT_FINISHED;
+ stmt->currTuple = -1;
+
+ mylog("SQLSpecialColumns(): EXIT, stmt=%u\n", stmt);
+ return SQL_SUCCESS;
+}
+
+RETCODE SQL_API SQLStatistics(
+ HSTMT hstmt,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName,
+ UWORD fUnique,
+ UWORD fAccuracy)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+char index_query[MAX_STATEMENT_LEN];
+HSTMT hindx_stmt;
+RETCODE result;
+char *table_name;
+char index_name[MAX_INFO_STRING];
+short fields_vector[8];
+SDWORD index_name_len, fields_vector_len;
+TupleNode *row;
+int i;
+HSTMT hcol_stmt;
+StatementClass *col_stmt, *indx_stmt;
+char column_name[MAX_INFO_STRING];
+char **column_names = 0;
+Int4 column_name_len;
+int total_columns = 0;
+char error = TRUE;
+
+mylog("**** SQLStatistics(): ENTER, stmt=%u\n", stmt);
+
+ if( ! stmt) {
+ return SQL_INVALID_HANDLE;
+ }
+
+ stmt->manual_result = TRUE;
+ stmt->errormsg_created = TRUE;
+
+ stmt->result = QR_Constructor();
+ if(!stmt->result) {
+ stmt->errormsg = "Couldn't allocate memory for SQLStatistics result.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ return SQL_ERROR;
+ }
+
+ // the binding structure for a statement is not set up until
+ // a statement is actually executed, so we'll have to do this ourselves.
+ extend_bindings(stmt, 13);
+
+ // set the field names
+ QR_set_num_fields(stmt->result, 13);
+ QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 3, "NON_UNIQUE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 4, "INDEX_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 5, "INDEX_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 6, "TYPE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 7, "SEQ_IN_INDEX", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 8, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 9, "COLLATION", PG_TYPE_CHAR, 1);
+ QR_set_field_info(stmt->result, 10, "CARDINALITY", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 11, "PAGES", PG_TYPE_INT4, 4);
+ QR_set_field_info(stmt->result, 12, "FILTER_CONDITION", PG_TYPE_TEXT, MAX_INFO_STRING);
+
+ // there are no unique indexes in postgres, so return nothing
+ // if those are requested
+ if(fUnique != SQL_INDEX_UNIQUE) {
+ // only use the table name... the owner should be redundant, and
+ // we never use qualifiers.
+ table_name = make_string(szTableName, cbTableName, NULL);
+ if ( ! table_name) {
+ stmt->errormsg = "No table name passed to SQLStatistics.";
+ stmt->errornumber = STMT_INTERNAL_ERROR;
+ return SQL_ERROR;
+ }
+
+ // we need to get a list of the field names first,
+ // so we can return them later.
+ result = SQLAllocStmt( stmt->hdbc, &hcol_stmt);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = "SQLAllocStmt failed in SQLStatistics for columns.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ goto SEEYA;
+ }
+
+ col_stmt = (StatementClass *) hcol_stmt;
+
+ result = SQLColumns(hcol_stmt, "", 0, "", 0,
+ table_name, (SWORD) strlen(table_name), "", 0);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = col_stmt->errormsg; // "SQLColumns failed in SQLStatistics.";
+ stmt->errornumber = col_stmt->errornumber; // STMT_EXEC_ERROR;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ goto SEEYA;
+ }
+ result = SQLBindCol(hcol_stmt, 4, SQL_C_CHAR,
+ column_name, MAX_INFO_STRING, &column_name_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = col_stmt->errormsg;
+ stmt->errornumber = col_stmt->errornumber;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ goto SEEYA;
+
+ }
+
+ result = SQLFetch(hcol_stmt);
+ while((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) {
+ total_columns++;
+
+ column_names =
+ (char **)realloc(column_names,
+ total_columns * sizeof(char *));
+ column_names[total_columns-1] =
+ (char *)malloc(strlen(column_name)+1);
+ strcpy(column_names[total_columns-1], column_name);
+
+ result = SQLFetch(hcol_stmt);
+ }
+ if(result != SQL_NO_DATA_FOUND || total_columns == 0) {
+ stmt->errormsg = SC_create_errormsg(hcol_stmt); // "Couldn't get column names in SQLStatistics.";
+ stmt->errornumber = col_stmt->errornumber;
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+ goto SEEYA;
+
+ }
+
+ SQLFreeStmt(hcol_stmt, SQL_DROP);
+
+ // get a list of indexes on this table
+ result = SQLAllocStmt( stmt->hdbc, &hindx_stmt);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = "SQLAllocStmt failed in SQLStatistics for indices.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ goto SEEYA;
+
+ }
+ indx_stmt = (StatementClass *) hindx_stmt;
+
+ sprintf(index_query, "select c.relname, i.indkey from pg_index i, pg_class c, pg_class d where c.oid = i.indexrelid and d.relname = '%s' and d.oid = i.indrelid",
+ table_name);
+
+ result = SQLExecDirect(hindx_stmt, index_query, strlen(index_query));
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = SC_create_errormsg(hindx_stmt); // "Couldn't execute index query (w/SQLExecDirect) in SQLStatistics.";
+ stmt->errornumber = indx_stmt->errornumber;
+ SQLFreeStmt(hindx_stmt, SQL_DROP);
+ goto SEEYA;
+
+ }
+
+ result = SQLBindCol(hindx_stmt, 1, SQL_C_CHAR,
+ index_name, MAX_INFO_STRING, &index_name_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = indx_stmt->errormsg; // "Couldn't bind column in SQLStatistics.";
+ stmt->errornumber = indx_stmt->errornumber;
+ SQLFreeStmt(hindx_stmt, SQL_DROP);
+ goto SEEYA;
+
+ }
+ // bind the vector column
+ result = SQLBindCol(hindx_stmt, 2, SQL_C_DEFAULT,
+ fields_vector, 16, &fields_vector_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = indx_stmt->errormsg; // "Couldn't bind column in SQLStatistics.";
+ stmt->errornumber = indx_stmt->errornumber;
+ SQLFreeStmt(hindx_stmt, SQL_DROP);
+ goto SEEYA;
+
+ }
+
+ result = SQLFetch(hindx_stmt);
+ while((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) {
+ i = 0;
+ // add a row in this table for each field in the index
+ while(i < 8 && fields_vector[i] != 0) {
+
+ row = (TupleNode *)malloc(sizeof(TupleNode) +
+ (13 - 1) * sizeof(TupleField));
+
+ // no table qualifier
+ set_tuplefield_string(&row->tuple[0], "");
+ // don't set the table owner, else Access tries to use it
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], table_name);
+
+ // Postgres95 indices always allow non-unique values.
+ set_tuplefield_int2(&row->tuple[3], TRUE);
+
+ // no index qualifier
+ set_tuplefield_string(&row->tuple[4], "");
+ set_tuplefield_string(&row->tuple[5], index_name);
+
+ // check this--what does it mean for an index
+ // to be clustered? (none of mine seem to be--
+ // we can and probably should find this out from
+ // the pg_index table)
+ set_tuplefield_int2(&row->tuple[6], SQL_INDEX_HASHED);
+ set_tuplefield_int2(&row->tuple[7], (Int2) (i+1));
+
+ if(fields_vector[i] < 0 || fields_vector[i] > total_columns)
+ set_tuplefield_string(&row->tuple[8], "UNKNOWN");
+ else
+ set_tuplefield_string(&row->tuple[8], column_names[fields_vector[i]-1]);
+
+ set_tuplefield_string(&row->tuple[9], "A");
+ set_tuplefield_null(&row->tuple[10]);
+ set_tuplefield_null(&row->tuple[11]);
+ set_tuplefield_null(&row->tuple[12]);
+
+ QR_add_tuple(stmt->result, row);
+ i++;
+ }
+
+ result = SQLFetch(hindx_stmt);
+ }
+ if(result != SQL_NO_DATA_FOUND) {
+ stmt->errormsg = SC_create_errormsg(hindx_stmt); // "SQLFetch failed in SQLStatistics.";
+ stmt->errornumber = indx_stmt->errornumber;
+ SQLFreeStmt(hindx_stmt, SQL_DROP);
+ goto SEEYA;
+ }
+
+ SQLFreeStmt(hindx_stmt, SQL_DROP);
+ }
+
+ // also, things need to think that this statement is finished so
+ // the results can be retrieved.
+ stmt->status = STMT_FINISHED;
+
+ // set up the current tuple pointer for SQLFetch
+ stmt->currTuple = -1;
+
+ error = FALSE;
+
+SEEYA:
+ /* These things should be freed on any error ALSO! */
+ free(table_name);
+ for(i = 0; i < total_columns; i++) {
+ free(column_names[i]);
+ }
+ free(column_names);
+
+ mylog("SQLStatistics(): EXIT, %s, stmt=%u\n", error ? "error" : "success", stmt);
+
+ if (error)
+ return SQL_ERROR;
+ else
+ return SQL_SUCCESS;
+}
+
+RETCODE SQL_API SQLColumnPrivileges(
+ HSTMT hstmt,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName,
+ UCHAR FAR * szColumnName,
+ SWORD cbColumnName)
+{
+ return SQL_ERROR;
+}
+
+RETCODE
+getPrimaryKeyString(StatementClass *stmt, char *szTableName, SWORD cbTableName, char *svKey, int *nKey)
+{
+HSTMT htbl_stmt;
+StatementClass *tbl_stmt;
+RETCODE result;
+char tables_query[MAX_STATEMENT_LEN];
+char attname[MAX_INFO_STRING];
+SDWORD attname_len;
+int nk = 0;
+
+ if (nKey != NULL)
+ *nKey = 0;
+
+ svKey[0] = '\0';
+
+ stmt->errormsg_created = TRUE;
+
+ result = SQLAllocStmt( stmt->hdbc, &htbl_stmt);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Couldn't allocate statement for Primary Key result.";
+ return SQL_ERROR;
+ }
+ tbl_stmt = (StatementClass *) htbl_stmt;
+
+ tables_query[0] = '\0';
+ if ( ! my_strcat(tables_query, "select distinct on attnum a2.attname, a2.attnum from pg_attribute a1, pg_attribute a2, pg_class c, pg_index i where c.relname = '%.*s_key' AND c.oid = i.indexrelid AND a1.attrelid = c.oid AND a2.attrelid = c.oid AND (i.indkey[0] = a1.attnum OR i.indkey[1] = a1.attnum OR i.indkey[2] = a1.attnum OR i.indkey[3] = a1.attnum OR i.indkey[4] = a1.attnum OR i.indkey[5] = a1.attnum OR i.indkey[6] = a1.attnum OR i.indkey[7] = a1.attnum) order by a2.attnum",
+ szTableName, cbTableName)) {
+
+ stmt->errormsg = "No Table specified to getPrimaryKeyString.";
+ stmt->errornumber = STMT_INTERNAL_ERROR;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ mylog("getPrimaryKeyString: tables_query='%s'\n", tables_query);
+
+ result = SQLExecDirect(htbl_stmt, tables_query, strlen(tables_query));
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = SC_create_errormsg(htbl_stmt);
+ stmt->errornumber = tbl_stmt->errornumber;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(htbl_stmt, 1, SQL_C_CHAR,
+ attname, MAX_INFO_STRING, &attname_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLFetch(htbl_stmt);
+ while((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) {
+
+ if (strlen(svKey) > 0)
+ strcat(svKey, "+");
+ strcat(svKey, attname);
+
+ result = SQLFetch(htbl_stmt);
+ nk++;
+ }
+
+ if(result != SQL_NO_DATA_FOUND) {
+ stmt->errormsg = SC_create_errormsg(htbl_stmt);
+ stmt->errornumber = tbl_stmt->errornumber;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+
+ if (nKey != NULL)
+ *nKey = nk;
+
+ mylog(">> getPrimaryKeyString: returning nKey=%d, svKey='%s'\n", nk, svKey);
+ return result;
+}
+
+RETCODE
+getPrimaryKeyArray(StatementClass *stmt, char *szTableName, SWORD cbTableName, char keyArray[][MAX_INFO_STRING], int *nKey)
+{
+RETCODE result;
+char svKey[MAX_KEYLEN], *svKeyPtr;
+int i = 0;
+
+ result = getPrimaryKeyString(stmt, szTableName, cbTableName, svKey, nKey);
+ if (result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND)
+ // error passed from above
+ return result;
+
+ // If no keys, return NO_DATA_FOUND
+ if (svKey[0] == '\0') {
+ mylog("!!!!!! getPrimaryKeyArray: svKey was null\n");
+ return SQL_NO_DATA_FOUND;
+ }
+
+ // mylog(">> primarykeyArray: nKey=%d, svKey='%s'\n", *nKey, svKey);
+
+ svKeyPtr = strtok(svKey, "+");
+ while (svKeyPtr != NULL && i < MAX_KEYPARTS) {
+ strcpy(keyArray[i++], svKeyPtr);
+ svKeyPtr = strtok(NULL, "+");
+ }
+
+ /*
+ for (i = 0; i < *nKey; i++)
+ mylog(">> keyArray[%d] = '%s'\n", i, keyArray[i]);
+ */
+
+ return result;
+}
+
+
+RETCODE SQL_API SQLPrimaryKeys(
+ HSTMT hstmt,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+TupleNode *row;
+RETCODE result;
+char svKey[MAX_KEYLEN], *ptr;
+int seq = 1, nkeys = 0;
+
+mylog("**** SQLPrimaryKeys(): ENTER, stmt=%u\n", stmt);
+
+ if( ! stmt) {
+ return SQL_INVALID_HANDLE;
+ }
+ stmt->manual_result = TRUE;
+
+ result = getPrimaryKeyString(stmt, szTableName, cbTableName, svKey, &nkeys);
+
+ mylog(">> PrimaryKeys: getPrimaryKeyString() returned %d, nkeys=%d, svKey = '%s'\n", result, nkeys, svKey);
+
+ if (result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND) {
+ // error msg passed from above
+ return result;
+ }
+
+ // I'm not sure if this is correct to return when there are no keys or
+ // if an empty result set would be better.
+ if (nkeys == 0) {
+ stmt->errornumber = STMT_INFO_ONLY;
+ stmt->errormsg = "No primary keys for this table.";
+ return SQL_SUCCESS_WITH_INFO;
+ }
+
+ stmt->result = QR_Constructor();
+ if(!stmt->result) {
+ stmt->errormsg = "Couldn't allocate memory for SQLPrimaryKeys result.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ return SQL_ERROR;
+ }
+
+
+ // the binding structure for a statement is not set up until
+ // a statement is actually executed, so we'll have to do this ourselves.
+ extend_bindings(stmt, 6);
+
+ // set the field names
+ QR_set_num_fields(stmt->result, 6);
+ QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 3, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 4, "KEY_SEQ", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 5, "PK_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+
+ // add the tuples
+ ptr = strtok(svKey, "+");
+ while( ptr != NULL) {
+ row = (TupleNode *)malloc(sizeof(TupleNode) + (6 - 1) * sizeof(TupleField));
+
+ set_tuplefield_string(&row->tuple[0], "");
+
+ // I have to hide the table owner from Access, otherwise it
+ // insists on referring to the table as 'owner.table'.
+ // (this is valid according to the ODBC SQL grammar, but
+ // Postgres won't support it.)
+
+ mylog(">> primaryKeys: ptab = '%s', seq = %d\n", ptr, seq);
+
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], szTableName);
+ set_tuplefield_string(&row->tuple[3], ptr);
+ set_tuplefield_int2(&row->tuple[4], (Int2) (seq++));
+ set_tuplefield_null(&row->tuple[5]);
+
+ QR_add_tuple(stmt->result, row);
+
+ ptr = strtok(NULL, "+");
+ }
+
+ // also, things need to think that this statement is finished so
+ // the results can be retrieved.
+ stmt->status = STMT_FINISHED;
+
+ // set up the current tuple pointer for SQLFetch
+ stmt->currTuple = -1;
+
+ mylog("SQLPrimaryKeys(): EXIT, stmt=%u\n", stmt);
+ return SQL_SUCCESS;
+}
+
+RETCODE SQL_API SQLForeignKeys(
+ HSTMT hstmt,
+ UCHAR FAR * szPkTableQualifier,
+ SWORD cbPkTableQualifier,
+ UCHAR FAR * szPkTableOwner,
+ SWORD cbPkTableOwner,
+ UCHAR FAR * szPkTableName,
+ SWORD cbPkTableName,
+ UCHAR FAR * szFkTableQualifier,
+ SWORD cbFkTableQualifier,
+ UCHAR FAR * szFkTableOwner,
+ SWORD cbFkTableOwner,
+ UCHAR FAR * szFkTableName,
+ SWORD cbFkTableName)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+TupleNode *row;
+HSTMT htbl_stmt;
+StatementClass *tbl_stmt;
+RETCODE result;
+char tables_query[MAX_STATEMENT_LEN];
+char relname[MAX_INFO_STRING], attnames[MAX_INFO_STRING], frelname[MAX_INFO_STRING];
+SDWORD relname_len, attnames_len, frelname_len;
+char *pktab, *fktab;
+char fkey = FALSE;
+char primaryKey[MAX_KEYPARTS][MAX_INFO_STRING];
+char *attnamePtr;
+int pkeys, seq;
+
+mylog("**** SQLForeignKeys(): ENTER, stmt=%u\n", stmt);
+
+ memset(primaryKey, 0, sizeof(primaryKey));
+
+ if( ! stmt) {
+ return SQL_INVALID_HANDLE;
+ }
+ stmt->manual_result = TRUE;
+ stmt->errormsg_created = TRUE;
+
+ result = SQLAllocStmt( stmt->hdbc, &htbl_stmt);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ stmt->errormsg = "Couldn't allocate statement for SQLForeignKeys result.";
+ return SQL_ERROR;
+ }
+
+ tbl_stmt = (StatementClass *) htbl_stmt;
+
+ pktab = make_string(szPkTableName, cbPkTableName, NULL);
+ fktab = make_string(szFkTableName, cbFkTableName, NULL);
+
+ if (pktab && fktab) {
+ // Get the primary key of the table listed in szPkTable
+ result = getPrimaryKeyArray(stmt, pktab, (SWORD) strlen(pktab), primaryKey, &pkeys);
+ if (result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND) {
+ // error msg passed from above
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ free(pktab); free(fktab);
+ return result;
+ }
+ if (pkeys == 0) {
+ stmt->errornumber = STMT_INFO_ONLY;
+ stmt->errormsg = "No primary keys for this table.";
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ free(pktab); free(fktab);
+ return SQL_SUCCESS_WITH_INFO;
+ }
+
+ sprintf(tables_query, "select relname, attnames, frelname from %s where relname='%s' AND frelname='%s'", KEYS_TABLE, fktab, pktab);
+ free(pktab); free(fktab);
+ }
+ else if (pktab) {
+ // Get the primary key of the table listed in szPkTable
+ result = getPrimaryKeyArray(stmt, pktab, (SWORD) strlen(pktab), primaryKey, &pkeys);
+ if (result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND) {
+ // error msg passed from above
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ free(pktab);
+ return result;
+ }
+ if (pkeys == 0) {
+ stmt->errornumber = STMT_INFO_ONLY;
+ stmt->errormsg = "No primary keys for this table.";
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ free(pktab);
+ return SQL_SUCCESS_WITH_INFO;
+ }
+
+ sprintf(tables_query, "select relname, attnames, frelname from %s where frelname='%s'", KEYS_TABLE, pktab);
+ free(pktab);
+ }
+ else if (fktab) {
+ // This query could involve multiple calls to getPrimaryKey()
+ // so put that off till we know what pktables we need.
+ fkey = TRUE;
+
+ sprintf(tables_query, "select relname, attnames, frelname from %s where relname='%s'", KEYS_TABLE, fktab);
+ free(fktab);
+ }
+ else {
+ stmt->errormsg = "No tables specified to SQLForeignKeys.";
+ stmt->errornumber = STMT_INTERNAL_ERROR;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLExecDirect(htbl_stmt, tables_query, strlen(tables_query));
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = SC_create_errormsg(htbl_stmt);
+ stmt->errornumber = tbl_stmt->errornumber;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(htbl_stmt, 1, SQL_C_CHAR,
+ relname, MAX_INFO_STRING, &relname_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+ result = SQLBindCol(htbl_stmt, 2, SQL_C_CHAR,
+ attnames, MAX_INFO_STRING, &attnames_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ result = SQLBindCol(htbl_stmt, 3, SQL_C_CHAR,
+ frelname, MAX_INFO_STRING, &frelname_len);
+ if((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) {
+ stmt->errormsg = tbl_stmt->errormsg;
+ stmt->errornumber = tbl_stmt->errornumber;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ stmt->result = QR_Constructor();
+ if(!stmt->result) {
+ stmt->errormsg = "Couldn't allocate memory for SQLForeignKeys result.";
+ stmt->errornumber = STMT_NO_MEMORY_ERROR;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ // the binding structure for a statement is not set up until
+ // a statement is actually executed, so we'll have to do this ourselves.
+ extend_bindings(stmt, 13);
+
+ // set the field names
+ QR_set_num_fields(stmt->result, 13);
+ QR_set_field_info(stmt->result, 0, "PKTABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 1, "PKTABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 2, "PKTABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 3, "PKCOLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 4, "FKTABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 5, "FKTABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 6, "FKTABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 7, "FKCOLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 8, "KEY_SEQ", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 9, "UPDATE_RULE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 10, "DELETE_RULE", PG_TYPE_INT2, 2);
+ QR_set_field_info(stmt->result, 11, "FK_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+ QR_set_field_info(stmt->result, 12, "PK_NAME", PG_TYPE_TEXT, MAX_INFO_STRING);
+
+ // add the tuples
+ result = SQLFetch(htbl_stmt);
+
+ while((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) {
+
+ if (fkey == TRUE) {
+ result = getPrimaryKeyArray(stmt, frelname, (SWORD) strlen(frelname), primaryKey, &pkeys);
+
+ // mylog(">> getPrimaryKeyArray: frelname = '%s', pkeys = %d, result = %d\n", frelname, pkeys, result);
+
+ // If an error occurs or for some reason there is no primary key for a
+ // table that is a foreign key, then skip that one.
+ if ((result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND) || pkeys == 0) {
+ result = SQLFetch(htbl_stmt);
+ continue;
+ }
+
+ /*
+ for (i = 0; i< pkeys; i++)
+ mylog(">> fkey: pkeys=%d, primaryKey[%d] = '%s'\n", pkeys, i, primaryKey[i]);
+ mylog(">> !!!!!!!!! pkeys = %d\n", pkeys);
+ */
+ }
+
+ // mylog(">> attnames='%s'\n", attnames);
+
+ attnamePtr = strtok(attnames, "+");
+ seq = 0;
+
+ while (attnamePtr != NULL && seq < pkeys) {
+
+ row = (TupleNode *)malloc(sizeof(TupleNode) + (13 - 1) * sizeof(TupleField));
+
+ set_tuplefield_null(&row->tuple[0]);
+
+ // I have to hide the table owner from Access, otherwise it
+ // insists on referring to the table as 'owner.table'.
+ // (this is valid according to the ODBC SQL grammar, but
+ // Postgres won't support it.)
+
+ mylog(">> foreign keys: pktab='%s' patt='%s' fktab='%s' fatt='%s' seq=%d\n",
+ frelname, primaryKey[seq], relname, attnamePtr, (seq+1));
+
+ set_tuplefield_string(&row->tuple[1], "");
+ set_tuplefield_string(&row->tuple[2], frelname);
+ set_tuplefield_string(&row->tuple[3], primaryKey[seq]);
+ set_tuplefield_null(&row->tuple[4]);
+ set_tuplefield_string(&row->tuple[5], "");
+ set_tuplefield_string(&row->tuple[6], relname);
+ set_tuplefield_string(&row->tuple[7], attnamePtr);
+ set_tuplefield_int2(&row->tuple[8], (Int2) (++seq));
+ set_tuplefield_null(&row->tuple[9]);
+ set_tuplefield_null(&row->tuple[10]);
+ set_tuplefield_null(&row->tuple[11]);
+ set_tuplefield_null(&row->tuple[12]);
+
+ QR_add_tuple(stmt->result, row);
+
+ attnamePtr = strtok(NULL, "+");
+ }
+ result = SQLFetch(htbl_stmt);
+ }
+
+ if(result != SQL_NO_DATA_FOUND) {
+ stmt->errormsg = SC_create_errormsg(htbl_stmt);
+ stmt->errornumber = tbl_stmt->errornumber;
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+ return SQL_ERROR;
+ }
+
+ SQLFreeStmt(htbl_stmt, SQL_DROP);
+
+ // also, things need to think that this statement is finished so
+ // the results can be retrieved.
+ stmt->status = STMT_FINISHED;
+
+ // set up the current tuple pointer for SQLFetch
+ stmt->currTuple = -1;
+
+ mylog("SQLForeignKeys(): EXIT, stmt=%u\n", stmt);
+ return SQL_SUCCESS;
+}
+
+
+
+RETCODE SQL_API SQLProcedureColumns(
+ HSTMT hstmt,
+ UCHAR FAR * szProcQualifier,
+ SWORD cbProcQualifier,
+ UCHAR FAR * szProcOwner,
+ SWORD cbProcOwner,
+ UCHAR FAR * szProcName,
+ SWORD cbProcName,
+ UCHAR FAR * szColumnName,
+ SWORD cbColumnName)
+{
+ return SQL_ERROR;
+}
+
+RETCODE SQL_API SQLProcedures(
+ HSTMT hstmt,
+ UCHAR FAR * szProcQualifier,
+ SWORD cbProcQualifier,
+ UCHAR FAR * szProcOwner,
+ SWORD cbProcOwner,
+ UCHAR FAR * szProcName,
+ SWORD cbProcName)
+{
+ return SQL_ERROR;
+}
+
+RETCODE SQL_API SQLTablePrivileges(
+ HSTMT hstmt,
+ UCHAR FAR * szTableQualifier,
+ SWORD cbTableQualifier,
+ UCHAR FAR * szTableOwner,
+ SWORD cbTableOwner,
+ UCHAR FAR * szTableName,
+ SWORD cbTableName)
+{
+ return SQL_ERROR;
+}
--- /dev/null
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+\r
+ Version 2, June 1991
+\r
+
+\r
+ Copyright (C) 1991 Free Software Foundation, Inc.
+\r
+ 675 Mass Ave, Cambridge, MA 02139, USA
+\r
+ Everyone is permitted to copy and distribute verbatim copies
+\r
+ of this license document, but changing it is not allowed.
+\r
+
+\r
+[This is the first released version of the library GPL. It is
+\r
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+\r
+
+\r
+ Preamble
+\r
+
+\r
+ The licenses for most software are designed to take away your
+\r
+freedom to share and change it. By contrast, the GNU General Public
+\r
+Licenses are intended to guarantee your freedom to share and change
+\r
+free software--to make sure the software is free for all its users.
+\r
+
+\r
+ This license, the Library General Public License, applies to some
+\r
+specially designated Free Software Foundation software, and to any
+\r
+other libraries whose authors decide to use it. You can use it for
+\r
+your libraries, too.
+\r
+
+\r
+ When we speak of free software, we are referring to freedom, not
+\r
+price. Our General Public Licenses are designed to make sure that you
+\r
+have the freedom to distribute copies of free software (and charge for
+\r
+this service if you wish), that you receive source code or can get it
+\r
+if you want it, that you can change the software or use pieces of it
+\r
+in new free programs; and that you know you can do these things.
+\r
+
+\r
+ To protect your rights, we need to make restrictions that forbid
+\r
+anyone to deny you these rights or to ask you to surrender the rights.
+\r
+These restrictions translate to certain responsibilities for you if
+\r
+you distribute copies of the library, or if you modify it.
+\r
+
+\r
+ For example, if you distribute copies of the library, whether gratis
+\r
+or for a fee, you must give the recipients all the rights that we gave
+\r
+you. You must make sure that they, too, receive or can get the source
+\r
+code. If you link a program with the library, you must provide
+\r
+complete object files to the recipients so that they can relink them
+\r
+with the library, after making changes to the library and recompiling
+\r
+it. And you must show them these terms so they know their rights.
+\r
+
+\r
+ Our method of protecting your rights has two steps: (1) copyright
+\r
+the library, and (2) offer you this license which gives you legal
+\r
+permission to copy, distribute and/or modify the library.
+\r
+
+\r
+ Also, for each distributor's protection, we want to make certain
+\r
+that everyone understands that there is no warranty for this free
+\r
+library. If the library is modified by someone else and passed on, we
+\r
+want its recipients to know that what they have is not the original
+\r
+version, so that any problems introduced by others will not reflect on
+\r
+the original authors' reputations.
+\r
+\f
+\r
+ Finally, any free program is threatened constantly by software
+\r
+patents. We wish to avoid the danger that companies distributing free
+\r
+software will individually obtain patent licenses, thus in effect
+\r
+transforming the program into proprietary software. To prevent this,
+\r
+we have made it clear that any patent must be licensed for everyone's
+\r
+free use or not licensed at all.
+\r
+
+\r
+ Most GNU software, including some libraries, is covered by the ordinary
+\r
+GNU General Public License, which was designed for utility programs. This
+\r
+license, the GNU Library General Public License, applies to certain
+\r
+designated libraries. This license is quite different from the ordinary
+\r
+one; be sure to read it in full, and don't assume that anything in it is
+\r
+the same as in the ordinary license.
+\r
+
+\r
+ The reason we have a separate public license for some libraries is that
+\r
+they blur the distinction we usually make between modifying or adding to a
+\r
+program and simply using it. Linking a program with a library, without
+\r
+changing the library, is in some sense simply using the library, and is
+\r
+analogous to running a utility program or application program. However, in
+\r
+a textual and legal sense, the linked executable is a combined work, a
+\r
+derivative of the original library, and the ordinary General Public License
+\r
+treats it as such.
+\r
+
+\r
+ Because of this blurred distinction, using the ordinary General
+\r
+Public License for libraries did not effectively promote software
+\r
+sharing, because most developers did not use the libraries. We
+\r
+concluded that weaker conditions might promote sharing better.
+\r
+
+\r
+ However, unrestricted linking of non-free programs would deprive the
+\r
+users of those programs of all benefit from the free status of the
+\r
+libraries themselves. This Library General Public License is intended to
+\r
+permit developers of non-free programs to use free libraries, while
+\r
+preserving your freedom as a user of such programs to change the free
+\r
+libraries that are incorporated in them. (We have not seen how to achieve
+\r
+this as regards changes in header files, but we have achieved it as regards
+\r
+changes in the actual functions of the Library.) The hope is that this
+\r
+will lead to faster development of free libraries.
+\r
+
+\r
+ The precise terms and conditions for copying, distribution and
+\r
+modification follow. Pay close attention to the difference between a
+\r
+"work based on the library" and a "work that uses the library". The
+\r
+former contains code derived from the library, while the latter only
+\r
+works together with the library.
+\r
+
+\r
+ Note that it is possible for a library to be covered by the ordinary
+\r
+General Public License rather than by this special one.
+\r
+\f
+\r
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+\r
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+\r
+
+\r
+ 0. This License Agreement applies to any software library which
+\r
+contains a notice placed by the copyright holder or other authorized
+\r
+party saying it may be distributed under the terms of this Library
+\r
+General Public License (also called "this License"). Each licensee is
+\r
+addressed as "you".
+\r
+
+\r
+ A "library" means a collection of software functions and/or data
+\r
+prepared so as to be conveniently linked with application programs
+\r
+(which use some of those functions and data) to form executables.
+\r
+
+\r
+ The "Library", below, refers to any such software library or work
+\r
+which has been distributed under these terms. A "work based on the
+\r
+Library" means either the Library or any derivative work under
+\r
+copyright law: that is to say, a work containing the Library or a
+\r
+portion of it, either verbatim or with modifications and/or translated
+\r
+straightforwardly into another language. (Hereinafter, translation is
+\r
+included without limitation in the term "modification".)
+\r
+
+\r
+ "Source code" for a work means the preferred form of the work for
+\r
+making modifications to it. For a library, complete source code means
+\r
+all the source code for all modules it contains, plus any associated
+\r
+interface definition files, plus the scripts used to control compilation
+\r
+and installation of the library.
+\r
+
+\r
+ Activities other than copying, distribution and modification are not
+\r
+covered by this License; they are outside its scope. The act of
+\r
+running a program using the Library is not restricted, and output from
+\r
+such a program is covered only if its contents constitute a work based
+\r
+on the Library (independent of the use of the Library in a tool for
+\r
+writing it). Whether that is true depends on what the Library does
+\r
+and what the program that uses the Library does.
+\r
+
+\r
+ 1. You may copy and distribute verbatim copies of the Library's
+\r
+complete source code as you receive it, in any medium, provided that
+\r
+you conspicuously and appropriately publish on each copy an
+\r
+appropriate copyright notice and disclaimer of warranty; keep intact
+\r
+all the notices that refer to this License and to the absence of any
+\r
+warranty; and distribute a copy of this License along with the
+\r
+Library.
+\r
+
+\r
+ You may charge a fee for the physical act of transferring a copy,
+\r
+and you may at your option offer warranty protection in exchange for a
+\r
+fee.
+\r
+\f
+\r
+ 2. You may modify your copy or copies of the Library or any portion
+\r
+of it, thus forming a work based on the Library, and copy and
+\r
+distribute such modifications or work under the terms of Section 1
+\r
+above, provided that you also meet all of these conditions:
+\r
+
+\r
+ a) The modified work must itself be a software library.
+\r
+
+\r
+ b) You must cause the files modified to carry prominent notices
+\r
+ stating that you changed the files and the date of any change.
+\r
+
+\r
+ c) You must cause the whole of the work to be licensed at no
+\r
+ charge to all third parties under the terms of this License.
+\r
+
+\r
+ d) If a facility in the modified Library refers to a function or a
+\r
+ table of data to be supplied by an application program that uses
+\r
+ the facility, other than as an argument passed when the facility
+\r
+ is invoked, then you must make a good faith effort to ensure that,
+\r
+ in the event an application does not supply such function or
+\r
+ table, the facility still operates, and performs whatever part of
+\r
+ its purpose remains meaningful.
+\r
+
+\r
+ (For example, a function in a library to compute square roots has
+\r
+ a purpose that is entirely well-defined independent of the
+\r
+ application. Therefore, Subsection 2d requires that any
+\r
+ application-supplied function or table used by this function must
+\r
+ be optional: if the application does not supply it, the square
+\r
+ root function must still compute square roots.)
+\r
+
+\r
+These requirements apply to the modified work as a whole. If
+\r
+identifiable sections of that work are not derived from the Library,
+\r
+and can be reasonably considered independent and separate works in
+\r
+themselves, then this License, and its terms, do not apply to those
+\r
+sections when you distribute them as separate works. But when you
+\r
+distribute the same sections as part of a whole which is a work based
+\r
+on the Library, the distribution of the whole must be on the terms of
+\r
+this License, whose permissions for other licensees extend to the
+\r
+entire whole, and thus to each and every part regardless of who wrote
+\r
+it.
+\r
+
+\r
+Thus, it is not the intent of this section to claim rights or contest
+\r
+your rights to work written entirely by you; rather, the intent is to
+\r
+exercise the right to control the distribution of derivative or
+\r
+collective works based on the Library.
+\r
+
+\r
+In addition, mere aggregation of another work not based on the Library
+\r
+with the Library (or with a work based on the Library) on a volume of
+\r
+a storage or distribution medium does not bring the other work under
+\r
+the scope of this License.
+\r
+
+\r
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+\r
+License instead of this License to a given copy of the Library. To do
+\r
+this, you must alter all the notices that refer to this License, so
+\r
+that they refer to the ordinary GNU General Public License, version 2,
+\r
+instead of to this License. (If a newer version than version 2 of the
+\r
+ordinary GNU General Public License has appeared, then you can specify
+\r
+that version instead if you wish.) Do not make any other change in
+\r
+these notices.
+\r
+\f
+\r
+ Once this change is made in a given copy, it is irreversible for
+\r
+that copy, so the ordinary GNU General Public License applies to all
+\r
+subsequent copies and derivative works made from that copy.
+\r
+
+\r
+ This option is useful when you wish to copy part of the code of
+\r
+the Library into a program that is not a library.
+\r
+
+\r
+ 4. You may copy and distribute the Library (or a portion or
+\r
+derivative of it, under Section 2) in object code or executable form
+\r
+under the terms of Sections 1 and 2 above provided that you accompany
+\r
+it with the complete corresponding machine-readable source code, which
+\r
+must be distributed under the terms of Sections 1 and 2 above on a
+\r
+medium customarily used for software interchange.
+\r
+
+\r
+ If distribution of object code is made by offering access to copy
+\r
+from a designated place, then offering equivalent access to copy the
+\r
+source code from the same place satisfies the requirement to
+\r
+distribute the source code, even though third parties are not
+\r
+compelled to copy the source along with the object code.
+\r
+
+\r
+ 5. A program that contains no derivative of any portion of the
+\r
+Library, but is designed to work with the Library by being compiled or
+\r
+linked with it, is called a "work that uses the Library". Such a
+\r
+work, in isolation, is not a derivative work of the Library, and
+\r
+therefore falls outside the scope of this License.
+\r
+
+\r
+ However, linking a "work that uses the Library" with the Library
+\r
+creates an executable that is a derivative of the Library (because it
+\r
+contains portions of the Library), rather than a "work that uses the
+\r
+library". The executable is therefore covered by this License.
+\r
+Section 6 states terms for distribution of such executables.
+\r
+
+\r
+ When a "work that uses the Library" uses material from a header file
+\r
+that is part of the Library, the object code for the work may be a
+\r
+derivative work of the Library even though the source code is not.
+\r
+Whether this is true is especially significant if the work can be
+\r
+linked without the Library, or if the work is itself a library. The
+\r
+threshold for this to be true is not precisely defined by law.
+\r
+
+\r
+ If such an object file uses only numerical parameters, data
+\r
+structure layouts and accessors, and small macros and small inline
+\r
+functions (ten lines or less in length), then the use of the object
+\r
+file is unrestricted, regardless of whether it is legally a derivative
+\r
+work. (Executables containing this object code plus portions of the
+\r
+Library will still fall under Section 6.)
+\r
+
+\r
+ Otherwise, if the work is a derivative of the Library, you may
+\r
+distribute the object code for the work under the terms of Section 6.
+\r
+Any executables containing that work also fall under Section 6,
+\r
+whether or not they are linked directly with the Library itself.
+\r
+\f
+\r
+ 6. As an exception to the Sections above, you may also compile or
+\r
+link a "work that uses the Library" with the Library to produce a
+\r
+work containing portions of the Library, and distribute that work
+\r
+under terms of your choice, provided that the terms permit
+\r
+modification of the work for the customer's own use and reverse
+\r
+engineering for debugging such modifications.
+\r
+
+\r
+ You must give prominent notice with each copy of the work that the
+\r
+Library is used in it and that the Library and its use are covered by
+\r
+this License. You must supply a copy of this License. If the work
+\r
+during execution displays copyright notices, you must include the
+\r
+copyright notice for the Library among them, as well as a reference
+\r
+directing the user to the copy of this License. Also, you must do one
+\r
+of these things:
+\r
+
+\r
+ a) Accompany the work with the complete corresponding
+\r
+ machine-readable source code for the Library including whatever
+\r
+ changes were used in the work (which must be distributed under
+\r
+ Sections 1 and 2 above); and, if the work is an executable linked
+\r
+ with the Library, with the complete machine-readable "work that
+\r
+ uses the Library", as object code and/or source code, so that the
+\r
+ user can modify the Library and then relink to produce a modified
+\r
+ executable containing the modified Library. (It is understood
+\r
+ that the user who changes the contents of definitions files in the
+\r
+ Library will not necessarily be able to recompile the application
+\r
+ to use the modified definitions.)
+\r
+
+\r
+ b) Accompany the work with a written offer, valid for at
+\r
+ least three years, to give the same user the materials
+\r
+ specified in Subsection 6a, above, for a charge no more
+\r
+ than the cost of performing this distribution.
+\r
+
+\r
+ c) If distribution of the work is made by offering access to copy
+\r
+ from a designated place, offer equivalent access to copy the above
+\r
+ specified materials from the same place.
+\r
+
+\r
+ d) Verify that the user has already received a copy of these
+\r
+ materials or that you have already sent this user a copy.
+\r
+
+\r
+ For an executable, the required form of the "work that uses the
+\r
+Library" must include any data and utility programs needed for
+\r
+reproducing the executable from it. However, as a special exception,
+\r
+the source code distributed need not include anything that is normally
+\r
+distributed (in either source or binary form) with the major
+\r
+components (compiler, kernel, and so on) of the operating system on
+\r
+which the executable runs, unless that component itself accompanies
+\r
+the executable.
+\r
+
+\r
+ It may happen that this requirement contradicts the license
+\r
+restrictions of other proprietary libraries that do not normally
+\r
+accompany the operating system. Such a contradiction means you cannot
+\r
+use both them and the Library together in an executable that you
+\r
+distribute.
+\r
+\f
+\r
+ 7. You may place library facilities that are a work based on the
+\r
+Library side-by-side in a single library together with other library
+\r
+facilities not covered by this License, and distribute such a combined
+\r
+library, provided that the separate distribution of the work based on
+\r
+the Library and of the other library facilities is otherwise
+\r
+permitted, and provided that you do these two things:
+\r
+
+\r
+ a) Accompany the combined library with a copy of the same work
+\r
+ based on the Library, uncombined with any other library
+\r
+ facilities. This must be distributed under the terms of the
+\r
+ Sections above.
+\r
+
+\r
+ b) Give prominent notice with the combined library of the fact
+\r
+ that part of it is a work based on the Library, and explaining
+\r
+ where to find the accompanying uncombined form of the same work.
+\r
+
+\r
+ 8. You may not copy, modify, sublicense, link with, or distribute
+\r
+the Library except as expressly provided under this License. Any
+\r
+attempt otherwise to copy, modify, sublicense, link with, or
+\r
+distribute the Library is void, and will automatically terminate your
+\r
+rights under this License. However, parties who have received copies,
+\r
+or rights, from you under this License will not have their licenses
+\r
+terminated so long as such parties remain in full compliance.
+\r
+
+\r
+ 9. You are not required to accept this License, since you have not
+\r
+signed it. However, nothing else grants you permission to modify or
+\r
+distribute the Library or its derivative works. These actions are
+\r
+prohibited by law if you do not accept this License. Therefore, by
+\r
+modifying or distributing the Library (or any work based on the
+\r
+Library), you indicate your acceptance of this License to do so, and
+\r
+all its terms and conditions for copying, distributing or modifying
+\r
+the Library or works based on it.
+\r
+
+\r
+ 10. Each time you redistribute the Library (or any work based on the
+\r
+Library), the recipient automatically receives a license from the
+\r
+original licensor to copy, distribute, link with or modify the Library
+\r
+subject to these terms and conditions. You may not impose any further
+\r
+restrictions on the recipients' exercise of the rights granted herein.
+\r
+You are not responsible for enforcing compliance by third parties to
+\r
+this License.
+\r
+\f
+\r
+ 11. If, as a consequence of a court judgment or allegation of patent
+\r
+infringement or for any other reason (not limited to patent issues),
+\r
+conditions are imposed on you (whether by court order, agreement or
+\r
+otherwise) that contradict the conditions of this License, they do not
+\r
+excuse you from the conditions of this License. If you cannot
+\r
+distribute so as to satisfy simultaneously your obligations under this
+\r
+License and any other pertinent obligations, then as a consequence you
+\r
+may not distribute the Library at all. For example, if a patent
+\r
+license would not permit royalty-free redistribution of the Library by
+\r
+all those who receive copies directly or indirectly through you, then
+\r
+the only way you could satisfy both it and this License would be to
+\r
+refrain entirely from distribution of the Library.
+\r
+
+\r
+If any portion of this section is held invalid or unenforceable under any
+\r
+particular circumstance, the balance of the section is intended to apply,
+\r
+and the section as a whole is intended to apply in other circumstances.
+\r
+
+\r
+It is not the purpose of this section to induce you to infringe any
+\r
+patents or other property right claims or to contest validity of any
+\r
+such claims; this section has the sole purpose of protecting the
+\r
+integrity of the free software distribution system which is
+\r
+implemented by public license practices. Many people have made
+\r
+generous contributions to the wide range of software distributed
+\r
+through that system in reliance on consistent application of that
+\r
+system; it is up to the author/donor to decide if he or she is willing
+\r
+to distribute software through any other system and a licensee cannot
+\r
+impose that choice.
+\r
+
+\r
+This section is intended to make thoroughly clear what is believed to
+\r
+be a consequence of the rest of this License.
+\r
+
+\r
+ 12. If the distribution and/or use of the Library is restricted in
+\r
+certain countries either by patents or by copyrighted interfaces, the
+\r
+original copyright holder who places the Library under this License may add
+\r
+an explicit geographical distribution limitation excluding those countries,
+\r
+so that distribution is permitted only in or among countries not thus
+\r
+excluded. In such case, this License incorporates the limitation as if
+\r
+written in the body of this License.
+\r
+
+\r
+ 13. The Free Software Foundation may publish revised and/or new
+\r
+versions of the Library General Public License from time to time.
+\r
+Such new versions will be similar in spirit to the present version,
+\r
+but may differ in detail to address new problems or concerns.
+\r
+
+\r
+Each version is given a distinguishing version number. If the Library
+\r
+specifies a version number of this License which applies to it and
+\r
+"any later version", you have the option of following the terms and
+\r
+conditions either of that version or of any later version published by
+\r
+the Free Software Foundation. If the Library does not specify a
+\r
+license version number, you may choose any version ever published by
+\r
+the Free Software Foundation.
+\r
+\f
+\r
+ 14. If you wish to incorporate parts of the Library into other free
+\r
+programs whose distribution conditions are incompatible with these,
+\r
+write to the author to ask for permission. For software which is
+\r
+copyrighted by the Free Software Foundation, write to the Free
+\r
+Software Foundation; we sometimes make exceptions for this. Our
+\r
+decision will be guided by the two goals of preserving the free status
+\r
+of all derivatives of our free software and of promoting the sharing
+\r
+and reuse of software generally.
+\r
+
+\r
+ NO WARRANTY
+\r
+
+\r
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+\r
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+\r
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+\r
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+\r
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+\r
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+\r
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+\r
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+\r
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+\r
+
+\r
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+\r
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+\r
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+\r
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+\r
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+\r
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+\r
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+\r
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+\r
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+\r
+DAMAGES.
+\r
+
+\r
+ END OF TERMS AND CONDITIONS
+\r
+\f
+\r
+ Appendix: How to Apply These Terms to Your New Libraries
+\r
+
+\r
+ If you develop a new library, and you want it to be of the greatest
+\r
+possible use to the public, we recommend making it free software that
+\r
+everyone can redistribute and change. You can do so by permitting
+\r
+redistribution under these terms (or, alternatively, under the terms of the
+\r
+ordinary General Public License).
+\r
+
+\r
+ To apply these terms, attach the following notices to the library. It is
+\r
+safest to attach them to the start of each source file to most effectively
+\r
+convey the exclusion of warranty; and each file should have at least the
+\r
+"copyright" line and a pointer to where the full notice is found.
+\r
+
+\r
+
+\r
+ Copyright (C)
+\r
+
+\r
+ This library is free software; you can redistribute it and/or
+\r
+ modify it under the terms of the GNU Library General Public
+\r
+ License as published by the Free Software Foundation; either
+\r
+ version 2 of the License, or (at your option) any later version.
+\r
+
+\r
+ This library is distributed in the hope that it will be useful,
+\r
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+\r
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+\r
+ Library General Public License for more details.
+\r
+
+\r
+ You should have received a copy of the GNU Library General Public
+\r
+ License along with this library; if not, write to the Free
+\r
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+\r
+
+\r
+Also add information on how to contact you by electronic and paper mail.
+\r
+
+\r
+You should also get your employer (if you work as a programmer) or your
+\r
+school, if any, to sign a "copyright disclaimer" for the library, if
+\r
+necessary. Here is a sample; alter the names:
+\r
+
+\r
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+\r
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+\r
+
+\r
+ , 1 April 1990
+\r
+ Ty Coon, President of Vice
+\r
+
+\r
+That's all there is to it!
+\r
--- /dev/null
+\r
+/* Module: misc.c\r
+ *\r
+ * Description: This module contains miscellaneous routines\r
+ * such as for debugging/logging and string functions.\r
+ *\r
+ * Classes: n/a\r
+ *\r
+ * API functions: none\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include
+#include
+#include
+
+#include "psqlodbc.h"
+\r
+extern GLOBAL_VALUES globals;
+
+
+#ifdef MY_LOG
+#include
+
+void
+mylog(va_alist)
+va_dcl
+{
+char *fmt;
+char *args;
+
+static FILE *LOGFP = 0;
+
+ if ( globals.debug) {
+ va_start(args);
+ fmt = va_arg(args, char *);
+
+ if (! LOGFP) {
+ LOGFP = fopen("c:\\mylog.log", "w");
+ setbuf(LOGFP, NULL);
+ }
+
+ if (LOGFP)
+ vfprintf(LOGFP, fmt, args);
+
+ va_end(args);
+ }
+}
+#endif
+
+
+#ifdef Q_LOG
+#include
+
+void qlog(va_alist)
+va_dcl
+{
+char *fmt;
+char *args;
+static FILE *LOGFP = 0;
+
+ if ( globals.commlog) {
+ va_start(args);
+ fmt = va_arg(args, char *);
+
+ if (! LOGFP) {
+ LOGFP = fopen("c:\\psqlodbc.log", "w");
+ setbuf(LOGFP, NULL);
+ }
+
+ if (LOGFP)
+ vfprintf(LOGFP, fmt, args);
+
+ va_end(args);
+ }
+}
+#endif
+
+
+/* returns STRCPY_FAIL, STRCPY_TRUNCATED, or #bytes copied (not including null term) */
+int
+my_strcpy(char *dst, size_t dst_len, char *src, size_t src_len)
+{
+ if (dst_len <= 0)
+ return STRCPY_FAIL;
+
+ if (src_len == SQL_NULL_DATA) {
+ dst[0] = '\0';
+ return STRCPY_NULL;
+ }
+
+ else if (src_len == SQL_NTS) {
+ if (src_len < dst_len)
+ strcpy(dst, src);\r
+ else {
+ memcpy(dst, src, dst_len-1);
+ dst[dst_len-1] = '\0'; /* truncated */
+ return STRCPY_TRUNCATED;
+ }
+ }
+
+ else if (src_len <= 0)
+ return STRCPY_FAIL;
+
+ else {
+ if (src_len < dst_len) {
+ memcpy(dst, src, src_len);
+ dst[src_len] = '\0';\r
+ }
+ else {
+ memcpy(dst, src, dst_len-1);
+ dst[dst_len-1] = '\0'; /* truncated */
+ return STRCPY_TRUNCATED;
+ }
+ }\r
+
+ return strlen(dst);
+}
+
+// strncpy copies up to len characters, and doesn't terminate
+// the destination string if src has len characters or more.
+// instead, I want it to copy up to len-1 characters and always
+// terminate the destination string.
+char *strncpy_null(char *dst, const char *src, size_t len)
+{
+unsigned int i;
+
+
+ if (NULL != dst) {
+
+ /* Just in case, check for special lengths */
+ if (len == SQL_NULL_DATA) {
+ dst[0] = '\0';
+ return NULL;
+ }
+ else if (len == SQL_NTS)
+ len = strlen(src) + 1;
+
+ for(i = 0; src[i] && i < len - 1; i++) {\r
+ dst[i] = src[i];
+ }
+
+ if(len > 0) {
+ dst[i] = '\0';
+ }
+ }
+ return dst;
+}
+
+// Create a null terminated string (handling the SQL_NTS thing):
+// 1. If buf is supplied, place the string in there (assumes enough space) and return buf.
+// 2. If buf is not supplied, malloc space and return this string
+char *
+make_string(char *s, int len, char *buf)
+{
+int length;
+char *str;
+
+ if(s && (len > 0 || len == SQL_NTS)) {
+ length = (len > 0) ? len : strlen(s);
+
+ if (buf) {
+ strncpy_null(buf, s, length+1);
+ return buf;
+ }
+
+ str = malloc(length + 1);
+ if ( ! str)
+ return NULL;
+
+ strncpy_null(str, s, length+1);
+ return str;
+ }
+
+ return NULL;
+}
+
+// Concatenate a single formatted argument to a given buffer handling the SQL_NTS thing.
+// "fmt" must contain somewhere in it the single form '%.*s'
+// This is heavily used in creating queries for info routines (SQLTables, SQLColumns).
+// This routine could be modified to use vsprintf() to handle multiple arguments.
+char *
+my_strcat(char *buf, char *fmt, char *s, int len)
+{
+
+ if (s && (len > 0 || (len == SQL_NTS && strlen(s) > 0))) {
+ int length = (len > 0) ? len : strlen(s);
+
+ int pos = strlen(buf);
+
+ sprintf(&buf[pos], fmt, length, s);
+ return buf;
+ }
+ return NULL;
+}
+
+void remove_newlines(char *string)
+{
+ unsigned int i;
+
+ for(i=0; i < strlen(string); i++) {
+ if((string[i] == '\n') ||
+ (string[i] == '\r')) {
+ string[i] = ' ';
+ }
+ }
+}
+
+char *
+trim(char *s)
+{
+ int i;
+
+ for (i = strlen(s) - 1; i >= 0; i--) {
+ if (s[i] == ' ')
+ s[i] = '\0';
+ else
+ break;
+ }
+
+ return s;
+}
--- /dev/null
+\r
+/* File: misc.h\r
+ *\r
+ * Description: See "misc.c"\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __MISC_H__
+#define __MISC_H__
+
+#include
+
+/* Uncomment MY_LOG define to compile in the mylog() statements.
+ Then, debug logging will occur if 'Debug' is set to 1 in the ODBCINST.INI
+ portion of the registry. You may have to manually add this key.
+ This logfile is intended for development use, not for an end user!
+*/
+// #define MY_LOG
+
+
+/* Uncomment Q_LOG to compile in the qlog() statements (Communications log, i.e. CommLog).
+ This logfile contains serious log statements that are intended for an
+ end user to be able to read and understand. It is controlled by the
+ 'CommLog' flag in the ODBCINST.INI portion of the registry (see above),\r
+ which is manipulated on the setup/connection dialog boxes.
+*/
+#define Q_LOG
+
+
+#ifdef MY_LOG
+void mylog(); /* prototype */
+#else
+#define mylog // mylog
+#endif
+
+#ifdef Q_LOG
+void qlog(); /* prototype */
+#else
+#define qlog // qlog
+#endif
+
+void remove_newlines(char *string);
+char *strncpy_null(char *dst, const char *src, size_t len);
+char *trim(char *string);
+char *make_string(char *s, int len, char *buf);
+char *my_strcat(char *buf, char *fmt, char *s, int len);
+
+/* defines for return value of my_strcpy */
+#define STRCPY_SUCCESS 1
+#define STRCPY_FAIL 0
+#define STRCPY_TRUNCATED -1
+#define STRCPY_NULL -2
+
+int my_strcpy(char *dst, size_t dst_len, char *src, size_t src_len);
+
+#endif
--- /dev/null
+\r
+/********************************************************************\r
+\r
+ PSQLODBC.DLL - A library to talk to the PostgreSQL DBMS using ODBC.\r
+\r
+\r
+ Copyright (C) 1998; Insight Distribution Systems\r
+\r
+ The code contained in this library is based on code written by \r
+ Christian Czezatke and Dan McGuirk, (C) 1996.\r
+\r
+\r
+ This library is free software; you can redistribute it and/or modify\r
+ it under the terms of the GNU Library General Public License as \r
+ published by the Free Software Foundation; either version 2 of the \r
+ License, or (at your option) any later version.\r
+\r
+ This library is distributed in the hope that it will be useful, but\r
+ WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+ Library General Public License for more details.\r
+\r
+ You should have received a copy of the GNU Library General Public\r
+ License along with this library (see "license.txt"); if not, write to\r
+ the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA\r
+ 02139, USA.\r
+\r
+\r
+ How to contact the author:\r
+\r
+\r
+\r
+***********************************************************************/\r
+\r
--- /dev/null
+\r
+/* Module: options.c\r
+ *\r
+ * Description: This module contains routines for getting/setting\r
+ * connection and statement options.\r
+ *\r
+ * Classes: n/a\r
+ *\r
+ * API functions: SQLSetConnectOption, SQLSetStmtOption, SQLGetConnectOption,\r
+ * SQLGetStmtOption\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "psqlodbc.h"
+#include
+#include
+#include "environ.h"
+#include "connection.h"
+#include "statement.h"
+
+/* Implements only SQL_AUTOCOMMIT */
+RETCODE SQL_API SQLSetConnectOption(
+ HDBC hdbc,
+ UWORD fOption,
+ UDWORD vParam)
+{
+ConnectionClass *conn = (ConnectionClass *) hdbc;
+
+ if ( ! conn)
+ return SQL_INVALID_HANDLE;
+
+ switch (fOption) {
+ case SQL_AUTOCOMMIT:
+
+ /* Since we are almost always in a transaction, this is now ok.
+ Even if we were, the logic will handle it by sending a commit
+ after the statement.
+
+ if (CC_is_in_trans(conn)) {
+ conn->errormsg = "Cannot switch commit mode while a transaction is in progres";
+ conn->errornumber = CONN_TRANSACT_IN_PROGRES;
+ return SQL_ERROR;
+ }
+ */
+
+ mylog("SQLSetConnectOption: AUTOCOMMIT: transact_status=%d, vparam=%d\n", conn->transact_status, vParam);
+
+ switch(vParam) {
+ case SQL_AUTOCOMMIT_OFF:
+ CC_set_autocommit_off(conn);
+ break;
+
+ case SQL_AUTOCOMMIT_ON:
+ CC_set_autocommit_on(conn);
+ break;
+
+ default:
+ conn->errormsg = "Illegal parameter value for SQL_AUTOCOMMIT";
+ conn->errornumber = CONN_INVALID_ARGUMENT_NO;
+ return SQL_ERROR;
+ }
+
+ break;
+
+ case SQL_LOGIN_TIMEOUT:
+ break;
+
+ case SQL_ACCESS_MODE:
+ break;
+
+ default:
+ conn->errormsg = "This option is currently unsupported by the driver";
+ conn->errornumber = CONN_UNSUPPORTED_OPTION;
+ return SQL_ERROR;
+
+ }
+ return SQL_SUCCESS;
+}
+
+// - - - - - - - - -
+
+RETCODE SQL_API SQLSetStmtOption(
+ HSTMT hstmt,
+ UWORD fOption,
+ UDWORD vParam)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+
+ // thought we could fake Access out by just returning SQL_SUCCESS
+ // all the time, but it tries to set a huge value for SQL_MAX_LENGTH
+ // and expects the driver to reduce it to the real value
+
+ if( ! stmt) {
+ return SQL_INVALID_HANDLE;
+ }
+
+ switch(fOption) {
+ case SQL_QUERY_TIMEOUT:
+ mylog("SetStmtOption: vParam = %d\n", vParam);
+ /*
+ stmt->errornumber = STMT_OPTION_VALUE_CHANGED;
+ stmt->errormsg = "Query Timeout: value changed to 0";
+ return SQL_SUCCESS_WITH_INFO;
+ */
+ return SQL_SUCCESS;
+ break;
+ case SQL_MAX_LENGTH:
+/* CC: Some apps consider returning SQL_SUCCESS_WITH_INFO to be an error */
+/* so if we're going to return SQL_SUCCESS, we better not set an */
+/* error message. (otherwise, if a subsequent function call returns */
+/* SQL_ERROR without setting a message, things can get confused.) */
+
+ /*
+ stmt->errormsg = "Requested value changed.";
+ stmt->errornumber = STMT_OPTION_VALUE_CHANGED;
+ */
+
+ return SQL_SUCCESS;
+ break;
+ case SQL_MAX_ROWS:
+ mylog("SetStmtOption(): SQL_MAX_ROWS = %d, returning success\n", vParam);
+ stmt->maxRows = vParam;
+ return SQL_SUCCESS;
+ break;
+ default:
+ return SQL_ERROR;
+ }
+
+ return SQL_SUCCESS;
+}
+
+// - - - - - - - - -
+
+/* This function just can tell you whether you are in Autcommit mode or not */
+RETCODE SQL_API SQLGetConnectOption(
+ HDBC hdbc,
+ UWORD fOption,
+ PTR pvParam)
+{
+ConnectionClass *conn = (ConnectionClass *) hdbc;
+
+ if (! conn)
+ return SQL_INVALID_HANDLE;
+
+ switch (fOption) {
+ case SQL_AUTOCOMMIT:
+ /* CC 28.05.96: Do not set fOption, but pvParam */
+ *((UDWORD *)pvParam) = (UDWORD)( CC_is_in_autocommit(conn) ?
+ SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF);
+ break;
+ /* we don't use qualifiers */
+ case SQL_CURRENT_QUALIFIER:
+ if(pvParam) {
+ strcpy(pvParam, "");
+ }
+ break;
+ default:
+ conn->errormsg = "This option is currently unsupported by the driver";
+ conn->errornumber = CONN_UNSUPPORTED_OPTION;
+ return SQL_ERROR;
+ break;
+
+ }
+
+ return SQL_SUCCESS;
+}
+
+// - - - - - - - - -
+
+RETCODE SQL_API SQLGetStmtOption(
+ HSTMT hstmt,
+ UWORD fOption,
+ PTR pvParam)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+
+ // thought we could fake Access out by just returning SQL_SUCCESS
+ // all the time, but it tries to set a huge value for SQL_MAX_LENGTH
+ // and expects the driver to reduce it to the real value
+
+ if( ! stmt) {
+ return SQL_INVALID_HANDLE;
+ }
+
+ switch(fOption) {
+ case SQL_QUERY_TIMEOUT:
+ // how long we wait on a query before returning to the
+ // application (0 == forever)
+ *((SDWORD *)pvParam) = 0;
+ break;
+ case SQL_MAX_LENGTH:
+ // what is the maximum length that will be returned in
+ // a single column
+ *((SDWORD *)pvParam) = 4096;
+ break;
+ case SQL_MAX_ROWS:
+ *((SDWORD *)pvParam) = stmt->maxRows;
+ mylog("GetSmtOption: MAX_ROWS, returning %d\n", stmt->maxRows);
+
+ break;
+ default:
+ return SQL_ERROR;
+ }
+
+ return SQL_SUCCESS;
+}
+
+// - - - - - - - - -
--- /dev/null
+\r
+/* Module: pgtypes.c\r
+ *\r
+ * Description: This module contains routines for getting information\r
+ * about the supported Postgres data types. Only the function\r
+ * pgtype_to_sqltype() returns an unknown condition. All other\r
+ * functions return a suitable default so that even data types that\r
+ * are not directly supported can be used (it is handled as char data).\r
+ *\r
+ * Classes: n/a\r
+ *\r
+ * API functions: none\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "psqlodbc.h"
+#include "pgtypes.h"
+#include
+#include
+#include
+
+/* these are the types we support. all of the pgtype_ functions should */
+/* return values for each one of these. */
+
+/* NOTE: Even types not directly supported are handled as character types
+ so all types should work (points, etc.) */
+
+Int4 pgtypes_defined[] = {
+ PG_TYPE_CHAR,
+ PG_TYPE_CHAR2,
+ PG_TYPE_CHAR4,
+ PG_TYPE_CHAR8,
+ PG_TYPE_BPCHAR,
+ PG_TYPE_VARCHAR,
+ PG_TYPE_DATE,
+ PG_TYPE_TIME,
+ PG_TYPE_ABSTIME, /* a timestamp, sort of */
+ PG_TYPE_TEXT,
+ PG_TYPE_NAME,
+ PG_TYPE_INT2,
+ PG_TYPE_INT4,
+ PG_TYPE_FLOAT4,
+ PG_TYPE_FLOAT8,
+ PG_TYPE_OID,
+ PG_TYPE_MONEY,
+ PG_TYPE_BOOL,
+ PG_TYPE_CHAR16,
+ PG_TYPE_DATETIME,\r
+ PG_TYPE_BYTEA,\r
+ 0 };
+
+
+/* There are two ways of calling this function:
+ 1. When going through the supported PG types (SQLGetTypeInfo)
+ 2. When taking any type id (SQLColumns, SQLGetData)
+
+ The first type will always work because all the types defined are returned here.
+ The second type will return PG_UNKNOWN when it does not know. The calling
+ routine checks for this and changes it to a char type. This allows for supporting
+ types that are unknown. All other pg routines in here return a suitable default.
+*/
+Int2 pgtype_to_sqltype(Int4 type)
+{
+ switch(type) {
+ case PG_TYPE_CHAR:
+ case PG_TYPE_CHAR2:
+ case PG_TYPE_CHAR4:
+ case PG_TYPE_CHAR8:
+ case PG_TYPE_CHAR16: return SQL_CHAR;
+
+ case PG_TYPE_BPCHAR:\r
+ case PG_TYPE_NAME: \r
+ case PG_TYPE_VARCHAR: return SQL_VARCHAR;\r
+
+ case PG_TYPE_TEXT: return SQL_LONGVARCHAR;\r
+ case PG_TYPE_BYTEA: return SQL_LONGVARBINARY;
+
+ case PG_TYPE_INT2: return SQL_SMALLINT;
+ case PG_TYPE_OID:
+ case PG_TYPE_INT4: return SQL_INTEGER;
+ case PG_TYPE_FLOAT4: return SQL_REAL;
+ case PG_TYPE_FLOAT8: return SQL_FLOAT;
+ case PG_TYPE_DATE: return SQL_DATE;
+ case PG_TYPE_TIME: return SQL_TIME;
+ case PG_TYPE_ABSTIME:
+ case PG_TYPE_DATETIME: return SQL_TIMESTAMP;
+ case PG_TYPE_MONEY: return SQL_FLOAT;
+ case PG_TYPE_BOOL: return SQL_CHAR;
+
+ default: return PG_UNKNOWN; /* check return for this */
+ }
+}
+
+Int2 pgtype_to_ctype(Int4 type)
+{
+ switch(type) {
+ case PG_TYPE_INT2: return SQL_C_SSHORT;
+ case PG_TYPE_OID:
+ case PG_TYPE_INT4: return SQL_C_SLONG;
+ case PG_TYPE_FLOAT4: return SQL_C_FLOAT;
+ case PG_TYPE_FLOAT8: return SQL_C_DOUBLE;
+ case PG_TYPE_DATE: return SQL_C_DATE;
+ case PG_TYPE_TIME: return SQL_C_TIME;
+ case PG_TYPE_ABSTIME:
+ case PG_TYPE_DATETIME: return SQL_C_TIMESTAMP;
+ case PG_TYPE_MONEY: return SQL_C_FLOAT;
+ case PG_TYPE_BOOL: return SQL_C_CHAR;
+\r
+ case PG_TYPE_BYTEA: return SQL_C_BINARY;\r
+
+ default: return SQL_C_CHAR;
+ }
+}
+
+char *pgtype_to_name(Int4 type)
+{
+ switch(type) {
+ case PG_TYPE_CHAR: return "char";
+ case PG_TYPE_CHAR2: return "char2";
+ case PG_TYPE_CHAR4: return "char4";
+ case PG_TYPE_CHAR8: return "char8";
+ case PG_TYPE_CHAR16: return "char16";
+ case PG_TYPE_VARCHAR: return "varchar";
+ case PG_TYPE_BPCHAR: return "bpchar";
+ case PG_TYPE_TEXT: return "text";
+ case PG_TYPE_NAME: return "name";
+ case PG_TYPE_INT2: return "int2";
+ case PG_TYPE_OID: return "oid";
+ case PG_TYPE_INT4: return "int4";
+ case PG_TYPE_FLOAT4: return "float4";
+ case PG_TYPE_FLOAT8: return "float8";
+ case PG_TYPE_DATE: return "date";
+ case PG_TYPE_TIME: return "time";
+ case PG_TYPE_ABSTIME: return "abstime";
+ case PG_TYPE_DATETIME: return "datetime";
+ case PG_TYPE_MONEY: return "money";
+ case PG_TYPE_BOOL: return "bool";\r
+ case PG_TYPE_BYTEA: return "bytea";
+
+ /* "unknown" can actually be used in alter table because it is a real PG type! */
+ default: return "unknown";
+ }
+}
+
+/* For PG_TYPE_VARCHAR, PG_TYPE_BPCHAR, SQLColumns will \r
+ override this length with the atttypmod length from pg_attribute \r
+*/
+Int4 pgtype_precision(Int4 type)
+{
+ switch(type) {
+
+ case PG_TYPE_CHAR: return 1;
+ case PG_TYPE_CHAR2: return 2;
+ case PG_TYPE_CHAR4: return 4;
+ case PG_TYPE_CHAR8: return 8;
+ case PG_TYPE_CHAR16: return 16;\r
+
+ case PG_TYPE_NAME: return 32;\r
+\r
+ case PG_TYPE_VARCHAR:\r
+ case PG_TYPE_BPCHAR: return MAX_VARCHAR_SIZE;\r
+
+ case PG_TYPE_INT2: return 5;\r
+\r
+ case PG_TYPE_OID:
+ case PG_TYPE_INT4: return 10;
+
+ case PG_TYPE_FLOAT4:
+ case PG_TYPE_MONEY: return 7;
+
+ case PG_TYPE_FLOAT8: return 15;
+
+ case PG_TYPE_DATE: return 10;
+ case PG_TYPE_TIME: return 8;
+
+ case PG_TYPE_ABSTIME:
+ case PG_TYPE_DATETIME: return 19;
+
+ case PG_TYPE_BOOL: return 1;
+
+ default:
+ return TEXT_FIELD_SIZE; /* text field types and unknown types */
+ }
+}
+
+/* For PG_TYPE_VARCHAR, PG_TYPE_BPCHAR, SQLColumns will \r
+ override this length with the atttypmod length from pg_attribute \r
+*/\r
+Int4 pgtype_length(Int4 type)
+{
+ switch(type) {
+
+ case PG_TYPE_CHAR: return 1;
+ case PG_TYPE_CHAR2: return 2;
+ case PG_TYPE_CHAR4: return 4;
+ case PG_TYPE_CHAR8: return 8;
+ case PG_TYPE_CHAR16: return 16;\r
+
+ case PG_TYPE_NAME: return 32;\r
+\r
+ case PG_TYPE_VARCHAR:\r
+ case PG_TYPE_BPCHAR: return MAX_VARCHAR_SIZE;\r
+\r
+ case PG_TYPE_INT2: return 2;
+
+ case PG_TYPE_OID:
+ case PG_TYPE_INT4: return 4;
+
+ case PG_TYPE_FLOAT4:
+ case PG_TYPE_MONEY: return 4;
+
+ case PG_TYPE_FLOAT8: return 8;
+
+ case PG_TYPE_DATE:
+ case PG_TYPE_TIME: return 6;
+
+ case PG_TYPE_ABSTIME:
+ case PG_TYPE_DATETIME: return 16;
+
+ case PG_TYPE_BOOL: return 1;
+
+ default:
+ return TEXT_FIELD_SIZE; /* text field types and unknown types */\r
+ }
+}
+
+Int2 pgtype_scale(Int4 type)
+{
+ switch(type) {
+
+ case PG_TYPE_INT2:
+ case PG_TYPE_OID:
+ case PG_TYPE_INT4:
+ case PG_TYPE_FLOAT4:
+ case PG_TYPE_FLOAT8:
+ case PG_TYPE_MONEY:
+ case PG_TYPE_BOOL:
+
+ /* Number of digits to the right of the decimal point in "yyyy-mm=dd hh:mm:ss[.f...]" */
+ case PG_TYPE_ABSTIME:
+ case PG_TYPE_DATETIME: return 0;
+
+ default: return -1;
+ }
+}
+
+
+Int2 pgtype_radix(Int4 type)
+{
+ switch(type) {
+ case PG_TYPE_INT2:
+ case PG_TYPE_OID:
+ case PG_TYPE_INT4:
+ case PG_TYPE_FLOAT4:
+ case PG_TYPE_MONEY:
+ case PG_TYPE_FLOAT8: return 10;
+
+ default: return -1;
+ }
+}
+
+Int2 pgtype_nullable(Int4 type)
+{
+ return SQL_NULLABLE; /* everything should be nullable */
+}
+
+Int2 pgtype_auto_increment(Int4 type)
+{
+ switch(type) {
+
+ case PG_TYPE_INT2:
+ case PG_TYPE_OID:
+ case PG_TYPE_INT4:
+ case PG_TYPE_FLOAT4:
+ case PG_TYPE_MONEY:
+ case PG_TYPE_BOOL:
+ case PG_TYPE_FLOAT8:
+
+ case PG_TYPE_DATE:
+ case PG_TYPE_TIME:
+ case PG_TYPE_ABSTIME:
+ case PG_TYPE_DATETIME: return FALSE;
+
+ default: return -1;
+ }
+}
+
+Int2 pgtype_case_sensitive(Int4 type)
+{
+ switch(type) {
+ case PG_TYPE_CHAR:
+
+ case PG_TYPE_CHAR2:
+ case PG_TYPE_CHAR4:
+ case PG_TYPE_CHAR8:
+ case PG_TYPE_CHAR16:
+
+ case PG_TYPE_VARCHAR:
+ case PG_TYPE_BPCHAR:
+ case PG_TYPE_TEXT:
+ case PG_TYPE_NAME: return TRUE;
+
+ default: return FALSE;
+ }
+}
+
+Int2 pgtype_money(Int4 type)
+{
+ switch(type) {
+ case PG_TYPE_MONEY: return TRUE;
+ default: return FALSE;
+ }
+}
+
+Int2 pgtype_searchable(Int4 type)
+{
+ switch(type) {
+ case PG_TYPE_CHAR:
+ case PG_TYPE_CHAR2:
+ case PG_TYPE_CHAR4:
+ case PG_TYPE_CHAR8:
+ case PG_TYPE_CHAR16:
+
+ case PG_TYPE_VARCHAR:
+ case PG_TYPE_BPCHAR:
+ case PG_TYPE_TEXT:
+ case PG_TYPE_NAME: return SQL_SEARCHABLE;
+
+ default: return SQL_ALL_EXCEPT_LIKE;
+
+ }
+}
+
+Int2 pgtype_unsigned(Int4 type)
+{
+ switch(type) {
+ case PG_TYPE_OID: return TRUE;
+
+ case PG_TYPE_INT2:
+ case PG_TYPE_INT4:
+ case PG_TYPE_FLOAT4:
+ case PG_TYPE_FLOAT8:
+ case PG_TYPE_MONEY: return FALSE;
+
+ default: return -1;
+ }
+}
+
+char *pgtype_literal_prefix(Int4 type)
+{
+ switch(type) {
+
+ case PG_TYPE_INT2:
+ case PG_TYPE_OID:
+ case PG_TYPE_INT4:
+ case PG_TYPE_FLOAT4:
+ case PG_TYPE_FLOAT8:
+ case PG_TYPE_MONEY: return NULL;
+
+ default: return "'";
+ }
+}
+
+char *pgtype_literal_suffix(Int4 type)
+{
+ switch(type) {
+
+ case PG_TYPE_INT2:
+ case PG_TYPE_OID:
+ case PG_TYPE_INT4:
+ case PG_TYPE_FLOAT4:
+ case PG_TYPE_FLOAT8:
+ case PG_TYPE_MONEY: return NULL;
+
+ default: return "'";
+ }
+}
+
+char *pgtype_create_params(Int4 type)
+{
+ switch(type) {
+ case PG_TYPE_CHAR:
+ case PG_TYPE_VARCHAR: return "max. length";
+ default: return NULL;
+ }
+}
+
+
+Int2 sqltype_to_default_ctype(Int2 sqltype)
+{
+ // from the table on page 623 of ODBC 2.0 Programmer's Reference
+ // (Appendix D)
+ switch(sqltype) {
+ case SQL_CHAR:
+ case SQL_VARCHAR:
+ case SQL_LONGVARCHAR:
+ case SQL_DECIMAL:
+ case SQL_NUMERIC:
+ case SQL_BIGINT:
+ return SQL_C_CHAR;
+
+ case SQL_BIT:
+ return SQL_C_BIT;
+
+ case SQL_TINYINT:
+ return SQL_C_STINYINT;
+
+ case SQL_SMALLINT:
+ return SQL_C_SSHORT;
+
+ case SQL_INTEGER:
+ return SQL_C_SLONG;
+
+ case SQL_REAL:
+ return SQL_C_FLOAT;
+
+ case SQL_FLOAT:
+ case SQL_DOUBLE:
+ return SQL_C_DOUBLE;
+
+ case SQL_BINARY:
+ case SQL_VARBINARY:
+ case SQL_LONGVARBINARY:
+ return SQL_C_BINARY;
+
+ case SQL_DATE:
+ return SQL_C_DATE;
+
+ case SQL_TIME:
+ return SQL_C_TIME;
+
+ case SQL_TIMESTAMP:
+ return SQL_C_TIMESTAMP;
+
+ default: /* should never happen */
+ return SQL_C_CHAR;
+ }
+}
+
--- /dev/null
+\r
+/* File: pgtypes.h\r
+ *\r
+ * Description: See "pgtypes.c"\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __PGTYPES_H__
+#define __PGTYPES_H__
+
+/* the type numbers are defined by the OID's of the types' rows */
+/* in table pg_type */
+
+#define PG_UNKNOWN -666 /* returned only from pgtype_to_sqltype() */
+
+#define PG_TYPE_BOOL 16
+#define PG_TYPE_BYTEA 17
+#define PG_TYPE_CHAR 18
+#define PG_TYPE_NAME 19
+#define PG_TYPE_CHAR16 20
+#define PG_TYPE_INT2 21
+#define PG_TYPE_INT28 22
+#define PG_TYPE_INT4 23
+#define PG_TYPE_REGPROC 24
+#define PG_TYPE_TEXT 25
+#define PG_TYPE_OID 26
+#define PG_TYPE_TID 27
+#define PG_TYPE_XID 28
+#define PG_TYPE_CID 29
+#define PG_TYPE_OID8 30
+#define PG_TYPE_SET 32
+#define PG_TYPE_CHAR2 409
+#define PG_TYPE_CHAR4 410
+#define PG_TYPE_CHAR8 411
+#define PG_TYPE_POINT 600
+#define PG_TYPE_LSEG 601
+#define PG_TYPE_PATH 602
+#define PG_TYPE_BOX 603
+#define PG_TYPE_POLYGON 604
+#define PG_TYPE_FILENAME 605
+#define PG_TYPE_FLOAT4 700
+#define PG_TYPE_FLOAT8 701
+#define PG_TYPE_ABSTIME 702
+#define PG_TYPE_RELTIME 703
+#define PG_TYPE_TINTERVAL 704
+#define PG_TYPE_UNKNOWN 705
+#define PG_TYPE_MONEY 790
+#define PG_TYPE_OIDINT2 810
+#define PG_TYPE_OIDINT4 910
+#define PG_TYPE_OIDNAME 911
+#define PG_TYPE_BPCHAR 1042
+#define PG_TYPE_VARCHAR 1043
+#define PG_TYPE_DATE 1082
+#define PG_TYPE_TIME 1083
+#define PG_TYPE_DATETIME 1184
+
+extern Int4 pgtypes_defined[];
+
+Int2 pgtype_to_sqltype(Int4 type);
+Int2 pgtype_to_ctype(Int4 type);
+char *pgtype_to_name(Int4 type);
+Int4 pgtype_precision(Int4 type);
+Int4 pgtype_length(Int4 type);
+Int2 pgtype_scale(Int4 type);
+Int2 pgtype_radix(Int4 type);
+Int2 pgtype_nullable(Int4 type);
+Int2 pgtype_auto_increment(Int4 type);
+Int2 pgtype_case_sensitive(Int4 type);
+Int2 pgtype_money(Int4 type);
+Int2 pgtype_searchable(Int4 type);
+Int2 pgtype_unsigned(Int4 type);
+char *pgtype_literal_prefix(Int4 type);
+char *pgtype_literal_suffix(Int4 type);
+char *pgtype_create_params(Int4 type);
+
+Int2 sqltype_to_default_ctype(Int2 sqltype);
+
+#endif
+
--- /dev/null
+\r
+/* Module: psqlodbc.c\r
+ *\r
+ * Description: This module contains the main entry point (DllMain) for the library.\r
+ * It also contains functions to get and set global variables for the\r
+ * driver in the registry.\r
+ *\r
+ * Classes: n/a\r
+ *\r
+ * API functions: none\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "psqlodbc.h"
+#include
+#include
+#include
+#include
+
+HINSTANCE NEAR s_hModule; /* Saved module handle. */
+GLOBAL_VALUES globals;\r
+\r
+
+/* This function reads the ODBCINST.INI portion of
+ the registry and gets any driver defaults.
+*/
+void getGlobalDefaults(void)
+{
+char temp[128];
+\r
+\r
+ // Fetch Count is stored in driver section
+ SQLGetPrivateProfileString(DBMS_NAME, INI_FETCH, "",
+ temp, sizeof(temp), ODBCINST_INI);
+ if ( temp[0] )
+ globals.fetch_max = atoi(temp);\r
+ else\r
+ globals.fetch_max = FETCH_MAX;
+\r
+
+ // Socket Buffersize is stored in driver section
+ SQLGetPrivateProfileString(DBMS_NAME, INI_SOCKET, "",
+ temp, sizeof(temp), ODBCINST_INI);
+ if ( temp[0] )
+ globals.socket_buffersize = atoi(temp);\r
+ else\r
+ globals.socket_buffersize = SOCK_BUFFER_SIZE;
+\r
+
+ // Debug is stored in the driver section
+ SQLGetPrivateProfileString(DBMS_NAME, INI_DEBUG, "0",
+ temp, sizeof(temp), ODBCINST_INI);
+ globals.debug = atoi(temp);
+\r
+\r
+ // CommLog is stored in the driver section\r
+ SQLGetPrivateProfileString(DBMS_NAME, INI_COMMLOG, "0", \r
+ temp, sizeof(temp), ODBCINST_INI);\r
+ globals.commlog = atoi(temp);\r
+\r
+\r
+ // Optimizer is stored in the driver section only (OFF, ON, or ON=x)\r
+ SQLGetPrivateProfileString(DBMS_NAME, INI_OPTIMIZER, "", \r
+ globals.optimizer, sizeof(globals.optimizer), ODBCINST_INI);\r
+\r
+\r
+ // ConnSettings is stored in the driver section and per datasource for override\r
+ SQLGetPrivateProfileString(DBMS_NAME, INI_CONNSETTINGS, "", \r
+ globals.conn_settings, sizeof(globals.conn_settings), ODBCINST_INI);\r
+}
+\r
+\r
+/* This function writes any global parameters (that can be manipulated)\r
+ to the ODBCINST.INI portion of the registry \r
+*/\r
+void updateGlobals(void)\r
+{\r
+char tmp[128];\r
+\r
+ sprintf(tmp, "%d", globals.commlog);\r
+ SQLWritePrivateProfileString(DBMS_NAME,\r
+ INI_COMMLOG, tmp, ODBCINST_INI);\r
+}
+
+/* This is where the Driver Manager attaches to this Driver */
+BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved)
+{
+WORD wVersionRequested;
+WSADATA wsaData;
+
+ switch (ul_reason_for_call) {
+ case DLL_PROCESS_ATTACH:
+ s_hModule = hInst; /* Save for dialog boxes */
+
+ /* Load the WinSock Library */
+ wVersionRequested = MAKEWORD(1, 1);
+
+ if ( WSAStartup(wVersionRequested, &wsaData))
+ return FALSE;
+
+ /* Verify that this is the minimum version of WinSock */
+ if ( LOBYTE( wsaData.wVersion ) != 1 ||
+ HIBYTE( wsaData.wVersion ) != 1 ) {
+
+ WSACleanup();
+ return FALSE;
+ }
+
+ getGlobalDefaults();
+ break;
+
+ case DLL_THREAD_ATTACH:
+ break;
+
+ case DLL_PROCESS_DETACH:
+
+ WSACleanup();
+
+ return TRUE;
+
+ case DLL_THREAD_DETACH:
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+
+ UNREFERENCED_PARAMETER(lpReserved);
+}
+
+/* This function is used to cause the Driver Manager to
+ call functions by number rather than name, which is faster.
+ The ordinal value of this function must be 199 to have the
+ Driver Manager do this. Also, the ordinal values of the
+ functions must match the value of fFunction in SQLGetFunctions()
+*/
+RETCODE SQL_API SQLDummyOrdinal(void)
+{
+ return SQL_SUCCESS;
+}
+
+
--- /dev/null
+LIBRARY psqlodbc
+EXPORTS
+SQLAllocConnect @1
+SQLAllocEnv @2
+SQLAllocStmt @3
+SQLBindCol @4
+SQLCancel @5
+SQLColAttributes @6
+SQLConnect @7
+SQLDescribeCol @8
+SQLDisconnect @9
+SQLError @10
+SQLExecDirect @11
+SQLExecute @12
+SQLFetch @13
+SQLFreeConnect @14
+SQLFreeEnv @15
+SQLFreeStmt @16
+SQLGetCursorName @17
+SQLNumResultCols @18
+SQLPrepare @19
+SQLRowCount @20
+SQLSetCursorName @21
+SQLTransact @23
+SQLColumns @40
+SQLDriverConnect @41
+SQLGetConnectOption @42
+SQLGetData @43
+SQLGetFunctions @44
+SQLGetInfo @45
+SQLGetStmtOption @46
+SQLGetTypeInfo @47
+SQLParamData @48
+SQLPutData @49
+SQLSetConnectOption @50
+SQLSetStmtOption @51
+SQLSpecialColumns @52
+SQLStatistics @53
+SQLTables @54
+SQLBrowseConnect @55
+SQLColumnPrivileges @56
+SQLDescribeParam @58
+SQLExtendedFetch @59
+SQLForeignKeys @60
+SQLMoreResults @61
+SQLNativeSql @62
+SQLNumParams @63
+SQLParamOptions @64
+SQLPrimaryKeys @65
+SQLProcedureColumns @66
+SQLProcedures @67
+SQLSetPos @68
+SQLSetScrollOptions @69
+SQLTablePrivileges @70
+SQLBindParameter @72
+SQLDummyOrdinal @199
+dconn_FDriverConnectProc @200
+DllMain @201
+ConfigDSN @202
+
--- /dev/null
+\r
+/* File: psqlodbc.h\r
+ *\r
+ * Description: This file contains defines and declarations that are related to\r
+ * the entire driver.\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __PSQLODBC_H__
+#define __PSQLODBC_H__
+
+#define Int4 int
+#define UInt4 unsigned int
+#define Int2 short
+#define UInt2 unsigned short
+
+typedef UInt4 Oid;
+
+
+/* Limits */
+#define MAX_MESSAGE_LEN 8192
+#define MAX_CONNECT_STRING 4096\r
+#define ERROR_MSG_LENGTH 4096
+#define FETCH_MAX 100 /* default number of rows to cache for declare/fetch */
+#define SOCK_BUFFER_SIZE 4096 /* default socket buffer size */\r
+#define MAX_CONNECTIONS 128 /* conns per environment (arbitrary) */
+#define MAX_FIELDS 512
+#define BYTELEN 8
+#define VARHDRSZ sizeof(Int4)
+\r
+/* Registry length limits */
+#define LARGE_REGISTRY_LEN 4096 /* used for special cases */\r
+#define MEDIUM_REGISTRY_LEN 128 /* normal size for user,database,etc. */\r
+#define SMALL_REGISTRY_LEN 10 /* for 1/0 settings */\r
+
+
+/* Connection Defaults */
+#define DEFAULT_PORT "5432"
+#define DEFAULT_READONLY "1"
+
+/* These prefixes denote system tables */
+#define INSIGHT_SYS_PREFIX "dd_"
+#define POSTGRES_SYS_PREFIX "pg_"
+#define KEYS_TABLE "dd_fkey"
+
+/* Info limits */
+#define MAX_INFO_STRING 128
+#define MAX_KEYPARTS 20
+#define MAX_KEYLEN 512 // max key of the form "date+outlet+invoice"
+#define MAX_STATEMENT_LEN MAX_MESSAGE_LEN
+
+/* Driver stuff */
+#define DRIVERNAME "PostgreSQL ODBC"
+#define DBMS_NAME "PostgreSQL"
+#define DBMS_VERSION "06.30.0000 PostgreSQL 6.3"
+#define POSTGRESDRIVERVERSION "06.30.0000"
+#define DRIVER_FILE_NAME "PSQLODBC.DLL"
+\r
+\r
+#define PG62 "6.2" /* "Protocol" key setting to force Postgres 6.2 */\r
+
+/* INI File Stuff */
+#define ODBC_INI "ODBC.INI" /* ODBC initialization file */
+#define ODBCINST_INI "ODBCINST.INI" /* ODBC Installation file */
+
+#define INI_DSN DBMS_NAME /* Name of default Datasource in ini file (not used?) */
+#define INI_KDESC "Description" /* Data source description */
+#define INI_SERVER "Servername" /* Name of Server running the Postgres service */
+#define INI_PORT "Port" /* Port on which the Postmaster is listening */
+#define INI_DATABASE "Database" /* Database Name */
+#define INI_USER "Username" /* Default User Name */
+#define INI_PASSWORD "Password" /* Default Password */
+#define INI_DEBUG "Debug" /* Debug flag */
+#define INI_FETCH "Fetch" /* Fetch Max Count */
+#define INI_SOCKET "Socket" /* Socket buffer size */
+#define INI_READONLY "ReadOnly" /* Database is read only */\r
+#define INI_COMMLOG "CommLog" /* Communication to backend logging */
+#define INI_PROTOCOL "Protocol" /* What protocol (6.2) */\r
+#define INI_OPTIMIZER "Optimizer" /* Use backend genetic optimizer */\r
+#define INI_CONNSETTINGS "ConnSettings" /* Anything to send to backend on successful connection */\r
+\r
+
+typedef struct ConnectionClass_ ConnectionClass;
+typedef struct StatementClass_ StatementClass;
+typedef struct QResultClass_ QResultClass;
+typedef struct SocketClass_ SocketClass;
+typedef struct BindInfoClass_ BindInfoClass;
+typedef struct ParameterInfoClass_ ParameterInfoClass;
+typedef struct ColumnInfoClass_ ColumnInfoClass;
+typedef struct TupleListClass_ TupleListClass;
+typedef struct EnvironmentClass_ EnvironmentClass;
+typedef struct TupleNode_ TupleNode;
+typedef struct TupleField_ TupleField;
+\r
+\r
+typedef struct GlobalValues_\r
+{\r
+ int fetch_max;\r
+ int socket_buffersize;\r
+ int debug;\r
+ int commlog;\r
+ char optimizer[MEDIUM_REGISTRY_LEN];\r
+ char conn_settings[LARGE_REGISTRY_LEN];\r
+} GLOBAL_VALUES;\r
+\r
+\r
+/* sizes */
+#define TEXT_FIELD_SIZE 4094 /* size of text fields (not including null term) */\r
+#define MAX_VARCHAR_SIZE 254 /* maximum size of a varchar (not including null term) */\r
+
+\r
+/* global prototypes */\r
+void updateGlobals(void);\r
+\r
+
+#include "misc.h"
+
+#endif
--- /dev/null
+//Microsoft Developer Studio generated resource script.\r
+//\r
+#include "resource.h"\r
+\r
+#define APSTUDIO_READONLY_SYMBOLS\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// Generated from the TEXTINCLUDE 2 resource.\r
+//\r
+#include "afxres.h"\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+#undef APSTUDIO_READONLY_SYMBOLS\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+// English (U.S.) resources\r
+\r
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r
+#ifdef _WIN32\r
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US\r
+#pragma code_page(1252)\r
+#endif //_WIN32\r
+\r
+#ifdef APSTUDIO_INVOKED\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// TEXTINCLUDE\r
+//\r
+\r
+1 TEXTINCLUDE DISCARDABLE \r
+BEGIN\r
+ "resource.h\0"\r
+END\r
+\r
+2 TEXTINCLUDE DISCARDABLE \r
+BEGIN\r
+ "#include ""afxres.h""\r\n"\r
+ "\0"\r
+END\r
+\r
+3 TEXTINCLUDE DISCARDABLE \r
+BEGIN\r
+ "\r\n"\r
+ "\0"\r
+END\r
+\r
+#endif // APSTUDIO_INVOKED\r
+\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// Dialog\r
+//\r
+\r
+DRIVERCONNDIALOG DIALOG DISCARDABLE 0, 0, 269, 133\r
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU\r
+CAPTION "PostgreSQL Connection"\r
+FONT 8, "MS Sans Serif"\r
+BEGIN\r
+ RTEXT "&Database:",IDC_STATIC,16,25,37,8\r
+ EDITTEXT DATABASE_EDIT,55,25,72,12,ES_AUTOHSCROLL\r
+ RTEXT "&Server:",IDC_STATIC,26,40,27,8\r
+ EDITTEXT SERVER_EDIT,55,40,72,12,ES_AUTOHSCROLL\r
+ RTEXT "&Port:",IDC_STATIC,150,40,20,8\r
+ EDITTEXT PORT_EDIT,172,40,72,12,ES_AUTOHSCROLL\r
+ RTEXT "&User Name:",IDC_STATIC,16,56,37,8\r
+ EDITTEXT USERNAME_EDIT,55,56,72,12,ES_AUTOHSCROLL\r
+ RTEXT "Pass&word:",IDC_STATIC,137,56,33,8\r
+ EDITTEXT PASSWORD_EDIT,172,56,72,12,ES_PASSWORD | ES_AUTOHSCROLL\r
+ GROUPBOX "Options:",IDC_STATIC,25,71,200,25\r
+ CONTROL "&ReadOnly:",READONLY_EDIT,"Button",BS_AUTOCHECKBOX | \r
+ BS_LEFTTEXT | BS_RIGHT | WS_GROUP | WS_TABSTOP,45,80,45,\r
+ 14\r
+ CONTROL "&CommLog (Global):",COMMLOG_EDIT,"Button",\r
+ BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_TABSTOP,\r
+ 100,80,75,14\r
+ CONTROL "6.2",PG62_EDIT,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | \r
+ BS_RIGHT | WS_TABSTOP,185,80,25,14\r
+ DEFPUSHBUTTON "OK",IDOK,84,108,40,14,WS_GROUP\r
+ PUSHBUTTON "Cancel",IDCANCEL,146,108,40,14\r
+ CTEXT "Please supply any missing information needed to connect.",\r
+ IDC_STATIC,40,7,188,11\r
+END\r
+\r
+CONFIGDSN DIALOG DISCARDABLE 65, 43, 292, 151\r
+STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | \r
+ WS_SYSMENU\r
+CAPTION "PostgreSQL Driver Setup"\r
+FONT 8, "MS Sans Serif"\r
+BEGIN\r
+ RTEXT "&Data Source:",IDC_DSNAMETEXT,5,30,50,12,NOT WS_GROUP\r
+ EDITTEXT IDC_DSNAME,57,30,72,12,ES_AUTOHSCROLL | WS_GROUP\r
+ RTEXT "Des&cription:",IDC_STATIC,135,30,39,12,NOT WS_GROUP\r
+ EDITTEXT IDC_DESC,175,30,108,12,ES_AUTOHSCROLL\r
+ RTEXT "Data&base:",IDC_STATIC,17,45,38,12,NOT WS_GROUP\r
+ EDITTEXT IDC_DATABASE,57,45,72,12,ES_AUTOHSCROLL\r
+ RTEXT "&Server:",IDC_STATIC,27,60,29,12,NOT WS_GROUP\r
+ EDITTEXT IDC_SERVER,57,60,72,12,ES_AUTOHSCROLL\r
+ RTEXT "&Port:",IDC_STATIC,153,60,22,12\r
+ EDITTEXT IDC_PORT,175,60,37,12,ES_AUTOHSCROLL\r
+ RTEXT "&User Name:",IDC_STATIC,17,75,39,12\r
+ EDITTEXT IDC_USER,57,75,72,12,ES_AUTOHSCROLL\r
+ RTEXT "Pass&word:",IDC_STATIC,141,75,34,12\r
+ EDITTEXT IDC_PASSWORD,175,75,72,12,ES_PASSWORD | ES_AUTOHSCROLL\r
+ GROUPBOX "Options:",IDC_STATIC,35,92,205,25\r
+ CONTROL "&ReadOnly:",IDC_READONLY,"Button",BS_AUTOCHECKBOX | \r
+ BS_LEFTTEXT | BS_RIGHT | WS_GROUP | WS_TABSTOP,50,100,45,\r
+ 14\r
+ CONTROL "&CommLog (Global):",IDC_COMMLOG,"Button",\r
+ BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_TABSTOP,\r
+ 105,100,75,14\r
+ CONTROL "6.2",IDC_PG62,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | \r
+ BS_RIGHT | WS_TABSTOP,195,100,25,14\r
+ DEFPUSHBUTTON "OK",IDOK,85,129,40,14,WS_GROUP\r
+ PUSHBUTTON "Cancel",IDCANCEL,145,129,40,14\r
+ CTEXT "Change data source name, description, or options. Then choose OK.",\r
+ IDC_STATIC,44,5,180,17\r
+END\r
+\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// DESIGNINFO\r
+//\r
+\r
+#ifdef APSTUDIO_INVOKED\r
+GUIDELINES DESIGNINFO DISCARDABLE \r
+BEGIN\r
+ DRIVERCONNDIALOG, DIALOG\r
+ BEGIN\r
+ RIGHTMARGIN, 268\r
+ END\r
+END\r
+#endif // APSTUDIO_INVOKED\r
+\r
+\r
+#ifndef _MAC\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// Version\r
+//\r
+\r
+VS_VERSION_INFO VERSIONINFO\r
+ FILEVERSION 6,30,0,0\r
+ PRODUCTVERSION 6,30,0,0\r
+ FILEFLAGSMASK 0x3L\r
+#ifdef _DEBUG\r
+ FILEFLAGS 0x1L\r
+#else\r
+ FILEFLAGS 0x0L\r
+#endif\r
+ FILEOS 0x4L\r
+ FILETYPE 0x2L\r
+ FILESUBTYPE 0x0L\r
+BEGIN\r
+ BLOCK "StringFileInfo"\r
+ BEGIN\r
+ BLOCK "040904e4"\r
+ BEGIN\r
+ VALUE "Comments", "PostgreSQL ODBC driver for Windows 95\0"\r
+ VALUE "CompanyName", "Insight Distribution Systems\0"\r
+ VALUE "FileDescription", "PostgreSQL Driver\0"\r
+ VALUE "FileVersion", " 6.30.0000\0"\r
+ VALUE "InternalName", "psqlodbc\0"\r
+ VALUE "LegalTrademarks", "ODBC(TM) is a trademark of Microsoft Corporation. Microsoft® is a registered trademark of Microsoft Corporation. Windows(TM) is a trademark of Microsoft Corporation.\0"\r
+ VALUE "OriginalFilename", "psqlodbc.dll\0"\r
+ VALUE "ProductName", "Microsoft Open Database Connectivity\0"\r
+ VALUE "ProductVersion", " 6.30.0000\0"\r
+ END\r
+ END\r
+ BLOCK "VarFileInfo"\r
+ BEGIN\r
+ VALUE "Translation", 0x409, 1252\r
+ END\r
+END\r
+\r
+#endif // !_MAC\r
+\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// String Table\r
+//\r
+\r
+STRINGTABLE DISCARDABLE \r
+BEGIN\r
+ IDS_BADDSN "Invalid DSN entry, please recheck."\r
+ IDS_MSGTITLE "Invalid DSN"\r
+END\r
+\r
+#endif // English (U.S.) resources\r
+/////////////////////////////////////////////////////////////////////////////\r
+\r
+\r
+\r
+#ifndef APSTUDIO_INVOKED\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// Generated from the TEXTINCLUDE 3 resource.\r
+//\r
+\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+#endif // not APSTUDIO_INVOKED\r
+\r
--- /dev/null
+\r
+/* Module: qresult.c\r
+ *\r
+ * Description: This module contains functions related to \r
+ * managing result information (i.e, fetching rows from the backend,\r
+ * managing the tuple cache, etc.) and retrieving it.\r
+ * Depending on the situation, a QResultClass will hold either data\r
+ * from the backend or a manually built result (see "qresult.h" to\r
+ * see which functions/macros are for manual or backend results.\r
+ * For manually built results, the QResultClass simply points to \r
+ * TupleList and ColumnInfo structures, which actually hold the data.\r
+ *\r
+ * Classes: QResultClass (Functions prefix: "QR_")\r
+ *\r
+ * API functions: none\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "qresult.h"
+#include "misc.h"
+#include
+
+extern GLOBAL_VALUES globals;
+
+/* Used for building a Manual Result only */\r
+/* All info functions call this function to create the manual result set. */
+void
+QR_set_num_fields(QResultClass *self, int new_num_fields)
+{
+ mylog("in QR_set_num_fields\n");
+
+ CI_set_num_fields(self->fields, new_num_fields);
+ if(self->manual_tuples)
+ TL_Destructor(self->manual_tuples);
+
+ self->manual_tuples = TL_Constructor(new_num_fields);
+
+ mylog("exit QR_set_num_fields\n");
+}
+
+/************************************/
+/* CLASS QResult */
+/************************************/
+
+QResultClass *
+QR_Constructor()
+{
+QResultClass *rv;
+
+ mylog("in QR_Constructor\n");
+ rv = (QResultClass *) malloc(sizeof(QResultClass));
+
+ if (rv != NULL) {
+ rv->status = PGRES_EMPTY_QUERY;
+
+ /* construct the column info */
+ if ( ! (rv->fields = CI_Constructor())) {
+ free(rv);
+ return NULL;
+ }
+ rv->manual_tuples = NULL;
+ rv->backend_tuples = NULL;
+ rv->message = NULL;
+ rv->command = NULL;
+ rv->notice = NULL;
+ rv->conn = NULL;
+ rv->inTuples = FALSE;
+ rv->fcount = 0;
+ rv->fetch_count = 0;
+ rv->num_fields = 0;
+ rv->tupleField = NULL;
+ rv->cursor = NULL;
+ }
+
+ mylog("exit QR_Constructor\n");
+ return rv;
+}
+
+void
+QR_Destructor(QResultClass *self)
+{
+ mylog("QResult: in DESTRUCTOR\n");
+
+ /* manual result set tuples */
+ if (self->manual_tuples)\r
+ TL_Destructor(self->manual_tuples);
+
+ // If conn is defined, then we may have used "backend_tuples",
+ // so in case we need to, free it up. Also, close the cursor.
+ if (self->conn && self->conn->sock && CC_is_in_trans(self->conn))
+ QR_close(self); // close the cursor if there is one
+
+ QR_free_memory(self); // safe to call anyway
+
+ // Should have been freed in the close() but just in case...
+ if (self->cursor)
+ free(self->cursor);
+
+ /* Free up column info */
+ if (self->fields)
+ CI_Destructor(self->fields);
+
+ /* Free command info (this is from strdup()) */
+ if (self->command)
+ free(self->command);
+
+ /* Free notice info (this is from strdup()) */
+ if (self->notice)
+ free(self->notice);
+
+ free(self);
+
+ mylog("QResult: exit DESTRUCTOR\n");
+
+}
+
+void
+QR_set_command(QResultClass *self, char *msg)
+{
+ if (self->command)
+ free(self->command);
+
+ self->command = msg ? strdup(msg) : NULL;
+}
+
+void
+QR_set_notice(QResultClass *self, char *msg)
+{
+ if (self->notice)
+ free(self->notice);
+
+ self->notice = msg ? strdup(msg) : NULL;
+}
+
+void
+QR_free_memory(QResultClass *self)
+{
+register int lf, row;
+register TupleField *tuple = self->backend_tuples;
+int fcount = self->fcount;
+int num_fields = self->num_fields;
+
+ mylog("QResult: free memory in, fcount=%d\n", fcount);
+
+ if ( self->backend_tuples) {
+
+ for (row = 0; row < fcount; row++) {
+ mylog("row = %d, num_fields = %d\n", row, num_fields);
+ for (lf=0; lf < num_fields; lf++) {
+ if (tuple[lf].value != NULL) {
+ mylog("free [lf=%d] %u\n", lf, tuple[lf].value);
+ free(tuple[lf].value);
+ }
+ }
+ tuple += num_fields; // next row
+ }
+
+ free(self->backend_tuples);
+ self->backend_tuples = NULL;
+ }
+
+ self->fcount = 0;
+
+ mylog("QResult: free memory out\n");
+}
+
+// This function is called by send_query()
+char
+QR_fetch_tuples(QResultClass *self, ConnectionClass *conn, char *cursor)
+{
+ // If called from send_query the first time (conn != NULL),
+ // then set the inTuples state,
+ // and read the tuples. If conn is NULL,
+ // it implies that we are being called from next_tuple(),
+ // like to get more rows so don't call next_tuple again!
+ if (conn != NULL) {
+ self->conn = conn;
+
+ mylog("QR_fetch_tuples: cursor = '%s', self->cursor=%u\n", cursor, self->cursor);
+
+ if (self->cursor)
+ free(self->cursor);
+
+ if ( ! cursor || cursor[0] == '\0') {
+ self->status = PGRES_INTERNAL_ERROR;
+ QR_set_message(self, "Internal Error -- no cursor for fetch");
+ return FALSE;
+ }
+ self->cursor = strdup(cursor);
+
+ // Read the field attributes.
+ // $$$$ Should do some error control HERE! $$$$
+ if ( CI_read_fields(self->fields, CC_get_socket(self->conn))) {
+ self->status = PGRES_FIELDS_OK;
+ self->num_fields = CI_get_num_fields(self->fields);
+ }
+ else {
+ self->status = PGRES_BAD_RESPONSE;
+ QR_set_message(self, "Error reading field information");
+ return FALSE;
+ }
+
+ mylog("QR_fetch_tuples: past CI_read_fields: num_fields = %d\n", self->num_fields);
+
+ /* allocate memory for the tuple cache */
+ self->backend_tuples = (TupleField *) malloc(self->num_fields * sizeof(TupleField) * globals.fetch_max);
+ if ( ! self->backend_tuples) {
+ self->status = PGRES_FATAL_ERROR;
+ QR_set_message(self, "Could not get memory for tuple cache.");
+ return FALSE;
+ }
+
+ self->inTuples = TRUE;
+
+ /* Force a read to occur in next_tuple */
+ self->fcount = globals.fetch_max+1;
+ self->fetch_count = globals.fetch_max+1;
+
+ return QR_next_tuple(self);
+ }
+ else {
+ // Always have to read the field attributes.
+ // But we dont have to reallocate memory for them!
+
+ if ( ! CI_read_fields(NULL, CC_get_socket(self->conn))) {
+ self->status = PGRES_BAD_RESPONSE;
+ QR_set_message(self, "Error reading field information");
+ return FALSE;
+ }
+ }
+}
+
+// Close the cursor and end the transaction
+// We only close cursor/end the transaction if a cursor was used.
+int
+QR_close(QResultClass *self)
+{
+QResultClass *res;
+
+ if (self->conn && self->cursor) {
+ char buf[64];
+
+ sprintf(buf, "close %s; END", self->cursor);
+
+ mylog("QResult: closing cursor: '%s'\n", buf);
+
+ res = CC_send_query(self->conn, buf, NULL, NULL);
+ CC_set_no_trans(self->conn);
+
+ self->inTuples = FALSE;
+ free(self->cursor);
+ self->cursor = NULL;
+
+ if (res == NULL) {
+ self->status = PGRES_FATAL_ERROR;
+ QR_set_message(self, "Error closing cursor.");
+ return FALSE;
+ }
+
+ }
+
+ return TRUE;
+}
+
+// This function is called by fetch_tuples() AND SQLFetch()
+int
+QR_next_tuple(QResultClass *self)
+{
+int id;
+QResultClass *res;
+SocketClass *sock;
+/* Speed up access */
+int fetch_count = self->fetch_count;
+int fcount = self->fcount;
+TupleField *the_tuples = self->backend_tuples;
+static char msgbuffer[MAX_MESSAGE_LEN+1];
+char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont need static
+
+ if (fetch_count < fcount) { /* return a row from cache */
+ mylog("next_tuple: fetch_count < fcount: returning tuple %d, fcount = %d\n", fetch_count, fcount);
+ self->tupleField = the_tuples + (fetch_count * self->num_fields); /* next row */
+ self->fetch_count++;
+ return TRUE;
+ }
+ else if (self->fcount < globals.fetch_max) { /* last row from cache */
+ // We are done because we didn't even get FETCH_MAX tuples
+ mylog("next_tuple: fcount < FETCH_MAX: fcount = %d, fetch_count = %d\n", fcount, fetch_count);
+ self->tupleField = NULL;
+ self->status = PGRES_END_TUPLES;
+ return -1; /* end of tuples */
+ }
+ else {
+ /* See if we need to fetch another group of rows.
+ We may be being called from send_query(), and
+ if so, don't send another fetch, just fall through
+ and read the tuples.
+ */
+ self->tupleField = NULL;
+ if ( ! self->inTuples) {
+ char fetch[128];
+
+ sprintf(fetch, "fetch %d in %s", globals.fetch_max, self->cursor);
+
+ mylog("next_tuple: sending actual fetch (%d) query '%s'\n", globals.fetch_max, fetch);
+
+ // don't read ahead for the next tuple (self) !
+ res = CC_send_query(self->conn, fetch, self, NULL);
+ if (res == NULL) {
+ self->status = PGRES_FATAL_ERROR;
+ QR_set_message(self, "Error fetching next group.");
+ return FALSE;
+ }
+ self->inTuples = TRUE;
+ /* This is a true fetch, like SQLFetch() */
+ self->fetch_count = 1;
+ }
+ else {
+ mylog("next_tuple: inTuples = true, falling through: fcount = %d, fetch_count = %d\n", self->fcount, self->fetch_count);
+ /* This is a pre-fetch (fetching rows right after query
+ but before any real SQLFetch() calls. This is done
+ so the field attributes are available.
+ */
+ self->fetch_count = 0;
+ }
+ // fall through and read the next group
+ }
+
+
+ self->fcount = 0;
+ sock = CC_get_socket(self->conn);
+ self->tupleField = NULL;
+
+ for ( ; ;) {
+
+ id = SOCK_get_char(sock);
+ switch (id) {
+ case 'T': /* Tuples within tuples cannot be handled */
+ self->status = PGRES_BAD_RESPONSE;
+ QR_set_message(self, "Tuples within tuples cannot be handled");
+ return FALSE;
+ case 'B': /* Tuples in binary format */
+ case 'D': /* Tuples in ASCII format */
+ if ( ! QR_read_tuple(self, (char) (id == 0))) {
+ self->status = PGRES_BAD_RESPONSE;
+ QR_set_message(self, "Error reading the tuple");
+ return FALSE;
+ }
+
+ self->fcount++;
+ break; // continue reading
+
+
+ case 'C': /* End of tuple list */
+ SOCK_get_string(sock, cmdbuffer, MAX_MESSAGE_LEN);
+ QR_set_command(self, cmdbuffer);
+
+ mylog("end of tuple list -- setting inUse to false: this = %u\n", self);
+
+ self->inTuples = FALSE;
+ if (self->fcount > 0) {
+
+ qlog(" [ fetched %d rows ]\n", self->fcount);
+ mylog("_next_tuple: 'C' fetch_max && fcount = %d\n", self->fcount);
+
+ /* set to first row */
+ self->tupleField = the_tuples;
+ return TRUE;
+ }
+ else { // We are surely done here (we read 0 tuples)
+ qlog(" [ fetched 0 rows ]\n");
+ mylog("_next_tuple: 'C': DONE (fcount == 0)\n");
+ return -1; /* end of tuples */
+ }
+
+ case 'E': /* Error */
+ SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
+ QR_set_message(self, msgbuffer);
+ self->status = PGRES_FATAL_ERROR;
+ CC_set_no_trans(self->conn);
+ qlog("ERROR from backend in next_tuple: '%s'\n", msgbuffer);
+
+ return FALSE;
+
+ case 'N': /* Notice */
+ SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
+ QR_set_message(self, msgbuffer);
+ self->status = PGRES_NONFATAL_ERROR;
+ qlog("NOTICE from backend in next_tuple: '%s'\n", msgbuffer);
+ continue;
+
+ default: /* this should only happen if the backend dumped core */
+ QR_set_message(self, "Unexpected result from backend. It probably crashed");
+ self->status = PGRES_FATAL_ERROR;
+ CC_set_no_trans(self->conn);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+char
+QR_read_tuple(QResultClass *self, char binary)
+{
+Int2 field_lf;
+TupleField *this_tuplefield;
+char bmp, bitmap[MAX_FIELDS]; /* Max. len of the bitmap */
+Int2 bitmaplen; /* len of the bitmap in bytes */
+Int2 bitmap_pos;
+Int2 bitcnt;
+Int4 len;
+char *buffer;
+int num_fields = self->num_fields; // speed up access
+SocketClass *sock = CC_get_socket(self->conn);
+\r
+
+ /* set the current row to read the fields into */
+ this_tuplefield = self->backend_tuples + (self->fcount * num_fields);
+
+ bitmaplen = (Int2) num_fields / BYTELEN;
+ if ((num_fields % BYTELEN) > 0)
+ bitmaplen++;
+
+ /*
+ At first the server sends a bitmap that indicates which
+ database fields are null
+ */
+ SOCK_get_n_char(sock, bitmap, bitmaplen);
+
+ bitmap_pos = 0;
+ bitcnt = 0;
+ bmp = bitmap[bitmap_pos];
+
+ for(field_lf = 0; field_lf < num_fields; field_lf++) {
+ /* Check if the current field is NULL */
+ if(!(bmp & 0200)) {
+ /* YES, it is NULL ! */
+ this_tuplefield[field_lf].len = 0;
+ this_tuplefield[field_lf].value = 0;
+ } else {
+ /*
+ NO, the field is not null. so get at first the
+ length of the field (four bytes)
+ */
+ len = SOCK_get_int(sock, VARHDRSZ);
+ if (!binary)
+ len -= VARHDRSZ;
+
+ buffer = (char *)malloc(len+1);
+ SOCK_get_n_char(sock, buffer, len);
+ buffer[len] = '\0';
+
+ // mylog("qresult: len=%d, buffer='%s'\n", len, buffer);
+
+ this_tuplefield[field_lf].len = len;
+ this_tuplefield[field_lf].value = buffer;
+ }
+ /*
+ Now adjust for the next bit to be scanned in the
+ next loop.
+ */
+ bitcnt++;
+ if (BYTELEN == bitcnt) {
+ bitmap_pos++;
+ bmp = bitmap[bitmap_pos];
+ bitcnt = 0;
+ } else
+ bmp <<= 1;
+ }
+ return TRUE;
+}
--- /dev/null
+\r
+/* File: qresult.h\r
+ *\r
+ * Description: See "qresult.c"\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __QRESULT_H__
+#define __QRESULT_H__
+
+#include "connection.h"
+#include "socket.h"
+#include "columninfo.h"
+#include "tuplelist.h"
+#include "psqlodbc.h"
+#include "tuple.h"
+
+enum QueryResultCode_ {
+ PGRES_EMPTY_QUERY = 0,
+ PGRES_COMMAND_OK, /* a query command that doesn't return */
+ /* anything was executed properly by the backend */
+ PGRES_TUPLES_OK, /* a query command that returns tuples */
+ /* was executed properly by the backend, PGresult */
+ /* contains the resulttuples */
+ PGRES_COPY_OUT,
+ PGRES_COPY_IN,
+ PGRES_BAD_RESPONSE, /* an unexpected response was recv'd from the backend */
+ PGRES_NONFATAL_ERROR,
+ PGRES_FATAL_ERROR,
+ PGRES_FIELDS_OK, /* field information from a query was successful */
+ PGRES_END_TUPLES,
+ PGRES_INTERNAL_ERROR
+};
+typedef enum QueryResultCode_ QueryResultCode;
+
+
+struct QResultClass_ {
+ ColumnInfoClass *fields; // the Column information
+ TupleListClass *manual_tuples; // manual result tuple list
+ ConnectionClass *conn; // the connection this result is using (backend)
+
+ // Stuff for declare/fetch tuples
+ int fetch_count; // logical rows read so far
+ int fcount; // actual rows read in the fetch\r
+
+ int num_fields; // number of fields in the result
+ QueryResultCode status;
+
+ char *message;
+ char *cursor; // The name of the cursor for select statements
+ char *command;
+ char *notice;
+
+ TupleField *backend_tuples; // data from the backend (the tuple cache)
+ TupleField *tupleField; // current backend tuple being retrieved
+
+ char inTuples; // is a fetch of rows from the backend in progress?
+};
+
+#define QR_get_fields(self) (self->fields)
+
+
+/* These functions are for retrieving data from the qresult */
+#define QR_get_value_manual(self, tupleno, fieldno) (TL_get_fieldval(self->manual_tuples, tupleno, fieldno))\r
+#define QR_get_value_backend(self, fieldno) (self->tupleField[fieldno].value) \r
+\r
+/* These functions are used by both manual and backend results */
+#define QR_NumResultCols(self) (CI_get_num_fields(self->fields))\r
+#define QR_get_fieldname(self, fieldno_) (CI_get_fieldname(self->fields, fieldno_))
+#define QR_get_fieldsize(self, fieldno_) (CI_get_fieldsize(self->fields, fieldno_))
+#define QR_get_field_type(self, fieldno_) (CI_get_oid(self->fields, fieldno_))\r
+\r
+/* These functions are used only for manual result sets */
+#define QR_get_num_tuples(self) (self->manual_tuples ? TL_get_num_tuples(self->manual_tuples) : 0)\r
+#define QR_add_tuple(self, new_tuple) (TL_add_tuple(self->manual_tuples, new_tuple))
+#define QR_set_field_info(self, field_num, name, adtid, adtsize) (CI_set_field_info(self->fields, field_num, name, adtid, adtsize))\r
+
+/* status macros */
+#define QR_command_successful(self) ( !(self->status == PGRES_BAD_RESPONSE || self->status == PGRES_NONFATAL_ERROR || self->status == PGRES_FATAL_ERROR))
+#define QR_command_nonfatal(self) ( self->status == PGRES_NONFATAL_ERROR)
+#define QR_end_tuples(self) ( self->status == PGRES_END_TUPLES)
+#define QR_set_status(self, condition) ( self->status = condition )
+#define QR_set_message(self, message_) ( self->message = message_)
+
+#define QR_get_message(self) (self->message)
+#define QR_get_command(self) (self->command)
+#define QR_get_notice(self) (self->notice)
+#define QR_get_status(self) (self->status)
+
+// Core Functions
+QResultClass *QR_Constructor();
+void QR_Destructor(QResultClass *self);
+char QR_read_tuple(QResultClass *self, char binary);
+int QR_next_tuple(QResultClass *self);
+int QR_close(QResultClass *self);
+char QR_fetch_tuples(QResultClass *self, ConnectionClass *conn, char *cursor);
+void QR_free_memory(QResultClass *self);
+void QR_set_command(QResultClass *self, char *msg);
+void QR_set_notice(QResultClass *self, char *msg);\r
+
+void QR_set_num_fields(QResultClass *self, int new_num_fields); /* manual result only */
+
+#endif
--- /dev/null
+//{{NO_DEPENDENCIES}}\r
+// Microsoft Developer Studio generated include file.\r
+// Used by psqlodbc.rc\r
+//\r
+#define IDS_BADDSN 1\r
+#define IDS_MSGTITLE 2\r
+#define DRIVERCONNDIALOG 101\r
+#define IDC_DSNAME 400\r
+#define IDC_DSNAMETEXT 401\r
+#define IDC_DESC 404\r
+#define IDC_SERVER 407\r
+#define IDC_DATABASE 408\r
+#define CONFIGDSN 1001\r
+#define IDC_PORT 1002\r
+#define IDC_USER 1006\r
+#define IDC_PASSWORD 1009\r
+#define IDC_READONLY 1011\r
+#define READONLY_EDIT 1012\r
+#define SAVEPASSWORD_EDIT 1013\r
+#define IDC_COMMLOG 1014\r
+#define COMMLOG_EDIT 1015\r
+#define IDC_PG62 1016\r
+#define PG62_EDIT 1017\r
+#define SERVER_EDIT 1501\r
+#define PORT_EDIT 1502\r
+#define DATABASE_EDIT 1503\r
+#define USERNAME_EDIT 1504\r
+#define PASSWORD_EDIT 1505\r
+\r
+// Next default values for new objects\r
+// \r
+#ifdef APSTUDIO_INVOKED\r
+#ifndef APSTUDIO_READONLY_SYMBOLS\r
+#define _APS_NEXT_RESOURCE_VALUE 102\r
+#define _APS_NEXT_COMMAND_VALUE 40001\r
+#define _APS_NEXT_CONTROL_VALUE 1018\r
+#define _APS_NEXT_SYMED_VALUE 101\r
+#endif\r
+#endif\r
--- /dev/null
+\r
+/* Module: results.c\r
+ *\r
+ * Description: This module contains functions related to \r
+ * retrieving result information through the ODBC API.\r
+ *\r
+ * Classes: n/a\r
+ *\r
+ * API functions: SQLRowCount, SQLNumResultCols, SQLDescribeCol, SQLColAttributes,\r
+ * SQLGetData, SQLFetch, SQLExtendedFetch, \r
+ * SQLMoreResults(NI), SQLSetPos(NI), SQLSetScrollOptions(NI),\r
+ * SQLSetCursorName(NI), SQLGetCursorName(NI)\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include
+#include "psqlodbc.h"
+#include "environ.h"
+#include "connection.h"
+#include "statement.h"
+#include "bind.h"
+#include "qresult.h"
+#include "convert.h"
+#include "pgtypes.h"
+
+#include
+#include
+#include
+
+
+RETCODE SQL_API SQLRowCount(
+ HSTMT hstmt,
+ SDWORD FAR *pcrow)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+QResultClass *res;
+char *msg, *ptr;
+
+ if ( ! stmt)
+ return SQL_ERROR;
+
+ if(stmt->statement_type == STMT_TYPE_SELECT) {
+ if (stmt->status == STMT_FINISHED) {
+ res = SC_get_Result(stmt);
+
+ if(res && pcrow) {
+ *pcrow = QR_get_num_tuples(res);
+ return SQL_SUCCESS;
+ }
+ }
+ } else {
+
+ res = SC_get_Result(stmt);
+ if (res && pcrow) {
+ msg = QR_get_command(res);
+ mylog("*** msg = '%s'\n", msg);
+ trim(msg); // get rid of trailing spaces
+ ptr = strrchr(msg, ' ');
+ if (ptr) {
+ *pcrow = atoi(ptr+1);
+ mylog("**** SQLRowCount(): THE ROWS: *pcrow = %d\n", *pcrow);
+ }
+ else {
+ *pcrow = -1;
+
+ mylog("**** SQLRowCount(): NO ROWS: *pcrow = %d\n", *pcrow);
+ }
+
+ return SQL_SUCCESS;
+ }
+ }
+
+ return SQL_ERROR;
+}
+
+
+// This returns the number of columns associated with the database
+// attached to "hstmt".
+
+
+RETCODE SQL_API SQLNumResultCols(
+ HSTMT hstmt,
+ SWORD FAR *pccol)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+QResultClass *result;
+
+ if ( ! stmt)
+ return SQL_INVALID_HANDLE;
+
+ SC_clear_error(stmt);
+
+ /* CC: Now check for the "prepared, but not executed" situation, that enables us to
+ deal with "SQLPrepare -- SQLDescribeCol -- ... -- SQLExecute" situations.
+ (AutoCAD 13 ASE/ASI just _loves_ that ;-) )
+ */
+ mylog("**** SQLNumResultCols: calling SC_pre_execute\n");
+
+ SC_pre_execute(stmt);
+
+ result = SC_get_Result(stmt);
+
+ mylog("SQLNumResultCols: result = %u, status = %d, numcols = %d\n", result, stmt->status, result != NULL ? QR_NumResultCols(result) : -1);
+ if (( ! result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE)) ) {
+ /* no query has been executed on this statement */
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ stmt->errormsg = "No query has been executed with that handle";
+ return SQL_ERROR;
+ }
+
+ *pccol = QR_NumResultCols(result);
+
+ return SQL_SUCCESS;
+}
+
+// - - - - - - - - -
+
+// Return information about the database column the user wants
+// information about.
+/* CC: preliminary implementation */
+RETCODE SQL_API SQLDescribeCol(
+ HSTMT hstmt,
+ UWORD icol,
+ UCHAR FAR *szColName,
+ SWORD cbColNameMax,
+ SWORD FAR *pcbColName,
+ SWORD FAR *pfSqlType,
+ UDWORD FAR *pcbColDef,
+ SWORD FAR *pibScale,
+ SWORD FAR *pfNullable)
+{
+ /* gets all the information about a specific column */
+StatementClass *stmt = (StatementClass *) hstmt;
+QResultClass *result;
+char *name;
+Int4 fieldtype;
+
+ if ( ! stmt)
+ return SQL_INVALID_HANDLE;
+
+ SC_clear_error(stmt);
+
+ /* CC: Now check for the "prepared, but not executed" situation, that enables us to
+ deal with "SQLPrepare -- SQLDescribeCol -- ... -- SQLExecute" situations.
+ (AutoCAD 13 ASE/ASI just _loves_ that ;-) )
+ */
+
+ SC_pre_execute(stmt);
+
+
+ result = SC_get_Result(stmt);
+ mylog("**** SQLDescribeCol: result = %u, stmt->status = %d, !finished=%d, !premature=%d\n", result, stmt->status, stmt->status != STMT_FINISHED, stmt->status != STMT_PREMATURE);
+ if ( (NULL == result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE))) {
+ /* no query has been executed on this statement */
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ stmt->errormsg = "No query has been assigned to this statement.";
+ return SQL_ERROR;
+ }
+
+ if (cbColNameMax >= 1) {
+ name = QR_get_fieldname(result, (Int2) (icol-1));
+ mylog("describeCol: col %d fieldname = '%s'\n", icol - 1, name);
+ /* our indices start from 0 whereas ODBC defines indices starting from 1 */
+ if (NULL != pcbColName) {
+ // we want to get the total number of bytes in the column name
+ if (NULL == name)
+ *pcbColName = 0;
+ else
+ *pcbColName = strlen(name);
+ }
+ if (NULL != szColName) {
+ // get the column name into the buffer if there is one
+ if (NULL == name)
+ szColName[0] = '\0';
+ else
+ strncpy_null(szColName, name, cbColNameMax);
+ }
+ }
+
+ fieldtype = QR_get_field_type(result, (Int2) (icol-1));
+ mylog("describeCol: col %d fieldtype = %d\n", icol - 1, fieldtype);
+
+ if (NULL != pfSqlType) {
+ *pfSqlType = pgtype_to_sqltype(fieldtype);
+ if (*pfSqlType == PG_UNKNOWN)
+ *pfSqlType = SQL_CHAR;
+ }
+
+ if (NULL != pcbColDef)
+ *pcbColDef = pgtype_precision(fieldtype);
+
+ if (NULL != pibScale) {
+ Int2 scale;
+ scale = pgtype_scale(fieldtype);
+ if(scale == -1) { scale = 0; }
+
+ *pibScale = scale;
+ }
+ if (NULL != pfNullable) {
+ *pfNullable = pgtype_nullable(fieldtype);
+ }
+
+ return SQL_SUCCESS;
+}
+
+// Returns result column descriptor information for a result set.
+
+RETCODE SQL_API SQLColAttributes(
+ HSTMT hstmt,
+ UWORD icol,
+ UWORD fDescType,
+ PTR rgbDesc,
+ SWORD cbDescMax,
+ SWORD FAR *pcbDesc,
+ SDWORD FAR *pfDesc)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+char *value;
+Int4 field_type;
+
+ if( ! stmt) {
+ return SQL_INVALID_HANDLE;
+ }
+
+ /* CC: Now check for the "prepared, but not executed" situation, that enables us to
+ deal with "SQLPrepare -- SQLDescribeCol -- ... -- SQLExecute" situations.
+ (AutoCAD 13 ASE/ASI just _loves_ that ;-) )
+ */
+ SC_pre_execute(stmt);
+
+ mylog("**** SQLColAtt: result = %u, status = %d, numcols = %d\n", stmt->result, stmt->status, stmt->result != NULL ? QR_NumResultCols(stmt->result) : -1);
+
+ if ( (NULL == stmt->result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE)) ) {
+ stmt->errormsg = "Can't get column attributes: no result found.";
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ return SQL_ERROR;
+ }
+
+ if(icol < 1) {
+ // we do not support bookmarks
+ stmt->errormsg = "Bookmarks are not currently supported.";
+ stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR;
+ return SQL_ERROR;
+ }
+
+ icol -= 1;
+ field_type = QR_get_field_type(stmt->result, icol);
+ mylog("colAttr: col %d field_type = %d\n", icol, field_type);
+ switch(fDescType) {
+ case SQL_COLUMN_AUTO_INCREMENT:
+ if (NULL != pfDesc) {
+ *pfDesc = pgtype_auto_increment(field_type);
+
+ if(*pfDesc == -1) { /* "not applicable" becomes false */
+ *pfDesc = FALSE;
+ }
+ }
+ break;
+ case SQL_COLUMN_CASE_SENSITIVE:
+ if (NULL != pfDesc)
+ *pfDesc = pgtype_case_sensitive(field_type);
+ break;
+ case SQL_COLUMN_COUNT:
+ if (NULL != pfDesc)
+ *pfDesc = QR_NumResultCols(stmt->result);
+ break;
+ case SQL_COLUMN_DISPLAY_SIZE:
+ if (NULL != pfDesc)
+ *pfDesc = pgtype_precision(field_type);\r
+\r
+ mylog("colAttr: col %d fieldsize = %d\n", icol, *pfDesc);
+
+ break;
+ case SQL_COLUMN_LABEL:
+ case SQL_COLUMN_NAME:
+ value = QR_get_fieldname(stmt->result, icol);
+ strncpy_null((char *)rgbDesc, value, cbDescMax);
+ /* CC: Check for Nullpointesr */
+ if (NULL != pcbDesc)
+ *pcbDesc = strlen(value);
+ break;
+ case SQL_COLUMN_LENGTH:
+ if (NULL != pfDesc)
+ *pfDesc = pgtype_precision(field_type);
+ return SQL_SUCCESS;
+ break;
+ case SQL_COLUMN_MONEY:
+ if (NULL != pfDesc)
+ *pfDesc = pgtype_money(field_type);
+ break;
+ case SQL_COLUMN_NULLABLE:
+ if (NULL != pfDesc)
+ *pfDesc = pgtype_nullable(field_type);
+ break;
+ case SQL_COLUMN_OWNER_NAME:
+ return SQL_ERROR;
+ break;
+ case SQL_COLUMN_PRECISION:
+ if (NULL != pfDesc)
+ *pfDesc = pgtype_precision(field_type);
+ break;
+ case SQL_COLUMN_QUALIFIER_NAME:
+ strncpy_null((char *)rgbDesc, "", cbDescMax);
+ if (NULL != pfDesc)
+ *pcbDesc = 1;
+ break;
+ case SQL_COLUMN_SCALE:
+ if (NULL != pfDesc)
+ *pfDesc = pgtype_scale(field_type);
+ break;
+ case SQL_COLUMN_SEARCHABLE:
+ if (NULL != pfDesc)
+ *pfDesc = pgtype_searchable(field_type);
+ break;
+ case SQL_COLUMN_TABLE_NAME:
+ return SQL_ERROR;
+ break;
+ case SQL_COLUMN_TYPE:
+ if (NULL != pfDesc) {
+ *pfDesc = pgtype_to_sqltype(field_type);
+ if (*pfDesc == PG_UNKNOWN)
+ *pfDesc = SQL_CHAR;
+ }
+ break;
+ case SQL_COLUMN_TYPE_NAME:
+ value = pgtype_to_name(field_type);
+ strncpy_null((char *)rgbDesc, value, cbDescMax);
+ if (NULL != pcbDesc)
+ *pcbDesc = strlen(value);
+ break;
+ case SQL_COLUMN_UNSIGNED:
+ if (NULL != pfDesc) {
+ *pfDesc = pgtype_unsigned(field_type);
+ if(*pfDesc == -1) {
+ *pfDesc = FALSE;
+ }
+ }
+ break;
+ case SQL_COLUMN_UPDATABLE:
+ // everything should be updatable, I guess, unless access permissions
+ // prevent it--are we supposed to check for that here? seems kind
+ // of complicated. hmm...
+ if (NULL != pfDesc)
+ *pfDesc = SQL_ATTR_WRITE;
+ break;
+ }
+
+ return SQL_SUCCESS;
+}
+
+// Returns result data for a single column in the current row.
+
+RETCODE SQL_API SQLGetData(
+ HSTMT hstmt,
+ UWORD icol,
+ SWORD fCType,
+ PTR rgbValue,
+ SDWORD cbValueMax,
+ SDWORD FAR *pcbValue)
+{
+QResultClass *res;
+StatementClass *stmt = (StatementClass *) hstmt;
+int num_cols, num_rows;
+Int4 field_type;
+void *value;
+int result;
+
+ if( ! stmt) {
+ return SQL_INVALID_HANDLE;
+ }
+ res = stmt->result;
+
+ if (STMT_EXECUTING == stmt->status) {
+ stmt->errormsg = "Can't get data while statement is still executing.";
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ return 0;
+ }
+
+ if (stmt->status != STMT_FINISHED) {
+ stmt->errornumber = STMT_STATUS_ERROR;
+ stmt->errormsg = "GetData can only be called after the successful execution on a SQL statement";
+ return 0;
+ }
+
+ if (icol == 0) {
+ stmt->errormsg = "Bookmarks are not currently supported.";
+ stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR;
+ return SQL_ERROR;
+ }
+
+ // use zero-based column numbers
+ icol--;
+
+ // make sure the column number is valid
+ num_cols = QR_NumResultCols(res);
+ if (icol >= num_cols) {
+ stmt->errormsg = "Invalid column number.";
+ stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR;
+ return SQL_ERROR;
+ }
+
+ if ( stmt->manual_result) {
+ // make sure we're positioned on a valid row
+ num_rows = QR_get_num_tuples(res);
+ if((stmt->currTuple < 0) ||
+ (stmt->currTuple >= num_rows)) {
+ stmt->errormsg = "Not positioned on a valid row for GetData.";
+ stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR;
+ return SQL_ERROR;
+ }
+ value = QR_get_value_manual(res, stmt->currTuple, icol);
+ }
+ else { /* its a SOCKET result (backend data) */
+ if (stmt->currTuple == -1 || ! res || QR_end_tuples(res)) {
+ stmt->errormsg = "Not positioned on a valid row for GetData.";
+ stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR;
+ return SQL_ERROR;
+ }
+
+ value = QR_get_value_backend(res, icol);
+
+ }
+
+ field_type = QR_get_field_type(res, icol);
+
+ mylog("**** SQLGetData: icol = %d, fCType = %d, field_type = %d, value = '%s'\n", icol, fCType, field_type, value);
+
+ result = copy_and_convert_field(field_type, value,
+ fCType, rgbValue, cbValueMax, pcbValue);
+
+
+ if(result == COPY_UNSUPPORTED_TYPE) {
+ stmt->errormsg = "Received an unsupported type from Postgres.";
+ stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR;
+ return SQL_ERROR;
+ } else if(result == COPY_UNSUPPORTED_CONVERSION) {
+ stmt->errormsg = "Couldn't handle the necessary data type conversion.";
+ stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR;
+ return SQL_ERROR;
+ } else if(result == COPY_RESULT_TRUNCATED) {
+ stmt->errornumber = STMT_TRUNCATED;
+ stmt->errormsg = "The buffer was too small for the result.";
+ return SQL_SUCCESS_WITH_INFO;
+ } else if(result != COPY_OK) {
+ stmt->errormsg = "Unrecognized return value from copy_and_convert_field.";
+ stmt->errornumber = STMT_INTERNAL_ERROR;
+ return SQL_ERROR;
+ }
+
+ return SQL_SUCCESS;
+}
+
+// Returns data for bound columns in the current row ("hstmt->iCursor"),
+// advances the cursor.
+
+RETCODE SQL_API SQLFetch(
+ HSTMT hstmt)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+QResultClass *res;
+int retval;
+Int2 num_cols, lf;
+Oid type;
+char *value;
+ColumnInfoClass *ci;
+
+
+ if ( ! stmt)
+ return SQL_INVALID_HANDLE;
+
+ SC_clear_error(stmt);
+
+ if ( ! (res = stmt->result)) {
+ stmt->errormsg = "Null statement result in SQLFetch.";
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ return SQL_ERROR;
+ }
+
+ ci = QR_get_fields(res); /* the column info */
+
+ if (stmt->status == STMT_EXECUTING) {
+ stmt->errormsg = "Can't fetch while statement is still executing.";
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ return SQL_ERROR;
+ }
+
+
+ if (stmt->status != STMT_FINISHED) {
+ stmt->errornumber = STMT_STATUS_ERROR;
+ stmt->errormsg = "Fetch can only be called after the successful execution on a SQL statement";
+ return SQL_ERROR;
+ }
+
+ if (stmt->bindings == NULL) {
+ // just to avoid a crash if the user insists on calling this
+ // function even if SQL_ExecDirect has reported an Error
+ stmt->errormsg = "Bindings were not allocated properly.";
+ stmt->errornumber = STMT_SEQUENCE_ERROR;
+ return SQL_ERROR;
+ }
+
+
+ if ( stmt->manual_result) {
+ if (QR_get_num_tuples(res) -1 == stmt->currTuple ||
+ (stmt->maxRows > 0 && stmt->currTuple == stmt->maxRows - 1))
+ /* if we are at the end of a tuple list, we return a "no data found" */
+ return SQL_NO_DATA_FOUND;
+
+ mylog("**** SQLFetch: manual_result\n");
+ (stmt->currTuple)++;
+ }
+ else {
+
+ // read from the cache or the physical next tuple
+ retval = QR_next_tuple(res);
+ if (retval < 0) {
+ mylog("**** SQLFetch: end_tuples\n");
+ return SQL_NO_DATA_FOUND;
+ }
+ else if (retval > 0)
+ (stmt->currTuple)++; // all is well
+
+ else {
+ mylog("SQLFetch: error\n");
+ stmt->errornumber = STMT_EXEC_ERROR;
+ stmt->errormsg = "Error fetching next row";
+ return SQL_ERROR;
+ }
+
+ }
+
+ num_cols = QR_NumResultCols(res);
+
+ for (lf=0; lf < num_cols; lf++) {
+
+ mylog("fetch: cols=%d, lf=%d, buffer[] = %u\n",
+ num_cols, lf, stmt->bindings[lf].buffer);
+
+ if (stmt->bindings[lf].buffer != NULL) {
+ // this column has a binding
+
+ // type = QR_get_field_type(res, lf);
+ type = CI_get_oid(ci, lf); /* speed things up */
+
+ if (stmt->manual_result)
+ value = QR_get_value_manual(res, stmt->currTuple, lf);
+ else
+ value = QR_get_value_backend(res, lf);
+
+ retval = copy_and_convert_field_bindinfo(type, value, &(stmt->bindings[lf]));
+
+ // check whether the complete result was copied
+ if(retval == COPY_UNSUPPORTED_TYPE) {
+ stmt->errormsg = "Received an unsupported type from Postgres.";
+ stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR;
+ return SQL_ERROR;
+
+ } else if(retval == COPY_UNSUPPORTED_CONVERSION) {
+ stmt->errormsg = "Couldn't handle the necessary data type conversion.";
+ stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR;
+ return SQL_ERROR;
+
+ } else if(retval == COPY_RESULT_TRUNCATED) {
+ /* The result has been truncated during the copy */
+ /* this will generate a SQL_SUCCESS_WITH_INFO result */
+ stmt->errornumber = STMT_TRUNCATED;
+ stmt->errormsg = "A buffer was too small for the return value to fit in";
+ return SQL_SUCCESS_WITH_INFO;
+
+ } else if(retval != COPY_OK) {
+ stmt->errormsg = "Unrecognized return value from copy_and_convert_field.";
+ stmt->errornumber = STMT_INTERNAL_ERROR;
+ return SQL_ERROR;
+
+ }
+ }
+ }
+
+ return SQL_SUCCESS;
+}
+
+// This fetchs a block of data (rowset).
+
+RETCODE SQL_API SQLExtendedFetch(
+ HSTMT hstmt,
+ UWORD fFetchType,
+ SDWORD irow,
+ UDWORD FAR *pcrow,
+ UWORD FAR *rgfRowStatus)
+{
+StatementClass *stmt = (StatementClass *) hstmt;
+
+ if ( ! stmt)
+ return SQL_INVALID_HANDLE;
+
+ /* Currently, only for manual results can this be done
+ because not all the tuples are read in ahead of time.
+ */
+ if ( ! stmt->manual_result)
+ return SQL_ERROR;
+
+ // CC: we currently only support fetches in one row bits
+ if (NULL != pcrow)
+ *pcrow = 1;
+ if (NULL != rgfRowStatus)
+ *rgfRowStatus = SQL_ROW_SUCCESS;
+
+ switch (fFetchType) {
+ case SQL_FETCH_NEXT:
+ return SQLFetch(hstmt);
+ case SQL_FETCH_PRIOR:
+ if (stmt->currTuple <= 0)
+ return SQL_ERROR;
+ stmt->currTuple--;
+ return SQLFetch(hstmt);
+ case SQL_FETCH_FIRST:
+ stmt->currTuple = -1;
+ return SQLFetch(hstmt);
+ case SQL_FETCH_LAST:
+ stmt->currTuple = QR_get_num_tuples(stmt->result)-1;
+ return SQLFetch(hstmt);
+ case SQL_FETCH_ABSOLUTE:
+ if (irow == 0) {
+ stmt->currTuple = stmt->currTuple > 0 ? stmt->currTuple-2 : -1;
+ } else if (irow > 0) {
+ stmt->currTuple = irow-2;
+ return SQLFetch(hstmt);
+ } else {
+ // CC: ??? not sure about the specification in that case
+ return SQL_ERROR;
+ }
+ default:
+ return SQL_ERROR;
+ }
+ return SQL_SUCCESS;
+}
+
+// This determines whether there are more results sets available for
+// the "hstmt".
+
+/* CC: return SQL_NO_DATA_FOUND since we do not support multiple result sets */
+RETCODE SQL_API SQLMoreResults(
+ HSTMT hstmt)
+{
+ return SQL_NO_DATA_FOUND;
+}
+
+// This positions the cursor within a block of data.
+
+RETCODE SQL_API SQLSetPos(
+ HSTMT hstmt,
+ UWORD irow,
+ UWORD fOption,
+ UWORD fLock)
+{
+ return SQL_ERROR;
+}
+
+// Sets options that control the behavior of cursors.
+
+RETCODE SQL_API SQLSetScrollOptions(
+ HSTMT hstmt,
+ UWORD fConcurrency,
+ SDWORD crowKeyset,
+ UWORD crowRowset)
+{
+ return SQL_ERROR;
+}
+
+
+// Set the cursor name on a statement handle
+
+RETCODE SQL_API SQLSetCursorName(
+ HSTMT hstmt,
+ UCHAR FAR *szCursor,
+ SWORD cbCursor)
+{
+ return SQL_SUCCESS;
+}
+
+// Return the cursor name for a statement handle
+
+RETCODE SQL_API SQLGetCursorName(
+ HSTMT hstmt,
+ UCHAR FAR *szCursor,
+ SWORD cbCursorMax,
+ SWORD FAR *pcbCursor)
+{
+ return SQL_ERROR;
+}
+
+
--- /dev/null
+\r
+/* Module: setup.c\r
+ *\r
+ * Description: This module contains the setup functions for \r
+ * adding/modifying a Data Source in the ODBC.INI portion\r
+ * of the registry.\r
+ *\r
+ * Classes: n/a\r
+ *\r
+ * API functions: ConfigDSN\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ *************************************************************************************/\r
+
+/*
+** SETUP.C - This is the ODBC sample driver code for
+** setup.
+**
+** This code is furnished on an as-is basis as part of the ODBC SDK and is
+** intended for example purposes only.
+**
+*/
+/*--------------------------------------------------------------------------
+ setup.c -- Sample ODBC setup
+
+ This code demonstrates how to interact with the ODBC Installer. These
+ functions may be part of your ODBC driver or in a separate DLL.
+
+ The ODBC Installer allows a driver to control the management of
+ data sources by calling the ConfigDSN entry point in the appropriate
+ DLL. When called, ConfigDSN receives four parameters:
+
+ hwndParent ---- Handle of the parent window for any dialogs which
+ may need to be created. If this handle is NULL,
+ then no dialogs should be displayed (that is, the
+ request should be processed silently).
+
+ fRequest ------ Flag indicating the type of request (add, configure
+ (edit), or remove).
+
+ lpszDriver ---- Far pointer to a null-terminated string containing
+ the name of your driver. This is the same string you
+ supply in the ODBC.INF file as your section header
+ and which ODBC Setup displays to the user in lieu
+ of the actual driver filename. This string needs to
+ be passed back to the ODBC Installer when adding a
+ new data source name.
+
+ lpszAttributes- Far pointer to a list of null-terminated attribute
+ keywords. This list is similar to the list passed
+ to SQLDriverConnect, except that each key-value
+ pair is separated by a null-byte rather than a
+ semicolon. The entire list is then terminated with
+ a null-byte (that is, two consecutive null-bytes
+ mark the end of the list). The keywords accepted
+ should be those for SQLDriverConnect which are
+ applicable, any new keywords you define for ODBC.INI,
+ and any additional keywords you decide to document.
+
+ ConfigDSN should return TRUE if the requested operation succeeds and
+ FALSE otherwise. The complete prototype for ConfigDSN is:
+
+ BOOL FAR PASCAL ConfigDSN(HWND hwndParent,
+ WORD fRequest,
+ LPSTR lpszDriver,
+ LPCSTR lpszAttributes)
+
+ Your setup code should not write to ODBC.INI directly to add or remove
+ data source names. Instead, link with ODBCINST.LIB (the ODBC Installer
+ library) and call SQLWriteDSNToIni and SQLRemoveDSNFromIni.
+ Use SQLWriteDSNToIni to add data source names. If the data source name
+ already exists, SQLWriteDSNToIni will delete it (removing all of its
+ associated keys) and rewrite it. SQLRemoveDSNToIni removes a data
+ source name and all of its associated keys.
+
+ For NT compatibility, the driver code should not use the
+ Get/WritePrivateProfileString windows functions for ODBC.INI, but instead,
+ use SQLGet/SQLWritePrivateProfileString functions that are macros (16 bit) or
+ calls to the odbcinst.dll (32 bit).
+
+--------------------------------------------------------------------------*/
+
+
+// Includes ----------------------------------------------------------------
+#include "psqlodbc.h" // Local include files
+#include
+#include
+#include // ODBC installer prototypes
+#include // C include files
+#include
+#include "resource.h"
+
+#define INTFUNC __stdcall
+
+extern HINSTANCE NEAR s_hModule; /* Saved module handle. */
+extern GLOBAL_VALUES globals;
+
+// Constants ---------------------------------------------------------------
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+
+#define MAXPATHLEN (255+1) // Max path length
+#define MAXKEYLEN (15+1) // Max keyword length
+#define MAXDESC (255+1) // Max description length
+#define MAXDSNAME (32+1) // Max data source name length
+
+static char far EMPTYSTR []= "";
+static char far OPTIONON []= "Yes";
+static char far OPTIONOFF []= "No";
+
+// Attribute key indexes (into an array of Attr structs, see below)
+#define KEY_DSN 0
+#define KEY_DESC 1
+#define KEY_PORT 2
+#define KEY_SERVER 3
+#define KEY_DATABASE 4
+#define KEY_USER 5
+#define KEY_PASSWORD 6
+#define KEY_DEBUG 7
+#define KEY_FETCH 8
+#define KEY_READONLY 9\r
+#define KEY_PROTOCOL 10\r
+#define NUMOFKEYS 11 // Number of keys supported
+
+// Attribute string look-up table (maps keys to associated indexes)
+static struct {
+ char szKey[MAXKEYLEN];
+ int iKey;
+} s_aLookup[] = { "DSN", KEY_DSN,
+ INI_KDESC, KEY_DESC,
+ INI_PORT, KEY_PORT,
+ INI_SERVER, KEY_SERVER,
+ INI_DATABASE, KEY_DATABASE,
+ INI_USER, KEY_USER,
+ INI_PASSWORD, KEY_PASSWORD,
+ INI_DEBUG, KEY_DEBUG,
+ INI_FETCH, KEY_FETCH,
+ INI_READONLY, KEY_READONLY,\r
+ INI_PROTOCOL, KEY_PROTOCOL,\r
+ "", 0
+ };
+
+
+
+// Types -------------------------------------------------------------------
+typedef struct tagAttr {
+ BOOL fSupplied;
+ char szAttr[MAXPATHLEN];
+} Attr, FAR * LPAttr;
+
+
+// Globals -----------------------------------------------------------------
+// NOTE: All these are used by the dialog procedures
+typedef struct tagSETUPDLG {
+ HWND hwndParent; // Parent window handle
+ LPCSTR lpszDrvr; // Driver description
+ Attr aAttr[NUMOFKEYS]; // Attribute array
+ char szDSN[MAXDSNAME]; // Original data source name
+ BOOL fNewDSN; // New data source flag
+ BOOL fDefault; // Default data source flag
+
+} SETUPDLG, FAR *LPSETUPDLG;
+
+
+
+// Prototypes --------------------------------------------------------------
+void INTFUNC CenterDialog (HWND hdlg);
+
+int CALLBACK ConfigDlgProc (HWND hdlg,
+ WORD wMsg,
+ WPARAM wParam,
+ LPARAM lParam);
+void INTFUNC ParseAttributes (LPCSTR lpszAttributes, LPSETUPDLG lpsetupdlg);
+
+/* CC: SetDSNAttributes is declared as "INTFUNC" below, but here it is declared as
+ "CALLBACK" -- Watcom complained about disagreeing modifiers. Changed
+ "CALLBACK" to "INTFUNC" here.
+ BOOL CALLBACK SetDSNAttributes(HWND hwnd, LPSETUPDLG lpsetupdlg);
+*/
+
+BOOL INTFUNC SetDSNAttributes(HWND hwnd, LPSETUPDLG lpsetupdlg);
+
+/* ConfigDSN ---------------------------------------------------------------
+ Description: ODBC Setup entry point
+ This entry point is called by the ODBC Installer
+ (see file header for more details)
+ Input : hwnd ----------- Parent window handle
+ fRequest ------- Request type (i.e., add, config, or remove)
+ lpszDriver ----- Driver name
+ lpszAttributes - data source attribute string
+ Output : TRUE success, FALSE otherwise
+--------------------------------------------------------------------------*/
+
+BOOL CALLBACK ConfigDSN (HWND hwnd,
+ WORD fRequest,
+ LPCSTR lpszDriver,
+ LPCSTR lpszAttributes)
+{
+ BOOL fSuccess; // Success/fail flag
+ GLOBALHANDLE hglbAttr;
+ LPSETUPDLG lpsetupdlg;
+
+
+ // Allocate attribute array
+ hglbAttr = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(SETUPDLG));
+ if (!hglbAttr)
+ return FALSE;
+ lpsetupdlg = (LPSETUPDLG)GlobalLock(hglbAttr);
+
+ // Parse attribute string
+ if (lpszAttributes)
+ ParseAttributes(lpszAttributes, lpsetupdlg);
+
+ // Save original data source name
+ if (lpsetupdlg->aAttr[KEY_DSN].fSupplied)
+ lstrcpy(lpsetupdlg->szDSN, lpsetupdlg->aAttr[KEY_DSN].szAttr);
+ else
+ lpsetupdlg->szDSN[0] = '\0';
+
+ // Remove data source
+ if (ODBC_REMOVE_DSN == fRequest) {
+ // Fail if no data source name was supplied
+ if (!lpsetupdlg->aAttr[KEY_DSN].fSupplied)
+ fSuccess = FALSE;
+
+ // Otherwise remove data source from ODBC.INI
+ else
+ fSuccess = SQLRemoveDSNFromIni(lpsetupdlg->aAttr[KEY_DSN].szAttr);
+ }
+
+ // Add or Configure data source
+ else {
+ // Save passed variables for global access (e.g., dialog access)
+ lpsetupdlg->hwndParent = hwnd;
+ lpsetupdlg->lpszDrvr = lpszDriver;
+ lpsetupdlg->fNewDSN = (ODBC_ADD_DSN == fRequest);
+ lpsetupdlg->fDefault =
+ !lstrcmpi(lpsetupdlg->aAttr[KEY_DSN].szAttr, INI_DSN);
+
+ // Display the appropriate dialog (if parent window handle supplied)
+ if (hwnd) {
+ // Display dialog(s)
+ fSuccess = (IDOK == DialogBoxParam(s_hModule,
+ MAKEINTRESOURCE(CONFIGDSN),
+ hwnd,
+ ConfigDlgProc,
+ (LONG)(LPSTR)lpsetupdlg));
+ }
+
+ else if (lpsetupdlg->aAttr[KEY_DSN].fSupplied)
+ fSuccess = SetDSNAttributes(hwnd, lpsetupdlg);
+ else
+ fSuccess = FALSE;
+ }
+
+ GlobalUnlock(hglbAttr);
+ GlobalFree(hglbAttr);
+
+ return fSuccess;
+}
+
+
+/* CenterDialog ------------------------------------------------------------
+ Description: Center the dialog over the frame window
+ Input : hdlg -- Dialog window handle
+ Output : None
+--------------------------------------------------------------------------*/
+void INTFUNC CenterDialog(HWND hdlg)
+{
+ HWND hwndFrame;
+ RECT rcDlg, rcScr, rcFrame;
+ int cx, cy;
+
+ hwndFrame = GetParent(hdlg);
+
+ GetWindowRect(hdlg, &rcDlg);
+ cx = rcDlg.right - rcDlg.left;
+ cy = rcDlg.bottom - rcDlg.top;
+
+ GetClientRect(hwndFrame, &rcFrame);
+ ClientToScreen(hwndFrame, (LPPOINT)(&rcFrame.left));
+ ClientToScreen(hwndFrame, (LPPOINT)(&rcFrame.right));
+ rcDlg.top = rcFrame.top + (((rcFrame.bottom - rcFrame.top) - cy) >> 1);
+ rcDlg.left = rcFrame.left + (((rcFrame.right - rcFrame.left) - cx) >> 1);
+ rcDlg.bottom = rcDlg.top + cy;
+ rcDlg.right = rcDlg.left + cx;
+
+ GetWindowRect(GetDesktopWindow(), &rcScr);
+ if (rcDlg.bottom > rcScr.bottom)
+ {
+ rcDlg.bottom = rcScr.bottom;
+ rcDlg.top = rcDlg.bottom - cy;
+ }
+ if (rcDlg.right > rcScr.right)
+ {
+ rcDlg.right = rcScr.right;
+ rcDlg.left = rcDlg.right - cx;
+ }
+
+ if (rcDlg.left < 0) rcDlg.left = 0;
+ if (rcDlg.top < 0) rcDlg.top = 0;
+
+ MoveWindow(hdlg, rcDlg.left, rcDlg.top, cx, cy, TRUE);
+ return;
+}
+
+/* ConfigDlgProc -----------------------------------------------------------
+ Description: Manage add data source name dialog
+ Input : hdlg --- Dialog window handle
+ wMsg --- Message
+ wParam - Message parameter
+ lParam - Message parameter
+ Output : TRUE if message processed, FALSE otherwise
+--------------------------------------------------------------------------*/
+
+
+
+int CALLBACK ConfigDlgProc
+ (HWND hdlg,
+ WORD wMsg,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+
+ switch (wMsg) {
+ // Initialize the dialog
+ case WM_INITDIALOG:
+ {
+ LPSETUPDLG lpsetupdlg;
+ LPCSTR lpszDSN;
+
+ SetWindowLong(hdlg, DWL_USER, lParam);
+ CenterDialog(hdlg); // Center dialog
+
+ lpsetupdlg = (LPSETUPDLG) lParam;
+ lpszDSN = lpsetupdlg->aAttr[KEY_DSN].szAttr;
+ // Initialize dialog fields
+ // NOTE: Values supplied in the attribute string will always
+ // override settings in ODBC.INI
+ SetDlgItemText(hdlg, IDC_DSNAME, lpszDSN);
+
+ // Description
+ if (!lpsetupdlg->aAttr[KEY_DESC].fSupplied)
+ SQLGetPrivateProfileString(lpszDSN, INI_KDESC,
+ EMPTYSTR,
+ lpsetupdlg->aAttr[KEY_DESC].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_DESC].szAttr),
+ ODBC_INI);
+ SetDlgItemText(hdlg, IDC_DESC, lpsetupdlg->aAttr[KEY_DESC].szAttr);
+
+ // Database
+ if (!lpsetupdlg->aAttr[KEY_DATABASE].fSupplied)
+ SQLGetPrivateProfileString(lpszDSN, INI_DATABASE,
+ EMPTYSTR,
+ lpsetupdlg->aAttr[KEY_DATABASE].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_DATABASE].szAttr),
+ ODBC_INI);
+ SetDlgItemText(hdlg, IDC_DATABASE, lpsetupdlg->aAttr[KEY_DATABASE].szAttr);
+
+ // Server
+ if (!lpsetupdlg->aAttr[KEY_SERVER].fSupplied)
+ SQLGetPrivateProfileString(lpszDSN, INI_SERVER,
+ EMPTYSTR,
+ lpsetupdlg->aAttr[KEY_SERVER].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_SERVER].szAttr),
+ ODBC_INI);
+ SetDlgItemText(hdlg, IDC_SERVER, lpsetupdlg->aAttr[KEY_SERVER].szAttr);
+
+ // Port
+ if (!lpsetupdlg->aAttr[KEY_PORT].fSupplied)
+ SQLGetPrivateProfileString(lpszDSN, INI_PORT,
+ EMPTYSTR,
+ lpsetupdlg->aAttr[KEY_PORT].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_PORT].szAttr),
+ ODBC_INI);
+ if (lpsetupdlg->aAttr[KEY_PORT].szAttr[0] == '\0')
+ strcpy(lpsetupdlg->aAttr[KEY_PORT].szAttr, DEFAULT_PORT);
+ SetDlgItemText(hdlg, IDC_PORT, lpsetupdlg->aAttr[KEY_PORT].szAttr);
+
+ /* Username */
+ if (!lpsetupdlg->aAttr[KEY_USER].fSupplied)
+ SQLGetPrivateProfileString(lpszDSN, INI_USER,
+ EMPTYSTR,
+ lpsetupdlg->aAttr[KEY_USER].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_USER].szAttr),
+ ODBC_INI);
+ SetDlgItemText(hdlg, IDC_USER, lpsetupdlg->aAttr[KEY_USER].szAttr);
+
+ // Password
+ if (!lpsetupdlg->aAttr[KEY_PASSWORD].fSupplied)
+ SQLGetPrivateProfileString(lpszDSN, INI_PASSWORD,
+ EMPTYSTR,
+ lpsetupdlg->aAttr[KEY_PASSWORD].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_PASSWORD].szAttr),
+ ODBC_INI);
+ SetDlgItemText(hdlg, IDC_PASSWORD, lpsetupdlg->aAttr[KEY_PASSWORD].szAttr);
+
+ // ReadOnly Parameter
+ if (!lpsetupdlg->aAttr[KEY_READONLY].fSupplied) {
+ SQLGetPrivateProfileString(lpszDSN, INI_READONLY,
+ EMPTYSTR,
+ lpsetupdlg->aAttr[KEY_READONLY].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_READONLY].szAttr),
+ ODBC_INI);
+ }
+ if (lpsetupdlg->aAttr[KEY_READONLY].szAttr[0] == '\0')
+ strcpy(lpsetupdlg->aAttr[KEY_READONLY].szAttr, DEFAULT_READONLY);
+ CheckDlgButton(hdlg, IDC_READONLY, atoi(lpsetupdlg->aAttr[KEY_READONLY].szAttr));
+
+ // Protocol Parameter\r
+ if (!lpsetupdlg->aAttr[KEY_PROTOCOL].fSupplied) {\r
+ SQLGetPrivateProfileString(lpszDSN, INI_PROTOCOL,\r
+ EMPTYSTR,\r
+ lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr,\r
+ sizeof(lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr),\r
+ ODBC_INI);\r
+ }\r
+ if (strncmp(lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr, PG62, strlen(PG62)) == 0)\r
+ CheckDlgButton(hdlg, IDC_PG62, 1);\r
+ else\r
+ CheckDlgButton(hdlg, IDC_PG62, 0);\r
+\r
+\r
+ // CommLog Parameter (this is global)\r
+ CheckDlgButton(hdlg, IDC_COMMLOG, globals.commlog);\r
+
+
+ if (lpsetupdlg->fDefault)
+ {
+ EnableWindow(GetDlgItem(hdlg, IDC_DSNAME), FALSE);
+ EnableWindow(GetDlgItem(hdlg, IDC_DSNAMETEXT), FALSE);
+ }
+ else
+ SendDlgItemMessage(hdlg, IDC_DSNAME,
+ EM_LIMITTEXT, (WPARAM)(MAXDSNAME-1), 0L);
+ SendDlgItemMessage(hdlg, IDC_DESC,
+ EM_LIMITTEXT, (WPARAM)(MAXDESC-1), 0L);
+ return TRUE; // Focus was not set
+ }
+
+
+ // Process buttons
+ case WM_COMMAND:
+ switch (GET_WM_COMMAND_ID(wParam, lParam)) {
+ // Ensure the OK button is enabled only when a data source name
+ // is entered
+ case IDC_DSNAME:
+ if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_CHANGE)
+ {
+ char szItem[MAXDSNAME]; // Edit control text
+
+ // Enable/disable the OK button
+ EnableWindow(GetDlgItem(hdlg, IDOK),
+ GetDlgItemText(hdlg, IDC_DSNAME,
+ szItem, sizeof(szItem)));
+ return TRUE;
+ }
+ break;
+
+ // Accept results
+ case IDOK:
+ {
+ LPSETUPDLG lpsetupdlg;
+
+ lpsetupdlg = (LPSETUPDLG)GetWindowLong(hdlg, DWL_USER);
+ // Retrieve dialog values
+ if (!lpsetupdlg->fDefault)
+ GetDlgItemText(hdlg, IDC_DSNAME,
+ lpsetupdlg->aAttr[KEY_DSN].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_DSN].szAttr));
+ GetDlgItemText(hdlg, IDC_DESC,
+ lpsetupdlg->aAttr[KEY_DESC].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_DESC].szAttr));
+
+ GetDlgItemText(hdlg, IDC_DATABASE,
+ lpsetupdlg->aAttr[KEY_DATABASE].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_DATABASE].szAttr));
+
+ GetDlgItemText(hdlg, IDC_PORT,
+ lpsetupdlg->aAttr[KEY_PORT].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_PORT].szAttr));
+
+ GetDlgItemText(hdlg, IDC_SERVER,
+ lpsetupdlg->aAttr[KEY_SERVER].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_SERVER].szAttr));
+
+ GetDlgItemText(hdlg, IDC_USER,
+ lpsetupdlg->aAttr[KEY_USER].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_USER].szAttr));
+
+ GetDlgItemText(hdlg, IDC_PASSWORD,
+ lpsetupdlg->aAttr[KEY_PASSWORD].szAttr,
+ sizeof(lpsetupdlg->aAttr[KEY_PASSWORD].szAttr));
+\r
+ if ( IsDlgButtonChecked(hdlg, IDC_PG62))\r
+ strcpy(lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr, PG62);\r
+ else\r
+ lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr[0] = '\0';\r
+
+ sprintf(lpsetupdlg->aAttr[KEY_READONLY].szAttr, "%d", IsDlgButtonChecked(hdlg, IDC_READONLY));\r
+\r
+ globals.commlog = IsDlgButtonChecked(hdlg, IDC_COMMLOG);
+
+
+ // Update ODBC.INI
+ SetDSNAttributes(hdlg, lpsetupdlg);
+ }
+
+ // Return to caller
+ case IDCANCEL:
+ EndDialog(hdlg, wParam);
+ return TRUE;
+ }
+ break;
+ }
+
+ // Message not processed
+ return FALSE;
+}
+
+
+/* ParseAttributes ---------------------------------------------------------
+ Description: Parse attribute string moving values into the aAttr array
+ Input : lpszAttributes - Pointer to attribute string
+ Output : None (global aAttr normally updated)
+--------------------------------------------------------------------------*/
+void INTFUNC ParseAttributes(LPCSTR lpszAttributes, LPSETUPDLG lpsetupdlg)
+{
+ LPCSTR lpsz;
+ LPCSTR lpszStart;
+ char aszKey[MAXKEYLEN];
+ int iElement;
+ int cbKey;
+
+ for (lpsz=lpszAttributes; *lpsz; lpsz++)
+ { // Extract key name (e.g., DSN), it must be terminated by an equals
+ lpszStart = lpsz;
+ for (;; lpsz++)
+ {
+ if (!*lpsz)
+ return; // No key was found
+ else if (*lpsz == '=')
+ break; // Valid key found
+ }
+ // Determine the key's index in the key table (-1 if not found)
+ iElement = -1;
+ cbKey = lpsz - lpszStart;
+ if (cbKey < sizeof(aszKey))
+ {
+ register int j;
+
+ _fmemcpy(aszKey, lpszStart, cbKey);
+ aszKey[cbKey] = '\0';
+ for (j = 0; *s_aLookup[j].szKey; j++)
+ {
+ if (!lstrcmpi(s_aLookup[j].szKey, aszKey))
+ {
+ iElement = s_aLookup[j].iKey;
+ break;
+ }
+ }
+ }
+
+ // Locate end of key value
+ lpszStart = ++lpsz;
+ for (; *lpsz; lpsz++);
+
+ // Save value if key is known
+ // NOTE: This code assumes the szAttr buffers in aAttr have been
+ // zero initialized
+ if (iElement >= 0)
+ {
+ lpsetupdlg->aAttr[iElement].fSupplied = TRUE;
+ _fmemcpy(lpsetupdlg->aAttr[iElement].szAttr,
+ lpszStart,
+ MIN(lpsz-lpszStart+1, sizeof(lpsetupdlg->aAttr[0].szAttr)-1));
+ }
+ }
+ return;
+}
+
+
+/* SetDSNAttributes --------------------------------------------------------
+ Description: Write data source attributes to ODBC.INI
+ Input : hwnd - Parent window handle (plus globals)
+ Output : TRUE if successful, FALSE otherwise
+--------------------------------------------------------------------------*/
+
+BOOL INTFUNC SetDSNAttributes(HWND hwndParent, LPSETUPDLG lpsetupdlg)
+{
+ LPCSTR lpszDSN; // Pointer to data source name
+
+ lpszDSN = lpsetupdlg->aAttr[KEY_DSN].szAttr;
+
+ // Validate arguments
+ if (lpsetupdlg->fNewDSN && !*lpsetupdlg->aAttr[KEY_DSN].szAttr)
+ return FALSE;
+
+ // Write the data source name
+ if (!SQLWriteDSNToIni(lpszDSN, lpsetupdlg->lpszDrvr))
+ {
+ if (hwndParent)
+ {
+ char szBuf[MAXPATHLEN];
+ char szMsg[MAXPATHLEN];
+
+ LoadString(s_hModule, IDS_BADDSN, szBuf, sizeof(szBuf));
+ wsprintf(szMsg, szBuf, lpszDSN);
+ LoadString(s_hModule, IDS_MSGTITLE, szBuf, sizeof(szBuf));
+ MessageBox(hwndParent, szMsg, szBuf, MB_ICONEXCLAMATION | MB_OK);
+ }
+ return FALSE;
+ }
+
+
+ // Update ODBC.INI
+ // Save the value if the data source is new, if it was edited, or if
+ // it was explicitly supplied
+ if (hwndParent || lpsetupdlg->aAttr[KEY_DESC].fSupplied )
+ SQLWritePrivateProfileString(lpszDSN,
+ INI_KDESC,
+ lpsetupdlg->aAttr[KEY_DESC].szAttr,
+ ODBC_INI);
+
+ if (hwndParent || lpsetupdlg->aAttr[KEY_DATABASE].fSupplied )
+ SQLWritePrivateProfileString(lpszDSN,
+ INI_DATABASE,
+ lpsetupdlg->aAttr[KEY_DATABASE].szAttr,
+ ODBC_INI);
+
+ if (hwndParent || lpsetupdlg->aAttr[KEY_PORT].fSupplied )
+ SQLWritePrivateProfileString(lpszDSN,
+ INI_PORT,
+ lpsetupdlg->aAttr[KEY_PORT].szAttr,
+ ODBC_INI);
+
+ if (hwndParent || lpsetupdlg->aAttr[KEY_SERVER].fSupplied )
+ SQLWritePrivateProfileString(lpszDSN,
+ INI_SERVER,
+ lpsetupdlg->aAttr[KEY_SERVER].szAttr,
+ ODBC_INI);
+
+ if (hwndParent || lpsetupdlg->aAttr[KEY_USER].fSupplied )
+ SQLWritePrivateProfileString(lpszDSN,
+ INI_USER,
+ lpsetupdlg->aAttr[KEY_USER].szAttr,
+ ODBC_INI);
+
+ if (hwndParent || lpsetupdlg->aAttr[KEY_PASSWORD].fSupplied )
+ SQLWritePrivateProfileString(lpszDSN,
+ INI_PASSWORD,
+ lpsetupdlg->aAttr[KEY_PASSWORD].szAttr,
+ ODBC_INI);
+
+ if (hwndParent || lpsetupdlg->aAttr[KEY_READONLY].fSupplied )
+ SQLWritePrivateProfileString(lpszDSN,
+ INI_READONLY,
+ lpsetupdlg->aAttr[KEY_READONLY].szAttr,
+ ODBC_INI);\r
+\r
+ if (hwndParent || lpsetupdlg->aAttr[KEY_PROTOCOL].fSupplied )\r
+ SQLWritePrivateProfileString(lpszDSN,\r
+ INI_PROTOCOL,\r
+ lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr,\r
+ ODBC_INI);\r
+\r
+ // CommLog Parameter -- write to ODBCINST_INI (for the whole driver)
+ if (hwndParent ) {\r
+ updateGlobals();\r
+ }\r
+
+ // If the data source name has changed, remove the old name
+ if (lpsetupdlg->aAttr[KEY_DSN].fSupplied &&
+ lstrcmpi(lpsetupdlg->szDSN, lpsetupdlg->aAttr[KEY_DSN].szAttr))
+ {
+ SQLRemoveDSNFromIni(lpsetupdlg->szDSN);
+ }
+ return TRUE;
+}
--- /dev/null
+\r
+/* Module: socket.c\r
+ *\r
+ * Description: This module contains functions for low level socket\r
+ * operations (connecting/reading/writing to the backend)\r
+ *\r
+ * Classes: SocketClass (Functions prefix: "SOCK_")\r
+ *\r
+ * API functions: none\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "socket.h"
+
+extern GLOBAL_VALUES globals;
+
+void
+SOCK_clear_error(SocketClass *self)
+{
+ self->errornumber = 0;
+ self->errormsg = NULL;
+}
+
+SocketClass *
+SOCK_Constructor()
+{
+SocketClass *rv;
+
+ rv = (SocketClass *) malloc(sizeof(SocketClass));
+
+ if (rv != NULL) {
+ rv->socket = (SOCKET) -1;
+ rv->buffer_filled_in = 0;
+ rv->buffer_filled_out = 0;
+ rv->buffer_read_in = 0;
+
+ rv->buffer_in = (unsigned char *) malloc(globals.socket_buffersize);
+ if ( ! rv->buffer_in)
+ return NULL;
+
+ rv->buffer_out = (unsigned char *) malloc(globals.socket_buffersize);
+ if ( ! rv->buffer_out)
+ return NULL;
+
+ rv->errormsg = NULL;
+ rv->errornumber = 0;\r
+\r
+ rv->reverse = FALSE;
+ }
+ return rv;
+
+}
+
+void
+SOCK_Destructor(SocketClass *self)
+{
+ if (self->socket != -1) {
+ if ( ! shutdown(self->socket, 2)) /* no sends or receives */
+ closesocket(self->socket);
+ }
+
+ if (self->buffer_in)
+ free(self->buffer_in);
+
+ if (self->buffer_out)
+ free(self->buffer_out);
+
+ free(self);
+
+}
+
+
+char
+SOCK_connect_to(SocketClass *self, unsigned short port, char *hostname)
+{
+struct hostent *host;
+struct sockaddr_in sadr;
+
+ if (self->socket != -1) {
+ self->errornumber = SOCKET_ALREADY_CONNECTED;
+ self->errormsg = "Socket is already connected";
+ return 0;
+ }
+
+ host = gethostbyname(hostname);
+ if (host == NULL) {
+ self->errornumber = SOCKET_HOST_NOT_FOUND;
+ self->errormsg = "Could not resolve hostname.";
+ return 0;
+ }
+
+ memset((char *)&sadr, 0, sizeof(sadr));
+ memcpy(&(sadr.sin_addr), host->h_addr, host->h_length);
+ sadr.sin_family = AF_INET;
+ sadr.sin_port = htons(port);
+
+ self->socket = socket(AF_INET, SOCK_STREAM, 0);
+ if (self->socket == -1) {
+ self->errornumber = SOCKET_COULD_NOT_CREATE_SOCKET;
+ self->errormsg = "Could not create Socket.";
+ return 0;
+ }
+
+ if ( connect(self->socket, (struct sockaddr *)&(sadr),
+ sizeof(sadr)) < 0) {
+
+ self->errornumber = SOCKET_COULD_NOT_CONNECT;
+ self->errormsg = "Could not connect to remote socket.";
+ closesocket(self->socket);
+ self->socket = (SOCKET) -1;
+ return 0;
+ }
+ return 1;
+}
+
+
+void
+SOCK_get_n_char(SocketClass *self, char *buffer, int len)
+{
+int lf;
+
+ if ( ! buffer) {
+ self->errornumber = SOCKET_NULLPOINTER_PARAMETER;
+ self->errormsg = "get_n_char was called with NULL-Pointer";
+ return;
+ }
+
+ for(lf=0; lf < len; lf++)
+ buffer[lf] = SOCK_get_next_byte(self);
+}
+
+
+void
+SOCK_put_n_char(SocketClass *self, char *buffer, int len)
+{
+int lf;
+
+ if ( ! buffer) {
+ self->errornumber = SOCKET_NULLPOINTER_PARAMETER;
+ self->errormsg = "put_n_char was called with NULL-Pointer";
+ return;
+ }
+
+ for(lf=0; lf < len; lf++)
+ SOCK_put_next_byte(self, (unsigned char)buffer[lf]);
+}
+
+
+/* bufsize must include room for the null terminator
+ will read at most bufsize-1 characters + null.
+*/
+void
+SOCK_get_string(SocketClass *self, char *buffer, int bufsize)
+{
+register int lf = 0;
+
+ for (lf = 0; lf < bufsize; lf++)
+ if ( ! (buffer[lf] = SOCK_get_next_byte(self)))
+ return;
+
+ buffer[bufsize-1] = '\0';
+}
+
+
+void
+SOCK_put_string(SocketClass *self, char *string)
+{
+register int lf;
+int len;
+
+ len = strlen(string)+1;
+
+ for(lf = 0; lf < len; lf++)
+ SOCK_put_next_byte(self, (unsigned char)string[lf]);
+}
+
+
+int
+SOCK_get_int(SocketClass *self, short len)
+{
+char buf[4];
+
+ switch (len) {
+ case 2:
+ SOCK_get_n_char(self, buf, len);
+ if (self->reverse)\r
+ return *((unsigned short *) buf);\r
+ else\r
+ return ntohs( *((unsigned short *) buf) );
+
+ case 4:
+ SOCK_get_n_char(self, buf, len);\r
+ if (self->reverse)\r
+ return *((unsigned int *) buf);\r
+ else
+ return ntohl( *((unsigned int *) buf) );
+
+ default:
+ self->errornumber = SOCKET_GET_INT_WRONG_LENGTH;
+ self->errormsg = "Cannot read ints of that length";
+ return 0;
+ }
+}\r
+
+
+void
+SOCK_put_int(SocketClass *self, int value, short len)
+{
+unsigned int rv;
+
+ switch (len) {
+ case 2:\r
+ rv = self->reverse ? value : htons( (unsigned short) value);
+ SOCK_put_n_char(self, (char *) &rv, 2);
+ return;
+
+ case 4:\r
+ rv = self->reverse ? value : htonl( (unsigned int) value);
+ SOCK_put_n_char(self, (char *) &rv, 4);
+ return;
+
+ default:
+ self->errornumber = SOCKET_PUT_INT_WRONG_LENGTH;
+ self->errormsg = "Cannot write ints of that length";
+ return;
+ }
+}
+
+
+void
+SOCK_flush_output(SocketClass *self)
+{
+int written;
+
+ written = send(self->socket, (char *)self->buffer_out, self->buffer_filled_out, 0);
+ if (written != self->buffer_filled_out) {
+ self->errornumber = SOCKET_WRITE_ERROR;
+ self->errormsg = "Could not flush socket buffer.";
+ }
+ self->buffer_filled_out = 0;
+}
+
+unsigned char
+SOCK_get_next_byte(SocketClass *self)
+{
+
+ if (self->buffer_read_in >= self->buffer_filled_in) {
+ // there are no more bytes left in the buffer ->
+ // reload the buffer
+
+ self->buffer_read_in = 0;
+ self->buffer_filled_in = recv(self->socket, (char *)self->buffer_in, globals.socket_buffersize, 0);\r
+
+ mylog("read %d, global_socket_buffersize=%d\n", self->buffer_filled_in, globals.socket_buffersize);
+
+ if (self->buffer_filled_in == -1) {
+ self->errornumber = SOCKET_READ_ERROR;
+ self->errormsg = "Error while reading from the socket.";
+ self->buffer_filled_in = 0;
+ }
+ if (self->buffer_filled_in == 0) {
+ self->errornumber = SOCKET_CLOSED;
+ self->errormsg = "Socket has been closed.";
+ self->buffer_filled_in = 0;
+ }
+
+ }
+ return self->buffer_in[self->buffer_read_in++];
+}
+
+void
+SOCK_put_next_byte(SocketClass *self, unsigned char next_byte)
+{
+int bytes_sent;
+
+ self->buffer_out[self->buffer_filled_out++] = next_byte;
+
+ if (self->buffer_filled_out == globals.socket_buffersize) {
+ // buffer is full, so write it out
+ bytes_sent = send(self->socket, (char *)self->buffer_out, globals.socket_buffersize, 0);
+ if (bytes_sent != globals.socket_buffersize) {
+ self->errornumber = SOCKET_WRITE_ERROR;
+ self->errormsg = "Error while writing to the socket.";
+ }
+ self->buffer_filled_out = 0;
+ }
+}
--- /dev/null
+\r
+/* File: socket.h\r
+ *\r
+ * Description: See "socket.c"\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __SOCKET_H__
+#define __SOCKET_H__
+
+#include
+#include "psqlodbc.h"
+
+#define SOCKET_ALREADY_CONNECTED 1
+#define SOCKET_HOST_NOT_FOUND 2
+#define SOCKET_COULD_NOT_CREATE_SOCKET 3
+#define SOCKET_COULD_NOT_CONNECT 4
+#define SOCKET_READ_ERROR 5
+#define SOCKET_WRITE_ERROR 6
+#define SOCKET_NULLPOINTER_PARAMETER 7
+#define SOCKET_PUT_INT_WRONG_LENGTH 8
+#define SOCKET_GET_INT_WRONG_LENGTH 9
+#define SOCKET_CLOSED 10
+
+
+struct SocketClass_ {
+
+ int buffer_filled_in;
+ int buffer_filled_out;
+ int buffer_read_in;
+ unsigned char *buffer_in;
+ unsigned char *buffer_out;
+
+ SOCKET socket;
+
+ char *errormsg;
+ int errornumber;\r
+\r
+ char reverse; /* used to handle Postgres 6.2 protocol (reverse byte order) */
+
+};
+
+#define SOCK_get_char(self) (SOCK_get_next_byte(self))
+#define SOCK_put_char(self, c) (SOCK_put_next_byte(self, c))
+
+
+/* error functions */
+#define SOCK_get_errcode(self) (self->errornumber)
+#define SOCK_get_errmsg(self) (self->errormsg)
+\r
+
+/* Socket prototypes */\r
+SocketClass *SOCK_Constructor();
+void SOCK_Destructor(SocketClass *self);
+char SOCK_connect_to(SocketClass *self, unsigned short port, char *hostname);
+void SOCK_get_n_char(SocketClass *self, char *buffer, int len);
+void SOCK_put_n_char(SocketClass *self, char *buffer, int len);
+void SOCK_get_string(SocketClass *self, char *buffer, int bufsize);
+void SOCK_put_string(SocketClass *self, char *string);
+int SOCK_get_int(SocketClass *self, short len);
+void SOCK_put_int(SocketClass *self, int value, short len);
+void SOCK_flush_output(SocketClass *self);
+unsigned char SOCK_get_next_byte(SocketClass *self);
+void SOCK_put_next_byte(SocketClass *self, unsigned char next_byte);
+void SOCK_clear_error(SocketClass *self);
+
+#endif
--- /dev/null
+\r
+/* Module: statement.c\r
+ *\r
+ * Description: This module contains functions related to creating\r
+ * and manipulating a statement.\r
+ *\r
+ * Classes: StatementClass (Functions prefix: "SC_")\r
+ *\r
+ * API functions: SQLAllocStmt, SQLFreeStmt\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#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;\r
+\r
+
+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) {\r
+ 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;\r
+ rv->stmt_with_params[0] = '\0';\r
+ 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;\r
+ rv->current_exec_param = -1;\r
+ rv->put_data = FALSE;\r
+ }
+ 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);
+\r
+ SC_free_params(self, STMT_FREE_PARAMS_ALL);\r
+\r
+ /* 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;
+}
+\r
+/* Free parameters and free the memory from the \r
+ data-at-execution parameters that was allocated in SQLPutData.\r
+*/\r
+void\r
+SC_free_params(StatementClass *self, char option)\r
+{\r
+int i;\r
+\r
+ if( ! self->parameters)\r
+ return;\r
+\r
+ for (i = 0; i < self->parameters_allocated; i++) {\r
+ if (self->parameters[i].data_at_exec == TRUE) {\r
+\r
+ if (self->parameters[i].EXEC_used) {\r
+ free(self->parameters[i].EXEC_used);\r
+ self->parameters[i].EXEC_used = NULL;\r
+ }\r
+\r
+ if (self->parameters[i].EXEC_buffer) {\r
+ free(self->parameters[i].EXEC_buffer);\r
+ self->parameters[i].EXEC_buffer = NULL;\r
+ }\r
+ }\r
+ }\r
+ self->data_at_exec = -1;\r
+ self->current_exec_param = -1;\r
+ self->put_data = FALSE;\r
+\r
+ if (option == STMT_FREE_PARAMS_ALL) {\r
+ free(self->parameters);\r
+ self->parameters = NULL;\r
+ self->parameters_allocated = 0;\r
+ }\r
+}\r
+
+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;
+\r
+ // Free any data at exec params before the statement is executed\r
+ // again. If not, then there will be a memory leak when\r
+ // the next SQLParamData/SQLPutData is called.\r
+ SC_free_params(self, STMT_FREE_PARAMS_DATA_AT_EXEC_ONLY);\r
+
+ 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)\r
+{\r
+ConnectionClass *conn;\r
+QResultClass *res;\r
+char ok, was_ok, was_nonfatal;\r
+Int2 oldstatus, numcols;\r
+\r
+\r
+ conn = SC_get_conn(self);\r
+\r
+ /* Begin a transaction if one is not already in progress */\r
+ /* The reason is because we can't use declare/fetch cursors without\r
+ starting a transaction first.\r
+ */\r
+\r
+ if ( ! CC_is_in_trans(conn)) {\r
+ mylog(" about to begin a transaction on statement = %u\n", self);\r
+ res = CC_send_query(conn, "BEGIN", NULL, NULL);\r
+ if ( ! res) {\r
+ self->errormsg = "Could not begin a transaction";\r
+ self->errornumber = STMT_EXEC_ERROR;\r
+ return SQL_ERROR;\r
+ }\r
+ \r
+ ok = QR_command_successful(res); \r
+ \r
+ mylog("SQLExecute: ok = %d, status = %d\n", ok, QR_get_status(res));\r
+ \r
+ QR_Destructor(res);\r
+ \r
+ if (!ok) {\r
+ self->errormsg = "Could not begin a transaction";\r
+ self->errornumber = STMT_EXEC_ERROR;\r
+ return SQL_ERROR;\r
+ }\r
+ else\r
+ CC_set_in_trans(conn);\r
+ }\r
+\r
+\r
+\r
+ oldstatus = conn->status;\r
+ conn->status = CONN_EXECUTING;\r
+ self->status = STMT_EXECUTING;\r
+\r
+\r
+ // If its a SELECT statement, use a cursor.\r
+ // Note that the declare cursor has already been prepended to the statement\r
+ // in copy_statement...\r
+ if (self->statement_type == STMT_TYPE_SELECT) {\r
+\r
+ char cursor[32];\r
+ char fetch[64];\r
+\r
+ sprintf(cursor, "C%u", self);\r
+\r
+ mylog(" Sending SELECT statement on stmt=%u\n", self);\r
+\r
+ /* send the declare/select */\r
+ self->result = CC_send_query(conn, self->stmt_with_params, NULL, NULL);\r
+ if (self->result != NULL) {\r
+ /* That worked, so now send the fetch to start getting data back */\r
+ sprintf(fetch, "fetch %d in %s", globals.fetch_max, cursor);\r
+ \r
+ // Save the cursor in the result for later use\r
+ self->result = CC_send_query( conn, fetch, NULL, cursor);\r
+ }\r
+\r
+ mylog(" done sending the query:\n");\r
+ \r
+ }\r
+ else { // not a SELECT statement so don't use a cursor \r
+ mylog(" its NOT a select statement: stmt=%u\n", self);\r
+ self->result = CC_send_query(conn, self->stmt_with_params, NULL, NULL);\r
+ \r
+ // If we are in autocommit, we must send the commit.\r
+ if (CC_is_in_autocommit(conn)) {\r
+ CC_send_query(conn, "COMMIT", NULL, NULL);\r
+ CC_set_no_trans(conn);\r
+ }\r
+ \r
+ }\r
+\r
+ conn->status = oldstatus;\r
+ self->status = STMT_FINISHED;\r
+\r
+ /* Check the status of the result */\r
+ if (self->result) {\r
+\r
+ was_ok = QR_command_successful(self->result);\r
+ was_nonfatal = QR_command_nonfatal(self->result);\r
+ \r
+ if ( was_ok)\r
+ self->errornumber = STMT_OK;\r
+ else\r
+ self->errornumber = was_nonfatal ? STMT_INFO_ONLY : STMT_ERROR_TAKEN_FROM_BACKEND;\r
+ \r
+ self->currTuple = -1; /* set cursor before the first tuple in the list */\r
+ \r
+ /* see if the query did return any result columns */\r
+ numcols = QR_NumResultCols(self->result);\r
+ \r
+ /* now allocate the array to hold the binding info */\r
+ if (numcols > 0) {\r
+ extend_bindings(self, numcols);\r
+ if (self->bindings == NULL) {\r
+ self->errornumber = STMT_NO_MEMORY_ERROR;\r
+ self->errormsg = "Could not get enough free memory to store the binding information";\r
+ return SQL_ERROR;\r
+ }\r
+ }\r
+ \r
+ } else { /* Bad Error -- The error message will be in the Connection */\r
+ \r
+ self->errornumber = STMT_EXEC_ERROR;\r
+ self->errormsg = "Error while executing the query";\r
+\r
+ CC_abort(conn);\r
+ }\r
+\r
+ if (self->errornumber == STMT_OK)\r
+ return SQL_SUCCESS;\r
+\r
+ else if (self->errornumber == STMT_INFO_ONLY)\r
+ return SQL_SUCCESS_WITH_INFO;\r
+\r
+ else \r
+ return SQL_ERROR;\r
+}\r
--- /dev/null
+\r
+/* File: statement.h\r
+ *\r
+ * Description: See "statement.c"\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __STATEMENT_H__
+#define __STATEMENT_H__
+
+#include
+#include
+#include "psqlodbc.h"
+
+typedef enum {
+ STMT_ALLOCATED, /* The statement handle is allocated, but not used so far */
+ STMT_READY, /* the statement is waiting to be executed */
+ STMT_PREMATURE, /* ODBC states that it is legal to call e.g. SQLDescribeCol before
+ a call to SQLExecute, but after SQLPrepare. To get all the necessary
+ information in such a case, we simply execute the query _before_ the
+ actual call to SQLExecute, so that statement is considered to be "premature".
+ */
+ STMT_FINISHED, /* statement execution has finished */
+ STMT_EXECUTING /* statement execution is still going on */
+} STMT_Status;
+
+#define STMT_TRUNCATED -2
+#define STMT_INFO_ONLY -1 /* not an error message, just a notification to be returned by SQLError */
+#define STMT_OK 0 /* will be interpreted as "no error pending" */
+#define STMT_EXEC_ERROR 1
+#define STMT_STATUS_ERROR 2
+#define STMT_SEQUENCE_ERROR 3
+#define STMT_NO_MEMORY_ERROR 4
+#define STMT_COLNUM_ERROR 5
+#define STMT_NO_STMTSTRING 6
+#define STMT_ERROR_TAKEN_FROM_BACKEND 7
+#define STMT_INTERNAL_ERROR 8
+#define STMT_STILL_EXECUTING 9
+#define STMT_NOT_IMPLEMENTED_ERROR 10
+#define STMT_BAD_PARAMETER_NUMBER_ERROR 11
+#define STMT_OPTION_OUT_OF_RANGE_ERROR 12
+#define STMT_INVALID_COLUMN_NUMBER_ERROR 13
+#define STMT_RESTRICTED_DATA_TYPE_ERROR 14
+#define STMT_INVALID_CURSOR_STATE_ERROR 15
+#define STMT_OPTION_VALUE_CHANGED 16
+
+
+/* statement types */
+#define STMT_TYPE_SELECT 0
+#define STMT_TYPE_INSERT 1
+#define STMT_TYPE_UPDATE 2
+#define STMT_TYPE_DELETE 3
+#define STMT_TYPE_OTHER 4
+#define STMT_TYPE_UNKNOWN 666 // 'unknown' means we don't have the statement yet,
+ // or haven't looked at it to see what type it is.
+ // 'other' means we looked, but couldn't tell.
+
+
+/******** Statement Handle ***********/
+struct StatementClass_ {
+ ConnectionClass *hdbc; /* pointer to ConnectionClass this statement belongs to */
+
+ QResultClass *result; /* result of the current statement */
+
+ STMT_Status status;
+ char *errormsg;
+ int errornumber;
+ int maxRows;
+
+ /* information on bindings */
+ BindInfoClass *bindings; /* array to store the binding information */
+ int bindings_allocated;
+
+ /* information on statement parameters */
+ int parameters_allocated;
+ ParameterInfoClass *parameters;
+
+ Int4 currTuple;
+
+ char *statement; /* if non--null pointer to the SQL statement that has been executed */
+\r
+ int statement_type; /* According to the defines above */
+ int data_at_exec; /* Number of params needing SQLPutData */\r
+ int current_exec_param; /* The current parameter for SQLPutData */\r
+\r
+ char put_data; /* Has SQLPutData been called yet? */\r
+
+ char errormsg_created; /* has an informative error msg been created? */
+ char manual_result; /* Is the statement result manually built? */
+ char prepare; /* is this statement a prepared statement or direct */\r
+\r
+ char stmt_with_params[65536 /* MAX_STATEMENT_LEN */]; /* statement after parameter substitution */\r
+
+};
+
+#define SC_get_conn(a) (a->hdbc)
+#define SC_get_Result(a) (a->result);
+\r
+/* options for SC_free_params() */\r
+#define STMT_FREE_PARAMS_ALL 0\r
+#define STMT_FREE_PARAMS_DATA_AT_EXEC_ONLY 1\r
+\r
+/* Statement prototypes */
+StatementClass *SC_Constructor();
+char SC_Destructor(StatementClass *self);
+int statement_type(char *statement);
+void SC_pre_execute(StatementClass *self);
+char SC_unbind_cols(StatementClass *self);
+char SC_recycle_statement(StatementClass *self);
+
+void SC_clear_error(StatementClass *self);
+char SC_get_error(StatementClass *self, int *number, char **message);
+char *SC_create_errormsg(StatementClass *self);
+RETCODE SC_execute(StatementClass *stmt);\r
+void SC_free_params(StatementClass *self, char option);\r
+
+#endif
--- /dev/null
+\r
+/* Module: tuple.c\r
+ *\r
+ * Description: This module contains functions for setting the data for individual\r
+ * fields (TupleField structure) of a manual result set.\r
+ *\r
+ * Important Note: These functions are ONLY used in building manual result sets for \r
+ * info functions (SQLTables, SQLColumns, etc.)\r
+ *\r
+ * Classes: n/a\r
+ *\r
+ * API functions: none\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include "tuple.h"
+#include
+#include
+
+void set_tuplefield_null(TupleField *tuple_field)
+{
+ tuple_field->len = 0;
+ tuple_field->value = strdup("");
+}
+
+void set_tuplefield_string(TupleField *tuple_field, char *string)
+{
+ tuple_field->len = strlen(string);
+ tuple_field->value = malloc(strlen(string)+1);
+ strcpy(tuple_field->value, string);
+}
+
+
+void set_tuplefield_int2(TupleField *tuple_field, Int2 value)
+{
+char buffer[10];
+
+ sprintf(buffer,"%d", value);
+
+ tuple_field->len = strlen(buffer)+1;
+ /* +1 ... is this correct (better be on the save side-...) */
+ tuple_field->value = strdup(buffer);
+}
+
+void set_tuplefield_int4(TupleField *tuple_field, Int4 value)
+{
+char buffer[15];
+
+ sprintf(buffer,"%ld", value);
+
+ tuple_field->len = strlen(buffer)+1;
+ /* +1 ... is this correct (better be on the save side-...) */
+ tuple_field->value = strdup(buffer);
+}
--- /dev/null
+\r
+/* File: tuple.h\r
+ *\r
+ * Description: See "tuple.c"\r
+ *\r
+ * Important NOTE: The TupleField structure is used both to hold backend data and\r
+ * manual result set data. The "set_" functions and the TupleNode\r
+ * structure are only used for manual result sets by info routines. \r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __TUPLE_H__
+#define __TUPLE_H__
+
+#include "psqlodbc.h"
+\r
+/* Used by backend data AND manual result sets */
+struct TupleField_ {
+ Int4 len; /* length of the current Tuple */
+ void *value; /* an array representing the value */
+};
+\r
+/* Used ONLY for manual result sets */
+struct TupleNode_ {
+ struct TupleNode_ *prev, *next;
+ TupleField tuple[1];
+};
+
+/* These macros are wrappers for the corresponding set_tuplefield functions
+ but these handle automatic NULL determination and call set_tuplefield_null()
+ if appropriate for the datatype (used by SQLGetTypeInfo).
+*/
+#define set_nullfield_string(FLD, VAL) (VAL ? set_tuplefield_string(FLD, VAL) : set_tuplefield_null(FLD))
+#define set_nullfield_int2(FLD, VAL) (VAL != -1 ? set_tuplefield_int2(FLD, VAL) : set_tuplefield_null(FLD))
+#define set_nullfield_int4(FLD, VAL) (VAL != -1 ? set_tuplefield_int4(FLD, VAL) : set_tuplefield_null(FLD))
+
+void set_tuplefield_null(TupleField *tuple_field);
+void set_tuplefield_string(TupleField *tuple_field, char *string);
+void set_tuplefield_int2(TupleField *tuple_field, Int2 value);
+void set_tuplefield_int4(TupleField *tuple_field, Int4 value);
+
+#endif
--- /dev/null
+\r
+/* Module: tuplelist.c\r
+ *\r
+ * Description: This module contains functions for creating a manual result set\r
+ * (the TupleList) and retrieving data from it for a specific row/column.\r
+ *\r
+ * Classes: TupleListClass (Functions prefix: "TL_")\r
+ *\r
+ * API functions: none\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#include
+#include
+#include "tuplelist.h"
+#include "tuple.h"
+
+TupleListClass *
+TL_Constructor(UInt4 fieldcnt)
+{
+TupleListClass *rv;
+\r
+ mylog("in TL_Constructor\n");\r
+
+ rv = (TupleListClass *) malloc(sizeof(TupleListClass));
+ if (rv) {
+
+ rv->num_fields = fieldcnt;
+ rv->num_tuples = 0;
+ rv->list_start = NULL;
+ rv->list_end = NULL;
+ rv->lastref = NULL;
+ rv->last_indexed = -1;
+ }
+
+ mylog("exit TL_Constructor\n");\r
+\r
+ return rv;
+}
+
+void
+TL_Destructor(TupleListClass *self)
+{
+int lf;
+TupleNode *node, *tp;
+
+ mylog("TupleList: in DESTRUCTOR\n");\r
+\r
+ node = self->list_start;
+ while(node != NULL) {
+ for (lf=0; lf < self->num_fields; lf++)
+ if (node->tuple[lf].value != NULL) {
+ free(node->tuple[lf].value);
+ }
+ tp = node->next;
+ free(node);
+ node = tp;
+ }\r
+\r
+ free(self);\r
+
+ mylog("TupleList: exit DESTRUCTOR\n");\r
+}
+
+
+void *
+TL_get_fieldval(TupleListClass *self, Int4 tupleno, Int2 fieldno)
+{
+Int4 lf;
+Int4 delta, from_end;
+char end_is_closer, start_is_closer;
+TupleNode *rv;
+
+ if (self->last_indexed == -1)
+ /* we have an empty tuple list */
+ return NULL;
+
+ /* some more sanity checks */
+ if ((tupleno >= self->num_tuples) || (tupleno < 0))
+ /* illegal tuple number range */
+ return NULL;
+
+ if ((fieldno >= self->num_fields) || (fieldno < 0))
+ /* illegel field number range */
+ return NULL;
+
+ /* check if we are accessing the same tuple that was used in
+ the last fetch (e.g: for fetching all the fields one after
+ another. Do this to speed things up
+ */
+ if (tupleno == self->last_indexed)
+ return self->lastref->tuple[fieldno].value;
+
+ /* now for the tricky part... */
+
+ /*
+ Since random access is quite inefficient for linked lists we use
+ the lastref pointer that points to the last element referenced
+ by a get_fieldval() call in conjunction with the its index number
+ that is stored in last_indexed. (So we use some locality of
+ reference principle to speed things up)
+ */
+
+ delta = tupleno - self->last_indexed;
+ /* if delta is positive, we have to go forward */
+
+ /* now check if we are closer to the start or the end of the list
+ than to our last_indexed pointer
+ */
+ from_end = (self->num_tuples - 1) - tupleno;
+
+ start_is_closer = labs(delta) > tupleno;
+ /* true if we are closer to the start of the list than to the
+ last_indexed pointer
+ */
+
+ end_is_closer = labs(delta) > from_end;
+ /* true if we are closer at the end of the list */
+
+ if (end_is_closer) {
+ /* scanning from the end is the shortest way. so we do that... */
+ rv = self->list_end;
+ for (lf=0; lf < from_end; lf++)
+ rv = rv->prev;
+ } else if (start_is_closer) {
+ /* the shortest way is to start the search from the head of the list */
+ rv = self->list_start;
+ for (lf=0; lf < tupleno; lf++)
+ rv = rv->next;
+ } else {
+ /* the closest way is starting from our lastref - pointer */
+ rv = self->lastref;
+ /* at first determine whether we have to search forward or backwards */
+ if (delta < 0) {
+ /* we have to search backwards */
+ for(lf=0; lf < (-1)*delta; lf++)
+ rv = rv->prev;
+ } else {
+ /* ok, we have to search forward... */
+ for (lf=0; lf < delta; lf++)
+ rv = rv->next;
+ }
+ }
+
+ /* now we have got our return pointer, so update the lastref
+ and the last_indexed values
+ */
+ self->lastref = rv;
+ self->last_indexed = tupleno;
+
+ return rv->tuple[fieldno].value;
+}
+
+
+
+char
+TL_add_tuple(TupleListClass *self, TupleNode *new_field)
+{
+ /* we append the tuple at the end of the doubly linked list
+ of the tuples we have already read in
+ */
+
+ new_field->prev = NULL;
+ new_field->next = NULL;
+
+ if (self->list_start == NULL) {
+ /* the list is empty, we have to add the first tuple */
+ self->list_start = new_field;
+ self->list_end = new_field;
+ self->lastref = new_field;
+ self->last_indexed = 0;
+ } else {
+ /* there is already an element in the list, so add the new
+ one at the end of the list
+ */
+ self->list_end->next = new_field;
+ new_field->prev = self->list_end;
+ self->list_end = new_field;
+ }
+ self->num_tuples++;
+
+ /* this method of building a list cannot fail, so we return 1 */
+ return 1;
+}
+
+
--- /dev/null
+\r
+/* File: tuplelist.h\r
+ *\r
+ * Description: See "tuplelist.c"\r
+ *\r
+ * Important Note: This structure and its functions are ONLY used in building manual result\r
+ * sets for info functions (SQLTables, SQLColumns, etc.)\r
+ *\r
+ * Comments: See "notice.txt" for copyright and license information.\r
+ *\r
+ */\r
+
+#ifndef __TUPLELIST_H__
+#define __TUPLELIST_H__
+
+#include "psqlodbc.h"
+
+struct TupleListClass_ {
+ Int4 num_fields;
+ Int4 num_tuples;
+ TupleNode *list_start, *list_end, *lastref;
+ Int4 last_indexed;
+};
+
+#define TL_get_num_tuples(x) (x->num_tuples)
+
+/* Create a TupleList. Each tuple consits of fieldcnt columns */
+TupleListClass *TL_Constructor(UInt4 fieldcnt);
+void TL_Destructor(TupleListClass *self);
+void *TL_get_fieldval(TupleListClass *self, Int4 tupleno, Int2 fieldno);
+char TL_add_tuple(TupleListClass *self, TupleNode *new_field);
+
+#endif