File indexing completed on 2024-05-05 04:48:33
0001 /**************************************************************************************** 0002 * Copyright (c) 2013 Konrad Zemek <konrad.zemek@gmail.com> * 0003 * * 0004 * This program is free software; you can redistribute it and/or modify it under * 0005 * the terms of the GNU General Public License as published by the Free Software * 0006 * Foundation; either version 2 of the License, or (at your option) any later * 0007 * version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0010 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0011 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0012 * * 0013 * You should have received a copy of the GNU General Public License along with * 0014 * this program. If not, see <http://www.gnu.org/licenses/>. * 0015 ****************************************************************************************/ 0016 0017 #include "ImporterSqlConnection.h" 0018 0019 #include "core/support/Debug.h" 0020 0021 #include <ThreadWeaver/Thread> 0022 0023 #include <QMutexLocker> 0024 #include <QSqlDriver> 0025 #include <QSqlError> 0026 #include <QSqlQuery> 0027 #include <QSqlRecord> 0028 #include <QUuid> 0029 0030 using namespace StatSyncing; 0031 0032 ImporterSqlConnection::ImporterSqlConnection( const QString &driver, 0033 const QString &hostname, 0034 const quint16 port, const QString &dbName, 0035 const QString &user, 0036 const QString &password ) 0037 : m_connectionName( QUuid::createUuid().toString() ) 0038 , m_apiMutex( QMutex::Recursive ) 0039 , m_openTransaction( false ) 0040 { 0041 QSqlDatabase db = QSqlDatabase::addDatabase( driver, m_connectionName ); 0042 db.setHostName( hostname ); 0043 db.setPort( port ); 0044 db.setDatabaseName( dbName ); 0045 db.setUserName( user ); 0046 db.setPassword( password ); 0047 } 0048 0049 ImporterSqlConnection::ImporterSqlConnection( const QString &dbPath ) 0050 : m_connectionName( QUuid::createUuid().toString() ) 0051 , m_apiMutex( QMutex::Recursive ) 0052 , m_openTransaction( false ) 0053 { 0054 QSqlDatabase db = QSqlDatabase::addDatabase( QStringLiteral("QSQLITE"), m_connectionName ); 0055 db.setDatabaseName( dbPath ); 0056 } 0057 0058 ImporterSqlConnection::ImporterSqlConnection() 0059 : m_connectionName( QUuid::createUuid().toString() ) 0060 , m_apiMutex( QMutex::Recursive ) 0061 , m_openTransaction( false ) 0062 { 0063 } 0064 0065 ImporterSqlConnection::~ImporterSqlConnection() 0066 { 0067 if( isTransaction() ) 0068 { 0069 QSqlDatabase db = connection(); 0070 if( db.isOpen() ) 0071 { 0072 warning() << __PRETTY_FUNCTION__ << "Rolling back unfinished transaction for" 0073 << "database" << db.databaseName() << "(" << db.hostName() << ":" 0074 << db.port() << ")"; 0075 0076 db.rollback(); 0077 } 0078 } 0079 0080 QSqlDatabase::removeDatabase( m_connectionName ); 0081 } 0082 0083 QSqlDatabase 0084 ImporterSqlConnection::connection() 0085 { 0086 Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() ); 0087 return QSqlDatabase::database( m_connectionName ); 0088 } 0089 0090 bool 0091 ImporterSqlConnection::isTransaction() const 0092 { 0093 return m_openTransaction; 0094 } 0095 0096 QList<QVariantList> 0097 ImporterSqlConnection::query( const QString &query, const QVariantMap &bindValues, 0098 bool* const ok ) 0099 { 0100 QMutexLocker lock( &m_apiMutex ); 0101 0102 QMetaObject::invokeMethod( this, "slotQuery", blockingConnectionType(), 0103 Q_ARG( QString, query ), Q_ARG( QVariantMap, bindValues ), 0104 Q_ARG( bool* const, ok ) ); 0105 0106 QList<QVariantList> result; 0107 result.swap( m_result ); 0108 return result; 0109 } 0110 0111 void 0112 ImporterSqlConnection::transaction() 0113 { 0114 QMutexLocker lock( &m_apiMutex ); 0115 if( isTransaction() ) 0116 return; 0117 0118 QMetaObject::invokeMethod( this, "slotTransaction", blockingConnectionType() ); 0119 if( isTransaction() ) // keep a lock for the duration of transaction 0120 m_apiMutex.lock(); 0121 } 0122 0123 void 0124 ImporterSqlConnection::rollback() 0125 { 0126 QMutexLocker lock( &m_apiMutex ); 0127 if( !isTransaction() ) 0128 return; 0129 0130 QMetaObject::invokeMethod( this, "slotRollback", blockingConnectionType() ); 0131 m_apiMutex.unlock(); // unlock second lock after releasing transaction 0132 } 0133 0134 void 0135 ImporterSqlConnection::commit() 0136 { 0137 QMutexLocker lock( &m_apiMutex ); 0138 if( !isTransaction() ) 0139 return; 0140 0141 QMetaObject::invokeMethod( this, "slotCommit", blockingConnectionType() ); 0142 m_apiMutex.unlock(); // unlock second lock after releasing transaction 0143 } 0144 0145 inline Qt::ConnectionType 0146 ImporterSqlConnection::blockingConnectionType() const 0147 { 0148 return this->thread() == ThreadWeaver::Thread::currentThread() 0149 ? Qt::DirectConnection : Qt::BlockingQueuedConnection; 0150 } 0151 0152 void 0153 ImporterSqlConnection::slotQuery( const QString &query, const QVariantMap &bindValues, 0154 bool* const ok ) 0155 { 0156 Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() ); 0157 0158 if( ok != nullptr ) 0159 *ok = false; 0160 0161 QSqlDatabase db = connection(); 0162 if( !db.isOpen() ) 0163 return; 0164 0165 QSqlQuery q( db ); 0166 q.setForwardOnly( true ); 0167 q.prepare( query ); 0168 for( QVariantMap::ConstIterator bindValue = bindValues.constBegin(); 0169 bindValue != bindValues.constEnd(); ++bindValue ) 0170 q.bindValue( bindValue.key(), bindValue.value() ); 0171 0172 if( q.exec() ) 0173 { 0174 if( ok != nullptr ) 0175 *ok = true; 0176 0177 m_result.reserve( q.size() ); 0178 while( q.next() ) 0179 { 0180 const int fields = q.record().count(); 0181 0182 QVariantList row; 0183 row.reserve( fields ); 0184 for( int field = 0; field < fields; ++field ) 0185 row.append( q.value( field ) ); 0186 0187 m_result.append( row ); 0188 } 0189 } 0190 else 0191 warning() << __PRETTY_FUNCTION__ << q.lastError().text(); 0192 0193 // This is a stupid QSqlDatabase connection manager; we don't want to leave connection 0194 // open unless we're inside a transaction. 0195 if( !isTransaction() ) 0196 db.close(); 0197 } 0198 0199 void 0200 ImporterSqlConnection::slotTransaction() 0201 { 0202 Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() ); 0203 0204 if( isTransaction() ) 0205 return; 0206 0207 QSqlDatabase db = connection(); 0208 if( db.isOpen() ) 0209 { 0210 if( db.driver()->hasFeature( QSqlDriver::Transactions ) && db.transaction() ) 0211 m_openTransaction = true; 0212 else 0213 db.close(); 0214 } 0215 } 0216 0217 void 0218 ImporterSqlConnection::slotRollback() 0219 { 0220 Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() ); 0221 0222 if( !isTransaction() ) 0223 return; 0224 0225 QSqlDatabase db = connection(); 0226 if( db.isOpen() ) 0227 { 0228 db.rollback(); 0229 db.close(); 0230 } 0231 0232 m_openTransaction = false; 0233 } 0234 0235 void 0236 ImporterSqlConnection::slotCommit() 0237 { 0238 Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() ); 0239 0240 if( !isTransaction() ) 0241 return; 0242 0243 QSqlDatabase db = connection(); 0244 if( db.isOpen() ) 0245 { 0246 db.commit(); 0247 db.close(); 0248 } 0249 0250 m_openTransaction = false; 0251 }