/**************************************************************************** ** ** Implementation of MYSQL 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_mysql.h" #include #include #include #include #define TQMYSQL_DRIVER_NAME "TQMYSQL3" #ifdef Q_OS_WIN32 // comment the next line out if you want to use MySQL/embedded on Win32 systems. // note that it will crash if you don't statically link to the mysql/e library! # define Q_NO_MYSQL_EMBEDDED #endif TQPtrDict *tqSqlOpenExtDict(); static int qMySqlConnectionCount = 0; static bool qMySqlInitHandledByUser = FALSE; class TQMYSQLOpenExtension : public TQSqlOpenExtension { public: TQMYSQLOpenExtension( TQMYSQLDriver *dri ) : TQSqlOpenExtension(), driver(dri) {} ~TQMYSQLOpenExtension() {} bool open( const TQString& db, const TQString& user, const TQString& password, const TQString& host, int port, const TQString& connOpts ); private: TQMYSQLDriver *driver; }; bool TQMYSQLOpenExtension::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 ); } class TQMYSQLDriverPrivate { public: TQMYSQLDriverPrivate() : mysql(0) {} MYSQL* mysql; }; class TQMYSQLResultPrivate : public TQMYSQLDriverPrivate { public: TQMYSQLResultPrivate() : TQMYSQLDriverPrivate(), result(0) {} MYSQL_RES* result; MYSQL_ROW row; TQValueVector fieldTypes; }; TQSqlError qMakeError( const TQString& err, int type, const TQMYSQLDriverPrivate* p ) { return TQSqlError(TQMYSQL_DRIVER_NAME ": " + err, TQString(mysql_error( p->mysql )), type, mysql_errno( p->mysql )); } TQVariant::Type qDecodeMYSQLType( int mysqltype, uint flags ) { TQVariant::Type type; switch ( mysqltype ) { case FIELD_TYPE_TINY : case FIELD_TYPE_SHORT : case FIELD_TYPE_LONG : case FIELD_TYPE_INT24 : type = (flags & UNSIGNED_FLAG) ? TQVariant::UInt : TQVariant::Int; break; case FIELD_TYPE_YEAR : type = TQVariant::Int; break; case FIELD_TYPE_LONGLONG : type = (flags & UNSIGNED_FLAG) ? TQVariant::ULongLong : TQVariant::LongLong; break; case FIELD_TYPE_DECIMAL : case FIELD_TYPE_FLOAT : case FIELD_TYPE_DOUBLE : type = TQVariant::Double; break; case FIELD_TYPE_DATE : type = TQVariant::Date; break; case FIELD_TYPE_TIME : type = TQVariant::Time; break; case FIELD_TYPE_DATETIME : case FIELD_TYPE_TIMESTAMP : type = TQVariant::DateTime; break; case FIELD_TYPE_BLOB : case FIELD_TYPE_TINY_BLOB : case FIELD_TYPE_MEDIUM_BLOB : case FIELD_TYPE_LONG_BLOB : type = (flags & BINARY_FLAG) ? TQVariant::ByteArray : TQVariant::CString; break; default: case FIELD_TYPE_ENUM : case FIELD_TYPE_SET : case FIELD_TYPE_STRING : case FIELD_TYPE_VAR_STRING : type = TQVariant::String; break; } return type; } TQMYSQLResult::TQMYSQLResult( const TQMYSQLDriver* db ) : TQSqlResult( db ) { d = new TQMYSQLResultPrivate(); d->mysql = db->d->mysql; } TQMYSQLResult::~TQMYSQLResult() { cleanup(); delete d; } MYSQL_RES* TQMYSQLResult::result() { return d->result; } void TQMYSQLResult::cleanup() { if ( d->result ) { mysql_free_result( d->result ); } d->result = NULL; d->row = NULL; setAt( -1 ); setActive( FALSE ); } bool TQMYSQLResult::fetch( int i ) { if ( isForwardOnly() ) { // fake a forward seek if ( at() < i ) { int x = i - at(); while ( --x && fetchNext() ); return fetchNext(); } else { return FALSE; } } if ( at() == i ) return TRUE; mysql_data_seek( d->result, i ); d->row = mysql_fetch_row( d->result ); if ( !d->row ) return FALSE; setAt( i ); return TRUE; } bool TQMYSQLResult::fetchNext() { d->row = mysql_fetch_row( d->result ); if ( !d->row ) return FALSE; setAt( at() + 1 ); return TRUE; } bool TQMYSQLResult::fetchLast() { if ( isForwardOnly() ) { // fake this since MySQL can't seek on forward only queries bool success = fetchNext(); // did we move at all? while ( fetchNext() ); return success; } my_ulonglong numRows = mysql_num_rows( d->result ); if ( !numRows ) return FALSE; return fetch( numRows - 1 ); } bool TQMYSQLResult::fetchFirst() { if ( isForwardOnly() ) // again, fake it return fetchNext(); return fetch( 0 ); } TQVariant TQMYSQLResult::data( int field ) { if ( !isSelect() || field >= (int) d->fieldTypes.count() ) { tqWarning( "TQMYSQLResult::data: column %d out of range", field ); return TQVariant(); } TQString val( d->row[field] ); switch ( d->fieldTypes.at( field ) ) { case TQVariant::LongLong: return TQVariant( val.toLongLong() ); case TQVariant::ULongLong: return TQVariant( val.toULongLong() ); case TQVariant::Int: return TQVariant( val.toInt() ); case TQVariant::UInt: return TQVariant( val.toUInt() ); case TQVariant::Double: return TQVariant( val.toDouble() ); case TQVariant::Date: if ( val.isEmpty() ) { return TQVariant( TQDate() ); } else { return TQVariant( TQDate::fromString( val, TQt::ISODate ) ); } case TQVariant::Time: if ( val.isEmpty() ) { return TQVariant( TQTime() ); } else { return TQVariant( TQTime::fromString( val, TQt::ISODate ) ); } case TQVariant::DateTime: if ( val.isEmpty() ) return TQVariant( TQDateTime() ); if ( val.length() == 14u ) // TIMESTAMPS have the format yyyyMMddhhmmss val.insert(4, "-").insert(7, "-").insert(10, 'T').insert(13, ':').insert(16, ':'); return TQVariant( TQDateTime::fromString( val, TQt::ISODate ) ); case TQVariant::ByteArray: { unsigned long* fl = mysql_fetch_lengths( d->result ); TQByteArray ba; ba.duplicate( d->row[field], fl[field] ); return TQVariant( ba ); } default: case TQVariant::String: case TQVariant::CString: return TQVariant( val ); } #ifdef QT_CHECK_RANGE tqWarning("TQMYSQLResult::data: unknown data type"); #endif return TQVariant(); } bool TQMYSQLResult::isNull( int field ) { if ( d->row[field] == NULL ) return TRUE; return FALSE; } bool TQMYSQLResult::reset ( const TQString& query ) { if ( !driver() ) return FALSE; if ( !driver()-> isOpen() || driver()->isOpenError() ) return FALSE; cleanup(); const char *encQuery = query.ascii(); if ( mysql_real_query( d->mysql, encQuery, tqstrlen(encQuery) ) ) { setLastError( qMakeError("Unable to execute query", TQSqlError::Statement, d ) ); return FALSE; } if ( isForwardOnly() ) { if ( isActive() || isValid() ) // have to empty the results from previous query fetchLast(); d->result = mysql_use_result( d->mysql ); } else { d->result = mysql_store_result( d->mysql ); } if ( !d->result && mysql_field_count( d->mysql ) > 0 ) { setLastError( qMakeError( "Unable to store result", TQSqlError::Statement, d ) ); return FALSE; } int numFields = mysql_field_count( d->mysql ); setSelect( !( numFields == 0) ); d->fieldTypes.resize( numFields ); if ( isSelect() ) { for( int i = 0; i < numFields; i++) { MYSQL_FIELD* field = mysql_fetch_field_direct( d->result, i ); if ( field->type == FIELD_TYPE_DECIMAL ) d->fieldTypes[i] = TQVariant::String; else d->fieldTypes[i] = qDecodeMYSQLType( field->type, field->flags ); } } setActive( TRUE ); return TRUE; } int TQMYSQLResult::size() { return isSelect() ? (int)mysql_num_rows( d->result ) : -1; } int TQMYSQLResult::numRowsAffected() { return (int)mysql_affected_rows( d->mysql ); } ///////////////////////////////////////////////////////// static void qServerEnd() { #ifndef Q_NO_MYSQL_EMBEDDED # if MYSQL_VERSION_ID >= 40000 mysql_server_end(); # endif // MYSQL_VERSION_ID #endif // Q_NO_MYSQL_EMBEDDED } static void qServerInit() { #ifndef Q_NO_MYSQL_EMBEDDED # if MYSQL_VERSION_ID >= 40000 if ( qMySqlInitHandledByUser || qMySqlConnectionCount > 1 ) return; // this should only be called once // has no effect on client/server library // but is vital for the embedded lib if ( mysql_server_init( 0, 0, 0 ) ) { # ifdef QT_CHECK_RANGE tqWarning( "TQMYSQLDriver::qServerInit: unable to start server." ); # endif } # endif // MYSQL_VERSION_ID #endif // Q_NO_MYSQL_EMBEDDED } TQMYSQLDriver::TQMYSQLDriver( TQObject * parent, const char * name ) : TQSqlDriver( parent, name ? name : TQMYSQL_DRIVER_NAME ) { init(); qServerInit(); } /*! Create a driver instance with an already open connection handle. */ TQMYSQLDriver::TQMYSQLDriver( MYSQL * con, TQObject * parent, const char * name ) : TQSqlDriver( parent, name ? name : TQMYSQL_DRIVER_NAME ) { init(); if ( con ) { d->mysql = (MYSQL *) con; setOpen( TRUE ); setOpenError( FALSE ); if (qMySqlConnectionCount == 1) qMySqlInitHandledByUser = TRUE; } else { qServerInit(); } } void TQMYSQLDriver::init() { tqSqlOpenExtDict()->insert( this, new TQMYSQLOpenExtension(this) ); d = new TQMYSQLDriverPrivate(); d->mysql = 0; qMySqlConnectionCount++; } TQMYSQLDriver::~TQMYSQLDriver() { qMySqlConnectionCount--; if (qMySqlConnectionCount == 0 && !qMySqlInitHandledByUser) qServerEnd(); delete d; if ( !tqSqlOpenExtDict()->isEmpty() ) { TQSqlOpenExtension *ext = tqSqlOpenExtDict()->take( this ); delete ext; } } bool TQMYSQLDriver::hasFeature( DriverFeature f ) const { switch ( f ) { case Transactions: // CLIENT_TRANSACTION should be defined in all recent mysql client libs > 3.23.34 #ifdef CLIENT_TRANSACTIONS if ( d->mysql ) { if ( ( d->mysql->server_capabilities & CLIENT_TRANSACTIONS ) == CLIENT_TRANSACTIONS ) return TRUE; } #endif return FALSE; case QuerySize: return TRUE; case BLOB: return TRUE; case Unicode: return FALSE; default: return FALSE; } } bool TQMYSQLDriver::open( const TQString&, const TQString&, const TQString&, const TQString&, int ) { tqWarning("TQMYSQLDriver::open(): This version of open() is no longer supported." ); return FALSE; } bool TQMYSQLDriver::open( const TQString& db, const TQString& user, const TQString& password, const TQString& host, int port, const TQString& connOpts ) { if ( isOpen() ) close(); unsigned int clientOptionFlags = 0; TQStringList raw = TQStringList::split( ';', connOpts ); TQStringList opts; TQStringList::ConstIterator it; // extract the real options from the string for ( it = raw.begin(); it != raw.end(); ++it ) { TQString tmp( *it ); int idx; if ( (idx = tmp.find( '=' )) != -1 ) { TQString val( tmp.mid( idx + 1 ) ); val.simplifyWhiteSpace(); if ( val == "TRUE" || val == "1" ) opts << tmp.left( idx ); else tqWarning( "TQMYSQLDriver::open: Illegal connect option value '%s'", tmp.latin1() ); } else { opts << tmp; } } if (!(d->mysql = mysql_init((MYSQL*) 0))) { setLastError( qMakeError( "Unable to connect", TQSqlError::Connection, d ) ); mysql_close( d->mysql ); setOpenError( TRUE ); return FALSE; } my_bool reconnect = 0; for ( it = opts.begin(); it != opts.end(); ++it ) { TQString opt( (*it).upper() ); if ( opt == "CLIENT_COMPRESS" ) clientOptionFlags |= CLIENT_COMPRESS; else if ( opt == "CLIENT_FOUND_ROWS" ) clientOptionFlags |= CLIENT_FOUND_ROWS; else if ( opt == "CLIENT_IGNORE_SPACE" ) clientOptionFlags |= CLIENT_IGNORE_SPACE; else if ( opt == "CLIENT_INTERACTIVE" ) clientOptionFlags |= CLIENT_INTERACTIVE; else if ( opt == "CLIENT_NO_SCHEMA" ) clientOptionFlags |= CLIENT_NO_SCHEMA; else if ( opt == "CLIENT_ODBC" ) clientOptionFlags |= CLIENT_ODBC; else if ( opt == "CLIENT_SSL" ) clientOptionFlags |= CLIENT_SSL; else if ( opt == "MYSQL_OPT_RECONNECT" ) reconnect = 1; else tqWarning( "TQMYSQLDriver::open: Unknown connect option '%s'", (*it).latin1() ); } mysql_options(d->mysql, MYSQL_OPT_RECONNECT, &reconnect); if ( mysql_real_connect( d->mysql, host, user, password, db.isNull() ? TQString("") : db, (port > -1) ? port : 0, NULL, clientOptionFlags ) ) { if ( !db.isEmpty() && mysql_select_db( d->mysql, db )) { setLastError( qMakeError("Unable open database '" + db + "'", TQSqlError::Connection, d ) ); mysql_close( d->mysql ); setOpenError( TRUE ); return FALSE; } } else { setLastError( qMakeError( "Unable to connect", TQSqlError::Connection, d ) ); mysql_close( d->mysql ); setOpenError( TRUE ); return FALSE; } setOpen( TRUE ); setOpenError( FALSE ); return TRUE; } void TQMYSQLDriver::close() { if ( isOpen() ) { mysql_close( d->mysql ); setOpen( FALSE ); setOpenError( FALSE ); } } bool TQMYSQLDriver::ping() { if ( !isOpen() ) { return FALSE; } if (mysql_ping( d->mysql )) { return TRUE; } else { setLastError( qMakeError("Unable to execute ping", TQSqlError::Statement, d ) ); return FALSE; } } TQSqlQuery TQMYSQLDriver::createQuery() const { return TQSqlQuery( new TQMYSQLResult( this ) ); } TQStringList TQMYSQLDriver::tables( const TQString& typeName ) const { TQStringList tl; if ( !isOpen() ) return tl; if ( !typeName.isEmpty() && !(typeName.toInt() & (int)TQSql::Tables) ) return tl; MYSQL_RES* tableRes = mysql_list_tables( d->mysql, NULL ); MYSQL_ROW row; int i = 0; while ( tableRes && TRUE ) { mysql_data_seek( tableRes, i ); row = mysql_fetch_row( tableRes ); if ( !row ) break; tl.append( TQString(row[0]) ); i++; } mysql_free_result( tableRes ); return tl; } TQSqlIndex TQMYSQLDriver::primaryIndex( const TQString& tablename ) const { TQSqlIndex idx; if ( !isOpen() ) return idx; TQSqlQuery i = createQuery(); TQString stmt( "show index from %1;" ); TQSqlRecord fil = record( tablename ); i.exec( stmt.arg( tablename ) ); while ( i.isActive() && i.next() ) { if ( i.value(2).toString() == "PRIMARY" ) { idx.append( *fil.field( i.value(4).toString() ) ); idx.setCursorName( i.value(0).toString() ); idx.setName( i.value(2).toString() ); } } return idx; } TQSqlRecord TQMYSQLDriver::record( const TQString& tablename ) const { TQSqlRecord fil; if ( !isOpen() ) return fil; MYSQL_RES* r = mysql_list_fields( d->mysql, tablename.local8Bit().data(), 0); if ( !r ) { return fil; } MYSQL_FIELD* field; while ( (field = mysql_fetch_field( r ))) { TQSqlField f ( TQString( field->name ) , qDecodeMYSQLType( (int)field->type, field->flags ) ); fil.append ( f ); } mysql_free_result( r ); return fil; } TQSqlRecord TQMYSQLDriver::record( const TQSqlQuery& query ) const { TQSqlRecord fil; if ( !isOpen() ) return fil; if ( query.isActive() && query.isSelect() && query.driver() == this ) { TQMYSQLResult* result = (TQMYSQLResult*)query.result(); TQMYSQLResultPrivate* p = result->d; if ( !mysql_errno( p->mysql ) ) { for ( ;; ) { MYSQL_FIELD* f = mysql_fetch_field( p->result ); if ( f ) { TQSqlField fi( TQString((const char*)f->name), qDecodeMYSQLType( f->type, f->flags ) ); fil.append( fi ); } else break; } } mysql_field_seek( p->result, 0 ); } return fil; } TQSqlRecordInfo TQMYSQLDriver::recordInfo( const TQString& tablename ) const { TQSqlRecordInfo info; if ( !isOpen() ) return info; MYSQL_RES* r = mysql_list_fields( d->mysql, tablename.local8Bit().data(), 0); if ( !r ) { return info; } MYSQL_FIELD* field; while ( (field = mysql_fetch_field( r ))) { info.append ( TQSqlFieldInfo( TQString( field->name ), qDecodeMYSQLType( (int)field->type, field->flags ), IS_NOT_NULL( field->flags ), (int)field->length, (int)field->decimals, TQString( field->def ), (int)field->type ) ); } mysql_free_result( r ); return info; } TQSqlRecordInfo TQMYSQLDriver::recordInfo( const TQSqlQuery& query ) const { TQSqlRecordInfo info; if ( !isOpen() ) return info; if ( query.isActive() && query.isSelect() && query.driver() == this ) { TQMYSQLResult* result = (TQMYSQLResult*)query.result(); TQMYSQLResultPrivate* p = result->d; if ( !mysql_errno( p->mysql ) ) { for ( ;; ) { MYSQL_FIELD* field = mysql_fetch_field( p->result ); if ( field ) { info.append ( TQSqlFieldInfo( TQString( field->name ), qDecodeMYSQLType( (int)field->type, field->flags ), IS_NOT_NULL( field->flags ), (int)field->length, (int)field->decimals, TQVariant(), (int)field->type ) ); } else break; } } mysql_field_seek( p->result, 0 ); } return info; } MYSQL* TQMYSQLDriver::mysql() { return d->mysql; } bool TQMYSQLDriver::beginTransaction() { #ifndef CLIENT_TRANSACTIONS return FALSE; #endif if ( !isOpen() ) { #ifdef QT_CHECK_RANGE tqWarning( "TQMYSQLDriver::beginTransaction: Database not open" ); #endif return FALSE; } if ( mysql_query( d->mysql, "BEGIN WORK" ) ) { setLastError( qMakeError("Unable to begin transaction", TQSqlError::Statement, d ) ); return FALSE; } return TRUE; } bool TQMYSQLDriver::commitTransaction() { #ifndef CLIENT_TRANSACTIONS return FALSE; #endif if ( !isOpen() ) { #ifdef QT_CHECK_RANGE tqWarning( "TQMYSQLDriver::commitTransaction: Database not open" ); #endif return FALSE; } if ( mysql_query( d->mysql, "COMMIT" ) ) { setLastError( qMakeError("Unable to commit transaction", TQSqlError::Statement, d ) ); return FALSE; } return TRUE; } bool TQMYSQLDriver::rollbackTransaction() { #ifndef CLIENT_TRANSACTIONS return FALSE; #endif if ( !isOpen() ) { #ifdef QT_CHECK_RANGE tqWarning( "TQMYSQLDriver::rollbackTransaction: Database not open" ); #endif return FALSE; } if ( mysql_query( d->mysql, "ROLLBACK" ) ) { setLastError( qMakeError("Unable to rollback transaction", TQSqlError::Statement, d ) ); return FALSE; } return TRUE; } TQString TQMYSQLDriver::formatValue( const TQSqlField* field, bool trimStrings ) const { TQString r; if ( field->isNull() ) { r = nullText(); } else { switch( field->type() ) { case TQVariant::ByteArray: { const TQByteArray ba = field->value().toByteArray(); // buffer has to be at least length*2+1 bytes char* buffer = new char[ ba.size() * 2 + 1 ]; /*uint escapedSize =*/ mysql_escape_string( buffer, ba.data(), ba.size() ); r.append("'").append(buffer).append("'"); delete[] buffer; } break; case TQVariant::String: case TQVariant::CString: { // Escape '\' characters r = TQSqlDriver::formatValue( field ); r.replace( "\\", "\\\\" ); break; } default: r = TQSqlDriver::formatValue( field, trimStrings ); } } return r; }