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