1 /*
2  * LibrePCB - Professional EDA for everyone!
3  * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4  * https://librepcb.org/
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /*******************************************************************************
21  *  Includes
22  ******************************************************************************/
23 #include "sqlitedatabase.h"
24 
25 #include "uuid.h"
26 
27 #include <QtCore>
28 
29 /*******************************************************************************
30  *  Namespace
31  ******************************************************************************/
32 namespace librepcb {
33 
34 /*******************************************************************************
35  *  Class TransactionScopeGuard
36  ******************************************************************************/
37 
TransactionScopeGuard(SQLiteDatabase & db)38 SQLiteDatabase::TransactionScopeGuard::TransactionScopeGuard(SQLiteDatabase& db)
39   : mDb(db), mIsCommited(false) {
40   mDb.beginTransaction();  // can throw
41 }
42 
commit()43 void SQLiteDatabase::TransactionScopeGuard::commit() {
44   mDb.commitTransaction();  // can throw
45   mIsCommited = true;
46 }
47 
~TransactionScopeGuard()48 SQLiteDatabase::TransactionScopeGuard::~TransactionScopeGuard() noexcept {
49   if (!mIsCommited) {
50     try {
51       mDb.rollbackTransaction();  // can throw
52     } catch (Exception& e) {
53       qCritical() << "Could not rollback database transaction:" << e.getMsg();
54     }
55   }
56 }
57 
58 /*******************************************************************************
59  *  Constructors / Destructor
60  ******************************************************************************/
61 
SQLiteDatabase(const FilePath & filepath)62 SQLiteDatabase::SQLiteDatabase(const FilePath& filepath)
63   : QObject(nullptr)  //, mNestedTransactionCount(0)
64 {
65   // create database (use random UUID as connection name)
66   mDb = QSqlDatabase::addDatabase("QSQLITE", Uuid::createRandom().toStr());
67   mDb.setDatabaseName(filepath.toStr());
68 
69   // check if database is valid
70   if (!mDb.isValid()) {
71     throw RuntimeError(__FILE__, __LINE__,
72                        tr("Invalid database: \"%1\"").arg(filepath.toNative()));
73   }
74 
75   // open the database
76   if (!mDb.open()) {
77     throw RuntimeError(
78         __FILE__, __LINE__,
79         tr("Could not open database: \"%1\"").arg(filepath.toNative()));
80   }
81 
82   // set SQLite options
83   exec("PRAGMA foreign_keys = ON");  // can throw
84   enableSqliteWriteAheadLogging();  // can throw
85 
86   // check if all required features are available
87   Q_ASSERT(mDb.driver() && mDb.driver()->hasFeature(QSqlDriver::Transactions));
88   Q_ASSERT(mDb.driver() &&
89            mDb.driver()->hasFeature(QSqlDriver::PreparedQueries));
90   Q_ASSERT(mDb.driver() && mDb.driver()->hasFeature(QSqlDriver::LastInsertId));
91   Q_ASSERT(getSqliteCompileOptions()["THREADSAFE"] == "1");  // can throw
92 }
93 
~SQLiteDatabase()94 SQLiteDatabase::~SQLiteDatabase() noexcept {
95   mDb.close();
96 }
97 
98 /*******************************************************************************
99  *  SQL Commands
100  ******************************************************************************/
101 
beginTransaction()102 void SQLiteDatabase::beginTransaction() {
103   // Q_ASSERT(mNestedTransactionCount >= 0);
104   // if (mNestedTransactionCount == 0) {
105   if (!mDb.transaction()) {
106     throw RuntimeError(__FILE__, __LINE__,
107                        tr("Could not start database transaction."));
108   }
109   //}
110   // mNestedTransactionCount++;
111 }
112 
commitTransaction()113 void SQLiteDatabase::commitTransaction() {
114   // Q_ASSERT(mNestedTransactionCount >= 0);
115   // if (mNestedTransactionCount == 1) {
116   if (!mDb.commit()) {
117     throw RuntimeError(__FILE__, __LINE__,
118                        tr("Could not commit database transaction."));
119   }
120   //} else if (mNestedTransactionCount == 0) {
121   //    throw RuntimeError(__FILE__, __LINE__,
122   //        tr("Cannot commit database transaction because no one is active."));
123   //}
124   // mNestedTransactionCount--;
125 }
126 
rollbackTransaction()127 void SQLiteDatabase::rollbackTransaction() {
128   // Q_ASSERT(mNestedTransactionCount >= 0);
129   // if (mNestedTransactionCount == 1) {
130   if (!mDb.rollback()) {
131     throw RuntimeError(__FILE__, __LINE__,
132                        tr("Could not rollback database transaction."));
133   }
134   //} else if (mNestedTransactionCount == 0) {
135   //    throw RuntimeError(__FILE__, __LINE__,
136   //        tr("Cannot rollback database transaction because no one is
137   //        active."));
138   //}
139   // mNestedTransactionCount--;
140 }
141 
clearTable(const QString & table)142 void SQLiteDatabase::clearTable(const QString& table) {
143   exec("DELETE FROM " % table);  // can throw
144 }
145 
146 /*******************************************************************************
147  *  General Methods
148  ******************************************************************************/
149 
prepareQuery(const QString & query) const150 QSqlQuery SQLiteDatabase::prepareQuery(const QString& query) const {
151   QSqlQuery q(mDb);
152   if (!q.prepare(query)) {
153     qDebug() << q.lastError().databaseText();
154     qDebug() << q.lastError().driverText();
155     throw RuntimeError(__FILE__, __LINE__,
156                        tr("Error while preparing SQL query: %1").arg(query));
157   }
158   return q;
159 }
160 
count(QSqlQuery & query)161 int SQLiteDatabase::count(QSqlQuery& query) {
162   exec(query);  // can throw
163 
164   int count = 0;
165   bool success = query.next() && query.value(0).isValid();
166   if (success) {
167     count = query.value(0).toInt(&success);
168   }
169   if (success) {
170     return count;
171   } else {
172     throw LogicError(__FILE__, __LINE__);
173   }
174 }
175 
insert(QSqlQuery & query)176 int SQLiteDatabase::insert(QSqlQuery& query) {
177   exec(query);  // can throw
178 
179   bool ok = false;
180   int id = query.lastInsertId().toInt(&ok);
181   if (ok) {
182     return id;
183   } else {
184     throw RuntimeError(
185         __FILE__, __LINE__,
186         tr("Error while executing SQL query: %1").arg(query.lastQuery()));
187   }
188 }
189 
exec(QSqlQuery & query)190 void SQLiteDatabase::exec(QSqlQuery& query) {
191   if (!query.exec()) {
192     qDebug() << query.lastError().databaseText();
193     qDebug() << query.lastError().driverText();
194     throw RuntimeError(
195         __FILE__, __LINE__,
196         tr("Error while executing SQL query: %1").arg(query.lastQuery()));
197   }
198 }
199 
exec(const QString & query)200 void SQLiteDatabase::exec(const QString& query) {
201   QSqlQuery q = prepareQuery(query);
202   exec(q);
203 }
204 
205 /*******************************************************************************
206  *  Private Methods
207  ******************************************************************************/
208 
enableSqliteWriteAheadLogging()209 void SQLiteDatabase::enableSqliteWriteAheadLogging() {
210   QSqlQuery query("PRAGMA journal_mode=WAL", mDb);
211   exec(query);  // can throw
212   bool success = query.first();
213   QString result = query.value(0).toString();
214   if ((!success) || (result != "wal")) {
215     throw LogicError(
216         __FILE__, __LINE__,
217         tr("Could not enable SQLite Write-Ahead Logging: \"%1\"").arg(result));
218   }
219 }
220 
getSqliteCompileOptions()221 QHash<QString, QString> SQLiteDatabase::getSqliteCompileOptions() {
222   QHash<QString, QString> options;
223   QSqlQuery query("PRAGMA compile_options", mDb);
224   exec(query);  // can throw
225   while (query.next()) {
226     QString option = query.value(0).toString();
227     QString key = option.section('=', 0, 0);
228     QString value = option.section('=', 1, -1);
229     options.insert(key, value);
230   }
231   return options;
232 }
233 
234 /*******************************************************************************
235  *  End of File
236  ******************************************************************************/
237 
238 }  // namespace librepcb
239