Replace old PostODBC driver with new one...

This one is based on an older PostODBC driver, rewritten and maintained by
InsightDist(?)
This commit is contained in:
Marc G. Fournier 1998-04-13 15:02:05 +00:00
parent d4d26f9c33
commit 9e3382dfc5
37 changed files with 12152 additions and 0 deletions

340
src/interfaces/odbc/bind.c Normal file
View File

@ -0,0 +1,340 @@
/* Module: bind.c
*
* Description: This module contains routines related to binding
* columns and parameters.
*
* Classes: BindInfoClass, ParameterInfoClass
*
* API functions: SQLBindParameter, SQLBindCol, SQLDescribeParam, SQLNumParams,
* SQLParamOptions(NI)
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include "bind.h"
#include "environ.h"
#include "statement.h"
#include "qresult.h"
#include "pgtypes.h"
#include <stdlib.h>
#include <malloc.h>
#include <sql.h>
#include <sqlext.h>
// Bind parameters on a statement handle
RETCODE SQL_API SQLBindParameter(
HSTMT hstmt,
UWORD ipar,
SWORD fParamType,
SWORD fCType,
SWORD fSqlType,
UDWORD cbColDef,
SWORD ibScale,
PTR rgbValue,
SDWORD cbValueMax,
SDWORD FAR *pcbValue)
{
StatementClass *stmt = (StatementClass *) hstmt;
if( ! stmt)
return SQL_INVALID_HANDLE;
if(stmt->parameters_allocated < ipar) {
ParameterInfoClass *old_parameters;
int i, old_parameters_allocated;
old_parameters = stmt->parameters;
old_parameters_allocated = stmt->parameters_allocated;
stmt->parameters = (ParameterInfoClass *) malloc(sizeof(ParameterInfoClass)*(ipar));
if ( ! stmt->parameters) {
stmt->errornumber = STMT_NO_MEMORY_ERROR;
stmt->errormsg = "Could not allocate memory for statement parameters";
return SQL_ERROR;
}
stmt->parameters_allocated = ipar;
// copy the old parameters over
for(i = 0; i < old_parameters_allocated; i++) {
// a structure copy should work
stmt->parameters[i] = old_parameters[i];
}
// get rid of the old parameters, if there were any
if(old_parameters)
free(old_parameters);
// zero out the newly allocated parameters (in case they skipped some,
// so we don't accidentally try to use them later)
for(; i < stmt->parameters_allocated; i++) {
stmt->parameters[i].buflen = 0;
stmt->parameters[i].buffer = 0;
stmt->parameters[i].used = 0;
stmt->parameters[i].paramType = 0;
stmt->parameters[i].CType = 0;
stmt->parameters[i].SQLType = 0;
stmt->parameters[i].precision = 0;
stmt->parameters[i].scale = 0;
stmt->parameters[i].data_at_exec = FALSE;
stmt->parameters[i].EXEC_used = NULL;
stmt->parameters[i].EXEC_buffer = NULL;
}
}
ipar--; /* use zero based column numbers for the below part */
// store the given info
stmt->parameters[ipar].buflen = cbValueMax;
stmt->parameters[ipar].buffer = rgbValue;
stmt->parameters[ipar].used = pcbValue;
stmt->parameters[ipar].paramType = fParamType;
stmt->parameters[ipar].CType = fCType;
stmt->parameters[ipar].SQLType = fSqlType;
stmt->parameters[ipar].precision = cbColDef;
stmt->parameters[ipar].scale = ibScale;
/* If rebinding a parameter that had data-at-exec stuff in it,
then free that stuff
*/
if (stmt->parameters[ipar].EXEC_used) {
free(stmt->parameters[ipar].EXEC_used);
stmt->parameters[ipar].EXEC_used = NULL;
}
if (stmt->parameters[ipar].EXEC_buffer) {
free(stmt->parameters[ipar].EXEC_buffer);
stmt->parameters[ipar].EXEC_buffer = NULL;
}
if (pcbValue && *pcbValue <= SQL_LEN_DATA_AT_EXEC_OFFSET)
stmt->parameters[ipar].data_at_exec = TRUE;
else
stmt->parameters[ipar].data_at_exec = FALSE;
return SQL_SUCCESS;
}
// - - - - - - - - -
// Associate a user-supplied buffer with a database column.
RETCODE SQL_API SQLBindCol(
HSTMT hstmt,
UWORD icol,
SWORD fCType,
PTR rgbValue,
SDWORD cbValueMax,
SDWORD FAR *pcbValue)
{
StatementClass *stmt = (StatementClass *) hstmt;
Int2 numcols;
mylog("**** SQLBindCol: stmt = %u, icol = %d\n", stmt, icol);
if ( ! stmt)
return SQL_INVALID_HANDLE;
if (icol < 1) {
/* currently we do not support bookmarks */
stmt->errormsg = "Bookmarks are not currently supported.";
stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR;
return SQL_ERROR;
}
icol--; /* use zero based col numbers */
SC_clear_error(stmt);
if( ! stmt->result) {
stmt->errormsg = "Can't bind columns with a NULL query result structure.";
stmt->errornumber = STMT_SEQUENCE_ERROR;
return SQL_ERROR;
}
if( stmt->status == STMT_EXECUTING) {
stmt->errormsg = "Can't bind columns while statement is still executing.";
stmt->errornumber = STMT_SEQUENCE_ERROR;
return SQL_ERROR;
}
numcols = QR_NumResultCols(stmt->result);
mylog("SQLBindCol: numcols = %d\n", numcols);
if (icol >= numcols) {
stmt->errornumber = STMT_COLNUM_ERROR;
stmt->errormsg = "Column number too big";
return SQL_ERROR;
}
if ( ! stmt->bindings) {
stmt->errormsg = "Bindings were not allocated properly.";
stmt->errornumber = STMT_SEQUENCE_ERROR;
return SQL_ERROR;
}
if ((cbValueMax == 0) || (rgbValue == NULL)) {
/* we have to unbind the column */
stmt->bindings[icol].buflen = 0;
stmt->bindings[icol].buffer = NULL;
stmt->bindings[icol].used = NULL;
stmt->bindings[icol].returntype = SQL_C_CHAR;
} else {
/* ok, bind that column */
stmt->bindings[icol].buflen = cbValueMax;
stmt->bindings[icol].buffer = rgbValue;
stmt->bindings[icol].used = pcbValue;
stmt->bindings[icol].returntype = fCType;
}
return SQL_SUCCESS;
}
// - - - - - - - - -
// Returns the description of a parameter marker.
RETCODE SQL_API SQLDescribeParam(
HSTMT hstmt,
UWORD ipar,
SWORD FAR *pfSqlType,
UDWORD FAR *pcbColDef,
SWORD FAR *pibScale,
SWORD FAR *pfNullable)
{
StatementClass *stmt = (StatementClass *) hstmt;
if( ! stmt)
return SQL_INVALID_HANDLE;
if( (ipar < 1) || (ipar > stmt->parameters_allocated) ) {
stmt->errormsg = "Invalid parameter number for SQLDescribeParam.";
stmt->errornumber = STMT_BAD_PARAMETER_NUMBER_ERROR;
return SQL_ERROR;
}
ipar--;
if(pfSqlType)
*pfSqlType = stmt->parameters[ipar].SQLType;
if(pcbColDef)
*pcbColDef = stmt->parameters[ipar].precision;
if(pibScale)
*pibScale = stmt->parameters[ipar].scale;
if(pfNullable)
*pfNullable = pgtype_nullable(stmt->parameters[ipar].paramType);
return SQL_SUCCESS;
}
// - - - - - - - - -
// Sets multiple values (arrays) for the set of parameter markers.
RETCODE SQL_API SQLParamOptions(
HSTMT hstmt,
UDWORD crow,
UDWORD FAR *pirow)
{
return SQL_ERROR;
}
// - - - - - - - - -
// Returns the number of parameter markers.
RETCODE SQL_API SQLNumParams(
HSTMT hstmt,
SWORD FAR *pcpar)
{
StatementClass *stmt = (StatementClass *) hstmt;
unsigned int i;
// I guess this is the number of actual parameter markers
// in the statement, not the number of parameters that are bound.
// why does this have to be driver-specific?
if(!stmt)
return SQL_INVALID_HANDLE;
if(!stmt->statement) {
// no statement has been allocated
*pcpar = 0;
stmt->errormsg = "SQLNumParams called with no statement ready.";
stmt->errornumber = STMT_SEQUENCE_ERROR;
return SQL_ERROR;
} else {
*pcpar = 0;
for(i=0; i < strlen(stmt->statement); i++) {
if(stmt->statement[i] == '?')
(*pcpar)++;
}
return SQL_SUCCESS;
}
}
/********************************************************************
* Bindings Implementation
*/
BindInfoClass *
create_empty_bindings(int num_columns)
{
BindInfoClass *new_bindings;
int i;
new_bindings = (BindInfoClass *)malloc(num_columns * sizeof(BindInfoClass));
if(!new_bindings) {
return 0;
}
for(i=0; i < num_columns; i++) {
new_bindings[i].buflen = 0;
new_bindings[i].buffer = NULL;
new_bindings[i].used = NULL;
}
return new_bindings;
}
void
extend_bindings(StatementClass *stmt, int num_columns)
{
BindInfoClass *new_bindings;
int i;
mylog("in extend_bindings\n");
/* if we have too few, allocate room for more, and copy the old */
/* entries into the new structure */
if(stmt->bindings_allocated < num_columns) {
new_bindings = create_empty_bindings(num_columns);
if(stmt->bindings) {
for(i=0; i<stmt->bindings_allocated; i++)
new_bindings[i] = stmt->bindings[i];
free(stmt->bindings);
}
stmt->bindings = new_bindings; // null indicates error
} else {
/* if we have too many, make sure the extra ones are emptied out */
/* so we don't accidentally try to use them for anything */
for(i = num_columns; i < stmt->bindings_allocated; i++) {
stmt->bindings[i].buflen = 0;
stmt->bindings[i].buffer = NULL;
stmt->bindings[i].used = NULL;
}
}
mylog("exit extend_bindings\n");
}

View File

@ -0,0 +1,45 @@
/* File: bind.h
*
* Description: See "bind.c"
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#ifndef __BIND_H__
#define __BIND_H__
#include "psqlodbc.h"
/*
* BindInfoClass -- stores information about a bound column
*/
struct BindInfoClass_ {
Int4 buflen; /* size of buffer */
char *buffer; /* pointer to the buffer */
Int4 *used; /* used space in the buffer (for strings not counting the '\0') */
Int2 returntype; /* kind of conversion to be applied when returning (SQL_C_DEFAULT, SQL_C_CHAR...) */
};
/*
* ParameterInfoClass -- stores information about a bound parameter
*/
struct ParameterInfoClass_ {
Int4 buflen;
char *buffer;
Int4 *used;
Int2 paramType;
Int2 CType;
Int2 SQLType;
UInt4 precision;
Int2 scale;
Int4 *EXEC_used;
char *EXEC_buffer;
char data_at_exec;
};
BindInfoClass *create_empty_bindings(int num_columns);
void extend_bindings(StatementClass *stmt, int num_columns);
#endif

View File

@ -0,0 +1,159 @@
/* Module: columninfo.c
*
* Description: This module contains routines related to
* reading and storing the field information from a query.
*
* Classes: ColumnInfoClass (Functions prefix: "CI_")
*
* API functions: none
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include "columninfo.h"
#include "socket.h"
#include <stdlib.h>
#include <malloc.h>
ColumnInfoClass *
CI_Constructor()
{
ColumnInfoClass *rv;
rv = (ColumnInfoClass *) malloc(sizeof(ColumnInfoClass));
if (rv) {
rv->num_fields = 0;
rv->name = NULL;
rv->adtid = NULL;
rv->adtsize = NULL;
}
return rv;
}
void
CI_Destructor(ColumnInfoClass *self)
{
CI_free_memory(self);
free(self);
}
/* Read in field descriptions.
If self is not null, then also store the information.
If self is null, then just read, don't store.
*/
char
CI_read_fields(ColumnInfoClass *self, SocketClass *sock)
{
Int2 lf;
int new_num_fields;
Oid new_adtid;
Int2 new_adtsize;
char new_field_name[MAX_MESSAGE_LEN+1];
/* at first read in the number of fields that are in the query */
new_num_fields = (Int2) SOCK_get_int(sock, sizeof(Int2));
mylog("num_fields = %d\n", new_num_fields);
if (self) { /* according to that allocate memory */
CI_set_num_fields(self, new_num_fields);
}
/* now read in the descriptions */
for(lf = 0; lf < new_num_fields; lf++) {
SOCK_get_string(sock, new_field_name, MAX_MESSAGE_LEN);
new_adtid = (Oid) SOCK_get_int(sock, 4);
new_adtsize = (Int2) SOCK_get_int(sock, 2);
mylog("CI_read_fields: fieldname='%s', adtid=%d, adtsize=%d\n", new_field_name, new_adtid, new_adtsize);
if (self)
CI_set_field_info(self, lf, new_field_name, new_adtid, new_adtsize);
}
return (SOCK_get_errcode(sock) == 0);
}
void
CI_free_memory(ColumnInfoClass *self)
{
register Int2 lf;
int num_fields = self->num_fields;
for (lf = 0; lf < num_fields; lf++) {
if( self->name[lf])
free (self->name[lf]);
}
/* Safe to call even if null */
free(self->name);
free(self->adtid);
free(self->adtsize);
}
void
CI_set_num_fields(ColumnInfoClass *self, int new_num_fields)
{
CI_free_memory(self); /* always safe to call */
self->num_fields = new_num_fields;
self->name = (char **) malloc (sizeof(char *) * self->num_fields);
self->adtid = (Oid *) malloc (sizeof(Oid) * self->num_fields);
self->adtsize = (Int2 *) malloc (sizeof(Int2) * self->num_fields);
}
void
CI_set_field_info(ColumnInfoClass *self, int field_num, char *new_name,
Oid new_adtid, Int2 new_adtsize)
{
// check bounds
if((field_num < 0) || (field_num >= self->num_fields)) {
return;
}
// store the info
self->name[field_num] = strdup(new_name);
self->adtid[field_num] = new_adtid;
self->adtsize[field_num] = new_adtsize;
}
char *
CI_get_fieldname(ColumnInfoClass *self, Int2 which)
{
char *rv = NULL;
if ( ! self->name)
return NULL;
if ((which >= 0) && (which < self->num_fields))
rv = self->name[which];
return rv;
}
Int2
CI_get_fieldsize(ColumnInfoClass *self, Int2 which)
{
Int2 rv = 0;
if ( ! self->adtsize)
return 0;
if ((which >= 0) && (which < self->num_fields))
rv = self->adtsize[which];
return rv;
}

View File

@ -0,0 +1,40 @@
/* File: columninfo.h
*
* Description: See "columninfo.c"
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#ifndef __COLUMNINFO_H__
#define __COLUMNINFO_H__
#include "psqlodbc.h"
struct ColumnInfoClass_ {
Int2 num_fields;
char **name; /* list of type names */
Oid *adtid; /* list of type ids */
Int2 *adtsize; /* list type sizes */
};
#define CI_get_num_fields(self) (self->num_fields)
#define CI_get_oid(self, col) (self->adtid[col])
ColumnInfoClass *CI_Constructor();
void CI_Destructor(ColumnInfoClass *self);
char CI_read_fields(ColumnInfoClass *self, SocketClass *sock);
/* functions for setting up the fields from within the program, */
/* without reading from a socket */
void CI_set_num_fields(ColumnInfoClass *self, int new_num_fields);
void CI_set_field_info(ColumnInfoClass *self, int field_num, char *new_name,
Oid new_adtid, Int2 new_adtsize);
char *CI_get_fieldname(ColumnInfoClass *self, Int2 which);
Int2 CI_get_fieldsize(ColumnInfoClass *self, Int2 which);
void CI_free_memory(ColumnInfoClass *self);
#endif

View File

@ -0,0 +1,979 @@
/* Module: connection.c
*
* Description: This module contains routines related to
* connecting to and disconnecting from the Postgres DBMS.
*
* Classes: ConnectionClass (Functions prefix: "CC_")
*
* API functions: SQLAllocConnect, SQLConnect, SQLDisconnect, SQLFreeConnect,
* SQLBrowseConnect(NI)
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include "environ.h"
#include "connection.h"
#include "socket.h"
#include "statement.h"
#include "qresult.h"
#include <stdio.h>
#include <odbcinst.h>
#define STMT_INCREMENT 16 /* how many statement holders to allocate at a time */
extern GLOBAL_VALUES globals;
RETCODE SQL_API SQLAllocConnect(
HENV henv,
HDBC FAR *phdbc)
{
EnvironmentClass *env = (EnvironmentClass *)henv;
ConnectionClass *conn;
conn = CC_Constructor();
mylog("**** SQLAllocConnect: henv = %u, conn = %u\n", henv, conn);
if( ! conn) {
env->errormsg = "Couldn't allocate memory for Connection object.";
env->errornumber = ENV_ALLOC_ERROR;
*phdbc = SQL_NULL_HDBC;
return SQL_ERROR;
}
if ( ! EN_add_connection(env, conn)) {
env->errormsg = "Maximum number of connections exceeded.";
env->errornumber = ENV_ALLOC_ERROR;
CC_Destructor(conn);
*phdbc = SQL_NULL_HDBC;
return SQL_ERROR;
}
*phdbc = (HDBC) conn;
return SQL_SUCCESS;
}
// - - - - - - - - -
RETCODE SQL_API SQLConnect(
HDBC hdbc,
UCHAR FAR *szDSN,
SWORD cbDSN,
UCHAR FAR *szUID,
SWORD cbUID,
UCHAR FAR *szAuthStr,
SWORD cbAuthStr)
{
ConnectionClass *conn = (ConnectionClass *) hdbc;
if ( ! conn)
return SQL_INVALID_HANDLE;
make_string(szDSN, cbDSN, conn->connInfo.dsn);
/* get the values for the DSN from the registry */
CC_DSN_info(conn, CONN_OVERWRITE);
/* override values from DSN info with UID and authStr(pwd)
This only occurs if the values are actually there.
*/
make_string(szUID, cbUID, conn->connInfo.username);
make_string(szAuthStr, cbAuthStr, conn->connInfo.password);
/* fill in any defaults */
CC_set_defaults(conn);
qlog("conn = %u, SQLConnect(DSN='%s', UID='%s', PWD='%s')\n", conn->connInfo.dsn, conn->connInfo.username, conn->connInfo.password);
if ( CC_connect(conn, FALSE) <= 0)
// Error messages are filled in
return SQL_ERROR;
return SQL_SUCCESS;
}
// - - - - - - - - -
RETCODE SQL_API SQLBrowseConnect(
HDBC hdbc,
UCHAR FAR *szConnStrIn,
SWORD cbConnStrIn,
UCHAR FAR *szConnStrOut,
SWORD cbConnStrOutMax,
SWORD FAR *pcbConnStrOut)
{
return SQL_SUCCESS;
}
// - - - - - - - - -
/* Drop any hstmts open on hdbc and disconnect from database */
RETCODE SQL_API SQLDisconnect(
HDBC hdbc)
{
ConnectionClass *conn = (ConnectionClass *) hdbc;
mylog("**** in SQLDisconnect\n");
if ( ! conn)
return SQL_INVALID_HANDLE;
qlog("conn=%u, SQLDisconnect\n", conn);
if (conn->status == CONN_EXECUTING) {
conn->errornumber = CONN_IN_USE;
conn->errormsg = "A transaction is currently being executed";
return SQL_ERROR;
}
mylog("SQLDisconnect: about to CC_cleanup\n");
/* Close the connection and free statements */
CC_cleanup(conn);
mylog("SQLDisconnect: done CC_cleanup\n");
mylog("exit SQLDisconnect\n");
return SQL_SUCCESS;
}
// - - - - - - - - -
RETCODE SQL_API SQLFreeConnect(
HDBC hdbc)
{
ConnectionClass *conn = (ConnectionClass *) hdbc;
mylog("**** in SQLFreeConnect: hdbc=%u\n", hdbc);
if ( ! conn)
return SQL_INVALID_HANDLE;
/* Remove the connection from the environment */
if ( ! EN_remove_connection(conn->henv, conn)) {
conn->errornumber = CONN_IN_USE;
conn->errormsg = "A transaction is currently being executed";
return SQL_ERROR;
}
CC_Destructor(conn);
mylog("exit SQLFreeConnect\n");
return SQL_SUCCESS;
}
/*
*
* IMPLEMENTATION CONNECTION CLASS
*
*/
ConnectionClass
*CC_Constructor()
{
ConnectionClass *rv;
rv = (ConnectionClass *)malloc(sizeof(ConnectionClass));
if (rv != NULL) {
rv->henv = NULL; /* not yet associated with an environment */
rv->errormsg = NULL;
rv->errornumber = 0;
rv->errormsg_created = FALSE;
rv->status = CONN_NOT_CONNECTED;
rv->transact_status = CONN_IN_AUTOCOMMIT; // autocommit by default
memset(&rv->connInfo, 0, sizeof(ConnInfo));
rv->sock = SOCK_Constructor();
if ( ! rv->sock)
return NULL;
rv->stmts = (StatementClass **) malloc( sizeof(StatementClass *) * STMT_INCREMENT);
if ( ! rv->stmts)
return NULL;
memset(rv->stmts, 0, sizeof(StatementClass *) * STMT_INCREMENT);
rv->num_stmts = STMT_INCREMENT;
}
return rv;
}
char
CC_Destructor(ConnectionClass *self)
{
mylog("enter CC_Destructor, self=%u\n", self);
if (self->status == CONN_EXECUTING)
return 0;
CC_cleanup(self); /* cleanup socket and statements */
mylog("after CC_Cleanup\n");
/* Free up statement holders */
if (self->stmts) {
free(self->stmts);
self->stmts = NULL;
}
mylog("after free statement holders\n");
free(self);
mylog("exit CC_Destructor\n");
return 1;
}
void
CC_clear_error(ConnectionClass *self)
{
self->errornumber = 0;
self->errormsg = NULL;
self->errormsg_created = FALSE;
}
// Used to cancel a transaction
// We are almost always in the middle of a transaction.
char
CC_abort(ConnectionClass *self)
{
QResultClass *res;
if ( CC_is_in_trans(self)) {
res = NULL;
mylog("CC_abort: sending ABORT!\n");
res = CC_send_query(self, "ABORT", NULL, NULL);
CC_set_no_trans(self);
if (res != NULL)
QR_Destructor(res);
else
return FALSE;
}
return TRUE;
}
/* This is called by SQLDisconnect also */
char
CC_cleanup(ConnectionClass *self)
{
int i;
StatementClass *stmt;
if (self->status == CONN_EXECUTING)
return FALSE;
mylog("in CC_Cleanup, self=%u\n", self);
// Cancel an ongoing transaction
// We are always in the middle of a transaction,
// even if we are in auto commit.
if (self->sock)
CC_abort(self);
mylog("after CC_abort\n");
/* This actually closes the connection to the dbase */
if (self->sock) {
SOCK_Destructor(self->sock);
self->sock = NULL;
}
mylog("after SOCK destructor\n");
/* Free all the stmts on this connection */
for (i = 0; i < self->num_stmts; i++) {
stmt = self->stmts[i];
if (stmt) {
stmt->hdbc = NULL; /* prevent any more dbase interactions */
SC_Destructor(stmt);
self->stmts[i] = NULL;
}
}
mylog("exit CC_Cleanup\n");
return TRUE;
}
void
CC_set_defaults(ConnectionClass *self)
{
ConnInfo *ci = &(self->connInfo);
if (ci->port[0] == '\0')
strcpy(ci->port, DEFAULT_PORT);
if (ci->readonly[0] == '\0')
strcpy(ci->readonly, DEFAULT_READONLY);
}
void
CC_DSN_info(ConnectionClass *self, char overwrite)
{
ConnInfo *ci = &(self->connInfo);
char *DSN = ci->dsn;
// If a driver keyword was present, then dont use a DSN and return.
// If DSN is null and no driver, then use the default datasource.
if ( DSN[0] == '\0') {
if ( ci->driver[0] != '\0')
return;
else
strcpy(DSN, "DEFAULT");
}
// Proceed with getting info for the given DSN.
if ( ci->server[0] == '\0' || overwrite)
SQLGetPrivateProfileString(DSN, INI_SERVER, "", ci->server, sizeof(ci->server), ODBC_INI);
if ( ci->database[0] == '\0' || overwrite)
SQLGetPrivateProfileString(DSN, INI_DATABASE, "", ci->database, sizeof(ci->database), ODBC_INI);
if ( ci->username[0] == '\0' || overwrite)
SQLGetPrivateProfileString(DSN, INI_USER, "", ci->username, sizeof(ci->username), ODBC_INI);
if ( ci->password[0] == '\0' || overwrite)
SQLGetPrivateProfileString(DSN, INI_PASSWORD, "", ci->password, sizeof(ci->password), ODBC_INI);
if ( ci->port[0] == '\0' || overwrite)
SQLGetPrivateProfileString(DSN, INI_PORT, "", ci->port, sizeof(ci->port), ODBC_INI);
if ( ci->readonly[0] == '\0' || overwrite)
SQLGetPrivateProfileString(DSN, INI_READONLY, "", ci->readonly, sizeof(ci->readonly), ODBC_INI);
if ( ci->protocol[0] == '\0' || overwrite)
SQLGetPrivateProfileString(DSN, INI_PROTOCOL, "", ci->protocol, sizeof(ci->protocol), ODBC_INI);
if ( ci->conn_settings[0] == '\0' || overwrite)
SQLGetPrivateProfileString(DSN, INI_CONNSETTINGS, "", ci->conn_settings, sizeof(ci->conn_settings), ODBC_INI);
qlog("conn=%u, DSN info(DSN='%s',server='%s',dbase='%s',user='%s',passwd='%s',port='%s',readonly='%s',protocol='%s',conn_settings='%s')\n",
self, DSN,
ci->server,
ci->database,
ci->username,
ci->password,
ci->port,
ci->readonly,
ci->protocol,
ci->conn_settings);
}
char
CC_connect(ConnectionClass *self, char do_password)
{
StartupPacket sp;
StartupPacket6_2 sp62;
QResultClass *res;
SocketClass *sock;
ConnInfo *ci = &(self->connInfo);
int areq = -1;
int beresp;
char msgbuffer[ERROR_MSG_LENGTH];
char salt[2];
if ( do_password)
sock = self->sock; /* already connected, just authenticate */
else {
if (self->status != CONN_NOT_CONNECTED) {
self->errormsg = "Already connected.";
self->errornumber = CONN_OPENDB_ERROR;
return 0;
}
if ( ci->server[0] == '\0' || ci->port[0] == '\0' || ci->database[0] == '\0') {
self->errornumber = CONN_INIREAD_ERROR;
self->errormsg = "Missing server name, port, or database name in call to CC_connect.";
return 0;
}
mylog("CC_connect(): DSN = '%s', server = '%s', port = '%s', database = '%s', username = '%s', password='%s'\n",
ci->dsn, ci->server, ci->port, ci->database, ci->username, ci->password);
/* If the socket was closed for some reason (like a SQLDisconnect, but no SQLFreeConnect
then create a socket now.
*/
if ( ! self->sock) {
self->sock = SOCK_Constructor();
if ( ! self->sock) {
self->errornumber = CONNECTION_SERVER_NOT_REACHED;
self->errormsg = "Could not open a socket to the server";
return 0;
}
}
sock = self->sock;
mylog("connecting to the server socket...\n");
SOCK_connect_to(sock, (short) atoi(ci->port), ci->server);
if (SOCK_get_errcode(sock) != 0) {
mylog("connection to the server socket failed.\n");
self->errornumber = CONNECTION_SERVER_NOT_REACHED;
self->errormsg = "Could not connect to the server";
return 0;
}
mylog("connection to the server socket succeeded.\n");
if ( PROTOCOL_62(ci)) {
sock->reverse = TRUE; /* make put_int and get_int work for 6.2 */
memset(&sp62, 0, sizeof(StartupPacket6_2));
SOCK_put_int(sock, htonl(4+sizeof(StartupPacket6_2)), 4);
sp62.authtype = htonl(NO_AUTHENTICATION);
strncpy(sp62.database, ci->database, PATH_SIZE);
strncpy(sp62.user, ci->username, NAMEDATALEN);
SOCK_put_n_char(sock, (char *) &sp62, sizeof(StartupPacket6_2));
SOCK_flush_output(sock);
}
else {
memset(&sp, 0, sizeof(StartupPacket));
mylog("sizeof startup packet = %d\n", sizeof(StartupPacket));
// Send length of Authentication Block
SOCK_put_int(sock, 4+sizeof(StartupPacket), 4);
sp.protoVersion = (ProtocolVersion) htonl(PG_PROTOCOL_LATEST);
strncpy(sp.database, ci->database, SM_DATABASE);
strncpy(sp.user, ci->username, SM_USER);
SOCK_put_n_char(sock, (char *) &sp, sizeof(StartupPacket));
SOCK_flush_output(sock);
}
mylog("sent the authentication block.\n");
if (sock->errornumber != 0) {
mylog("couldn't send the authentication block properly.\n");
self->errornumber = CONN_INVALID_AUTHENTICATION;
self->errormsg = "Sending the authentication packet failed";
return 0;
}
mylog("sent the authentication block successfully.\n");
}
mylog("gonna do authentication\n");
// ***************************************************
// Now get the authentication request from backend
// ***************************************************
if ( ! PROTOCOL_62(ci)) do {
if (do_password)
beresp = 'R';
else
beresp = SOCK_get_char(sock);
switch(beresp) {
case 'E':
mylog("auth got 'E'\n");
SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
self->errornumber = CONN_INVALID_AUTHENTICATION;
self->errormsg = msgbuffer;
qlog("ERROR from backend during authentication: '%s'\n", self->errormsg);
return 0;
case 'R':
if (do_password) {
mylog("in 'R' do_password\n");
areq = AUTH_REQ_PASSWORD;
do_password = FALSE;
}
else {
mylog("auth got 'R'\n");
areq = SOCK_get_int(sock, 4);
if (areq == AUTH_REQ_CRYPT)
SOCK_get_n_char(sock, salt, 2);
mylog("areq = %d\n", areq);
}
switch(areq) {
case AUTH_REQ_OK:
break;
case AUTH_REQ_KRB4:
self->errormsg = "Kerberos 4 authentication not supported";
self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED;
return 0;
case AUTH_REQ_KRB5:
self->errormsg = "Kerberos 5 authentication not supported";
self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED;
return 0;
case AUTH_REQ_PASSWORD:
mylog("in AUTH_REQ_PASSWORD\n");
if (ci->password[0] == '\0') {
self->errornumber = CONNECTION_NEED_PASSWORD;
self->errormsg = "A password is required for this connection.";
return -1; /* need password */
}
mylog("past need password\n");
SOCK_put_int(sock, 4+strlen(ci->password)+1, 4);
SOCK_put_n_char(sock, ci->password, strlen(ci->password) + 1);
SOCK_flush_output(sock);
mylog("past flush\n");
break;
case AUTH_REQ_CRYPT:
self->errormsg = "Password crypt authentication not supported";
self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED;
return 0;
default:
self->errormsg = "Unknown authentication type";
self->errornumber = CONN_AUTH_TYPE_UNSUPPORTED;
return 0;
}
break;
default:
self->errormsg = "Unexpected protocol character during authentication";
self->errornumber = CONN_INVALID_AUTHENTICATION;
return 0;
}
} while (areq != AUTH_REQ_OK);
CC_clear_error(self); /* clear any password error */
/* send an empty query in order to find out whether the specified */
/* database really exists on the server machine */
mylog("sending an empty query...\n");
res = CC_send_query(self, " ", NULL, NULL);
if ( res == NULL || QR_get_status(res) != PGRES_EMPTY_QUERY) {
mylog("got no result from the empty query. (probably database does not exist)\n");
self->errornumber = CONNECTION_NO_SUCH_DATABASE;
self->errormsg = "The database does not exist on the server\nor user authentication failed.";
if (res != NULL)
QR_Destructor(res);
return 0;
}
if (res)
QR_Destructor(res);
mylog("empty query seems to be OK.\n");
/**********************************************/
/******* Send any initial settings *********/
/**********************************************/
CC_send_settings(self);
CC_clear_error(self); /* clear any initial command errors */
self->status = CONN_CONNECTED;
return 1;
}
char
CC_add_statement(ConnectionClass *self, StatementClass *stmt)
{
int i;
mylog("CC_add_statement: self=%u, stmt=%u\n", self, stmt);
for (i = 0; i < self->num_stmts; i++) {
if ( ! self->stmts[i]) {
stmt->hdbc = self;
self->stmts[i] = stmt;
return TRUE;
}
}
/* no more room -- allocate more memory */
self->stmts = (StatementClass **) realloc( self->stmts, sizeof(StatementClass *) * (STMT_INCREMENT + self->num_stmts));
if ( ! self->stmts)
return FALSE;
memset(&self->stmts[self->num_stmts], 0, sizeof(StatementClass *) * STMT_INCREMENT);
stmt->hdbc = self;
self->stmts[self->num_stmts] = stmt;
self->num_stmts += STMT_INCREMENT;
return TRUE;
}
char
CC_remove_statement(ConnectionClass *self, StatementClass *stmt)
{
int i;
for (i = 0; i < self->num_stmts; i++) {
if (self->stmts[i] == stmt && stmt->status != STMT_EXECUTING) {
self->stmts[i] = NULL;
return TRUE;
}
}
return FALSE;
}
/* Create a more informative error message by concatenating the connection
error message with its socket error message.
*/
char *
CC_create_errormsg(ConnectionClass *self)
{
SocketClass *sock = self->sock;
int pos;
static char msg[4096];
mylog("enter CC_create_errormsg\n");
msg[0] = '\0';
if (self->errormsg)
strcpy(msg, self->errormsg);
mylog("msg = '%s'\n", msg);
if (sock && sock->errormsg && sock->errormsg[0] != '\0') {
pos = strlen(msg);
sprintf(&msg[pos], ";\n%s", sock->errormsg);
}
mylog("exit CC_create_errormsg\n");
return msg;
}
char
CC_get_error(ConnectionClass *self, int *number, char **message)
{
int rv;
mylog("enter CC_get_error\n");
// Create a very informative errormsg if it hasn't been done yet.
if ( ! self->errormsg_created) {
self->errormsg = CC_create_errormsg(self);
self->errormsg_created = TRUE;
}
if (self->errornumber) {
*number = self->errornumber;
*message = self->errormsg;
}
rv = (self->errornumber != 0);
self->errornumber = 0; // clear the error
mylog("exit CC_get_error\n");
return rv;
}
/* The "result_in" is only used by QR_next_tuple() to fetch another group of rows into
the same existing QResultClass (this occurs when the tuple cache is depleted and
needs to be re-filled).
The "cursor" is used by SQLExecute to associate a statement handle as the cursor name
(i.e., C3326857) for SQL select statements. This cursor is then used in future
'declare cursor C3326857 for ...' and 'fetch 100 in C3326857' statements.
*/
QResultClass *
CC_send_query(ConnectionClass *self, char *query, QResultClass *result_in, char *cursor)
{
QResultClass *res = NULL;
char id, swallow;
SocketClass *sock = self->sock;
static char msgbuffer[MAX_MESSAGE_LEN+1];
char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont need static
mylog("send_query(): conn=%u, query='%s'\n", self, query);
qlog("conn=%u, query='%s'\n", self, query);
// Indicate that we are sending a query to the backend
if(strlen(query) > MAX_MESSAGE_LEN-2) {
self->errornumber = CONNECTION_MSG_TOO_LONG;
self->errormsg = "Query string is too long";
return NULL;
}
if ((NULL == query) || (query[0] == '\0'))
return NULL;
if (SOCK_get_errcode(sock) != 0) {
self->errornumber = CONNECTION_COULD_NOT_SEND;
self->errormsg = "Could not send Query to backend";
CC_set_no_trans(self);
return NULL;
}
SOCK_put_char(sock, 'Q');
if (SOCK_get_errcode(sock) != 0) {
self->errornumber = CONNECTION_COULD_NOT_SEND;
self->errormsg = "Could not send Query to backend";
CC_set_no_trans(self);
return NULL;
}
SOCK_put_string(sock, query);
SOCK_flush_output(sock);
if (SOCK_get_errcode(sock) != 0) {
self->errornumber = CONNECTION_COULD_NOT_SEND;
self->errormsg = "Could not send Query to backend";
CC_set_no_trans(self);
return NULL;
}
mylog("send_query: done sending query\n");
while(1) {
/* what type of message is comming now ? */
id = SOCK_get_char(sock);
if ((SOCK_get_errcode(sock) != 0) || (id == EOF)) {
self->errornumber = CONNECTION_NO_RESPONSE;
self->errormsg = "No response from the backend";
if (res)
QR_Destructor(res);
mylog("send_query: 'id' - %s\n", self->errormsg);
CC_set_no_trans(self);
return NULL;
}
mylog("send_query: got id = '%c'\n", id);
switch (id) {
case 'A' : /* Asynchronous Messages are ignored */
(void)SOCK_get_int(sock, 4); /* id of notification */
SOCK_get_string(sock, msgbuffer, MAX_MESSAGE_LEN);
/* name of the relation the message comes from */
break;
case 'C' : /* portal query command, no tuples returned */
/* read in the return message from the backend */
SOCK_get_string(sock, cmdbuffer, MAX_MESSAGE_LEN);
if (SOCK_get_errcode(sock) != 0) {
self->errornumber = CONNECTION_NO_RESPONSE;
self->errormsg = "No response from backend while receiving a portal query command";
mylog("send_query: 'C' - %s\n", self->errormsg);
CC_set_no_trans(self);
return NULL;
} else {
char clear = 0;
mylog("send_query: ok - 'C' - %s\n", cmdbuffer);
if (res == NULL) /* allow for "show" style notices */
res = QR_Constructor();
mylog("send_query: setting cmdbuffer = '%s'\n", cmdbuffer);
/* Only save the first command */
QR_set_status(res, PGRES_COMMAND_OK);
QR_set_command(res, cmdbuffer);
/* (Quotation from the original comments)
since backend may produze more than one result for some commands
we need to poll until clear
so we send an empty query, and keep reading out of the pipe
until an 'I' is received
*/
SOCK_put_string(sock, "Q ");
SOCK_flush_output(sock);
while(!clear) {
SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH);
mylog("send_query: read command '%s'\n", cmdbuffer);
clear = (cmdbuffer[0] == 'I');
}
mylog("send_query: returning res = %u\n", res);
return res;
}
case 'N' : /* NOTICE: */
SOCK_get_string(sock, cmdbuffer, ERROR_MSG_LENGTH);
res = QR_Constructor();
QR_set_status(res, PGRES_NONFATAL_ERROR);
QR_set_notice(res, cmdbuffer); /* will dup this string */
mylog("~~~ NOTICE: '%s'\n", cmdbuffer);
qlog("NOTICE from backend during send_query: '%s'\n", cmdbuffer);
continue; // dont return a result -- continue reading
case 'I' : /* The server sends an empty query */
/* There is a closing '\0' following the 'I', so we eat it */
swallow = SOCK_get_char(sock);
if ((swallow != '\0') || SOCK_get_errcode(sock) != 0) {
self->errornumber = CONNECTION_BACKEND_CRAZY;
self->errormsg = "Unexpected protocol character from backend";
res = QR_Constructor();
QR_set_status(res, PGRES_FATAL_ERROR);
return res;
} else {
/* We return the empty query */
res = QR_Constructor();
QR_set_status(res, PGRES_EMPTY_QUERY);
return res;
}
break;
case 'E' :
SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
/* Remove a newline */
if (msgbuffer[0] != '\0' && msgbuffer[strlen(msgbuffer)-1] == '\n')
msgbuffer[strlen(msgbuffer)-1] = '\0';
self->errormsg = msgbuffer;
mylog("send_query: 'E' - %s\n", self->errormsg);
qlog("ERROR from backend during send_query: '%s'\n", self->errormsg);
if ( ! strncmp(self->errormsg, "FATAL", 5)) {
self->errornumber = CONNECTION_SERVER_REPORTED_ERROR;
CC_set_no_trans(self);
}
else
self->errornumber = CONNECTION_SERVER_REPORTED_WARNING;
return NULL;
case 'P' : /* get the Portal name */
SOCK_get_string(sock, msgbuffer, MAX_MESSAGE_LEN);
break;
case 'T': /* Tuple results start here */
if (result_in == NULL) {
result_in = QR_Constructor();
mylog("send_query: 'T' no result_in: res = %u\n", result_in);
if ( ! result_in) {
self->errornumber = CONNECTION_COULD_NOT_RECEIVE;
self->errormsg = "Could not create result info in send_query.";
return NULL;
}
if ( ! QR_fetch_tuples(result_in, self, cursor)) {
self->errornumber = CONNECTION_COULD_NOT_RECEIVE;
self->errormsg = QR_get_message(result_in);
return NULL;
}
}
else { // next fetch, so reuse an existing result
if ( ! QR_fetch_tuples(result_in, NULL, NULL)) {
self->errornumber = CONNECTION_COULD_NOT_RECEIVE;
self->errormsg = QR_get_message(result_in);
return NULL;
}
}
return result_in;
case 'D': /* Copy in command began successfully */
res = QR_Constructor();
QR_set_status(res, PGRES_COPY_IN);
return res;
case 'B': /* Copy out command began successfully */
res = QR_Constructor();
QR_set_status(res, PGRES_COPY_OUT);
return res;
default:
self->errornumber = CONNECTION_BACKEND_CRAZY;
self->errormsg = "Unexpected protocol character from backend";
CC_set_no_trans(self);
mylog("send_query: error - %s\n", self->errormsg);
return NULL;
}
}
}
char
CC_send_settings(ConnectionClass *self)
{
char ini_query[MAX_MESSAGE_LEN];
ConnInfo *ci = &(self->connInfo);
QResultClass *res;
ini_query[0] = '\0';
/* Turn on/off genetic optimizer based on global flag */
if (globals.optimizer[0] != '\0')
sprintf(ini_query, "set geqo to '%s'", globals.optimizer);
/* Global settings */
if (globals.conn_settings[0] != '\0')
sprintf(&ini_query[strlen(ini_query)], "%s%s",
ini_query[0] != '\0' ? "; " : "",
globals.conn_settings);
/* Per Datasource settings */
if (ci->conn_settings[0] != '\0')
sprintf(&ini_query[strlen(ini_query)], "%s%s",
ini_query[0] != '\0' ? "; " : "",
ci->conn_settings);
if (ini_query[0] != '\0') {
mylog("Sending Initial Connection query: '%s'\n", ini_query);
res = CC_send_query(self, ini_query, NULL, NULL);
if (res && QR_get_status(res) != PGRES_FATAL_ERROR) {
mylog("Initial Query response: '%s'\n", QR_get_notice(res));
}
if ( res == NULL ||
QR_get_status(res) == PGRES_BAD_RESPONSE ||
QR_get_status(res) == PGRES_FATAL_ERROR ||
QR_get_status(res) == PGRES_INTERNAL_ERROR) {
self->errornumber = CONNECTION_COULD_NOT_SEND;
self->errormsg = "Error sending ConnSettings";
if (res)
QR_Destructor(res);
return 0;
}
if (res)
QR_Destructor(res);
}
return TRUE;
}

View File

@ -0,0 +1,183 @@
/* File: connection.h
*
* Description: See "connection.c"
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#ifndef __CONNECTION_H__
#define __CONNECTION_H__
#include <windows.h>
#include <sql.h>
#include "psqlodbc.h"
typedef enum {
CONN_NOT_CONNECTED, /* Connection has not been established */
CONN_CONNECTED, /* Connection is up and has been established */
CONN_DOWN, /* Connection is broken */
CONN_EXECUTING /* the connection is currently executing a statement */
} CONN_Status;
/* These errors have general sql error state */
#define CONNECTION_SERVER_NOT_REACHED 1
#define CONNECTION_MSG_TOO_LONG 3
#define CONNECTION_COULD_NOT_SEND 4
#define CONNECTION_NO_SUCH_DATABASE 5
#define CONNECTION_BACKEND_CRAZY 6
#define CONNECTION_NO_RESPONSE 7
#define CONNECTION_SERVER_REPORTED_ERROR 8
#define CONNECTION_COULD_NOT_RECEIVE 9
#define CONNECTION_SERVER_REPORTED_WARNING 10
#define CONNECTION_NEED_PASSWORD 12
/* These errors correspond to specific SQL states */
#define CONN_INIREAD_ERROR 1
#define CONN_OPENDB_ERROR 2
#define CONN_STMT_ALLOC_ERROR 3
#define CONN_IN_USE 4
#define CONN_UNSUPPORTED_OPTION 5
/* Used by SetConnectoption to indicate unsupported options */
#define CONN_INVALID_ARGUMENT_NO 6
/* SetConnectOption: corresponds to ODBC--"S1009" */
#define CONN_TRANSACT_IN_PROGRES 7
#define CONN_NO_MEMORY_ERROR 8
#define CONN_NOT_IMPLEMENTED_ERROR 9
#define CONN_INVALID_AUTHENTICATION 10
#define CONN_AUTH_TYPE_UNSUPPORTED 11
/* Conn_status defines */
#define CONN_IN_AUTOCOMMIT 0x01
#define CONN_IN_TRANSACTION 0x02
/* AutoCommit functions */
#define CC_set_autocommit_off(x) (x->transact_status &= ~CONN_IN_AUTOCOMMIT)
#define CC_set_autocommit_on(x) (x->transact_status |= CONN_IN_AUTOCOMMIT)
#define CC_is_in_autocommit(x) (x->transact_status & CONN_IN_AUTOCOMMIT)
/* Transaction in/not functions */
#define CC_set_in_trans(x) (x->transact_status |= CONN_IN_TRANSACTION)
#define CC_set_no_trans(x) (x->transact_status &= ~CONN_IN_TRANSACTION)
#define CC_is_in_trans(x) (x->transact_status & CONN_IN_TRANSACTION)
/* Authentication types */
#define AUTH_REQ_OK 0
#define AUTH_REQ_KRB4 1
#define AUTH_REQ_KRB5 2
#define AUTH_REQ_PASSWORD 3
#define AUTH_REQ_CRYPT 4
/* Startup Packet sizes */
#define SM_DATABASE 64
#define SM_USER 32
#define SM_OPTIONS 64
#define SM_UNUSED 64
#define SM_TTY 64
/* Old 6.2 protocol defines */
#define NO_AUTHENTICATION 7
#define PATH_SIZE 64
#define ARGV_SIZE 64
#define NAMEDATALEN 16
typedef unsigned int ProtocolVersion;
#define PG_PROTOCOL(major, minor) (((major) << 16) | (minor))
#define PG_PROTOCOL_LATEST PG_PROTOCOL(1, 0)
#define PG_PROTOCOL_EARLIEST PG_PROTOCOL(0, 0)
/* This startup packet is to support latest Postgres protocol (6.3) */
typedef struct _StartupPacket
{
ProtocolVersion protoVersion;
char database[SM_DATABASE];
char user[SM_USER];
char options[SM_OPTIONS];
char unused[SM_UNUSED];
char tty[SM_TTY];
} StartupPacket;
/* This startup packet is to support pre-Postgres 6.3 protocol */
typedef struct _StartupPacket6_2
{
unsigned int authtype;
char database[PATH_SIZE];
char user[NAMEDATALEN];
char options[ARGV_SIZE];
char execfile[ARGV_SIZE];
char tty[PATH_SIZE];
} StartupPacket6_2;
/* Structure to hold all the connection attributes for a specific
connection (used for both registry and file, DSN and DRIVER)
*/
typedef struct {
char dsn[MEDIUM_REGISTRY_LEN];
char driver[MEDIUM_REGISTRY_LEN];
char server[MEDIUM_REGISTRY_LEN];
char database[MEDIUM_REGISTRY_LEN];
char username[MEDIUM_REGISTRY_LEN];
char password[MEDIUM_REGISTRY_LEN];
char conn_settings[LARGE_REGISTRY_LEN];
char protocol[SMALL_REGISTRY_LEN];
char port[SMALL_REGISTRY_LEN];
char readonly[SMALL_REGISTRY_LEN];
char focus_password;
} ConnInfo;
/* Macro to determine is the connection using 6.2 protocol? */
#define PROTOCOL_62(conninfo_) (strncmp((conninfo_)->protocol, PG62, strlen(PG62)) == 0)
/******* The Connection handle ************/
struct ConnectionClass_ {
HENV henv; /* environment this connection was created on */
char *errormsg;
int errornumber;
CONN_Status status;
ConnInfo connInfo;
StatementClass **stmts;
int num_stmts;
SocketClass *sock;
char transact_status; /* Is a transaction is currently in progress */
char errormsg_created; /* has an informative error msg been created? */
};
/* Accessor functions */
#define CC_get_socket(x) (x->sock)
#define CC_get_database(x) (x->connInfo.database)
#define CC_get_server(x) (x->connInfo.server)
#define CC_get_DSN(x) (x->connInfo.dsn)
#define CC_get_username(x) (x->connInfo.username)
#define CC_is_readonly(x) (x->connInfo.readonly[0] == '1')
/* for CC_DSN_info */
#define CONN_DONT_OVERWRITE 0
#define CONN_OVERWRITE 1
/* prototypes */
ConnectionClass *CC_Constructor();
char CC_Destructor(ConnectionClass *self);
char CC_cleanup(ConnectionClass *self);
char CC_abort(ConnectionClass *self);
void CC_DSN_info(ConnectionClass *self, char overwrite);
void CC_set_defaults(ConnectionClass *self);
char CC_connect(ConnectionClass *self, char do_password);
char CC_add_statement(ConnectionClass *self, StatementClass *stmt);
char CC_remove_statement(ConnectionClass *self, StatementClass *stmt);
char CC_get_error(ConnectionClass *self, int *number, char **message);
QResultClass *CC_send_query(ConnectionClass *self, char *query, QResultClass *result_in, char *cursor);
void CC_clear_error(ConnectionClass *self);
char *CC_create_errormsg(ConnectionClass *self);
char CC_send_settings(ConnectionClass *self);
#endif

View File

@ -0,0 +1,995 @@
/* Module: convert.c
*
* Description: This module contains routines related to
* converting parameters and columns into requested data types.
* Parameters are converted from their SQL_C data types into
* the appropriate postgres type. Columns are converted from
* their postgres type (SQL type) into the appropriate SQL_C
* data type.
*
* Classes: n/a
*
* API functions: none
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <sql.h>
#include <sqlext.h>
#include <time.h>
#include <math.h>
#include "convert.h"
#include "statement.h"
#include "bind.h"
#include "pgtypes.h"
/******** A Guide for date/time/timestamp conversions **************
field_type fCType Output
---------- ------ ----------
PG_TYPE_DATE SQL_C_DEFAULT SQL_C_DATE
PG_TYPE_DATE SQL_C_DATE SQL_C_DATE
PG_TYPE_DATE SQL_C_TIMESTAMP SQL_C_TIMESTAMP (time = 0 (midnight))
PG_TYPE_TIME SQL_C_DEFAULT SQL_C_TIME
PG_TYPE_TIME SQL_C_TIME SQL_C_TIME
PG_TYPE_TIME SQL_C_TIMESTAMP SQL_C_TIMESTAMP (date = current date)
PG_TYPE_ABSTIME SQL_C_DEFAULT SQL_C_TIMESTAMP
PG_TYPE_ABSTIME SQL_C_DATE SQL_C_DATE (time is truncated)
PG_TYPE_ABSTIME SQL_C_TIME SQL_C_TIME (date is truncated)
PG_TYPE_ABSTIME SQL_C_TIMESTAMP SQL_C_TIMESTAMP
******************************************************************************/
/* This is called by SQLFetch() */
int
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,
(SDWORD)bic->buflen, (SDWORD *)bic->used);
}
/* This is called by SQLGetData() */
int
copy_and_convert_field(Int4 field_type, void *value, Int2 fCType, PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue)
{
Int4 len = 0, nf;
char day[4], mon[4], tz[4];
SIMPLE_TIME st;
time_t t = time(NULL);
struct tm *tim;
int bool;
memset(&st, 0, sizeof(SIMPLE_TIME));
/* Initialize current date */
tim = localtime(&t);
st.m = tim->tm_mon + 1;
st.d = tim->tm_mday;
st.y = tim->tm_year + 1900;
bool = 0;
mylog("copy_and_convert: field_type = %d, fctype = %d, value = '%s', cbValueMax=%d\n", field_type, fCType, value, cbValueMax);
if(value) {
/********************************************************************
First convert any specific postgres types into more
useable data.
NOTE: Conversions from PG char/varchar of a date/time/timestamp
value to SQL_C_DATE,SQL_C_TIME, SQL_C_TIMESTAMP not supported
*********************************************************************/
switch(field_type) {
/* $$$ need to add parsing for date/time/timestamp strings in PG_TYPE_CHAR,VARCHAR $$$ */
case PG_TYPE_DATE:
sscanf(value, "%2d-%2d-%4d", &st.m, &st.d, &st.y);
break;
case PG_TYPE_TIME:
sscanf(value, "%2d:%2d:%2d", &st.hh, &st.mm, &st.ss);
break;
case PG_TYPE_ABSTIME:
case PG_TYPE_DATETIME:
if (strnicmp(value, "invalid", 7) != 0) {
nf = sscanf(value, "%3s %3s %2d %2d:%2d:%2d %4d %3s", &day, &mon, &st.d, &st.hh, &st.mm, &st.ss, &st.y, &tz);
if (nf == 7 || nf == 8) {
/* convert month name to month number */
st.m = monthToNumber(mon);
}
} else { /* The timestamp is invalid so set something conspicuous, like the epoch */
t = 0;
tim = localtime(&t);
st.m = tim->tm_mon + 1;
st.d = tim->tm_mday;
st.y = tim->tm_year + 1900;
st.hh = tim->tm_hour;
st.mm = tim->tm_min;
st.ss = tim->tm_sec;
}
break;
case PG_TYPE_BOOL: { /* change T/F to 1/0 */
char *s = (char *) value;
if (s[0] == 'T' || s[0] == 't' || s[0] == '1')
bool = 1;
else
bool = 0;
}
break;
/* This is for internal use by SQLStatistics() */
case PG_TYPE_INT28: {
// this is an array of eight integers
short *short_array = (short *)rgbValue;
len = 16;
sscanf(value, "%hd %hd %hd %hd %hd %hd %hd %hd",
&short_array[0],
&short_array[1],
&short_array[2],
&short_array[3],
&short_array[4],
&short_array[5],
&short_array[6],
&short_array[7]);
/* There is no corresponding fCType for this. */
if(pcbValue)
*pcbValue = len;
return COPY_OK; /* dont go any further or the data will be trashed */
}
}
/* Change default into something useable */
if (fCType == SQL_C_DEFAULT) {
fCType = pgtype_to_ctype(field_type);
mylog("copy_and_convert, SQL_C_DEFAULT: fCType = %d\n", fCType);
}
if(fCType == SQL_C_CHAR) {
/* Special character formatting as required */
switch(field_type) {
case PG_TYPE_DATE:
len = 11;
if (cbValueMax > len)
sprintf((char *)rgbValue, "%.4d-%.2d-%.2d", st.y, st.m, st.d);
break;
case PG_TYPE_TIME:
len = 9;
if (cbValueMax > len)
sprintf((char *)rgbValue, "%.2d:%.2d:%.2d", st.hh, st.mm, st.ss);
break;
case PG_TYPE_ABSTIME:
case PG_TYPE_DATETIME:
len = 19;
if (cbValueMax > len)
sprintf((char *) rgbValue, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d",
st.y, st.m, st.d, st.hh, st.mm, st.ss);
break;
case PG_TYPE_BOOL:
len = 1;
if (cbValueMax > len) {
strcpy((char *) rgbValue, bool ? "1" : "0");
mylog("PG_TYPE_BOOL: rgbValue = '%s'\n", rgbValue);
}
break;
case PG_TYPE_BYTEA: // convert binary data to hex strings (i.e, 255 = "FF")
len = convert_pgbinary_to_char(value, rgbValue, cbValueMax);
break;
default:
/* convert linefeeds to carriage-return/linefeed */
convert_linefeeds( (char *) value, rgbValue, cbValueMax);
len = strlen(rgbValue);
mylog(" SQL_C_CHAR, default: len = %d, cbValueMax = %d, rgbValue = '%s'\n", len, cbValueMax, rgbValue);
break;
}
} else {
/* 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.
*/
if (field_type == PG_TYPE_MONEY)
convert_money(value);
switch(fCType) {
case SQL_C_DATE:
len = 6;
if (cbValueMax >= len) {
DATE_STRUCT *ds = (DATE_STRUCT *) rgbValue;
ds->year = st.y;
ds->month = st.m;
ds->day = st.d;
}
break;
case SQL_C_TIME:
len = 6;
if (cbValueMax >= len) {
TIME_STRUCT *ts = (TIME_STRUCT *) rgbValue;
ts->hour = st.hh;
ts->minute = st.mm;
ts->second = st.ss;
}
break;
case SQL_C_TIMESTAMP:
len = 16;
if (cbValueMax >= len) {
TIMESTAMP_STRUCT *ts = (TIMESTAMP_STRUCT *) rgbValue;
ts->year = st.y;
ts->month = st.m;
ts->day = st.d;
ts->hour = st.hh;
ts->minute = st.mm;
ts->second = st.ss;
ts->fraction = 0;
}
break;
case SQL_C_BIT:
len = 1;
if (cbValueMax >= len || field_type == PG_TYPE_BOOL) {
*((UCHAR *)rgbValue) = (UCHAR) bool;
mylog("SQL_C_BIT: val = %d, cb = %d, rgb=%d\n", atoi(value), cbValueMax, *((UCHAR *)rgbValue));
}
break;
case SQL_C_STINYINT:
case SQL_C_TINYINT:
len = 1;
if (cbValueMax >= len)
*((SCHAR *) rgbValue) = atoi(value);
break;
case SQL_C_UTINYINT:
len = 1;
if (cbValueMax >= len)
*((UCHAR *) rgbValue) = atoi(value);
break;
case SQL_C_FLOAT:
len = 4;
if(cbValueMax >= len)
*((SFLOAT *)rgbValue) = (float) atof(value);
break;
case SQL_C_DOUBLE:
len = 8;
if(cbValueMax >= len)
*((SDOUBLE *)rgbValue) = atof(value);
break;
case SQL_C_SSHORT:
case SQL_C_SHORT:
len = 2;
if(cbValueMax >= len)
*((SWORD *)rgbValue) = atoi(value);
break;
case SQL_C_USHORT:
len = 2;
if(cbValueMax >= len)
*((UWORD *)rgbValue) = atoi(value);
break;
case SQL_C_SLONG:
case SQL_C_LONG:
len = 4;
if(cbValueMax >= len)
*((SDWORD *)rgbValue) = atol(value);
break;
case SQL_C_ULONG:
len = 4;
if(cbValueMax >= len)
*((UDWORD *)rgbValue) = atol(value);
break;
case SQL_C_BINARY:
// truncate if necessary
// convert octal escapes to bytes
len = convert_from_pgbinary(value, rgbValue, cbValueMax);
mylog("SQL_C_BINARY: len = %d\n", len);
break;
default:
return COPY_UNSUPPORTED_TYPE;
}
}
} else {
/* handle a null just by returning SQL_NULL_DATA in pcbValue, */
/* and doing nothing to the buffer. */
if(pcbValue) {
*pcbValue = SQL_NULL_DATA;
}
}
// store the length of what was copied, if there's a place for it
// unless it was a NULL (in which case it was already set above)
if(pcbValue && value)
*pcbValue = len;
if(len > cbValueMax) {
mylog("!!! COPY_RESULT_TRUNCATED !!!\n");
// Don't return truncated because an application
// (like Access) will try to call GetData again
// to retrieve the rest of the data. Since we
// are not currently ready for this, and the result
// is an endless loop, we better just silently
// truncate the data.
// return COPY_RESULT_TRUNCATED;
*pcbValue = cbValueMax -1;
return COPY_OK;
} else {
return COPY_OK;
}
}
/* This function inserts parameters into an SQL statements.
It will also modify a SELECT statement for use with declare/fetch cursors.
This function no longer does any dynamic memory allocation!
*/
int
copy_statement_with_parameters(StatementClass *stmt)
{
unsigned int opos, npos;
char param_string[128], tmp[256], cbuf[TEXT_FIELD_SIZE+5];
int param_number;
Int2 param_ctype, param_sqltype;
char *old_statement = stmt->statement;
char *new_statement = stmt->stmt_with_params;
SIMPLE_TIME st;
time_t t = time(NULL);
struct tm *tim;
SDWORD FAR *used;
char *buffer, *buf;
if ( ! old_statement)
return SQL_ERROR;
memset(&st, 0, sizeof(SIMPLE_TIME));
/* Initialize current date */
tim = localtime(&t);
st.m = tim->tm_mon + 1;
st.d = tim->tm_mday;
st.y = tim->tm_year + 1900;
// For selects, prepend a declare cursor to the statement
if (stmt->statement_type == STMT_TYPE_SELECT) {
sprintf(new_statement, "declare C%u cursor for ", stmt);
npos = strlen(new_statement);
}
else {
new_statement[0] = '0';
npos = 0;
}
param_number = -1;
for (opos = 0; opos < strlen(old_statement); opos++) {
// Squeeze carriage-returns/linfeed pairs to linefeed only
if (old_statement[opos] == '\r' && opos+1<strlen(old_statement) && old_statement[opos+1] == '\n') {
continue;
}
// Handle literals (date, time, timestamp)
else if (old_statement[opos] == '{') {
char *esc;
char *begin = &old_statement[opos + 1];
char *end = strchr(begin, '}');
if ( ! end)
continue;
*end = '\0';
esc = convert_escape(begin);
if (esc) {
memcpy(&new_statement[npos], esc, strlen(esc));
npos += strlen(esc);
}
opos += end - begin + 2;
*end = '}';
continue;
}
else if (old_statement[opos] != '?') { // a regular character
new_statement[npos++] = old_statement[opos];
continue;
}
/****************************************************/
/* Its a '?' parameter alright */
/****************************************************/
param_number++;
if (param_number >= stmt->parameters_allocated)
break;
/* Assign correct buffers based on data at exec param or not */
if ( stmt->parameters[param_number].data_at_exec) {
used = stmt->parameters[param_number].EXEC_used;
buffer = stmt->parameters[param_number].EXEC_buffer;
}
else {
used = stmt->parameters[param_number].used;
buffer = stmt->parameters[param_number].buffer;
}
/* Handle NULL parameter data */
if (used && *used == SQL_NULL_DATA) {
strcpy(&new_statement[npos], "NULL");
npos += 4;
continue;
}
/* If no buffer, and its not null, then what the hell is it?
Just leave it alone then.
*/
if ( ! buffer) {
new_statement[npos++] = '?';
continue;
}
param_ctype = stmt->parameters[param_number].CType;
param_sqltype = stmt->parameters[param_number].SQLType;
mylog("copy_statement_with_params: from(fcType)=%d, to(fSqlType)=%d\n",
param_ctype,
param_sqltype);
// replace DEFAULT with something we can use
if(param_ctype == SQL_C_DEFAULT)
param_ctype = sqltype_to_default_ctype(param_sqltype);
buf = NULL;
param_string[0] = '\0';
cbuf[0] = '\0';
/* Convert input C type to a neutral format */
switch(param_ctype) {
case SQL_C_BINARY:
case SQL_C_CHAR:
buf = buffer;
break;
case SQL_C_DOUBLE:
sprintf(param_string, "%f",
*((SDOUBLE *) buffer));
break;
case SQL_C_FLOAT:
sprintf(param_string, "%f",
*((SFLOAT *) buffer));
break;
case SQL_C_SLONG:
case SQL_C_LONG:
sprintf(param_string, "%ld",
*((SDWORD *) buffer));
break;
case SQL_C_SSHORT:
case SQL_C_SHORT:
sprintf(param_string, "%d",
*((SWORD *) buffer));
break;
case SQL_C_STINYINT:
case SQL_C_TINYINT:
sprintf(param_string, "%d",
*((SCHAR *) buffer));
break;
case SQL_C_ULONG:
sprintf(param_string, "%lu",
*((UDWORD *) buffer));
break;
case SQL_C_USHORT:
sprintf(param_string, "%u",
*((UWORD *) buffer));
break;
case SQL_C_UTINYINT:
sprintf(param_string, "%u",
*((UCHAR *) buffer));
break;
case SQL_C_BIT: {
int i = *((UCHAR *) buffer);
sprintf(param_string, "'%s'", i ? "t" : "f");
break;
}
case SQL_C_DATE: {
DATE_STRUCT *ds = (DATE_STRUCT *) buffer;
st.m = ds->month;
st.d = ds->day;
st.y = ds->year;
break;
}
case SQL_C_TIME: {
TIME_STRUCT *ts = (TIME_STRUCT *) buffer;
st.hh = ts->hour;
st.mm = ts->minute;
st.ss = ts->second;
break;
}
case SQL_C_TIMESTAMP: {
TIMESTAMP_STRUCT *tss = (TIMESTAMP_STRUCT *) buffer;
st.m = tss->month;
st.d = tss->day;
st.y = tss->year;
st.hh = tss->hour;
st.mm = tss->minute;
st.ss = tss->second;
mylog("m=%d,d=%d,y=%d,hh=%d,mm=%d,ss=%d\n",
st.m, st.d, st.y, st.hh, st.mm, st.ss);
break;
}
default:
// error
stmt->errormsg = "Unrecognized C_parameter type in copy_statement_with_parameters";
stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR;
new_statement[npos] = '\0'; // just in case
return SQL_ERROR;
}
/* Now that the input data is in a neutral format, convert it to
the desired output format (sqltype)
*/
switch(param_sqltype) {
case SQL_CHAR:
case SQL_VARCHAR:
case SQL_LONGVARCHAR:
new_statement[npos++] = '\''; /* Open Quote */
/* it was a SQL_C_CHAR */
if (buf) {
convert_returns(buf, &new_statement[npos], used ? *used : SQL_NTS);
npos += strlen(&new_statement[npos]);
}
/* it was a numeric type */
else if (param_string[0] != '\0') {
strcpy(&new_statement[npos], param_string);
npos += strlen(param_string);
}
/* it was date,time,timestamp -- use m,d,y,hh,mm,ss */
else {
char *buf = convert_time(&st);
strcpy(&new_statement[npos], buf);
npos += strlen(buf);
}
new_statement[npos++] = '\''; /* Close Quote */
break;
case SQL_DATE:
if (buf && used) { /* copy char data to time */
my_strcpy(cbuf, sizeof(cbuf), buf, *used);
parse_datetime(cbuf, &st);
}
sprintf(tmp, "'%.2d-%.2d-%.4d'", st.m, st.d, st.y);
strcpy(&new_statement[npos], tmp);
npos += strlen(tmp);
break;
case SQL_TIME:
if (buf && used) { /* copy char data to time */
my_strcpy(cbuf, sizeof(cbuf), buf, *used);
parse_datetime(cbuf, &st);
}
sprintf(tmp, "'%.2d:%.2d:%.2d'", st.hh, st.mm, st.ss);
strcpy(&new_statement[npos], tmp);
npos += strlen(tmp);
break;
case SQL_TIMESTAMP: {
char *tbuf;
if (buf && used) {
my_strcpy(cbuf, sizeof(cbuf), buf, *used);
parse_datetime(cbuf, &st);
}
tbuf = convert_time(&st);
sprintf(&new_statement[npos], "'%s'", tbuf);
npos += strlen(tbuf) + 2;
break;
}
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY: /* non-ascii characters should be converted to octal */
new_statement[npos++] = '\''; /* Open Quote */
mylog("SQL_LONGVARBINARY: about to call convert_to_pgbinary, *used = %d\n", *used);
npos += convert_to_pgbinary(buf, &new_statement[npos], *used);
new_statement[npos++] = '\''; /* Close Quote */
break;
default: /* a numeric type */
strcpy(&new_statement[npos], param_string);
npos += strlen(param_string);
break;
}
} /* end, for */
// make sure new_statement is always null-terminated
new_statement[npos] = '\0';
return SQL_SUCCESS;
}
// This function returns a pointer to static memory!
char *
convert_escape(char *value)
{
char key[32], val[256];
static char escape[256];
SIMPLE_TIME st;
sscanf(value, "%[^'] '%[^']'", key, val);
mylog("convert_escape: key='%s', val='%s'\n", key, val);
if ( ! strncmp(key, "d", 1)) {
sscanf(val, "%4d-%2d-%2d", &st.y, &st.m, &st.d);
sprintf(escape, "'%.2d-%.2d-%.4d'", st.m, st.d, st.y);
} else if (! strncmp(key, "t", 1)) {
sprintf(escape, "'%s'", val);
} else if (! strncmp(key, "ts", 2)) {
sscanf(val, "%4d-%2d-%2d %2d:%2d:%2d", &st.y, &st.m, &st.d, &st.hh, &st.mm, &st.ss);
strcpy(escape, convert_time(&st));
}
else {
return NULL;
}
return escape;
}
int
monthToNumber(char *mon)
{
int m = 0;
if ( ! stricmp(mon, "Jan"))
m = 1;
else if ( ! stricmp(mon, "Feb"))
m = 2;
else if ( ! stricmp(mon, "Mar"))
m = 3;
else if ( ! stricmp(mon, "Apr"))
m = 4;
else if ( ! stricmp(mon, "May"))
m = 5;
else if ( ! stricmp(mon, "Jun"))
m = 6;
else if ( ! stricmp(mon, "Jul"))
m = 7;
else if ( ! stricmp(mon, "Aug"))
m = 8;
else if ( ! stricmp(mon, "Sep"))
m = 9;
else if ( ! stricmp(mon, "Oct"))
m = 10;
else if ( ! stricmp(mon, "Nov"))
m = 11;
else if ( ! stricmp(mon, "Dec"))
m = 12;
return m;
}
char *
convert_money(char *s)
{
size_t i = 0, out = 0;
for (i = 0; i < strlen(s); i++) {
if (s[i] == '$' || s[i] == ',' || s[i] == ')')
; // skip these characters
else if (s[i] == '(')
s[out++] = '-';
else
s[out++] = s[i];
}
s[out] = '\0';
return s;
}
/* Convert a discrete time into a localized string */
char *
convert_time(SIMPLE_TIME *st)
{
struct tm tim;
static char buf[1024];
mylog("convert_time: m=%d,d=%d,y=%d,hh=%d,mm=%d,ss=%d\n",
st->m, st->d, st->y, st->hh, st->mm, st->ss);
memset(&tim, 0, sizeof(tim));
tim.tm_mon = st->m - 1;
tim.tm_mday = st->d;
tim.tm_year = st->y - 1900;
tim.tm_hour = st->hh;
tim.tm_min = st->mm;
tim.tm_sec = st->ss;
/* Dont bother trying to figure out the day of week because
postgres will determine it correctly. However, the timezone
should be taken into account. $$$$
*/
// tim.tm_isdst = _daylight;
strftime(buf, sizeof(buf), "%b %d %H:%M:%S %Y",
&tim);
mylog("convert_time: buf = '%s'\n", buf);
return buf;
}
/* This function parses a character string for date/time info and fills in SIMPLE_TIME */
/* It does not zero out SIMPLE_TIME in case it is desired to initialize it with a value */
char
parse_datetime(char *buf, SIMPLE_TIME *st)
{
int y,m,d,hh,mm,ss;
int nf;
y = m = d = hh = mm = ss = 0;
if (buf[4] == '-') /* year first */
nf = sscanf(buf, "%4d-%2d-%2d %2d:%2d:%2d", &y,&m,&d,&hh,&mm,&ss);
else
nf = sscanf(buf, "%2d-%2d-%4d %2d:%2d:%2d", &m,&d,&y,&hh,&mm,&ss);
if (nf == 5 || nf == 6) {
st->y = y;
st->m = m;
st->d = d;
st->hh = hh;
st->mm = mm;
st->ss = ss;
return TRUE;
}
if (buf[4] == '-') /* year first */
nf = sscanf(buf, "%4d-%2d-%2d", &y, &m, &d);
else
nf = sscanf(buf, "%2d-%2d-%4d", &m, &d, &y);
if (nf == 3) {
st->y = y;
st->m = m;
st->d = d;
return TRUE;
}
nf = sscanf(buf, "%2d:%2d:%2d", &hh, &mm, &ss);
if (nf == 2 || nf == 3) {
st->hh = hh;
st->mm = mm;
st->ss = ss;
return TRUE;
}
return FALSE;
}
/* Change linefeed to carriage-return/linefeed */
char *
convert_linefeeds(char *si, char *dst, size_t max)
{
size_t i = 0, out = 0;
static char sout[TEXT_FIELD_SIZE+5];
char *p;
if (dst)
p = dst;
else {
p = sout;
max = sizeof(sout);
}
p[0] = '\0';
for (i = 0; i < strlen(si) && out < max-2; i++) {
if (si[i] == '\n') {
p[out++] = '\r';
p[out++] = '\n';
}
else
p[out++] = si[i];
}
p[out] = '\0';
return p;
}
/* Change carriage-return/linefeed to just linefeed */
char *
convert_returns(char *si, char *dst, int used)
{
size_t i = 0, out = 0, max;
static char sout[TEXT_FIELD_SIZE+5];
char *p;
if (dst)
p = dst;
else
p = sout;
p[0] = '\0';
if (used == SQL_NTS)
max = strlen(si);
else
max = used;
for (i = 0; i < max; i++) {
if (si[i] == '\r' && i+1 < strlen(si) && si[i+1] == '\n')
continue;
else
p[out++] = si[i];
}
p[out] = '\0';
return p;
}
int
convert_pgbinary_to_char(char *value, char *rgbValue, int cbValueMax)
{
return 0;
}
unsigned int
conv_from_octal(unsigned char *s)
{
int i, y=0;
for (i = 1; i <= 3; i++) {
y += (s[i] - 48) * (int) pow(8, 3-i);
}
return y;
}
// convert octal escapes to bytes
int
convert_from_pgbinary(unsigned char *value, unsigned char *rgbValue, int cbValueMax)
{
size_t i;
int o=0;
for (i = 0; i < strlen(value); ) {
if (value[i] == '\\') {
rgbValue[o] = conv_from_octal(&value[i]);
i += 4;
}
else {
rgbValue[o] = value[i++];
}
mylog("convert_from_pgbinary: i=%d, rgbValue[%d] = %d, %c\n", i, o, rgbValue[o], rgbValue[o]);
o++;
}
return o;
}
char *
conv_to_octal(unsigned char val)
{
int i;
static char x[6];
x[0] = '\\';
x[1] = '\\';
x[5] = '\0';
for (i = 4; i > 1; i--) {
x[i] = (val & 7) + 48;
val >>= 3;
}
return x;
}
// convert non-ascii bytes to octal escape sequences
int
convert_to_pgbinary(unsigned char *in, char *out, int len)
{
int i, o=0;
for (i = 0; i < len; i++) {
mylog("convert_to_pgbinary: in[%d] = %d, %c\n", i, in[i], in[i]);
if (in[i] < 32 || in[i] > 126) {
strcpy(&out[o], conv_to_octal(in[i]));
o += 5;
}
else
out[o++] = in[i];
}
mylog("convert_to_pgbinary: returning %d, out='%.*s'\n", o, o, out);
return o;
}

View File

@ -0,0 +1,47 @@
/* File: convert.h
*
* Description: See "convert.c"
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#ifndef __CONVERT_H__
#define __CONVERT_H__
#include "psqlodbc.h"
/* copy_and_convert results */
#define COPY_OK 0
#define COPY_UNSUPPORTED_TYPE 1
#define COPY_UNSUPPORTED_CONVERSION 2
#define COPY_RESULT_TRUNCATED 3
typedef struct {
int m;
int d;
int y;
int hh;
int mm;
int ss;
} SIMPLE_TIME;
int copy_and_convert_field_bindinfo(Int4 field_type, void *value, BindInfoClass *bic);
int copy_and_convert_field(Int4 field_type, void *value,
Int2 fCType, PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue);
int copy_statement_with_parameters(StatementClass *stmt);
char *convert_escape(char *value);
char *convert_money(char *s);
int monthToNumber(char *mon);
char *convert_time(SIMPLE_TIME *st);
char parse_datetime(char *buf, SIMPLE_TIME *st);
char *convert_linefeeds(char *s, char *dst, size_t max);
char *convert_returns(char *si, char *dst, int used);
int convert_pgbinary_to_char(char *value, char *rgbValue, int cbValueMax);
int convert_from_pgbinary(unsigned char *value, unsigned char *rgbValue, int cbValueMax);
int convert_to_pgbinary(unsigned char *in, char *out, int len);
#endif

View File

@ -0,0 +1,327 @@
/* Module: drvconn.c
*
* Description: This module contains only routines related to
* implementing SQLDriverConnect.
*
* Classes: n/a
*
* API functions: SQLDriverConnect
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include "psqlodbc.h"
#include "connection.h"
#include <winsock.h>
#include <sqlext.h>
#include <string.h>
#include <windows.h>
#include <windowsx.h>
#include <odbcinst.h>
#include "resource.h"
/* prototypes */
BOOL FAR PASCAL dconn_FDriverConnectProc(HWND hdlg, UINT wMsg, WPARAM wParam, LPARAM lParam);
RETCODE dconn_DoDialog(HWND hwnd, ConnInfo *ci);
void dconn_get_connect_attributes(UCHAR FAR *connect_string, ConnInfo *ci);
extern HINSTANCE NEAR s_hModule; /* Saved module handle. */
extern GLOBAL_VALUES globals;
RETCODE SQL_API SQLDriverConnect(
HDBC hdbc,
HWND hwnd,
UCHAR FAR *szConnStrIn,
SWORD cbConnStrIn,
UCHAR FAR *szConnStrOut,
SWORD cbConnStrOutMax,
SWORD FAR *pcbConnStrOut,
UWORD fDriverCompletion)
{
ConnectionClass *conn = (ConnectionClass *) hdbc;
ConnInfo *ci;
RETCODE dialog_result;
char connect_string[MAX_CONNECT_STRING];
int retval;
char password_required = FALSE;
mylog("**** SQLDriverConnect: fDriverCompletion=%d, connStrIn='%s'\n", fDriverCompletion, szConnStrIn);
if ( ! conn)
return SQL_INVALID_HANDLE;
qlog("conn=%u, SQLDriverConnect( in)='%s'\n", conn, szConnStrIn);
ci = &(conn->connInfo);
// Parse the connect string and fill in conninfo for this hdbc.
dconn_get_connect_attributes(szConnStrIn, ci);
// If the ConnInfo in the hdbc is missing anything,
// this function will fill them in from the registry (assuming
// of course there is a DSN given -- if not, it does nothing!)
CC_DSN_info(conn, CONN_DONT_OVERWRITE);
// Fill in any default parameters if they are not there.
CC_set_defaults(conn);
dialog:
ci->focus_password = password_required;
switch(fDriverCompletion) {
case SQL_DRIVER_PROMPT:
dialog_result = dconn_DoDialog(hwnd, ci);
if(dialog_result != SQL_SUCCESS) {
return dialog_result;
}
break;
case SQL_DRIVER_COMPLETE:
case SQL_DRIVER_COMPLETE_REQUIRED:
/* Password is not a required parameter. */
if( ci->username[0] == '\0' ||
ci->server[0] == '\0' ||
ci->database[0] == '\0' ||
ci->port[0] == '\0' ||
password_required) {
dialog_result = dconn_DoDialog(hwnd, ci);
if(dialog_result != SQL_SUCCESS) {
return dialog_result;
}
}
break;
case SQL_DRIVER_NOPROMPT:
break;
}
/* Password is not a required parameter unless authentication asks for it.
For now, I think its better to just let the application ask over and over until
a password is entered (the user can always hit Cancel to get out)
*/
if( ci->username[0] == '\0' ||
ci->server[0] == '\0' ||
ci->database[0] == '\0' ||
ci->port[0] == '\0') {
// (password_required && ci->password[0] == '\0'))
return SQL_NO_DATA_FOUND;
}
if(szConnStrOut) {
// return the completed string to the caller.
char got_dsn = (ci->dsn[0] != '\0');
sprintf(connect_string, "%s=%s;DATABASE=%s;SERVER=%s;PORT=%s;UID=%s;READONLY=%s;PWD=%s;PROTOCOL=%s;CONNSETTINGS=%s",
got_dsn ? "DSN" : "DRIVER",
got_dsn ? ci->dsn : ci->driver,
ci->database,
ci->server,
ci->port,
ci->username,
ci->readonly,
ci->password,
ci->protocol,
ci->conn_settings);
if(pcbConnStrOut) {
*pcbConnStrOut = strlen(connect_string);
}
strncpy_null(szConnStrOut, connect_string, cbConnStrOutMax);
}
mylog("szConnStrOut = '%s'\n", szConnStrOut);
qlog("conn=%u, SQLDriverConnect(out)='%s'\n", conn, szConnStrOut);
// do the actual connect
retval = CC_connect(conn, password_required);
if (retval < 0) { /* need a password */
if (fDriverCompletion == SQL_DRIVER_NOPROMPT)
return SQL_ERROR; /* need a password but not allowed to prompt so error */
else {
password_required = TRUE;
goto dialog;
}
}
else if (retval == 0) {
// error msg filled in above
return SQL_ERROR;
}
mylog("SQLDRiverConnect: returning success\n");
return SQL_SUCCESS;
}
RETCODE dconn_DoDialog(HWND hwnd, ConnInfo *ci)
{
int dialog_result;
mylog("dconn_DoDialog: ci = %u\n", ci);
if(hwnd) {
dialog_result = DialogBoxParam(s_hModule, MAKEINTRESOURCE(DRIVERCONNDIALOG),
hwnd, dconn_FDriverConnectProc, (LPARAM) ci);
if(!dialog_result || (dialog_result == -1)) {
return SQL_NO_DATA_FOUND;
} else {
return SQL_SUCCESS;
}
}
return SQL_ERROR;
}
BOOL FAR PASCAL dconn_FDriverConnectProc(
HWND hdlg,
UINT wMsg,
WPARAM wParam,
LPARAM lParam)
{
static ConnInfo *ci;
switch (wMsg) {
case WM_INITDIALOG:
ci = (ConnInfo *) lParam; // Save the ConnInfo for the "OK"
SetDlgItemText(hdlg, SERVER_EDIT, ci->server);
SetDlgItemText(hdlg, DATABASE_EDIT, ci->database);
SetDlgItemText(hdlg, USERNAME_EDIT, ci->username);
SetDlgItemText(hdlg, PASSWORD_EDIT, ci->password);
SetDlgItemText(hdlg, PORT_EDIT, ci->port);
CheckDlgButton(hdlg, READONLY_EDIT, atoi(ci->readonly));
CheckDlgButton(hdlg, PG62_EDIT, PROTOCOL_62(ci));
/* The driver connect dialog box allows manipulating this global variable */
CheckDlgButton(hdlg, COMMLOG_EDIT, globals.commlog);
if (ci->database[0] == '\0')
; /* default focus */
else if (ci->server[0] == '\0')
SetFocus(GetDlgItem(hdlg, SERVER_EDIT));
else if (ci->port[0] == '\0')
SetFocus(GetDlgItem(hdlg, PORT_EDIT));
else if (ci->username[0] == '\0')
SetFocus(GetDlgItem(hdlg, USERNAME_EDIT));
else if (ci->focus_password)
SetFocus(GetDlgItem(hdlg, PASSWORD_EDIT));
break;
case WM_COMMAND:
switch (GET_WM_COMMAND_ID(wParam, lParam)) {
case IDOK:
GetDlgItemText(hdlg, SERVER_EDIT, ci->server, sizeof(ci->server));
GetDlgItemText(hdlg, DATABASE_EDIT, ci->database, sizeof(ci->database));
GetDlgItemText(hdlg, USERNAME_EDIT, ci->username, sizeof(ci->username));
GetDlgItemText(hdlg, PASSWORD_EDIT, ci->password, sizeof(ci->password));
GetDlgItemText(hdlg, PORT_EDIT, ci->port, sizeof(ci->port));
sprintf(ci->readonly, "%d", IsDlgButtonChecked(hdlg, READONLY_EDIT));
if (IsDlgButtonChecked(hdlg, PG62_EDIT))
strcpy(ci->protocol, PG62);
else
ci->protocol[0] = '\0';
/* The driver connect dialog box allows manipulating this global variable */
globals.commlog = IsDlgButtonChecked(hdlg, COMMLOG_EDIT);
updateGlobals();
case IDCANCEL:
EndDialog(hdlg, GET_WM_COMMAND_ID(wParam, lParam) == IDOK);
return TRUE;
}
}
return FALSE;
}
void dconn_get_connect_attributes(UCHAR FAR *connect_string, ConnInfo *ci)
{
char *our_connect_string;
char *pair, *attribute, *value, *equals;
char *strtok_arg;
memset(ci, 0, sizeof(ConnInfo));
our_connect_string = strdup(connect_string);
strtok_arg = our_connect_string;
mylog("our_connect_string = '%s'\n", our_connect_string);
while(1) {
pair = strtok(strtok_arg, ";");
if(strtok_arg) {
strtok_arg = 0;
}
if(!pair) {
break;
}
equals = strchr(pair, '=');
if ( ! equals)
continue;
*equals = '\0';
attribute = pair; // ex. DSN
value = equals + 1; // ex. 'CEO co1'
mylog("attribute = '%s', value = '%s'\n", attribute, value);
if( !attribute || !value)
continue;
/*********************************************************/
/* PARSE ATTRIBUTES */
/*********************************************************/
if(stricmp(attribute, "DSN") == 0)
strcpy(ci->dsn, value);
else if(stricmp(attribute, "driver") == 0)
strcpy(ci->driver, value);
else if(stricmp(attribute, "uid") == 0)
strcpy(ci->username, value);
else if(stricmp(attribute, "pwd") == 0)
strcpy(ci->password, value);
else if ((stricmp(attribute, "server") == 0) ||
(stricmp(attribute, "servername") == 0))
strcpy(ci->server, value);
else if(stricmp(attribute, "port") == 0)
strcpy(ci->port, value);
else if(stricmp(attribute, "database") == 0)
strcpy(ci->database, value);
else if (stricmp(attribute, "readonly") == 0)
strcpy(ci->readonly, value);
else if (stricmp(attribute, "protocol") == 0)
strcpy(ci->protocol, value);
else if (stricmp(attribute, "connsettings") == 0)
strcpy(ci->conn_settings, value);
}
free(our_connect_string);
}

View File

@ -0,0 +1,416 @@
/* Module: environ.c
*
* Description: This module contains routines related to
* the environment, such as storing connection handles,
* and returning errors.
*
* Classes: EnvironmentClass (Functions prefix: "EN_")
*
* API functions: SQLAllocEnv, SQLFreeEnv, SQLError
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include "environ.h"
#include "connection.h"
#include "statement.h"
#include <stdlib.h>
#include <malloc.h>
/* The one instance of the handles */
ConnectionClass *conns[MAX_CONNECTIONS];
RETCODE SQL_API SQLAllocEnv(HENV FAR *phenv)
{
mylog("**** in SQLAllocEnv ** \n");
*phenv = (HENV) EN_Constructor();
if ( ! *phenv) {
*phenv = SQL_NULL_HENV;
return SQL_ERROR;
}
mylog("** exit SQLAllocEnv: phenv = %u **\n", *phenv);
return SQL_SUCCESS;
}
RETCODE SQL_API SQLFreeEnv(HENV henv)
{
EnvironmentClass *env = (EnvironmentClass *) henv;
mylog("**** in SQLFreeEnv: env = %u ** \n", env);
if (env && EN_Destructor(env)) {
mylog(" ok\n");
return SQL_SUCCESS;
}
mylog(" error\n");
return SQL_ERROR;
}
// Returns the next SQL error information.
RETCODE SQL_API SQLError(
HENV henv,
HDBC hdbc,
HSTMT hstmt,
UCHAR FAR *szSqlState,
SDWORD FAR *pfNativeError,
UCHAR FAR *szErrorMsg,
SWORD cbErrorMsgMax,
SWORD FAR *pcbErrorMsg)
{
char *msg;
int status;
mylog("**** SQLError: henv=%u, hdbc=%u, hstmt=%u\n", henv, hdbc, hstmt);
if (SQL_NULL_HSTMT != hstmt) {
// CC: return an error of a hstmt
StatementClass *stmt = (StatementClass *) hstmt;
if (NULL == stmt)
return SQL_INVALID_HANDLE;
if (SC_get_error(stmt, &status, &msg)) {
mylog("SC_get_error: status = %d, msg = #%s#\n", status, msg);
if (NULL == msg) {
if (NULL != szSqlState)
strcpy(szSqlState, "00000");
if (NULL != pcbErrorMsg)
*pcbErrorMsg = 0;
if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
szErrorMsg[0] = '\0';
return SQL_NO_DATA_FOUND;
}
if (NULL != pcbErrorMsg)
*pcbErrorMsg = (SWORD)strlen(msg);
if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
strncpy_null(szErrorMsg, msg, cbErrorMsgMax);
if (NULL != pfNativeError)
*pfNativeError = status;
if (NULL != szSqlState)
switch (status) {
// now determine the SQLSTATE to be returned
case STMT_TRUNCATED:
strcpy(szSqlState, "01004");
// data truncated
break;
case STMT_INFO_ONLY:
strcpy(szSqlState, "00000");
// just information that is returned, no error
break;
case STMT_EXEC_ERROR:
strcpy(szSqlState, "08S01");
// communication link failure
break;
case STMT_STATUS_ERROR:
case STMT_SEQUENCE_ERROR:
strcpy(szSqlState, "S1010");
// Function sequence error
break;
case STMT_NO_MEMORY_ERROR:
strcpy(szSqlState, "S1001");
// memory allocation failure
break;
case STMT_COLNUM_ERROR:
strcpy(szSqlState, "S1002");
// invalid column number
break;
case STMT_NO_STMTSTRING:
strcpy(szSqlState, "S1001");
// having no stmtstring is also a malloc problem
break;
case STMT_ERROR_TAKEN_FROM_BACKEND:
strcpy(szSqlState, "S1000");
// general error
break;
case STMT_INTERNAL_ERROR:
strcpy(szSqlState, "S1000");
// general error
break;
case STMT_NOT_IMPLEMENTED_ERROR:
strcpy(szSqlState, "S1C00"); // == 'driver not capable'
break;
case STMT_OPTION_OUT_OF_RANGE_ERROR:
strcpy(szSqlState, "S1092");
break;
case STMT_BAD_PARAMETER_NUMBER_ERROR:
strcpy(szSqlState, "S1093");
break;
case STMT_INVALID_COLUMN_NUMBER_ERROR:
strcpy(szSqlState, "S1002");
break;
case STMT_RESTRICTED_DATA_TYPE_ERROR:
strcpy(szSqlState, "07006");
break;
case STMT_INVALID_CURSOR_STATE_ERROR:
strcpy(szSqlState, "24000");
break;
case STMT_OPTION_VALUE_CHANGED:
strcpy(szSqlState, "01S02");
break;
default:
strcpy(szSqlState, "S1000");
// also a general error
break;
}
mylog(" szSqlState = '%s', szError='%s'\n", szSqlState, szErrorMsg);
} else {
if (NULL != szSqlState)
strcpy(szSqlState, "00000");
if (NULL != pcbErrorMsg)
*pcbErrorMsg = 0;
if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
szErrorMsg[0] = '\0';
mylog(" returning NO_DATA_FOUND\n");
return SQL_NO_DATA_FOUND;
}
return SQL_SUCCESS;
} else if (SQL_NULL_HDBC != hdbc) {
ConnectionClass *conn = (ConnectionClass *) hdbc;
mylog("calling CC_get_error\n");
if (CC_get_error(conn, &status, &msg)) {
mylog("CC_get_error: status = %d, msg = #%s#\n", status, msg);
if (NULL == msg) {
if (NULL != szSqlState)
strcpy(szSqlState, "00000");
if (NULL != pcbErrorMsg)
*pcbErrorMsg = 0;
if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
szErrorMsg[0] = '\0';
return SQL_NO_DATA_FOUND;
}
if (NULL != pcbErrorMsg)
*pcbErrorMsg = (SWORD)strlen(msg);
if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
strncpy_null(szErrorMsg, msg, cbErrorMsgMax);
if (NULL != pfNativeError)
*pfNativeError = status;
if (NULL != szSqlState)
switch(status) {
case CONN_INIREAD_ERROR:
strcpy(szSqlState, "IM002");
// data source not found
break;
case CONN_OPENDB_ERROR:
strcpy(szSqlState, "08001");
// unable to connect to data source
break;
case CONN_INVALID_AUTHENTICATION:
case CONN_AUTH_TYPE_UNSUPPORTED:
strcpy(szSqlState, "28000");
break;
case CONN_STMT_ALLOC_ERROR:
strcpy(szSqlState, "S1001");
// memory allocation failure
break;
case CONN_IN_USE:
strcpy(szSqlState, "S1000");
// general error
break;
case CONN_UNSUPPORTED_OPTION:
strcpy(szSqlState, "IM001");
// driver does not support this function
case CONN_INVALID_ARGUMENT_NO:
strcpy(szSqlState, "S1009");
// invalid argument value
break;
case CONN_TRANSACT_IN_PROGRES:
strcpy(szSqlState, "S1010");
// when the user tries to switch commit mode in a transaction
// -> function sequence error
break;
case CONN_NO_MEMORY_ERROR:
strcpy(szSqlState, "S1001");
break;
case CONN_NOT_IMPLEMENTED_ERROR:
strcpy(szSqlState, "S1C00");
break;
default:
strcpy(szSqlState, "S1000");
// general error
break;
}
} else {
mylog("CC_Get_error returned nothing.\n");
if (NULL != szSqlState)
strcpy(szSqlState, "00000");
if (NULL != pcbErrorMsg)
*pcbErrorMsg = 0;
if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
szErrorMsg[0] = '\0';
return SQL_NO_DATA_FOUND;
}
return SQL_SUCCESS;
} else if (SQL_NULL_HENV != henv) {
EnvironmentClass *env = (EnvironmentClass *)henv;
if(EN_get_error(env, &status, &msg)) {
mylog("EN_get_error: status = %d, msg = #%s#\n", status, msg);
if (NULL == msg) {
if (NULL != szSqlState)
strcpy(szSqlState, "00000");
if (NULL != pcbErrorMsg)
*pcbErrorMsg = 0;
if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
szErrorMsg[0] = '\0';
return SQL_NO_DATA_FOUND;
}
if (NULL != pcbErrorMsg)
*pcbErrorMsg = (SWORD)strlen(msg);
if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
strncpy_null(szErrorMsg, msg, cbErrorMsgMax);
if (NULL != pfNativeError)
*pfNativeError = status;
if(szSqlState) {
switch(status) {
case ENV_ALLOC_ERROR:
// memory allocation failure
strcpy(szSqlState, "S1001");
break;
default:
strcpy(szSqlState, "S1000");
// general error
break;
}
}
} else {
if (NULL != szSqlState)
strcpy(szSqlState, "00000");
if (NULL != pcbErrorMsg)
*pcbErrorMsg = 0;
if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
szErrorMsg[0] = '\0';
return SQL_NO_DATA_FOUND;
}
return SQL_SUCCESS;
}
if (NULL != szSqlState)
strcpy(szSqlState, "00000");
if (NULL != pcbErrorMsg)
*pcbErrorMsg = 0;
if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
szErrorMsg[0] = '\0';
return SQL_NO_DATA_FOUND;
}
/*********************************************************************/
/*
* EnvironmentClass implementation
*/
EnvironmentClass
*EN_Constructor(void)
{
EnvironmentClass *rv;
rv = (EnvironmentClass *) malloc(sizeof(EnvironmentClass));
if( rv) {
rv->errormsg = 0;
rv->errornumber = 0;
}
return rv;
}
char
EN_Destructor(EnvironmentClass *self)
{
int lf;
char rv = 1;
mylog("in EN_Destructor, self=%u\n", self);
// the error messages are static strings distributed throughout
// the source--they should not be freed
/* Free any connections belonging to this environment */
for (lf = 0; lf < MAX_CONNECTIONS; lf++) {
if (conns[lf] && conns[lf]->henv == self)
rv = rv && CC_Destructor(conns[lf]);
}
mylog("exit EN_Destructor: rv = %d\n", rv);
return rv;
}
char
EN_get_error(EnvironmentClass *self, int *number, char **message)
{
if(self && self->errormsg && self->errornumber) {
*message = self->errormsg;
*number = self->errornumber;
self->errormsg = 0;
self->errornumber = 0;
return 1;
} else {
return 0;
}
}
char
EN_add_connection(EnvironmentClass *self, ConnectionClass *conn)
{
int i;
mylog("EN_add_connection: self = %u, conn = %u\n", self, conn);
for (i = 0; i < MAX_CONNECTIONS; i++) {
if ( ! conns[i]) {
conn->henv = self;
conns[i] = conn;
mylog(" added at i =%d, conn->henv = %u, conns[i]->henv = %u\n",
i, conn->henv, conns[i]->henv);
return TRUE;
}
}
return FALSE;
}
char
EN_remove_connection(EnvironmentClass *self, ConnectionClass *conn)
{
int i;
for (i = 0; i < MAX_CONNECTIONS; i++)
if (conns[i] == conn && conns[i]->status != CONN_EXECUTING) {
conns[i] = NULL;
return TRUE;
}
return FALSE;
}

View File

@ -0,0 +1,32 @@
/* File: environ.h
*
* Description: See "environ.c"
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#ifndef __ENVIRON_H__
#define __ENVIRON_H__
#include "psqlodbc.h"
#include <windows.h>
#include <sql.h>
#define ENV_ALLOC_ERROR 1
/********** Environment Handle *************/
struct EnvironmentClass_ {
char *errormsg;
int errornumber;
};
/* Environment prototypes */
EnvironmentClass *EN_Constructor(void);
char EN_Destructor(EnvironmentClass *self);
char EN_get_error(EnvironmentClass *self, int *number, char **message);
char EN_add_connection(EnvironmentClass *self, ConnectionClass *conn);
char EN_remove_connection(EnvironmentClass *self, ConnectionClass *conn);
#endif

View File

@ -0,0 +1,535 @@
/* Module: execute.c
*
* Description: This module contains routines related to
* preparing and executing an SQL statement.
*
* Classes: n/a
*
* API functions: SQLPrepare, SQLExecute, SQLExecDirect, SQLTransact,
* SQLCancel, SQLNativeSql, SQLParamData, SQLPutData
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include "psqlodbc.h"
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <sqlext.h>
#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;
ConnectionClass *conn;
int i, retval;
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;
}
/* The bound parameters could have possibly changed since the last execute
of this statement? Therefore check for params and re-copy.
*/
stmt->data_at_exec = -1;
for (i = 0; i < stmt->parameters_allocated; i++) {
/* Check for data at execution parameters */
if ( stmt->parameters[i].data_at_exec == TRUE) {
if (stmt->data_at_exec < 0)
stmt->data_at_exec = 1;
else
stmt->data_at_exec++;
}
}
// If there are some data at execution parameters, return need data
// SQLParamData and SQLPutData will be used to send params and execute the statement.
if (stmt->data_at_exec > 0)
return SQL_NEED_DATA;
mylog("SQLExecute: copying statement params: trans_status=%d, len=%d, stmt='%s'\n", conn->transact_status, strlen(stmt->statement), stmt->statement);
// Create the statement with parameters substituted.
retval = copy_statement_with_parameters(stmt);
if( retval != SQL_SUCCESS)
/* error msg passed from above */
return retval;
mylog(" stmt_with_params = '%s'\n", stmt->stmt_with_params);
return SC_execute(stmt);
}
// - - - - - - - - -
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 <MAX_CONNECTIONS; 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;
// Check if this can handle canceling in the middle of a SQLPutData?
if ( ! stmt)
return SQL_INVALID_HANDLE;
// Not in the middle of SQLParamData/SQLPutData so cancel like a close.
if (stmt->data_at_exec < 0)
return SQLFreeStmt(hstmt, SQL_CLOSE);
// In the middle of SQLParamData/SQLPutData, so cancel that.
// Note, any previous data-at-exec buffers will be freed in the recycle
// if they call SQLExecDirect or SQLExecute again.
stmt->data_at_exec = -1;
stmt->current_exec_param = -1;
stmt->put_data = FALSE;
}
// - - - - - - - - -
// 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)
{
StatementClass *stmt = (StatementClass *) hstmt;
int i, retval;
if ( ! stmt)
return SQL_INVALID_HANDLE;
if (stmt->data_at_exec < 0) {
stmt->errornumber = STMT_SEQUENCE_ERROR;
stmt->errormsg = "No execution-time parameters for this statement";
return SQL_ERROR;
}
if (stmt->data_at_exec > stmt->parameters_allocated) {
stmt->errornumber = STMT_SEQUENCE_ERROR;
stmt->errormsg = "Too many execution-time parameters were present";
return SQL_ERROR;
}
/* Done, now copy the params and then execute the statement */
if (stmt->data_at_exec == 0) {
retval = copy_statement_with_parameters(stmt);
if (retval != SQL_SUCCESS)
return retval;
return SC_execute(stmt);
}
/* At least 1 data at execution parameter, so Fill in the token value */
for (i = 0; i < stmt->parameters_allocated; i++) {
if (stmt->parameters[i].data_at_exec == TRUE) {
stmt->data_at_exec--;
stmt->current_exec_param = i;
stmt->put_data = FALSE;
*prgbValue = stmt->parameters[i].buffer; /* token */
}
}
return SQL_NEED_DATA;
}
// - - - - - - - - -
// 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;
char *buffer;
SDWORD *used;
int old_pos;
if ( ! stmt)
return SQL_INVALID_HANDLE;
if (stmt->current_exec_param < 0) {
stmt->errornumber = STMT_SEQUENCE_ERROR;
stmt->errormsg = "Previous call was not SQLPutData or SQLParamData";
return SQL_ERROR;
}
if ( ! stmt->put_data) { /* first call */
mylog("SQLPutData: (1) cbValue = %d\n", cbValue);
stmt->put_data = TRUE;
used = (SDWORD *) malloc(sizeof(SDWORD));
if ( ! used) {
stmt->errornumber = STMT_NO_MEMORY_ERROR;
stmt->errormsg = "Out of memory in SQLPutData (1)";
return SQL_ERROR;
}
*used = cbValue;
stmt->parameters[stmt->current_exec_param].EXEC_used = used;
if (cbValue == SQL_NULL_DATA)
return SQL_SUCCESS;
if (cbValue == SQL_NTS) {
buffer = strdup(rgbValue);
if ( ! buffer) {
stmt->errornumber = STMT_NO_MEMORY_ERROR;
stmt->errormsg = "Out of memory in SQLPutData (2)";
return SQL_ERROR;
}
}
else {
buffer = malloc(cbValue + 1);
if ( ! buffer) {
stmt->errornumber = STMT_NO_MEMORY_ERROR;
stmt->errormsg = "Out of memory in SQLPutData (2)";
return SQL_ERROR;
}
memcpy(buffer, rgbValue, cbValue);
buffer[cbValue] = '\0';
}
stmt->parameters[stmt->current_exec_param].EXEC_buffer = buffer;
}
else { /* calling SQLPutData more than once */
mylog("SQLPutData: (>1) cbValue = %d\n", cbValue);
used = stmt->parameters[stmt->current_exec_param].EXEC_used;
buffer = stmt->parameters[stmt->current_exec_param].EXEC_buffer;
if (cbValue == SQL_NTS) {
buffer = realloc(buffer, strlen(buffer) + strlen(rgbValue) + 1);
if ( ! buffer) {
stmt->errornumber = STMT_NO_MEMORY_ERROR;
stmt->errormsg = "Out of memory in SQLPutData (3)";
return SQL_ERROR;
}
strcat(buffer, rgbValue);
mylog(" cbValue = SQL_NTS: strlen(buffer) = %d\n", strlen(buffer));
*used = cbValue;
}
else if (cbValue > 0) {
old_pos = *used;
*used += cbValue;
mylog(" cbValue = %d, old_pos = %d, *used = %d\n", cbValue, old_pos, *used);
buffer = realloc(buffer, *used + 1);
if ( ! buffer) {
stmt->errornumber = STMT_NO_MEMORY_ERROR;
stmt->errormsg = "Out of memory in SQLPutData (3)";
return SQL_ERROR;
}
memcpy(&buffer[old_pos], rgbValue, cbValue);
buffer[*used] = '\0';
}
else
return SQL_ERROR;
/* reassign buffer incase realloc moved it */
stmt->parameters[stmt->current_exec_param].EXEC_buffer = buffer;
}
return SQL_SUCCESS;
}

2180
src/interfaces/odbc/info.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,962 @@
GNU LIBRARY GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1991 Free Software Foundation, Inc.
675 Mass Ave, Cambridge, MA 02139, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the library GPL. It is
numbered 2 because it goes with version 2 of the ordinary GPL.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Library General Public License, applies to some
specially designated Free Software Foundation software, and to any
other libraries whose authors decide to use it. You can use it for
your libraries, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if
you distribute copies of the library, or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link a program with the library, you must provide
complete object files to the recipients so that they can relink them
with the library, after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
Our method of protecting your rights has two steps: (1) copyright
the library, and (2) offer you this license which gives you legal
permission to copy, distribute and/or modify the library.
Also, for each distributor's protection, we want to make certain
that everyone understands that there is no warranty for this free
library. If the library is modified by someone else and passed on, we
want its recipients to know that what they have is not the original
version, so that any problems introduced by others will not reflect on
the original authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that companies distributing free
software will individually obtain patent licenses, thus in effect
transforming the program into proprietary software. To prevent this,
we have made it clear that any patent must be licensed for everyone's
free use or not licensed at all.
Most GNU software, including some libraries, is covered by the ordinary
GNU General Public License, which was designed for utility programs. This
license, the GNU Library General Public License, applies to certain
designated libraries. This license is quite different from the ordinary
one; be sure to read it in full, and don't assume that anything in it is
the same as in the ordinary license.
The reason we have a separate public license for some libraries is that
they blur the distinction we usually make between modifying or adding to a
program and simply using it. Linking a program with a library, without
changing the library, is in some sense simply using the library, and is
analogous to running a utility program or application program. However, in
a textual and legal sense, the linked executable is a combined work, a
derivative of the original library, and the ordinary General Public License
treats it as such.
Because of this blurred distinction, using the ordinary General
Public License for libraries did not effectively promote software
sharing, because most developers did not use the libraries. We
concluded that weaker conditions might promote sharing better.
However, unrestricted linking of non-free programs would deprive the
users of those programs of all benefit from the free status of the
libraries themselves. This Library General Public License is intended to
permit developers of non-free programs to use free libraries, while
preserving your freedom as a user of such programs to change the free
libraries that are incorporated in them. (We have not seen how to achieve
this as regards changes in header files, but we have achieved it as regards
changes in the actual functions of the Library.) The hope is that this
will lead to faster development of free libraries.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, while the latter only
works together with the library.
Note that it is possible for a library to be covered by the ordinary
General Public License rather than by this special one.
GNU LIBRARY GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library which
contains a notice placed by the copyright holder or other authorized
party saying it may be distributed under the terms of this Library
General Public License (also called "this License"). Each licensee is
addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also compile or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
c) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
d) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the source code distributed need not include anything that is normally
distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Library General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
Appendix: How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

224
src/interfaces/odbc/misc.c Normal file
View File

@ -0,0 +1,224 @@
/* Module: misc.c
*
* Description: This module contains miscellaneous routines
* such as for debugging/logging and string functions.
*
* Classes: n/a
*
* API functions: none
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include <stdio.h>
#include <windows.h>
#include <sql.h>
#include "psqlodbc.h"
extern GLOBAL_VALUES globals;
#ifdef MY_LOG
#include <varargs.h>
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 <varargs.h>
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);
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';
}
else {
memcpy(dst, src, dst_len-1);
dst[dst_len-1] = '\0'; /* truncated */
return STRCPY_TRUNCATED;
}
}
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++) {
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;
}

View File

@ -0,0 +1,58 @@
/* File: misc.h
*
* Description: See "misc.c"
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#ifndef __MISC_H__
#define __MISC_H__
#include <stdio.h>
/* 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),
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

View File

@ -0,0 +1,35 @@
/********************************************************************
PSQLODBC.DLL - A library to talk to the PostgreSQL DBMS using ODBC.
Copyright (C) 1998; Insight Distribution Systems
The code contained in this library is based on code written by
Christian Czezatke and Dan McGuirk, (C) 1996.
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library (see "license.txt"); if not, write to
the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
02139, USA.
How to contact the author:
email: byronn@insightdist.com (Byron Nikolaidis)
***********************************************************************/

View File

@ -0,0 +1,210 @@
/* Module: options.c
*
* Description: This module contains routines for getting/setting
* connection and statement options.
*
* Classes: n/a
*
* API functions: SQLSetConnectOption, SQLSetStmtOption, SQLGetConnectOption,
* SQLGetStmtOption
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include "psqlodbc.h"
#include <windows.h>
#include <sql.h>
#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;
}
// - - - - - - - - -

View File

@ -0,0 +1,441 @@
/* Module: pgtypes.c
*
* Description: This module contains routines for getting information
* about the supported Postgres data types. Only the function
* pgtype_to_sqltype() returns an unknown condition. All other
* functions return a suitable default so that even data types that
* are not directly supported can be used (it is handled as char data).
*
* Classes: n/a
*
* API functions: none
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include "psqlodbc.h"
#include "pgtypes.h"
#include <windows.h>
#include <sql.h>
#include <sqlext.h>
/* 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,
PG_TYPE_BYTEA,
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:
case PG_TYPE_NAME:
case PG_TYPE_VARCHAR: return SQL_VARCHAR;
case PG_TYPE_TEXT: return SQL_LONGVARCHAR;
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;
case PG_TYPE_BYTEA: return SQL_C_BINARY;
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";
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
override this length with the atttypmod length from pg_attribute
*/
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;
case PG_TYPE_NAME: return 32;
case PG_TYPE_VARCHAR:
case PG_TYPE_BPCHAR: return MAX_VARCHAR_SIZE;
case PG_TYPE_INT2: return 5;
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
override this length with the atttypmod length from pg_attribute
*/
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;
case PG_TYPE_NAME: return 32;
case PG_TYPE_VARCHAR:
case PG_TYPE_BPCHAR: return MAX_VARCHAR_SIZE;
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 */
}
}
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;
}
}

View File

@ -0,0 +1,81 @@
/* File: pgtypes.h
*
* Description: See "pgtypes.c"
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#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

View File

@ -0,0 +1,146 @@
/* Module: psqlodbc.c
*
* Description: This module contains the main entry point (DllMain) for the library.
* It also contains functions to get and set global variables for the
* driver in the registry.
*
* Classes: n/a
*
* API functions: none
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include "psqlodbc.h"
#include <winsock.h>
#include <windows.h>
#include <sql.h>
#include <odbcinst.h>
HINSTANCE NEAR s_hModule; /* Saved module handle. */
GLOBAL_VALUES globals;
/* This function reads the ODBCINST.INI portion of
the registry and gets any driver defaults.
*/
void getGlobalDefaults(void)
{
char temp[128];
// 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);
else
globals.fetch_max = FETCH_MAX;
// 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);
else
globals.socket_buffersize = SOCK_BUFFER_SIZE;
// Debug is stored in the driver section
SQLGetPrivateProfileString(DBMS_NAME, INI_DEBUG, "0",
temp, sizeof(temp), ODBCINST_INI);
globals.debug = atoi(temp);
// CommLog is stored in the driver section
SQLGetPrivateProfileString(DBMS_NAME, INI_COMMLOG, "0",
temp, sizeof(temp), ODBCINST_INI);
globals.commlog = atoi(temp);
// Optimizer is stored in the driver section only (OFF, ON, or ON=x)
SQLGetPrivateProfileString(DBMS_NAME, INI_OPTIMIZER, "",
globals.optimizer, sizeof(globals.optimizer), ODBCINST_INI);
// ConnSettings is stored in the driver section and per datasource for override
SQLGetPrivateProfileString(DBMS_NAME, INI_CONNSETTINGS, "",
globals.conn_settings, sizeof(globals.conn_settings), ODBCINST_INI);
}
/* This function writes any global parameters (that can be manipulated)
to the ODBCINST.INI portion of the registry
*/
void updateGlobals(void)
{
char tmp[128];
sprintf(tmp, "%d", globals.commlog);
SQLWritePrivateProfileString(DBMS_NAME,
INI_COMMLOG, tmp, ODBCINST_INI);
}
/* 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;
}

View File

@ -0,0 +1,60 @@
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

View File

@ -0,0 +1,120 @@
/* File: psqlodbc.h
*
* Description: This file contains defines and declarations that are related to
* the entire driver.
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#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
#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 */
#define MAX_CONNECTIONS 128 /* conns per environment (arbitrary) */
#define MAX_FIELDS 512
#define BYTELEN 8
#define VARHDRSZ sizeof(Int4)
/* Registry length limits */
#define LARGE_REGISTRY_LEN 4096 /* used for special cases */
#define MEDIUM_REGISTRY_LEN 128 /* normal size for user,database,etc. */
#define SMALL_REGISTRY_LEN 10 /* for 1/0 settings */
/* 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"
#define PG62 "6.2" /* "Protocol" key setting to force Postgres 6.2 */
/* 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 */
#define INI_COMMLOG "CommLog" /* Communication to backend logging */
#define INI_PROTOCOL "Protocol" /* What protocol (6.2) */
#define INI_OPTIMIZER "Optimizer" /* Use backend genetic optimizer */
#define INI_CONNSETTINGS "ConnSettings" /* Anything to send to backend on successful connection */
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;
typedef struct GlobalValues_
{
int fetch_max;
int socket_buffersize;
int debug;
int commlog;
char optimizer[MEDIUM_REGISTRY_LEN];
char conn_settings[LARGE_REGISTRY_LEN];
} GLOBAL_VALUES;
/* sizes */
#define TEXT_FIELD_SIZE 4094 /* size of text fields (not including null term) */
#define MAX_VARCHAR_SIZE 254 /* maximum size of a varchar (not including null term) */
/* global prototypes */
void updateGlobals(void);
#include "misc.h"
#endif

View File

@ -0,0 +1,205 @@
//Microsoft Developer Studio generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE DISCARDABLE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE DISCARDABLE
BEGIN
"#include ""afxres.h""\r\n"
"\0"
END
3 TEXTINCLUDE DISCARDABLE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
DRIVERCONNDIALOG DIALOG DISCARDABLE 0, 0, 269, 133
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "PostgreSQL Connection"
FONT 8, "MS Sans Serif"
BEGIN
RTEXT "&Database:",IDC_STATIC,16,25,37,8
EDITTEXT DATABASE_EDIT,55,25,72,12,ES_AUTOHSCROLL
RTEXT "&Server:",IDC_STATIC,26,40,27,8
EDITTEXT SERVER_EDIT,55,40,72,12,ES_AUTOHSCROLL
RTEXT "&Port:",IDC_STATIC,150,40,20,8
EDITTEXT PORT_EDIT,172,40,72,12,ES_AUTOHSCROLL
RTEXT "&User Name:",IDC_STATIC,16,56,37,8
EDITTEXT USERNAME_EDIT,55,56,72,12,ES_AUTOHSCROLL
RTEXT "Pass&word:",IDC_STATIC,137,56,33,8
EDITTEXT PASSWORD_EDIT,172,56,72,12,ES_PASSWORD | ES_AUTOHSCROLL
GROUPBOX "Options:",IDC_STATIC,25,71,200,25
CONTROL "&ReadOnly:",READONLY_EDIT,"Button",BS_AUTOCHECKBOX |
BS_LEFTTEXT | BS_RIGHT | WS_GROUP | WS_TABSTOP,45,80,45,
14
CONTROL "&CommLog (Global):",COMMLOG_EDIT,"Button",
BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_TABSTOP,
100,80,75,14
CONTROL "6.2",PG62_EDIT,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT |
BS_RIGHT | WS_TABSTOP,185,80,25,14
DEFPUSHBUTTON "OK",IDOK,84,108,40,14,WS_GROUP
PUSHBUTTON "Cancel",IDCANCEL,146,108,40,14
CTEXT "Please supply any missing information needed to connect.",
IDC_STATIC,40,7,188,11
END
CONFIGDSN DIALOG DISCARDABLE 65, 43, 292, 151
STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION |
WS_SYSMENU
CAPTION "PostgreSQL Driver Setup"
FONT 8, "MS Sans Serif"
BEGIN
RTEXT "&Data Source:",IDC_DSNAMETEXT,5,30,50,12,NOT WS_GROUP
EDITTEXT IDC_DSNAME,57,30,72,12,ES_AUTOHSCROLL | WS_GROUP
RTEXT "Des&cription:",IDC_STATIC,135,30,39,12,NOT WS_GROUP
EDITTEXT IDC_DESC,175,30,108,12,ES_AUTOHSCROLL
RTEXT "Data&base:",IDC_STATIC,17,45,38,12,NOT WS_GROUP
EDITTEXT IDC_DATABASE,57,45,72,12,ES_AUTOHSCROLL
RTEXT "&Server:",IDC_STATIC,27,60,29,12,NOT WS_GROUP
EDITTEXT IDC_SERVER,57,60,72,12,ES_AUTOHSCROLL
RTEXT "&Port:",IDC_STATIC,153,60,22,12
EDITTEXT IDC_PORT,175,60,37,12,ES_AUTOHSCROLL
RTEXT "&User Name:",IDC_STATIC,17,75,39,12
EDITTEXT IDC_USER,57,75,72,12,ES_AUTOHSCROLL
RTEXT "Pass&word:",IDC_STATIC,141,75,34,12
EDITTEXT IDC_PASSWORD,175,75,72,12,ES_PASSWORD | ES_AUTOHSCROLL
GROUPBOX "Options:",IDC_STATIC,35,92,205,25
CONTROL "&ReadOnly:",IDC_READONLY,"Button",BS_AUTOCHECKBOX |
BS_LEFTTEXT | BS_RIGHT | WS_GROUP | WS_TABSTOP,50,100,45,
14
CONTROL "&CommLog (Global):",IDC_COMMLOG,"Button",
BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_TABSTOP,
105,100,75,14
CONTROL "6.2",IDC_PG62,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT |
BS_RIGHT | WS_TABSTOP,195,100,25,14
DEFPUSHBUTTON "OK",IDOK,85,129,40,14,WS_GROUP
PUSHBUTTON "Cancel",IDCANCEL,145,129,40,14
CTEXT "Change data source name, description, or options. Then choose OK.",
IDC_STATIC,44,5,180,17
END
/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO DISCARDABLE
BEGIN
DRIVERCONNDIALOG, DIALOG
BEGIN
RIGHTMARGIN, 268
END
END
#endif // APSTUDIO_INVOKED
#ifndef _MAC
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 6,30,0,0
PRODUCTVERSION 6,30,0,0
FILEFLAGSMASK 0x3L
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "Comments", "PostgreSQL ODBC driver for Windows 95\0"
VALUE "CompanyName", "Insight Distribution Systems\0"
VALUE "FileDescription", "PostgreSQL Driver\0"
VALUE "FileVersion", " 6.30.0000\0"
VALUE "InternalName", "psqlodbc\0"
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"
VALUE "OriginalFilename", "psqlodbc.dll\0"
VALUE "ProductName", "Microsoft Open Database Connectivity\0"
VALUE "ProductVersion", " 6.30.0000\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
#endif // !_MAC
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE DISCARDABLE
BEGIN
IDS_BADDSN "Invalid DSN entry, please recheck."
IDS_MSGTITLE "Invalid DSN"
END
#endif // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@ -0,0 +1,472 @@
/* Module: qresult.c
*
* Description: This module contains functions related to
* managing result information (i.e, fetching rows from the backend,
* managing the tuple cache, etc.) and retrieving it.
* Depending on the situation, a QResultClass will hold either data
* from the backend or a manually built result (see "qresult.h" to
* see which functions/macros are for manual or backend results.
* For manually built results, the QResultClass simply points to
* TupleList and ColumnInfo structures, which actually hold the data.
*
* Classes: QResultClass (Functions prefix: "QR_")
*
* API functions: none
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include "qresult.h"
#include "misc.h"
#include <stdio.h>
extern GLOBAL_VALUES globals;
/* Used for building a Manual Result only */
/* 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)
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);
/* 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;
}

View File

@ -0,0 +1,105 @@
/* File: qresult.h
*
* Description: See "qresult.c"
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#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
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))
#define QR_get_value_backend(self, fieldno) (self->tupleField[fieldno].value)
/* These functions are used by both manual and backend results */
#define QR_NumResultCols(self) (CI_get_num_fields(self->fields))
#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_))
/* 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)
#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))
/* 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);
void QR_set_num_fields(QResultClass *self, int new_num_fields); /* manual result only */
#endif

View File

@ -0,0 +1,39 @@
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by psqlodbc.rc
//
#define IDS_BADDSN 1
#define IDS_MSGTITLE 2
#define DRIVERCONNDIALOG 101
#define IDC_DSNAME 400
#define IDC_DSNAMETEXT 401
#define IDC_DESC 404
#define IDC_SERVER 407
#define IDC_DATABASE 408
#define CONFIGDSN 1001
#define IDC_PORT 1002
#define IDC_USER 1006
#define IDC_PASSWORD 1009
#define IDC_READONLY 1011
#define READONLY_EDIT 1012
#define SAVEPASSWORD_EDIT 1013
#define IDC_COMMLOG 1014
#define COMMLOG_EDIT 1015
#define IDC_PG62 1016
#define PG62_EDIT 1017
#define SERVER_EDIT 1501
#define PORT_EDIT 1502
#define DATABASE_EDIT 1503
#define USERNAME_EDIT 1504
#define PASSWORD_EDIT 1505
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1018
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View File

@ -0,0 +1,694 @@
/* Module: results.c
*
* Description: This module contains functions related to
* retrieving result information through the ODBC API.
*
* Classes: n/a
*
* API functions: SQLRowCount, SQLNumResultCols, SQLDescribeCol, SQLColAttributes,
* SQLGetData, SQLFetch, SQLExtendedFetch,
* SQLMoreResults(NI), SQLSetPos(NI), SQLSetScrollOptions(NI),
* SQLSetCursorName(NI), SQLGetCursorName(NI)
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include <string.h>
#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 <stdio.h>
#include <windows.h>
#include <sqlext.h>
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);
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;
}

679
src/interfaces/odbc/setup.c Normal file
View File

@ -0,0 +1,679 @@
/* Module: setup.c
*
* Description: This module contains the setup functions for
* adding/modifying a Data Source in the ODBC.INI portion
* of the registry.
*
* Classes: n/a
*
* API functions: ConfigDSN
*
* Comments: See "notice.txt" for copyright and license information.
*
*************************************************************************************/
/*
** 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 <windows.h>
#include <windowsx.h>
#include <odbcinst.h> // ODBC installer prototypes
#include <string.h> // C include files
#include <stdlib.h>
#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
#define KEY_PROTOCOL 10
#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,
INI_PROTOCOL, KEY_PROTOCOL,
"", 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
if (!lpsetupdlg->aAttr[KEY_PROTOCOL].fSupplied) {
SQLGetPrivateProfileString(lpszDSN, INI_PROTOCOL,
EMPTYSTR,
lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr,
sizeof(lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr),
ODBC_INI);
}
if (strncmp(lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr, PG62, strlen(PG62)) == 0)
CheckDlgButton(hdlg, IDC_PG62, 1);
else
CheckDlgButton(hdlg, IDC_PG62, 0);
// CommLog Parameter (this is global)
CheckDlgButton(hdlg, IDC_COMMLOG, globals.commlog);
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));
if ( IsDlgButtonChecked(hdlg, IDC_PG62))
strcpy(lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr, PG62);
else
lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr[0] = '\0';
sprintf(lpsetupdlg->aAttr[KEY_READONLY].szAttr, "%d", IsDlgButtonChecked(hdlg, IDC_READONLY));
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);
if (hwndParent || lpsetupdlg->aAttr[KEY_PROTOCOL].fSupplied )
SQLWritePrivateProfileString(lpszDSN,
INI_PROTOCOL,
lpsetupdlg->aAttr[KEY_PROTOCOL].szAttr,
ODBC_INI);
// CommLog Parameter -- write to ODBCINST_INI (for the whole driver)
if (hwndParent ) {
updateGlobals();
}
// 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;
}

View File

@ -0,0 +1,289 @@
/* Module: socket.c
*
* Description: This module contains functions for low level socket
* operations (connecting/reading/writing to the backend)
*
* Classes: SocketClass (Functions prefix: "SOCK_")
*
* API functions: none
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#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;
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)
return *((unsigned short *) buf);
else
return ntohs( *((unsigned short *) buf) );
case 4:
SOCK_get_n_char(self, buf, len);
if (self->reverse)
return *((unsigned int *) buf);
else
return ntohl( *((unsigned int *) buf) );
default:
self->errornumber = SOCKET_GET_INT_WRONG_LENGTH;
self->errormsg = "Cannot read ints of that length";
return 0;
}
}
void
SOCK_put_int(SocketClass *self, int value, short len)
{
unsigned int rv;
switch (len) {
case 2:
rv = self->reverse ? value : htons( (unsigned short) value);
SOCK_put_n_char(self, (char *) &rv, 2);
return;
case 4:
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);
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;
}
}

View File

@ -0,0 +1,69 @@
/* File: socket.h
*
* Description: See "socket.c"
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#ifndef __SOCKET_H__
#define __SOCKET_H__
#include <winsock.h>
#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;
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)
/* Socket prototypes */
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

View File

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

View File

@ -0,0 +1,119 @@
/* File: statement.h
*
* Description: See "statement.c"
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#ifndef __STATEMENT_H__
#define __STATEMENT_H__
#include <windows.h>
#include <sql.h>
#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 */
int statement_type; /* According to the defines above */
int data_at_exec; /* Number of params needing SQLPutData */
int current_exec_param; /* The current parameter for SQLPutData */
char put_data; /* Has SQLPutData been called yet? */
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 */
char stmt_with_params[65536 /* MAX_STATEMENT_LEN */]; /* statement after parameter substitution */
};
#define SC_get_conn(a) (a->hdbc)
#define SC_get_Result(a) (a->result);
/* options for SC_free_params() */
#define STMT_FREE_PARAMS_ALL 0
#define STMT_FREE_PARAMS_DATA_AT_EXEC_ONLY 1
/* 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);
void SC_free_params(StatementClass *self, char option);
#endif

View File

@ -0,0 +1,56 @@
/* Module: tuple.c
*
* Description: This module contains functions for setting the data for individual
* fields (TupleField structure) of a manual result set.
*
* Important Note: These functions are ONLY used in building manual result sets for
* info functions (SQLTables, SQLColumns, etc.)
*
* Classes: n/a
*
* API functions: none
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include "tuple.h"
#include <string.h>
#include <stdlib.h>
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);
}

View File

@ -0,0 +1,44 @@
/* File: tuple.h
*
* Description: See "tuple.c"
*
* Important NOTE: The TupleField structure is used both to hold backend data and
* manual result set data. The "set_" functions and the TupleNode
* structure are only used for manual result sets by info routines.
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#ifndef __TUPLE_H__
#define __TUPLE_H__
#include "psqlodbc.h"
/* Used by backend data AND manual result sets */
struct TupleField_ {
Int4 len; /* length of the current Tuple */
void *value; /* an array representing the value */
};
/* 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

View File

@ -0,0 +1,188 @@
/* Module: tuplelist.c
*
* Description: This module contains functions for creating a manual result set
* (the TupleList) and retrieving data from it for a specific row/column.
*
* Classes: TupleListClass (Functions prefix: "TL_")
*
* API functions: none
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#include <stdlib.h>
#include <malloc.h>
#include "tuplelist.h"
#include "tuple.h"
TupleListClass *
TL_Constructor(UInt4 fieldcnt)
{
TupleListClass *rv;
mylog("in TL_Constructor\n");
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");
return rv;
}
void
TL_Destructor(TupleListClass *self)
{
int lf;
TupleNode *node, *tp;
mylog("TupleList: in DESTRUCTOR\n");
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;
}
free(self);
mylog("TupleList: exit DESTRUCTOR\n");
}
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;
}

View File

@ -0,0 +1,33 @@
/* File: tuplelist.h
*
* Description: See "tuplelist.c"
*
* Important Note: This structure and its functions are ONLY used in building manual result
* sets for info functions (SQLTables, SQLColumns, etc.)
*
* Comments: See "notice.txt" for copyright and license information.
*
*/
#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