1 /****************************************************************************************
2  * Copyright (c) 2013 Konrad Zemek <konrad.zemek@gmail.com>                             *
3  *                                                                                      *
4  * This program is free software; you can redistribute it and/or modify it under        *
5  * the terms of the GNU General Public License as published by the Free Software        *
6  * Foundation; either version 2 of the License, or (at your option) any later           *
7  * version.                                                                             *
8  *                                                                                      *
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
11  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
12  *                                                                                      *
13  * You should have received a copy of the GNU General Public License along with         *
14  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
15  ****************************************************************************************/
16 
17 #include "ImporterSqlConnection.h"
18 
19 #include "core/support/Debug.h"
20 
21 #include <ThreadWeaver/Thread>
22 
23 #include <QMutexLocker>
24 #include <QSqlDriver>
25 #include <QSqlError>
26 #include <QSqlQuery>
27 #include <QSqlRecord>
28 #include <QUuid>
29 
30 using namespace StatSyncing;
31 
ImporterSqlConnection(const QString & driver,const QString & hostname,const quint16 port,const QString & dbName,const QString & user,const QString & password)32 ImporterSqlConnection::ImporterSqlConnection( const QString &driver,
33                                               const QString &hostname,
34                                               const quint16 port, const QString &dbName,
35                                               const QString &user,
36                                               const QString &password )
37     : m_connectionName( QUuid::createUuid().toString() )
38     , m_apiMutex( QMutex::Recursive )
39     , m_openTransaction( false )
40 {
41     QSqlDatabase db = QSqlDatabase::addDatabase( driver, m_connectionName );
42     db.setHostName( hostname );
43     db.setPort( port );
44     db.setDatabaseName( dbName );
45     db.setUserName( user );
46     db.setPassword( password );
47 }
48 
ImporterSqlConnection(const QString & dbPath)49 ImporterSqlConnection::ImporterSqlConnection( const QString &dbPath )
50     : m_connectionName( QUuid::createUuid().toString() )
51     , m_apiMutex( QMutex::Recursive )
52     , m_openTransaction( false )
53 {
54     QSqlDatabase db = QSqlDatabase::addDatabase( QStringLiteral("QSQLITE"), m_connectionName );
55     db.setDatabaseName( dbPath );
56 }
57 
ImporterSqlConnection()58 ImporterSqlConnection::ImporterSqlConnection()
59     : m_connectionName( QUuid::createUuid().toString() )
60     , m_apiMutex( QMutex::Recursive )
61     , m_openTransaction( false )
62 {
63 }
64 
~ImporterSqlConnection()65 ImporterSqlConnection::~ImporterSqlConnection()
66 {
67     if( isTransaction() )
68     {
69         QSqlDatabase db = connection();
70         if( db.isOpen() )
71         {
72             warning() << __PRETTY_FUNCTION__ << "Rolling back unfinished transaction for"
73                       << "database" << db.databaseName() << "(" << db.hostName() << ":"
74                       << db.port() << ")";
75 
76             db.rollback();
77         }
78     }
79 
80     QSqlDatabase::removeDatabase( m_connectionName );
81 }
82 
83 QSqlDatabase
connection()84 ImporterSqlConnection::connection()
85 {
86     Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() );
87     return QSqlDatabase::database( m_connectionName );
88 }
89 
90 bool
isTransaction() const91 ImporterSqlConnection::isTransaction() const
92 {
93     return m_openTransaction;
94 }
95 
96 QList<QVariantList>
query(const QString & query,const QVariantMap & bindValues,bool * const ok)97 ImporterSqlConnection::query( const QString &query, const QVariantMap &bindValues,
98                               bool* const ok )
99 {
100     QMutexLocker lock( &m_apiMutex );
101 
102     QMetaObject::invokeMethod( this, "slotQuery", blockingConnectionType(),
103                                Q_ARG( QString, query ), Q_ARG( QVariantMap, bindValues ),
104                                Q_ARG( bool* const, ok ) );
105 
106     QList<QVariantList> result;
107     result.swap( m_result );
108     return result;
109 }
110 
111 void
transaction()112 ImporterSqlConnection::transaction()
113 {
114     QMutexLocker lock( &m_apiMutex );
115     if( isTransaction() )
116         return;
117 
118     QMetaObject::invokeMethod( this, "slotTransaction", blockingConnectionType() );
119     if( isTransaction() ) // keep a lock for the duration of transaction
120         m_apiMutex.lock();
121 }
122 
123 void
rollback()124 ImporterSqlConnection::rollback()
125 {
126     QMutexLocker lock( &m_apiMutex );
127     if( !isTransaction() )
128         return;
129 
130     QMetaObject::invokeMethod( this, "slotRollback", blockingConnectionType() );
131     m_apiMutex.unlock(); // unlock second lock after releasing transaction
132 }
133 
134 void
commit()135 ImporterSqlConnection::commit()
136 {
137     QMutexLocker lock( &m_apiMutex );
138     if( !isTransaction() )
139         return;
140 
141     QMetaObject::invokeMethod( this, "slotCommit", blockingConnectionType() );
142     m_apiMutex.unlock(); // unlock second lock after releasing transaction
143 }
144 
145 inline Qt::ConnectionType
blockingConnectionType() const146 ImporterSqlConnection::blockingConnectionType() const
147 {
148     return this->thread() == ThreadWeaver::Thread::currentThread()
149             ? Qt::DirectConnection : Qt::BlockingQueuedConnection;
150 }
151 
152 void
slotQuery(const QString & query,const QVariantMap & bindValues,bool * const ok)153 ImporterSqlConnection::slotQuery( const QString &query, const QVariantMap &bindValues,
154                                   bool* const ok )
155 {
156     Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() );
157 
158     if( ok != 0 )
159         *ok = false;
160 
161     QSqlDatabase db = connection();
162     if( !db.isOpen() )
163         return;
164 
165     QSqlQuery q( db );
166     q.setForwardOnly( true );
167     q.prepare( query );
168     for( QVariantMap::ConstIterator bindValue = bindValues.constBegin();
169                                      bindValue != bindValues.constEnd(); ++bindValue )
170         q.bindValue( bindValue.key(), bindValue.value() );
171 
172     if( q.exec() )
173     {
174         if( ok != 0 )
175             *ok = true;
176 
177         m_result.reserve( q.size() );
178         while( q.next() )
179         {
180             const int fields = q.record().count();
181 
182             QVariantList row;
183             row.reserve( fields );
184             for( int field = 0; field < fields; ++field )
185                 row.append( q.value( field ) );
186 
187             m_result.append( row );
188         }
189     }
190     else
191         warning() << __PRETTY_FUNCTION__ << q.lastError().text();
192 
193     // This is a stupid QSqlDatabase connection manager; we don't want to leave connection
194     // open unless we're inside a transaction.
195     if( !isTransaction() )
196         db.close();
197 }
198 
199 void
slotTransaction()200 ImporterSqlConnection::slotTransaction()
201 {
202     Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() );
203 
204     if( isTransaction() )
205         return;
206 
207     QSqlDatabase db = connection();
208     if( db.isOpen() )
209     {
210         if( db.driver()->hasFeature( QSqlDriver::Transactions ) && db.transaction() )
211             m_openTransaction = true;
212         else
213             db.close();
214     }
215 }
216 
217 void
slotRollback()218 ImporterSqlConnection::slotRollback()
219 {
220     Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() );
221 
222     if( !isTransaction() )
223         return;
224 
225     QSqlDatabase db = connection();
226     if( db.isOpen() )
227     {
228         db.rollback();
229         db.close();
230     }
231 
232     m_openTransaction = false;
233 }
234 
235 void
slotCommit()236 ImporterSqlConnection::slotCommit()
237 {
238     Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() );
239 
240     if( !isTransaction() )
241         return;
242 
243     QSqlDatabase db = connection();
244     if( db.isOpen() )
245     {
246         db.commit();
247         db.close();
248     }
249 
250     m_openTransaction = false;
251 }
252