mirror of
https://github.com/postgres/postgres.git
synced 2025-06-26 12:21:12 +03:00
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:
340
src/interfaces/odbc/bind.c
Normal file
340
src/interfaces/odbc/bind.c
Normal 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");
|
||||||
|
}
|
45
src/interfaces/odbc/bind.h
Normal file
45
src/interfaces/odbc/bind.h
Normal 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
|
159
src/interfaces/odbc/columninfo.c
Normal file
159
src/interfaces/odbc/columninfo.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
40
src/interfaces/odbc/columninfo.h
Normal file
40
src/interfaces/odbc/columninfo.h
Normal 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
|
979
src/interfaces/odbc/connection.c
Normal file
979
src/interfaces/odbc/connection.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
183
src/interfaces/odbc/connection.h
Normal file
183
src/interfaces/odbc/connection.h
Normal 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
|
995
src/interfaces/odbc/convert.c
Normal file
995
src/interfaces/odbc/convert.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
47
src/interfaces/odbc/convert.h
Normal file
47
src/interfaces/odbc/convert.h
Normal 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
|
327
src/interfaces/odbc/drvconn.c
Normal file
327
src/interfaces/odbc/drvconn.c
Normal 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);
|
||||||
|
}
|
416
src/interfaces/odbc/environ.c
Normal file
416
src/interfaces/odbc/environ.c
Normal 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;
|
||||||
|
}
|
32
src/interfaces/odbc/environ.h
Normal file
32
src/interfaces/odbc/environ.h
Normal 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
|
535
src/interfaces/odbc/execute.c
Normal file
535
src/interfaces/odbc/execute.c
Normal 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
2180
src/interfaces/odbc/info.c
Normal file
File diff suppressed because it is too large
Load Diff
962
src/interfaces/odbc/license.txt
Normal file
962
src/interfaces/odbc/license.txt
Normal 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
224
src/interfaces/odbc/misc.c
Normal 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;
|
||||||
|
}
|
58
src/interfaces/odbc/misc.h
Normal file
58
src/interfaces/odbc/misc.h
Normal 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
|
35
src/interfaces/odbc/notice.txt
Normal file
35
src/interfaces/odbc/notice.txt
Normal 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)
|
||||||
|
|
||||||
|
|
||||||
|
***********************************************************************/
|
||||||
|
|
210
src/interfaces/odbc/options.c
Normal file
210
src/interfaces/odbc/options.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - -
|
441
src/interfaces/odbc/pgtypes.c
Normal file
441
src/interfaces/odbc/pgtypes.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
81
src/interfaces/odbc/pgtypes.h
Normal file
81
src/interfaces/odbc/pgtypes.h
Normal 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
|
||||||
|
|
146
src/interfaces/odbc/psqlodbc.c
Normal file
146
src/interfaces/odbc/psqlodbc.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
60
src/interfaces/odbc/psqlodbc.def
Normal file
60
src/interfaces/odbc/psqlodbc.def
Normal 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
|
||||||
|
|
120
src/interfaces/odbc/psqlodbc.h
Normal file
120
src/interfaces/odbc/psqlodbc.h
Normal 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
|
205
src/interfaces/odbc/psqlodbc.rc
Normal file
205
src/interfaces/odbc/psqlodbc.rc
Normal 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<66> 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
|
||||||
|
|
472
src/interfaces/odbc/qresult.c
Normal file
472
src/interfaces/odbc/qresult.c
Normal 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;
|
||||||
|
}
|
105
src/interfaces/odbc/qresult.h
Normal file
105
src/interfaces/odbc/qresult.h
Normal 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
|
39
src/interfaces/odbc/resource.h
Normal file
39
src/interfaces/odbc/resource.h
Normal 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
|
694
src/interfaces/odbc/results.c
Normal file
694
src/interfaces/odbc/results.c
Normal 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
679
src/interfaces/odbc/setup.c
Normal 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;
|
||||||
|
}
|
289
src/interfaces/odbc/socket.c
Normal file
289
src/interfaces/odbc/socket.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
69
src/interfaces/odbc/socket.h
Normal file
69
src/interfaces/odbc/socket.h
Normal 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
|
545
src/interfaces/odbc/statement.c
Normal file
545
src/interfaces/odbc/statement.c
Normal 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;
|
||||||
|
}
|
119
src/interfaces/odbc/statement.h
Normal file
119
src/interfaces/odbc/statement.h
Normal 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
|
56
src/interfaces/odbc/tuple.c
Normal file
56
src/interfaces/odbc/tuple.c
Normal 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);
|
||||||
|
}
|
44
src/interfaces/odbc/tuple.h
Normal file
44
src/interfaces/odbc/tuple.h
Normal 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
|
188
src/interfaces/odbc/tuplelist.c
Normal file
188
src/interfaces/odbc/tuplelist.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
33
src/interfaces/odbc/tuplelist.h
Normal file
33
src/interfaces/odbc/tuplelist.h
Normal 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
|
Reference in New Issue
Block a user