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: 85.2% 363 / 0 / 426
Functions: 85.0% 17 / 0 / 20
Branches: 73.3% 622 / 0 / 848

data/MailSnapshotReader.cpp
Line Branch Exec Source
1 #include "MailSnapshotReader.h"
2
3 #include <QScopeGuard>
4 #include <QSqlDriver>
5 #include <QSqlError>
6 #include <QSqlQuery>
7 #include <QVariant>
8 #include <QtEndian>
9 #include <sqlite3.h>
10
11 #include "util/MessageId.h"
12 #include "util/SpamHeaders.h"
13 #include "util/Utf8Digest.h"
14 #include "data/MailCacheSql.h"
15
16 using MailCacheSql::decodeThreadIds;
17
18
19 // displaySnapshot step: interrupt the read's SQLite statements once
20 // `cancelled` is set. Returns an error text when the connection cannot be
21 // interrupted (the read is then refused).
22 1098 QString MailSnapshotReader::installDisplayCancellation(const QSqlDatabase &db,
23 const std::atomic_bool *cancelled,
24 sqlite3 **database) {
25
5/8
✓ Branch 2 → 3 taken 1098 times.
✓ Branch 3 → 4 taken 1098 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 1098 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 1098 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 14 taken 1098 times.
1098 if (!db.isOpen() || !db.driver()) {
26 ✗ return QStringLiteral("Display reader is not open");
27 }
28
2/2
✓ Branch 14 → 15 taken 1098 times.
✓ Branch 15 → 16 taken 1098 times.
1098 auto handle = db.driver()->handle();
29
7/13
✓ Branch 16 → 17 taken 1098 times.
✓ Branch 17 → 18 taken 1098 times.
✗ Branch 17 → 22 not taken.
✓ Branch 18 → 19 taken 1098 times.
✓ Branch 19 → 20 taken 1098 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 1098 times.
✓ Branch 24 → 25 taken 1098 times.
✗ Branch 24 → 26 not taken.
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 32 taken 1098 times.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
1098 if (!handle.isValid() || QByteArray(handle.typeName()) != "sqlite3*") {
30 ✗ return QStringLiteral("Display reader requires interruptible SQLite");
31 }
32
1/1
✓ Branch 32 → 33 taken 1098 times.
1098 *database = *static_cast<sqlite3 **>(handle.data());
33
1/1
✓ Branch 34 → 35 taken 1098 times.
1098 sqlite3_progress_handler(*database, 1000, [](void *context) {
34 ✗ return static_cast<const std::atomic_bool *>(context)->load(
35 ✗ std::memory_order_relaxed) ? 1 : 0;
36 }, const_cast<std::atomic_bool *>(cancelled));
37 1098 return {};
38 1098 }
39
40 // displaySnapshot step: the bounded identity/size row of one mail.
41 1114 QString MailSnapshotReader::displayRowQuery() {
42 1114 return QStringLiteral(
43 "SELECT h.id, CASE WHEN length(CAST(f.path AS BLOB)) <= 4096 THEN f.path END, f.uidvalidity, "
44 "COALESCE(length(CAST(h.subject AS BLOB)),0) + "
45 "COALESCE(length(CAST(h.from_addr AS BLOB)),0) + "
46 "COALESCE(length(CAST(h.to_addr AS BLOB)),0) + "
47 "COALESCE(length(CAST(h.message_id AS BLOB)),0) + "
48 "COALESCE(length(CAST(h.in_reply_to AS BLOB)),0) + "
49 "COALESCE(length(CAST(h.ref_ids AS BLOB)),0), "
50 "COALESCE(length(CAST(b.text_plain AS BLOB)),0), "
51 "COALESCE(length(CAST(b.text_html AS BLOB)),0), b.header_id "
52 "FROM folders f JOIN headers h ON h.folder_id=f.id "
53 "LEFT JOIN bodies b ON b.header_id=h.id "
54 "WHERE f.account=:account AND f.id=:fid AND h.uid=:uid");
55 }
56
57 // displaySnapshot step: header fields of the selected row.
58 1107 void MailSnapshotReader::readDisplayHeader(const QSqlQuery &query, qint64 uid, qint64 folderId,
59 MailHeader &header) {
60 1107 header.uid = uid;
61 1107 header.folderId = folderId;
62
2/2
✓ Branch 2 → 3 taken 1107 times.
✓ Branch 3 → 4 taken 1107 times.
1107 header.subject = query.value(0).toString();
63
2/2
✓ Branch 7 → 8 taken 1107 times.
✓ Branch 8 → 9 taken 1107 times.
1107 header.from = query.value(1).toString();
64
2/2
✓ Branch 12 → 13 taken 1107 times.
✓ Branch 13 → 14 taken 1107 times.
1107 header.to = query.value(2).toString();
65
4/4
✓ Branch 17 → 18 taken 1107 times.
✓ Branch 18 → 19 taken 1107 times.
✓ Branch 20 → 21 taken 1057 times.
✓ Branch 20 → 28 taken 50 times.
1107 if (!query.value(3).isNull())
66
3/3
✓ Branch 21 → 22 taken 1057 times.
✓ Branch 22 → 23 taken 1057 times.
✓ Branch 23 → 24 taken 1057 times.
1057 header.date = QDateTime::fromSecsSinceEpoch(query.value(3).toLongLong());
67
2/2
✓ Branch 28 → 29 taken 1107 times.
✓ Branch 29 → 30 taken 1107 times.
1107 header.flags = query.value(4).toUInt();
68
2/2
✓ Branch 31 → 32 taken 1107 times.
✓ Branch 32 → 33 taken 1107 times.
1107 header.size = query.value(5).toLongLong();
69
2/2
✓ Branch 34 → 35 taken 1107 times.
✓ Branch 35 → 36 taken 1107 times.
1107 header.hasAttachments = query.value(6).toBool();
70
3/3
✓ Branch 37 → 38 taken 1107 times.
✓ Branch 38 → 39 taken 1107 times.
✓ Branch 39 → 40 taken 1107 times.
1107 header.messageId = MessageId::canonicalFirst(query.value(7).toString());
71
3/3
✓ Branch 44 → 45 taken 1107 times.
✓ Branch 45 → 46 taken 1107 times.
✓ Branch 46 → 47 taken 1107 times.
1107 header.inReplyToIds = decodeThreadIds(query.value(8).toString());
72
2/2
✓ Branch 52 → 53 taken 35 times.
✓ Branch 52 → 55 taken 1072 times.
1107 if (!header.inReplyToIds.isEmpty()) header.inReplyTo = header.inReplyToIds.first();
73
3/3
✓ Branch 55 → 56 taken 1107 times.
✓ Branch 56 → 57 taken 1107 times.
✓ Branch 57 → 58 taken 1107 times.
1107 header.references = decodeThreadIds(query.value(9).toString());
74
2/2
✓ Branch 62 → 63 taken 1107 times.
✓ Branch 63 → 64 taken 1107 times.
1107 header.isSpam = query.value(10).toBool();
75 // Sprint 105 S105.2.3: the verdict stored at header sync (NULL tool = unknown).
76
2/2
✓ Branch 65 → 66 taken 1107 times.
✓ Branch 66 → 67 taken 1107 times.
1107 header.spamKnown = !query.value(13).isNull();
77
6/6
✓ Branch 68 → 69 taken 1107 times.
✓ Branch 69 → 70 taken 1107 times.
✓ Branch 71 → 72 taken 4 times.
✓ Branch 71 → 77 taken 1103 times.
✓ Branch 72 → 73 taken 4 times.
✓ Branch 73 → 74 taken 4 times.
1107 if (!query.value(11).isNull()) header.spamScore = query.value(11).toDouble();
78
6/6
✓ Branch 77 → 78 taken 1107 times.
✓ Branch 78 → 79 taken 1107 times.
✓ Branch 80 → 81 taken 4 times.
✓ Branch 80 → 86 taken 1103 times.
✓ Branch 81 → 82 taken 4 times.
✓ Branch 82 → 83 taken 4 times.
1107 if (!query.value(12).isNull()) header.spamThreshold = query.value(12).toDouble();
79
2/2
✓ Branch 86 → 87 taken 1107 times.
✓ Branch 87 → 88 taken 1107 times.
1107 header.spamTool = query.value(13).toString();
80 1107 }
81
82 // displaySnapshot step (P90.8): the body columns the reading mode can
83 // display; an empty preferred variant loads both so the fallback works.
84 896 QString MailSnapshotReader::displayBodyProjection(MailDisplaySnapshot::PreferredContent preferred,
85 qint64 plainBytes, qint64 htmlBytes,
86 MailBody::Projection &bodyProjection) {
87
4/4
✓ Branch 2 → 3 taken 824 times.
✓ Branch 2 → 5 taken 72 times.
✓ Branch 3 → 4 taken 821 times.
✓ Branch 3 → 5 taken 3 times.
896 const bool textProjection = preferred == MailDisplaySnapshot::PreferredContent::Text &&
88 plainBytes > 0;
89
4/4
✓ Branch 6 → 7 taken 34 times.
✓ Branch 6 → 9 taken 862 times.
✓ Branch 7 → 8 taken 24 times.
✓ Branch 7 → 9 taken 10 times.
896 const bool htmlProjection = preferred == MailDisplaySnapshot::PreferredContent::Html &&
90 htmlBytes > 0;
91
2/2
✓ Branch 10 → 11 taken 821 times.
✓ Branch 10 → 12 taken 75 times.
971 bodyProjection = textProjection ? MailBody::Projection::TextOnly
92
2/2
✓ Branch 12 → 13 taken 24 times.
✓ Branch 12 → 14 taken 51 times.
75 : htmlProjection ? MailBody::Projection::HtmlOnly
93 : MailBody::Projection::Full;
94 // F100-56: the header block's first 16 KiB for the spam filter's lines.
95 896 const QString headerPrefix = QStringLiteral(", substr(CAST(raw_body AS BLOB),1,16384) FROM bodies WHERE header_id=:hid");
96 return textProjection
97
2/6
✓ Branch 45 → 46 taken 821 times.
✓ Branch 45 → 47 taken 75 times.
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
1717 ? QStringLiteral("text_plain, ''") + headerPrefix
98 : htmlProjection
99
6/10
✓ Branch 25 → 26 taken 24 times.
✓ Branch 25 → 30 taken 51 times.
✓ Branch 41 → 42 taken 24 times.
✓ Branch 41 → 43 taken 872 times.
✓ Branch 43 → 44 taken 821 times.
✓ Branch 43 → 45 taken 75 times.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 60 not taken.
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 64 not taken.
995 ? QStringLiteral("'' , text_html") + headerPrefix
100
11/15
✓ Branch 20 → 21 taken 821 times.
✓ Branch 20 → 25 taken 75 times.
✓ Branch 24 → 34 taken 821 times.
✓ Branch 29 → 34 taken 24 times.
✓ Branch 33 → 34 taken 51 times.
✓ Branch 35 → 36 taken 51 times.
✓ Branch 35 → 37 taken 845 times.
✓ Branch 37 → 38 taken 51 times.
✓ Branch 37 → 39 taken 845 times.
✓ Branch 39 → 40 taken 24 times.
✓ Branch 39 → 41 taken 872 times.
✗ Branch 50 → 51 not taken.
✗ Branch 50 → 52 not taken.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
3584 : QStringLiteral("text_plain, text_html") + headerPrefix;
101 896 }
102
103 // displaySnapshot step: the body columns of displayBodyProjection().
104 896 void MailSnapshotReader::readDisplayBody(const QSqlQuery &query, MailBody &body) {
105
2/2
✓ Branch 2 → 3 taken 896 times.
✓ Branch 3 → 4 taken 896 times.
896 body.textPlain = query.value(0).toString();
106
2/2
✓ Branch 7 → 8 taken 896 times.
✓ Branch 8 → 9 taken 896 times.
896 body.textHtml = query.value(1).toString();
107
3/3
✓ Branch 12 → 13 taken 896 times.
✓ Branch 13 → 14 taken 896 times.
✓ Branch 14 → 15 taken 896 times.
896 body.spamHeaders = SpamHeaders::extract(query.value(2).toByteArray()); // F100-56
108 // The display reader already runs on a resident SQL worker. Carry the
109 // HTML-light digest so the first Quick frame avoids a full HTML scan.
110
5/6
✓ Branch 20 → 21 taken 47 times.
✓ Branch 20 → 24 taken 849 times.
✓ Branch 22 → 23 taken 47 times.
✗ Branch 22 → 24 not taken.
✓ Branch 25 → 26 taken 47 times.
✓ Branch 25 → 34 taken 849 times.
896 if (!body.textHtml.isEmpty() && body.textHtml.size() <= 1024 * 1024) {
111
1/1
✓ Branch 27 → 28 taken 47 times.
47 const auto digest = MailJD::sha256Utf8HexUpTo(body.textHtml, 1024 * 1024);
112
1/2
✓ Branch 29 → 30 taken 47 times.
✗ Branch 29 → 32 not taken.
47 if (digest) body.htmlDigest = *digest;
113 47 }
114 896 }
115
116 // displaySnapshot step: one bounded attachment metadata row.
117 278 Attachment MailSnapshotReader::readDisplayAttachment(const QSqlQuery &query) {
118 278 Attachment attachment;
119
2/2
✓ Branch 2 → 3 taken 278 times.
✓ Branch 3 → 4 taken 278 times.
278 attachment.id = query.value(0).toLongLong();
120
2/2
✓ Branch 5 → 6 taken 278 times.
✓ Branch 6 → 7 taken 278 times.
278 attachment.filename = query.value(1).toString();
121
2/2
✓ Branch 10 → 11 taken 278 times.
✓ Branch 11 → 12 taken 278 times.
278 attachment.contentType = query.value(2).toString();
122
2/2
✓ Branch 15 → 16 taken 278 times.
✓ Branch 16 → 17 taken 278 times.
278 attachment.size = query.value(3).toLongLong();
123
2/2
✓ Branch 18 → 19 taken 278 times.
✓ Branch 19 → 20 taken 278 times.
278 attachment.contentId = query.value(4).toString();
124
2/2
✓ Branch 23 → 24 taken 278 times.
✓ Branch 24 → 25 taken 278 times.
278 const int presentation = query.value(5).toInt();
125
2/4
✓ Branch 26 → 27 taken 278 times.
✗ Branch 26 → 29 not taken.
✓ Branch 27 → 28 taken 278 times.
✗ Branch 27 → 29 not taken.
278 if (presentation >= 0 && presentation <= static_cast<int>(AttachmentPresentation::RejectedInline))
126 278 attachment.presentation = static_cast<AttachmentPresentation>(presentation);
127 278 return attachment;
128 ✗ }
129
130 1115 MailDisplaySnapshot MailSnapshotReader::displaySnapshot(
131 const QString &accountId, qint64 folderId, qint64 uid,
132 MailDisplaySnapshot::PreferredContent preferred,
133 const std::atomic_bool *cancelled) {
134 1115 MailDisplaySnapshot result;
135 9223 const auto superseded = [&] {
136
4/4
✓ Branch 2 → 3 taken 8600 times.
✓ Branch 2 → 6 taken 623 times.
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 8596 times.
9223 return cancelled && cancelled->load(std::memory_order_relaxed);
137 1115 };
138 1115 sqlite3 *database = nullptr;
139 13 const auto rollbackRead = [&] {
140
2/2
✓ Branch 2 → 3 taken 4 times.
✓ Branch 2 → 4 taken 9 times.
13 if (database) sqlite3_progress_handler(database, 0, nullptr, nullptr);
141 13 m_db.rollback();
142 1128 };
143 5 const auto fail = [&](const QString &error) {
144 5 rollbackRead();
145 5 result = {};
146 5 result.error = error;
147 5 return result;
148 5 };
149
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 1115 times.
1115 if (superseded()) return result;
150
2/2
✓ Branch 7 → 8 taken 1098 times.
✓ Branch 7 → 15 taken 17 times.
1115 if (cancelled) {
151
1/1
✓ Branch 8 → 9 taken 1098 times.
1098 result.error = installDisplayCancellation(m_db, cancelled, &database);
152
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 15 taken 1098 times.
1098 if (!result.error.isEmpty()) return result;
153 }
154 3345 const auto clearProgress = qScopeGuard([database] {
155
2/2
✓ Branch 2 → 3 taken 1098 times.
✓ Branch 2 → 4 taken 17 times.
1115 if (database) sqlite3_progress_handler(database, 0, nullptr, nullptr);
156 1115 });
157
2/3
✓ Branch 16 → 17 taken 1115 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 25 taken 1115 times.
1115 if (!m_db.transaction())
158 ✗ return fail(m_db.lastError().text());
159
3/3
✓ Branch 26 → 27 taken 1 time.
✓ Branch 26 → 35 taken 1114 times.
✓ Branch 30 → 31 taken 1 time.
1116 if (superseded()) return fail(QStringLiteral("Display read superseded"));
160
1/1
✓ Branch 35 → 36 taken 1114 times.
1114 QSqlQuery query(m_db);
161
2/2
✓ Branch 36 → 37 taken 1114 times.
✓ Branch 37 → 38 taken 1114 times.
1114 query.prepare(displayRowQuery());
162
1/1
✓ Branch 44 → 45 taken 1114 times.
2228 query.bindValue(QStringLiteral(":account"), accountId);
163
1/1
✓ Branch 53 → 54 taken 1114 times.
2228 query.bindValue(QStringLiteral(":fid"), folderId);
164
1/1
✓ Branch 62 → 63 taken 1114 times.
2228 query.bindValue(QStringLiteral(":uid"), uid);
165
2/3
✓ Branch 66 → 67 taken 1114 times.
✗ Branch 67 → 68 not taken.
✓ Branch 67 → 75 taken 1114 times.
1114 if (!query.exec())
166 ✗ return fail(query.lastError().text());
167
3/3
✓ Branch 76 → 77 taken 1 time.
✓ Branch 76 → 85 taken 1113 times.
✓ Branch 80 → 81 taken 1 time.
1115 if (superseded()) return fail(QStringLiteral("Display read superseded"));
168
3/3
✓ Branch 85 → 86 taken 1113 times.
✓ Branch 86 → 87 taken 2 times.
✓ Branch 86 → 100 taken 1111 times.
1113 if (!query.next()) {
169
3/7
✓ Branch 87 → 88 taken 2 times.
✓ Branch 88 → 89 taken 2 times.
✗ Branch 90 → 91 not taken.
✓ Branch 90 → 98 taken 2 times.
✗ Branch 91 → 92 not taken.
✗ Branch 92 → 93 not taken.
✗ Branch 93 → 94 not taken.
2 if (query.lastError().isValid()) return fail(query.lastError().text());
170 2 result.outcome = MailDisplaySnapshot::Outcome::HeaderMissing;
171
1/1
✓ Branch 98 → 99 taken 2 times.
2 rollbackRead();
172 2 return result;
173 }
174
1/3
✗ Branch 101 → 102 not taken.
✓ Branch 101 → 110 taken 1111 times.
✗ Branch 105 → 106 not taken.
1111 if (superseded()) return fail(QStringLiteral("Display read superseded"));
175
3/4
✓ Branch 110 → 111 taken 1111 times.
✓ Branch 111 → 112 taken 1111 times.
✗ Branch 113 → 114 not taken.
✓ Branch 113 → 116 taken 1111 times.
1111 if (query.value(1).isNull()) {
176 ✗ result.outcome = MailDisplaySnapshot::Outcome::Limited;
177 ✗ rollbackRead();
178 ✗ return result;
179 }
180
2/2
✓ Branch 116 → 117 taken 1111 times.
✓ Branch 117 → 118 taken 1111 times.
1111 const qint64 headerId = query.value(0).toLongLong();
181
2/2
✓ Branch 119 → 120 taken 1111 times.
✓ Branch 120 → 121 taken 1111 times.
1111 result.folderPath = query.value(1).toString();
182
2/2
✓ Branch 124 → 125 taken 1111 times.
✓ Branch 125 → 126 taken 1111 times.
1111 result.uidValidity = query.value(2).toUInt();
183
2/2
✓ Branch 127 → 128 taken 1111 times.
✓ Branch 128 → 129 taken 1111 times.
1111 const qint64 plainBytes = query.value(4).toLongLong();
184
2/2
✓ Branch 130 → 131 taken 1111 times.
✓ Branch 131 → 132 taken 1111 times.
1111 const qint64 htmlBytes = query.value(5).toLongLong();
185
2/2
✓ Branch 133 → 134 taken 1111 times.
✓ Branch 134 → 135 taken 1111 times.
1111 const bool bodyPresent = !query.value(6).isNull();
186
7/10
✓ Branch 136 → 137 taken 1111 times.
✓ Branch 137 → 138 taken 1111 times.
✓ Branch 138 → 139 taken 1110 times.
✓ Branch 138 → 140 taken 1 time.
✓ Branch 142 → 143 taken 1111 times.
✗ Branch 142 → 144 not taken.
✓ Branch 144 → 145 taken 2 times.
✓ Branch 144 → 147 taken 1109 times.
✗ Branch 508 → 509 not taken.
✗ Branch 508 → 510 not taken.
2221 if (query.value(3).toLongLong() > MailDisplaySnapshot::MaxMetadataBytes ||
187
2/2
✓ Branch 139 → 140 taken 1 time.
✓ Branch 139 → 141 taken 1109 times.
1110 plainBytes + htmlBytes > MailDisplaySnapshot::MaxTextBytes / 2) {
188 2 result.outcome = MailDisplaySnapshot::Outcome::Limited;
189
1/1
✓ Branch 145 → 146 taken 2 times.
2 rollbackRead();
190 2 return result;
191 }
192
1/1
✓ Branch 147 → 148 taken 1109 times.
1109 query.finish();
193
1/1
✓ Branch 151 → 152 taken 1109 times.
1109 query.prepare(QStringLiteral(
194 "SELECT subject, from_addr, to_addr, date, flags, size, has_attachments, "
195 "message_id, in_reply_to, ref_ids, is_spam, spam_score, spam_threshold, "
196 "CASE WHEN length(CAST(spam_tool AS BLOB)) <= 64 THEN spam_tool END FROM headers WHERE id=:hid"));
197
1/1
✓ Branch 159 → 160 taken 1109 times.
2218 query.bindValue(QStringLiteral(":hid"), headerId);
198
5/11
✓ Branch 163 → 164 taken 1109 times.
✓ Branch 164 → 165 taken 1109 times.
✗ Branch 164 → 167 not taken.
✓ Branch 165 → 166 taken 1109 times.
✗ Branch 166 → 167 not taken.
✓ Branch 166 → 168 taken 1109 times.
✗ Branch 169 → 170 not taken.
✓ Branch 169 → 177 taken 1109 times.
✗ Branch 170 → 171 not taken.
✗ Branch 171 → 172 not taken.
✗ Branch 172 → 173 not taken.
1109 if (!query.exec() || !query.next()) return fail(query.lastError().text());
199
3/3
✓ Branch 178 → 179 taken 2 times.
✓ Branch 178 → 187 taken 1107 times.
✓ Branch 182 → 183 taken 2 times.
1111 if (superseded()) return fail(QStringLiteral("Display read superseded"));
200 1107 auto &header = result.header;
201
1/1
✓ Branch 187 → 188 taken 1107 times.
1107 readDisplayHeader(query, uid, folderId, header);
202
1/1
✓ Branch 188 → 189 taken 1107 times.
1107 query.finish();
203
1/1
✓ Branch 192 → 193 taken 1107 times.
1107 query.prepare(QStringLiteral(
204 "SELECT CASE WHEN length(CAST(label AS BLOB)) <= 1048576 THEN label END, "
205 "length(CAST(label AS BLOB)) FROM mail_labels "
206 "WHERE header_id=:hid LIMIT 257"));
207
1/1
✓ Branch 200 → 201 taken 1107 times.
2214 query.bindValue(QStringLiteral(":hid"), headerId);
208
6/6
✓ Branch 204 → 205 taken 1107 times.
✓ Branch 205 → 206 taken 1 time.
✓ Branch 205 → 213 taken 1106 times.
✓ Branch 206 → 207 taken 1 time.
✓ Branch 207 → 208 taken 1 time.
✓ Branch 208 → 209 taken 1 time.
1107 if (!query.exec()) return fail(query.lastError().text());
209 1106 qint64 metadataBytes = 0;
210
3/3
✓ Branch 241 → 242 taken 1381 times.
✓ Branch 242 → 214 taken 277 times.
✓ Branch 242 → 243 taken 1104 times.
1381 while (query.next()) {
211
1/3
✗ Branch 215 → 216 not taken.
✓ Branch 215 → 224 taken 277 times.
✗ Branch 219 → 220 not taken.
277 if (superseded()) return fail(QStringLiteral("Display read superseded"));
212
2/2
✓ Branch 224 → 225 taken 277 times.
✓ Branch 225 → 226 taken 277 times.
277 metadataBytes += query.value(1).toLongLong();
213
6/6
✓ Branch 228 → 229 taken 276 times.
✓ Branch 228 → 230 taken 1 time.
✓ Branch 229 → 230 taken 1 time.
✓ Branch 229 → 231 taken 275 times.
✓ Branch 232 → 233 taken 2 times.
✓ Branch 232 → 235 taken 275 times.
277 if (header.labels.size() >= MailDisplaySnapshot::MaxLabels ||
214 metadataBytes > MailDisplaySnapshot::MaxMetadataBytes) {
215 2 result.outcome = MailDisplaySnapshot::Outcome::Limited;
216
1/1
✓ Branch 233 → 234 taken 2 times.
2 rollbackRead();
217 2 return result;
218 }
219
3/3
✓ Branch 235 → 236 taken 275 times.
✓ Branch 236 → 237 taken 275 times.
✓ Branch 237 → 238 taken 275 times.
275 header.labels.append(query.value(0).toString());
220 }
221
3/7
✓ Branch 243 → 244 taken 1104 times.
✓ Branch 244 → 245 taken 1104 times.
✗ Branch 246 → 247 not taken.
✓ Branch 246 → 254 taken 1104 times.
✗ Branch 247 → 248 not taken.
✗ Branch 248 → 249 not taken.
✗ Branch 249 → 250 not taken.
1104 if (query.lastError().isValid()) return fail(query.lastError().text());
222
1/1
✓ Branch 254 → 255 taken 1104 times.
1104 query.finish();
223
1/3
✗ Branch 256 → 257 not taken.
✓ Branch 256 → 265 taken 1104 times.
✗ Branch 260 → 261 not taken.
1104 if (superseded()) return fail(QStringLiteral("Display read superseded"));
224
2/2
✓ Branch 265 → 266 taken 896 times.
✓ Branch 265 → 385 taken 208 times.
1104 if (bodyPresent) {
225 // P90.8: load exactly the variant the reading mode can display. The
226 // skipped column reads as an empty value, which is precisely the
227 // emptiness signal the reader's mode fallback relies on. When the
228 // preferred variant is empty, both are loaded so the native/plain
229 // fallback keeps working.
230 const QString projection =
231
1/1
✓ Branch 266 → 267 taken 896 times.
896 displayBodyProjection(preferred, plainBytes, htmlBytes, result.body.projection);
232
2/2
✓ Branch 270 → 271 taken 896 times.
✓ Branch 271 → 272 taken 896 times.
896 query.prepare(QStringLiteral("SELECT ") + projection);
233
1/1
✓ Branch 280 → 281 taken 896 times.
1792 query.bindValue(QStringLiteral(":hid"), headerId);
234
5/11
✓ Branch 284 → 285 taken 896 times.
✓ Branch 285 → 286 taken 896 times.
✗ Branch 285 → 288 not taken.
✓ Branch 286 → 287 taken 896 times.
✗ Branch 287 → 288 not taken.
✓ Branch 287 → 289 taken 896 times.
✗ Branch 290 → 291 not taken.
✓ Branch 290 → 298 taken 896 times.
✗ Branch 291 → 292 not taken.
✗ Branch 292 → 293 not taken.
✗ Branch 293 → 294 not taken.
896 if (!query.exec() || !query.next()) return fail(query.lastError().text());
235
1/3
✗ Branch 299 → 300 not taken.
✓ Branch 299 → 308 taken 896 times.
✗ Branch 303 → 304 not taken.
896 if (superseded()) return fail(QStringLiteral("Display read superseded"));
236 896 result.body.uid = uid;
237 896 result.body.cacheUidValidity = result.uidValidity;
238 896 result.body.cacheAccountId = accountId;
239
1/1
✓ Branch 309 → 310 taken 896 times.
896 readDisplayBody(query, result.body);
240
1/1
✓ Branch 310 → 311 taken 896 times.
896 query.finish();
241
1/1
✓ Branch 314 → 315 taken 896 times.
896 query.prepare(QStringLiteral(
242 "SELECT id, "
243 "CASE WHEN length(CAST(filename AS BLOB)) <= 1048576 THEN filename END, "
244 "CASE WHEN length(CAST(content_type AS BLOB)) <= 1048576 THEN content_type END, size, "
245 "CASE WHEN length(CAST(content_id AS BLOB)) <= 1048576 THEN content_id END, presentation, "
246 "COALESCE(length(CAST(filename AS BLOB)),0) + "
247 "COALESCE(length(CAST(content_type AS BLOB)),0) + "
248 "COALESCE(length(CAST(content_id AS BLOB)),0) "
249 "FROM attachments WHERE header_id=:hid ORDER BY id LIMIT 257"));
250
1/1
✓ Branch 322 → 323 taken 896 times.
1792 query.bindValue(QStringLiteral(":hid"), headerId);
251
2/6
✓ Branch 326 → 327 taken 896 times.
✗ Branch 327 → 328 not taken.
✓ Branch 327 → 335 taken 896 times.
✗ Branch 328 → 329 not taken.
✗ Branch 329 → 330 not taken.
✗ Branch 330 → 331 not taken.
896 if (!query.exec()) return fail(query.lastError().text());
252
3/3
✓ Branch 365 → 366 taken 1174 times.
✓ Branch 366 → 336 taken 280 times.
✓ Branch 366 → 367 taken 894 times.
1174 while (query.next()) {
253
1/3
✗ Branch 337 → 338 not taken.
✓ Branch 337 → 346 taken 280 times.
✗ Branch 341 → 342 not taken.
280 if (superseded()) return fail(QStringLiteral("Display read superseded"));
254
2/2
✓ Branch 346 → 347 taken 280 times.
✓ Branch 347 → 348 taken 280 times.
280 metadataBytes += query.value(6).toLongLong();
255
6/6
✓ Branch 350 → 351 taken 279 times.
✓ Branch 350 → 352 taken 1 time.
✓ Branch 351 → 352 taken 1 time.
✓ Branch 351 → 353 taken 278 times.
✓ Branch 354 → 355 taken 2 times.
✓ Branch 354 → 359 taken 278 times.
280 if (result.body.attachments.size() >= MailDisplaySnapshot::MaxAttachments ||
256 metadataBytes > MailDisplaySnapshot::MaxMetadataBytes) {
257 2 result.body = {};
258 2 result.outcome = MailDisplaySnapshot::Outcome::Limited;
259
1/1
✓ Branch 357 → 358 taken 2 times.
2 rollbackRead();
260 2 return result;
261 }
262
1/1
✓ Branch 359 → 360 taken 278 times.
278 Attachment attachment = readDisplayAttachment(query);
263
1/1
✓ Branch 361 → 362 taken 278 times.
278 result.body.attachments.append(std::move(attachment));
264 278 }
265
3/7
✓ Branch 367 → 368 taken 894 times.
✓ Branch 368 → 369 taken 894 times.
✗ Branch 370 → 371 not taken.
✓ Branch 370 → 378 taken 894 times.
✗ Branch 371 → 372 not taken.
✗ Branch 372 → 373 not taken.
✗ Branch 373 → 374 not taken.
894 if (query.lastError().isValid()) return fail(query.lastError().text());
266
1/1
✓ Branch 378 → 379 taken 894 times.
894 query.finish();
267 896 }
268
1/3
✗ Branch 386 → 387 not taken.
✓ Branch 386 → 395 taken 1102 times.
✗ Branch 390 → 391 not taken.
1102 if (superseded()) return fail(QStringLiteral("Display read superseded"));
269
2/6
✓ Branch 395 → 396 taken 1102 times.
✗ Branch 396 → 397 not taken.
✓ Branch 396 → 404 taken 1102 times.
✗ Branch 397 → 398 not taken.
✗ Branch 398 → 399 not taken.
✗ Branch 399 → 400 not taken.
1102 if (!m_db.commit()) return fail(m_db.lastError().text());
270
2/2
✓ Branch 404 → 405 taken 894 times.
✓ Branch 404 → 406 taken 208 times.
1102 result.outcome = bodyPresent ? MailDisplaySnapshot::Outcome::Ready
271 : MailDisplaySnapshot::Outcome::BodyMissing;
272 1102 return result;
273 1115 }
274
275 198 MailSelectionResult MailSnapshotReader::selectionSnapshot(const MailSelectionQuery &selection,
276 const std::atomic_bool &cancelled) {
277 198 MailSelectionResult result;
278 // F-41: explicit membership (visible-order range) needs only valid UIDs;
279 // the date/UID endpoints are required for the implicit range form.
280 bool explicitMembership =
281
3/4
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 7 taken 191 times.
✓ Branch 5 → 6 taken 7 times.
✗ Branch 5 → 7 not taken.
205 !selection.explicitUids.isEmpty() &&
282 7 selection.explicitUids.size() <= MailSelectionQuery::MaximumExplicitUids;
283
2/2
✓ Branch 15 → 10 taken 35 times.
✓ Branch 15 → 16 taken 198 times.
233 for (qint64 uid : selection.explicitUids)
284
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 35 times.
35 if (uid < 0) explicitMembership = false;
285
1/2
✓ Branch 19 → 20 taken 198 times.
✗ Branch 19 → 29 not taken.
396 if (selection.accountId.isEmpty() || selection.accountId.size() > 4096 ||
286
5/8
✓ Branch 17 → 18 taken 198 times.
✗ Branch 17 → 29 not taken.
✓ Branch 20 → 21 taken 198 times.
✗ Branch 20 → 29 not taken.
✓ Branch 22 → 23 taken 198 times.
✗ Branch 22 → 29 not taken.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 31 → 39 taken 197 times.
594 selection.folderId <= 0 || selection.text.size() > 1024 ||
287
4/4
✓ Branch 23 → 24 taken 105 times.
✓ Branch 23 → 30 taken 93 times.
✓ Branch 24 → 25 taken 98 times.
✓ Branch 24 → 30 taken 7 times.
198 (selection.range && !explicitMembership &&
288
2/2
✓ Branch 25 → 26 taken 97 times.
✓ Branch 25 → 29 taken 1 time.
98 (selection.first.folderId != selection.folderId ||
289
1/2
✓ Branch 26 → 27 taken 97 times.
✗ Branch 26 → 29 not taken.
97 selection.last.folderId != selection.folderId ||
290
2/4
✓ Branch 27 → 28 taken 97 times.
✗ Branch 27 → 29 not taken.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 97 times.
97 selection.first.uid < 0 || selection.last.uid < 0))) {
291 1 result.error = QStringLiteral("Invalid selection scope");
292 1 return result;
293 }
294
2/2
✓ Branch 40 → 41 taken 1 time.
✓ Branch 40 → 42 taken 196 times.
197 if (cancelled.load()) { result.outcome = MailSelectionResult::Outcome::Cancelled; return result; }
295
2/5
✓ Branch 42 → 43 taken 196 times.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 50 taken 196 times.
✗ Branch 44 → 45 not taken.
✗ Branch 45 → 46 not taken.
196 if (!m_db.transaction()) { result.error = m_db.lastError().text(); return result; }
296 392 const auto rollback = qScopeGuard([this] { m_db.rollback(); });
297
1/1
✓ Branch 51 → 52 taken 196 times.
196 QSqlQuery query(m_db);
298
1/1
✓ Branch 55 → 56 taken 196 times.
196 query.prepare(QStringLiteral("SELECT CASE WHEN length(CAST(path AS BLOB))<=4096 THEN path END, uidvalidity "
299 "FROM folders WHERE account=:account AND id=:folder"));
300
2/2
✓ Branch 60 → 61 taken 196 times.
✓ Branch 61 → 62 taken 196 times.
196 query.bindValue(":account", selection.accountId);
301
2/2
✓ Branch 66 → 67 taken 196 times.
✓ Branch 67 → 68 taken 196 times.
196 query.bindValue(":folder", selection.folderId);
302
2/5
✓ Branch 70 → 71 taken 196 times.
✗ Branch 71 → 72 not taken.
✓ Branch 71 → 78 taken 196 times.
✗ Branch 72 → 73 not taken.
✗ Branch 73 → 74 not taken.
196 if (!query.exec()) { result.error = query.lastError().text(); return result; }
303
3/3
✓ Branch 78 → 79 taken 196 times.
✓ Branch 79 → 80 taken 1 time.
✓ Branch 79 → 92 taken 195 times.
196 if (!query.next()) {
304
3/4
✓ Branch 80 → 81 taken 1 time.
✓ Branch 81 → 82 taken 1 time.
✗ Branch 82 → 83 not taken.
✓ Branch 82 → 84 taken 1 time.
1 result.outcome = query.lastError().isValid() ? MailSelectionResult::Outcome::Failed
305 : MailSelectionResult::Outcome::MissingFolder;
306
2/2
✓ Branch 86 → 87 taken 1 time.
✓ Branch 87 → 88 taken 1 time.
1 result.error = query.lastError().text();
307 1 return result;
308 }
309
3/4
✓ Branch 92 → 93 taken 195 times.
✓ Branch 93 → 94 taken 195 times.
✗ Branch 95 → 96 not taken.
✓ Branch 95 → 97 taken 195 times.
195 if (query.value(0).isNull()) { result.outcome = MailSelectionResult::Outcome::Limited; return result; }
310
1/1
✓ Branch 97 → 98 taken 195 times.
195 auto snapshot = std::make_shared<MailSelectionSnapshot>();
311 195 snapshot->accountId = selection.accountId;
312 195 snapshot->folderId = selection.folderId;
313
2/2
✓ Branch 101 → 102 taken 195 times.
✓ Branch 102 → 103 taken 195 times.
195 snapshot->folderPath = query.value(0).toString();
314
2/2
✓ Branch 107 → 108 taken 195 times.
✓ Branch 108 → 109 taken 195 times.
195 snapshot->uidValidity = query.value(1).toUInt();
315
1/1
✓ Branch 111 → 112 taken 195 times.
195 query.finish();
316
3/3
✓ Branch 112 → 113 taken 195 times.
✓ Branch 113 → 114 taken 195 times.
✓ Branch 114 → 115 taken 195 times.
195 snapshot->storage = std::make_shared<QTemporaryDir>(QDir::tempPath() + "/mailjd-selection-XXXXXX");
317
2/4
✓ Branch 122 → 123 taken 195 times.
✗ Branch 123 → 124 not taken.
✓ Branch 123 → 130 taken 195 times.
✗ Branch 126 → 127 not taken.
195 if (!snapshot->storage->isValid()) { result.error = snapshot->storage->errorString(); return result; }
318
2/2
✓ Branch 131 → 132 taken 195 times.
✓ Branch 132 → 133 taken 195 times.
195 QFile output(snapshot->path());
319
5/8
✓ Branch 135 → 136 taken 195 times.
✓ Branch 136 → 137 taken 195 times.
✗ Branch 136 → 140 not taken.
✓ Branch 138 → 139 taken 195 times.
✗ Branch 139 → 140 not taken.
✓ Branch 139 → 141 taken 195 times.
✗ Branch 142 → 143 not taken.
✓ Branch 142 → 147 taken 195 times.
195 if (!output.open(QIODevice::WriteOnly) || !output.setPermissions(QFile::ReadOwner | QFile::WriteOwner)) {
320 ✗ result.error = output.errorString();
321 ✗ return result;
322 }
323 6316 const auto less = [](const MailSelectionKey &a, const MailSelectionKey &b) {
324
2/2
✓ Branch 4 → 5 taken 918 times.
✓ Branch 4 → 7 taken 5398 times.
6316 if (a.date.isValid() != b.date.isValid()) return !a.date.isValid();
325
6/6
✓ Branch 8 → 9 taken 5251 times.
✓ Branch 8 → 13 taken 147 times.
✓ Branch 11 → 12 taken 5005 times.
✓ Branch 11 → 13 taken 246 times.
✓ Branch 14 → 15 taken 5005 times.
✓ Branch 14 → 18 taken 393 times.
5398 if (a.date.isValid() && a.date.toSecsSinceEpoch() != b.date.toSecsSinceEpoch())
326 5005 return a.date.toSecsSinceEpoch() < b.date.toSecsSinceEpoch();
327 393 return a.uid < b.uid;
328 };
329 195 auto low = selection.first, high = selection.last;
330
7/7
✓ Branch 149 → 150 taken 104 times.
✓ Branch 149 → 153 taken 91 times.
✓ Branch 150 → 151 taken 104 times.
✓ Branch 151 → 152 taken 44 times.
✓ Branch 151 → 153 taken 60 times.
✓ Branch 154 → 155 taken 44 times.
✓ Branch 154 → 156 taken 151 times.
195 if (selection.range && less(high, low)) std::swap(low, high);
331 // F-41: visible-order range — membership by explicit UID set instead of
332 // the date/UID span.
333 195 QSet<qint64> explicitSet;
334
2/2
✓ Branch 157 → 158 taken 7 times.
✓ Branch 157 → 164 taken 188 times.
195 if (explicitMembership)
335 7 explicitSet = QSet<qint64>(selection.explicitUids.cbegin(),
336
1/1
✓ Branch 160 → 161 taken 7 times.
7 selection.explicitUids.cend());
337 195 const bool text = !selection.text.isEmpty();
338 390 QString sql = QStringLiteral("SELECT uid,date,") + (text
339
2/4
✓ Branch 168 → 169 taken 25 times.
✓ Branch 168 → 172 taken 170 times.
✗ Branch 464 → 465 not taken.
✗ Branch 464 → 466 not taken.
610 ? QStringLiteral("CASE WHEN COALESCE(length(CAST(subject AS BLOB)),0)<=1048576 THEN COALESCE(subject,'') END,"
340 "CASE WHEN COALESCE(length(CAST(from_addr AS BLOB)),0)<=1048576 THEN COALESCE(from_addr,'') END ")
341
6/8
✓ Branch 178 → 179 taken 195 times.
✓ Branch 179 → 180 taken 195 times.
✓ Branch 184 → 185 taken 170 times.
✓ Branch 184 → 186 taken 25 times.
✓ Branch 186 → 187 taken 25 times.
✓ Branch 186 → 188 taken 170 times.
✗ Branch 460 → 461 not taken.
✗ Branch 460 → 462 not taken.
610 : QStringLiteral("NULL,NULL ")) + QStringLiteral("FROM headers WHERE folder_id=:folder ");
342
3/3
✓ Branch 190 → 191 taken 17 times.
✓ Branch 190 → 198 taken 178 times.
✓ Branch 194 → 195 taken 17 times.
212 if (selection.unreadOnly) sql += QStringLiteral("AND (flags & :seen)=0 ");
343
3/3
✓ Branch 198 → 199 taken 16 times.
✓ Branch 198 → 206 taken 179 times.
✓ Branch 202 → 203 taken 16 times.
211 if (selection.starredOnly) sql += QStringLiteral("AND (flags & :flagged)!=0 ");
344
3/3
✓ Branch 206 → 207 taken 16 times.
✓ Branch 206 → 214 taken 179 times.
✓ Branch 210 → 211 taken 16 times.
211 if (selection.attachmentsOnly) sql += QStringLiteral("AND has_attachments!=0 ");
345
1/1
✓ Branch 217 → 218 taken 195 times.
195 sql += QStringLiteral("ORDER BY uid ASC");
346
1/1
✓ Branch 220 → 221 taken 195 times.
195 query.setForwardOnly(true);
347
1/1
✓ Branch 221 → 222 taken 195 times.
195 query.prepare(sql);
348
2/2
✓ Branch 224 → 225 taken 195 times.
✓ Branch 225 → 226 taken 195 times.
195 query.bindValue(":folder", selection.folderId);
349
4/4
✓ Branch 228 → 229 taken 17 times.
✓ Branch 228 → 236 taken 178 times.
✓ Branch 231 → 232 taken 17 times.
✓ Branch 232 → 233 taken 17 times.
212 if (selection.unreadOnly) query.bindValue(":seen", int(MailFlag::Seen));
350
4/4
✓ Branch 236 → 237 taken 16 times.
✓ Branch 236 → 244 taken 179 times.
✓ Branch 239 → 240 taken 16 times.
✓ Branch 240 → 241 taken 16 times.
211 if (selection.starredOnly) query.bindValue(":flagged", int(MailFlag::Flagged));
351
2/5
✓ Branch 244 → 245 taken 195 times.
✗ Branch 245 → 246 not taken.
✓ Branch 245 → 252 taken 195 times.
✗ Branch 246 → 247 not taken.
✗ Branch 247 → 248 not taken.
195 if (!query.exec()) { result.error = query.lastError().text(); return result; }
352 195 QByteArray buffer;
353
1/1
✓ Branch 252 → 253 taken 195 times.
195 buffer.reserve(64 * 1024);
354
3/3
✓ Branch 348 → 349 taken 126133 times.
✓ Branch 349 → 254 taken 125940 times.
✓ Branch 349 → 350 taken 193 times.
126133 while (query.next()) {
355
2/2
✓ Branch 255 → 256 taken 1 time.
✓ Branch 255 → 257 taken 125939 times.
125941 if (cancelled.load()) { result.outcome = MailSelectionResult::Outcome::Cancelled; return result; }
356 125939 MailSelectionKey key;
357
2/2
✓ Branch 258 → 259 taken 125939 times.
✓ Branch 259 → 260 taken 125939 times.
125939 key.uid = query.value(0).toLongLong();
358
1/2
✗ Branch 261 → 262 not taken.
✓ Branch 261 → 269 taken 125939 times.
125939 if (key.uid < 0) { result.error = QStringLiteral("Invalid cached mail identity"); return result; }
359
7/7
✓ Branch 269 → 270 taken 125939 times.
✓ Branch 270 → 271 taken 125939 times.
✓ Branch 272 → 273 taken 125634 times.
✓ Branch 272 → 280 taken 305 times.
✓ Branch 273 → 274 taken 125634 times.
✓ Branch 274 → 275 taken 125634 times.
✓ Branch 275 → 276 taken 125634 times.
125939 if (!query.value(1).isNull()) key.date = QDateTime::fromSecsSinceEpoch(query.value(1).toLongLong());
360
2/2
✓ Branch 280 → 281 taken 2906 times.
✓ Branch 280 → 284 taken 123033 times.
125939 if (explicitMembership) {
361
2/2
✓ Branch 282 → 283 taken 2871 times.
✓ Branch 282 → 293 taken 35 times.
2906 if (!explicitSet.contains(key.uid)) continue;
362
10/10
✓ Branch 284 → 285 taken 4574 times.
✓ Branch 284 → 290 taken 118459 times.
✓ Branch 285 → 286 taken 4574 times.
✓ Branch 286 → 287 taken 1638 times.
✓ Branch 286 → 289 taken 2936 times.
✓ Branch 287 → 288 taken 1638 times.
✓ Branch 288 → 289 taken 962 times.
✓ Branch 288 → 290 taken 676 times.
✓ Branch 291 → 292 taken 3898 times.
✓ Branch 291 → 293 taken 119135 times.
123033 } else if (selection.range && (less(key, low) || less(high, key))) {
363 3898 continue;
364 }
365
2/2
✓ Branch 293 → 294 taken 151 times.
✓ Branch 293 → 328 taken 119019 times.
119170 if (text) {
366
12/18
✓ Branch 294 → 295 taken 151 times.
✓ Branch 295 → 296 taken 151 times.
✓ Branch 296 → 297 taken 150 times.
✓ Branch 296 → 300 taken 1 time.
✓ Branch 297 → 298 taken 150 times.
✓ Branch 298 → 299 taken 150 times.
✗ Branch 299 → 300 not taken.
✓ Branch 299 → 301 taken 150 times.
✓ Branch 302 → 303 taken 150 times.
✓ Branch 302 → 304 taken 1 time.
✓ Branch 304 → 305 taken 151 times.
✗ Branch 304 → 306 not taken.
✓ Branch 306 → 307 taken 1 time.
✓ Branch 306 → 308 taken 150 times.
✗ Branch 535 → 536 not taken.
✗ Branch 535 → 537 not taken.
✗ Branch 539 → 540 not taken.
✗ Branch 539 → 541 not taken.
151 if (query.value(2).isNull() || query.value(3).isNull()) {
367 1 result.outcome = MailSelectionResult::Outcome::Limited;
368 1 return result; // Never return a partial, silently narrowed selection.
369 }
370
8/13
✓ Branch 308 → 309 taken 150 times.
✓ Branch 309 → 310 taken 150 times.
✓ Branch 310 → 311 taken 150 times.
✓ Branch 311 → 312 taken 138 times.
✓ Branch 311 → 317 taken 12 times.
✓ Branch 324 → 325 taken 150 times.
✗ Branch 324 → 326 not taken.
✓ Branch 326 → 327 taken 112 times.
✓ Branch 326 → 328 taken 38 times.
✗ Branch 551 → 552 not taken.
✗ Branch 551 → 553 not taken.
✗ Branch 555 → 556 not taken.
✗ Branch 555 → 557 not taken.
438 if (!query.value(2).toString().contains(selection.text, Qt::CaseInsensitive) &&
371
10/15
✓ Branch 312 → 313 taken 138 times.
✓ Branch 313 → 314 taken 138 times.
✓ Branch 314 → 315 taken 138 times.
✓ Branch 315 → 316 taken 112 times.
✓ Branch 315 → 317 taken 26 times.
✓ Branch 318 → 319 taken 138 times.
✓ Branch 318 → 320 taken 12 times.
✓ Branch 320 → 321 taken 138 times.
✓ Branch 320 → 322 taken 12 times.
✓ Branch 322 → 323 taken 150 times.
✗ Branch 322 → 324 not taken.
✗ Branch 543 → 544 not taken.
✗ Branch 543 → 545 not taken.
✗ Branch 547 → 548 not taken.
✗ Branch 547 → 549 not taken.
400 !query.value(3).toString().contains(selection.text, Qt::CaseInsensitive)) continue;
372 }
373 119057 const auto encoded = qToLittleEndian(key.uid);
374
1/1
✓ Branch 329 → 330 taken 119057 times.
119057 buffer.append(reinterpret_cast<const char *>(&encoded), sizeof(encoded));
375 119057 ++snapshot->count;
376
2/2
✓ Branch 332 → 333 taken 13 times.
✓ Branch 332 → 341 taken 119044 times.
119057 if (buffer.size() == 64 * 1024) {
377
2/4
✓ Branch 333 → 334 taken 13 times.
✗ Branch 335 → 336 not taken.
✓ Branch 335 → 340 taken 13 times.
✗ Branch 336 → 337 not taken.
13 if (output.write(buffer) != buffer.size()) { result.error = output.errorString(); return result; }
378
1/1
✓ Branch 340 → 341 taken 13 times.
13 buffer.clear();
379 }
380 125939 }
381
3/6
✓ Branch 350 → 351 taken 193 times.
✓ Branch 351 → 352 taken 193 times.
✗ Branch 353 → 354 not taken.
✓ Branch 353 → 360 taken 193 times.
✗ Branch 354 → 355 not taken.
✗ Branch 355 → 356 not taken.
193 if (query.lastError().isValid()) { result.error = query.lastError().text(); return result; }
382
5/9
✓ Branch 360 → 361 taken 193 times.
✓ Branch 362 → 363 taken 193 times.
✗ Branch 362 → 365 not taken.
✓ Branch 363 → 364 taken 193 times.
✗ Branch 364 → 365 not taken.
✓ Branch 364 → 366 taken 193 times.
✗ Branch 367 → 368 not taken.
✓ Branch 367 → 372 taken 193 times.
✗ Branch 368 → 369 not taken.
193 if (output.write(buffer) != buffer.size() || !output.flush()) { result.error = output.errorString(); return result; }
383
1/1
✓ Branch 372 → 373 taken 193 times.
193 output.close();
384
1/1
✓ Branch 373 → 374 taken 193 times.
193 query.finish();
385
1/2
✗ Branch 375 → 376 not taken.
✓ Branch 375 → 377 taken 193 times.
193 if (cancelled.load()) { result.outcome = MailSelectionResult::Outcome::Cancelled; return result; }
386 193 result.snapshot = std::move(snapshot);
387 193 result.outcome = MailSelectionResult::Outcome::Ready;
388 193 return result;
389 196 }
390
391 5 MailSnapshotReader::FlagSnapshotDiff MailSnapshotReader::compareFlagSnapshot(
392 const QString &account, const QString &folder, qint64 folderId,
393 quint32 uidValidity, const QList<QPair<qint64, quint32>> &serverFlags,
394 const QHash<qint64, quint32> &visibleFlags,
395 const QSet<qint64> &pendingFlagUids,
396 const std::atomic_bool &cancelled,
397 const QList<qint64> *membership) const {
398 5 FlagSnapshotDiff result;
399
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 5 times.
5 if (cancelled.load(std::memory_order_relaxed)) {
400 ✗ result.cancelled = true;
401 ✗ return result;
402 }
403 // knownFlags: server flags we have; serverUids: every UID on the server
404 // (the snapshot's own UIDs unless a separate membership list is given).
405 5 QHash<qint64, quint32> knownFlags;
406
1/1
✓ Branch 6 → 7 taken 5 times.
5 knownFlags.reserve(serverFlags.size());
407
3/3
✓ Branch 12 → 13 taken 308 times.
✓ Branch 15 → 9 taken 308 times.
✓ Branch 15 → 16 taken 5 times.
313 for (const auto &[uid, flags] : serverFlags) knownFlags.insert(uid, flags);
408 5 QSet<qint64> serverUids;
409
2/2
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 28 taken 3 times.
5 if (membership) {
410
1/1
✓ Branch 19 → 20 taken 2 times.
2 serverUids.reserve(membership->size());
411
3/3
✓ Branch 23 → 24 taken 20 times.
✓ Branch 26 → 22 taken 20 times.
✓ Branch 26 → 27 taken 2 times.
22 for (const qint64 uid : *membership) serverUids.insert(uid);
412 } else {
413
1/1
✓ Branch 29 → 30 taken 3 times.
3 serverUids.reserve(knownFlags.size());
414
3/3
✓ Branch 32 → 33 taken 306 times.
✓ Branch 36 → 31 taken 306 times.
✓ Branch 36 → 37 taken 3 times.
309 for (auto it = knownFlags.cbegin(); it != knownFlags.cend(); ++it) serverUids.insert(it.key());
415 }
416 5 qsizetype processed = 0;
417
2/2
✓ Branch 71 → 39 taken 308 times.
✓ Branch 71 → 72 taken 5 times.
313 for (auto it = knownFlags.cbegin(); it != knownFlags.cend(); ++it) {
418
4/6
✓ Branch 39 → 40 taken 1 time.
✓ Branch 39 → 43 taken 307 times.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 1 time.
✗ Branch 44 → 45 not taken.
✓ Branch 44 → 46 taken 308 times.
308 if ((++processed & 255) == 0 && cancelled.load(std::memory_order_relaxed)) {
419 ✗ result.cancelled = true;
420 ✗ return result;
421 }
422 308 const auto visible = visibleFlags.constFind(it.key());
423
3/4
✓ Branch 53 → 54 taken 1 time.
✓ Branch 53 → 61 taken 50 times.
✓ Branch 56 → 57 taken 1 time.
✗ Branch 56 → 61 not taken.
360 if (visible != visibleFlags.cend() && visible.value() != it.value() &&
424
5/6
✓ Branch 50 → 51 taken 51 times.
✓ Branch 50 → 61 taken 257 times.
✓ Branch 59 → 60 taken 1 time.
✗ Branch 59 → 61 not taken.
✓ Branch 62 → 63 taken 1 time.
✓ Branch 62 → 68 taken 307 times.
360 serverUids.contains(it.key()) && !pendingFlagUids.contains(it.key()))
425
1/1
✓ Branch 66 → 67 taken 1 time.
1 result.visibleChanged.append({it.key(), it.value()});
426 }
427
2/2
✓ Branch 81 → 73 taken 51 times.
✓ Branch 81 → 82 taken 5 times.
56 for (auto it = visibleFlags.cbegin(); it != visibleFlags.cend(); ++it)
428
1/3
✗ Branch 75 → 76 not taken.
✓ Branch 75 → 78 taken 51 times.
✗ Branch 77 → 78 not taken.
51 if (!serverUids.contains(it.key())) result.visibleExpunged.append(it.key());
429
6/11
✓ Branch 82 → 83 taken 5 times.
✓ Branch 83 → 84 taken 5 times.
✗ Branch 83 → 89 not taken.
✓ Branch 85 → 86 taken 5 times.
✗ Branch 85 → 89 not taken.
✓ Branch 87 → 88 taken 5 times.
✗ Branch 87 → 89 not taken.
✗ Branch 88 → 89 not taken.
✓ Branch 88 → 90 taken 5 times.
✗ Branch 91 → 92 not taken.
✓ Branch 91 → 102 taken 5 times.
5 if (!m_db.isOpen() || account.isEmpty() || folder.isEmpty() || folderId <= 0) {
430 ✗ result.error = QStringLiteral("Cache reader or folder identity is unavailable");
431 ✗ result.missingUids = serverUids.values();
432 ✗ return result;
433 }
434
1/1
✓ Branch 102 → 103 taken 5 times.
5 QSqlQuery identity(m_db);
435
1/1
✓ Branch 106 → 107 taken 5 times.
5 identity.prepare(QStringLiteral(
436 "SELECT uidvalidity FROM folders WHERE id=? AND account=? AND path=?"));
437
1/1
✓ Branch 111 → 112 taken 5 times.
5 identity.addBindValue(folderId);
438
1/1
✓ Branch 115 → 116 taken 5 times.
5 identity.addBindValue(account);
439
1/1
✓ Branch 119 → 120 taken 5 times.
5 identity.addBindValue(folder);
440
2/3
✓ Branch 121 → 122 taken 5 times.
✗ Branch 122 → 123 not taken.
✓ Branch 122 → 132 taken 5 times.
5 if (!identity.exec()) {
441 ✗ result.error = identity.lastError().text();
442 ✗ result.missingUids = serverUids.values();
443 ✗ return result;
444 }
445
7/13
✓ Branch 132 → 133 taken 5 times.
✓ Branch 133 → 134 taken 5 times.
✗ Branch 133 → 137 not taken.
✓ Branch 134 → 135 taken 5 times.
✓ Branch 135 → 136 taken 5 times.
✗ Branch 136 → 137 not taken.
✓ Branch 136 → 138 taken 5 times.
✓ Branch 139 → 140 taken 5 times.
✗ Branch 139 → 141 not taken.
✗ Branch 141 → 142 not taken.
✓ Branch 141 → 143 taken 5 times.
✗ Branch 264 → 265 not taken.
✗ Branch 264 → 266 not taken.
5 if (!identity.next() || identity.value(0).toUInt() != uidValidity) {
446 ✗ result.stale = true;
447 ✗ return result;
448 }
449
1/1
✓ Branch 143 → 144 taken 5 times.
5 identity.finish();
450
1/1
✓ Branch 144 → 145 taken 5 times.
5 QSqlQuery cached(m_db);
451
1/1
✓ Branch 148 → 149 taken 5 times.
5 cached.prepare(QStringLiteral("SELECT uid, flags FROM headers WHERE folder_id=?"));
452
1/1
✓ Branch 153 → 154 taken 5 times.
5 cached.addBindValue(folderId);
453
2/3
✓ Branch 155 → 156 taken 5 times.
✗ Branch 156 → 157 not taken.
✓ Branch 156 → 166 taken 5 times.
5 if (!cached.exec()) {
454 ✗ result.error = cached.lastError().text();
455 ✗ result.missingUids = serverUids.values();
456 ✗ return result;
457 }
458 5 qsizetype scanned = 0;
459 5 QList<QPair<qint64, quint32>> offPageChanged;
460 5 QList<qint64> offPageExpunged;
461
3/3
✓ Branch 203 → 204 taken 333 times.
✓ Branch 204 → 167 taken 328 times.
✓ Branch 204 → 205 taken 5 times.
333 while (cached.next()) {
462
4/6
✓ Branch 167 → 168 taken 1 time.
✓ Branch 167 → 171 taken 327 times.
✗ Branch 169 → 170 not taken.
✓ Branch 169 → 171 taken 1 time.
✗ Branch 172 → 173 not taken.
✓ Branch 172 → 174 taken 328 times.
328 if ((++scanned & 255) == 0 && cancelled.load(std::memory_order_relaxed)) {
463 ✗ result.cancelled = true;
464 ✗ return result;
465 }
466
2/2
✓ Branch 174 → 175 taken 328 times.
✓ Branch 175 → 176 taken 328 times.
328 const qint64 uid = cached.value(0).toLongLong();
467
1/1
✓ Branch 177 → 178 taken 328 times.
328 const bool present = serverUids.remove(uid);
468 // Visible rows are reconciled against the GUI's own snapshot above.
469
2/2
✓ Branch 179 → 180 taken 277 times.
✓ Branch 179 → 202 taken 51 times.
328 if (!visibleFlags.contains(uid)) {
470 277 const auto known = knownFlags.constFind(uid);
471
2/2
✓ Branch 181 → 182 taken 3 times.
✓ Branch 181 → 183 taken 274 times.
277 if (!present)
472
1/1
✓ Branch 182 → 201 taken 3 times.
3 offPageExpunged.append(uid);
473
10/12
✓ Branch 185 → 186 taken 257 times.
✓ Branch 185 → 193 taken 17 times.
✓ Branch 187 → 188 taken 257 times.
✓ Branch 188 → 189 taken 257 times.
✓ Branch 189 → 190 taken 5 times.
✓ Branch 189 → 193 taken 252 times.
✓ Branch 194 → 195 taken 257 times.
✓ Branch 194 → 196 taken 17 times.
✓ Branch 196 → 197 taken 5 times.
✓ Branch 196 → 201 taken 269 times.
✗ Branch 286 → 287 not taken.
✗ Branch 286 → 288 not taken.
279 else if (known != knownFlags.cend() && known.value() != cached.value(1).toUInt() &&
474
1/2
✓ Branch 191 → 192 taken 5 times.
✗ Branch 191 → 193 not taken.
5 !pendingFlagUids.contains(uid))
475
1/1
✓ Branch 199 → 200 taken 5 times.
5 offPageChanged.append({uid, known.value()});
476 }
477 }
478
3/4
✓ Branch 205 → 206 taken 5 times.
✓ Branch 206 → 207 taken 5 times.
✗ Branch 208 → 209 not taken.
✓ Branch 208 → 215 taken 5 times.
5 if (cached.lastError().isValid()) {
479 ✗ result.error = cached.lastError().text();
480 // A partial scan can only overstate missing membership. The old
481 // backfill path may refetch cached rows but cannot skip a real arrival.
482 // Off-page expunges need the complete scan and are not reported.
483 } else {
484 5 result.offPageChanged = std::move(offPageChanged);
485 5 result.offPageExpunged = std::move(offPageExpunged);
486 }
487
1/1
✓ Branch 219 → 220 taken 5 times.
5 result.missingUids = serverUids.values();
488 5 return result;
489 5 }
490
491 32 MailSnapshotReader::ComposeSourceSnapshot MailSnapshotReader::composeSourceSnapshot(
492 const QString &accountId, qint64 folderId, qint64 uid,
493 quint32 uidValidity,
494 const std::atomic_bool &cancelled) {
495 32 ComposeSourceSnapshot result;
496
5/8
✓ Branch 3 → 4 taken 32 times.
✓ Branch 5 → 6 taken 32 times.
✓ Branch 6 → 7 taken 32 times.
✗ Branch 6 → 12 not taken.
✓ Branch 8 → 9 taken 32 times.
✗ Branch 8 → 12 not taken.
✓ Branch 9 → 10 taken 32 times.
✗ Branch 9 → 12 not taken.
64 if (!m_db.isOpen() || !m_db.driver() || accountId.isEmpty() ||
497
4/8
✓ Branch 4 → 5 taken 32 times.
✗ Branch 4 → 12 not taken.
✓ Branch 10 → 11 taken 32 times.
✗ Branch 10 → 12 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 32 times.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 23 taken 32 times.
64 folderId <= 0 || uid <= 0 || uidValidity == 0) {
498 ✗ result.error = QStringLiteral("Invalid compose source identity");
499 ✗ return result;
500 }
501
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 27 taken 32 times.
32 if (cancelled.load(std::memory_order_relaxed)) return result;
502
2/2
✓ Branch 27 → 28 taken 32 times.
✓ Branch 28 → 29 taken 32 times.
32 auto handle = m_db.driver()->handle();
503
7/13
✓ Branch 29 → 30 taken 32 times.
✓ Branch 30 → 31 taken 32 times.
✗ Branch 30 → 35 not taken.
✓ Branch 31 → 32 taken 32 times.
✓ Branch 32 → 33 taken 32 times.
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 32 times.
✓ Branch 37 → 38 taken 32 times.
✗ Branch 37 → 39 not taken.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 48 taken 32 times.
✗ Branch 217 → 218 not taken.
✗ Branch 217 → 219 not taken.
32 if (!handle.isValid() || QByteArray(handle.typeName()) != "sqlite3*") {
504 ✗ result.error = QStringLiteral("Compose source requires interruptible SQLite");
505 ✗ return result;
506 }
507
1/1
✓ Branch 48 → 49 taken 32 times.
32 auto *database = *static_cast<sqlite3 **>(handle.data());
508
1/1
✓ Branch 50 → 51 taken 32 times.
32 sqlite3_progress_handler(database, 1000, [](void *context) {
509 ✗ return static_cast<const std::atomic_bool *>(context)->load(
510 ✗ std::memory_order_relaxed) ? 1 : 0;
511 }, const_cast<std::atomic_bool *>(&cancelled));
512 96 const auto clearProgress = qScopeGuard([database] {
513 32 sqlite3_progress_handler(database, 0, nullptr, nullptr);
514 32 });
515 ✗ const auto rollback = [&] {
516 ✗ sqlite3_progress_handler(database, 0, nullptr, nullptr);
517 ✗ m_db.rollback();
518 32 };
519
2/3
✓ Branch 52 → 53 taken 32 times.
✗ Branch 53 → 54 not taken.
✓ Branch 53 → 61 taken 32 times.
32 if (!m_db.transaction()) {
520 ✗ result.error = m_db.lastError().text();
521 ✗ return result;
522 }
523
1/1
✓ Branch 61 → 62 taken 32 times.
32 QSqlQuery query(m_db);
524
1/1
✓ Branch 65 → 66 taken 32 times.
32 query.prepare(QStringLiteral(
525 "SELECT f.uidvalidity, "
526 "CASE WHEN length(CAST(h.subject AS BLOB))<=1048576 THEN h.subject END, "
527 "CASE WHEN length(CAST(h.from_addr AS BLOB))<=1048576 THEN h.from_addr END, "
528 "CASE WHEN length(CAST(h.to_addr AS BLOB))<=1048576 THEN h.to_addr END, "
529 "h.date, "
530 "CASE WHEN length(CAST(h.message_id AS BLOB))<=1048576 THEN h.message_id END, "
531 "CASE WHEN length(CAST(h.ref_ids AS BLOB))<=1048576 THEN h.ref_ids END, "
532 "substr(b.text_plain,1,262145), "
533 "substr(CAST(b.raw_body AS BLOB),1,65536), "
534 "COALESCE(length(CAST(h.subject AS BLOB)),0)+"
535 "COALESCE(length(CAST(h.from_addr AS BLOB)),0)+"
536 "COALESCE(length(CAST(h.to_addr AS BLOB)),0)+"
537 "COALESCE(length(CAST(h.message_id AS BLOB)),0)+"
538 "COALESCE(length(CAST(h.ref_ids AS BLOB)),0), b.header_id, "
539 "CASE WHEN length(CAST(b.text_html AS BLOB))<=1048576 THEN b.text_html END "
540 "FROM folders f JOIN headers h ON h.folder_id=f.id "
541 "LEFT JOIN bodies b ON b.header_id=h.id "
542 "WHERE f.account=? AND f.id=? AND h.uid=? AND f.uidvalidity=?"));
543
1/1
✓ Branch 70 → 71 taken 32 times.
32 query.addBindValue(accountId);
544
1/1
✓ Branch 74 → 75 taken 32 times.
32 query.addBindValue(folderId);
545
1/1
✓ Branch 78 → 79 taken 32 times.
32 query.addBindValue(uid);
546
1/1
✓ Branch 82 → 83 taken 32 times.
32 query.addBindValue(uidValidity);
547
4/7
✓ Branch 84 → 85 taken 32 times.
✓ Branch 85 → 86 taken 32 times.
✗ Branch 85 → 88 not taken.
✗ Branch 87 → 88 not taken.
✓ Branch 87 → 89 taken 32 times.
✗ Branch 90 → 91 not taken.
✓ Branch 90 → 99 taken 32 times.
32 if (!query.exec() || cancelled.load(std::memory_order_relaxed)) {
548 ✗ result.error = query.lastError().text();
549 ✗ rollback();
550 ✗ return result;
551 }
552
2/3
✓ Branch 99 → 100 taken 32 times.
✗ Branch 100 → 101 not taken.
✓ Branch 100 → 115 taken 32 times.
32 if (!query.next()) {
553 ✗ result.outcome = query.lastError().isValid()
554 ✗ ? ComposeSourceSnapshot::Outcome::Failed
555 : ComposeSourceSnapshot::Outcome::Missing;
556 ✗ result.error = query.lastError().text();
557 ✗ rollback();
558 ✗ return result;
559 }
560
1/2
✗ Branch 116 → 117 not taken.
✓ Branch 116 → 120 taken 32 times.
32 if (cancelled.load(std::memory_order_relaxed)) {
561 ✗ rollback();
562 ✗ return result;
563 }
564
3/4
✓ Branch 120 → 121 taken 32 times.
✓ Branch 121 → 122 taken 32 times.
✗ Branch 123 → 124 not taken.
✓ Branch 123 → 127 taken 32 times.
32 if (query.value(9).toLongLong() > MailDisplaySnapshot::MaxMetadataBytes) {
565 ✗ result.outcome = ComposeSourceSnapshot::Outcome::Limited;
566 ✗ rollback();
567 ✗ return result;
568 }
569
2/2
✓ Branch 127 → 128 taken 32 times.
✓ Branch 128 → 129 taken 32 times.
32 result.uidValidity = query.value(0).toUInt();
570 32 result.header.folderId = folderId;
571 32 result.header.uid = uid;
572
2/2
✓ Branch 130 → 131 taken 32 times.
✓ Branch 131 → 132 taken 32 times.
32 result.header.subject = query.value(1).toString();
573
2/2
✓ Branch 135 → 136 taken 32 times.
✓ Branch 136 → 137 taken 32 times.
32 result.header.from = query.value(2).toString();
574
2/2
✓ Branch 140 → 141 taken 32 times.
✓ Branch 141 → 142 taken 32 times.
32 result.header.to = query.value(3).toString();
575
3/4
✓ Branch 145 → 146 taken 32 times.
✓ Branch 146 → 147 taken 32 times.
✓ Branch 148 → 149 taken 32 times.
✗ Branch 148 → 156 not taken.
32 if (!query.value(4).isNull())
576
3/3
✓ Branch 149 → 150 taken 32 times.
✓ Branch 150 → 151 taken 32 times.
✓ Branch 151 → 152 taken 32 times.
32 result.header.date = QDateTime::fromSecsSinceEpoch(query.value(4).toLongLong());
577
3/3
✓ Branch 156 → 157 taken 32 times.
✓ Branch 157 → 158 taken 32 times.
✓ Branch 158 → 159 taken 32 times.
32 result.header.messageId = MessageId::canonicalFirst(query.value(5).toString());
578
3/3
✓ Branch 163 → 164 taken 32 times.
✓ Branch 164 → 165 taken 32 times.
✓ Branch 165 → 166 taken 32 times.
32 result.header.references = decodeThreadIds(query.value(6).toString());
579
2/2
✓ Branch 170 → 171 taken 32 times.
✓ Branch 171 → 172 taken 32 times.
32 result.quoteText = query.value(7).toString();
580
2/2
✓ Branch 175 → 176 taken 32 times.
✓ Branch 176 → 177 taken 32 times.
32 result.envelopePrefix = query.value(8).toByteArray();
581
2/2
✓ Branch 180 → 181 taken 32 times.
✓ Branch 181 → 182 taken 32 times.
32 result.bodyPresent = !query.value(10).isNull();
582
2/2
✓ Branch 183 → 184 taken 32 times.
✓ Branch 184 → 185 taken 32 times.
32 result.quoteHtml = query.value(11).toString();
583 32 constexpr qsizetype MaxQuoteCharacters = 262144;
584
1/2
✗ Branch 189 → 190 not taken.
✓ Branch 189 → 195 taken 32 times.
32 if (result.quoteText.size() > MaxQuoteCharacters) {
585 ✗ result.quoteTruncated = true;
586 ✗ qsizetype keep = MaxQuoteCharacters;
587 ✗ if (result.quoteText.at(keep - 1).isHighSurrogate()) --keep;
588 ✗ result.quoteText.truncate(keep);
589 }
590
1/1
✓ Branch 195 → 196 taken 32 times.
32 query.finish();
591
1/2
✗ Branch 197 → 198 not taken.
✓ Branch 197 → 201 taken 32 times.
32 if (cancelled.load(std::memory_order_relaxed)) {
592 ✗ rollback();
593 ✗ return {};
594 }
595
2/3
✓ Branch 201 → 202 taken 32 times.
✗ Branch 202 → 203 not taken.
✓ Branch 202 → 210 taken 32 times.
32 if (!m_db.commit()) {
596 ✗ result.error = m_db.lastError().text();
597 ✗ rollback();
598 ✗ return result;
599 }
600 32 result.outcome = ComposeSourceSnapshot::Outcome::Ready;
601 32 return result;
602 32 }
603