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