1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "sqlitedatabasebackend.h"
27
28 #include "sqlitebasestatement.h"
29 #include "sqlitedatabase.h"
30 #include "sqliteexception.h"
31 #include "sqlitereadstatement.h"
32 #include "sqlitereadwritestatement.h"
33 #include "sqlitewritestatement.h"
34
35 #include <QFileInfo>
36 #include <QThread>
37 #include <QDebug>
38
39 #include "sqlite.h"
40
41 #include <chrono>
42 #include <thread>
43
44 extern "C" {
45 int sqlite3_carray_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi);
46 }
47
48 namespace Sqlite {
49
50 using namespace std::literals;
51
DatabaseBackend(Database & database)52 DatabaseBackend::DatabaseBackend(Database &database)
53 : m_database(database)
54 , m_databaseHandle(nullptr)
55 , m_busyHandler([](int) {
56 std::this_thread::sleep_for(10ms);
57 return true;
58 })
59 {
60 }
61
~DatabaseBackend()62 DatabaseBackend::~DatabaseBackend()
63 {
64 closeWithoutException();
65 }
66
setRanslatorentriesapSize(qint64 defaultSize,qint64 maximumSize)67 void DatabaseBackend::setRanslatorentriesapSize(qint64 defaultSize, qint64 maximumSize)
68 {
69 int resultCode = sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, defaultSize, maximumSize);
70 checkMmapSizeIsSet(resultCode);
71 }
72
activateMultiThreading()73 void DatabaseBackend::activateMultiThreading()
74 {
75 int resultCode = sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
76 checkIfMultithreadingIsActivated(resultCode);
77 }
78
sqliteLog(void *,int errorCode,const char * errorMessage)79 static void sqliteLog(void*,int errorCode,const char *errorMessage)
80 {
81 std::cout << "Sqlite " << sqlite3_errstr(errorCode) << ": " << errorMessage << std::endl;
82 }
83
activateLogging()84 void DatabaseBackend::activateLogging()
85 {
86 if (qEnvironmentVariableIsSet("QTC_SQLITE_LOGGING")) {
87 int resultCode = sqlite3_config(SQLITE_CONFIG_LOG, sqliteLog, nullptr);
88 checkIfLoogingIsActivated(resultCode);
89 }
90 }
91
initializeSqliteLibrary()92 void DatabaseBackend::initializeSqliteLibrary()
93 {
94 int resultCode = sqlite3_initialize();
95 checkInitializeSqliteLibraryWasSuccesful(resultCode);
96 }
97
shutdownSqliteLibrary()98 void DatabaseBackend::shutdownSqliteLibrary()
99 {
100 int resultCode = sqlite3_shutdown();
101 checkShutdownSqliteLibraryWasSuccesful(resultCode);
102 }
103
checkpointFullWalLog()104 void DatabaseBackend::checkpointFullWalLog()
105 {
106 int resultCode = sqlite3_wal_checkpoint_v2(sqliteDatabaseHandle(), nullptr, SQLITE_CHECKPOINT_FULL, nullptr, nullptr);
107 checkIfLogCouldBeCheckpointed(resultCode);
108 }
109
open(Utils::SmallStringView databaseFilePath,OpenMode mode)110 void DatabaseBackend::open(Utils::SmallStringView databaseFilePath, OpenMode mode)
111 {
112 checkCanOpenDatabase(databaseFilePath);
113
114 int resultCode = sqlite3_open_v2(databaseFilePath.data(), &m_databaseHandle, openMode(mode), nullptr);
115
116 checkDatabaseCouldBeOpened(resultCode);
117
118 sqlite3_extended_result_codes(m_databaseHandle, true);
119 resultCode = sqlite3_carray_init(m_databaseHandle, nullptr, nullptr);
120
121 checkCarrayCannotBeIntialized(resultCode);
122 }
123
sqliteDatabaseHandle() const124 sqlite3 *DatabaseBackend::sqliteDatabaseHandle() const
125 {
126 checkDatabaseHandleIsNotNull();
127 return m_databaseHandle;
128 }
129
setPragmaValue(Utils::SmallStringView pragmaKey,Utils::SmallStringView newPragmaValue)130 void DatabaseBackend::setPragmaValue(Utils::SmallStringView pragmaKey, Utils::SmallStringView newPragmaValue)
131 {
132 ReadWriteStatement<1>{Utils::SmallString::join({"PRAGMA ", pragmaKey, "='", newPragmaValue, "'"}),
133 m_database}
134 .execute();
135 Utils::SmallString pragmeValueInDatabase = toValue<Utils::SmallString>("PRAGMA " + pragmaKey);
136
137 checkPragmaValue(pragmeValueInDatabase, newPragmaValue);
138 }
139
pragmaValue(Utils::SmallStringView pragma) const140 Utils::SmallString DatabaseBackend::pragmaValue(Utils::SmallStringView pragma) const
141 {
142 return toValue<Utils::SmallString>("PRAGMA " + pragma);
143 }
144
setJournalMode(JournalMode journalMode)145 void DatabaseBackend::setJournalMode(JournalMode journalMode)
146 {
147 setPragmaValue("journal_mode", journalModeToPragma(journalMode));
148 }
149
journalMode()150 JournalMode DatabaseBackend::journalMode()
151 {
152 return pragmaToJournalMode(pragmaValue("journal_mode"));
153 }
154
155 namespace {
lockingModeToPragma(LockingMode lockingMode)156 Utils::SmallStringView lockingModeToPragma(LockingMode lockingMode)
157 {
158 switch (lockingMode) {
159 case LockingMode::Default:
160 return "";
161 case LockingMode::Normal:
162 return "normal";
163 case LockingMode::Exclusive:
164 return "exclusive";
165 }
166
167 return "";
168 }
pragmaToLockingMode(Utils::SmallStringView pragma)169 LockingMode pragmaToLockingMode(Utils::SmallStringView pragma)
170 {
171 if (pragma == "normal")
172 return LockingMode::Normal;
173 else if (pragma == "exclusive")
174 return LockingMode::Exclusive;
175
176 return LockingMode::Default;
177 }
178 } // namespace
179
setLockingMode(LockingMode lockingMode)180 void DatabaseBackend::setLockingMode(LockingMode lockingMode)
181 {
182 if (lockingMode != LockingMode::Default)
183 setPragmaValue("main.locking_mode", lockingModeToPragma(lockingMode));
184 }
185
lockingMode() const186 LockingMode DatabaseBackend::lockingMode() const
187 {
188 return pragmaToLockingMode(pragmaValue("main.locking_mode"));
189 }
190
changesCount() const191 int DatabaseBackend::changesCount() const
192 {
193 return sqlite3_changes(sqliteDatabaseHandle());
194 }
195
totalChangesCount() const196 int DatabaseBackend::totalChangesCount() const
197 {
198 return sqlite3_total_changes(sqliteDatabaseHandle());
199 }
200
lastInsertedRowId() const201 int64_t DatabaseBackend::lastInsertedRowId() const
202 {
203 return sqlite3_last_insert_rowid(sqliteDatabaseHandle());
204 }
205
setLastInsertedRowId(int64_t rowId)206 void DatabaseBackend::setLastInsertedRowId(int64_t rowId)
207 {
208 sqlite3_set_last_insert_rowid(sqliteDatabaseHandle(), rowId);
209 }
210
execute(Utils::SmallStringView sqlStatement)211 void DatabaseBackend::execute(Utils::SmallStringView sqlStatement)
212 {
213 try {
214 ReadWriteStatement<0> statement(sqlStatement, m_database);
215 statement.execute();
216 } catch (StatementIsBusy &) {
217 execute(sqlStatement);
218 }
219 }
220
close()221 void DatabaseBackend::close()
222 {
223 checkForOpenDatabaseWhichCanBeClosed();
224
225 int resultCode = sqlite3_close(m_databaseHandle);
226
227 checkDatabaseClosing(resultCode);
228
229 m_databaseHandle = nullptr;
230
231 }
232
databaseIsOpen() const233 bool DatabaseBackend::databaseIsOpen() const
234 {
235 return m_databaseHandle != nullptr;
236 }
237
closeWithoutException()238 void DatabaseBackend::closeWithoutException()
239 {
240 if (m_databaseHandle) {
241 int resultCode = sqlite3_close_v2(m_databaseHandle);
242 m_databaseHandle = nullptr;
243 if (resultCode != SQLITE_OK)
244 qWarning() << "SqliteDatabaseBackend::closeWithoutException: Unexpected error at closing the database!";
245 }
246 }
247
248 namespace {
249
busyHandlerCallback(void * userData,int counter)250 int busyHandlerCallback(void *userData, int counter)
251 {
252 auto &&busyHandler = *static_cast<DatabaseBackend::BusyHandler *>(userData);
253
254 return busyHandler(counter);
255 }
256
257 } // namespace
258
registerBusyHandler()259 void DatabaseBackend::registerBusyHandler()
260 {
261 int resultCode = sqlite3_busy_handler(sqliteDatabaseHandle(), &busyHandlerCallback, &m_busyHandler);
262
263 checkIfBusyTimeoutWasSet(resultCode);
264 }
265
checkForOpenDatabaseWhichCanBeClosed()266 void DatabaseBackend::checkForOpenDatabaseWhichCanBeClosed()
267 {
268 if (m_databaseHandle == nullptr)
269 throw DatabaseIsAlreadyClosed("SqliteDatabaseBackend::close: database is not open so it cannot be closed.");
270 }
271
checkDatabaseClosing(int resultCode)272 void DatabaseBackend::checkDatabaseClosing(int resultCode)
273 {
274 switch (resultCode) {
275 case SQLITE_OK: return;
276 case SQLITE_BUSY: throw DatabaseIsBusy("SqliteDatabaseBackend::close: database is busy because of e.g. unfinalized statements and will stay open!");
277 default: throwUnknowError("SqliteDatabaseBackend::close: unknown error happens at closing!");
278 }
279 }
280
checkCanOpenDatabase(Utils::SmallStringView databaseFilePath)281 void DatabaseBackend::checkCanOpenDatabase(Utils::SmallStringView databaseFilePath)
282 {
283 if (databaseFilePath.isEmpty())
284 throw DatabaseFilePathIsEmpty("SqliteDatabaseBackend::SqliteDatabaseBackend: database cannot be opened because the file path is empty!");
285
286 if (!QFileInfo::exists(QFileInfo(QString(databaseFilePath)).path()))
287 throw WrongFilePath("SqliteDatabaseBackend::SqliteDatabaseBackend: database cannot be opened because of wrong file path!",
288 Utils::SmallString(databaseFilePath));
289
290 if (databaseIsOpen())
291 throw DatabaseIsAlreadyOpen("SqliteDatabaseBackend::SqliteDatabaseBackend: database cannot be opened because it is already open!");
292 }
293
checkDatabaseCouldBeOpened(int resultCode)294 void DatabaseBackend::checkDatabaseCouldBeOpened(int resultCode)
295 {
296 switch (resultCode) {
297 case SQLITE_OK:
298 return;
299 default:
300 closeWithoutException();
301 throw Exception(
302 "SqliteDatabaseBackend::SqliteDatabaseBackend: database cannot be opened:",
303 sqlite3_errmsg(sqliteDatabaseHandle()));
304 }
305 }
306
checkCarrayCannotBeIntialized(int resultCode)307 void DatabaseBackend::checkCarrayCannotBeIntialized(int resultCode)
308 {
309 if (resultCode != SQLITE_OK)
310 throwDatabaseIsNotOpen(
311 "SqliteDatabaseBackend: database cannot be opened because carray failed!");
312 }
313
checkPragmaValue(Utils::SmallStringView databaseValue,Utils::SmallStringView expectedValue)314 void DatabaseBackend::checkPragmaValue(Utils::SmallStringView databaseValue,
315 Utils::SmallStringView expectedValue)
316 {
317 if (databaseValue != expectedValue)
318 throw PragmaValueNotSet("SqliteDatabaseBackend::setPragmaValue: pragma value is not set!");
319 }
320
checkDatabaseHandleIsNotNull() const321 void DatabaseBackend::checkDatabaseHandleIsNotNull() const
322 {
323 if (m_databaseHandle == nullptr)
324 throwDatabaseIsNotOpen("SqliteDatabaseBackend: database is not open!");
325 }
326
checkIfMultithreadingIsActivated(int resultCode)327 void DatabaseBackend::checkIfMultithreadingIsActivated(int resultCode)
328 {
329 if (resultCode != SQLITE_OK)
330 throwExceptionStatic(
331 "SqliteDatabaseBackend::activateMultiThreading: multithreading can't be activated!");
332 }
333
checkIfLoogingIsActivated(int resultCode)334 void DatabaseBackend::checkIfLoogingIsActivated(int resultCode)
335 {
336 if (resultCode != SQLITE_OK)
337 throwExceptionStatic("SqliteDatabaseBackend::activateLogging: logging can't be activated!");
338 }
339
checkMmapSizeIsSet(int resultCode)340 void DatabaseBackend::checkMmapSizeIsSet(int resultCode)
341 {
342 if (resultCode != SQLITE_OK)
343 throwExceptionStatic(
344 "SqliteDatabaseBackend::checkMmapSizeIsSet: mmap size can't be changed!");
345 }
346
checkInitializeSqliteLibraryWasSuccesful(int resultCode)347 void DatabaseBackend::checkInitializeSqliteLibraryWasSuccesful(int resultCode)
348 {
349 if (resultCode != SQLITE_OK)
350 throwExceptionStatic(
351 "SqliteDatabaseBackend::initializeSqliteLibrary: SqliteLibrary cannot initialized!");
352 }
353
checkShutdownSqliteLibraryWasSuccesful(int resultCode)354 void DatabaseBackend::checkShutdownSqliteLibraryWasSuccesful(int resultCode)
355 {
356 if (resultCode != SQLITE_OK)
357 throwExceptionStatic(
358 "SqliteDatabaseBackend::shutdownSqliteLibrary: SqliteLibrary cannot be shutdowned!");
359 }
360
checkIfLogCouldBeCheckpointed(int resultCode)361 void DatabaseBackend::checkIfLogCouldBeCheckpointed(int resultCode)
362 {
363 if (resultCode != SQLITE_OK)
364 throwException("SqliteDatabaseBackend::checkpointFullWalLog: WAL log could not be checkpointed!");
365 }
366
checkIfBusyTimeoutWasSet(int resultCode)367 void DatabaseBackend::checkIfBusyTimeoutWasSet(int resultCode)
368 {
369 if (resultCode != SQLITE_OK)
370 throwException("SqliteDatabaseBackend::setBusyTimeout: Busy timeout cannot be set!");
371 }
372
373 namespace {
374 template<std::size_t Size>
indexOfPragma(Utils::SmallStringView pragma,const Utils::SmallStringView (& pragmas)[Size])375 int indexOfPragma(Utils::SmallStringView pragma, const Utils::SmallStringView (&pragmas)[Size])
376 {
377 for (unsigned int index = 0; index < Size; index++) {
378 if (pragma == pragmas[index])
379 return int(index);
380 }
381
382 return -1;
383 }
384 }
385
386 const Utils::SmallStringView journalModeStrings[] = {"delete",
387 "truncate",
388 "persist",
389 "memory",
390 "wal"};
391
journalModeToPragma(JournalMode journalMode)392 Utils::SmallStringView DatabaseBackend::journalModeToPragma(JournalMode journalMode)
393 {
394 return journalModeStrings[int(journalMode)];
395 }
396
pragmaToJournalMode(Utils::SmallStringView pragma)397 JournalMode DatabaseBackend::pragmaToJournalMode(Utils::SmallStringView pragma)
398 {
399 int index = indexOfPragma(pragma, journalModeStrings);
400
401 if (index < 0)
402 throwExceptionStatic("SqliteDatabaseBackend::pragmaToJournalMode: pragma can't be transformed in a journal mode enumeration!");
403
404 return static_cast<JournalMode>(index);
405 }
406
openMode(OpenMode mode)407 int DatabaseBackend::openMode(OpenMode mode)
408 {
409 int sqliteMode = SQLITE_OPEN_CREATE;
410
411 switch (mode) {
412 case OpenMode::ReadOnly: sqliteMode |= SQLITE_OPEN_READONLY; break;
413 case OpenMode::ReadWrite: sqliteMode |= SQLITE_OPEN_READWRITE; break;
414 }
415
416 return sqliteMode;
417 }
418
setBusyTimeout(std::chrono::milliseconds timeout)419 void DatabaseBackend::setBusyTimeout(std::chrono::milliseconds timeout)
420 {
421 sqlite3_busy_timeout(m_databaseHandle, int(timeout.count()));
422 }
423
walCheckpointFull()424 void DatabaseBackend::walCheckpointFull()
425 {
426 int resultCode = sqlite3_wal_checkpoint_v2(m_databaseHandle,
427 nullptr,
428 SQLITE_CHECKPOINT_TRUNCATE,
429 nullptr,
430 nullptr);
431
432 switch (resultCode) {
433 case SQLITE_OK:
434 break;
435 case SQLITE_BUSY_RECOVERY:
436 case SQLITE_BUSY_SNAPSHOT:
437 case SQLITE_BUSY_TIMEOUT:
438 case SQLITE_BUSY:
439 throw DatabaseIsBusy("DatabaseBackend::walCheckpointFull: Operation could not concluded "
440 "because database is busy!");
441 case SQLITE_ERROR_MISSING_COLLSEQ:
442 case SQLITE_ERROR_RETRY:
443 case SQLITE_ERROR_SNAPSHOT:
444 case SQLITE_ERROR:
445 throwException("DatabaseBackend::walCheckpointFull: Error occurred!");
446 case SQLITE_MISUSE:
447 throwExceptionStatic("DatabaseBackend::walCheckpointFull: Misuse of database!");
448 }
449 }
450
setUpdateHook(void * object,void (* callback)(void * object,int,char const * database,char const *,long long rowId))451 void DatabaseBackend::setUpdateHook(
452 void *object,
453 void (*callback)(void *object, int, char const *database, char const *, long long rowId))
454 {
455 sqlite3_update_hook(m_databaseHandle, callback, object);
456 }
457
resetUpdateHook()458 void DatabaseBackend::resetUpdateHook()
459 {
460 sqlite3_update_hook(m_databaseHandle, nullptr, nullptr);
461 }
462
setBusyHandler(DatabaseBackend::BusyHandler && busyHandler)463 void DatabaseBackend::setBusyHandler(DatabaseBackend::BusyHandler &&busyHandler)
464 {
465 m_busyHandler = std::move(busyHandler);
466 registerBusyHandler();
467 }
468
throwExceptionStatic(const char * whatHasHappens)469 void DatabaseBackend::throwExceptionStatic(const char *whatHasHappens)
470 {
471 throw Exception(whatHasHappens);
472 }
473
throwException(const char * whatHasHappens) const474 void DatabaseBackend::throwException(const char *whatHasHappens) const
475 {
476 if (m_databaseHandle)
477 throw Exception(whatHasHappens, sqlite3_errmsg(m_databaseHandle));
478 else
479 throw Exception(whatHasHappens);
480 }
481
throwUnknowError(const char * whatHasHappens) const482 void DatabaseBackend::throwUnknowError(const char *whatHasHappens) const
483 {
484 throw UnknowError(whatHasHappens);
485 }
486
throwDatabaseIsNotOpen(const char * whatHasHappens) const487 void DatabaseBackend::throwDatabaseIsNotOpen(const char *whatHasHappens) const
488 {
489 throw DatabaseIsNotOpen(whatHasHappens);
490 }
491
492 template<typename Type>
toValue(Utils::SmallStringView sqlStatement) const493 Type DatabaseBackend::toValue(Utils::SmallStringView sqlStatement) const
494 {
495 try {
496 ReadWriteStatement<1> statement(sqlStatement, m_database);
497
498 statement.next();
499
500 return statement.fetchValue<Type>(0);
501 } catch (StatementIsBusy &) {
502 return toValue<Type>(sqlStatement);
503 }
504 }
505
506 } // namespace Sqlite
507