File indexing completed on 2025-02-23 04:27:58

0001 /****************************************************************************************
0002  * Copyright (c) 2008 Edward Toroshchin <edward.hades@gmail.com>                        *
0003  * Copyright (c) 2009 Jeff Mitchell <mitchell@kde.org>                                  *
0004  *                                                                                      *
0005  * This program is free software; you can redistribute it and/or modify it under        *
0006  * the terms of the GNU General Public License as published by the Free Software        *
0007  * Foundation; either version 2 of the License, or (at your option) any later           *
0008  * version.                                                                             *
0009  *                                                                                      *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0013  *                                                                                      *
0014  * You should have received a copy of the GNU General Public License along with         *
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0016  ****************************************************************************************/
0017 
0018 #define DEBUG_PREFIX "MySqlStorage"
0019 
0020 #include "MySqlStorage.h"
0021 
0022 #include "core/support/Amarok.h"
0023 #include "core/support/Debug.h"
0024 #include "amarokconfig.h"
0025 
0026 #include <QMutexLocker>
0027 #include <QThreadStorage>
0028 #include <QVarLengthArray>
0029 
0030 
0031 #include <mysql.h>
0032 
0033 /**
0034  * This class is used by MySqlStorage to fulfill mysql's thread
0035  * requirements. In every function that calls mysql_*, an init() method of
0036  * this class must be invoked.
0037  */
0038 class ThreadInitializer
0039 {
0040     static int threadsCount;
0041     static QMutex countMutex;
0042     static QThreadStorage< ThreadInitializer* > storage;
0043 
0044     /**
0045      * This should be called ONLY by init()
0046      */
0047     ThreadInitializer()
0048     {
0049         mysql_thread_init();
0050 
0051         countMutex.lock();
0052         threadsCount++;
0053 
0054         debug() << "Initialized thread, count ==" << threadsCount;
0055 
0056         countMutex.unlock();
0057     }
0058 
0059 public:
0060     /**
0061      * This is called by QThreadStorage when a thread is destroyed
0062      */
0063     ~ThreadInitializer()
0064     {
0065         mysql_thread_end();
0066 
0067         countMutex.lock();
0068         threadsCount--;
0069 
0070         debug() << "Deinitialized thread, count ==" << threadsCount;
0071 
0072         if( threadsCount == 0 )
0073             mysql_library_end();
0074 
0075         countMutex.unlock();
0076     }
0077 
0078     static void init()
0079     {
0080         if( !storage.hasLocalData() )
0081             storage.setLocalData( new ThreadInitializer() );
0082     }
0083 };
0084 
0085 int ThreadInitializer::threadsCount = 0;
0086 QMutex ThreadInitializer::countMutex;
0087 QThreadStorage< ThreadInitializer* > ThreadInitializer::storage;
0088 
0089 
0090 MySqlStorage::MySqlStorage()
0091     : SqlStorage()
0092     , m_db( nullptr )
0093     , m_mutex( QMutex::Recursive )
0094     , m_debugIdent( "MySQL-none" )
0095 {
0096     //Relevant code must be implemented in subclasses
0097 }
0098 
0099 MySqlStorage::~MySqlStorage()
0100 { }
0101 
0102 QStringList MySqlStorage::query( const QString& statement )
0103 {
0104     //DEBUG_BLOCK
0105     //debug() << "[ATTN!] MySql::query( " << statement << " )";
0106 
0107     initThreadInitializer();
0108     QMutexLocker locker( &m_mutex );
0109 
0110     QStringList values;
0111     if( !m_db )
0112     {
0113         error() << "Tried to perform query on uninitialized MySQL";
0114         return values;
0115     }
0116 
0117     int res = mysql_query( m_db, statement.toUtf8() ); 
0118     
0119     if( res )
0120     {
0121         reportError( statement );
0122         return values;
0123     }
0124 
0125     MYSQL_RES *pres = mysql_store_result( m_db );
0126     if( !pres ) // No results... check if any were expected
0127     {
0128         if( mysql_field_count( m_db ) )
0129             reportError( statement );
0130         return values;
0131     }
0132     
0133     int number = mysql_num_fields( pres );
0134     if( number <= 0 )
0135     {
0136         warning() << "Errr... query returned but with no fields";
0137     }
0138 
0139     int rows = mysql_num_rows( pres );
0140     values.reserve( rows );
0141     MYSQL_ROW row = mysql_fetch_row( pres );
0142     while( row )
0143     {
0144         for( int i = 0; i < number; ++i )
0145         {
0146             values << QString::fromUtf8( (const char*) row[i] );
0147         }
0148 
0149         row = mysql_fetch_row( pres );
0150     }
0151 
0152     mysql_free_result( pres );
0153     return values;
0154 }
0155 
0156 int MySqlStorage::insert( const QString& statement, const QString& /* table */ )
0157 {
0158     //DEBUG_BLOCK
0159     //debug() << "[ATTN!] MySql::insert( " << statement << " )";
0160 
0161     initThreadInitializer();
0162     QMutexLocker locker( &m_mutex );
0163 
0164     if( !m_db )
0165     {
0166         error() << "Tried to perform insert on uninitialized MySQL";
0167         return 0;
0168     }
0169 
0170     int res = mysql_query( m_db, statement.toUtf8() ); 
0171     if( res )
0172     {
0173         reportError( statement );
0174         return 0;
0175     }
0176 
0177     MYSQL_RES *pres = mysql_store_result( m_db );
0178     if( pres )
0179     {
0180         warning() << "[IMPORTANT!] insert returned data";
0181         mysql_free_result( pres );
0182     }
0183 
0184     res = mysql_insert_id( m_db ); 
0185     
0186     return res;
0187 }
0188 
0189 QString
0190 MySqlStorage::escape( const QString &text ) const
0191 {
0192     if( !m_db )
0193     {
0194         error() << "Tried to perform escape() on uninitialized MySQL";
0195         return QString();
0196     }
0197 
0198     const QByteArray utfText = text.toUtf8();
0199     const int length = utfText.length() * 2 + 1;
0200     QVarLengthArray<char, 1000> outputBuffer( length );
0201 
0202     {
0203         QMutexLocker locker( &m_mutex );
0204         mysql_real_escape_string( m_db, outputBuffer.data(), utfText.constData(), utfText.length() );
0205     }
0206 
0207     return QString::fromUtf8( outputBuffer.constData() );
0208 }
0209 
0210 QString
0211 MySqlStorage::randomFunc() const
0212 {
0213     return "RAND()";
0214 }
0215 
0216 QString
0217 MySqlStorage::boolTrue() const
0218 {
0219     return "1";
0220 }
0221 
0222 QString
0223 MySqlStorage::boolFalse() const
0224 {
0225     return "0";
0226 }
0227 
0228 QString
0229 MySqlStorage::idType() const
0230 {
0231     return "INTEGER PRIMARY KEY AUTO_INCREMENT";
0232 }
0233 
0234 QString
0235 MySqlStorage::textColumnType( int length ) const
0236 {
0237     return QStringLiteral( "VARCHAR(%1)" ).arg( length );
0238 }
0239 
0240 QString
0241 MySqlStorage::exactTextColumnType( int length ) const
0242 {
0243     return textColumnType( length );
0244 }
0245 
0246 QString
0247 MySqlStorage::exactIndexableTextColumnType( int length ) const
0248 {
0249     return textColumnType( length );
0250 }
0251 
0252 QString
0253 MySqlStorage::longTextColumnType() const
0254 {
0255     return "TEXT";
0256 }
0257 
0258 QStringList
0259 MySqlStorage::getLastErrors() const
0260 {
0261     QMutexLocker locker( &m_mutex );
0262     return m_lastErrors;
0263 }
0264 
0265 void
0266 MySqlStorage::clearLastErrors()
0267 {
0268     QMutexLocker locker( &m_mutex );
0269     m_lastErrors.clear();
0270 }
0271 
0272 void
0273 MySqlStorage::reportError( const QString& message )
0274 {
0275     QMutexLocker locker( &m_mutex );
0276     QString errorMessage;
0277     if( m_db )
0278         errorMessage = m_debugIdent + " query failed! (" + QString::number( mysql_errno( m_db ) ) + ") " + mysql_error( m_db ) + " on " + message;
0279     else
0280         errorMessage = m_debugIdent + " something failed! on " + message;
0281     error() << errorMessage;
0282 
0283     if( m_lastErrors.count() < 20 )
0284         m_lastErrors.append( errorMessage );
0285 }
0286 
0287 
0288 void
0289 MySqlStorage::initThreadInitializer()
0290 {
0291     ThreadInitializer::init();
0292 }
0293 
0294 bool
0295 MySqlStorage::sharedInit( const QString &databaseName )
0296 {
0297     QMutexLocker locker( &m_mutex );
0298     if( mysql_query( m_db, QString( "SET NAMES 'utf8'" ).toUtf8() ) )
0299         reportError( "SET NAMES 'utf8' died" );
0300     if( mysql_query( m_db, QString( "CREATE DATABASE IF NOT EXISTS %1 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_bin" ).arg( databaseName ).toUtf8() ) )
0301         reportError( QString( "Could not create %1 database" ).arg( databaseName ) );
0302     if( mysql_query( m_db, QString( "ALTER DATABASE %1 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_bin" ).arg( databaseName ).toUtf8() ) )
0303         reportError( "Could not alter database charset/collation" );
0304     if( mysql_query( m_db, QString( "USE %1" ).arg( databaseName ).toUtf8() ) )
0305     {
0306         reportError( "Could not select database" );
0307         return false; // this error is fatal
0308     }
0309 
0310     debug() << "Connected to MySQL server" << mysql_get_server_info( m_db );
0311     return true;
0312 }