MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 97.0% 541 / 0 / 558
Functions: 94.7% 18 / 0 / 19
Branches: 64.3% 1212 / 0 / 1884

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