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 }