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