/****************************************************************************
**
** Implementation of ODBC driver classes
**
** Created : 001103
**
** Copyright (C) 1992-2008 Trolltech ASA.  All rights reserved.
**
** This file is part of the sql module of the TQt GUI Toolkit.
**
** This file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free
** Software Foundation and appearing in the files LICENSE.GPL2
** and LICENSE.GPL3 included in the packaging of this file.
** Alternatively you may (at your option) use any later version
** of the GNU General Public License if such license has been
** publicly approved by Trolltech ASA (or its successors, if any)
** and the KDE Free TQt Foundation.
**
** Please review the following information to ensure GNU General
** Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/.
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** This file may be used under the terms of the Q Public License as
** defined by Trolltech ASA and appearing in the file LICENSE.TQPL
** included in the packaging of this file.  Licensees holding valid TQt
** Commercial licenses may use this file in accordance with the TQt
** Commercial License Agreement provided with the Software.
**
** This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted
** herein.
**
**********************************************************************/

#include "qsql_odbc.h"
#include <ntqsqlrecord.h>

#if defined (Q_OS_WIN32)
#include <qt_windows.h>
#include <ntqapplication.h>
#endif
#include <ntqdatetime.h>
#include <private/qsqlextension_p.h>
#include <private/qinternal_p.h>
#include <stdlib.h>

// undefine this to prevent initial check of the ODBC driver
#define ODBC_CHECK_DRIVER

#if defined(Q_ODBC_VERSION_2)
//crude hack to get non-unicode capable driver managers to work
# undef UNICODE
# define SQLTCHAR SQLCHAR
# define SQL_C_WCHAR SQL_C_CHAR
#endif

// newer platform SDKs use SQLLEN instead of SQLINTEGER
#if defined(SQLLEN) || defined(Q_OS_WIN64) || defined(Q_OS_UNIX)
# define TQSQLLEN SQLLEN
#else
# define TQSQLLEN SQLINTEGER
#endif

#if defined(SQLULEN) || defined(Q_OS_WIN64) || defined(Q_OS_UNIX)
# define TQSQLULEN SQLULEN
#else
# define TQSQLULEN SQLUINTEGER
#endif

static const TQSQLLEN COLNAMESIZE = 256;
//Map TQt parameter types to ODBC types
static const SQLSMALLINT qParamType[ 4 ] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };

class TQODBCPrivate
{
public:
    TQODBCPrivate()
    : hEnv(0), hDbc(0), hStmt(0), useSchema(FALSE)
    {
	sql_char_type = sql_varchar_type = sql_longvarchar_type = TQVariant::CString;
	unicode = FALSE;
    }

    SQLHANDLE hEnv;
    SQLHANDLE hDbc;
    SQLHANDLE hStmt;

    bool unicode;
    bool useSchema;
    TQVariant::Type sql_char_type;
    TQVariant::Type sql_varchar_type;
    TQVariant::Type sql_longvarchar_type;

    TQSqlRecordInfo rInf;

    bool checkDriver() const;
    void checkUnicode();
    void checkSchemaUsage();
    bool setConnectionOptions( const TQString& connOpts );
    void splitTableQualifier(const TQString &qualifier, TQString &catalog,
			     TQString &schema, TQString &table);
};

class TQODBCPreparedExtension : public TQSqlExtension
{
public:
    TQODBCPreparedExtension( TQODBCResult * r )
	: result( r ) {}

    bool prepare( const TQString& query )
    {
	return result->prepare( query );
    }

    bool exec()
    {
	return result->exec();
    }

    TQODBCResult * result;
};

TQPtrDict<TQSqlOpenExtension> *tqSqlOpenExtDict();

class TQODBCOpenExtension : public TQSqlOpenExtension
{
public:
    TQODBCOpenExtension( TQODBCDriver *dri )
	: TQSqlOpenExtension(), driver(dri) {}
    ~TQODBCOpenExtension() {}

    bool open( const TQString& db,
	       const TQString& user,
	       const TQString& password,
	       const TQString& host,
	       int port,
	       const TQString& connOpts );
private:
    TQODBCDriver *driver;
};

bool TQODBCOpenExtension::open( const TQString& db,
			       const TQString& user,
			       const TQString& password,
			       const TQString& host,
			       int port,
			       const TQString& connOpts )
{
    return driver->open( db, user, password, host, port, connOpts );
}

static TQString qWarnODBCHandle(int handleType, SQLHANDLE handle)
{
    SQLINTEGER nativeCode_;
    SQLSMALLINT msgLen;
    SQLRETURN r = SQL_ERROR;
    SQLTCHAR state_[SQL_SQLSTATE_SIZE+1];
    SQLTCHAR description_[SQL_MAX_MESSAGE_LENGTH];
    r = SQLGetDiagRec( handleType,
			 handle,
			 1,
			 (SQLTCHAR*)state_,
			 &nativeCode_,
			 (SQLTCHAR*)description_,
			 SQL_MAX_MESSAGE_LENGTH-1, /* in bytes, not in characters */
			 &msgLen);
    if ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO )
#ifdef UNICODE
	return TQString( (const TQChar*)description_, (uint)msgLen );
#else
	return TQString::fromLocal8Bit( (const char*)description_ );
#endif
    return TQString::null;
}

static TQString qODBCWarn( const TQODBCPrivate* odbc)
{
    return ( qWarnODBCHandle( SQL_HANDLE_ENV, odbc->hEnv ) + " "
	     + qWarnODBCHandle( SQL_HANDLE_DBC, odbc->hDbc ) + " "
	     + qWarnODBCHandle( SQL_HANDLE_STMT, odbc->hStmt ) );
}

static void qSqlWarning( const TQString& message, const TQODBCPrivate* odbc )
{
#ifdef QT_CHECK_RANGE
    tqWarning( "%s\tError: %s", message.local8Bit().data(), qODBCWarn( odbc ).local8Bit().data() );
#endif
}

static TQSqlError qMakeError( const TQString& err, int type, const TQODBCPrivate* p )
{
    return TQSqlError( "TQODBC3: " + err, qODBCWarn(p), type );
}

static TQVariant::Type qDecodeODBCType( SQLSMALLINT sqltype, const TQODBCPrivate* p )
{
    TQVariant::Type type = TQVariant::Invalid;
    switch ( sqltype ) {
    case SQL_DECIMAL:
    case SQL_NUMERIC:
    case SQL_REAL:
    case SQL_FLOAT:
    case SQL_DOUBLE:
	type = TQVariant::Double;
	break;
    case SQL_SMALLINT:
    case SQL_INTEGER:
    case SQL_BIT:
    case SQL_TINYINT:
	type = TQVariant::Int;
	break;
    case SQL_BIGINT:
	type = TQVariant::LongLong;
	break;
    case SQL_BINARY:
    case SQL_VARBINARY:
    case SQL_LONGVARBINARY:
	type = TQVariant::ByteArray;
	break;
    case SQL_DATE:
    case SQL_TYPE_DATE:
	type = TQVariant::Date;
	break;
    case SQL_TIME:
    case SQL_TYPE_TIME:
	type = TQVariant::Time;
	break;
    case SQL_TIMESTAMP:
    case SQL_TYPE_TIMESTAMP:
	type = TQVariant::DateTime;
	break;
#ifndef Q_ODBC_VERSION_2
    case SQL_WCHAR:
    case SQL_WVARCHAR:
    case SQL_WLONGVARCHAR:
	type = TQVariant::String;
	break;
#endif
    case SQL_CHAR:
	type = p->sql_char_type;
	break;
    case SQL_VARCHAR:
	type = p->sql_varchar_type;
	break;
    case SQL_LONGVARCHAR:
	type = p->sql_longvarchar_type;
	break;
    default:
	type = TQVariant::CString;
	break;
    }
    return type;
}

static TQString qGetStringData( SQLHANDLE hStmt, int column, int colSize, bool& isNull, bool unicode = FALSE )
{
    TQString     fieldVal;
    SQLRETURN   r = SQL_ERROR;
    TQSQLLEN  lengthIndicator = 0;

    if ( colSize <= 0 ) {
	colSize = 256;
    } else if ( colSize > 65536 ) { // limit buffer size to 64 KB
	colSize = 65536;
    } else {
	colSize++; // make sure there is room for more than the 0 termination
	if ( unicode ) {
	    colSize *= 2; // a tiny bit faster, since it saves a SQLGetData() call
	}
    }
    char* buf = new char[ colSize ];
    while ( TRUE ) {
	r = SQLGetData( hStmt,
			column+1,
			unicode ? SQL_C_WCHAR : SQL_C_CHAR,
			(SQLPOINTER)buf,
			(TQSQLLEN)colSize,
			&lengthIndicator );
	if ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO ) {
	    if ( lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL ) {
		fieldVal = TQString::null;
		isNull = TRUE;
		break;
	    }
	    // if SQL_SUCCESS_WITH_INFO is returned, indicating that
	    // more data can be fetched, the length indicator does NOT
	    // contain the number of bytes returned - it contains the
	    // total number of bytes that CAN be fetched
	    // colSize-1: remove 0 termination when there is more data to fetch
	    int rSize = (r == SQL_SUCCESS_WITH_INFO) ? (unicode ? colSize-2 : colSize-1) : lengthIndicator;
	    if ( unicode ) {
		fieldVal += TQString( (TQChar*) buf, rSize / 2 );
	    } else {
		buf[ rSize ] = 0;
		fieldVal += buf;
	    }
	    if ( lengthIndicator < colSize ) {
		// workaround for Drivermanagers that don't return SQL_NO_DATA
		break;
	    }
	} else if ( r == SQL_NO_DATA ) {
	    break;
	} else {
#ifdef QT_CHECK_RANGE
	    tqWarning( "qGetStringData: Error while fetching data (%d)", r );
#endif
	    fieldVal = TQString::null;
	    break;
	}
    }
    delete[] buf;
    return fieldVal;
}

static TQByteArray qGetBinaryData( SQLHANDLE hStmt, int column, TQSQLLEN& lengthIndicator, bool& isNull )
{
    TQByteArray fieldVal;
    SQLSMALLINT colNameLen;
    SQLSMALLINT colType;
    TQSQLULEN colSize;
    SQLSMALLINT colScale;
    SQLSMALLINT nullable;
    SQLRETURN r = SQL_ERROR;

    SQLTCHAR colName[COLNAMESIZE];
    r = SQLDescribeCol( hStmt,
			column+1,
			colName,
			COLNAMESIZE,
			&colNameLen,
			&colType,
			&colSize,
			&colScale,
			&nullable );
#ifdef QT_CHECK_RANGE
    if ( r != SQL_SUCCESS )
	tqWarning( "qGetBinaryData: Unable to describe column %d", column );
#endif
    // SQLDescribeCol may return 0 if size cannot be determined
    if (!colSize) {
	colSize = 256;
    }
    if ( colSize > 65536 ) { // read the field in 64 KB chunks
	colSize = 65536;
    }
    char * buf = new char[ colSize ];
    while ( TRUE ) {
	r = SQLGetData( hStmt,
			column+1,
			SQL_C_BINARY,
			(SQLPOINTER) buf,
			(TQSQLLEN)colSize,
			&lengthIndicator );
	if ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO ) {
	    if ( lengthIndicator == SQL_NULL_DATA ) {
 		isNull = TRUE;
		break;
	    } else {
		int rSize;
		r == SQL_SUCCESS ? rSize = lengthIndicator : rSize = colSize;
		if ( lengthIndicator == SQL_NO_TOTAL ) { // size cannot be determined
		    rSize = colSize;
		}
		// NB! This is not a memleak - the mem will be deleted by TQByteArray when
		// no longer ref'd
		char * tmp = (char *) malloc( rSize + fieldVal.size() );
		if ( fieldVal.size() ) {
		    memcpy( tmp, fieldVal.data(), fieldVal.size() );
		}
		memcpy( tmp + fieldVal.size(), buf, rSize );
		fieldVal = fieldVal.assign( tmp, fieldVal.size() + rSize );

		if ( r == SQL_SUCCESS ) { // the whole field was read in one chunk
		    break;
		}
	    }
	} else {
	    break;
	}
    }
    delete [] buf;
    return fieldVal;
}

static int qGetIntData( SQLHANDLE hStmt, int column, bool& isNull  )
{
    TQSQLLEN intbuf = 0;
    isNull = FALSE;
    TQSQLLEN lengthIndicator = 0;
    SQLRETURN r = SQLGetData( hStmt,
			      column+1,
			      SQL_C_SLONG,
			      (SQLPOINTER)&intbuf,
			      (TQSQLLEN)0,
			      &lengthIndicator );
    if ( ( r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO ) || lengthIndicator == SQL_NULL_DATA ) {
	isNull = TRUE;
	return 0;
    }
    return (int)intbuf;
}

static double qGetDoubleData( SQLHANDLE hStmt, int column, bool& isNull )
{
    SQLDOUBLE dblbuf;
    TQSQLLEN lengthIndicator = 0;
    isNull = FALSE;
    SQLRETURN r = SQLGetData( hStmt,
			      column+1,
			      SQL_C_DOUBLE,
			      (SQLPOINTER)&dblbuf,
			      (TQSQLLEN)0,
			      &lengthIndicator );
    if ( ( r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO ) || lengthIndicator == SQL_NULL_DATA ) {
	isNull = TRUE;
	return 0.0;
    }

    return (double) dblbuf;
}

static SQLBIGINT qGetBigIntData( SQLHANDLE hStmt, int column, bool& isNull )
{
    SQLBIGINT lngbuf = TQ_INT64_C( 0 );
    isNull = FALSE;
    TQSQLLEN lengthIndicator = 0;
    SQLRETURN r = SQLGetData( hStmt,
			      column+1,
			      SQL_C_SBIGINT,
			      (SQLPOINTER) &lngbuf,
			      (TQSQLLEN)0,
			      &lengthIndicator );
    if ( ( r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO ) || lengthIndicator == SQL_NULL_DATA )
	isNull = TRUE;

    return lngbuf;
}

// creates a TQSqlFieldInfo from a valid hStmt generated
// by SQLColumns. The hStmt has to point to a valid position.
static TQSqlFieldInfo qMakeFieldInfo( const SQLHANDLE hStmt, const TQODBCPrivate* p )
{
    bool isNull;
    TQString fname = qGetStringData( hStmt, 3, -1, isNull, p->unicode );
    int type = qGetIntData( hStmt, 4, isNull ); // column type
    int required = qGetIntData( hStmt, 10, isNull ); // nullable-flag
    // required can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
    if ( required == SQL_NO_NULLS ) {
	required = 1;
    } else if ( required == SQL_NULLABLE ) {
	required = 0;
    } else {
	required = -1;
    }
    int size = qGetIntData( hStmt, 6, isNull ); // column size
    int prec = qGetIntData( hStmt, 8, isNull ); // precision
    return TQSqlFieldInfo( fname, qDecodeODBCType( type, p ), required, size, prec, TQVariant(), type );
}

static TQSqlFieldInfo qMakeFieldInfo( const TQODBCPrivate* p, int i  )
{
    SQLSMALLINT colNameLen;
    SQLSMALLINT colType;
    TQSQLULEN colSize;
    SQLSMALLINT colScale;
    SQLSMALLINT nullable;
    SQLRETURN r = SQL_ERROR;
    SQLTCHAR colName[ COLNAMESIZE ];
    r = SQLDescribeCol( p->hStmt,
			i+1,
			colName,
			(TQSQLULEN)COLNAMESIZE,
			&colNameLen,
			&colType,
			&colSize,
			&colScale,
			&nullable);

    if ( r != SQL_SUCCESS ) {
#ifdef QT_CHECK_RANGE
	qSqlWarning( TQString("qMakeField: Unable to describe column %1").arg(i), p );
#endif
	return TQSqlFieldInfo();
    }
#ifdef UNICODE
    TQString qColName( (const TQChar*)colName, (uint)colNameLen );
#else
    TQString qColName = TQString::fromLocal8Bit( (const char*)colName );
#endif
    // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
    int required = -1;
    if ( nullable == SQL_NO_NULLS ) {
	required = 1;
    } else if ( nullable == SQL_NULLABLE ) {
	required = 0;
    }
    TQVariant::Type type = qDecodeODBCType( colType, p );
    return TQSqlFieldInfo( qColName,
    			  type,
    			  required,
    			  (int)colSize == 0 ? -1 : (int)colSize,
    			  (int)colScale == 0 ? -1 : (int)colScale,
    			  TQVariant(),
    			  (int)colType );
}

bool TQODBCPrivate::setConnectionOptions( const TQString& connOpts )
{
    // Set any connection attributes
    TQStringList raw = TQStringList::split( ';', connOpts );
    TQStringList opts;
    SQLRETURN r = SQL_SUCCESS;
    TQMap<TQString, TQString> connMap;
    for ( TQStringList::ConstIterator it = raw.begin(); it != raw.end(); ++it ) {
	TQString tmp( *it );
	int idx;
	if ( (idx = tmp.find( '=' )) != -1 )
	    connMap[ tmp.left( idx ) ] = tmp.mid( idx + 1 ).simplifyWhiteSpace();
	else
	    tqWarning( "TQODBCDriver::open: Illegal connect option value '%s'", tmp.latin1() );
    }
    if ( connMap.count() ) {
	TQMap<TQString, TQString>::ConstIterator it;
	TQString opt, val;
	SQLUINTEGER v = 0;
	for ( it = connMap.begin(); it != connMap.end(); ++it ) {
	    opt = it.key().upper();
	    val = it.data().upper();
	    r = SQL_SUCCESS;
	    if ( opt == "SQL_ATTR_ACCESS_MODE" ) {
		if ( val == "SQL_MODE_READ_ONLY" ) {
		    v = SQL_MODE_READ_ONLY;
		} else if ( val == "SQL_MODE_READ_WRITE" ) {
		    v = SQL_MODE_READ_WRITE;
		} else {
		    tqWarning( "TQODBCDriver::open: Unknown option value '%s'", (*it).latin1() );
		    break;
		}
		r = SQLSetConnectAttr( hDbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER) v, 0 );
	    } else if ( opt == "SQL_ATTR_CONNECTION_TIMEOUT" ) {
		v = val.toUInt();
		r = SQLSetConnectAttr( hDbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) v, 0 );
	    } else if ( opt == "SQL_ATTR_LOGIN_TIMEOUT" ) {
		v = val.toUInt();
		r = SQLSetConnectAttr( hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) v, 0 );
	    } else if ( opt == "SQL_ATTR_CURRENT_CATALOG" ) {
		val.ucs2(); // 0 terminate
		r = SQLSetConnectAttr( hDbc, SQL_ATTR_CURRENT_CATALOG,
#ifdef UNICODE
				       (SQLWCHAR*) val.unicode(),
#else
				       (SQLCHAR*) val.latin1(),
#endif
				       SQL_NTS );
	    } else if ( opt == "SQL_ATTR_METADATA_ID" ) {
		if ( val == "SQL_TRUE" ) {
		    v = SQL_TRUE;
		} else if ( val == "SQL_FALSE" ) {
		    v = SQL_FALSE;
		} else {
		    tqWarning( "TQODBCDriver::open: Unknown option value '%s'", (*it).latin1() );
		    break;
		}
		r = SQLSetConnectAttr( hDbc, SQL_ATTR_METADATA_ID, (SQLPOINTER) v, 0 );
	    } else if ( opt == "SQL_ATTR_PACKET_SIZE" ) {
		v = val.toUInt();
		r = SQLSetConnectAttr( hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) v, 0 );
	    } else if ( opt == "SQL_ATTR_TRACEFILE" ) {
		val.ucs2(); // 0 terminate
		r = SQLSetConnectAttr( hDbc, SQL_ATTR_TRACEFILE,
#ifdef UNICODE
				       (SQLWCHAR*) val.unicode(),
#else
				       (SQLCHAR*) val.latin1(),
#endif
				       SQL_NTS );
	    } else if ( opt == "SQL_ATTR_TRACE" ) {
		if ( val == "SQL_OPT_TRACE_OFF" ) {
		    v = SQL_OPT_TRACE_OFF;
		} else if ( val == "SQL_OPT_TRACE_ON" ) {
		    v = SQL_OPT_TRACE_ON;
		} else {
		    tqWarning( "TQODBCDriver::open: Unknown option value '%s'", (*it).latin1() );
		    break;
		}
		r = SQLSetConnectAttr( hDbc, SQL_ATTR_TRACE, (SQLPOINTER) v, 0 );
	    }
#ifdef QT_CHECK_RANGE
              else {
		  tqWarning( "TQODBCDriver::open: Unknown connection attribute '%s'", opt.latin1() );
	    }
#endif
	    if ( r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO ) {
#ifdef QT_CHECK_RANGE
		qSqlWarning( TQString("TQODBCDriver::open: Unable to set connection attribute '%1'").arg( opt ), this );
#endif
		return FALSE;
	    }
	}
    }
    return TRUE;
}

void TQODBCPrivate::splitTableQualifier(const TQString & qualifier, TQString &catalog,
				       TQString &schema, TQString &table)
{
    if (!useSchema) {
	table = qualifier;
	return;
    }
    TQStringList l = TQStringList::split( ".", qualifier, TRUE );
    if ( l.count() > 3 )
	return; // can't possibly be a valid table qualifier
    int i = 0, n = l.count();
    if ( n == 1 ) {
	table = qualifier;
    } else {
	for ( TQStringList::Iterator it = l.begin(); it != l.end(); ++it ) {
	    if ( n == 3 ) {
		if ( i == 0 ) {
		    catalog = *it;
		} else if ( i == 1 ) {
		    schema = *it;
		} else if ( i == 2 ) {
		    table = *it;
		}
	    } else if ( n == 2 ) {
		if ( i == 0 ) {
		    schema = *it;
		} else if ( i == 1 ) {
		    table = *it;
		}
	    }
	    i++;
	}
    }
}

////////////////////////////////////////////////////////////////////////////

TQODBCResult::TQODBCResult( const TQODBCDriver * db, TQODBCPrivate* p )
: TQSqlResult(db)
{
    d = new TQODBCPrivate();
    (*d) = (*p);
    setExtension( new TQODBCPreparedExtension( this ) );
}

TQODBCResult::~TQODBCResult()
{
    if ( d->hStmt && driver()->isOpen() ) {
	SQLRETURN r = SQLFreeHandle( SQL_HANDLE_STMT, d->hStmt );
#ifdef QT_CHECK_RANGE
	if ( r != SQL_SUCCESS )
	    qSqlWarning( "TQODBCDriver: Unable to free statement handle " + TQString::number(r), d );
#endif
    }

    delete d;
}

bool TQODBCResult::reset ( const TQString& query )
{
    setActive( FALSE );
    setAt( TQSql::BeforeFirst );
    SQLRETURN r;

    d->rInf.clear();
    // Always reallocate the statement handle - the statement attributes
    // are not reset if SQLFreeStmt() is called which causes some problems.
    if ( d->hStmt ) {
	r = SQLFreeHandle( SQL_HANDLE_STMT, d->hStmt );
	if ( r != SQL_SUCCESS ) {
#ifdef QT_CHECK_RANGE
	    qSqlWarning( "TQODBCResult::reset: Unable to free statement handle", d );
#endif
	    return FALSE;
	}
    }
    r  = SQLAllocHandle( SQL_HANDLE_STMT,
			 d->hDbc,
			 &d->hStmt );
    if ( r != SQL_SUCCESS ) {
#ifdef QT_CHECK_RANGE
	qSqlWarning( "TQODBCResult::reset: Unable to allocate statement handle", d );
#endif
	return FALSE;
    }

    if ( isForwardOnly() ) {
	r = SQLSetStmtAttr( d->hStmt,
			    SQL_ATTR_CURSOR_TYPE,
			    (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
			    SQL_IS_UINTEGER );
    } else {
	r = SQLSetStmtAttr( d->hStmt,
			    SQL_ATTR_CURSOR_TYPE,
			    (SQLPOINTER)SQL_CURSOR_STATIC,
			    SQL_IS_UINTEGER );
    }
    if ( r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO ) {
#ifdef QT_CHECK_RANGE
	qSqlWarning( "TQODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. Please check your ODBC driver configuration", d );
#endif
	return FALSE;
    }

#ifdef UNICODE
    r = SQLExecDirect( d->hStmt,
		       (SQLWCHAR*) query.unicode(),
		       (SQLINTEGER) query.length() );
#else
    TQCString query8 = query.local8Bit();
    r = SQLExecDirect( d->hStmt,
                       (SQLCHAR*) query8.data(),
                       (SQLINTEGER) query8.length() );
#endif
    if ( r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO ) {
	setLastError( qMakeError( "Unable to execute statement", TQSqlError::Statement, d ) );
	return FALSE;
    }
    SQLSMALLINT count;
    r = SQLNumResultCols( d->hStmt, &count );
    if ( count ) {
	setSelect( TRUE );
	for ( int i = 0; i < count; ++i ) {
	    d->rInf.append( qMakeFieldInfo( d, i ) );
	}
    } else {
	setSelect( FALSE );
    }
    setActive( TRUE );
    return TRUE;
}

bool TQODBCResult::fetch(int i)
{
    if ( isForwardOnly() && i < at() )
	return FALSE;
    if ( i == at() )
	return TRUE;
    fieldCache.clear();
    nullCache.clear();
    int actualIdx = i + 1;
    if ( actualIdx <= 0 ) {
	setAt( TQSql::BeforeFirst );
	return FALSE;
    }
    SQLRETURN r;
    if ( isForwardOnly() ) {
	bool ok = TRUE;
	while ( ok && i > at() )
	    ok = fetchNext();
	return ok;
    } else {
	r = SQLFetchScroll( d->hStmt,
			    SQL_FETCH_ABSOLUTE,
			    actualIdx );
    }
    if ( r != SQL_SUCCESS ){
 	return FALSE;
    }
    setAt( i );
    return TRUE;
}

bool TQODBCResult::fetchNext()
{
    SQLRETURN r;
    fieldCache.clear();
    nullCache.clear();
    r = SQLFetchScroll( d->hStmt,
		       SQL_FETCH_NEXT,
		       0 );
    if ( r != SQL_SUCCESS )
	return FALSE;
    setAt( at() + 1 );
    return TRUE;
}

bool TQODBCResult::fetchFirst()
{
    if ( isForwardOnly() && at() != TQSql::BeforeFirst )
	return FALSE;
    SQLRETURN r;
    fieldCache.clear();
    nullCache.clear();
    if ( isForwardOnly() ) {
	return fetchNext();
    }
    r = SQLFetchScroll( d->hStmt,
		       SQL_FETCH_FIRST,
		       0 );
    if ( r != SQL_SUCCESS )
	return FALSE;
    setAt( 0 );
    return TRUE;
}

bool TQODBCResult::fetchPrior()
{
    if ( isForwardOnly() )
	return FALSE;
    SQLRETURN r;
    fieldCache.clear();
    nullCache.clear();
    r = SQLFetchScroll( d->hStmt,
		       SQL_FETCH_PRIOR,
		       0 );
    if ( r != SQL_SUCCESS )
	return FALSE;
    setAt( at() - 1 );
    return TRUE;
}

bool TQODBCResult::fetchLast()
{
    SQLRETURN r;
    fieldCache.clear();
    nullCache.clear();

    if ( isForwardOnly() ) {
	// cannot seek to last row in forwardOnly mode, so we have to use brute force
	int i = at();
	if ( i == TQSql::AfterLast )
	    return FALSE;
	if ( i == TQSql::BeforeFirst )
	    i = 0;
	while ( fetchNext() )
	    ++i;
	setAt( i );
	return TRUE;
    }

    r = SQLFetchScroll( d->hStmt,
		       SQL_FETCH_LAST,
		       0 );
    if ( r != SQL_SUCCESS ) {
	return FALSE;
    }
    SQLINTEGER currRow;
    r = SQLGetStmtAttr( d->hStmt,
			SQL_ROW_NUMBER,
			&currRow,
			SQL_IS_INTEGER,
			0 );
    if ( r != SQL_SUCCESS )
	return FALSE;
    setAt( currRow-1 );
    return TRUE;
}

TQVariant TQODBCResult::data( int field )
{
    if ( field >= (int) d->rInf.count() ) {
	tqWarning( "TQODBCResult::data: column %d out of range", field );
	return TQVariant();
    }
    if ( fieldCache.contains( field ) )
	return fieldCache[ field ];
    SQLRETURN r(0);
    TQSQLLEN lengthIndicator = 0;
    bool isNull = FALSE;
    int current = fieldCache.count();
    for ( ; current < (field + 1); ++current ) {
	const TQSqlFieldInfo info = d->rInf[ current ];
	switch ( info.type() ) {
	case TQVariant::LongLong:
	    fieldCache[ current ] = TQVariant( (TQ_LLONG) qGetBigIntData( d->hStmt, current, isNull ) );
	    nullCache[ current ] = isNull;
	    break;
	case TQVariant::Int:
	    fieldCache[ current ] = TQVariant( qGetIntData( d->hStmt, current, isNull ) );
	    nullCache[ current ] = isNull;
	    break;
	case TQVariant::Date:
	    DATE_STRUCT dbuf;
	    r = SQLGetData( d->hStmt,
			    current+1,
			    SQL_C_DATE,
			    (SQLPOINTER)&dbuf,
			    (TQSQLLEN)0,
			    &lengthIndicator );
	    if ( ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO ) && ( lengthIndicator != SQL_NULL_DATA ) ) {
		fieldCache[ current ] = TQVariant( TQDate( dbuf.year, dbuf.month, dbuf.day ) );
		nullCache[ current ] = FALSE;
	    } else {
		fieldCache[ current ] = TQVariant( TQDate() );
		nullCache[ current ] = TRUE;
	    }
	    break;
	case TQVariant::Time:
	    TIME_STRUCT tbuf;
	    r = SQLGetData( d->hStmt,
			    current+1,
			    SQL_C_TIME,
			    (SQLPOINTER)&tbuf,
			    (TQSQLLEN)0,
			    &lengthIndicator );
	    if ( ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO ) && ( lengthIndicator != SQL_NULL_DATA ) ) {
		fieldCache[ current ] = TQVariant( TQTime( tbuf.hour, tbuf.minute, tbuf.second ) );
		nullCache[ current ] = FALSE;
	    } else {
		fieldCache[ current ] = TQVariant( TQTime() );
		nullCache[ current ] = TRUE;
	    }
	    break;
	case TQVariant::DateTime:
	    TIMESTAMP_STRUCT dtbuf;
	    r = SQLGetData( d->hStmt,
			    current+1,
			    SQL_C_TIMESTAMP,
			    (SQLPOINTER)&dtbuf,
			    (TQSQLLEN)0,
			    &lengthIndicator );
	    if ( ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO ) && ( lengthIndicator != SQL_NULL_DATA ) ) {
		fieldCache[ current ] = TQVariant( TQDateTime( TQDate( dtbuf.year, dtbuf.month, dtbuf.day ), TQTime( dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000 ) ) );
		nullCache[ current ] = FALSE;
	    } else {
		fieldCache[ current ] = TQVariant( TQDateTime() );
		nullCache[ current ] = TRUE;
	    }
	    break;
        case TQVariant::ByteArray: {
	    isNull = FALSE;
	    TQByteArray val = qGetBinaryData( d->hStmt, current, lengthIndicator, isNull );
	    fieldCache[ current ] = TQVariant( val );
	    nullCache[ current ] = isNull;
	    break; }
	case TQVariant::String:
	    isNull = FALSE;
	    fieldCache[ current ] = TQVariant( qGetStringData( d->hStmt, current,
					      info.length(), isNull, TRUE ) );
	    nullCache[ current ] = isNull;
	    break;
	case TQVariant::Double:
	    if ( info.typeID() == SQL_DECIMAL || info.typeID() == SQL_NUMERIC )
		// bind Double values as string to prevent loss of precision
		fieldCache[ current ] = TQVariant( qGetStringData( d->hStmt, current,
						  info.length() + 1, isNull, FALSE ) ); // length + 1 for the comma
	    else
		fieldCache[ current ] = TQVariant( qGetDoubleData( d->hStmt, current, isNull ) );
	    nullCache[ current ] = isNull;
	    break;
	case TQVariant::CString:
	default:
	    isNull = FALSE;
	    fieldCache[ current ] = TQVariant( qGetStringData( d->hStmt, current,
					      info.length(), isNull, FALSE ) );
	    nullCache[ current ] = isNull;
	    break;
	}
    }
    return fieldCache[ --current ];
}

bool TQODBCResult::isNull( int field )
{
    if ( !fieldCache.contains( field ) ) {
	// since there is no good way to find out whether the value is NULL
	// without fetching the field we'll fetch it here.
	// (data() also sets the NULL flag)
	data( field );
    }
    return nullCache[ field ];
}

int TQODBCResult::size()
{
    return -1;
}

int TQODBCResult::numRowsAffected()
{
    TQSQLLEN affectedRowCount(0);
    SQLRETURN r = SQLRowCount( d->hStmt, &affectedRowCount );
    if ( r == SQL_SUCCESS )
	return affectedRowCount;
#ifdef QT_CHECK_RANGE
    else
	qSqlWarning( "TQODBCResult::numRowsAffected: Unable to count affected rows", d );
#endif
    return -1;
}

bool TQODBCResult::prepare( const TQString& query )
{
    setActive( FALSE );
    setAt( TQSql::BeforeFirst );
    SQLRETURN r;

    d->rInf.clear();
    if ( d->hStmt ) {
	r = SQLFreeHandle( SQL_HANDLE_STMT, d->hStmt );
	if ( r != SQL_SUCCESS ) {
#ifdef QT_CHECK_RANGE
	    qSqlWarning( "TQODBCResult::prepare: Unable to close statement", d );
#endif
	    return FALSE;
	}
    }
    r  = SQLAllocHandle( SQL_HANDLE_STMT,
	    		 d->hDbc,
			 &d->hStmt );
    if ( r != SQL_SUCCESS ) {
#ifdef QT_CHECK_RANGE
	qSqlWarning( "TQODBCResult::prepare: Unable to allocate statement handle", d );
#endif
	return FALSE;
    }

    if ( isForwardOnly() ) {
	r = SQLSetStmtAttr( d->hStmt,
	    		    SQL_ATTR_CURSOR_TYPE,
			    (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
			    SQL_IS_UINTEGER );
    } else {
	r = SQLSetStmtAttr( d->hStmt,
			    SQL_ATTR_CURSOR_TYPE,
			    (SQLPOINTER)SQL_CURSOR_STATIC,
			    SQL_IS_UINTEGER );
    }
    if ( r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO ) {
#ifdef QT_CHECK_RANGE
	qSqlWarning( "TQODBCResult::prepare: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. Please check your ODBC driver configuration", d );
#endif
	return FALSE;
    }

#ifdef UNICODE
    r = SQLPrepare( d->hStmt,
		    (SQLWCHAR*) query.unicode(),
		    (SQLINTEGER) query.length() );
#else
    TQCString query8 = query.local8Bit();
    r = SQLPrepare( d->hStmt,
		    (SQLCHAR*) query8.data(),
		    (SQLINTEGER) query8.length() );
#endif

    if ( r != SQL_SUCCESS ) {
#ifdef QT_CHECK_RANGE
	qSqlWarning( "TQODBCResult::prepare: Unable to prepare statement", d );
#endif
	return FALSE;
    }
    return TRUE;
}

bool TQODBCResult::exec()
{
    SQLRETURN r;
    TQPtrList<TQVirtualDestructor> tmpStorage; // holds temporary ptrs. which will be deleted on fu exit
    tmpStorage.setAutoDelete( TRUE );

    setActive( FALSE );
    setAt( TQSql::BeforeFirst );
    d->rInf.clear();

    if ( !d->hStmt ) {
#ifdef QT_CHECK_RANGE
	qSqlWarning( "TQODBCResult::exec: No statement handle available", d );
#endif
	return FALSE;
    } else {
	r = SQLFreeStmt( d->hStmt, SQL_CLOSE );
	if ( r != SQL_SUCCESS ) {
	    qSqlWarning( "TQODBCResult::exec: Unable to close statement handle", d );
	    return FALSE;
	}
    }

    // bind parameters - only positional binding allowed
    if ( extension()->index.count() > 0 ) {
	TQMap<int, TQString>::Iterator it;
	int para = 1;
	TQVariant val;
	for ( it = extension()->index.begin(); it != extension()->index.end(); ++it ) {
	    val = extension()->values[ it.data() ].value;
	    TQSQLLEN *ind = new TQSQLLEN( SQL_NTS );
	    tmpStorage.append( qAutoDeleter(ind) );
	    if ( val.isNull() ) {
		*ind = SQL_NULL_DATA;
	    }
	    switch ( val.type() ) {
		case TQVariant::Date: {
		    DATE_STRUCT * dt = new DATE_STRUCT;
		    tmpStorage.append( qAutoDeleter(dt) );
		    TQDate qdt = val.toDate();
		    dt->year = qdt.year();
		    dt->month = qdt.month();
		    dt->day = qdt.day();
		    r = SQLBindParameter( d->hStmt,
					  para,
					  qParamType[ (int)extension()->values[ it.data() ].typ ],
					  SQL_C_DATE,
					  SQL_DATE,
					  0,
					  0,
					  (void *) dt,
					  (TQSQLLEN)0,
					  *ind == SQL_NULL_DATA ? ind : NULL );
		    break; }
		case TQVariant::Time: {
		    TIME_STRUCT * dt = new TIME_STRUCT;
		    tmpStorage.append( qAutoDeleter(dt) );
		    TQTime qdt = val.toTime();
		    dt->hour = qdt.hour();
		    dt->minute = qdt.minute();
		    dt->second = qdt.second();
		    r = SQLBindParameter( d->hStmt,
					  para,
					  qParamType[ (int)extension()->values[ it.data() ].typ ],
					  SQL_C_TIME,
					  SQL_TIME,
					  0,
					  0,
					  (void *) dt,
					  (TQSQLLEN)0,
	        			  *ind == SQL_NULL_DATA ? ind : NULL );
		    break; }
		case TQVariant::DateTime: {
		    TIMESTAMP_STRUCT * dt = new TIMESTAMP_STRUCT;
		    tmpStorage.append( qAutoDeleter(dt) );
		    TQDateTime qdt = val.toDateTime();
		    dt->year = qdt.date().year();
		    dt->month = qdt.date().month();
		    dt->day = qdt.date().day();
		    dt->hour = qdt.time().hour();
		    dt->minute = qdt.time().minute();
		    dt->second = qdt.time().second();
		    dt->fraction = 0;
		    r = SQLBindParameter( d->hStmt,
					  para,
					  qParamType[ (int)extension()->values[ it.data() ].typ ],
					  SQL_C_TIMESTAMP,
					  SQL_TIMESTAMP,
					  0,
					  0,
					  (void *) dt,
					  (TQSQLLEN)0,
					  *ind == SQL_NULL_DATA ? ind : NULL );
		    break; }
	        case TQVariant::Int: {
		    int * v = new int( val.toInt() );
		    tmpStorage.append( qAutoDeleter(v) );
		    r = SQLBindParameter( d->hStmt,
					  para,
					  qParamType[ (int)extension()->values[ it.data() ].typ ],
					  SQL_C_SLONG,
					  SQL_INTEGER,
					  0,
					  0,
					  (void *) v,
					  (TQSQLLEN)0,
					  *ind == SQL_NULL_DATA ? ind : NULL );
		    break; }
	        case TQVariant::Double: {
		    double * v = new double( val.toDouble() );
		    tmpStorage.append( qAutoDeleter(v) );
		    r = SQLBindParameter( d->hStmt,
					  para,
					  qParamType[ (int)extension()->values[ it.data() ].typ ],
					  SQL_C_DOUBLE,
					  SQL_DOUBLE,
					  0,
					  0,
					  (void *) v,
					  (TQSQLLEN)0,
					  *ind == SQL_NULL_DATA ? ind : NULL );
		    break; }
	        case TQVariant::ByteArray: {
		    if ( *ind != SQL_NULL_DATA ) {
			*ind = val.asByteArray().size();
		    }
		    r = SQLBindParameter( d->hStmt,
					  para,
					  qParamType[ (int)extension()->values[ it.data() ].typ ],
					  SQL_C_BINARY,
					  SQL_LONGVARBINARY,
					  val.asByteArray().size(),
					  0,
					  (void *) val.asByteArray().data(),
					  (TQSQLLEN)val.asByteArray().size(),
					  ind );
		    break; }
#ifndef Q_ODBC_VERSION_2
	        case TQVariant::String:
		    if ( d->unicode ) {
			TQString * str = new TQString( val.asString() );
			str->ucs2();
			int len = str->length()*2;
			tmpStorage.append( qAutoDeleter(str) );
			r = SQLBindParameter( d->hStmt,
					      para,
					      qParamType[ (int)extension()->values[ it.data() ].typ ],
					      SQL_C_WCHAR,
					      len > 8000 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
					      len > 8000 ? len : 0,
					      0,
					      (void *) str->unicode(),
					      (TQSQLLEN) len,
					      ind );
			break;
		    }
#endif
		    // fall through
	        default: {
		    TQCString * str = new TQCString( val.asString().local8Bit() );
		    tmpStorage.append( qAutoDeleter(str) );
		    r = SQLBindParameter( d->hStmt,
					  para,
					  qParamType[ (int)extension()->values[ it.data() ].typ ],
					  SQL_C_CHAR,
					  str->length() > 4000 ? SQL_LONGVARCHAR : SQL_VARCHAR,
					  str->length() + 1,
					  0,
					  (void *) str->data(),
					  (TQSQLLEN)(str->length() + 1),
					  ind );
		    break; }
	    }
	    para++;
	    if ( r != SQL_SUCCESS ) {
#ifdef QT_CHECK_RANGE
		tqWarning( "TQODBCResult::exec: unable to bind variable: %s", qODBCWarn( d ).local8Bit().data() );
#endif
		setLastError( qMakeError( "Unable to bind variable", TQSqlError::Statement, d ) );
		return FALSE;
	    }
	}
    }
    r = SQLExecute( d->hStmt );
    if ( r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO ) {
#ifdef QT_CHECK_RANGE
	tqWarning( "TQODBCResult::exec: Unable to execute statement: %s", qODBCWarn( d ).local8Bit().data() );
#endif
	setLastError( qMakeError( "Unable to execute statement", TQSqlError::Statement, d ) );
	return FALSE;
    }
    SQLSMALLINT count;
    r = SQLNumResultCols( d->hStmt, &count );
    if ( count ) {
	setSelect( TRUE );
	for ( int i = 0; i < count; ++i ) {
	    d->rInf.append( qMakeFieldInfo( d, i ) );
	}
    } else {
	setSelect( FALSE );
    }
    setActive( TRUE );

    //get out parameters
    if ( extension()->index.count() > 0 ) {
	TQMap<int, TQString>::Iterator it;
	for ( it = extension()->index.begin(); it != extension()->index.end(); ++it ) {

	    SQLINTEGER* indPtr = qAutoDeleterData( (TQAutoDeleter<SQLINTEGER>*)tmpStorage.getFirst() );
	    if ( !indPtr )
		return FALSE;
	    bool isNull = (*indPtr == SQL_NULL_DATA);
	    tmpStorage.removeFirst();

	    TQVariant::Type type = extension()->values[ it.data() ].value.type();
	    if ( isNull ) {
		TQVariant v;
		v.cast(type);
		extension()->values[ it.data() ].value = v;
		if (type != TQVariant::ByteArray)
		    tmpStorage.removeFirst();
		continue;
	    }

	    switch (type) {
		case TQVariant::Date: {
		    DATE_STRUCT * ds = qAutoDeleterData( (TQAutoDeleter<DATE_STRUCT>*)tmpStorage.getFirst() );
		    extension()->values[ it.data() ].value = TQVariant( TQDate( ds->year, ds->month, ds->day ) );
		    break; }
		case TQVariant::Time: {
		    TIME_STRUCT * dt = qAutoDeleterData( (TQAutoDeleter<TIME_STRUCT>*)tmpStorage.getFirst() );
		    extension()->values[ it.data() ].value = TQVariant( TQTime( dt->hour, dt->minute, dt->second ) );
		    break; }
		case TQVariant::DateTime: {
		    TIMESTAMP_STRUCT * dt = qAutoDeleterData( (TQAutoDeleter<TIMESTAMP_STRUCT>*)tmpStorage.getFirst() );
		    extension()->values[ it.data() ].value = TQVariant( TQDateTime( TQDate( dt->year, dt->month, dt->day ),
										  TQTime( dt->hour, dt->minute, dt->second ) ) );
		    break; }
	        case TQVariant::Int: {
		    int * v = qAutoDeleterData( (TQAutoDeleter<int>*)tmpStorage.getFirst() );
		    extension()->values[ it.data() ].value = TQVariant( *v );
		    break; }
	        case TQVariant::Double: {
		    double * v = qAutoDeleterData( (TQAutoDeleter<double>*)tmpStorage.getFirst() );
		    extension()->values[ it.data() ].value = TQVariant( *v );
		    break; }
	        case TQVariant::ByteArray:
		    break;
	        case TQVariant::String:
		    if ( d->unicode ) {
			TQString * str = qAutoDeleterData( (TQAutoDeleter<TQString>*)tmpStorage.getFirst() );
			extension()->values[ it.data() ].value = TQVariant( *str );
			break;
		    }
		    // fall through
	        default: {
		    TQCString * str = qAutoDeleterData( (TQAutoDeleter<TQCString>*)tmpStorage.getFirst() );
		    extension()->values[ it.data() ].value = TQVariant( *str );
		    break; }
	    }
	    if (type != TQVariant::ByteArray)
		tmpStorage.removeFirst();
	}
    }

    return TRUE;
}

////////////////////////////////////////


TQODBCDriver::TQODBCDriver( TQObject * parent, const char * name )
    : TQSqlDriver(parent,name ? name : "TQODBC")
{
    init();
}

TQODBCDriver::TQODBCDriver( SQLHANDLE env, SQLHANDLE con, TQObject * parent, const char * name )
    : TQSqlDriver(parent,name ? name : "TQODBC")
{
    init();
    d->hEnv = env;
    d->hDbc = con;
    if ( env && con ) {
	setOpen( TRUE );
	setOpenError( FALSE );
    }
}

void TQODBCDriver::init()
{
    tqSqlOpenExtDict()->insert( this, new TQODBCOpenExtension(this) );
    d = new TQODBCPrivate();
}

TQODBCDriver::~TQODBCDriver()
{
    cleanup();
    delete d;
    if ( !tqSqlOpenExtDict()->isEmpty() ) {
	TQSqlOpenExtension *ext = tqSqlOpenExtDict()->take( this );
	delete ext;
    }
}

bool TQODBCDriver::hasFeature( DriverFeature f ) const
{
    switch ( f ) {
    case Transactions: {
	if ( !d->hDbc )
	    return FALSE;
	SQLUSMALLINT txn;
	SQLSMALLINT t;
	int r = SQLGetInfo( d->hDbc,
			(SQLUSMALLINT)SQL_TXN_CAPABLE,
			&txn,
			sizeof(txn),
			&t);
	if ( r != SQL_SUCCESS || txn == SQL_TC_NONE )
	    return FALSE;
	else
	    return TRUE;
    }
    case QuerySize:
	return FALSE;
    case BLOB:
	return TRUE;
    case Unicode:
	return d->unicode;
    case PreparedQueries:
	return TRUE;
    case PositionalPlaceholders:
	return TRUE;
    default:
	return FALSE;
    }
}

bool TQODBCDriver::open( const TQString&,
			const TQString&,
			const TQString&,
			const TQString&,
			int )
{
    tqWarning("TQODBCDriver::open(): This version of open() is no longer supported." );
    return FALSE;
}

bool TQODBCDriver::open( const TQString & db,
			const TQString & user,
			const TQString & password,
			const TQString &,
			int,
			const TQString& connOpts )
{
    if ( isOpen() )
      close();
    SQLRETURN r;
    r = SQLAllocHandle( SQL_HANDLE_ENV,
			SQL_NULL_HANDLE,
			&d->hEnv);
    if ( r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO ) {
#ifdef QT_CHECK_RANGE
	qSqlWarning( "TQODBCDriver::open: Unable to allocate environment", d );
#endif
	setOpenError( TRUE );
	return FALSE;
    }
    r = SQLSetEnvAttr( d->hEnv,
		       SQL_ATTR_ODBC_VERSION,
		       (SQLPOINTER)SQL_OV_ODBC2,
		       SQL_IS_UINTEGER );
    r = SQLAllocHandle( SQL_HANDLE_DBC,
		        d->hEnv,
		        &d->hDbc);
    if ( r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO ) {
#ifdef QT_CHECK_RANGE
	qSqlWarning( "TQODBCDriver::open: Unable to allocate connection", d );
#endif
	setOpenError( TRUE );
	return FALSE;
    }

    if ( !d->setConnectionOptions( connOpts ) )
	return FALSE;

    // Create the connection string
    TQString connTQStr;
    // support the "DRIVER={SQL SERVER};SERVER=blah" syntax
    if ( db.contains(".dsn") )
	connTQStr = "FILEDSN=" + db;
    else if ( db.contains( "DRIVER" ) || db.contains( "SERVER" ) )
	connTQStr = db;
    else
	connTQStr = "DSN=" + db;
    connTQStr += ";UID=" + user + ";PWD=" + password;
    SQLSMALLINT cb;
    SQLTCHAR connOut[1024];
    r = SQLDriverConnect( d->hDbc,
			  NULL,
#ifdef UNICODE
			  (SQLWCHAR*)connTQStr.unicode(),
#else
			  (SQLCHAR*)connTQStr.latin1(),
#endif
			  (SQLSMALLINT)connTQStr.length(),
			  connOut,
			  1024,
			  &cb,
			  SQL_DRIVER_NOPROMPT );
    if ( r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO ) {
	setLastError( qMakeError( "Unable to connect", TQSqlError::Connection, d ) );
	setOpenError( TRUE );
	return FALSE;
    }

    if ( !d->checkDriver() ) {
	setLastError( qMakeError( "Unable to connect - Driver doesn't support all needed functionality", TQSqlError::Connection, d ) );
	setOpenError( TRUE );
	return FALSE;
    }

    d->checkUnicode();
    d->checkSchemaUsage();

    setOpen( TRUE );
    setOpenError( FALSE );
    return TRUE;
}

void TQODBCDriver::close()
{
    cleanup();
    setOpen( FALSE );
    setOpenError( FALSE );
}

bool TQODBCDriver::ping()
{
    // FIXME
    // Implement ping if supported
    return TRUE;
}

void TQODBCDriver::cleanup()
{
    SQLRETURN r;
    if ( !d )
	return;

    if( d->hDbc ) {
	// Open statements/descriptors handles are automatically cleaned up by SQLDisconnect
	if ( isOpen() ) {
	    r = SQLDisconnect( d->hDbc );
#ifdef QT_CHECK_RANGE
	    if ( r != SQL_SUCCESS )
		qSqlWarning( "TQODBCDriver::disconnect: Unable to disconnect datasource", d );
#endif
	}

	r = SQLFreeHandle( SQL_HANDLE_DBC, d->hDbc );
#ifdef QT_CHECK_RANGE
	if ( r != SQL_SUCCESS )
	    qSqlWarning( "TQODBCDriver::cleanup: Unable to free connection handle", d );
#endif
	d->hDbc = 0;
    }

    if ( d->hEnv ) {
	r = SQLFreeHandle( SQL_HANDLE_ENV, d->hEnv );
#ifdef QT_CHECK_RANGE
	if ( r != SQL_SUCCESS )
	    qSqlWarning( "TQODBCDriver::cleanup: Unable to free environment handle", d );
#endif
	d->hEnv = 0;
    }
}

// checks whether the server can return char, varchar and longvarchar
// as two byte unicode characters
void TQODBCPrivate::checkUnicode()
{
#if defined(Q_WS_WIN)
    if ( !qt_winunicode ) {
	unicode = FALSE;
	return;
    }
#endif
    SQLRETURN   r;
    SQLUINTEGER fFunc;

    unicode = FALSE;
    r = SQLGetInfo( hDbc,
		    SQL_CONVERT_CHAR,
		    (SQLPOINTER)&fFunc,
		    sizeof(fFunc),
		    NULL );
    if ( ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO ) && ( fFunc & SQL_CVT_WCHAR ) ) {
	sql_char_type = TQVariant::String;
	unicode = TRUE;
    }

    r = SQLGetInfo( hDbc,
		    SQL_CONVERT_VARCHAR,
		    (SQLPOINTER)&fFunc,
		    sizeof(fFunc),
		    NULL );
    if ( ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO ) && ( fFunc & SQL_CVT_WVARCHAR ) ) {
	sql_varchar_type = TQVariant::String;
	unicode = TRUE;
    }

    r = SQLGetInfo( hDbc,
		    SQL_CONVERT_LONGVARCHAR,
		    (SQLPOINTER)&fFunc,
		    sizeof(fFunc),
		    NULL );
    if ( ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO ) && ( fFunc & SQL_CVT_WLONGVARCHAR ) ) {
	sql_longvarchar_type = TQVariant::String;
	unicode = TRUE;
    }
}

bool TQODBCPrivate::checkDriver() const
{
#ifdef ODBC_CHECK_DRIVER
    // do not query for SQL_API_SQLFETCHSCROLL because it can't be used at this time
    static const SQLUSMALLINT reqFunc[] = {
		SQL_API_SQLDESCRIBECOL, SQL_API_SQLGETDATA, SQL_API_SQLCOLUMNS,
		SQL_API_SQLGETSTMTATTR, SQL_API_SQLGETDIAGREC, SQL_API_SQLEXECDIRECT,
		SQL_API_SQLGETINFO, SQL_API_SQLTABLES, 0
    };

    // these functions are optional
    static const SQLUSMALLINT optFunc[] = {
	SQL_API_SQLNUMRESULTCOLS, SQL_API_SQLROWCOUNT, 0
    };

    SQLRETURN r;
    SQLUSMALLINT sup;


    int i;
    // check the required functions
    for ( i = 0; reqFunc[ i ] != 0; ++i ) {

	r = SQLGetFunctions( hDbc, reqFunc[ i ], &sup );

#ifdef QT_CHECK_RANGE
        if ( r != SQL_SUCCESS ) {
	    qSqlWarning( "TQODBCDriver::checkDriver: Cannot get list of supported functions", this );
	    return FALSE;
	}
#endif
	if ( sup == SQL_FALSE ) {
#ifdef QT_CHECK_RANGE
	    tqWarning ( "TQODBCDriver::open: Warning - Driver doesn't support all needed functionality (%d). "
		       "Please look at the TQt SQL Module Driver documentation for more information.", reqFunc[ i ] );
#endif
	    return FALSE;
	}
    }

    // these functions are optional and just generate a warning
    for ( i = 0; optFunc[ i ] != 0; ++i ) {

	r = SQLGetFunctions( hDbc, optFunc[ i ], &sup );

#ifdef QT_CHECK_RANGE
        if ( r != SQL_SUCCESS ) {
	    qSqlWarning( "TQODBCDriver::checkDriver: Cannot get list of supported functions", this );
	    return FALSE;
	}
#endif
	if ( sup == SQL_FALSE ) {
#ifdef QT_CHECK_RANGE
	    tqWarning( "TQODBCDriver::checkDriver: Warning - Driver doesn't support some non-critical functions (%d)", optFunc[ i ] );
#endif
	    return TRUE;
	}
    }
#endif //ODBC_CHECK_DRIVER

    return TRUE;
}

void TQODBCPrivate::checkSchemaUsage()
{
    SQLRETURN   r;
    SQLUINTEGER val;

    r = SQLGetInfo(hDbc,
		   SQL_SCHEMA_USAGE,
		   (SQLPOINTER) &val,
		   sizeof(val),
		   NULL);
    if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
	useSchema = (val != 0);
}

TQSqlQuery TQODBCDriver::createQuery() const
{
    return TQSqlQuery( new TQODBCResult( this, d ) );
}

bool TQODBCDriver::beginTransaction()
{
    if ( !isOpen() ) {
#ifdef QT_CHECK_RANGE
	tqWarning(" TQODBCDriver::beginTransaction: Database not open" );
#endif
	return FALSE;
    }
    SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF);
    SQLRETURN r  = SQLSetConnectAttr( d->hDbc,
				      SQL_ATTR_AUTOCOMMIT,
				      (SQLPOINTER)ac,
				      sizeof(ac) );
    if ( r != SQL_SUCCESS ) {
	setLastError( qMakeError( "Unable to disable autocommit", TQSqlError::Transaction, d ) );
	return FALSE;
    }
    return TRUE;
}

bool TQODBCDriver::commitTransaction()
{
    if ( !isOpen() ) {
#ifdef QT_CHECK_RANGE
	tqWarning(" TQODBCDriver::commitTransaction: Database not open" );
#endif
	return FALSE;
    }
    SQLRETURN r = SQLEndTran( SQL_HANDLE_DBC,
			      d->hDbc,
			      SQL_COMMIT );
    if ( r != SQL_SUCCESS ) {
	setLastError( qMakeError("Unable to commit transaction", TQSqlError::Transaction, d ) );
	return FALSE;
    }
    return endTrans();
}

bool TQODBCDriver::rollbackTransaction()
{
    if ( !isOpen() ) {
#ifdef QT_CHECK_RANGE
	tqWarning(" TQODBCDriver::rollbackTransaction: Database not open" );
#endif
	return FALSE;
    }
    SQLRETURN r = SQLEndTran( SQL_HANDLE_DBC,
			      d->hDbc,
			      SQL_ROLLBACK );
    if ( r != SQL_SUCCESS ) {
	setLastError( qMakeError( "Unable to rollback transaction", TQSqlError::Transaction, d ) );
	return FALSE;
    }
    return endTrans();
}

bool TQODBCDriver::endTrans()
{
    SQLUINTEGER ac(SQL_AUTOCOMMIT_ON);
    SQLRETURN r  = SQLSetConnectAttr( d->hDbc,
				      SQL_ATTR_AUTOCOMMIT,
				      (SQLPOINTER)ac,
				      sizeof(ac));
    if ( r != SQL_SUCCESS ) {
	setLastError( qMakeError( "Unable to enable autocommit", TQSqlError::Transaction, d ) );
	return FALSE;
    }
    return TRUE;
}

TQStringList TQODBCDriver::tables( const TQString& typeName ) const
{
    TQStringList tl;
    if ( !isOpen() )
	return tl;
    int type = typeName.toInt();
    SQLHANDLE hStmt;

    SQLRETURN r = SQLAllocHandle( SQL_HANDLE_STMT,
				  d->hDbc,
				  &hStmt );
    if ( r != SQL_SUCCESS ) {
#ifdef QT_CHECK_RANGE
	qSqlWarning( "TQODBCDriver::tables: Unable to allocate handle", d );
#endif
	return tl;
    }
    r = SQLSetStmtAttr( hStmt,
			SQL_ATTR_CURSOR_TYPE,
			(SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
			SQL_IS_UINTEGER );
    TQString tableType;
    if ( typeName.isEmpty() || ((type & (int)TQSql::Tables) == (int)TQSql::Tables) )
	tableType += "TABLE,";
    if ( (type & (int)TQSql::Views) == (int)TQSql::Views )
	tableType += "VIEW,";
    if ( (type & (int)TQSql::SystemTables) == (int)TQSql::SystemTables )
	tableType += "SYSTEM TABLE,";
    if ( tableType.isEmpty() )
	return tl;
    tableType.truncate( tableType.length() - 1 );

    r = SQLTables( hStmt,
		   NULL,
		   0,
		   NULL,
		   0,
		   NULL,
		   0,
#ifdef UNICODE
		   (SQLWCHAR*)tableType.unicode(),
#else
		   (SQLCHAR*)tableType.latin1(),
#endif
		   tableType.length() /* characters, not bytes */ );

#ifdef QT_CHECK_RANGE
    if ( r != SQL_SUCCESS )
	qSqlWarning( "TQODBCDriver::tables Unable to execute table list", d );
#endif
    r = SQLFetchScroll( hStmt,
			SQL_FETCH_NEXT,
			0);
    while ( r == SQL_SUCCESS ) {
	bool isNull;
	TQString fieldVal = qGetStringData( hStmt, 2, -1, isNull, d->unicode );
	tl.append( fieldVal );
	r = SQLFetchScroll( hStmt,
			    SQL_FETCH_NEXT,
			    0);
    }

    r = SQLFreeHandle( SQL_HANDLE_STMT, hStmt );
    if ( r!= SQL_SUCCESS )
	qSqlWarning( "TQODBCDriver: Unable to free statement handle" + TQString::number(r), d );
    return tl;
}

TQSqlIndex TQODBCDriver::primaryIndex( const TQString& tablename ) const
{
    TQSqlIndex index( tablename );
    if ( !isOpen() )
	return index;
    bool usingSpecialColumns = FALSE;
    TQSqlRecord rec = record( tablename );

    SQLHANDLE hStmt;
    SQLRETURN r = SQLAllocHandle( SQL_HANDLE_STMT,
				  d->hDbc,
				  &hStmt );
    if ( r != SQL_SUCCESS ) {
#ifdef QT_CHECK_RANGE
	qSqlWarning( "TQODBCDriver::primaryIndex: Unable to list primary key", d );
#endif
	return index;
    }
    TQString catalog, schema, table;
    d->splitTableQualifier( tablename, catalog, schema, table );
    r = SQLSetStmtAttr( hStmt,
			SQL_ATTR_CURSOR_TYPE,
			(SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
			SQL_IS_UINTEGER );
    r = SQLPrimaryKeys( hStmt,
#ifdef UNICODE
			catalog.length() == 0 ? NULL : (SQLWCHAR*)catalog.unicode(),
#else
			catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.latin1(),
#endif
			catalog.length(),
#ifdef UNICODE
			schema.length() == 0 ? NULL : (SQLWCHAR*)schema.unicode(),
#else
			schema.length() == 0 ? NULL : (SQLCHAR*)schema.latin1(),
#endif
			schema.length(),
#ifdef UNICODE
			(SQLWCHAR*)table.unicode(),
#else
			(SQLCHAR*)table.latin1(),
#endif
			table.length() /* in characters, not in bytes */);

    // if the SQLPrimaryKeys() call does not succeed (e.g the driver
    // does not support it) - try an alternative method to get hold of
    // the primary index (e.g MS Access and FoxPro)
    if ( r != SQL_SUCCESS ) {
	    r = SQLSpecialColumns( hStmt,
				   SQL_BEST_ROWID,
#ifdef UNICODE
				   catalog.length() == 0 ? NULL : (SQLWCHAR*)catalog.unicode(),
#else
				   catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.latin1(),
#endif
				   catalog.length(),
#ifdef UNICODE
				   schema.length() == 0 ? NULL : (SQLWCHAR*)schema.unicode(),
#else
				   schema.length() == 0 ? NULL : (SQLCHAR*)schema.latin1(),
#endif
				   schema.length(),
#ifdef UNICODE
				   (SQLWCHAR*)table.unicode(),
#else
				   (SQLCHAR*)table.latin1(),
#endif

				   table.length(),
				   SQL_SCOPE_CURROW,
				   SQL_NULLABLE );

	    if ( r != SQL_SUCCESS ) {
#ifdef QT_CHECK_RANGE
		qSqlWarning( "TQODBCDriver::primaryIndex: Unable to execute primary key list", d );
#endif
	    } else {
		usingSpecialColumns = TRUE;
	    }
    }
    r = SQLFetchScroll( hStmt,
			SQL_FETCH_NEXT,
			0 );
    bool isNull;
    int fakeId = 0;
    TQString cName, idxName;
    // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
    while ( r == SQL_SUCCESS ) {
	if ( usingSpecialColumns ) {
	    cName = qGetStringData( hStmt, 1, -1, isNull, d->unicode ); // column name
	    idxName = TQString::number( fakeId++ ); // invent a fake index name
	} else {
	    cName = qGetStringData( hStmt, 3, -1, isNull, d->unicode ); // column name
	    idxName = qGetStringData( hStmt, 5, -1, isNull, d->unicode ); // pk index name
	}
        TQSqlField *fld = rec.field(cName);
        if (fld)
	    index.append(*fld);
	index.setName( idxName );
	r = SQLFetchScroll( hStmt,
			    SQL_FETCH_NEXT,
			    0 );
    }
    r = SQLFreeHandle( SQL_HANDLE_STMT, hStmt );
    if ( r!= SQL_SUCCESS )
	qSqlWarning( "TQODBCDriver: Unable to free statement handle" + TQString::number(r), d );
    return index;
}

TQSqlRecord TQODBCDriver::record( const TQString& tablename ) const
{
    return recordInfo( tablename ).toRecord();
}

TQSqlRecord TQODBCDriver::record( const TQSqlQuery& query ) const
{
    return recordInfo( query ).toRecord();
}

TQSqlRecordInfo TQODBCDriver::recordInfo( const TQString& tablename ) const
{
    TQSqlRecordInfo fil;
    if ( !isOpen() )
	return fil;

    SQLHANDLE hStmt;
    TQString catalog, schema, table;
    d->splitTableQualifier( tablename, catalog, schema, table );
    SQLRETURN r = SQLAllocHandle( SQL_HANDLE_STMT,
				  d->hDbc,
				  &hStmt );
    if ( r != SQL_SUCCESS ) {
#ifdef QT_CHECK_RANGE
	qSqlWarning( "TQODBCDriver::record: Unable to allocate handle", d );
#endif
	return fil;
    }
    r = SQLSetStmtAttr( hStmt,
			SQL_ATTR_CURSOR_TYPE,
			(SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
			SQL_IS_UINTEGER );
    r =  SQLColumns( hStmt,
#ifdef UNICODE
		     catalog.length() == 0 ? NULL : (SQLWCHAR*)catalog.unicode(),
#else
		     catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.latin1(),
#endif
		     catalog.length(),
#ifdef UNICODE
		     schema.length() == 0 ? NULL : (SQLWCHAR*)schema.unicode(),
#else
		     schema.length() == 0 ? NULL : (SQLCHAR*)schema.latin1(),
#endif
		     schema.length(),
#ifdef UNICODE
		     (SQLWCHAR*)table.unicode(),
#else
		     (SQLCHAR*)table.latin1(),
#endif
		     table.length(),
		     NULL,
		     0 );
#ifdef QT_CHECK_RANGE
    if ( r != SQL_SUCCESS )
	qSqlWarning( "TQODBCDriver::record: Unable to execute column list", d );
#endif
    r = SQLFetchScroll( hStmt,
			SQL_FETCH_NEXT,
			0);
    // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
    while ( r == SQL_SUCCESS ) {

	fil.append( qMakeFieldInfo( hStmt, d ) );

	r = SQLFetchScroll( hStmt,
			    SQL_FETCH_NEXT,
			    0);
    }

    r = SQLFreeHandle( SQL_HANDLE_STMT, hStmt );
    if ( r!= SQL_SUCCESS )
	qSqlWarning( "TQODBCDriver: Unable to free statement handle " + TQString::number(r), d );

    return fil;
}

TQSqlRecordInfo TQODBCDriver::recordInfo( const TQSqlQuery& query ) const
{
    TQSqlRecordInfo fil;
    if ( !isOpen() )
	return fil;
    if ( query.isActive() && query.driver() == this ) {
	TQODBCResult* result = (TQODBCResult*)query.result();
	fil = result->d->rInf;
    }
    return fil;
}

SQLHANDLE TQODBCDriver::environment()
{
    return d->hEnv;
}

SQLHANDLE TQODBCDriver::connection()
{
    return d->hDbc;
}

TQString TQODBCDriver::formatValue( const TQSqlField* field,
				  bool trimStrings ) const
{
    TQString r;
    if ( field->isNull() ) {
	r = nullText();
    } else if ( field->type() == TQVariant::DateTime ) {
	// Use an escape sequence for the datetime fields
	if ( field->value().toDateTime().isValid() ){
	    TQDate dt = field->value().toDateTime().date();
	    TQTime tm = field->value().toDateTime().time();
	    // Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10
	    r = "{ ts '" +
		TQString::number(dt.year()) + "-" +
		TQString::number(dt.month()).rightJustify( 2, '0', TRUE ) + "-" +
		TQString::number(dt.day()).rightJustify( 2, '0', TRUE ) + " " +
		tm.toString() +
		"' }";
	} else
	    r = nullText();
    } else if ( field->type() == TQVariant::ByteArray ) {
	TQByteArray ba = field->value().toByteArray();
	TQString res;
	static const char hexchars[] = "0123456789abcdef";
	for ( uint i = 0; i < ba.size(); ++i ) {
	    uchar s = (uchar) ba[(int)i];
	    res += hexchars[s >> 4];
	    res += hexchars[s & 0x0f];
	}
	r = "0x" + res;
    } else {
	r = TQSqlDriver::formatValue( field, trimStrings );
    }
    return r;
}
