/**************************************************************************** ** ** Implementation of ODBC driver classes ** ** Created : 001103 ** ** Copyright (C) 2010 Timothy Pearson and (C) 1992-2008 Trolltech ASA. ** ** 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 "tqsql_odbc.h" #include #if defined (TQ_OS_WIN32) #include #include #endif #include #include #include #include // undefine this to prevent initial check of the ODBC driver #define ODBC_CHECK_DRIVER #if defined(TQ_ODBC_VERSION_2) //crude hack to get non-tqunicode capable driver managers to work # undef UNICODE # define STQLTCHAR STQLCHAR # define STQL_C_WCHAR STQL_C_CHAR #endif // newer platform SDKs use STQLLEN instead of STQLINTEGER #ifdef STQLLEN # define TQSTQLLEN STQLLEN #else # define TQSTQLLEN STQLINTEGER #endif #ifdef STQLULEN # define TQSQLULEN STQLULEN #else # define TQSQLULEN STQLUINTEGER #endif static const TQSTQLLEN COLNAMESIZE = 256; //Map TQt parameter types to ODBC types static const STQLSMALLINT qParamType[ 4 ] = { STQL_PARAM_INPUT, STQL_PARAM_INPUT, STQL_PARAM_OUTPUT, STQL_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; tqunicode = FALSE; } STQLHANDLE hEnv; STQLHANDLE hDbc; STQLHANDLE hStmt; bool tqunicode; 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 *qSqlOpenExtDict(); 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, STQLHANDLE handle) { STQLINTEGER nativeCode_; STQLSMALLINT msgLen; STQLRETURN r = STQL_ERROR; STQLTCHAR state_[STQL_STQLSTATE_SIZE+1]; STQLTCHAR description_[STQL_MAX_MESSAGE_LENGTH]; r = STQLGetDiagRec( handleType, handle, 1, (STQLTCHAR*)state_, &nativeCode_, (STQLTCHAR*)description_, STQL_MAX_MESSAGE_LENGTH-1, /* in bytes, not in characters */ &msgLen); if ( r == STQL_SUCCESS || r == STQL_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( STQL_HANDLE_ENV, odbc->hEnv ) + " " + qWarnODBCHandle( STQL_HANDLE_DBC, odbc->hDbc ) + " " + qWarnODBCHandle( STQL_HANDLE_STMT, odbc->hStmt ) ); } static void qSqlWarning( const TQString& message, const TQODBCPrivate* odbc ) { #ifdef TQT_CHECK_RANGE qWarning( "%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( STQLSMALLINT sqltype, const TQODBCPrivate* p ) { TQVariant::Type type = TQVariant::Invalid; switch ( sqltype ) { case STQL_DECIMAL: case STQL_NUMERIC: case STQL_REAL: case STQL_FLOAT: case STQL_DOUBLE: type = TQVariant::Double; break; case STQL_SMALLINT: case STQL_INTEGER: case STQL_BIT: case STQL_TINYINT: type = TQVariant::Int; break; case STQL_BIGINT: type = TQVariant::LongLong; break; case STQL_BINARY: case STQL_VARBINARY: case STQL_LONGVARBINARY: type = TQVariant::ByteArray; break; case STQL_DATE: case STQL_TYPE_DATE: type = TQVariant::Date; break; case STQL_TIME: case STQL_TYPE_TIME: type = TQVariant::Time; break; case STQL_TIMESTAMP: case STQL_TYPE_TIMESTAMP: type = TQVariant::DateTime; break; #ifndef TQ_ODBC_VERSION_2 case STQL_WCHAR: case STQL_WVARCHAR: case STQL_WLONGVARCHAR: type = TQVariant::String; break; #endif case STQL_CHAR: type = p->sql_char_type; break; case STQL_VARCHAR: type = p->sql_varchar_type; break; case STQL_LONGVARCHAR: type = p->sql_longvarchar_type; break; default: type = TQVariant::CString; break; } return type; } static TQString qGetStringData( STQLHANDLE hStmt, int column, int colSize, bool& isNull, bool tqunicode = FALSE ) { TQString fieldVal; STQLRETURN r = STQL_ERROR; TQSTQLLEN 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 ( tqunicode ) { colSize *= 2; // a tiny bit faster, since it saves a STQLGetData() call } } char* buf = new char[ colSize ]; while ( TRUE ) { r = STQLGetData( hStmt, column+1, tqunicode ? STQL_C_WCHAR : STQL_C_CHAR, (STQLPOINTER)buf, (TQSTQLLEN)colSize, &lengthIndicator ); if ( r == STQL_SUCCESS || r == STQL_SUCCESS_WITH_INFO ) { if ( lengthIndicator == STQL_NULL_DATA || lengthIndicator == STQL_NO_TOTAL ) { fieldVal = TQString::null; isNull = TRUE; break; } // if STQL_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 == STQL_SUCCESS_WITH_INFO) ? (tqunicode ? colSize-2 : colSize-1) : lengthIndicator; if ( tqunicode ) { fieldVal += TQString( (TQChar*) buf, rSize / 2 ); } else { buf[ rSize ] = 0; fieldVal += buf; } if ( lengthIndicator < colSize ) { // workaround for Drivermanagers that don't return STQL_NO_DATA break; } } else if ( r == STQL_NO_DATA ) { break; } else { #ifdef TQT_CHECK_RANGE qWarning( "qGetStringData: Error while fetching data (%d)", r ); #endif fieldVal = TQString::null; break; } } delete[] buf; return fieldVal; } static TQByteArray qGetBinaryData( STQLHANDLE hStmt, int column, TQSTQLLEN& lengthIndicator, bool& isNull ) { TQByteArray fieldVal; STQLSMALLINT colNameLen; STQLSMALLINT colType; TQSQLULEN colSize; STQLSMALLINT colScale; STQLSMALLINT nullable; STQLRETURN r = STQL_ERROR; STQLTCHAR colName[COLNAMESIZE]; r = STQLDescribeCol( hStmt, column+1, colName, COLNAMESIZE, &colNameLen, &colType, &colSize, &colScale, &nullable ); #ifdef TQT_CHECK_RANGE if ( r != STQL_SUCCESS ) qWarning( "qGetBinaryData: Unable to describe column %d", column ); #endif // STQLDescribeCol 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 = STQLGetData( hStmt, column+1, STQL_C_BINARY, (STQLPOINTER) buf, (TQSTQLLEN)colSize, &lengthIndicator ); if ( r == STQL_SUCCESS || r == STQL_SUCCESS_WITH_INFO ) { if ( lengthIndicator == STQL_NULL_DATA ) { isNull = TRUE; break; } else { int rSize; r == STQL_SUCCESS ? rSize = lengthIndicator : rSize = colSize; if ( lengthIndicator == STQL_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 == STQL_SUCCESS ) { // the whole field was read in one chunk break; } } } else { break; } } delete [] buf; return fieldVal; } static int qGetIntData( STQLHANDLE hStmt, int column, bool& isNull ) { TQSTQLLEN intbuf = 0; isNull = FALSE; TQSTQLLEN lengthIndicator = 0; STQLRETURN r = STQLGetData( hStmt, column+1, STQL_C_SLONG, (STQLPOINTER)&intbuf, (TQSTQLLEN)0, &lengthIndicator ); if ( ( r != STQL_SUCCESS && r != STQL_SUCCESS_WITH_INFO ) || lengthIndicator == STQL_NULL_DATA ) { isNull = TRUE; return 0; } return (int)intbuf; } static double qGetDoubleData( STQLHANDLE hStmt, int column, bool& isNull ) { STQLDOUBLE dblbuf; TQSTQLLEN lengthIndicator = 0; isNull = FALSE; STQLRETURN r = STQLGetData( hStmt, column+1, STQL_C_DOUBLE, (STQLPOINTER)&dblbuf, (TQSTQLLEN)0, &lengthIndicator ); if ( ( r != STQL_SUCCESS && r != STQL_SUCCESS_WITH_INFO ) || lengthIndicator == STQL_NULL_DATA ) { isNull = TRUE; return 0.0; } return (double) dblbuf; } static STQLBIGINT qGetBigIntData( STQLHANDLE hStmt, int column, bool& isNull ) { STQLBIGINT lngbuf = TQ_INT64_C( 0 ); isNull = FALSE; TQSTQLLEN lengthIndicator = 0; STQLRETURN r = STQLGetData( hStmt, column+1, STQL_C_SBIGINT, (STQLPOINTER) &lngbuf, (TQSTQLLEN)0, &lengthIndicator ); if ( ( r != STQL_SUCCESS && r != STQL_SUCCESS_WITH_INFO ) || lengthIndicator == STQL_NULL_DATA ) isNull = TRUE; return lngbuf; } // creates a TQSqlFieldInfo from a valid hStmt generated // by STQLColumns. The hStmt has to point to a valid position. static TQSqlFieldInfo qMakeFieldInfo( const STQLHANDLE hStmt, const TQODBCPrivate* p ) { bool isNull; TQString fname = qGetStringData( hStmt, 3, -1, isNull, p->tqunicode ); int type = qGetIntData( hStmt, 4, isNull ); // column type int required = qGetIntData( hStmt, 10, isNull ); // nullable-flag // required can be STQL_NO_NULLS, STQL_NULLABLE or STQL_NULLABLE_UNKNOWN if ( required == STQL_NO_NULLS ) { required = 1; } else if ( required == STQL_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 ) { STQLSMALLINT colNameLen; STQLSMALLINT colType; TQSQLULEN colSize; STQLSMALLINT colScale; STQLSMALLINT nullable; STQLRETURN r = STQL_ERROR; STQLTCHAR colName[ COLNAMESIZE ]; r = STQLDescribeCol( p->hStmt, i+1, colName, (TQSQLULEN)COLNAMESIZE, &colNameLen, &colType, &colSize, &colScale, &nullable); if ( r != STQL_SUCCESS ) { #ifdef TQT_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 STQL_NO_NULLS, STQL_NULLABLE or STQL_NULLABLE_UNKNOWN int required = -1; if ( nullable == STQL_NO_NULLS ) { required = 1; } else if ( nullable == STQL_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; STQLRETURN r = STQL_SUCCESS; TQMap 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 qWarning( "TQODBCDriver::open: Illegal connect option value '%s'", tmp.latin1() ); } if ( connMap.count() ) { TQMap::ConstIterator it; TQString opt, val; STQLUINTEGER v = 0; for ( it = connMap.begin(); it != connMap.end(); ++it ) { opt = it.key().upper(); val = it.data().upper(); r = STQL_SUCCESS; if ( opt == "STQL_ATTR_ACCESS_MODE" ) { if ( val == "STQL_MODE_READ_ONLY" ) { v = STQL_MODE_READ_ONLY; } else if ( val == "STQL_MODE_READ_WRITE" ) { v = STQL_MODE_READ_WRITE; } else { qWarning( TQString( "TQODBCDriver::open: Unknown option value '%1'" ).arg( *it ) ); break; } r = STQLSetConnectAttr( hDbc, STQL_ATTR_ACCESS_MODE, (STQLPOINTER) v, 0 ); } else if ( opt == "STQL_ATTR_CONNECTION_TIMEOUT" ) { v = val.toUInt(); r = STQLSetConnectAttr( hDbc, STQL_ATTR_CONNECTION_TIMEOUT, (STQLPOINTER) v, 0 ); } else if ( opt == "STQL_ATTR_LOGIN_TIMEOUT" ) { v = val.toUInt(); r = STQLSetConnectAttr( hDbc, STQL_ATTR_LOGIN_TIMEOUT, (STQLPOINTER) v, 0 ); } else if ( opt == "STQL_ATTR_CURRENT_CATALOG" ) { val.ucs2(); // 0 terminate r = STQLSetConnectAttr( hDbc, STQL_ATTR_CURRENT_CATALOG, #ifdef UNICODE (STQLWCHAR*) val.tqunicode(), #else (STQLCHAR*) val.latin1(), #endif STQL_NTS ); } else if ( opt == "STQL_ATTR_METADATA_ID" ) { if ( val == "STQL_TRUE" ) { v = STQL_TRUE; } else if ( val == "STQL_FALSE" ) { v = STQL_FALSE; } else { qWarning( TQString( "TQODBCDriver::open: Unknown option value '%1'" ).arg( *it ) ); break; } r = STQLSetConnectAttr( hDbc, STQL_ATTR_METADATA_ID, (STQLPOINTER) v, 0 ); } else if ( opt == "STQL_ATTR_PACKET_SIZE" ) { v = val.toUInt(); r = STQLSetConnectAttr( hDbc, STQL_ATTR_PACKET_SIZE, (STQLPOINTER) v, 0 ); } else if ( opt == "STQL_ATTR_TRACEFILE" ) { val.ucs2(); // 0 terminate r = STQLSetConnectAttr( hDbc, STQL_ATTR_TRACEFILE, #ifdef UNICODE (STQLWCHAR*) val.tqunicode(), #else (STQLCHAR*) val.latin1(), #endif STQL_NTS ); } else if ( opt == "STQL_ATTR_TRACE" ) { if ( val == "STQL_OPT_TRACE_OFF" ) { v = STQL_OPT_TRACE_OFF; } else if ( val == "STQL_OPT_TRACE_ON" ) { v = STQL_OPT_TRACE_ON; } else { qWarning( TQString( "TQODBCDriver::open: Unknown option value '%1'" ).arg( *it ) ); break; } r = STQLSetConnectAttr( hDbc, STQL_ATTR_TRACE, (STQLPOINTER) v, 0 ); } #ifdef TQT_CHECK_RANGE else { qWarning( TQString("TQODBCDriver::open: Unknown connection attribute '%1'").arg( opt ) ); } #endif if ( r != STQL_SUCCESS && r != STQL_SUCCESS_WITH_INFO ) { #ifdef TQT_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() ) { STQLRETURN r = STQLFreeHandle( STQL_HANDLE_STMT, d->hStmt ); #ifdef TQT_CHECK_RANGE if ( r != STQL_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 ); STQLRETURN r; d->rInf.clear(); // Always reallocate the statement handle - the statement attributes // are not reset if STQLFreeStmt() is called which causes some problems. if ( d->hStmt ) { r = STQLFreeHandle( STQL_HANDLE_STMT, d->hStmt ); if ( r != STQL_SUCCESS ) { #ifdef TQT_CHECK_RANGE qSqlWarning( "TQODBCResult::reset: Unable to free statement handle", d ); #endif return FALSE; } } r = STQLAllocHandle( STQL_HANDLE_STMT, d->hDbc, &d->hStmt ); if ( r != STQL_SUCCESS ) { #ifdef TQT_CHECK_RANGE qSqlWarning( "TQODBCResult::reset: Unable to allocate statement handle", d ); #endif return FALSE; } if ( isForwardOnly() ) { r = STQLSetStmtAttr( d->hStmt, STQL_ATTR_CURSOR_TYPE, (STQLPOINTER)STQL_CURSOR_FORWARD_ONLY, STQL_IS_UINTEGER ); } else { r = STQLSetStmtAttr( d->hStmt, STQL_ATTR_CURSOR_TYPE, (STQLPOINTER)STQL_CURSOR_STATIC, STQL_IS_UINTEGER ); } if ( r != STQL_SUCCESS && r != STQL_SUCCESS_WITH_INFO ) { #ifdef TQT_CHECK_RANGE qSqlWarning( "TQODBCResult::reset: Unable to set 'STQL_CURSOR_STATIC' as statement attribute. Please check your ODBC driver configuration", d ); #endif return FALSE; } #ifdef UNICODE r = STQLExecDirect( d->hStmt, (STQLWCHAR*) query.tqunicode(), (STQLINTEGER) query.length() ); #else TQCString query8 = query.local8Bit(); r = STQLExecDirect( d->hStmt, (STQLCHAR*) query8.data(), (STQLINTEGER) query8.length() ); #endif if ( r != STQL_SUCCESS && r != STQL_SUCCESS_WITH_INFO ) { setLastError( qMakeError( "Unable to execute statement", TQSqlError::Statement, d ) ); return FALSE; } STQLSMALLINT count; r = STQLNumResultCols( 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; } STQLRETURN r; if ( isForwardOnly() ) { bool ok = TRUE; while ( ok && i > at() ) ok = fetchNext(); return ok; } else { r = STQLFetchScroll( d->hStmt, STQL_FETCH_ABSOLUTE, actualIdx ); } if ( r != STQL_SUCCESS ){ return FALSE; } setAt( i ); return TRUE; } bool TQODBCResult::fetchNext() { STQLRETURN r; fieldCache.clear(); nullCache.clear(); r = STQLFetchScroll( d->hStmt, STQL_FETCH_NEXT, 0 ); if ( r != STQL_SUCCESS ) return FALSE; setAt( at() + 1 ); return TRUE; } bool TQODBCResult::fetchFirst() { if ( isForwardOnly() && at() != TQSql::BeforeFirst ) return FALSE; STQLRETURN r; fieldCache.clear(); nullCache.clear(); if ( isForwardOnly() ) { return fetchNext(); } r = STQLFetchScroll( d->hStmt, STQL_FETCH_FIRST, 0 ); if ( r != STQL_SUCCESS ) return FALSE; setAt( 0 ); return TRUE; } bool TQODBCResult::fetchPrior() { if ( isForwardOnly() ) return FALSE; STQLRETURN r; fieldCache.clear(); nullCache.clear(); r = STQLFetchScroll( d->hStmt, STQL_FETCH_PRIOR, 0 ); if ( r != STQL_SUCCESS ) return FALSE; setAt( at() - 1 ); return TRUE; } bool TQODBCResult::fetchLast() { STQLRETURN 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 = STQLFetchScroll( d->hStmt, STQL_FETCH_LAST, 0 ); if ( r != STQL_SUCCESS ) { return FALSE; } STQLINTEGER currRow; r = STQLGetStmtAttr( d->hStmt, STQL_ROW_NUMBER, &currRow, STQL_IS_INTEGER, 0 ); if ( r != STQL_SUCCESS ) return FALSE; setAt( currRow-1 ); return TRUE; } TQVariant TQODBCResult::data( int field ) { if ( field >= (int) d->rInf.count() ) { qWarning( "TQODBCResult::data: column %d out of range", field ); return TQVariant(); } if ( fieldCache.contains( field ) ) return fieldCache[ field ]; STQLRETURN r(0); TQSTQLLEN 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 = STQLGetData( d->hStmt, current+1, STQL_C_DATE, (STQLPOINTER)&dbuf, (TQSTQLLEN)0, &lengthIndicator ); if ( ( r == STQL_SUCCESS || r == STQL_SUCCESS_WITH_INFO ) && ( lengthIndicator != STQL_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 = STQLGetData( d->hStmt, current+1, STQL_C_TIME, (STQLPOINTER)&tbuf, (TQSTQLLEN)0, &lengthIndicator ); if ( ( r == STQL_SUCCESS || r == STQL_SUCCESS_WITH_INFO ) && ( lengthIndicator != STQL_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 = STQLGetData( d->hStmt, current+1, STQL_C_TIMESTAMP, (STQLPOINTER)&dtbuf, (TQSTQLLEN)0, &lengthIndicator ); if ( ( r == STQL_SUCCESS || r == STQL_SUCCESS_WITH_INFO ) && ( lengthIndicator != STQL_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() == STQL_DECIMAL || info.typeID() == STQL_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() { TQSTQLLEN affectedRowCount(0); STQLRETURN r = STQLRowCount( d->hStmt, &affectedRowCount ); if ( r == STQL_SUCCESS ) return affectedRowCount; #ifdef TQT_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 ); STQLRETURN r; d->rInf.clear(); if ( d->hStmt ) { r = STQLFreeHandle( STQL_HANDLE_STMT, d->hStmt ); if ( r != STQL_SUCCESS ) { #ifdef TQT_CHECK_RANGE qSqlWarning( "TQODBCResult::prepare: Unable to close statement", d ); #endif return FALSE; } } r = STQLAllocHandle( STQL_HANDLE_STMT, d->hDbc, &d->hStmt ); if ( r != STQL_SUCCESS ) { #ifdef TQT_CHECK_RANGE qSqlWarning( "TQODBCResult::prepare: Unable to allocate statement handle", d ); #endif return FALSE; } if ( isForwardOnly() ) { r = STQLSetStmtAttr( d->hStmt, STQL_ATTR_CURSOR_TYPE, (STQLPOINTER)STQL_CURSOR_FORWARD_ONLY, STQL_IS_UINTEGER ); } else { r = STQLSetStmtAttr( d->hStmt, STQL_ATTR_CURSOR_TYPE, (STQLPOINTER)STQL_CURSOR_STATIC, STQL_IS_UINTEGER ); } if ( r != STQL_SUCCESS && r != STQL_SUCCESS_WITH_INFO ) { #ifdef TQT_CHECK_RANGE qSqlWarning( "TQODBCResult::prepare: Unable to set 'STQL_CURSOR_STATIC' as statement attribute. Please check your ODBC driver configuration", d ); #endif return FALSE; } #ifdef UNICODE r = STQLPrepare( d->hStmt, (STQLWCHAR*) query.tqunicode(), (STQLINTEGER) query.length() ); #else TQCString query8 = query.local8Bit(); r = STQLPrepare( d->hStmt, (STQLCHAR*) query8.data(), (STQLINTEGER) query8.length() ); #endif if ( r != STQL_SUCCESS ) { #ifdef TQT_CHECK_RANGE qSqlWarning( "TQODBCResult::prepare: Unable to prepare statement", d ); #endif return FALSE; } return TRUE; } bool TQODBCResult::exec() { STQLRETURN r; TQPtrList 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 TQT_CHECK_RANGE qSqlWarning( "TQODBCResult::exec: No statement handle available", d ); #endif return FALSE; } else { r = STQLFreeStmt( d->hStmt, STQL_CLOSE ); if ( r != STQL_SUCCESS ) { qSqlWarning( "TQODBCResult::exec: Unable to close statement handle", d ); return FALSE; } } // bind parameters - only positional binding allowed if ( extension()->index.count() > 0 ) { TQMap::Iterator it; int para = 1; TQVariant val; for ( it = extension()->index.begin(); it != extension()->index.end(); ++it ) { val = extension()->values[ it.data() ].value; TQSTQLLEN *ind = new TQSTQLLEN( STQL_NTS ); tmpStorage.append( qAutoDeleter(ind) ); if ( val.isNull() ) { *ind = STQL_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 = STQLBindParameter( d->hStmt, para, qParamType[ (int)extension()->values[ it.data() ].typ ], STQL_C_DATE, STQL_DATE, 0, 0, (void *) dt, (TQSTQLLEN)0, *ind == STQL_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 = STQLBindParameter( d->hStmt, para, qParamType[ (int)extension()->values[ it.data() ].typ ], STQL_C_TIME, STQL_TIME, 0, 0, (void *) dt, (TQSTQLLEN)0, *ind == STQL_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 = STQLBindParameter( d->hStmt, para, qParamType[ (int)extension()->values[ it.data() ].typ ], STQL_C_TIMESTAMP, STQL_TIMESTAMP, 0, 0, (void *) dt, (TQSTQLLEN)0, *ind == STQL_NULL_DATA ? ind : NULL ); break; } case TQVariant::Int: { int * v = new int( val.toInt() ); tmpStorage.append( qAutoDeleter(v) ); r = STQLBindParameter( d->hStmt, para, qParamType[ (int)extension()->values[ it.data() ].typ ], STQL_C_SLONG, STQL_INTEGER, 0, 0, (void *) v, (TQSTQLLEN)0, *ind == STQL_NULL_DATA ? ind : NULL ); break; } case TQVariant::Double: { double * v = new double( val.toDouble() ); tmpStorage.append( qAutoDeleter(v) ); r = STQLBindParameter( d->hStmt, para, qParamType[ (int)extension()->values[ it.data() ].typ ], STQL_C_DOUBLE, STQL_DOUBLE, 0, 0, (void *) v, (TQSTQLLEN)0, *ind == STQL_NULL_DATA ? ind : NULL ); break; } case TQVariant::ByteArray: { if ( *ind != STQL_NULL_DATA ) { *ind = val.asByteArray().size(); } r = STQLBindParameter( d->hStmt, para, qParamType[ (int)extension()->values[ it.data() ].typ ], STQL_C_BINARY, STQL_LONGVARBINARY, val.asByteArray().size(), 0, (void *) val.asByteArray().data(), (TQSTQLLEN)val.asByteArray().size(), ind ); break; } #ifndef TQ_ODBC_VERSION_2 case TQVariant::String: if ( d->tqunicode ) { TQString * str = new TQString( val.asString() ); str->ucs2(); int len = str->length()*2; tmpStorage.append( qAutoDeleter(str) ); r = STQLBindParameter( d->hStmt, para, qParamType[ (int)extension()->values[ it.data() ].typ ], STQL_C_WCHAR, len > 8000 ? STQL_WLONGVARCHAR : STQL_WVARCHAR, len > 8000 ? len : 0, 0, (void *) str->tqunicode(), (TQSTQLLEN) len, ind ); break; } #endif // fall through default: { TQCString * str = new TQCString( val.asString().local8Bit() ); tmpStorage.append( qAutoDeleter(str) ); r = STQLBindParameter( d->hStmt, para, qParamType[ (int)extension()->values[ it.data() ].typ ], STQL_C_CHAR, str->length() > 4000 ? STQL_LONGVARCHAR : STQL_VARCHAR, str->length() + 1, 0, (void *) str->data(), (TQSTQLLEN)(str->length() + 1), ind ); break; } } para++; if ( r != STQL_SUCCESS ) { #ifdef TQT_CHECK_RANGE qWarning( "TQODBCResult::exec: unable to bind variable: %s", qODBCWarn( d ).local8Bit().data() ); #endif setLastError( qMakeError( "Unable to bind variable", TQSqlError::Statement, d ) ); return FALSE; } } } r = STQLExecute( d->hStmt ); if ( r != STQL_SUCCESS && r != STQL_SUCCESS_WITH_INFO ) { #ifdef TQT_CHECK_RANGE qWarning( "TQODBCResult::exec: Unable to execute statement: %s", qODBCWarn( d ).local8Bit().data() ); #endif setLastError( qMakeError( "Unable to execute statement", TQSqlError::Statement, d ) ); return FALSE; } STQLSMALLINT count; r = STQLNumResultCols( 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::Iterator it; for ( it = extension()->index.begin(); it != extension()->index.end(); ++it ) { STQLINTEGER* indPtr = qAutoDeleterData( (TQAutoDeleter*)tmpStorage.getFirst() ); if ( !indPtr ) return FALSE; bool isNull = (*indPtr == STQL_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*)tmpStorage.getFirst() ); extension()->values[ it.data() ].value = TQVariant( TQDate( ds->year, ds->month, ds->day ) ); break; } case TQVariant::Time: { TIME_STRUCT * dt = qAutoDeleterData( (TQAutoDeleter*)tmpStorage.getFirst() ); extension()->values[ it.data() ].value = TQVariant( TQTime( dt->hour, dt->minute, dt->second ) ); break; } case TQVariant::DateTime: { TIMESTAMP_STRUCT * dt = qAutoDeleterData( (TQAutoDeleter*)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*)tmpStorage.getFirst() ); extension()->values[ it.data() ].value = TQVariant( *v ); break; } case TQVariant::Double: { double * v = qAutoDeleterData( (TQAutoDeleter*)tmpStorage.getFirst() ); extension()->values[ it.data() ].value = TQVariant( *v ); break; } case TQVariant::ByteArray: break; case TQVariant::String: if ( d->tqunicode ) { TQString * str = qAutoDeleterData( (TQAutoDeleter*)tmpStorage.getFirst() ); extension()->values[ it.data() ].value = TQVariant( *str ); break; } // fall through default: { TQCString * str = qAutoDeleterData( (TQAutoDeleter*)tmpStorage.getFirst() ); extension()->values[ it.data() ].value = TQVariant( *str ); break; } } if (type != TQVariant::ByteArray) tmpStorage.removeFirst(); } } return TRUE; } //////////////////////////////////////// TQODBCDriver::TQODBCDriver( TQObject * tqparent, const char * name ) : TQSqlDriver(tqparent,name ? name : "TQODBC") { init(); } TQODBCDriver::TQODBCDriver( STQLHANDLE env, STQLHANDLE con, TQObject * tqparent, const char * name ) : TQSqlDriver(tqparent,name ? name : "TQODBC") { init(); d->hEnv = env; d->hDbc = con; if ( env && con ) { setOpen( TRUE ); setOpenError( FALSE ); } } void TQODBCDriver::init() { qSqlOpenExtDict()->insert( this, new TQODBCOpenExtension(this) ); d = new TQODBCPrivate(); } TQODBCDriver::~TQODBCDriver() { cleanup(); delete d; if ( !qSqlOpenExtDict()->isEmpty() ) { TQSqlOpenExtension *ext = qSqlOpenExtDict()->take( this ); delete ext; } } bool TQODBCDriver::hasFeature( DriverFeature f ) const { switch ( f ) { case Transactions: { if ( !d->hDbc ) return FALSE; STQLUSMALLINT txn; STQLSMALLINT t; int r = STQLGetInfo( d->hDbc, (STQLUSMALLINT)STQL_TXN_CAPABLE, &txn, sizeof(txn), &t); if ( r != STQL_SUCCESS || txn == STQL_TC_NONE ) return FALSE; else return TRUE; } case QuerySize: return FALSE; case BLOB: return TRUE; case Unicode: return d->tqunicode; case PreparedQueries: return TRUE; case PositionalPlaceholders: return TRUE; default: return FALSE; } } bool TQODBCDriver::open( const TQString&, const TQString&, const TQString&, const TQString&, int ) { qWarning("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(); STQLRETURN r; r = STQLAllocHandle( STQL_HANDLE_ENV, STQL_NULL_HANDLE, &d->hEnv); if ( r != STQL_SUCCESS && r != STQL_SUCCESS_WITH_INFO ) { #ifdef TQT_CHECK_RANGE qSqlWarning( "TQODBCDriver::open: Unable to allocate environment", d ); #endif setOpenError( TRUE ); return FALSE; } r = STQLSetEnvAttr( d->hEnv, STQL_ATTR_ODBC_VERSION, (STQLPOINTER)STQL_OV_ODBC2, STQL_IS_UINTEGER ); r = STQLAllocHandle( STQL_HANDLE_DBC, d->hEnv, &d->hDbc); if ( r != STQL_SUCCESS && r != STQL_SUCCESS_WITH_INFO ) { #ifdef TQT_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; STQLSMALLINT cb; STQLTCHAR connOut[1024]; r = STQLDriverConnect( d->hDbc, NULL, #ifdef UNICODE (STQLWCHAR*)connTQStr.tqunicode(), #else (STQLCHAR*)connTQStr.latin1(), #endif (STQLSMALLINT)connTQStr.length(), connOut, 1024, &cb, STQL_DRIVER_NOPROMPT ); if ( r != STQL_SUCCESS && r != STQL_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 ); } void TQODBCDriver::cleanup() { STQLRETURN r; if ( !d ) return; if( d->hDbc ) { // Open statements/descriptors handles are automatically cleaned up by STQLDisconnect if ( isOpen() ) { r = STQLDisconnect( d->hDbc ); #ifdef TQT_CHECK_RANGE if ( r != STQL_SUCCESS ) qSqlWarning( "TQODBCDriver::disconnect: Unable to disconnect datasource", d ); #endif } r = STQLFreeHandle( STQL_HANDLE_DBC, d->hDbc ); #ifdef TQT_CHECK_RANGE if ( r != STQL_SUCCESS ) qSqlWarning( "TQODBCDriver::cleanup: Unable to free connection handle", d ); #endif d->hDbc = 0; } if ( d->hEnv ) { r = STQLFreeHandle( STQL_HANDLE_ENV, d->hEnv ); #ifdef TQT_CHECK_RANGE if ( r != STQL_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 tqunicode characters void TQODBCPrivate::checkUnicode() { #if defined(TQ_WS_WIN) if ( !qt_wintqunicode ) { tqunicode = FALSE; return; } #endif STQLRETURN r; STQLUINTEGER fFunc; tqunicode = FALSE; r = STQLGetInfo( hDbc, STQL_CONVERT_CHAR, (STQLPOINTER)&fFunc, sizeof(fFunc), NULL ); if ( ( r == STQL_SUCCESS || r == STQL_SUCCESS_WITH_INFO ) && ( fFunc & STQL_CVT_WCHAR ) ) { sql_char_type = TQVariant::String; tqunicode = TRUE; } r = STQLGetInfo( hDbc, STQL_CONVERT_VARCHAR, (STQLPOINTER)&fFunc, sizeof(fFunc), NULL ); if ( ( r == STQL_SUCCESS || r == STQL_SUCCESS_WITH_INFO ) && ( fFunc & STQL_CVT_WVARCHAR ) ) { sql_varchar_type = TQVariant::String; tqunicode = TRUE; } r = STQLGetInfo( hDbc, STQL_CONVERT_LONGVARCHAR, (STQLPOINTER)&fFunc, sizeof(fFunc), NULL ); if ( ( r == STQL_SUCCESS || r == STQL_SUCCESS_WITH_INFO ) && ( fFunc & STQL_CVT_WLONGVARCHAR ) ) { sql_longvarchar_type = TQVariant::String; tqunicode = TRUE; } } bool TQODBCPrivate::checkDriver() const { #ifdef ODBC_CHECK_DRIVER // do not query for STQL_API_STQLFETCHSCROLL because it can't be used at this time static const STQLUSMALLINT reqFunc[] = { STQL_API_STQLDESCRIBECOL, STQL_API_STQLGETDATA, STQL_API_STQLCOLUMNS, STQL_API_STQLGETSTMTATTR, STQL_API_STQLGETDIAGREC, STQL_API_STQLEXECDIRECT, STQL_API_STQLGETINFO, STQL_API_STQLTABLES, 0 }; // these functions are optional static const STQLUSMALLINT optFunc[] = { STQL_API_STQLNUMRESULTCOLS, STQL_API_STQLROWCOUNT, 0 }; STQLRETURN r; STQLUSMALLINT sup; int i; // check the required functions for ( i = 0; reqFunc[ i ] != 0; ++i ) { r = STQLGetFunctions( hDbc, reqFunc[ i ], &sup ); #ifdef TQT_CHECK_RANGE if ( r != STQL_SUCCESS ) { qSqlWarning( "TQODBCDriver::checkDriver: Cannot get list of supported functions", this ); return FALSE; } #endif if ( sup == STQL_FALSE ) { #ifdef TQT_CHECK_RANGE qWarning ( "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 = STQLGetFunctions( hDbc, optFunc[ i ], &sup ); #ifdef TQT_CHECK_RANGE if ( r != STQL_SUCCESS ) { qSqlWarning( "TQODBCDriver::checkDriver: Cannot get list of supported functions", this ); return FALSE; } #endif if ( sup == STQL_FALSE ) { #ifdef TQT_CHECK_RANGE qWarning( "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() { STQLRETURN r; STQLUINTEGER val; r = STQLGetInfo(hDbc, STQL_SCHEMA_USAGE, (STQLPOINTER) &val, sizeof(val), NULL); if (r == STQL_SUCCESS || r == STQL_SUCCESS_WITH_INFO) useSchema = (val != 0); } TQSqlQuery TQODBCDriver::createQuery() const { return TQSqlQuery( new TQODBCResult( this, d ) ); } bool TQODBCDriver::beginTransaction() { if ( !isOpen() ) { #ifdef TQT_CHECK_RANGE qWarning(" TQODBCDriver::beginTransaction: Database not open" ); #endif return FALSE; } STQLUINTEGER ac(STQL_AUTOCOMMIT_OFF); STQLRETURN r = STQLSetConnectAttr( d->hDbc, STQL_ATTR_AUTOCOMMIT, (STQLPOINTER)ac, sizeof(ac) ); if ( r != STQL_SUCCESS ) { setLastError( qMakeError( "Unable to disable autocommit", TQSqlError::Transaction, d ) ); return FALSE; } return TRUE; } bool TQODBCDriver::commitTransaction() { if ( !isOpen() ) { #ifdef TQT_CHECK_RANGE qWarning(" TQODBCDriver::commitTransaction: Database not open" ); #endif return FALSE; } STQLRETURN r = STQLEndTran( STQL_HANDLE_DBC, d->hDbc, STQL_COMMIT ); if ( r != STQL_SUCCESS ) { setLastError( qMakeError("Unable to commit transaction", TQSqlError::Transaction, d ) ); return FALSE; } return endTrans(); } bool TQODBCDriver::rollbackTransaction() { if ( !isOpen() ) { #ifdef TQT_CHECK_RANGE qWarning(" TQODBCDriver::rollbackTransaction: Database not open" ); #endif return FALSE; } STQLRETURN r = STQLEndTran( STQL_HANDLE_DBC, d->hDbc, STQL_ROLLBACK ); if ( r != STQL_SUCCESS ) { setLastError( qMakeError( "Unable to rollback transaction", TQSqlError::Transaction, d ) ); return FALSE; } return endTrans(); } bool TQODBCDriver::endTrans() { STQLUINTEGER ac(STQL_AUTOCOMMIT_ON); STQLRETURN r = STQLSetConnectAttr( d->hDbc, STQL_ATTR_AUTOCOMMIT, (STQLPOINTER)ac, sizeof(ac)); if ( r != STQL_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(); STQLHANDLE hStmt; STQLRETURN r = STQLAllocHandle( STQL_HANDLE_STMT, d->hDbc, &hStmt ); if ( r != STQL_SUCCESS ) { #ifdef TQT_CHECK_RANGE qSqlWarning( "TQODBCDriver::tables: Unable to allocate handle", d ); #endif return tl; } r = STQLSetStmtAttr( hStmt, STQL_ATTR_CURSOR_TYPE, (STQLPOINTER)STQL_CURSOR_FORWARD_ONLY, STQL_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 = STQLTables( hStmt, NULL, 0, NULL, 0, NULL, 0, #ifdef UNICODE (STQLWCHAR*)tableType.tqunicode(), #else (STQLCHAR*)tableType.latin1(), #endif tableType.length() /* characters, not bytes */ ); #ifdef TQT_CHECK_RANGE if ( r != STQL_SUCCESS ) qSqlWarning( "TQODBCDriver::tables Unable to execute table list", d ); #endif r = STQLFetchScroll( hStmt, STQL_FETCH_NEXT, 0); while ( r == STQL_SUCCESS ) { bool isNull; TQString fieldVal = qGetStringData( hStmt, 2, -1, isNull, d->tqunicode ); tl.append( fieldVal ); r = STQLFetchScroll( hStmt, STQL_FETCH_NEXT, 0); } r = STQLFreeHandle( STQL_HANDLE_STMT, hStmt ); if ( r!= STQL_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 ); STQLHANDLE hStmt; STQLRETURN r = STQLAllocHandle( STQL_HANDLE_STMT, d->hDbc, &hStmt ); if ( r != STQL_SUCCESS ) { #ifdef TQT_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 = STQLSetStmtAttr( hStmt, STQL_ATTR_CURSOR_TYPE, (STQLPOINTER)STQL_CURSOR_FORWARD_ONLY, STQL_IS_UINTEGER ); r = STQLPrimaryKeys( hStmt, #ifdef UNICODE catalog.length() == 0 ? NULL : (STQLWCHAR*)catalog.tqunicode(), #else catalog.length() == 0 ? NULL : (STQLCHAR*)catalog.latin1(), #endif catalog.length(), #ifdef UNICODE schema.length() == 0 ? NULL : (STQLWCHAR*)schema.tqunicode(), #else schema.length() == 0 ? NULL : (STQLCHAR*)schema.latin1(), #endif schema.length(), #ifdef UNICODE (STQLWCHAR*)table.tqunicode(), #else (STQLCHAR*)table.latin1(), #endif table.length() /* in characters, not in bytes */); // if the STQLPrimaryKeys() 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 != STQL_SUCCESS ) { r = STQLSpecialColumns( hStmt, STQL_BEST_ROWID, #ifdef UNICODE catalog.length() == 0 ? NULL : (STQLWCHAR*)catalog.tqunicode(), #else catalog.length() == 0 ? NULL : (STQLCHAR*)catalog.latin1(), #endif catalog.length(), #ifdef UNICODE schema.length() == 0 ? NULL : (STQLWCHAR*)schema.tqunicode(), #else schema.length() == 0 ? NULL : (STQLCHAR*)schema.latin1(), #endif schema.length(), #ifdef UNICODE (STQLWCHAR*)table.tqunicode(), #else (STQLCHAR*)table.latin1(), #endif table.length(), STQL_SCOPE_CURROW, STQL_NULLABLE ); if ( r != STQL_SUCCESS ) { #ifdef TQT_CHECK_RANGE qSqlWarning( "TQODBCDriver::primaryIndex: Unable to execute primary key list", d ); #endif } else { usingSpecialColumns = TRUE; } } r = STQLFetchScroll( hStmt, STQL_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 == STQL_SUCCESS ) { if ( usingSpecialColumns ) { cName = qGetStringData( hStmt, 1, -1, isNull, d->tqunicode ); // column name idxName = TQString::number( fakeId++ ); // invent a fake index name } else { cName = qGetStringData( hStmt, 3, -1, isNull, d->tqunicode ); // column name idxName = qGetStringData( hStmt, 5, -1, isNull, d->tqunicode ); // pk index name } TQSqlField *fld = rec.field(cName); if (fld) index.append(*fld); index.setName( idxName ); r = STQLFetchScroll( hStmt, STQL_FETCH_NEXT, 0 ); } r = STQLFreeHandle( STQL_HANDLE_STMT, hStmt ); if ( r!= STQL_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; STQLHANDLE hStmt; TQString catalog, schema, table; d->splitTableQualifier( tablename, catalog, schema, table ); STQLRETURN r = STQLAllocHandle( STQL_HANDLE_STMT, d->hDbc, &hStmt ); if ( r != STQL_SUCCESS ) { #ifdef TQT_CHECK_RANGE qSqlWarning( "TQODBCDriver::record: Unable to allocate handle", d ); #endif return fil; } r = STQLSetStmtAttr( hStmt, STQL_ATTR_CURSOR_TYPE, (STQLPOINTER)STQL_CURSOR_FORWARD_ONLY, STQL_IS_UINTEGER ); r = STQLColumns( hStmt, #ifdef UNICODE catalog.length() == 0 ? NULL : (STQLWCHAR*)catalog.tqunicode(), #else catalog.length() == 0 ? NULL : (STQLCHAR*)catalog.latin1(), #endif catalog.length(), #ifdef UNICODE schema.length() == 0 ? NULL : (STQLWCHAR*)schema.tqunicode(), #else schema.length() == 0 ? NULL : (STQLCHAR*)schema.latin1(), #endif schema.length(), #ifdef UNICODE (STQLWCHAR*)table.tqunicode(), #else (STQLCHAR*)table.latin1(), #endif table.length(), NULL, 0 ); #ifdef TQT_CHECK_RANGE if ( r != STQL_SUCCESS ) qSqlWarning( "TQODBCDriver::record: Unable to execute column list", d ); #endif r = STQLFetchScroll( hStmt, STQL_FETCH_NEXT, 0); // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop while ( r == STQL_SUCCESS ) { fil.append( qMakeFieldInfo( hStmt, d ) ); r = STQLFetchScroll( hStmt, STQL_FETCH_NEXT, 0); } r = STQLFreeHandle( STQL_HANDLE_STMT, hStmt ); if ( r!= STQL_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; } STQLHANDLE TQODBCDriver::environment() { return d->hEnv; } STQLHANDLE 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; }