You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tqt3/src/sql/drivers/odbc/qsql_odbc.cpp

2042 lines
55 KiB

/****************************************************************************
**
** 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(TQ_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;
}