data/ThreadProjectionIndex.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include <QTimeZone> | ||
| 2 | #include "ThreadProjectionIndex.h" | ||
| 3 | #include "MailCache.h" | ||
| 4 | #include "MailDisplaySnapshot.h" | ||
| 5 | #include "MailSortSql.h" | ||
| 6 | #include "controller/ThreadBuilder.h" | ||
| 7 | #include "util/MessageId.h" | ||
| 8 | #include <QFileInfo> | ||
| 9 | #include <QJsonArray> | ||
| 10 | #include <QJsonDocument> | ||
| 11 | #include <QScopeGuard> | ||
| 12 | #include <QRegularExpression> | ||
| 13 | #include <QSqlError> | ||
| 14 | #include <QSqlDriver> | ||
| 15 | #include <QSqlQuery> | ||
| 16 | #include <QUuid> | ||
| 17 | #include <QtEndian> | ||
| 18 | #include <sqlite3.h> | ||
| 19 | #include <algorithm> | ||
| 20 | |||
| 21 | namespace { | ||
| 22 | 950420 | QStringList threadIds(const QString &stored) { | |
| 23 |
4/4✓ Branch 2 → 3 taken 950420 times.
✓ Branch 4 → 5 taken 950420 times.
✓ Branch 6 → 7 taken 225926 times.
✓ Branch 6 → 64 taken 724494 times.
|
950420 | if (!stored.trimmed().startsWith('[')) { |
| 24 |
1/1✓ Branch 7 → 8 taken 225926 times.
|
225926 | const auto parsed = MessageId::parseTokens(stored); |
| 25 |
5/11✓ Branch 9 → 10 taken 225926 times.
✗ Branch 9 → 13 not taken.
✓ Branch 10 → 11 taken 225926 times.
✓ Branch 12 → 13 taken 225926 times.
✗ Branch 12 → 14 not taken.
✓ Branch 15 → 16 taken 225926 times.
✗ Branch 15 → 17 not taken.
✓ Branch 17 → 18 taken 225926 times.
✗ Branch 17 → 20 not taken.
✗ Branch 111 → 112 not taken.
✗ Branch 111 → 113 not taken.
|
225926 | if (!parsed.isEmpty() || stored.trimmed().isEmpty()) return parsed; |
| 26 | ✗ | QStringList result; | |
| 27 | ✗ | for (const auto &part : stored.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts)) { | |
| 28 | ✗ | for (const auto &id : MessageId::parseTokens(part)) { | |
| 29 | ✗ | if (result.size() >= MessageId::MaximumTokens) return result; | |
| 30 | ✗ | result.append(id); | |
| 31 | ✗ | } | |
| 32 | ✗ | } | |
| 33 | ✗ | return result; | |
| 34 | 225926 | } | |
| 35 | 724494 | QStringList result; | |
| 36 |
7/7✓ Branch 64 → 65 taken 724494 times.
✓ Branch 65 → 66 taken 724494 times.
✓ Branch 66 → 67 taken 724494 times.
✓ Branch 69 → 70 taken 724494 times.
✓ Branch 70 → 71 taken 724494 times.
✓ Branch 97 → 72 taken 391856 times.
✓ Branch 97 → 98 taken 724494 times.
|
1116350 | for (const auto value : QJsonDocument::fromJson(stored.toUtf8()).array()) { |
| 37 |
6/6✓ Branch 74 → 75 taken 391856 times.
✓ Branch 75 → 76 taken 391856 times.
✓ Branch 78 → 79 taken 391856 times.
✓ Branch 79 → 80 taken 391856 times.
✓ Branch 89 → 81 taken 391856 times.
✓ Branch 89 → 90 taken 391856 times.
|
783712 | for (const auto &id : MessageId::parseTokens(value.toString())) { |
| 38 |
1/2✗ Branch 83 → 84 not taken.
✓ Branch 83 → 86 taken 391856 times.
|
391856 | if (result.size() >= MessageId::MaximumTokens) return result; |
| 39 |
1/1✓ Branch 86 → 87 taken 391856 times.
|
391856 | result.append(id); |
| 40 | 391856 | } | |
| 41 | 724494 | } | |
| 42 | 724494 | return result; | |
| 43 | 724494 | } | |
| 44 | const QString HeaderColumns = QStringLiteral("h.uid,h.subject,h.from_addr,h.to_addr,h.date,h.flags,h.size,h.has_attachments,h.message_id,h.in_reply_to,h.ref_ids,h.is_spam"); | ||
| 45 | // The list needs bounded preview fields, never the complete References or | ||
| 46 | // recipient envelope. Opening a message reads its canonical cached header. | ||
| 47 | const QString PreviewColumns = QStringLiteral("h.uid,substr(h.subject,1,2048),substr(h.from_addr,1,2048),'',h.date,h.flags,h.size,h.has_attachments,'','','',h.is_spam"); | ||
| 48 | // Index builds only compare dates and read epoch seconds: UTC avoids a | ||
| 49 | // local time-zone conversion per mail. Page rows keep local time for display. | ||
| 50 | enum class DateZone { Local, Utc }; | ||
| 51 | 475210 | MailHeader readHeader(const QSqlQuery &query, qint64 folder, DateZone zone = DateZone::Local) { | |
| 52 | 475210 | MailHeader header; | |
| 53 |
2/2✓ Branch 3 → 4 taken 475210 times.
✓ Branch 4 → 5 taken 475210 times.
|
475210 | header.folderId = folder; header.uid = query.value(0).toLongLong(); |
| 54 |
4/4✓ Branch 6 → 7 taken 475210 times.
✓ Branch 7 → 8 taken 475210 times.
✓ Branch 11 → 12 taken 475210 times.
✓ Branch 12 → 13 taken 475210 times.
|
475210 | header.subject = query.value(1).toString(); header.from = query.value(2).toString(); |
| 55 |
2/2✓ Branch 16 → 17 taken 475210 times.
✓ Branch 17 → 18 taken 475210 times.
|
475210 | header.to = query.value(3).toString(); |
| 56 |
3/4✓ Branch 21 → 22 taken 475210 times.
✓ Branch 22 → 23 taken 475210 times.
✓ Branch 24 → 25 taken 475210 times.
✗ Branch 24 → 37 not taken.
|
475210 | if (!query.value(4).isNull()) { |
| 57 |
2/2✓ Branch 25 → 26 taken 475210 times.
✓ Branch 26 → 27 taken 475210 times.
|
475210 | const qint64 seconds = query.value(4).toLongLong(); |
| 58 |
6/8✓ Branch 28 → 29 taken 362247 times.
✓ Branch 28 → 31 taken 112963 times.
✓ Branch 30 → 32 taken 362247 times.
✓ Branch 31 → 32 taken 112963 times.
✓ Branch 34 → 35 taken 362247 times.
✓ Branch 34 → 36 taken 112963 times.
✗ Branch 94 → 95 not taken.
✗ Branch 94 → 96 not taken.
|
950420 | header.date = zone == DateZone::Utc ? QDateTime::fromSecsSinceEpoch(seconds, QTimeZone::UTC) |
| 59 | 475210 | : QDateTime::fromSecsSinceEpoch(seconds); | |
| 60 | } | ||
| 61 |
4/4✓ Branch 37 → 38 taken 475210 times.
✓ Branch 38 → 39 taken 475210 times.
✓ Branch 40 → 41 taken 475210 times.
✓ Branch 41 → 42 taken 475210 times.
|
475210 | header.flags = query.value(5).toUInt(); header.size = query.value(6).toLongLong(); |
| 62 |
4/4✓ Branch 43 → 44 taken 475210 times.
✓ Branch 44 → 45 taken 475210 times.
✓ Branch 46 → 47 taken 475210 times.
✓ Branch 47 → 48 taken 475210 times.
|
475210 | header.hasAttachments = query.value(7).toBool(); header.messageId = query.value(8).toString(); |
| 63 |
3/3✓ Branch 51 → 52 taken 475210 times.
✓ Branch 52 → 53 taken 475210 times.
✓ Branch 53 → 54 taken 475210 times.
|
475210 | header.inReplyToIds = threadIds(query.value(9).toString()); |
| 64 |
1/1✓ Branch 58 → 59 taken 475210 times.
|
475210 | header.inReplyTo = header.inReplyToIds.value(0); |
| 65 |
3/3✓ Branch 61 → 62 taken 475210 times.
✓ Branch 62 → 63 taken 475210 times.
✓ Branch 63 → 64 taken 475210 times.
|
475210 | header.references = threadIds(query.value(10).toString()); |
| 66 |
2/2✓ Branch 68 → 69 taken 475210 times.
✓ Branch 69 → 70 taken 475210 times.
|
475210 | header.isSpam = query.value(11).toBool(); |
| 67 | 475210 | return header; | |
| 68 | ✗ | } | |
| 69 | 113084 | ThreadProjectionCursor readCursor(const QSqlQuery &query, int offset) { | |
| 70 | return {query.value(offset), query.value(offset+1), | ||
| 71 |
1/1✓ Branch 5 → 6 taken 113084 times.
|
226168 | query.value(offset+2).toLongLong(), query.value(offset+3).toByteArray(), true}; |
| 72 | 226168 | } | |
| 73 | // F100-66: the two leading order columns of a page over projection alias | ||
| 74 | // `p` (then root_uid, path, all ascending). Date newest first keeps the | ||
| 75 | // stored columns and their index; date oldest first negates the stored | ||
| 76 | // date; sender and subject order by the thread's first mail (its key, or | ||
| 77 | // the byte-reversed key for Z to A), so the threads stay together. | ||
| 78 | struct ThreadOrderColumns { QString high, low; }; | ||
| 79 | 1397 | ThreadOrderColumns threadOrderColumns(const MailSortOrder &sort) { | |
| 80 | using Column = MailSortOrder::Column; | ||
| 81 |
4/4✓ Branch 2 → 3 taken 1356 times.
✓ Branch 2 → 4 taken 41 times.
✓ Branch 3 → 4 taken 42 times.
✓ Branch 3 → 36 taken 1314 times.
|
1397 | if (sort.column == Column::Sender || sort.column == Column::Subject) { |
| 82 | 166 | const QString key = QStringLiteral("(SELECT %1 FROM headers r WHERE r.folder_id=p.folder_id AND r.uid=p.root_uid)") | |
| 83 |
2/2✓ Branch 11 → 12 taken 83 times.
✓ Branch 12 → 13 taken 83 times.
|
83 | .arg(MailSort::sqlKey(sort.column, QStringLiteral("r"))); |
| 84 | // A text constant: SQLite reads an integer in ORDER BY as a column number. | ||
| 85 |
2/6✓ Branch 31 → 32 taken 42 times.
✓ Branch 31 → 33 taken 41 times.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 73 not taken.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
|
125 | return {QStringLiteral("''"), sort.ascending ? key : QStringLiteral("mailjd_desc_key(%1)").arg(key)}; |
| 86 | 83 | } | |
| 87 |
4/4✓ Branch 47 → 48 taken 1293 times.
✓ Branch 47 → 49 taken 21 times.
✓ Branch 49 → 50 taken 21 times.
✓ Branch 49 → 51 taken 1293 times.
|
1314 | return {QStringLiteral("p.undated"), sort.ascending ? QStringLiteral("-p.newest_order") : QStringLiteral("p.newest_order")}; |
| 88 | 4150 | } | |
| 89 | // Seed UIDs per full-build batch. Each batch expands to complete threads, | ||
| 90 | // reads their headers once and builds them in memory (a few KiB per mail), | ||
| 91 | // so this bounds the transient memory of a million-mail build. Every batch | ||
| 92 | // repeats the frontier expansion and the header read. | ||
| 93 | constexpr int FullBuildSeedBatch = 10000; | ||
| 94 | |||
| 95 | 182147 | QByteArray childPath(QByteArray parent, quint32 ordinal) { | |
| 96 |
2/2✓ Branch 2 → 3 taken 182147 times.
✓ Branch 3 → 4 taken 182147 times.
|
364294 | char bytes[4]; qToBigEndian(ordinal, bytes); parent.append(bytes, 4); return parent; |
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | 98 | ThreadProjectionIndex::~ThreadProjectionIndex() { | |
| 101 | 98 | m_db.close(); m_db = {}; | |
| 102 |
2/2✓ Branch 7 → 8 taken 97 times.
✓ Branch 7 → 9 taken 1 time.
|
98 | if (!m_connection.isEmpty()) QSqlDatabase::removeDatabase(m_connection); |
| 103 | 98 | } | |
| 104 | |||
| 105 | 24973 | bool ThreadProjectionIndex::exec(const QString &sql, const QVariantList &bindings) { | |
| 106 |
1/1✓ Branch 2 → 3 taken 24973 times.
|
24973 | QSqlQuery query(m_db); |
| 107 |
1/1✓ Branch 3 → 4 taken 24973 times.
|
24973 | query.prepare(sql); |
| 108 |
3/3✓ Branch 8 → 9 taken 8117 times.
✓ Branch 11 → 6 taken 8117 times.
✓ Branch 11 → 12 taken 24973 times.
|
33090 | for (const auto &value : bindings) query.addBindValue(value); |
| 109 |
3/3✓ Branch 12 → 13 taken 24973 times.
✓ Branch 13 → 14 taken 24972 times.
✓ Branch 13 → 15 taken 1 time.
|
24973 | if (query.exec()) return true; |
| 110 |
2/2✓ Branch 15 → 16 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
|
1 | m_error = query.lastError().text(); return false; |
| 111 | 24973 | } | |
| 112 | |||
| 113 | 97 | bool ThreadProjectionIndex::open(const QString &databasePath) { | |
| 114 |
3/6✓ Branch 3 → 4 taken 97 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 97 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 97 times.
|
97 | if (m_db.isOpen() || !QFileInfo::exists(databasePath)) return false; |
| 115 | 97 | m_matchReady = false; | |
| 116 |
3/3✓ Branch 10 → 11 taken 97 times.
✓ Branch 11 → 12 taken 97 times.
✓ Branch 15 → 16 taken 97 times.
|
194 | m_connection = QStringLiteral("thread_projection_") + QUuid::createUuid().toString(QUuid::WithoutBraces); |
| 117 |
2/2✓ Branch 24 → 25 taken 97 times.
✓ Branch 25 → 26 taken 97 times.
|
194 | m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connection); |
| 118 | 97 | m_db.setDatabaseName(databasePath); | |
| 119 |
1/4✗ Branch 31 → 32 not taken.
✓ Branch 31 → 38 taken 97 times.
✗ Branch 32 → 33 not taken.
✗ Branch 33 → 34 not taken.
|
97 | if (!m_db.open()) { m_error = m_db.lastError().text(); return false; } |
| 120 | 97 | MailSort::registerSqlFunctions(m_db); | |
| 121 | // P90.1/P90.3: publish transactions must survive normal mail-writer | ||
| 122 | // activity (sync batches, journal commits); a hard 250 ms busy window | ||
| 123 | // turned routine contention into user-visible projection failures. The | ||
| 124 | // wait stays bounded and lives on the background worker only. | ||
| 125 |
12/40✓ Branch 49 → 50 taken 97 times.
✓ Branch 50 → 51 taken 97 times.
✗ Branch 50 → 82 not taken.
✓ Branch 55 → 56 taken 97 times.
✓ Branch 56 → 57 taken 97 times.
✗ Branch 56 → 82 not taken.
✓ Branch 109 → 110 taken 97 times.
✗ Branch 109 → 111 not taken.
✓ Branch 111 → 112 taken 97 times.
✗ Branch 111 → 113 not taken.
✓ Branch 113 → 114 taken 97 times.
✗ Branch 113 → 115 not taken.
✓ Branch 115 → 116 taken 97 times.
✗ Branch 115 → 117 not taken.
✓ Branch 117 → 118 taken 97 times.
✗ Branch 117 → 119 not taken.
✓ Branch 119 → 120 taken 97 times.
✗ Branch 119 → 121 not taken.
✓ Branch 121 → 122 taken 97 times.
✗ Branch 121 → 123 not taken.
✓ Branch 123 → 124 taken 97 times.
✗ Branch 123 → 125 not taken.
✗ Branch 199 → 200 not taken.
✗ Branch 199 → 201 not taken.
✗ Branch 203 → 204 not taken.
✗ Branch 203 → 205 not taken.
✗ Branch 207 → 208 not taken.
✗ Branch 207 → 209 not taken.
✗ Branch 211 → 212 not taken.
✗ Branch 211 → 213 not taken.
✗ Branch 215 → 216 not taken.
✗ Branch 215 → 217 not taken.
✗ Branch 219 → 220 not taken.
✗ Branch 219 → 221 not taken.
✗ Branch 223 → 224 not taken.
✗ Branch 223 → 225 not taken.
✗ Branch 227 → 228 not taken.
✗ Branch 227 → 229 not taken.
✗ Branch 231 → 232 not taken.
✗ Branch 231 → 233 not taken.
|
582 | return exec(QStringLiteral("PRAGMA foreign_keys=ON")) && exec(QStringLiteral("PRAGMA busy_timeout=2000")) && exec(QStringLiteral("PRAGMA temp_store=FILE")) && |
| 126 |
5/15✓ Branch 61 → 62 taken 97 times.
✓ Branch 62 → 63 taken 97 times.
✗ Branch 62 → 82 not taken.
✓ Branch 103 → 104 taken 97 times.
✗ Branch 103 → 105 not taken.
✓ Branch 105 → 106 taken 97 times.
✗ Branch 105 → 107 not taken.
✓ Branch 107 → 108 taken 97 times.
✗ Branch 107 → 109 not taken.
✗ Branch 187 → 188 not taken.
✗ Branch 187 → 189 not taken.
✗ Branch 191 → 192 not taken.
✗ Branch 191 → 193 not taken.
✗ Branch 195 → 196 not taken.
✗ Branch 195 → 197 not taken.
|
388 | exec(QStringLiteral("CREATE TEMP TABLE thread_work(uid INTEGER PRIMARY KEY, processed INTEGER NOT NULL DEFAULT 0)")) && |
| 127 |
5/15✓ Branch 67 → 68 taken 97 times.
✓ Branch 68 → 69 taken 97 times.
✗ Branch 68 → 82 not taken.
✓ Branch 97 → 98 taken 97 times.
✗ Branch 97 → 99 not taken.
✓ Branch 99 → 100 taken 97 times.
✗ Branch 99 → 101 not taken.
✓ Branch 101 → 102 taken 97 times.
✗ Branch 101 → 103 not taken.
✗ Branch 175 → 176 not taken.
✗ Branch 175 → 177 not taken.
✗ Branch 179 → 180 not taken.
✗ Branch 179 → 181 not taken.
✗ Branch 183 → 184 not taken.
✗ Branch 183 → 185 not taken.
|
388 | exec(QStringLiteral("CREATE TEMP TABLE thread_new_terms(term TEXT NOT NULL,uid INTEGER NOT NULL,PRIMARY KEY(term,uid)) WITHOUT ROWID")) && |
| 128 |
7/18✓ Branch 43 → 44 taken 97 times.
✓ Branch 44 → 45 taken 97 times.
✗ Branch 44 → 82 not taken.
✓ Branch 73 → 74 taken 97 times.
✓ Branch 74 → 75 taken 97 times.
✗ Branch 74 → 82 not taken.
✓ Branch 91 → 92 taken 97 times.
✗ Branch 91 → 93 not taken.
✓ Branch 93 → 94 taken 97 times.
✗ Branch 93 → 95 not taken.
✓ Branch 95 → 96 taken 97 times.
✗ Branch 95 → 97 not taken.
✗ Branch 163 → 164 not taken.
✗ Branch 163 → 165 not taken.
✗ Branch 167 → 168 not taken.
✗ Branch 167 → 169 not taken.
✗ Branch 171 → 172 not taken.
✗ Branch 171 → 173 not taken.
|
679 | exec(QStringLiteral("CREATE TEMP TABLE thread_new_fallback(subject TEXT NOT NULL,participant TEXT NOT NULL,reply INTEGER NOT NULL,date INTEGER NOT NULL,uid INTEGER NOT NULL,PRIMARY KEY(subject,participant,reply,date,uid)) WITHOUT ROWID")) && |
| 129 |
6/17✓ Branch 79 → 80 taken 97 times.
✓ Branch 80 → 81 taken 97 times.
✗ Branch 80 → 82 not taken.
✓ Branch 83 → 84 taken 97 times.
✗ Branch 83 → 85 not taken.
✓ Branch 85 → 86 taken 97 times.
✗ Branch 85 → 87 not taken.
✓ Branch 87 → 88 taken 97 times.
✗ Branch 87 → 89 not taken.
✓ Branch 89 → 90 taken 97 times.
✗ Branch 89 → 91 not taken.
✗ Branch 151 → 152 not taken.
✗ Branch 151 → 153 not taken.
✗ Branch 155 → 156 not taken.
✗ Branch 155 → 157 not taken.
✗ Branch 159 → 160 not taken.
✗ Branch 159 → 161 not taken.
|
291 | exec(QStringLiteral("CREATE TEMP TABLE thread_seen_terms(term TEXT PRIMARY KEY) WITHOUT ROWID")); |
| 130 | } | ||
| 131 | |||
| 132 | 1416 | bool ThreadProjectionIndex::refresh(const ThreadProjectionRequest &request, | |
| 133 | const std::atomic_bool &cancelled, ThreadProjectionPage &result) { | ||
| 134 | 1912354 | const auto check = [&] { return cancelled.load(std::memory_order_relaxed); }; | |
| 135 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1415 times.
|
1416 | if (check()) { result.outcome = ThreadProjectionPage::Outcome::Cancelled; return false; } |
| 136 |
2/5✓ Branch 5 → 6 taken 1415 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 13 taken 1415 times.
✗ Branch 7 → 8 not taken.
✗ Branch 8 → 9 not taken.
|
1415 | if (!m_db.transaction()) { m_error = m_db.lastError().text(); return false; } |
| 137 | 1420 | auto rollback = qScopeGuard([this] { m_db.rollback(); }); | |
| 138 |
1/1✓ Branch 14 → 15 taken 1415 times.
|
1415 | QSqlQuery identity(m_db); |
| 139 |
1/1✓ Branch 18 → 19 taken 1415 times.
|
1415 | identity.prepare(QStringLiteral("SELECT f.uidvalidity,coalesce(r.revision,0),m.version,m.revision,m.uidvalidity FROM folders f LEFT JOIN thread_revisions r ON r.folder_id=f.id LEFT JOIN thread_projection_meta m ON m.folder_id=f.id WHERE f.id=? AND f.account=?")); |
| 140 |
2/2✓ Branch 23 → 24 taken 1415 times.
✓ Branch 27 → 28 taken 1415 times.
|
1415 | identity.addBindValue(request.folderId); identity.addBindValue(request.account); |
| 141 |
2/5✓ Branch 29 → 30 taken 1415 times.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 37 taken 1415 times.
✗ Branch 31 → 32 not taken.
✗ Branch 32 → 33 not taken.
|
1415 | if (!identity.exec()) { m_error = identity.lastError().text(); return false; } |
| 142 |
11/13✓ Branch 37 → 38 taken 1415 times.
✓ Branch 38 → 39 taken 1414 times.
✓ Branch 38 → 42 taken 1 time.
✓ Branch 39 → 40 taken 1414 times.
✓ Branch 40 → 41 taken 1414 times.
✓ Branch 41 → 42 taken 2 times.
✓ Branch 41 → 43 taken 1412 times.
✓ Branch 44 → 45 taken 1414 times.
✓ Branch 44 → 46 taken 1 time.
✓ Branch 46 → 47 taken 3 times.
✓ Branch 46 → 48 taken 1412 times.
✗ Branch 1265 → 1266 not taken.
✗ Branch 1265 → 1267 not taken.
|
1415 | if (!identity.next() || identity.value(0).toUInt() != request.uidValidity) { |
| 143 | 3 | result.outcome = ThreadProjectionPage::Outcome::Stale; return false; | |
| 144 | } | ||
| 145 |
2/2✓ Branch 48 → 49 taken 1412 times.
✓ Branch 49 → 50 taken 1412 times.
|
1412 | result.revision = identity.value(1).toLongLong(); |
| 146 |
4/4✓ Branch 51 → 52 taken 778 times.
✓ Branch 51 → 54 taken 634 times.
✓ Branch 52 → 53 taken 2 times.
✓ Branch 52 → 54 taken 776 times.
|
1412 | if (request.expectedRevision >= 0 && request.expectedRevision != result.revision) { |
| 147 | 2 | result.outcome = ThreadProjectionPage::Outcome::Stale; return false; | |
| 148 | } | ||
| 149 |
11/16✓ Branch 54 → 55 taken 1410 times.
✓ Branch 55 → 56 taken 1410 times.
✓ Branch 56 → 57 taken 1343 times.
✓ Branch 56 → 60 taken 67 times.
✓ Branch 57 → 58 taken 1343 times.
✓ Branch 58 → 59 taken 1343 times.
✓ Branch 59 → 60 taken 4 times.
✓ Branch 59 → 61 taken 1339 times.
✓ Branch 62 → 63 taken 1343 times.
✓ Branch 62 → 64 taken 67 times.
✓ Branch 64 → 65 taken 1410 times.
✗ Branch 64 → 66 not taken.
✗ Branch 1272 → 1273 not taken.
✗ Branch 1272 → 1274 not taken.
✗ Branch 1276 → 1277 not taken.
✗ Branch 1276 → 1278 not taken.
|
1410 | const bool full = identity.value(2).toInt() != Version || identity.value(4).toUInt() != request.uidValidity; |
| 150 |
8/10✓ Branch 66 → 67 taken 1339 times.
✓ Branch 66 → 71 taken 71 times.
✓ Branch 67 → 68 taken 1339 times.
✓ Branch 68 → 69 taken 1339 times.
✓ Branch 69 → 70 taken 1002 times.
✓ Branch 69 → 71 taken 337 times.
✓ Branch 72 → 73 taken 1339 times.
✓ Branch 72 → 74 taken 71 times.
✗ Branch 1280 → 1281 not taken.
✗ Branch 1280 → 1282 not taken.
|
1410 | const bool current = !full && identity.value(3).toLongLong() == result.revision; |
| 151 |
1/1✓ Branch 74 → 75 taken 1410 times.
|
1410 | identity.finish(); |
| 152 |
2/2✓ Branch 75 → 76 taken 1002 times.
✓ Branch 75 → 79 taken 408 times.
|
1410 | if (current) { |
| 153 |
1/1✓ Branch 76 → 77 taken 1002 times.
|
1002 | m_db.commit(); rollback.dismiss(); return true; |
| 154 | } | ||
| 155 |
7/20✓ Branch 84 → 85 taken 408 times.
✓ Branch 85 → 86 taken 408 times.
✓ Branch 86 → 87 taken 408 times.
✗ Branch 86 → 131 not taken.
✓ Branch 179 → 180 taken 408 times.
✗ Branch 179 → 181 not taken.
✓ Branch 181 → 182 taken 408 times.
✗ Branch 181 → 183 not taken.
✓ Branch 183 → 184 taken 408 times.
✗ Branch 183 → 185 not taken.
✗ Branch 185 → 186 not taken.
✓ Branch 185 → 187 taken 408 times.
✗ Branch 1372 → 1373 not taken.
✗ Branch 1372 → 1374 not taken.
✗ Branch 1376 → 1377 not taken.
✗ Branch 1376 → 1378 not taken.
✗ Branch 1380 → 1381 not taken.
✗ Branch 1380 → 1382 not taken.
✗ Branch 1384 → 1385 not taken.
✗ Branch 1384 → 1386 not taken.
|
816 | if (!exec("DELETE FROM thread_work") || !exec("DELETE FROM thread_new_terms") || |
| 156 |
5/12✓ Branch 88 → 89 taken 408 times.
✓ Branch 89 → 90 taken 408 times.
✓ Branch 90 → 91 taken 408 times.
✗ Branch 90 → 131 not taken.
✓ Branch 175 → 176 taken 408 times.
✗ Branch 175 → 177 not taken.
✓ Branch 177 → 178 taken 408 times.
✗ Branch 177 → 179 not taken.
✗ Branch 1364 → 1365 not taken.
✗ Branch 1364 → 1366 not taken.
✗ Branch 1368 → 1369 not taken.
✗ Branch 1368 → 1370 not taken.
|
816 | !exec("DELETE FROM thread_new_fallback") || |
| 157 |
5/12✓ Branch 92 → 93 taken 408 times.
✓ Branch 93 → 94 taken 408 times.
✓ Branch 94 → 95 taken 408 times.
✗ Branch 94 → 131 not taken.
✓ Branch 171 → 172 taken 408 times.
✗ Branch 171 → 173 not taken.
✓ Branch 173 → 174 taken 408 times.
✗ Branch 173 → 175 not taken.
✗ Branch 1356 → 1357 not taken.
✗ Branch 1356 → 1358 not taken.
✗ Branch 1360 → 1361 not taken.
✗ Branch 1360 → 1362 not taken.
|
816 | !exec("CREATE INDEX IF NOT EXISTS temp.thread_new_terms_uid ON thread_new_terms(uid)") || |
| 158 |
5/12✓ Branch 96 → 97 taken 408 times.
✓ Branch 97 → 98 taken 408 times.
✓ Branch 98 → 99 taken 408 times.
✗ Branch 98 → 131 not taken.
✓ Branch 167 → 168 taken 408 times.
✗ Branch 167 → 169 not taken.
✓ Branch 169 → 170 taken 408 times.
✗ Branch 169 → 171 not taken.
✗ Branch 1348 → 1349 not taken.
✗ Branch 1348 → 1350 not taken.
✗ Branch 1352 → 1353 not taken.
✗ Branch 1352 → 1354 not taken.
|
816 | !exec("CREATE INDEX IF NOT EXISTS temp.thread_new_fallback_uid ON thread_new_fallback(uid)") || |
| 159 |
5/12✓ Branch 100 → 101 taken 408 times.
✓ Branch 101 → 102 taken 408 times.
✓ Branch 102 → 103 taken 408 times.
✗ Branch 102 → 131 not taken.
✓ Branch 163 → 164 taken 408 times.
✗ Branch 163 → 165 not taken.
✓ Branch 165 → 166 taken 408 times.
✗ Branch 165 → 167 not taken.
✗ Branch 1340 → 1341 not taken.
✗ Branch 1340 → 1342 not taken.
✗ Branch 1344 → 1345 not taken.
✗ Branch 1344 → 1346 not taken.
|
816 | !exec("CREATE TEMP TABLE IF NOT EXISTS thread_frontier(term TEXT PRIMARY KEY) WITHOUT ROWID") || |
| 160 |
5/12✓ Branch 104 → 105 taken 408 times.
✓ Branch 105 → 106 taken 408 times.
✓ Branch 106 → 107 taken 408 times.
✗ Branch 106 → 131 not taken.
✓ Branch 159 → 160 taken 408 times.
✗ Branch 159 → 161 not taken.
✓ Branch 161 → 162 taken 408 times.
✗ Branch 161 → 163 not taken.
✗ Branch 1332 → 1333 not taken.
✗ Branch 1332 → 1334 not taken.
✗ Branch 1336 → 1337 not taken.
✗ Branch 1336 → 1338 not taken.
|
816 | !exec("CREATE TEMP TABLE IF NOT EXISTS thread_fallback_frontier(subject TEXT NOT NULL,participant TEXT NOT NULL,reply INTEGER NOT NULL,date INTEGER NOT NULL,uid INTEGER NOT NULL,PRIMARY KEY(subject,participant,reply,date,uid)) WITHOUT ROWID") || |
| 161 |
5/12✓ Branch 108 → 109 taken 408 times.
✓ Branch 109 → 110 taken 408 times.
✓ Branch 110 → 111 taken 408 times.
✗ Branch 110 → 131 not taken.
✓ Branch 155 → 156 taken 408 times.
✗ Branch 155 → 157 not taken.
✓ Branch 157 → 158 taken 408 times.
✗ Branch 157 → 159 not taken.
✗ Branch 1324 → 1325 not taken.
✗ Branch 1324 → 1326 not taken.
✗ Branch 1328 → 1329 not taken.
✗ Branch 1328 → 1330 not taken.
|
816 | !exec("CREATE TEMP TABLE IF NOT EXISTS thread_stage AS SELECT * FROM thread_projection WHERE 0") || |
| 162 |
5/12✓ Branch 112 → 113 taken 408 times.
✓ Branch 113 → 114 taken 408 times.
✓ Branch 114 → 115 taken 408 times.
✗ Branch 114 → 131 not taken.
✓ Branch 151 → 152 taken 408 times.
✗ Branch 151 → 153 not taken.
✓ Branch 153 → 154 taken 408 times.
✗ Branch 153 → 155 not taken.
✗ Branch 1316 → 1317 not taken.
✗ Branch 1316 → 1318 not taken.
✗ Branch 1320 → 1321 not taken.
✗ Branch 1320 → 1322 not taken.
|
816 | !exec("CREATE TEMP TABLE IF NOT EXISTS thread_stage_terms AS SELECT * FROM thread_terms WHERE 0") || |
| 163 |
5/12✓ Branch 116 → 117 taken 408 times.
✓ Branch 117 → 118 taken 408 times.
✓ Branch 118 → 119 taken 408 times.
✗ Branch 118 → 131 not taken.
✓ Branch 147 → 148 taken 408 times.
✗ Branch 147 → 149 not taken.
✓ Branch 149 → 150 taken 408 times.
✗ Branch 149 → 151 not taken.
✗ Branch 1308 → 1309 not taken.
✗ Branch 1308 → 1310 not taken.
✗ Branch 1312 → 1313 not taken.
✗ Branch 1312 → 1314 not taken.
|
816 | !exec("CREATE TEMP TABLE IF NOT EXISTS thread_stage_fallback AS SELECT * FROM thread_fallback_candidates WHERE 0") || |
| 164 |
13/28✓ Branch 80 → 81 taken 408 times.
✓ Branch 81 → 82 taken 408 times.
✓ Branch 82 → 83 taken 408 times.
✗ Branch 82 → 131 not taken.
✓ Branch 120 → 121 taken 408 times.
✓ Branch 121 → 122 taken 408 times.
✓ Branch 122 → 123 taken 408 times.
✗ Branch 122 → 131 not taken.
✓ Branch 124 → 125 taken 408 times.
✓ Branch 125 → 126 taken 408 times.
✓ Branch 126 → 127 taken 408 times.
✗ Branch 126 → 131 not taken.
✓ Branch 139 → 140 taken 408 times.
✗ Branch 139 → 141 not taken.
✓ Branch 141 → 142 taken 408 times.
✗ Branch 141 → 143 not taken.
✓ Branch 143 → 144 taken 408 times.
✗ Branch 143 → 145 not taken.
✓ Branch 145 → 146 taken 408 times.
✗ Branch 145 → 147 not taken.
✗ Branch 1292 → 1293 not taken.
✗ Branch 1292 → 1294 not taken.
✗ Branch 1296 → 1297 not taken.
✗ Branch 1296 → 1298 not taken.
✗ Branch 1300 → 1301 not taken.
✗ Branch 1300 → 1302 not taken.
✗ Branch 1304 → 1305 not taken.
✗ Branch 1304 → 1306 not taken.
|
1632 | !exec("DELETE FROM thread_stage") || !exec("DELETE FROM thread_stage_terms") || |
| 165 |
6/14✓ Branch 128 → 129 taken 408 times.
✓ Branch 129 → 130 taken 408 times.
✗ Branch 130 → 131 not taken.
✓ Branch 130 → 132 taken 408 times.
✓ Branch 133 → 134 taken 408 times.
✗ Branch 133 → 135 not taken.
✓ Branch 135 → 136 taken 408 times.
✗ Branch 135 → 137 not taken.
✓ Branch 137 → 138 taken 408 times.
✗ Branch 137 → 139 not taken.
✗ Branch 1284 → 1285 not taken.
✗ Branch 1284 → 1286 not taken.
✗ Branch 1288 → 1289 not taken.
✗ Branch 1288 → 1290 not taken.
|
816 | !exec("DELETE FROM thread_stage_fallback")) return false; |
| 166 |
2/2✓ Branch 187 → 188 taken 71 times.
✓ Branch 187 → 245 taken 337 times.
|
408 | if (full) { |
| 167 | // Freeze canonical headers on disk, not in a folder-sized C++ vector. | ||
| 168 | // The TEMP copy is private to this worker and has no mail writer lock. | ||
| 169 |
2/8✓ Branch 241 → 242 taken 71 times.
✗ Branch 241 → 243 not taken.
✗ Branch 243 → 244 not taken.
✓ Branch 243 → 256 taken 71 times.
✗ Branch 1440 → 1441 not taken.
✗ Branch 1440 → 1442 not taken.
✗ Branch 1444 → 1445 not taken.
✗ Branch 1444 → 1446 not taken.
|
71 | if (!exec("DROP TABLE IF EXISTS temp.thread_source") || |
| 170 |
8/20✓ Branch 194 → 195 taken 71 times.
✓ Branch 195 → 196 taken 71 times.
✓ Branch 196 → 197 taken 71 times.
✗ Branch 196 → 213 not taken.
✓ Branch 233 → 234 taken 71 times.
✗ Branch 233 → 235 not taken.
✓ Branch 235 → 236 taken 71 times.
✗ Branch 235 → 239 not taken.
✓ Branch 237 → 238 taken 71 times.
✓ Branch 237 → 239 taken 71 times.
✓ Branch 239 → 240 taken 71 times.
✗ Branch 239 → 241 not taken.
✗ Branch 1420 → 1421 not taken.
✗ Branch 1420 → 1422 not taken.
✗ Branch 1424 → 1425 not taken.
✗ Branch 1424 → 1426 not taken.
✗ Branch 1428 → 1429 not taken.
✗ Branch 1428 → 1432 not taken.
✗ Branch 1430 → 1431 not taken.
✗ Branch 1430 → 1432 not taken.
|
213 | !exec("CREATE TEMP TABLE thread_source AS SELECT * FROM headers WHERE folder_id=?", {request.folderId}) || |
| 171 |
5/12✓ Branch 198 → 199 taken 71 times.
✓ Branch 199 → 200 taken 71 times.
✓ Branch 200 → 201 taken 71 times.
✗ Branch 200 → 213 not taken.
✓ Branch 229 → 230 taken 71 times.
✗ Branch 229 → 231 not taken.
✓ Branch 231 → 232 taken 71 times.
✗ Branch 231 → 233 not taken.
✗ Branch 1412 → 1413 not taken.
✗ Branch 1412 → 1414 not taken.
✗ Branch 1416 → 1417 not taken.
✗ Branch 1416 → 1418 not taken.
|
142 | !exec("CREATE UNIQUE INDEX temp.thread_source_uid ON thread_source(uid)") || |
| 172 |
5/12✓ Branch 202 → 203 taken 71 times.
✓ Branch 203 → 204 taken 71 times.
✓ Branch 204 → 205 taken 71 times.
✗ Branch 204 → 213 not taken.
✓ Branch 225 → 226 taken 71 times.
✗ Branch 225 → 227 not taken.
✓ Branch 227 → 228 taken 71 times.
✗ Branch 227 → 229 not taken.
✗ Branch 1404 → 1405 not taken.
✗ Branch 1404 → 1406 not taken.
✗ Branch 1408 → 1409 not taken.
✗ Branch 1408 → 1410 not taken.
|
142 | !exec("CREATE TEMP TABLE IF NOT EXISTS thread_remaining(uid INTEGER PRIMARY KEY)") || |
| 173 |
8/16✓ Branch 189 → 190 taken 71 times.
✓ Branch 190 → 191 taken 71 times.
✓ Branch 191 → 192 taken 71 times.
✗ Branch 191 → 213 not taken.
✓ Branch 206 → 207 taken 71 times.
✓ Branch 207 → 208 taken 71 times.
✓ Branch 208 → 209 taken 71 times.
✗ Branch 208 → 213 not taken.
✓ Branch 221 → 222 taken 71 times.
✗ Branch 221 → 223 not taken.
✓ Branch 223 → 224 taken 71 times.
✗ Branch 223 → 225 not taken.
✗ Branch 1396 → 1397 not taken.
✗ Branch 1396 → 1398 not taken.
✗ Branch 1400 → 1401 not taken.
✗ Branch 1400 → 1402 not taken.
|
284 | !exec("DELETE FROM thread_remaining") || |
| 174 |
6/14✓ Branch 210 → 211 taken 71 times.
✓ Branch 211 → 212 taken 71 times.
✗ Branch 212 → 213 not taken.
✓ Branch 212 → 214 taken 71 times.
✓ Branch 215 → 216 taken 71 times.
✗ Branch 215 → 217 not taken.
✓ Branch 217 → 218 taken 71 times.
✗ Branch 217 → 219 not taken.
✓ Branch 219 → 220 taken 71 times.
✗ Branch 219 → 221 not taken.
✗ Branch 1388 → 1389 not taken.
✗ Branch 1388 → 1390 not taken.
✗ Branch 1392 → 1393 not taken.
✗ Branch 1392 → 1394 not taken.
|
142 | !exec("INSERT INTO thread_remaining SELECT uid FROM thread_source")) return false; |
| 175 |
5/8✓ Branch 247 → 248 taken 337 times.
✓ Branch 248 → 249 taken 337 times.
✓ Branch 252 → 253 taken 337 times.
✓ Branch 252 → 254 taken 337 times.
✗ Branch 254 → 255 not taken.
✓ Branch 254 → 256 taken 337 times.
✗ Branch 1454 → 1455 not taken.
✗ Branch 1454 → 1456 not taken.
|
674 | } else if (!exec("INSERT INTO thread_work(uid) SELECT uid FROM thread_dirty WHERE folder_id=?", {request.folderId})) return false; |
| 176 | |||
| 177 | // Preserve old dependency keys during incremental discovery so removed | ||
| 178 | // references, late roots and component splits all invalidate their peers. | ||
| 179 |
3/3✓ Branch 256 → 257 taken 408 times.
✓ Branch 257 → 258 taken 408 times.
✓ Branch 258 → 259 taken 408 times.
|
408 | QSqlQuery changed(m_db), addTerm(m_db), addFallback(m_db); |
| 180 | // Incremental reads start from the (small) work set: CROSS JOIN fixes the | ||
| 181 | // loop order, otherwise SQLite scanned every row of the folder even for an | ||
| 182 | // empty work set (0.76 s per revision change at 1M mails). | ||
| 183 |
5/5✓ Branch 259 → 260 taken 71 times.
✓ Branch 259 → 261 taken 337 times.
✓ Branch 262 → 263 taken 408 times.
✓ Branch 263 → 264 taken 408 times.
✓ Branch 264 → 265 taken 408 times.
|
408 | changed.prepare("SELECT " + HeaderColumns + (full ? " FROM thread_source h" : " FROM thread_work w CROSS JOIN headers h ON h.folder_id=? AND h.uid=w.uid")); |
| 184 |
3/3✓ Branch 267 → 268 taken 337 times.
✓ Branch 267 → 273 taken 71 times.
✓ Branch 270 → 271 taken 337 times.
|
745 | if (!full) changed.addBindValue(request.folderId); |
| 185 |
2/5✓ Branch 273 → 274 taken 408 times.
✗ Branch 274 → 275 not taken.
✓ Branch 274 → 281 taken 408 times.
✗ Branch 275 → 276 not taken.
✗ Branch 276 → 277 not taken.
|
408 | if (!changed.exec()) { m_error=changed.lastError().text(); return false; } |
| 186 |
2/2✓ Branch 281 → 282 taken 408 times.
✓ Branch 282 → 283 taken 408 times.
|
408 | addTerm.prepare("INSERT OR IGNORE INTO thread_new_terms VALUES(?,?)"); |
| 187 |
2/2✓ Branch 284 → 285 taken 408 times.
✓ Branch 285 → 286 taken 408 times.
|
408 | addFallback.prepare("INSERT OR IGNORE INTO thread_new_fallback VALUES(?,?,?,?,?)"); |
| 188 |
3/3✓ Branch 368 → 369 taken 142880 times.
✓ Branch 369 → 288 taken 142472 times.
✓ Branch 369 → 370 taken 408 times.
|
142880 | while (changed.next()) { |
| 189 |
1/2✗ Branch 289 → 290 not taken.
✓ Branch 289 → 291 taken 142472 times.
|
142472 | if (check()) { result.outcome=ThreadProjectionPage::Outcome::Cancelled; return false; } |
| 190 |
1/1✓ Branch 291 → 292 taken 142472 times.
|
142472 | const auto header=readHeader(changed, request.folderId, DateZone::Utc); |
| 191 |
5/5✓ Branch 292 → 293 taken 142472 times.
✓ Branch 293 → 294 taken 142472 times.
✓ Branch 294 → 295 taken 142472 times.
✓ Branch 315 → 296 taken 261054 times.
✓ Branch 315 → 316 taken 142472 times.
|
403526 | for (const auto &key : ThreadBuilder::dependencyKeys(header)) { |
| 192 |
2/2✓ Branch 299 → 300 taken 261054 times.
✓ Branch 303 → 304 taken 261054 times.
|
261054 | addTerm.bindValue(0, key); addTerm.bindValue(1, header.uid); |
| 193 |
2/5✓ Branch 305 → 306 taken 261054 times.
✗ Branch 306 → 307 not taken.
✓ Branch 306 → 313 taken 261054 times.
✗ Branch 307 → 308 not taken.
✗ Branch 308 → 309 not taken.
|
261054 | if (!addTerm.exec()) { m_error=addTerm.lastError().text(); return false; } |
| 194 | 142472 | } | |
| 195 |
5/5✓ Branch 320 → 322 taken 142472 times.
✓ Branch 322 → 323 taken 142472 times.
✓ Branch 323 → 324 taken 142472 times.
✓ Branch 356 → 325 taken 275344 times.
✓ Branch 356 → 357 taken 142472 times.
|
417816 | for (const auto &candidate : ThreadBuilder::fallbackCandidates(header)) { |
| 196 |
1/1✓ Branch 328 → 329 taken 275344 times.
|
275344 | addFallback.bindValue(0, candidate.subject); |
| 197 |
1/1✓ Branch 332 → 333 taken 275344 times.
|
275344 | addFallback.bindValue(1, candidate.participant); |
| 198 |
1/1✓ Branch 336 → 337 taken 275344 times.
|
275344 | addFallback.bindValue(2, int(candidate.reply)); |
| 199 |
1/1✓ Branch 340 → 341 taken 275344 times.
|
275344 | addFallback.bindValue(3, candidate.date); |
| 200 |
1/1✓ Branch 344 → 345 taken 275344 times.
|
275344 | addFallback.bindValue(4, header.uid); |
| 201 |
2/5✓ Branch 346 → 347 taken 275344 times.
✗ Branch 347 → 348 not taken.
✓ Branch 347 → 354 taken 275344 times.
✗ Branch 348 → 349 not taken.
✗ Branch 349 → 350 not taken.
|
275344 | if (!addFallback.exec()) { m_error=addFallback.lastError().text(); return false; } |
| 202 | 142472 | } | |
| 203 | 142472 | } | |
| 204 |
1/1✓ Branch 370 → 371 taken 408 times.
|
408 | changed.finish(); |
| 205 |
2/2✓ Branch 371 → 372 taken 71 times.
✓ Branch 371 → 411 taken 337 times.
|
408 | if (full) { |
| 206 | // The full snapshot already has every dependency key in the TEMP index. | ||
| 207 | // Stage them in one SQLite operation instead of parsing every header and | ||
| 208 | // executing one INSERT per key again while writing projection rows. | ||
| 209 |
5/16✓ Branch 394 → 395 taken 71 times.
✗ Branch 394 → 396 not taken.
✓ Branch 396 → 397 taken 71 times.
✗ Branch 396 → 400 not taken.
✓ Branch 398 → 399 taken 71 times.
✓ Branch 398 → 400 taken 71 times.
✗ Branch 400 → 401 not taken.
✓ Branch 400 → 402 taken 71 times.
✗ Branch 1549 → 1550 not taken.
✗ Branch 1549 → 1551 not taken.
✗ Branch 1553 → 1554 not taken.
✗ Branch 1553 → 1555 not taken.
✗ Branch 1557 → 1558 not taken.
✗ Branch 1557 → 1561 not taken.
✗ Branch 1559 → 1560 not taken.
✗ Branch 1559 → 1561 not taken.
|
142 | if (!exec("INSERT INTO thread_stage_terms SELECT ?,term,uid FROM thread_new_terms", |
| 210 |
3/4✓ Branch 374 → 375 taken 71 times.
✓ Branch 375 → 376 taken 71 times.
✓ Branch 376 → 377 taken 71 times.
✗ Branch 376 → 382 not taken.
|
142 | {request.folderId}) || |
| 211 |
9/22✓ Branch 379 → 380 taken 71 times.
✓ Branch 380 → 381 taken 71 times.
✗ Branch 381 → 382 not taken.
✓ Branch 381 → 383 taken 71 times.
✓ Branch 384 → 385 taken 71 times.
✗ Branch 384 → 386 not taken.
✓ Branch 386 → 387 taken 71 times.
✗ Branch 386 → 388 not taken.
✓ Branch 388 → 389 taken 71 times.
✗ Branch 388 → 392 not taken.
✓ Branch 390 → 391 taken 71 times.
✓ Branch 390 → 392 taken 71 times.
✓ Branch 392 → 393 taken 71 times.
✗ Branch 392 → 394 not taken.
✗ Branch 1529 → 1530 not taken.
✗ Branch 1529 → 1531 not taken.
✗ Branch 1533 → 1534 not taken.
✗ Branch 1533 → 1535 not taken.
✗ Branch 1537 → 1538 not taken.
✗ Branch 1537 → 1541 not taken.
✗ Branch 1539 → 1540 not taken.
✗ Branch 1539 → 1541 not taken.
|
213 | !exec("INSERT INTO thread_stage_fallback SELECT ?,subject,participant,reply,date,uid FROM thread_new_fallback", |
| 212 | ✗ | {request.folderId})) return false; | |
| 213 |
2/5✓ Branch 402 → 403 taken 71 times.
✗ Branch 403 → 404 not taken.
✓ Branch 403 → 410 taken 71 times.
✗ Branch 404 → 405 not taken.
✗ Branch 405 → 406 not taken.
|
71 | if (!m_db.commit()) { m_error=m_db.lastError().text(); return false; } |
| 214 | 71 | rollback.dismiss(); | |
| 215 | } | ||
| 216 | 408 | const QString fallbackMatch = QStringLiteral("c.subject=f.subject AND c.participant=f.participant " | |
| 217 | "AND c.reply=1-f.reply AND c.date BETWEEN f.date-(?*f.reply) " | ||
| 218 | "AND f.date+(?*(1-f.reply))"); | ||
| 219 |
2/2✓ Branch 415 → 416 taken 408 times.
✓ Branch 416 → 417 taken 408 times.
|
408 | QSqlQuery expandNewFallback(m_db), expandOldFallback(m_db); |
| 220 |
3/4✓ Branch 417 → 418 taken 408 times.
✓ Branch 418 → 419 taken 408 times.
✗ Branch 420 → 421 not taken.
✓ Branch 420 → 427 taken 408 times.
|
408 | if (!expandNewFallback.prepare("INSERT OR IGNORE INTO thread_work(uid) SELECT c.uid FROM thread_fallback_frontier f CROSS JOIN thread_new_fallback c ON " + fallbackMatch)) { |
| 221 | ✗ | m_error=expandNewFallback.lastError().text(); return false; | |
| 222 | } | ||
| 223 |
8/12✓ Branch 427 → 428 taken 337 times.
✓ Branch 427 → 432 taken 71 times.
✓ Branch 428 → 429 taken 337 times.
✓ Branch 429 → 430 taken 337 times.
✗ Branch 430 → 431 not taken.
✓ Branch 430 → 432 taken 337 times.
✓ Branch 433 → 434 taken 337 times.
✓ Branch 433 → 435 taken 71 times.
✗ Branch 435 → 436 not taken.
✓ Branch 435 → 442 taken 408 times.
✗ Branch 1580 → 1581 not taken.
✗ Branch 1580 → 1582 not taken.
|
408 | if (!full && !expandOldFallback.prepare("INSERT OR IGNORE INTO thread_work(uid) SELECT c.uid FROM thread_fallback_frontier f CROSS JOIN thread_fallback_candidates c ON c.folder_id=? AND " + fallbackMatch)) { |
| 224 | ✗ | m_error=expandOldFallback.lastError().text(); return false; | |
| 225 | } | ||
| 226 | 408 | bool finished=false; | |
| 227 |
2/2✓ Branch 1053 → 443 taken 487 times.
✓ Branch 1053 → 1054 taken 337 times.
|
824 | while (!finished) { |
| 228 |
2/2✓ Branch 443 → 444 taken 150 times.
✓ Branch 443 → 493 taken 337 times.
|
487 | if (full) { |
| 229 |
5/12✓ Branch 445 → 446 taken 150 times.
✓ Branch 446 → 447 taken 150 times.
✓ Branch 447 → 448 taken 150 times.
✗ Branch 447 → 456 not taken.
✓ Branch 468 → 469 taken 150 times.
✗ Branch 468 → 470 not taken.
✗ Branch 470 → 471 not taken.
✓ Branch 470 → 472 taken 150 times.
✗ Branch 1605 → 1606 not taken.
✗ Branch 1605 → 1607 not taken.
✗ Branch 1609 → 1610 not taken.
✗ Branch 1609 → 1611 not taken.
|
600 | if (!exec("DELETE FROM thread_work") || |
| 230 |
5/15✓ Branch 454 → 455 taken 150 times.
✗ Branch 455 → 456 not taken.
✓ Branch 455 → 457 taken 150 times.
✓ Branch 462 → 463 taken 150 times.
✗ Branch 462 → 464 not taken.
✓ Branch 464 → 465 taken 150 times.
✗ Branch 464 → 466 not taken.
✓ Branch 466 → 467 taken 150 times.
✗ Branch 466 → 468 not taken.
✗ Branch 1593 → 1594 not taken.
✗ Branch 1593 → 1595 not taken.
✗ Branch 1597 → 1598 not taken.
✗ Branch 1597 → 1599 not taken.
✗ Branch 1601 → 1602 not taken.
✗ Branch 1601 → 1603 not taken.
|
600 | !exec(QStringLiteral("INSERT INTO thread_work(uid) SELECT uid FROM thread_remaining ORDER BY uid LIMIT %1") |
| 231 |
3/7✓ Branch 453 → 454 taken 150 times.
✓ Branch 458 → 459 taken 150 times.
✗ Branch 458 → 460 not taken.
✓ Branch 460 → 461 taken 150 times.
✗ Branch 460 → 462 not taken.
✗ Branch 1588 → 1589 not taken.
✗ Branch 1588 → 1590 not taken.
|
300 | .arg(FullBuildSeedBatch))) return false; |
| 232 |
1/1✓ Branch 472 → 473 taken 150 times.
|
150 | QSqlQuery remaining(m_db); |
| 233 |
3/6✓ Branch 473 → 474 taken 150 times.
✓ Branch 474 → 475 taken 150 times.
✗ Branch 476 → 477 not taken.
✓ Branch 476 → 483 taken 150 times.
✗ Branch 477 → 478 not taken.
✗ Branch 478 → 479 not taken.
|
150 | if (!remaining.exec("SELECT 1 FROM thread_work LIMIT 1")) { m_error=remaining.lastError().text(); return false; } |
| 234 |
3/3✓ Branch 483 → 484 taken 150 times.
✓ Branch 484 → 485 taken 71 times.
✓ Branch 484 → 486 taken 79 times.
|
150 | if (!remaining.next()) break; |
| 235 | 150 | } | |
| 236 |
3/4✓ Branch 494 → 495 taken 416 times.
✓ Branch 495 → 496 taken 416 times.
✗ Branch 498 → 499 not taken.
✓ Branch 498 → 500 taken 416 times.
|
416 | if (!exec("DELETE FROM thread_seen_terms")) return false; |
| 237 | while (true) { | ||
| 238 |
1/2✗ Branch 501 → 502 not taken.
✓ Branch 501 → 503 taken 1631 times.
|
1631 | if (check()) { result.outcome=ThreadProjectionPage::Outcome::Cancelled; return false; } |
| 239 |
2/8✓ Branch 535 → 536 taken 1631 times.
✗ Branch 535 → 537 not taken.
✗ Branch 537 → 538 not taken.
✓ Branch 537 → 539 taken 1631 times.
✗ Branch 1653 → 1654 not taken.
✗ Branch 1653 → 1655 not taken.
✗ Branch 1657 → 1658 not taken.
✗ Branch 1657 → 1659 not taken.
|
1631 | if (!exec("DELETE FROM thread_frontier") || |
| 240 |
5/12✓ Branch 508 → 509 taken 1631 times.
✓ Branch 509 → 510 taken 1631 times.
✓ Branch 510 → 511 taken 1631 times.
✗ Branch 510 → 519 not taken.
✓ Branch 531 → 532 taken 1631 times.
✗ Branch 531 → 533 not taken.
✓ Branch 533 → 534 taken 1631 times.
✗ Branch 533 → 535 not taken.
✗ Branch 1645 → 1646 not taken.
✗ Branch 1645 → 1647 not taken.
✗ Branch 1649 → 1650 not taken.
✗ Branch 1649 → 1651 not taken.
|
3262 | !exec("DELETE FROM thread_fallback_frontier") || |
| 241 |
8/16✓ Branch 504 → 505 taken 1631 times.
✓ Branch 505 → 506 taken 1631 times.
✓ Branch 506 → 507 taken 1631 times.
✗ Branch 506 → 519 not taken.
✓ Branch 512 → 513 taken 1631 times.
✓ Branch 513 → 514 taken 1631 times.
✓ Branch 514 → 515 taken 1631 times.
✗ Branch 514 → 519 not taken.
✓ Branch 527 → 528 taken 1631 times.
✗ Branch 527 → 529 not taken.
✓ Branch 529 → 530 taken 1631 times.
✗ Branch 529 → 531 not taken.
✗ Branch 1637 → 1638 not taken.
✗ Branch 1637 → 1639 not taken.
✗ Branch 1641 → 1642 not taken.
✗ Branch 1641 → 1643 not taken.
|
6524 | !exec("INSERT OR IGNORE INTO thread_frontier SELECT t.term FROM thread_new_terms t JOIN thread_work w ON w.uid=t.uid WHERE w.processed=0 AND NOT EXISTS(SELECT 1 FROM thread_seen_terms s WHERE s.term=t.term)") || |
| 242 |
6/14✓ Branch 516 → 517 taken 1631 times.
✓ Branch 517 → 518 taken 1631 times.
✗ Branch 518 → 519 not taken.
✓ Branch 518 → 520 taken 1631 times.
✓ Branch 521 → 522 taken 1631 times.
✗ Branch 521 → 523 not taken.
✓ Branch 523 → 524 taken 1631 times.
✗ Branch 523 → 525 not taken.
✓ Branch 525 → 526 taken 1631 times.
✗ Branch 525 → 527 not taken.
✗ Branch 1629 → 1630 not taken.
✗ Branch 1629 → 1631 not taken.
✗ Branch 1633 → 1634 not taken.
✗ Branch 1633 → 1635 not taken.
|
3262 | !exec("INSERT OR IGNORE INTO thread_fallback_frontier SELECT c.* FROM thread_new_fallback c JOIN thread_work w ON w.uid=c.uid WHERE w.processed=0")) return false; |
| 243 |
14/24✓ Branch 539 → 540 taken 1552 times.
✓ Branch 539 → 546 taken 79 times.
✓ Branch 542 → 543 taken 1552 times.
✓ Branch 543 → 544 taken 1552 times.
✗ Branch 544 → 545 not taken.
✓ Branch 544 → 546 taken 1552 times.
✓ Branch 547 → 548 taken 1552 times.
✓ Branch 547 → 549 taken 79 times.
✓ Branch 549 → 550 taken 1552 times.
✓ Branch 549 → 551 taken 79 times.
✓ Branch 551 → 552 taken 1552 times.
✓ Branch 551 → 555 taken 79 times.
✓ Branch 553 → 554 taken 1552 times.
✓ Branch 553 → 555 taken 1552 times.
✗ Branch 555 → 556 not taken.
✓ Branch 555 → 557 taken 1631 times.
✗ Branch 1661 → 1662 not taken.
✗ Branch 1661 → 1663 not taken.
✗ Branch 1665 → 1666 not taken.
✗ Branch 1665 → 1667 not taken.
✗ Branch 1669 → 1670 not taken.
✗ Branch 1669 → 1673 not taken.
✗ Branch 1671 → 1672 not taken.
✗ Branch 1671 → 1673 not taken.
|
4735 | if (!full && !exec("INSERT OR IGNORE INTO thread_frontier SELECT t.term FROM thread_work w CROSS JOIN thread_terms t ON t.folder_id=? AND t.uid=w.uid WHERE w.processed=0 AND NOT EXISTS(SELECT 1 FROM thread_seen_terms s WHERE s.term=t.term)", {request.folderId})) return false; |
| 244 |
14/24✓ Branch 557 → 558 taken 1552 times.
✓ Branch 557 → 564 taken 79 times.
✓ Branch 560 → 561 taken 1552 times.
✓ Branch 561 → 562 taken 1552 times.
✗ Branch 562 → 563 not taken.
✓ Branch 562 → 564 taken 1552 times.
✓ Branch 565 → 566 taken 1552 times.
✓ Branch 565 → 567 taken 79 times.
✓ Branch 567 → 568 taken 1552 times.
✓ Branch 567 → 569 taken 79 times.
✓ Branch 569 → 570 taken 1552 times.
✓ Branch 569 → 573 taken 79 times.
✓ Branch 571 → 572 taken 1552 times.
✓ Branch 571 → 573 taken 1552 times.
✗ Branch 573 → 574 not taken.
✓ Branch 573 → 575 taken 1631 times.
✗ Branch 1681 → 1682 not taken.
✗ Branch 1681 → 1683 not taken.
✗ Branch 1685 → 1686 not taken.
✗ Branch 1685 → 1687 not taken.
✗ Branch 1689 → 1690 not taken.
✗ Branch 1689 → 1693 not taken.
✗ Branch 1691 → 1692 not taken.
✗ Branch 1691 → 1693 not taken.
|
4735 | if (!full && !exec("INSERT OR IGNORE INTO thread_fallback_frontier SELECT c.subject,c.participant,c.reply,c.date,c.uid FROM thread_work w CROSS JOIN thread_fallback_candidates c ON c.folder_id=? AND c.uid=w.uid WHERE w.processed=0", {request.folderId})) return false; |
| 245 |
5/12✓ Branch 576 → 577 taken 1631 times.
✓ Branch 577 → 578 taken 1631 times.
✓ Branch 578 → 579 taken 1631 times.
✗ Branch 578 → 583 not taken.
✓ Branch 591 → 592 taken 1631 times.
✗ Branch 591 → 593 not taken.
✗ Branch 593 → 594 not taken.
✓ Branch 593 → 595 taken 1631 times.
✗ Branch 1709 → 1710 not taken.
✗ Branch 1709 → 1711 not taken.
✗ Branch 1713 → 1714 not taken.
✗ Branch 1713 → 1715 not taken.
|
4893 | if (!exec("UPDATE thread_work SET processed=1 WHERE processed=0") || |
| 246 |
6/14✓ Branch 580 → 581 taken 1631 times.
✓ Branch 581 → 582 taken 1631 times.
✗ Branch 582 → 583 not taken.
✓ Branch 582 → 584 taken 1631 times.
✓ Branch 585 → 586 taken 1631 times.
✗ Branch 585 → 587 not taken.
✓ Branch 587 → 588 taken 1631 times.
✗ Branch 587 → 589 not taken.
✓ Branch 589 → 590 taken 1631 times.
✗ Branch 589 → 591 not taken.
✗ Branch 1701 → 1702 not taken.
✗ Branch 1701 → 1703 not taken.
✗ Branch 1705 → 1706 not taken.
✗ Branch 1705 → 1707 not taken.
|
3262 | !exec("INSERT OR IGNORE INTO thread_seen_terms SELECT term FROM thread_frontier")) return false; |
| 247 |
1/1✓ Branch 595 → 596 taken 1631 times.
|
1631 | QSqlQuery expand(m_db); |
| 248 |
2/2✓ Branch 596 → 597 taken 1631 times.
✓ Branch 597 → 598 taken 1631 times.
|
1631 | expand.prepare("INSERT OR IGNORE INTO thread_work(uid) SELECT t.uid FROM thread_frontier f CROSS JOIN thread_new_terms t ON t.term=f.term"); |
| 249 |
2/5✓ Branch 599 → 600 taken 1631 times.
✗ Branch 600 → 601 not taken.
✓ Branch 600 → 607 taken 1631 times.
✗ Branch 601 → 602 not taken.
✗ Branch 602 → 603 not taken.
|
1631 | if (!expand.exec()) { m_error=expand.lastError().text(); return false; } |
| 250 |
1/1✓ Branch 607 → 608 taken 1631 times.
|
1631 | bool grew=expand.numRowsAffected()>0; |
| 251 |
2/2✓ Branch 608 → 609 taken 1552 times.
✓ Branch 608 → 626 taken 79 times.
|
1631 | if (!full) { |
| 252 |
2/2✓ Branch 609 → 610 taken 1552 times.
✓ Branch 610 → 611 taken 1552 times.
|
1552 | expand.prepare("INSERT OR IGNORE INTO thread_work(uid) SELECT t.uid FROM thread_frontier f CROSS JOIN thread_terms t ON t.folder_id=? AND t.term=f.term"); |
| 253 |
1/1✓ Branch 614 → 615 taken 1552 times.
|
1552 | expand.addBindValue(request.folderId); |
| 254 |
2/5✓ Branch 616 → 617 taken 1552 times.
✗ Branch 617 → 618 not taken.
✓ Branch 617 → 624 taken 1552 times.
✗ Branch 618 → 619 not taken.
✗ Branch 619 → 620 not taken.
|
1552 | if (!expand.exec()) { m_error=expand.lastError().text(); return false; } |
| 255 |
1/1✓ Branch 624 → 625 taken 1552 times.
|
1552 | grew |= expand.numRowsAffected()>0; |
| 256 | } | ||
| 257 | // A subject edge exists only between an unprefixed root and a reply | ||
| 258 | // with an overlapping participant in the builder's exact date window. | ||
| 259 | // Frontier rows include both old and new keys during incremental work, | ||
| 260 | // so a removed link still invalidates every peer of the old component. | ||
| 261 |
1/1✓ Branch 628 → 629 taken 1631 times.
|
1631 | expandNewFallback.bindValue(0, ThreadBuilder::FallbackWindowSeconds); |
| 262 |
1/1✓ Branch 632 → 633 taken 1631 times.
|
1631 | expandNewFallback.bindValue(1, ThreadBuilder::FallbackWindowSeconds); |
| 263 |
2/5✓ Branch 634 → 635 taken 1631 times.
✗ Branch 635 → 636 not taken.
✓ Branch 635 → 642 taken 1631 times.
✗ Branch 636 → 637 not taken.
✗ Branch 637 → 638 not taken.
|
1631 | if (!expandNewFallback.exec()) { m_error=expandNewFallback.lastError().text(); return false; } |
| 264 |
1/1✓ Branch 642 → 643 taken 1631 times.
|
1631 | grew |= expandNewFallback.numRowsAffected()>0; |
| 265 |
2/2✓ Branch 643 → 644 taken 1552 times.
✓ Branch 643 → 666 taken 79 times.
|
1631 | if (!full) { |
| 266 |
1/1✓ Branch 646 → 647 taken 1552 times.
|
1552 | expandOldFallback.bindValue(0, request.folderId); |
| 267 |
1/1✓ Branch 650 → 651 taken 1552 times.
|
1552 | expandOldFallback.bindValue(1, ThreadBuilder::FallbackWindowSeconds); |
| 268 |
1/1✓ Branch 654 → 655 taken 1552 times.
|
1552 | expandOldFallback.bindValue(2, ThreadBuilder::FallbackWindowSeconds); |
| 269 |
2/5✓ Branch 656 → 657 taken 1552 times.
✗ Branch 657 → 658 not taken.
✓ Branch 657 → 664 taken 1552 times.
✗ Branch 658 → 659 not taken.
✗ Branch 659 → 660 not taken.
|
1552 | if (!expandOldFallback.exec()) { m_error=expandOldFallback.lastError().text(); return false; } |
| 270 |
1/1✓ Branch 664 → 665 taken 1552 times.
|
1552 | grew |= expandOldFallback.numRowsAffected()>0; |
| 271 | } | ||
| 272 |
2/2✓ Branch 666 → 667 taken 416 times.
✓ Branch 666 → 668 taken 1215 times.
|
1631 | if (!grew) break; |
| 273 | 2846 | } | |
| 274 | 416 | QList<MailHeader> headers; | |
| 275 |
1/1✓ Branch 674 → 675 taken 416 times.
|
416 | QSqlQuery read(m_db); |
| 276 |
2/2✓ Branch 678 → 679 taken 416 times.
✓ Branch 681 → 682 taken 416 times.
|
1248 | read.prepare("SELECT " + HeaderColumns + (full ? " FROM thread_source h JOIN thread_work w ON w.uid=h.uid WHERE h.folder_id=?" |
| 277 |
4/4✓ Branch 675 → 676 taken 79 times.
✓ Branch 675 → 677 taken 337 times.
✓ Branch 679 → 680 taken 416 times.
✓ Branch 680 → 681 taken 416 times.
|
1664 | : " FROM thread_work w CROSS JOIN headers h ON h.folder_id=? AND h.uid=w.uid") + " ORDER BY h.uid"); |
| 278 |
1/1✓ Branch 687 → 688 taken 416 times.
|
416 | read.addBindValue(request.folderId); |
| 279 |
2/5✓ Branch 689 → 690 taken 416 times.
✗ Branch 690 → 691 not taken.
✓ Branch 690 → 697 taken 416 times.
✗ Branch 691 → 692 not taken.
✗ Branch 692 → 693 not taken.
|
416 | if (!read.exec()) { m_error=read.lastError().text(); return false; } |
| 280 |
3/3✓ Branch 705 → 706 taken 220191 times.
✓ Branch 706 → 698 taken 219775 times.
✓ Branch 706 → 707 taken 416 times.
|
220191 | while (read.next()) { |
| 281 |
1/2✗ Branch 699 → 700 not taken.
✓ Branch 699 → 701 taken 219775 times.
|
219775 | if (check()) { result.outcome=ThreadProjectionPage::Outcome::Cancelled; return false; } |
| 282 |
2/2✓ Branch 701 → 702 taken 219775 times.
✓ Branch 702 → 703 taken 219775 times.
|
219775 | headers.append(readHeader(read, request.folderId, DateZone::Utc)); |
| 283 | } | ||
| 284 |
1/1✓ Branch 707 → 708 taken 416 times.
|
416 | read.finish(); |
| 285 |
2/2✓ Branch 708 → 709 taken 337 times.
✓ Branch 708 → 718 taken 79 times.
|
416 | if (!full) { |
| 286 |
2/5✓ Branch 709 → 710 taken 337 times.
✗ Branch 710 → 711 not taken.
✓ Branch 710 → 717 taken 337 times.
✗ Branch 711 → 712 not taken.
✗ Branch 712 → 713 not taken.
|
337 | if (!m_db.commit()) { m_error=m_db.lastError().text(); return false; } |
| 287 | 337 | rollback.dismiss(); | |
| 288 | } | ||
| 289 | 416 | result.rebuiltHeaders += headers.size(); | |
| 290 | 416 | result.largestBuildBatch = qMax(result.largestBuildBatch, headers.size()); | |
| 291 |
1/1✓ Branch 722 → 723 taken 416 times.
|
416 | auto roots=ThreadBuilder::buildThreads(headers, check); |
| 292 |
1/1✓ Branch 724 → 725 taken 416 times.
|
416 | headers.clear(); |
| 293 |
1/2✗ Branch 726 → 727 not taken.
✓ Branch 726 → 728 taken 416 times.
|
416 | if (check()) { result.outcome=ThreadProjectionPage::Outcome::Cancelled; return false; } |
| 294 | // Every seed batch expands to complete connected components. A single | ||
| 295 | // pathological component can be larger; splitting it would change threads. | ||
| 296 |
2/5✓ Branch 728 → 729 taken 416 times.
✗ Branch 729 → 730 not taken.
✓ Branch 729 → 736 taken 416 times.
✗ Branch 730 → 731 not taken.
✗ Branch 731 → 732 not taken.
|
416 | if (!m_db.transaction()) { m_error=m_db.lastError().text(); return false; } |
| 297 | 416 | auto rollbackStage=qScopeGuard([this] { m_db.rollback(); }); | |
| 298 |
3/3✓ Branch 737 → 738 taken 416 times.
✓ Branch 738 → 739 taken 416 times.
✓ Branch 739 → 740 taken 416 times.
|
416 | QSqlQuery insert(m_db), term(m_db), fallbackTerm(m_db); |
| 299 |
2/2✓ Branch 740 → 741 taken 416 times.
✓ Branch 741 → 742 taken 416 times.
|
416 | insert.prepare("INSERT INTO thread_stage VALUES(?,?,?,?,?,?,?,?,?,?,?,?)"); |
| 300 |
2/2✓ Branch 743 → 744 taken 416 times.
✓ Branch 744 → 745 taken 416 times.
|
416 | term.prepare("INSERT INTO thread_stage_terms VALUES(?,?,?)"); |
| 301 |
2/2✓ Branch 746 → 747 taken 416 times.
✓ Branch 747 → 748 taken 416 times.
|
416 | fallbackTerm.prepare("INSERT INTO thread_stage_fallback VALUES(?,?,?,?,?,?)"); |
| 302 | struct Pending { ThreadNode *node; QByteArray path; }; | ||
| 303 |
2/2✓ Branch 997 → 751 taken 37628 times.
✓ Branch 997 → 998 taken 416 times.
|
38044 | for (const auto &root : roots) { |
| 304 |
1/1✓ Branch 753 → 754 taken 37628 times.
|
37628 | QList<ThreadNode *> postorder{root.get()}; |
| 305 |
2/2✓ Branch 766 → 755 taken 219775 times.
✓ Branch 766 → 767 taken 37628 times.
|
257403 | for (qsizetype i = 0; i < postorder.size(); ++i) |
| 306 |
4/4✓ Branch 755 → 756 taken 219775 times.
✓ Branch 760 → 761 taken 182147 times.
✓ Branch 763 → 758 taken 182147 times.
✓ Branch 763 → 764 taken 219775 times.
|
401922 | for (const auto &child : postorder[i]->children) postorder.append(child.get()); |
| 307 |
4/4✓ Branch 779 → 780 taken 219775 times.
✓ Branch 781 → 782 taken 257403 times.
✓ Branch 782 → 768 taken 219775 times.
✓ Branch 782 → 783 taken 37628 times.
|
257403 | for (auto it = postorder.crbegin(); it != postorder.crend(); ++it) { |
| 308 |
1/1✓ Branch 768 → 769 taken 219775 times.
|
219775 | auto *node = *it; |
| 309 |
2/2✓ Branch 778 → 771 taken 182147 times.
✓ Branch 778 → 779 taken 219775 times.
|
401922 | for (const auto &child : node->children) { |
| 310 | 182147 | node->cachedDescendants += 1 + child->cachedDescendants; | |
| 311 | 182147 | node->cachedUnreadDescendants += int(!child->header.isSeen()) + child->cachedUnreadDescendants; | |
| 312 | } | ||
| 313 | } | ||
| 314 |
1/1✓ Branch 784 → 785 taken 37628 times.
|
37628 | const auto newest = root->newestDate(); |
| 315 |
2/3✓ Branch 785 → 786 taken 37628 times.
✓ Branch 786 → 787 taken 37628 times.
✗ Branch 786 → 788 not taken.
|
37628 | const int undated = newest.isValid() ? 0 : 1; |
| 316 |
3/4✓ Branch 789 → 790 taken 37628 times.
✓ Branch 790 → 791 taken 37628 times.
✗ Branch 790 → 793 not taken.
✓ Branch 791 → 792 taken 37628 times.
|
37628 | const qint64 newestOrder = newest.isValid() ? -newest.toSecsSinceEpoch() : 0; |
| 317 | 37628 | const qint64 rootUid = root->header.uid; | |
| 318 |
2/4✓ Branch 799 → 800 taken 37628 times.
✓ Branch 799 → 801 taken 37628 times.
✗ Branch 1811 → 1812 not taken.
✗ Branch 1811 → 1813 not taken.
|
75256 | QList<Pending> pending{{root.get(), QByteArray(1, '\0')}}; |
| 319 |
2/2✓ Branch 981 → 802 taken 219775 times.
✓ Branch 981 → 982 taken 37628 times.
|
257403 | while (!pending.isEmpty()) { |
| 320 |
1/2✗ Branch 803 → 804 not taken.
✓ Branch 803 → 805 taken 219775 times.
|
219775 | if (check()) { result.outcome = ThreadProjectionPage::Outcome::Cancelled; return false; } |
| 321 |
1/1✓ Branch 805 → 806 taken 219775 times.
|
219775 | const auto row = pending.takeLast(); |
| 322 | 219775 | const auto *node = row.node; | |
| 323 | 219775 | const auto &header = node->header; | |
| 324 | // Bind into the prepared statement directly. A million-row cold | ||
| 325 | // rebuild must not allocate a twelve-element QVariantList per mail. | ||
| 326 |
1/1✓ Branch 808 → 809 taken 219775 times.
|
219775 | insert.bindValue(0, request.folderId); |
| 327 |
1/1✓ Branch 812 → 813 taken 219775 times.
|
219775 | insert.bindValue(1, header.uid); |
| 328 |
3/3✓ Branch 815 → 816 taken 182147 times.
✓ Branch 815 → 817 taken 37628 times.
✓ Branch 819 → 820 taken 219775 times.
|
219775 | insert.bindValue(2, node->parent ? node->parent->header.uid : qint64(0)); |
| 329 |
1/1✓ Branch 823 → 824 taken 219775 times.
|
219775 | insert.bindValue(3, rootUid); |
| 330 |
1/1✓ Branch 827 → 828 taken 219775 times.
|
219775 | insert.bindValue(4, undated); |
| 331 |
1/1✓ Branch 831 → 832 taken 219775 times.
|
219775 | insert.bindValue(5, newestOrder); |
| 332 |
1/1✓ Branch 835 → 836 taken 219775 times.
|
219775 | insert.bindValue(6, row.path); |
| 333 |
1/1✓ Branch 839 → 840 taken 219775 times.
|
219775 | insert.bindValue(7, node->depth); |
| 334 |
1/1✓ Branch 843 → 844 taken 219775 times.
|
219775 | insert.bindValue(8, node->cachedDescendants); |
| 335 |
1/1✓ Branch 847 → 848 taken 219775 times.
|
219775 | insert.bindValue(9, node->cachedUnreadDescendants); |
| 336 | // subject_fold/sender_fold are NOT NULL: a header without Subject or | ||
| 337 | // From has a null QString, which QSqlQuery binds as SQL NULL. | ||
| 338 |
6/8✓ Branch 851 → 852 taken 3 times.
✓ Branch 851 → 855 taken 219772 times.
✓ Branch 855 → 856 taken 219772 times.
✓ Branch 857 → 858 taken 219775 times.
✓ Branch 860 → 861 taken 3 times.
✓ Branch 860 → 862 taken 219772 times.
✗ Branch 1864 → 1865 not taken.
✗ Branch 1864 → 1866 not taken.
|
219778 | insert.bindValue(10, header.subject.isEmpty() ? QStringLiteral("") : header.subject.toCaseFolded()); |
| 339 |
6/8✓ Branch 864 → 865 taken 3 times.
✓ Branch 864 → 868 taken 219772 times.
✓ Branch 868 → 869 taken 219772 times.
✓ Branch 870 → 871 taken 219775 times.
✓ Branch 873 → 874 taken 3 times.
✓ Branch 873 → 875 taken 219772 times.
✗ Branch 1875 → 1876 not taken.
✗ Branch 1875 → 1877 not taken.
|
219778 | insert.bindValue(11, header.from.isEmpty() ? QStringLiteral("") : header.from.toCaseFolded()); |
| 340 |
2/5✓ Branch 875 → 876 taken 219775 times.
✗ Branch 876 → 877 not taken.
✓ Branch 876 → 883 taken 219775 times.
✗ Branch 877 → 878 not taken.
✗ Branch 878 → 879 not taken.
|
219775 | if (!insert.exec()) { m_error = insert.lastError().text(); return false; } |
| 341 |
2/2✓ Branch 883 → 884 taken 78057 times.
✓ Branch 883 → 963 taken 141718 times.
|
219775 | if (!full) { |
| 342 |
5/5✓ Branch 884 → 885 taken 78057 times.
✓ Branch 885 → 886 taken 78057 times.
✓ Branch 886 → 887 taken 78057 times.
✓ Branch 911 → 888 taken 219299 times.
✓ Branch 911 → 912 taken 78057 times.
|
297356 | for (const auto &key : ThreadBuilder::dependencyKeys(header)) { |
| 343 |
3/3✓ Branch 891 → 892 taken 219299 times.
✓ Branch 895 → 896 taken 219299 times.
✓ Branch 899 → 900 taken 219299 times.
|
219299 | term.bindValue(0, request.folderId); term.bindValue(1, key); term.bindValue(2, header.uid); |
| 344 |
2/5✓ Branch 901 → 902 taken 219299 times.
✗ Branch 902 → 903 not taken.
✓ Branch 902 → 909 taken 219299 times.
✗ Branch 903 → 904 not taken.
✗ Branch 904 → 905 not taken.
|
219299 | if (!term.exec()) { m_error = term.lastError().text(); return false; } |
| 345 | 78057 | } | |
| 346 |
5/5✓ Branch 916 → 918 taken 78057 times.
✓ Branch 918 → 919 taken 78057 times.
✓ Branch 919 → 920 taken 78057 times.
✓ Branch 956 → 921 taken 139953 times.
✓ Branch 956 → 957 taken 78057 times.
|
218010 | for (const auto &candidate : ThreadBuilder::fallbackCandidates(header)) { |
| 347 |
1/1✓ Branch 924 → 925 taken 139953 times.
|
139953 | fallbackTerm.bindValue(0, request.folderId); |
| 348 |
1/1✓ Branch 928 → 929 taken 139953 times.
|
139953 | fallbackTerm.bindValue(1, candidate.subject); |
| 349 |
1/1✓ Branch 932 → 933 taken 139953 times.
|
139953 | fallbackTerm.bindValue(2, candidate.participant); |
| 350 |
1/1✓ Branch 936 → 937 taken 139953 times.
|
139953 | fallbackTerm.bindValue(3, int(candidate.reply)); |
| 351 |
1/1✓ Branch 940 → 941 taken 139953 times.
|
139953 | fallbackTerm.bindValue(4, candidate.date); |
| 352 |
1/1✓ Branch 944 → 945 taken 139953 times.
|
139953 | fallbackTerm.bindValue(5, header.uid); |
| 353 |
2/5✓ Branch 946 → 947 taken 139953 times.
✗ Branch 947 → 948 not taken.
✓ Branch 947 → 954 taken 139953 times.
✗ Branch 948 → 949 not taken.
✗ Branch 949 → 950 not taken.
|
139953 | if (!fallbackTerm.exec()) { m_error = fallbackTerm.lastError().text(); return false; } |
| 354 | 78057 | } | |
| 355 | } | ||
| 356 |
2/2✓ Branch 973 → 964 taken 182147 times.
✓ Branch 973 → 974 taken 219775 times.
|
401922 | for (quint32 i = 0; i < node->children.size(); ++i) |
| 357 |
1/1✓ Branch 968 → 969 taken 182147 times.
|
364294 | pending.append({node->children[i].get(), childPath(row.path, i+1)}); |
| 358 | 219775 | } | |
| 359 | 37628 | } | |
| 360 |
10/16✓ Branch 998 → 999 taken 79 times.
✓ Branch 998 → 1004 taken 337 times.
✓ Branch 1000 → 1001 taken 79 times.
✓ Branch 1001 → 1002 taken 79 times.
✗ Branch 1002 → 1003 not taken.
✓ Branch 1002 → 1004 taken 79 times.
✓ Branch 1005 → 1006 taken 79 times.
✓ Branch 1005 → 1007 taken 337 times.
✓ Branch 1007 → 1008 taken 79 times.
✓ Branch 1007 → 1009 taken 337 times.
✗ Branch 1009 → 1010 not taken.
✓ Branch 1009 → 1011 taken 416 times.
✗ Branch 1953 → 1954 not taken.
✗ Branch 1953 → 1955 not taken.
✗ Branch 1957 → 1958 not taken.
✗ Branch 1957 → 1959 not taken.
|
416 | if (full && !exec("DELETE FROM thread_remaining WHERE uid IN(SELECT uid FROM thread_work)")) return false; |
| 361 |
2/5✓ Branch 1011 → 1012 taken 416 times.
✗ Branch 1012 → 1013 not taken.
✓ Branch 1012 → 1019 taken 416 times.
✗ Branch 1013 → 1014 not taken.
✗ Branch 1014 → 1015 not taken.
|
416 | if (!m_db.commit()) { m_error=m_db.lastError().text(); return false; } |
| 362 | 416 | rollbackStage.dismiss(); | |
| 363 |
3/3✓ Branch 1021 → 1022 taken 5 times.
✓ Branch 1021 → 1023 taken 411 times.
✓ Branch 1022 → 1023 taken 5 times.
|
416 | if (m_batchObserver) m_batchObserver(result.rebuiltHeaders); |
| 364 | 416 | finished=!full; | |
| 365 | 416 | } | |
| 366 | // Publish the captured revision without retiring newer dirty entries. | ||
| 367 | // Concurrent mail changes then need only a bounded incremental catch-up; | ||
| 368 | // page admission still requires the current canonical revision. | ||
| 369 |
3/4✓ Branch 1055 → 1056 taken 408 times.
✓ Branch 1056 → 1057 taken 408 times.
✗ Branch 1059 → 1060 not taken.
✓ Branch 1059 → 1061 taken 408 times.
|
408 | if (!exec("BEGIN IMMEDIATE")) return false; |
| 370 | 409 | auto rollbackPublish = qScopeGuard([this] { m_db.rollback(); }); | |
| 371 |
1/1✓ Branch 1062 → 1063 taken 408 times.
|
408 | QSqlQuery revision(m_db); |
| 372 |
2/2✓ Branch 1063 → 1064 taken 408 times.
✓ Branch 1064 → 1065 taken 408 times.
|
408 | revision.prepare("SELECT f.uidvalidity,coalesce(m.revision,0) FROM folders f LEFT JOIN thread_projection_meta m ON m.folder_id=f.id WHERE f.id=? AND f.account=?"); |
| 373 |
2/2✓ Branch 1068 → 1069 taken 408 times.
✓ Branch 1072 → 1073 taken 408 times.
|
408 | revision.addBindValue(request.folderId); revision.addBindValue(request.account); |
| 374 |
2/5✓ Branch 1074 → 1075 taken 408 times.
✗ Branch 1075 → 1076 not taken.
✓ Branch 1075 → 1082 taken 408 times.
✗ Branch 1076 → 1077 not taken.
✗ Branch 1077 → 1078 not taken.
|
408 | if (!revision.exec()) { m_error = revision.lastError().text(); return false; } |
| 375 |
11/21✓ Branch 1082 → 1083 taken 408 times.
✓ Branch 1083 → 1084 taken 408 times.
✗ Branch 1083 → 1090 not taken.
✓ Branch 1084 → 1085 taken 408 times.
✓ Branch 1085 → 1086 taken 408 times.
✓ Branch 1086 → 1087 taken 408 times.
✗ Branch 1086 → 1090 not taken.
✓ Branch 1087 → 1088 taken 408 times.
✓ Branch 1088 → 1089 taken 408 times.
✗ Branch 1089 → 1090 not taken.
✓ Branch 1089 → 1091 taken 408 times.
✓ Branch 1092 → 1093 taken 408 times.
✗ Branch 1092 → 1094 not taken.
✓ Branch 1094 → 1095 taken 408 times.
✗ Branch 1094 → 1096 not taken.
✗ Branch 1096 → 1097 not taken.
✓ Branch 1096 → 1098 taken 408 times.
✗ Branch 2001 → 2002 not taken.
✗ Branch 2001 → 2003 not taken.
✗ Branch 2005 → 2006 not taken.
✗ Branch 2005 → 2007 not taken.
|
408 | if (!revision.next() || revision.value(0).toUInt() != request.uidValidity || revision.value(1).toLongLong() > result.revision) { |
| 376 | ✗ | result.outcome = ThreadProjectionPage::Outcome::Stale; return false; | |
| 377 | } | ||
| 378 |
1/1✓ Branch 1098 → 1099 taken 408 times.
|
408 | revision.finish(); |
| 379 | 2037 | for (const auto &table : {QStringLiteral("thread_projection"), QStringLiteral("thread_terms"), | |
| 380 |
2/2✓ Branch 1138 → 1114 taken 1222 times.
✓ Branch 1138 → 1139 taken 407 times.
|
5301 | QStringLiteral("thread_fallback_candidates")}) |
| 381 |
14/20✓ Branch 1116 → 1117 taken 211 times.
✓ Branch 1116 → 1118 taken 1011 times.
✓ Branch 1121 → 1122 taken 1222 times.
✓ Branch 1122 → 1123 taken 1222 times.
✓ Branch 1123 → 1124 taken 1222 times.
✓ Branch 1124 → 1125 taken 1222 times.
✓ Branch 1129 → 1130 taken 1011 times.
✓ Branch 1129 → 1131 taken 211 times.
✓ Branch 1133 → 1134 taken 1222 times.
✓ Branch 1133 → 1135 taken 1222 times.
✓ Branch 1135 → 1136 taken 1 time.
✓ Branch 1135 → 1137 taken 1221 times.
✓ Branch 1141 → 1142 taken 1224 times.
✓ Branch 1141 → 1144 taken 408 times.
✗ Branch 2019 → 2020 not taken.
✗ Branch 2019 → 2021 not taken.
✗ Branch 2028 → 2029 not taken.
✗ Branch 2028 → 2030 not taken.
✗ Branch 2039 → 2040 not taken.
✗ Branch 2039 → 2043 not taken.
|
5087 | if (!exec("DELETE FROM " + table + " WHERE folder_id=?" + (full ? QString{} : QStringLiteral(" AND uid IN(SELECT uid FROM thread_work)")), {request.folderId})) return false; |
| 382 |
2/8✓ Branch 1170 → 1171 taken 407 times.
✗ Branch 1170 → 1172 not taken.
✗ Branch 1172 → 1173 not taken.
✓ Branch 1172 → 1174 taken 407 times.
✗ Branch 2062 → 2063 not taken.
✗ Branch 2062 → 2064 not taken.
✗ Branch 2066 → 2067 not taken.
✗ Branch 2066 → 2068 not taken.
|
407 | if (!exec("INSERT INTO thread_projection SELECT * FROM thread_stage") || |
| 383 |
8/16✓ Branch 1146 → 1148 taken 407 times.
✓ Branch 1148 → 1149 taken 407 times.
✓ Branch 1149 → 1150 taken 407 times.
✗ Branch 1149 → 1158 not taken.
✓ Branch 1151 → 1152 taken 407 times.
✓ Branch 1152 → 1153 taken 407 times.
✓ Branch 1153 → 1154 taken 407 times.
✗ Branch 1153 → 1158 not taken.
✓ Branch 1166 → 1167 taken 407 times.
✗ Branch 1166 → 1168 not taken.
✓ Branch 1168 → 1169 taken 407 times.
✗ Branch 1168 → 1170 not taken.
✗ Branch 2054 → 2055 not taken.
✗ Branch 2054 → 2056 not taken.
✗ Branch 2058 → 2059 not taken.
✗ Branch 2058 → 2060 not taken.
|
1221 | !exec("INSERT OR IGNORE INTO thread_terms SELECT * FROM thread_stage_terms") || |
| 384 |
6/14✓ Branch 1155 → 1156 taken 407 times.
✓ Branch 1156 → 1157 taken 407 times.
✗ Branch 1157 → 1158 not taken.
✓ Branch 1157 → 1159 taken 407 times.
✓ Branch 1160 → 1161 taken 407 times.
✗ Branch 1160 → 1162 not taken.
✓ Branch 1162 → 1163 taken 407 times.
✗ Branch 1162 → 1164 not taken.
✓ Branch 1164 → 1165 taken 407 times.
✗ Branch 1164 → 1166 not taken.
✗ Branch 2046 → 2047 not taken.
✗ Branch 2046 → 2048 not taken.
✗ Branch 2050 → 2051 not taken.
✗ Branch 2050 → 2052 not taken.
|
814 | !exec("INSERT OR IGNORE INTO thread_fallback_candidates SELECT * FROM thread_stage_fallback")) return false; |
| 385 |
5/16✓ Branch 1213 → 1214 taken 407 times.
✗ Branch 1213 → 1215 not taken.
✓ Branch 1215 → 1216 taken 407 times.
✗ Branch 1215 → 1219 not taken.
✓ Branch 1217 → 1218 taken 407 times.
✓ Branch 1217 → 1219 taken 407 times.
✗ Branch 1219 → 1220 not taken.
✓ Branch 1219 → 1221 taken 407 times.
✗ Branch 2110 → 2111 not taken.
✗ Branch 2110 → 2112 not taken.
✗ Branch 2114 → 2115 not taken.
✗ Branch 2114 → 2116 not taken.
✗ Branch 2118 → 2119 not taken.
✗ Branch 2118 → 2122 not taken.
✗ Branch 2120 → 2121 not taken.
✗ Branch 2120 → 2122 not taken.
|
814 | if (!exec("INSERT OR IGNORE INTO thread_revisions(folder_id) VALUES(?)", {request.folderId}) || |
| 386 |
11/24✓ Branch 1176 → 1177 taken 407 times.
✓ Branch 1177 → 1178 taken 407 times.
✓ Branch 1178 → 1179 taken 407 times.
✗ Branch 1178 → 1193 not taken.
✓ Branch 1184 → 1185 taken 407 times.
✓ Branch 1185 → 1186 taken 407 times.
✓ Branch 1186 → 1187 taken 407 times.
✗ Branch 1186 → 1193 not taken.
✓ Branch 1205 → 1206 taken 407 times.
✗ Branch 1205 → 1207 not taken.
✓ Branch 1207 → 1208 taken 407 times.
✗ Branch 1207 → 1211 not taken.
✓ Branch 1209 → 1210 taken 1628 times.
✓ Branch 1209 → 1211 taken 407 times.
✓ Branch 1211 → 1212 taken 407 times.
✗ Branch 1211 → 1213 not taken.
✗ Branch 2090 → 2091 not taken.
✗ Branch 2090 → 2092 not taken.
✗ Branch 2094 → 2095 not taken.
✗ Branch 2094 → 2096 not taken.
✗ Branch 2098 → 2099 not taken.
✗ Branch 2098 → 2102 not taken.
✗ Branch 2100 → 2101 not taken.
✗ Branch 2100 → 2102 not taken.
|
3256 | !exec("INSERT OR REPLACE INTO thread_projection_meta VALUES(?,?,?,?)", {request.folderId, Version, result.revision, request.uidValidity}) || |
| 387 |
9/22✓ Branch 1190 → 1191 taken 407 times.
✓ Branch 1191 → 1192 taken 407 times.
✗ Branch 1192 → 1193 not taken.
✓ Branch 1192 → 1194 taken 407 times.
✓ Branch 1195 → 1196 taken 407 times.
✗ Branch 1195 → 1197 not taken.
✓ Branch 1197 → 1198 taken 407 times.
✗ Branch 1197 → 1199 not taken.
✓ Branch 1199 → 1200 taken 407 times.
✗ Branch 1199 → 1203 not taken.
✓ Branch 1201 → 1202 taken 814 times.
✓ Branch 1201 → 1203 taken 407 times.
✓ Branch 1203 → 1204 taken 407 times.
✗ Branch 1203 → 1205 not taken.
✗ Branch 2070 → 2071 not taken.
✗ Branch 2070 → 2072 not taken.
✗ Branch 2074 → 2075 not taken.
✗ Branch 2074 → 2076 not taken.
✗ Branch 2078 → 2079 not taken.
✗ Branch 2078 → 2082 not taken.
✗ Branch 2080 → 2081 not taken.
✗ Branch 2080 → 2082 not taken.
|
1628 | !exec("DELETE FROM thread_dirty WHERE folder_id=? AND revision<=?", {request.folderId, result.revision})) return false; |
| 388 |
2/5✓ Branch 1221 → 1222 taken 407 times.
✗ Branch 1222 → 1223 not taken.
✓ Branch 1222 → 1229 taken 407 times.
✗ Branch 1223 → 1224 not taken.
✗ Branch 1224 → 1225 not taken.
|
407 | if (!m_db.commit()) { m_error = m_db.lastError().text(); return false; } |
| 389 | 407 | rollbackPublish.dismiss(); | |
| 390 | 407 | return true; | |
| 391 | 227287 | } | |
| 392 | |||
| 393 | 1418 | ThreadProjectionPage ThreadProjectionIndex::page(const ThreadProjectionRequest &request, | |
| 394 | const std::atomic_bool &cancelled) { | ||
| 395 | 1418 | ThreadProjectionPage result; | |
| 396 |
3/3✓ Branch 2 → 3 taken 1418 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 11 taken 1417 times.
|
1419 | if (!m_db.isOpen()) { result.error = QStringLiteral("Thread index is not open"); return result; } |
| 397 |
4/4✓ Branch 11 → 12 taken 778 times.
✓ Branch 11 → 20 taken 639 times.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 20 taken 777 times.
|
1417 | if (request.after.valid && request.expectedRevision < 0) { |
| 398 | 1 | result.error = QStringLiteral("Thread page cursor requires its source revision"); return result; | |
| 399 | } | ||
| 400 |
2/2✓ Branch 20 → 21 taken 1416 times.
✓ Branch 21 → 22 taken 1416 times.
|
1416 | auto handle = m_db.driver()->handle(); |
| 401 |
7/13✓ Branch 22 → 23 taken 1416 times.
✓ Branch 23 → 24 taken 1416 times.
✗ Branch 23 → 28 not taken.
✓ Branch 24 → 25 taken 1416 times.
✓ Branch 25 → 26 taken 1416 times.
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 1416 times.
✓ Branch 30 → 31 taken 1416 times.
✗ Branch 30 → 32 not taken.
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 40 taken 1416 times.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
|
1416 | if (!handle.isValid() || QByteArray(handle.typeName()) != "sqlite3*") { |
| 402 | ✗ | result.error = QStringLiteral("Thread index requires an interruptible SQLite connection"); return result; | |
| 403 | } | ||
| 404 |
1/1✓ Branch 40 → 41 taken 1416 times.
|
1416 | auto *database = *static_cast<sqlite3 **>(handle.data()); |
| 405 |
1/1✓ Branch 42 → 43 taken 1416 times.
|
1416 | sqlite3_progress_handler(database, 1000, [](void *context) { |
| 406 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 192908 times.
|
192909 | return static_cast<const std::atomic_bool *>(context)->load(std::memory_order_relaxed) ? 1 : 0; |
| 407 | }, const_cast<std::atomic_bool *>(&cancelled)); | ||
| 408 | 2832 | const auto reset = qScopeGuard([database] { sqlite3_progress_handler(database, 0, nullptr, nullptr); }); | |
| 409 |
1/1✓ Branch 44 → 45 taken 1416 times.
|
1416 | result = readPage(request, cancelled); |
| 410 |
3/3✓ Branch 48 → 49 taken 2 times.
✓ Branch 48 → 51 taken 1414 times.
✓ Branch 49 → 50 taken 2 times.
|
1416 | if (cancelled.load()) { result.rows.clear(); result.outcome = ThreadProjectionPage::Outcome::Cancelled; } |
| 411 | 1416 | return result; | |
| 412 | 1416 | } | |
| 413 | |||
| 414 | 1416 | ThreadProjectionPage ThreadProjectionIndex::readPage(const ThreadProjectionRequest &request, | |
| 415 | const std::atomic_bool &cancelled) { | ||
| 416 | 1416 | ThreadProjectionPage result; | |
| 417 |
7/8✓ Branch 2 → 3 taken 1416 times.
✓ Branch 3 → 4 taken 1416 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 1416 times.
✓ Branch 5 → 6 taken 7 times.
✓ Branch 5 → 7 taken 1409 times.
✓ Branch 8 → 9 taken 7 times.
✓ Branch 8 → 11 taken 1409 times.
|
1416 | if (!m_db.isOpen() || !refresh(request, cancelled, result)) { result.error = m_error; return result; } |
| 418 |
2/5✓ Branch 11 → 12 taken 1409 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 19 taken 1409 times.
✗ Branch 13 → 14 not taken.
✗ Branch 14 → 15 not taken.
|
1409 | if (!m_db.transaction()) { result.error = m_db.lastError().text(); return result; } |
| 419 | 2742 | auto rollback = qScopeGuard([this] { m_db.rollback(); }); | |
| 420 |
1/1✓ Branch 20 → 21 taken 1409 times.
|
1409 | QSqlQuery revision(m_db); |
| 421 |
2/2✓ Branch 21 → 22 taken 1409 times.
✓ Branch 22 → 23 taken 1409 times.
|
1409 | revision.prepare("SELECT f.uidvalidity,r.revision FROM folders f JOIN thread_revisions r ON r.folder_id=f.id WHERE f.id=? AND f.account=?"); |
| 422 |
2/2✓ Branch 26 → 27 taken 1409 times.
✓ Branch 30 → 31 taken 1409 times.
|
1409 | revision.addBindValue(request.folderId); revision.addBindValue(request.account); |
| 423 |
2/5✓ Branch 32 → 33 taken 1409 times.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 40 taken 1409 times.
✗ Branch 34 → 35 not taken.
✗ Branch 35 → 36 not taken.
|
1409 | if (!revision.exec()) { result.error = revision.lastError().text(); return result; } |
| 424 |
7/11✓ Branch 40 → 41 taken 1409 times.
✓ Branch 41 → 42 taken 1409 times.
✗ Branch 41 → 49 not taken.
✓ Branch 42 → 43 taken 1409 times.
✓ Branch 43 → 44 taken 1409 times.
✓ Branch 44 → 45 taken 1409 times.
✗ Branch 44 → 49 not taken.
✓ Branch 55 → 56 taken 12 times.
✓ Branch 55 → 57 taken 1397 times.
✗ Branch 1127 → 1128 not taken.
✗ Branch 1127 → 1129 not taken.
|
4227 | if (!revision.next() || revision.value(0).toUInt() != request.uidValidity || |
| 425 |
8/12✓ Branch 45 → 46 taken 1409 times.
✓ Branch 46 → 47 taken 1409 times.
✓ Branch 47 → 48 taken 13 times.
✓ Branch 47 → 50 taken 1396 times.
✓ Branch 48 → 49 taken 12 times.
✓ Branch 48 → 50 taken 1 time.
✓ Branch 51 → 52 taken 1409 times.
✗ Branch 51 → 53 not taken.
✓ Branch 53 → 54 taken 1409 times.
✗ Branch 53 → 55 not taken.
✗ Branch 1123 → 1124 not taken.
✗ Branch 1123 → 1125 not taken.
|
2818 | (revision.value(1).toLongLong() != result.revision && !request.acceptOlderRevision)) { |
| 426 | 12 | result.outcome = ThreadProjectionPage::Outcome::Stale; return result; | |
| 427 | } | ||
| 428 |
1/1✓ Branch 57 → 58 taken 1397 times.
|
1397 | revision.finish(); |
| 429 | // Folder totals belong to the same revision-fenced read transaction as | ||
| 430 | // the published page. An older cache without aggregates keeps the page | ||
| 431 | // usable but cannot supply a no-extra-read folder bootstrap. | ||
| 432 |
1/1✓ Branch 58 → 59 taken 1397 times.
|
1397 | QSqlQuery counts(m_db); |
| 433 |
1/1✓ Branch 62 → 63 taken 1397 times.
|
1397 | counts.prepare(QStringLiteral("SELECT total,unread FROM header_counts WHERE folder_id=?")); |
| 434 |
1/1✓ Branch 67 → 68 taken 1397 times.
|
1397 | counts.addBindValue(request.folderId); |
| 435 |
2/3✓ Branch 69 → 70 taken 1397 times.
✓ Branch 70 → 71 taken 1397 times.
✗ Branch 70 → 85 not taken.
|
1397 | if (counts.exec()) { |
| 436 |
3/3✓ Branch 71 → 72 taken 1397 times.
✓ Branch 72 → 73 taken 1396 times.
✓ Branch 72 → 80 taken 1 time.
|
1397 | if (counts.next()) { |
| 437 |
2/2✓ Branch 73 → 74 taken 1396 times.
✓ Branch 74 → 75 taken 1396 times.
|
1396 | result.totalCount = counts.value(0).toInt(); |
| 438 |
2/2✓ Branch 76 → 77 taken 1396 times.
✓ Branch 77 → 78 taken 1396 times.
|
1396 | result.unreadCount = counts.value(1).toInt(); |
| 439 |
3/4✓ Branch 80 → 81 taken 1 time.
✓ Branch 81 → 82 taken 1 time.
✓ Branch 83 → 84 taken 1 time.
✗ Branch 83 → 85 not taken.
|
1 | } else if (!counts.lastError().isValid()) { |
| 440 | 1 | result.totalCount = 0; | |
| 441 | 1 | result.unreadCount = 0; | |
| 442 | } | ||
| 443 | } | ||
| 444 |
1/1✓ Branch 85 → 86 taken 1397 times.
|
1397 | counts.finish(); |
| 445 |
6/8✓ Branch 87 → 88 taken 1152 times.
✓ Branch 87 → 91 taken 245 times.
✓ Branch 88 → 89 taken 912 times.
✓ Branch 88 → 91 taken 240 times.
✓ Branch 89 → 90 taken 912 times.
✗ Branch 89 → 91 not taken.
✗ Branch 90 → 91 not taken.
✓ Branch 90 → 92 taken 912 times.
|
1397 | const bool filtered = !request.filter.isEmpty() || request.unread || request.starred || request.attachments; |
| 446 |
1/1✓ Branch 93 → 94 taken 1397 times.
|
1397 | const auto order = threadOrderColumns(request.sort); |
| 447 |
1/1✓ Branch 97 → 98 taken 1397 times.
|
1397 | const QString orderTuple = QStringLiteral("(%1,%2,p.root_uid,p.path)").arg(order.high, order.low); |
| 448 |
1/1✓ Branch 103 → 104 taken 1397 times.
|
1397 | const QString orderAscending = QStringLiteral("ORDER BY %1,%2,p.root_uid,p.path").arg(order.high, order.low); |
| 449 |
2/2✓ Branch 107 → 108 taken 1 time.
✓ Branch 107 → 115 taken 1396 times.
|
1398 | if (request.retainedUids.size() > 4096) { result.error = QStringLiteral("Too many retained unread rows"); return result; } |
| 450 | 1396 | const MatchScope matchScope{request.account, request.filter.toCaseFolded(), | |
| 451 | 1396 | request.folderId, result.revision, request.uidValidity, | |
| 452 | 1396 | request.unread, request.starred, request.attachments, | |
| 453 |
3/5✓ Branch 116 → 117 taken 1396 times.
✓ Branch 117 → 118 taken 265 times.
✓ Branch 117 → 119 taken 1131 times.
✗ Branch 1162 → 1163 not taken.
✗ Branch 1162 → 1164 not taken.
|
1396 | request.unread ? request.retainedUids : QSet<qint64>{}}; |
| 454 |
7/7✓ Branch 120 → 121 taken 484 times.
✓ Branch 120 → 125 taken 912 times.
✓ Branch 121 → 122 taken 450 times.
✓ Branch 121 → 124 taken 34 times.
✓ Branch 122 → 123 taken 450 times.
✓ Branch 123 → 124 taken 42 times.
✓ Branch 123 → 125 taken 408 times.
|
1396 | const bool rebuildMatches = filtered && (!m_matchReady || !(m_matchScope == matchScope)); |
| 455 |
2/2✓ Branch 126 → 127 taken 76 times.
✓ Branch 126 → 128 taken 1320 times.
|
1396 | qint64 matchCount = rebuildMatches ? -1 : m_matchCount; |
| 456 | // Retained rows (read while this unread view showed them) pass the | ||
| 457 | // unread condition; their uids are integers, so they are inlined. | ||
| 458 | 1396 | QStringList retained; | |
| 459 |
1/1✓ Branch 130 → 131 taken 1396 times.
|
1396 | retained.reserve(matchScope.retained.size()); |
| 460 |
4/4✓ Branch 134 → 135 taken 352 times.
✓ Branch 135 → 136 taken 352 times.
✓ Branch 139 → 133 taken 352 times.
✓ Branch 139 → 140 taken 1396 times.
|
1748 | for (const qint64 uid : matchScope.retained) retained << QString::number(uid); |
| 461 | // A null QString binds as SQL NULL, and NULL='' would reject every row. | ||
| 462 |
4/4✓ Branch 141 → 142 taken 1151 times.
✓ Branch 141 → 145 taken 245 times.
✓ Branch 146 → 147 taken 1151 times.
✓ Branch 146 → 148 taken 245 times.
|
2547 | const QString text = matchScope.filter.isEmpty() ? QStringLiteral("") : matchScope.filter; |
| 463 |
5/6✓ Branch 148 → 149 taken 1131 times.
✓ Branch 148 → 151 taken 265 times.
✓ Branch 149 → 150 taken 1015 times.
✓ Branch 149 → 151 taken 116 times.
✗ Branch 150 → 151 not taken.
✓ Branch 150 → 152 taken 1015 times.
|
1396 | const bool chips = request.unread || request.starred || request.attachments; |
| 464 | // The direct match condition on projection alias `row` (headers joined as | ||
| 465 | // `header` when chips are set). Text-only filters use the folded subject | ||
| 466 | // and sender of the projection; joining every header cost most of a scan. | ||
| 467 | struct DirectMatch { QString join; QString condition; QVariantList bindings; }; | ||
| 468 | 193 | const auto directMatch = [&](const QString &row, const QString &header) { | |
| 469 | 193 | DirectMatch match; | |
| 470 |
2/2✓ Branch 2 → 3 taken 57 times.
✓ Branch 2 → 19 taken 136 times.
|
193 | if (!chips) { |
| 471 |
1/1✓ Branch 7 → 8 taken 57 times.
|
114 | match.condition = QStringLiteral("(instr(%1.subject_fold,?)>0 OR instr(%1.sender_fold,?)>0)").arg(row); |
| 472 |
2/2✓ Branch 13 → 14 taken 57 times.
✓ Branch 15 → 16 taken 57 times.
|
57 | match.bindings << text << text; |
| 473 | 57 | return match; | |
| 474 | } | ||
| 475 | 136 | const QString unreadCondition = retained.isEmpty() | |
| 476 |
2/6✓ Branch 41 → 42 taken 100 times.
✓ Branch 41 → 43 taken 36 times.
✗ Branch 109 → 110 not taken.
✗ Branch 109 → 111 not taken.
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 115 not taken.
|
236 | ? QStringLiteral("(%1.flags & 1)=0").arg(header) |
| 477 |
13/19✓ Branch 20 → 21 taken 100 times.
✓ Branch 20 → 26 taken 36 times.
✓ Branch 25 → 33 taken 100 times.
✓ Branch 31 → 32 taken 36 times.
✓ Branch 32 → 33 taken 36 times.
✓ Branch 33 → 34 taken 36 times.
✓ Branch 33 → 35 taken 100 times.
✓ Branch 35 → 36 taken 36 times.
✓ Branch 35 → 37 taken 100 times.
✓ Branch 37 → 38 taken 36 times.
✓ Branch 37 → 39 taken 100 times.
✓ Branch 39 → 40 taken 100 times.
✓ Branch 39 → 41 taken 36 times.
✗ Branch 94 → 95 not taken.
✗ Branch 94 → 96 not taken.
✗ Branch 100 → 101 not taken.
✗ Branch 100 → 102 not taken.
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 106 not taken.
|
272 | : QStringLiteral("((%1.flags & 1)=0 OR %2.uid IN(%3))").arg(header, row, retained.join(QLatin1Char(','))); |
| 478 |
1/1✓ Branch 46 → 47 taken 136 times.
|
136 | match.join = QStringLiteral("JOIN headers %1 ON %1.folder_id=%2.folder_id AND %1.uid=%2.uid ").arg(header, row); |
| 479 | 272 | match.condition = QStringLiteral("(?='' OR instr(%1.subject_fold,?)>0 OR instr(%1.sender_fold,?)>0) AND (?=0 OR %2) " | |
| 480 | "AND (?=0 OR (%3.flags & 4)!=0) AND (?=0 OR %3.has_attachments!=0)") | ||
| 481 |
1/1✓ Branch 54 → 55 taken 136 times.
|
136 | .arg(row, unreadCondition, header); |
| 482 |
6/6✓ Branch 60 → 61 taken 136 times.
✓ Branch 62 → 63 taken 136 times.
✓ Branch 64 → 65 taken 136 times.
✓ Branch 66 → 67 taken 136 times.
✓ Branch 68 → 69 taken 136 times.
✓ Branch 70 → 71 taken 136 times.
|
136 | match.bindings << text << text << text << request.unread << request.starred << request.attachments; |
| 483 | 136 | return match; | |
| 484 | 136 | }; | |
| 485 | // Dense filters in large folders (the first typed characters match most | ||
| 486 | // mail) do not materialize the match set: the page walk keeps a row when | ||
| 487 | // it or a mail below it in its thread matches, and fills a page after a | ||
| 488 | // few hundred rows. A probe of the folder's first rows decides. | ||
| 489 |
6/6✓ Branch 153 → 154 taken 484 times.
✓ Branch 153 → 157 taken 912 times.
✓ Branch 154 → 155 taken 408 times.
✓ Branch 154 → 157 taken 76 times.
✓ Branch 155 → 156 taken 98 times.
✓ Branch 155 → 157 taken 310 times.
|
1396 | bool lazy = filtered && !rebuildMatches && m_matchLazy; |
| 490 |
6/6✓ Branch 158 → 159 taken 76 times.
✓ Branch 158 → 229 taken 1320 times.
✓ Branch 159 → 160 taken 30 times.
✓ Branch 159 → 229 taken 46 times.
✓ Branch 160 → 161 taken 19 times.
✓ Branch 160 → 229 taken 11 times.
|
1396 | if (rebuildMatches && m_lazyMatchFolderRows >= 0 && result.totalCount >= m_lazyMatchFolderRows) { |
| 491 |
1/1✓ Branch 167 → 168 taken 19 times.
|
38 | const auto probe = directMatch(QStringLiteral("p"), QStringLiteral("h")); |
| 492 |
1/1✓ Branch 172 → 173 taken 19 times.
|
19 | QSqlQuery density(m_db); |
| 493 |
1/1✓ Branch 179 → 180 taken 19 times.
|
57 | density.prepare(QStringLiteral("SELECT count(*), coalesce(sum(hit),0) FROM (SELECT (%1) AS hit FROM thread_projection p %2" |
| 494 | "WHERE p.folder_id=? %3 LIMIT %4)") | ||
| 495 |
2/2✓ Branch 176 → 177 taken 19 times.
✓ Branch 178 → 179 taken 19 times.
|
57 | .arg(probe.condition, probe.join, orderAscending).arg(LazyProbeRows)); |
| 496 |
3/3✓ Branch 188 → 189 taken 74 times.
✓ Branch 191 → 186 taken 74 times.
✓ Branch 191 → 192 taken 19 times.
|
93 | for (const auto &binding : probe.bindings) density.addBindValue(binding); |
| 497 |
1/1✓ Branch 194 → 195 taken 19 times.
|
19 | density.addBindValue(request.folderId); |
| 498 |
5/10✓ Branch 196 → 197 taken 19 times.
✓ Branch 197 → 198 taken 19 times.
✗ Branch 197 → 200 not taken.
✓ Branch 198 → 199 taken 19 times.
✗ Branch 199 → 200 not taken.
✓ Branch 199 → 201 taken 19 times.
✗ Branch 202 → 203 not taken.
✓ Branch 202 → 209 taken 19 times.
✗ Branch 203 → 204 not taken.
✗ Branch 204 → 205 not taken.
|
19 | if (!density.exec() || !density.next()) { result.error = density.lastError().text(); return result; } |
| 499 |
2/2✓ Branch 209 → 210 taken 19 times.
✓ Branch 210 → 211 taken 19 times.
|
19 | const qint64 probed = density.value(0).toLongLong(); |
| 500 |
2/2✓ Branch 212 → 213 taken 19 times.
✓ Branch 213 → 214 taken 19 times.
|
19 | const qint64 hits = density.value(1).toLongLong(); |
| 501 |
3/4✓ Branch 215 → 216 taken 19 times.
✗ Branch 215 → 218 not taken.
✓ Branch 216 → 217 taken 12 times.
✓ Branch 216 → 218 taken 7 times.
|
19 | lazy = probed > 0 && hits * LazyMinimumShareDivisor >= probed; |
| 502 | 19 | } | |
| 503 | 1396 | result.lazyMatches = lazy; | |
| 504 | 1396 | QString lazyPredicate; | |
| 505 | 1396 | QVariantList lazyBindings; | |
| 506 |
2/2✓ Branch 229 → 230 taken 110 times.
✓ Branch 229 → 261 taken 1286 times.
|
1396 | if (lazy) { |
| 507 |
1/1✓ Branch 236 → 237 taken 110 times.
|
220 | const auto below = directMatch(QStringLiteral("d"), QStringLiteral("dh")); |
| 508 | 220 | lazyPredicate = QStringLiteral("AND EXISTS(SELECT 1 FROM thread_projection d %1WHERE d.folder_id=p.folder_id " | |
| 509 | "%3AND d.root_uid=p.root_uid " | ||
| 510 | "AND d.path>=p.path AND substr(d.path,1,length(p.path))=p.path AND %2) ") | ||
| 511 |
1/1✓ Branch 250 → 251 taken 110 times.
|
220 | .arg(below.join, below.condition, |
| 512 | // The stored date columns lead the order index. | ||
| 513 |
2/6✓ Branch 245 → 246 taken 110 times.
✗ Branch 245 → 249 not taken.
✓ Branch 254 → 255 taken 110 times.
✗ Branch 254 → 256 not taken.
✗ Branch 1229 → 1230 not taken.
✗ Branch 1229 → 1231 not taken.
|
330 | request.sort.isDefault() ? QStringLiteral("AND d.undated=p.undated AND d.newest_order=p.newest_order ") |
| 514 | 110 | : QString()); | |
| 515 | 110 | lazyBindings = below.bindings; | |
| 516 | 110 | } | |
| 517 |
4/4✓ Branch 261 → 262 taken 76 times.
✓ Branch 261 → 427 taken 1320 times.
✓ Branch 262 → 263 taken 64 times.
✓ Branch 262 → 427 taken 12 times.
|
1396 | if (rebuildMatches && !lazy) { |
| 518 | // Typing extends the text: a filter containing the previous one (same | ||
| 519 | // folder, revision and chips) can only match mails the previous one | ||
| 520 | // matched, so it searches that set instead of the whole folder. | ||
| 521 | 64 | MatchScope widened = matchScope; | |
| 522 | 64 | widened.filter = m_matchScope.filter; | |
| 523 |
4/4✓ Branch 266 → 267 taken 27 times.
✓ Branch 266 → 274 taken 5 times.
✓ Branch 268 → 269 taken 21 times.
✓ Branch 268 → 274 taken 6 times.
|
32 | const bool narrow = m_matchReady && !m_matchLazy && !m_matchScope.filter.isEmpty() && |
| 524 |
8/8✓ Branch 265 → 266 taken 32 times.
✓ Branch 265 → 274 taken 32 times.
✓ Branch 269 → 270 taken 21 times.
✓ Branch 270 → 271 taken 10 times.
✓ Branch 270 → 274 taken 11 times.
✓ Branch 271 → 272 taken 10 times.
✓ Branch 272 → 273 taken 8 times.
✓ Branch 272 → 274 taken 2 times.
|
96 | matchScope.filter.contains(m_matchScope.filter) && widened == m_matchScope; |
| 525 |
2/8✓ Branch 316 → 317 taken 64 times.
✗ Branch 316 → 318 not taken.
✗ Branch 318 → 319 not taken.
✓ Branch 318 → 321 taken 64 times.
✗ Branch 1276 → 1277 not taken.
✗ Branch 1276 → 1278 not taken.
✗ Branch 1280 → 1281 not taken.
✗ Branch 1280 → 1282 not taken.
|
64 | if (!exec("CREATE TEMP TABLE IF NOT EXISTS thread_matches(uid INTEGER PRIMARY KEY)") || |
| 526 |
5/12✓ Branch 280 → 281 taken 64 times.
✓ Branch 281 → 282 taken 64 times.
✓ Branch 282 → 283 taken 64 times.
✗ Branch 282 → 296 not taken.
✓ Branch 312 → 313 taken 64 times.
✗ Branch 312 → 314 not taken.
✓ Branch 314 → 315 taken 64 times.
✗ Branch 314 → 316 not taken.
✗ Branch 1268 → 1269 not taken.
✗ Branch 1268 → 1270 not taken.
✗ Branch 1272 → 1273 not taken.
✗ Branch 1272 → 1274 not taken.
|
128 | !exec("CREATE TEMP TABLE IF NOT EXISTS thread_matches_prev(uid INTEGER PRIMARY KEY)") || |
| 527 |
7/14✓ Branch 284 → 285 taken 64 times.
✓ Branch 285 → 286 taken 64 times.
✓ Branch 286 → 287 taken 64 times.
✗ Branch 286 → 296 not taken.
✓ Branch 287 → 288 taken 8 times.
✓ Branch 287 → 292 taken 56 times.
✓ Branch 308 → 309 taken 64 times.
✗ Branch 308 → 310 not taken.
✓ Branch 310 → 311 taken 64 times.
✗ Branch 310 → 312 not taken.
✗ Branch 1260 → 1261 not taken.
✗ Branch 1260 → 1262 not taken.
✗ Branch 1264 → 1265 not taken.
✗ Branch 1264 → 1266 not taken.
|
128 | !exec("DELETE FROM thread_matches_prev") || |
| 528 |
9/16✓ Branch 276 → 277 taken 64 times.
✓ Branch 277 → 278 taken 64 times.
✓ Branch 278 → 279 taken 64 times.
✗ Branch 278 → 296 not taken.
✓ Branch 289 → 290 taken 8 times.
✓ Branch 290 → 291 taken 8 times.
✓ Branch 291 → 292 taken 8 times.
✗ Branch 291 → 296 not taken.
✓ Branch 304 → 305 taken 8 times.
✓ Branch 304 → 306 taken 56 times.
✓ Branch 306 → 307 taken 64 times.
✗ Branch 306 → 308 not taken.
✗ Branch 1252 → 1253 not taken.
✗ Branch 1252 → 1254 not taken.
✗ Branch 1256 → 1257 not taken.
✗ Branch 1256 → 1258 not taken.
|
200 | (narrow && !exec("INSERT INTO thread_matches_prev SELECT uid FROM thread_matches")) || |
| 529 |
7/14✓ Branch 293 → 294 taken 64 times.
✓ Branch 294 → 295 taken 64 times.
✗ Branch 295 → 296 not taken.
✓ Branch 295 → 297 taken 64 times.
✓ Branch 298 → 299 taken 64 times.
✗ Branch 298 → 300 not taken.
✓ Branch 300 → 301 taken 64 times.
✗ Branch 300 → 302 not taken.
✓ Branch 302 → 303 taken 8 times.
✓ Branch 302 → 304 taken 56 times.
✗ Branch 1244 → 1245 not taken.
✗ Branch 1244 → 1246 not taken.
✗ Branch 1248 → 1249 not taken.
✗ Branch 1248 → 1250 not taken.
|
128 | !exec("DELETE FROM thread_matches")) { result.error = m_error; return result; } |
| 530 | // The candidate set: the whole folder, or the previous matches (the | ||
| 531 | // CROSS JOIN keeps them as the outer loop). | ||
| 532 | const QString source = narrow | ||
| 533 | 8 | ? QStringLiteral("thread_matches_prev prev CROSS JOIN thread_projection p ON p.folder_id=? AND p.uid=prev.uid ") | |
| 534 |
6/6✓ Branch 321 → 322 taken 8 times.
✓ Branch 321 → 325 taken 56 times.
✓ Branch 328 → 329 taken 56 times.
✓ Branch 328 → 330 taken 8 times.
✓ Branch 330 → 331 taken 8 times.
✓ Branch 330 → 332 taken 56 times.
|
128 | : QStringLiteral("thread_projection p "); |
| 535 |
4/4✓ Branch 332 → 333 taken 8 times.
✓ Branch 332 → 334 taken 56 times.
✓ Branch 337 → 338 taken 56 times.
✓ Branch 337 → 339 taken 8 times.
|
120 | const QString folderCondition = narrow ? QString() : QStringLiteral("p.folder_id=? AND "); |
| 536 |
1/1✓ Branch 345 → 346 taken 64 times.
|
128 | const auto match = directMatch(QStringLiteral("p"), QStringLiteral("h")); |
| 537 |
1/1✓ Branch 353 → 354 taken 64 times.
|
64 | const QString direct = QStringLiteral("SELECT p.uid FROM %1%2WHERE %3%4").arg(source, match.join, folderCondition, match.condition); |
| 538 |
2/4✓ Branch 359 → 360 taken 64 times.
✓ Branch 359 → 361 taken 64 times.
✗ Branch 1303 → 1304 not taken.
✗ Branch 1303 → 1305 not taken.
|
128 | QVariantList bindings{request.folderId}; |
| 539 |
2/2✓ Branch 361 → 362 taken 64 times.
✓ Branch 363 → 364 taken 64 times.
|
64 | bindings << match.bindings << request.folderId; |
| 540 |
4/5✓ Branch 365 → 366 taken 64 times.
✓ Branch 366 → 367 taken 64 times.
✓ Branch 367 → 368 taken 64 times.
✗ Branch 370 → 371 not taken.
✓ Branch 370 → 373 taken 64 times.
|
64 | if (!exec("WITH RECURSIVE matched(uid) AS (" + direct + " UNION SELECT p.parent_uid FROM thread_projection p JOIN matched m ON p.uid=m.uid WHERE p.folder_id=? AND p.parent_uid!=0) INSERT OR IGNORE INTO thread_matches SELECT uid FROM matched", |
| 541 | ✗ | bindings)) { result.error = m_error; return result; } | |
| 542 |
1/1✓ Branch 373 → 374 taken 64 times.
|
64 | QSqlQuery count(m_db); |
| 543 |
7/16✓ Branch 377 → 378 taken 64 times.
✓ Branch 378 → 379 taken 64 times.
✗ Branch 378 → 381 not taken.
✓ Branch 379 → 380 taken 64 times.
✗ Branch 380 → 381 not taken.
✓ Branch 380 → 382 taken 64 times.
✓ Branch 383 → 384 taken 64 times.
✗ Branch 383 → 385 not taken.
✓ Branch 385 → 386 taken 64 times.
✗ Branch 385 → 387 not taken.
✗ Branch 387 → 388 not taken.
✓ Branch 387 → 394 taken 64 times.
✗ Branch 1321 → 1322 not taken.
✗ Branch 1321 → 1323 not taken.
✗ Branch 1325 → 1326 not taken.
✗ Branch 1325 → 1327 not taken.
|
128 | if (!count.exec(QStringLiteral("SELECT count(*) FROM thread_matches")) || !count.next()) { |
| 544 | ✗ | result.error = count.lastError().text(); return result; | |
| 545 | } | ||
| 546 |
2/2✓ Branch 394 → 395 taken 64 times.
✓ Branch 395 → 396 taken 64 times.
|
64 | matchCount = count.value(0).toLongLong(); |
| 547 | 64 | } | |
| 548 | // Few matches: read the page from the match set and sort it, instead of | ||
| 549 | // walking the folder's order index until enough matches passed (a single | ||
| 550 | // match in a 1M folder walked every row). Many matches keep the walk, | ||
| 551 | // which finds a page after a few hundred rows. | ||
| 552 |
6/6✓ Branch 427 → 428 taken 484 times.
✓ Branch 427 → 431 taken 912 times.
✓ Branch 428 → 429 taken 374 times.
✓ Branch 428 → 431 taken 110 times.
✓ Branch 429 → 430 taken 179 times.
✓ Branch 429 → 431 taken 195 times.
|
1396 | const bool sparse = filtered && matchCount >= 0 && matchCount <= m_sparseMatchRows; |
| 553 | const QString matchedRows = sparse | ||
| 554 |
0/2✗ Branch 1367 → 1368 not taken.
✗ Branch 1367 → 1369 not taken.
|
179 | ? QStringLiteral("thread_matches matches CROSS JOIN thread_projection p ON p.uid=matches.uid ") |
| 555 |
4/8✓ Branch 446 → 447 taken 1217 times.
✓ Branch 446 → 448 taken 179 times.
✓ Branch 448 → 449 taken 1217 times.
✓ Branch 448 → 450 taken 179 times.
✗ Branch 1351 → 1352 not taken.
✗ Branch 1351 → 1353 not taken.
✗ Branch 1355 → 1356 not taken.
✗ Branch 1355 → 1357 not taken.
|
2613 | : QStringLiteral("thread_projection p ") + |
| 556 |
13/17✓ Branch 432 → 433 taken 179 times.
✓ Branch 432 → 436 taken 1217 times.
✓ Branch 436 → 437 taken 305 times.
✓ Branch 436 → 441 taken 912 times.
✓ Branch 437 → 438 taken 195 times.
✓ Branch 437 → 441 taken 110 times.
✓ Branch 445 → 446 taken 1217 times.
✓ Branch 450 → 451 taken 1217 times.
✓ Branch 450 → 452 taken 179 times.
✓ Branch 452 → 453 taken 195 times.
✓ Branch 452 → 454 taken 1201 times.
✓ Branch 454 → 455 taken 179 times.
✓ Branch 454 → 456 taken 1217 times.
✗ Branch 1359 → 1360 not taken.
✗ Branch 1359 → 1361 not taken.
✗ Branch 1363 → 1364 not taken.
✗ Branch 1363 → 1365 not taken.
|
4383 | (filtered && !lazy ? QStringLiteral("JOIN thread_matches matches ON matches.uid=p.uid ") : QString()); |
| 557 | 1396 | QList<ThreadProjectionCursor> collapsed; | |
| 558 |
2/2✓ Branch 457 → 458 taken 1 time.
✓ Branch 457 → 465 taken 1395 times.
|
1397 | if (request.collapsed.size() > 4096) { result.error = QStringLiteral("Too many collapsed thread anchors"); return result; } |
| 559 | // P90.5: one set query for all collapsed anchors instead of one SELECT per | ||
| 560 | // UID. Chunking stays far below SQLite's host-parameter limit. | ||
| 561 | { | ||
| 562 | 1395 | QList<qint64> anchorUids; | |
| 563 |
1/1✓ Branch 466 → 467 taken 1395 times.
|
1395 | anchorUids.reserve(request.collapsed.size()); |
| 564 |
3/3✓ Branch 470 → 471 taken 22 times.
✓ Branch 473 → 469 taken 22 times.
✓ Branch 473 → 474 taken 1395 times.
|
1417 | for (const auto uid : request.collapsed) anchorUids.append(uid); |
| 565 |
2/2✓ Branch 538 → 475 taken 20 times.
✓ Branch 538 → 539 taken 1395 times.
|
1415 | for (int at = 0; at < anchorUids.size(); at += 500) { |
| 566 | 20 | const int count = qMin(500, anchorUids.size() - at); | |
| 567 | 20 | QStringList marks; | |
| 568 |
1/1✓ Branch 477 → 478 taken 20 times.
|
20 | marks.reserve(count); |
| 569 |
3/3✓ Branch 482 → 483 taken 22 times.
✓ Branch 486 → 479 taken 22 times.
✓ Branch 486 → 487 taken 20 times.
|
42 | for (int i = 0; i < count; ++i) marks << QStringLiteral("?"); |
| 570 |
1/1✓ Branch 487 → 488 taken 20 times.
|
20 | QSqlQuery collapse(m_db); |
| 571 |
1/1✓ Branch 495 → 496 taken 20 times.
|
60 | collapse.prepare(QStringLiteral("SELECT p.uid,%2,%3,p.root_uid,p.path FROM thread_projection p WHERE p.folder_id=? AND p.uid IN(%1)") |
| 572 |
2/2✓ Branch 493 → 494 taken 20 times.
✓ Branch 494 → 495 taken 20 times.
|
60 | .arg(marks.join(QLatin1Char(',')), order.high, order.low)); |
| 573 |
1/1✓ Branch 502 → 503 taken 20 times.
|
20 | collapse.addBindValue(request.folderId); |
| 574 |
3/3✓ Branch 508 → 509 taken 22 times.
✓ Branch 511 → 505 taken 22 times.
✓ Branch 511 → 512 taken 20 times.
|
42 | for (int i = 0; i < count; ++i) collapse.addBindValue(anchorUids.at(at + i)); |
| 575 |
2/5✓ Branch 512 → 513 taken 20 times.
✗ Branch 513 → 514 not taken.
✓ Branch 513 → 520 taken 20 times.
✗ Branch 514 → 515 not taken.
✗ Branch 515 → 516 not taken.
|
20 | if (!collapse.exec()) { result.error = collapse.lastError().text(); return result; } |
| 576 |
5/5✓ Branch 521 → 522 taken 22 times.
✓ Branch 522 → 523 taken 22 times.
✓ Branch 525 → 526 taken 42 times.
✓ Branch 526 → 521 taken 22 times.
✓ Branch 526 → 527 taken 20 times.
|
42 | while (collapse.next()) collapsed.append(readCursor(collapse, 1)); |
| 577 | 20 | } | |
| 578 | 1395 | } | |
| 579 |
1/1✓ Branch 543 → 545 taken 1395 times.
|
1395 | auto cursor = request.after; |
| 580 | 1395 | bool includeAnchor = false; | |
| 581 |
2/2✓ Branch 545 → 546 taken 78 times.
✓ Branch 545 → 583 taken 1317 times.
|
1395 | if (request.anchorUid > 0) { |
| 582 |
1/1✓ Branch 546 → 547 taken 78 times.
|
78 | QSqlQuery anchor(m_db); |
| 583 |
1/1✓ Branch 551 → 552 taken 78 times.
|
234 | anchor.prepare(QStringLiteral("SELECT %1,%2,p.root_uid,p.path FROM thread_projection p WHERE p.folder_id=? AND p.uid=?") |
| 584 |
1/1✓ Branch 550 → 551 taken 78 times.
|
234 | .arg(order.high, order.low)); |
| 585 |
2/2✓ Branch 557 → 558 taken 78 times.
✓ Branch 561 → 562 taken 78 times.
|
78 | anchor.addBindValue(request.folderId); anchor.addBindValue(request.anchorUid); |
| 586 |
2/5✓ Branch 563 → 564 taken 78 times.
✗ Branch 564 → 565 not taken.
✓ Branch 564 → 571 taken 78 times.
✗ Branch 565 → 566 not taken.
✗ Branch 566 → 567 not taken.
|
78 | if (!anchor.exec()) { result.error = anchor.lastError().text(); return result; } |
| 587 |
4/4✓ Branch 571 → 572 taken 78 times.
✓ Branch 572 → 573 taken 77 times.
✓ Branch 572 → 577 taken 1 time.
✓ Branch 573 → 574 taken 77 times.
|
78 | if (anchor.next()) { cursor = readCursor(anchor, 0); includeAnchor = true; } |
| 588 | 78 | } | |
| 589 |
4/4✓ Branch 583 → 584 taken 77 times.
✓ Branch 583 → 701 taken 1318 times.
✓ Branch 584 → 585 taken 60 times.
✓ Branch 584 → 701 taken 17 times.
|
1395 | if (includeAnchor && !request.before) { |
| 590 |
1/1✓ Branch 588 → 589 taken 60 times.
|
60 | QString sql = QStringLiteral("SELECT 1 FROM ") + matchedRows; |
| 591 |
2/2✓ Branch 594 → 595 taken 60 times.
✓ Branch 595 → 596 taken 60 times.
|
60 | sql += QStringLiteral("WHERE p.folder_id=? ") + lazyPredicate; |
| 592 |
2/2✓ Branch 600 → 601 taken 16 times.
✓ Branch 600 → 636 taken 44 times.
|
60 | if (!request.excludedUids.isEmpty()) { |
| 593 |
1/2✗ Branch 602 → 603 not taken.
✓ Branch 602 → 610 taken 16 times.
|
16 | if (request.excludedUids.size() > 4096) { result.error = QStringLiteral("Too many pending move exclusions"); return result; } |
| 594 | 16 | QStringList marks; | |
| 595 |
3/3✓ Branch 614 → 615 taken 31 times.
✓ Branch 619 → 611 taken 31 times.
✓ Branch 619 → 620 taken 16 times.
|
47 | for (qsizetype i = 0; i < request.excludedUids.size(); ++i) marks << QStringLiteral("?"); |
| 596 |
3/3✓ Branch 626 → 627 taken 16 times.
✓ Branch 627 → 628 taken 16 times.
✓ Branch 628 → 629 taken 16 times.
|
32 | sql += QStringLiteral("AND p.uid NOT IN(%1) ").arg(marks.join(QLatin1Char(','))); |
| 597 | 16 | } | |
| 598 |
2/2✓ Branch 640 → 641 taken 60 times.
✓ Branch 641 → 642 taken 60 times.
|
120 | sql += QStringLiteral("AND %1 < (?,?,?,?) LIMIT 1").arg(orderTuple); |
| 599 |
1/1✓ Branch 645 → 646 taken 60 times.
|
60 | QSqlQuery before(m_db); |
| 600 |
1/1✓ Branch 646 → 647 taken 60 times.
|
60 | before.prepare(sql); |
| 601 |
1/1✓ Branch 649 → 650 taken 60 times.
|
60 | before.addBindValue(request.folderId); |
| 602 |
3/3✓ Branch 656 → 657 taken 40 times.
✓ Branch 659 → 654 taken 40 times.
✓ Branch 659 → 660 taken 60 times.
|
100 | for (const auto &binding : std::as_const(lazyBindings)) before.addBindValue(binding); |
| 603 |
3/3✓ Branch 665 → 666 taken 31 times.
✓ Branch 669 → 662 taken 31 times.
✓ Branch 669 → 670 taken 60 times.
|
91 | for (const auto uid : request.excludedUids) before.addBindValue(uid); |
| 604 |
2/2✓ Branch 671 → 672 taken 60 times.
✓ Branch 673 → 674 taken 60 times.
|
60 | before.addBindValue(cursor.high); before.addBindValue(cursor.low); |
| 605 |
2/2✓ Branch 676 → 677 taken 60 times.
✓ Branch 680 → 681 taken 60 times.
|
60 | before.addBindValue(cursor.rootUid); before.addBindValue(cursor.path); |
| 606 |
2/5✓ Branch 682 → 683 taken 60 times.
✗ Branch 683 → 684 not taken.
✓ Branch 683 → 690 taken 60 times.
✗ Branch 684 → 685 not taken.
✗ Branch 685 → 686 not taken.
|
60 | if (!before.exec()) { result.error = before.lastError().text(); return result; } |
| 607 |
1/1✓ Branch 690 → 691 taken 60 times.
|
60 | result.hasBefore = before.next(); |
| 608 | 60 | } | |
| 609 |
1/1✓ Branch 701 → 702 taken 1395 times.
|
1395 | const int limit = qBound(1, request.limit, MaximumPageRows); |
| 610 |
1/2✓ Branch 938 → 703 taken 1417 times.
✗ Branch 938 → 939 not taken.
|
1417 | while (result.rows.size() <= limit) { |
| 611 |
1/2✗ Branch 704 → 705 not taken.
✓ Branch 704 → 706 taken 1417 times.
|
1418 | if (cancelled.load()) { result.outcome = ThreadProjectionPage::Outcome::Cancelled; return result; } |
| 612 |
1/1✓ Branch 706 → 707 taken 1417 times.
|
1417 | QSqlQuery read(m_db); |
| 613 |
2/2✓ Branch 707 → 708 taken 1417 times.
✓ Branch 708 → 709 taken 1417 times.
|
2834 | QString sql = "SELECT " + PreviewColumns + ",p.parent_uid,p.depth,p.descendants,p.unread_descendants," + |
| 614 |
4/4✓ Branch 709 → 710 taken 1417 times.
✓ Branch 710 → 711 taken 1417 times.
✓ Branch 711 → 712 taken 1417 times.
✓ Branch 712 → 713 taken 1417 times.
|
2834 | order.high + "," + order.low + ",p.root_uid,p.path FROM " + |
| 615 |
2/2✓ Branch 713 → 714 taken 1417 times.
✓ Branch 714 → 715 taken 1417 times.
|
1417 | matchedRows + "JOIN headers h ON h.folder_id=p.folder_id AND h.uid=p.uid "; |
| 616 |
2/2✓ Branch 722 → 723 taken 1417 times.
✓ Branch 723 → 724 taken 1417 times.
|
1417 | sql += "WHERE p.folder_id=? " + lazyPredicate; |
| 617 | // P90.16: journaled-away rows never re-enter a visible page while the | ||
| 618 | // canonical removal is pending. Bindings stay positional: folder, then | ||
| 619 | // the exclusion set, then the cursor and the limit. | ||
| 620 | 1417 | QList<qint64> exclusions; | |
| 621 |
2/2✓ Branch 726 → 727 taken 46 times.
✓ Branch 726 → 767 taken 1371 times.
|
1417 | if (!request.excludedUids.isEmpty()) { |
| 622 |
2/2✓ Branch 728 → 729 taken 1 time.
✓ Branch 728 → 736 taken 45 times.
|
47 | if (request.excludedUids.size() > 4096) { result.error = QStringLiteral("Too many pending move exclusions"); return result; } |
| 623 | 45 | QStringList marks; | |
| 624 |
1/1✓ Branch 737 → 738 taken 45 times.
|
45 | marks.reserve(request.excludedUids.size()); |
| 625 |
4/4✓ Branch 744 → 745 taken 85 times.
✓ Branch 747 → 748 taken 85 times.
✓ Branch 750 → 740 taken 85 times.
✓ Branch 750 → 751 taken 45 times.
|
130 | for (const auto uid : request.excludedUids) { marks << QStringLiteral("?"); exclusions.append(uid); } |
| 626 |
3/3✓ Branch 757 → 758 taken 45 times.
✓ Branch 758 → 759 taken 45 times.
✓ Branch 759 → 760 taken 45 times.
|
90 | sql += QStringLiteral("AND p.uid NOT IN(%1) ").arg(marks.join(QLatin1Char(','))); |
| 627 | 45 | } | |
| 628 |
2/2✓ Branch 767 → 768 taken 874 times.
✓ Branch 767 → 786 taken 542 times.
|
4038 | if (cursor.valid) sql += QStringLiteral("AND %1 %2 (?,?,?,?) ").arg(orderTuple, |
| 629 |
8/8✓ Branch 771 → 772 taken 103 times.
✓ Branch 771 → 776 taken 771 times.
✓ Branch 772 → 773 taken 18 times.
✓ Branch 772 → 774 taken 85 times.
✓ Branch 776 → 777 taken 60 times.
✓ Branch 776 → 778 taken 711 times.
✓ Branch 780 → 781 taken 874 times.
✓ Branch 781 → 782 taken 874 times.
|
1748 | request.before ? (includeAnchor ? "<=" : "<") : (includeAnchor ? ">=" : ">")); |
| 630 |
6/10✓ Branch 786 → 787 taken 104 times.
✓ Branch 786 → 791 taken 1312 times.
✓ Branch 790 → 795 taken 104 times.
✓ Branch 794 → 795 taken 1312 times.
✓ Branch 803 → 804 taken 104 times.
✓ Branch 803 → 805 taken 1312 times.
✗ Branch 1589 → 1590 not taken.
✗ Branch 1589 → 1591 not taken.
✗ Branch 1593 → 1594 not taken.
✗ Branch 1593 → 1595 not taken.
|
5664 | sql += request.before ? QStringLiteral("ORDER BY %1 DESC,%2 DESC,p.root_uid DESC,p.path DESC LIMIT ?").arg(order.high, order.low) |
| 631 |
7/11✓ Branch 795 → 796 taken 1416 times.
✓ Branch 797 → 798 taken 1312 times.
✓ Branch 797 → 799 taken 104 times.
✓ Branch 799 → 800 taken 1312 times.
✓ Branch 799 → 801 taken 104 times.
✓ Branch 801 → 802 taken 104 times.
✓ Branch 801 → 803 taken 1312 times.
✗ Branch 1581 → 1582 not taken.
✗ Branch 1581 → 1583 not taken.
✗ Branch 1585 → 1586 not taken.
✗ Branch 1585 → 1587 not taken.
|
4144 | : orderAscending + QStringLiteral(" LIMIT ?"); |
| 632 |
2/2✓ Branch 805 → 806 taken 1416 times.
✓ Branch 808 → 809 taken 1416 times.
|
2832 | read.prepare(sql); read.addBindValue(request.folderId); |
| 633 |
3/3✓ Branch 815 → 816 taken 612 times.
✓ Branch 818 → 813 taken 612 times.
✓ Branch 818 → 819 taken 1416 times.
|
2028 | for (const auto &binding : std::as_const(lazyBindings)) read.addBindValue(binding); |
| 634 |
5/5✓ Branch 819 → 820 taken 1416 times.
✓ Branch 820 → 821 taken 1416 times.
✓ Branch 825 → 826 taken 85 times.
✓ Branch 829 → 822 taken 85 times.
✓ Branch 829 → 830 taken 1416 times.
|
1501 | for (const auto uid : exclusions) read.addBindValue(uid); |
| 635 |
6/6✓ Branch 830 → 831 taken 874 times.
✓ Branch 830 → 844 taken 542 times.
✓ Branch 832 → 833 taken 874 times.
✓ Branch 834 → 835 taken 874 times.
✓ Branch 837 → 838 taken 874 times.
✓ Branch 841 → 842 taken 874 times.
|
2290 | if (cursor.valid) { read.addBindValue(cursor.high); read.addBindValue(cursor.low); read.addBindValue(cursor.rootUid); read.addBindValue(cursor.path); } |
| 636 |
1/1✓ Branch 846 → 847 taken 1416 times.
|
1416 | read.addBindValue(limit+1); |
| 637 |
2/5✓ Branch 848 → 849 taken 1416 times.
✗ Branch 849 → 850 not taken.
✓ Branch 849 → 856 taken 1416 times.
✗ Branch 850 → 851 not taken.
✗ Branch 851 → 852 not taken.
|
1416 | if (!read.exec()) { result.error = read.lastError().text(); return result; } |
| 638 | 1416 | bool jumped = false; | |
| 639 |
3/3✓ Branch 911 → 912 taken 113454 times.
✓ Branch 912 → 857 taken 112985 times.
✓ Branch 912 → 913 taken 469 times.
|
113454 | while (read.next()) { |
| 640 |
1/1✓ Branch 857 → 858 taken 112985 times.
|
112985 | const auto next = readCursor(read, 16); |
| 641 |
1/1✓ Branch 858 → 859 taken 112985 times.
|
112985 | cursor = next; includeAnchor = false; |
| 642 |
4/4✓ Branch 859 → 860 taken 112985 times.
✓ Branch 860 → 861 taken 112985 times.
✓ Branch 883 → 862 taken 356 times.
✓ Branch 883 → 884 taken 112963 times.
|
113319 | for (const auto &hidden : collapsed) { |
| 643 |
7/8✓ Branch 863 → 864 taken 43 times.
✓ Branch 863 → 871 taken 313 times.
✓ Branch 866 → 867 taken 22 times.
✓ Branch 866 → 871 taken 21 times.
✓ Branch 869 → 870 taken 22 times.
✗ Branch 869 → 871 not taken.
✓ Branch 872 → 873 taken 22 times.
✓ Branch 872 → 881 taken 334 times.
|
356 | if (next.rootUid == hidden.rootUid && next.path.size() > hidden.path.size() && next.path.startsWith(hidden.path)) { |
| 644 |
1/1✓ Branch 873 → 874 taken 22 times.
|
22 | cursor = hidden; |
| 645 | // Seek past this subtree, without scanning all of its descendants. | ||
| 646 |
2/2✓ Branch 874 → 875 taken 1 time.
✓ Branch 874 → 876 taken 21 times.
|
22 | if (request.before) includeAnchor = true; |
| 647 |
2/2✓ Branch 876 → 877 taken 21 times.
✓ Branch 877 → 878 taken 21 times.
|
21 | else cursor.path.append(QByteArray((ThreadBuilder::MaximumDepth+1)*4, '\xff')); |
| 648 | 22 | jumped = true; break; | |
| 649 | } | ||
| 650 | } | ||
| 651 |
2/2✓ Branch 884 → 885 taken 22 times.
✓ Branch 884 → 886 taken 112963 times.
|
112985 | if (jumped) break; |
| 652 |
2/2✓ Branch 888 → 889 taken 112963 times.
✓ Branch 890 → 891 taken 112963 times.
|
451852 | result.rows.append({readHeader(read, request.folderId), read.value(12).toLongLong(), read.value(13).toInt(), |
| 653 |
2/2✓ Branch 892 → 893 taken 112963 times.
✓ Branch 894 → 895 taken 112963 times.
|
338889 | read.value(14).toInt(), read.value(15).toInt(), next}); |
| 654 |
2/2✓ Branch 903 → 904 taken 925 times.
✓ Branch 903 → 905 taken 112038 times.
|
112963 | if (result.rows.size() > limit) break; |
| 655 | 112985 | } | |
| 656 |
5/6✓ Branch 913 → 914 taken 22 times.
✓ Branch 913 → 916 taken 1394 times.
✗ Branch 915 → 916 not taken.
✓ Branch 915 → 917 taken 22 times.
✓ Branch 918 → 919 taken 1394 times.
✓ Branch 918 → 920 taken 22 times.
|
1416 | if (!jumped || result.rows.size() > limit) break; |
| 657 | 4207 | } | |
| 658 | 1394 | result.hasMore = result.rows.size() > limit; | |
| 659 |
2/2✓ Branch 940 → 941 taken 925 times.
✓ Branch 940 → 942 taken 469 times.
|
1394 | if (result.hasMore) result.rows.removeLast(); |
| 660 | // P90.5: labels for the whole page in one bulk query. The per-uid window | ||
| 661 | // row-number cap reproduces the previous per-mail LIMIT exactly, and the | ||
| 662 | // ordered stream keeps deterministic label assignment per row. | ||
| 663 | 1394 | qint64 pageBytes = 0; | |
| 664 |
2/2✓ Branch 943 → 944 taken 1380 times.
✓ Branch 943 → 1051 taken 14 times.
|
1394 | if (!result.rows.isEmpty()) { |
| 665 | 1380 | QStringList marks; | |
| 666 |
1/1✓ Branch 945 → 946 taken 1380 times.
|
1380 | marks.reserve(result.rows.size()); |
| 667 |
3/3✓ Branch 950 → 951 taken 112038 times.
✓ Branch 955 → 947 taken 112038 times.
✓ Branch 955 → 956 taken 1380 times.
|
113418 | for (int i = 0; i < result.rows.size(); ++i) marks << QStringLiteral("?"); |
| 668 |
1/1✓ Branch 956 → 957 taken 1380 times.
|
1380 | QSqlQuery labels(m_db); |
| 669 |
1/1✓ Branch 965 → 966 taken 1380 times.
|
4140 | labels.prepare(QStringLiteral( |
| 670 | "SELECT uid,lbl,bytes FROM (SELECT h.uid AS uid," | ||
| 671 | "CASE WHEN length(CAST(ml.label AS BLOB))<=1048576 THEN ml.label END AS lbl," | ||
| 672 | "length(CAST(ml.label AS BLOB)) AS bytes," | ||
| 673 | "row_number() OVER (PARTITION BY h.uid ORDER BY ml.label) AS rn " | ||
| 674 | "FROM mail_labels ml JOIN headers h ON h.id=ml.header_id " | ||
| 675 | "WHERE h.folder_id=? AND h.uid IN(%1)) WHERE rn<=257 ORDER BY uid,lbl") | ||
| 676 |
2/2✓ Branch 963 → 964 taken 1380 times.
✓ Branch 964 → 965 taken 1380 times.
|
4140 | .arg(marks.join(QLatin1Char(',')))); |
| 677 |
1/1✓ Branch 972 → 973 taken 1380 times.
|
1380 | labels.addBindValue(request.folderId); |
| 678 |
5/5✓ Branch 974 → 975 taken 1380 times.
✓ Branch 975 → 976 taken 1380 times.
✓ Branch 980 → 981 taken 112038 times.
✓ Branch 984 → 977 taken 112038 times.
✓ Branch 984 → 985 taken 1380 times.
|
113418 | for (const auto &row : result.rows) labels.addBindValue(row.header.uid); |
| 679 |
2/5✓ Branch 985 → 986 taken 1380 times.
✗ Branch 986 → 987 not taken.
✓ Branch 986 → 993 taken 1380 times.
✗ Branch 987 → 988 not taken.
✗ Branch 988 → 989 not taken.
|
1380 | if (!labels.exec()) { result.error = labels.lastError().text(); return result; } |
| 680 | 1380 | QHash<qint64, int> rowByUid; | |
| 681 |
1/1✓ Branch 994 → 995 taken 1380 times.
|
1380 | rowByUid.reserve(result.rows.size()); |
| 682 |
4/4✓ Branch 996 → 997 taken 112038 times.
✓ Branch 997 → 998 taken 112038 times.
✓ Branch 1000 → 996 taken 112038 times.
✓ Branch 1000 → 1001 taken 1380 times.
|
113418 | for (int i = 0; i < result.rows.size(); ++i) rowByUid.insert(result.rows[i].header.uid, i); |
| 683 | 1380 | qint64 currentUid = 0; | |
| 684 | 1380 | int currentRow = -1; | |
| 685 | 1380 | qint64 labelBytes = 0; | |
| 686 |
3/3✓ Branch 1035 → 1036 taken 1762 times.
✓ Branch 1036 → 1002 taken 383 times.
✓ Branch 1036 → 1037 taken 1379 times.
|
1762 | while (labels.next()) { |
| 687 |
2/2✓ Branch 1002 → 1003 taken 383 times.
✓ Branch 1003 → 1004 taken 383 times.
|
383 | const qint64 uid = labels.value(0).toLongLong(); |
| 688 |
2/2✓ Branch 1005 → 1006 taken 58 times.
✓ Branch 1005 → 1008 taken 325 times.
|
383 | if (uid != currentUid) { |
| 689 | 58 | currentUid = uid; | |
| 690 | 58 | currentRow = rowByUid.value(uid, -1); | |
| 691 | 58 | labelBytes = 0; | |
| 692 | } | ||
| 693 |
1/2✗ Branch 1008 → 1009 not taken.
✓ Branch 1008 → 1010 taken 383 times.
|
383 | if (currentRow < 0) continue; |
| 694 |
2/2✓ Branch 1010 → 1011 taken 383 times.
✓ Branch 1011 → 1012 taken 383 times.
|
383 | labelBytes += labels.value(2).toLongLong(); |
| 695 |
1/1✓ Branch 1013 → 1014 taken 383 times.
|
383 | auto &row = result.rows[currentRow]; |
| 696 |
5/6✓ Branch 1015 → 1016 taken 382 times.
✓ Branch 1015 → 1017 taken 1 time.
✗ Branch 1016 → 1017 not taken.
✓ Branch 1016 → 1018 taken 382 times.
✓ Branch 1019 → 1020 taken 1 time.
✓ Branch 1019 → 1027 taken 382 times.
|
383 | if (row.header.labels.size() >= MailDisplaySnapshot::MaxLabels || |
| 697 | labelBytes > MailDisplaySnapshot::MaxMetadataBytes) { | ||
| 698 | 1 | result.error = QStringLiteral("Message labels exceed the supported metadata limit"); return result; | |
| 699 | } | ||
| 700 |
3/3✓ Branch 1027 → 1028 taken 382 times.
✓ Branch 1028 → 1029 taken 382 times.
✓ Branch 1029 → 1030 taken 382 times.
|
382 | row.header.labels.append(labels.value(1).toString()); |
| 701 | } | ||
| 702 | 1382 | } | |
| 703 |
2/2✓ Branch 1070 → 1052 taken 112008 times.
✓ Branch 1070 → 1071 taken 1393 times.
|
113401 | for (int i=0; i<result.rows.size(); ++i) { |
| 704 |
1/1✓ Branch 1052 → 1053 taken 112008 times.
|
112008 | auto &row=result.rows[i]; |
| 705 | 112008 | qint64 rowBytes=sizeof(ThreadProjectionRow)+64+row.cursor.path.size()+2*(row.header.subject.size()+row.header.from.size()); | |
| 706 |
4/4✓ Branch 1056 → 1057 taken 112008 times.
✓ Branch 1057 → 1058 taken 112008 times.
✓ Branch 1063 → 1059 taken 117 times.
✓ Branch 1063 → 1064 taken 112008 times.
|
112125 | for (const auto &label : row.header.labels) rowBytes+=sizeof(QString)+2*label.size(); |
| 707 |
3/4✓ Branch 1064 → 1065 taken 110629 times.
✓ Branch 1064 → 1068 taken 1379 times.
✗ Branch 1065 → 1066 not taken.
✓ Branch 1065 → 1068 taken 110629 times.
|
112008 | if (i>0 && pageBytes+rowBytes>3*1024*1024) { |
| 708 | ✗ | result.rows.resize(i); result.hasMore=true; break; | |
| 709 | } | ||
| 710 | 112008 | pageBytes+=rowBytes; | |
| 711 | } | ||
| 712 |
5/5✓ Branch 1071 → 1072 taken 103 times.
✓ Branch 1071 → 1075 taken 1290 times.
✓ Branch 1072 → 1073 taken 103 times.
✓ Branch 1073 → 1074 taken 103 times.
✓ Branch 1074 → 1075 taken 103 times.
|
1393 | if (request.before) std::reverse(result.rows.begin(), result.rows.end()); |
| 713 |
2/2✓ Branch 1075 → 1076 taken 76 times.
✓ Branch 1075 → 1087 taken 1317 times.
|
1393 | if (rebuildMatches) { |
| 714 | // Keep the TEMP match set only after the complete page succeeded. A | ||
| 715 | // cancelled or failed read rolls back both the set and its admission. | ||
| 716 |
2/5✓ Branch 1076 → 1077 taken 76 times.
✗ Branch 1077 → 1078 not taken.
✓ Branch 1077 → 1084 taken 76 times.
✗ Branch 1078 → 1079 not taken.
✗ Branch 1079 → 1080 not taken.
|
76 | if (!m_db.commit()) { result.error = m_db.lastError().text(); return result; } |
| 717 | 76 | rollback.dismiss(); | |
| 718 | 76 | m_matchScope = matchScope; | |
| 719 | 76 | m_matchCount = matchCount; | |
| 720 | 76 | m_matchLazy = lazy; | |
| 721 | 76 | m_matchReady = true; | |
| 722 | } | ||
| 723 | 1393 | result.outcome = ThreadProjectionPage::Outcome::Ready; | |
| 724 | 1393 | return result; | |
| 725 | 566288 | } | |
| 726 |