data/SqliteInterruptScope.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QByteArray> | ||
| 4 | #include <QElapsedTimer> | ||
| 5 | #include <QHash> | ||
| 6 | #include <QSqlDatabase> | ||
| 7 | #include <QSqlDriver> | ||
| 8 | #include <atomic> | ||
| 9 | #include <sqlite3.h> | ||
| 10 | |||
| 11 | // Interrupts long SQLite statements on a QSQLITE connection (Sprint 86 | ||
| 12 | // search pages, Sprint 101 summary): while the scope lives, the progress | ||
| 13 | // handler stops the statement when `cancelled` turns true or the time limit | ||
| 14 | // passes. SQLite holds one handler per connection: a nested scope on the | ||
| 15 | // same connection takes it over and hands it back to the enclosing scope | ||
| 16 | // on destruction (S106-113), so scopes nest in LIFO order. A connection is | ||
| 17 | // used by one thread, hence the thread-local registry. | ||
| 18 | class SqliteInterruptScope { | ||
| 19 | public: | ||
| 20 | 1058 | SqliteInterruptScope(QSqlDatabase &database, const std::atomic_bool &cancelled, int timeLimitMs) | |
| 21 | 1058 | : m_cancelled(cancelled), m_timeLimitMs(timeLimitMs) { | |
| 22 |
4/5✓ Branch 3 → 4 taken 1058 times.
✓ Branch 4 → 5 taken 1058 times.
✗ Branch 4 → 7 not taken.
✓ Branch 5 → 6 taken 1058 times.
✓ Branch 6 → 8 taken 1058 times.
|
1058 | const auto handle = database.driver() ? database.driver()->handle() : QVariant(); |
| 23 |
7/13✓ Branch 8 → 9 taken 1058 times.
✓ Branch 9 → 10 taken 1058 times.
✗ Branch 9 → 14 not taken.
✓ Branch 10 → 11 taken 1058 times.
✓ Branch 11 → 12 taken 1058 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 1058 times.
✓ Branch 16 → 17 taken 1058 times.
✗ Branch 16 → 18 not taken.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 1058 times.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 36 not taken.
|
1058 | if (!handle.isValid() || QByteArray(handle.typeName()) != "sqlite3*") return; |
| 24 | 1058 | m_database = *static_cast<sqlite3 *const *>(handle.data()); | |
| 25 | 1058 | m_elapsed.start(); | |
| 26 | 1058 | m_enclosing = active().value(m_database, nullptr); | |
| 27 |
1/1✓ Branch 25 → 26 taken 1058 times.
|
1058 | active().insert(m_database, this); |
| 28 |
1/1✓ Branch 26 → 27 taken 1058 times.
|
1058 | sqlite3_progress_handler(m_database, 1000, &SqliteInterruptScope::onProgress, this); |
| 29 | 1058 | } | |
| 30 | 2116 | ~SqliteInterruptScope() { | |
| 31 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1058 times.
|
1058 | if (!m_database) return; |
| 32 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 8 taken 1057 times.
|
1058 | if (m_enclosing) { |
| 33 | 1 | active().insert(m_database, m_enclosing); | |
| 34 | 1 | sqlite3_progress_handler(m_database, 1000, &SqliteInterruptScope::onProgress, m_enclosing); | |
| 35 | } else { | ||
| 36 | 1057 | active().remove(m_database); | |
| 37 | 1057 | sqlite3_progress_handler(m_database, 0, nullptr, nullptr); | |
| 38 | } | ||
| 39 | 1058 | } | |
| 40 | SqliteInterruptScope(const SqliteInterruptScope &) = delete; | ||
| 41 | SqliteInterruptScope &operator=(const SqliteInterruptScope &) = delete; | ||
| 42 | |||
| 43 | // False when the connection is no interruptible SQLite connection. | ||
| 44 | 1057 | bool valid() const { return m_database != nullptr; } | |
| 45 | // True when the handler stopped a statement because of the time limit. | ||
| 46 | 1034 | bool timedOut() const { return m_timedOut; } | |
| 47 | // True once the limit passed, also between statements (row loops in C++). | ||
| 48 |
3/6✓ Branch 2 → 3 taken 105962 times.
✗ Branch 2 → 6 not taken.
✓ Branch 3 → 4 taken 105962 times.
✗ Branch 3 → 7 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 105962 times.
|
105962 | bool overTime() const { return m_timedOut || (m_database && m_elapsed.elapsed() >= m_timeLimitMs); } |
| 49 |
3/4✓ Branch 3 → 4 taken 105962 times.
✓ Branch 3 → 6 taken 4 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 105962 times.
|
105966 | bool stopped() const { return m_cancelled.load() || overTime(); } |
| 50 |
1/2✓ Branch 2 → 3 taken 514 times.
✗ Branch 2 → 4 not taken.
|
514 | qint64 elapsedMs() const { return m_database ? m_elapsed.elapsed() : 0; } |
| 51 | |||
| 52 | private: | ||
| 53 | 3174 | static QHash<sqlite3 *, SqliteInterruptScope *> &active() { | |
| 54 |
2/2✓ Branch 2 → 3 taken 177 times.
✓ Branch 2 → 4 taken 2997 times.
|
3174 | thread_local QHash<sqlite3 *, SqliteInterruptScope *> scopes; |
| 55 | 3174 | return scopes; | |
| 56 | } | ||
| 57 | 20962 | static int onProgress(void *context) { | |
| 58 | 20962 | auto &scope = *static_cast<SqliteInterruptScope *>(context); | |
| 59 | 20962 | scope.m_timedOut = scope.m_elapsed.elapsed() >= scope.m_timeLimitMs; | |
| 60 |
4/4✓ Branch 4 → 5 taken 20925 times.
✓ Branch 4 → 6 taken 37 times.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 20924 times.
|
20962 | return scope.m_cancelled.load() || scope.m_timedOut ? 1 : 0; |
| 61 | } | ||
| 62 | const std::atomic_bool &m_cancelled; | ||
| 63 | int m_timeLimitMs; | ||
| 64 | sqlite3 *m_database = nullptr; | ||
| 65 | SqliteInterruptScope *m_enclosing = nullptr; | ||
| 66 | QElapsedTimer m_elapsed; | ||
| 67 | bool m_timedOut = false; | ||
| 68 | }; | ||
| 69 |