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 }