data/MutationJournalStore.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "data/MutationJournalStore.h" | ||
| 2 | |||
| 3 | #include "data/MailCacheSql.h" | ||
| 4 | #include <QCryptographicHash> | ||
| 5 | #include <QDateTime> | ||
| 6 | #include <QLoggingCategory> | ||
| 7 | #include <QScopeGuard> | ||
| 8 | #include <QSqlError> | ||
| 9 | #include <QSqlQuery> | ||
| 10 | #include <QVariant> | ||
| 11 | |||
| 12 | // Same category as MailCache (defined there), see docs/LOGGING.md. | ||
| 13 | Q_DECLARE_LOGGING_CATEGORY(lcCache) | ||
| 14 | |||
| 15 | using MailCacheSql::ScopedWriteUnit; | ||
| 16 | using MailCacheSql::beginWriteTransaction; | ||
| 17 | |||
| 18 | 5035 | MutationJournalStore::MutationJournalStore(QSqlDatabase &db, QString &lastError, Notify notify) | |
| 19 | 5035 | : m_db(db), m_lastError(lastError), m_notify(std::move(notify)) {} | |
| 20 | |||
| 21 | // --- Durable mutation journal (Sprint 84) --- | ||
| 22 | |||
| 23 | namespace { | ||
| 24 | constexpr auto MutationColumns = | ||
| 25 | "token, account_id, folder_id, folder_path, uid, kind, replay_class, " | ||
| 26 | "phase, flag_name, desired_state, target_folder, message_id, uidvalidity, " | ||
| 27 | "uidplus_evidence, error, created_at, updated_at"; | ||
| 28 | |||
| 29 | 2948614 | MutationRecord mutationFromQuery(const QSqlQuery &query) { | |
| 30 | 2948614 | MutationRecord record; | |
| 31 |
2/2✓ Branch 2 → 3 taken 2948614 times.
✓ Branch 3 → 4 taken 2948614 times.
|
2948614 | record.token = query.value(0).toLongLong(); |
| 32 |
2/2✓ Branch 5 → 6 taken 2948614 times.
✓ Branch 6 → 7 taken 2948614 times.
|
2948614 | record.mailId.accountId = query.value(1).toString(); |
| 33 |
2/2✓ Branch 10 → 11 taken 2948614 times.
✓ Branch 11 → 12 taken 2948614 times.
|
2948614 | record.mailId.folderId = query.value(2).toLongLong(); |
| 34 |
2/2✓ Branch 13 → 14 taken 2948614 times.
✓ Branch 14 → 15 taken 2948614 times.
|
2948614 | record.folderPath = query.value(3).toString(); |
| 35 |
2/2✓ Branch 18 → 19 taken 2948614 times.
✓ Branch 19 → 20 taken 2948614 times.
|
2948614 | record.mailId.uid = query.value(4).toLongLong(); |
| 36 |
2/2✓ Branch 21 → 22 taken 2948614 times.
✓ Branch 22 → 23 taken 2948614 times.
|
2948614 | record.kind = static_cast<MutationKind>(query.value(5).toInt()); |
| 37 | 2948614 | record.replayClass = | |
| 38 |
2/2✓ Branch 24 → 25 taken 2948614 times.
✓ Branch 25 → 26 taken 2948614 times.
|
2948614 | static_cast<MutationReplayClass>(query.value(6).toInt()); |
| 39 |
2/2✓ Branch 27 → 28 taken 2948614 times.
✓ Branch 28 → 29 taken 2948614 times.
|
2948614 | record.phase = static_cast<MutationPhase>(query.value(7).toInt()); |
| 40 |
2/2✓ Branch 30 → 31 taken 2948614 times.
✓ Branch 31 → 32 taken 2948614 times.
|
2948614 | record.flag = query.value(8).toString(); |
| 41 |
2/2✓ Branch 35 → 36 taken 2948614 times.
✓ Branch 36 → 37 taken 2948614 times.
|
2948614 | record.desiredState = query.value(9).toBool(); |
| 42 |
2/2✓ Branch 38 → 39 taken 2948614 times.
✓ Branch 39 → 40 taken 2948614 times.
|
2948614 | record.targetFolder = query.value(10).toString(); |
| 43 |
2/2✓ Branch 43 → 44 taken 2948614 times.
✓ Branch 44 → 45 taken 2948614 times.
|
2948614 | record.messageId = query.value(11).toString(); |
| 44 |
2/2✓ Branch 48 → 49 taken 2948614 times.
✓ Branch 49 → 50 taken 2948614 times.
|
2948614 | record.uidValidity = query.value(12).toUInt(); |
| 45 |
2/2✓ Branch 51 → 52 taken 2948614 times.
✓ Branch 52 → 53 taken 2948614 times.
|
2948614 | record.uidPlusEvidence = query.value(13).toString(); |
| 46 |
2/2✓ Branch 56 → 57 taken 2948614 times.
✓ Branch 57 → 58 taken 2948614 times.
|
2948614 | record.error = query.value(14).toString(); |
| 47 |
2/2✓ Branch 61 → 62 taken 2948614 times.
✓ Branch 62 → 63 taken 2948614 times.
|
2948614 | record.createdAt = query.value(15).toLongLong(); |
| 48 |
2/2✓ Branch 64 → 65 taken 2948614 times.
✓ Branch 65 → 66 taken 2948614 times.
|
2948614 | record.updatedAt = query.value(16).toLongLong(); |
| 49 | 2948614 | return record; | |
| 50 | ✗ | } | |
| 51 | |||
| 52 | 6408 | qint64 estimatedMutationBytes(const MutationRecord &record) { | |
| 53 | 6408 | return 128 + 2LL * | |
| 54 | 6408 | (record.mailId.accountId.size() + record.folderPath.size() + | |
| 55 | 6408 | record.flag.size() + record.targetFolder.size() + | |
| 56 | 6408 | record.messageId.size() + record.uidPlusEvidence.size() + | |
| 57 | 6408 | record.error.size()); | |
| 58 | } | ||
| 59 | } // namespace | ||
| 60 | |||
| 61 | 117 | QSet<qint64> MutationJournalStore::pendingMoveUids(const QString &account, qint64 folderId, | |
| 62 | bool *ok) const { | ||
| 63 |
2/2✓ Branch 2 → 3 taken 57 times.
✓ Branch 2 → 4 taken 60 times.
|
117 | if (ok) *ok = false; |
| 64 |
1/1✓ Branch 4 → 5 taken 117 times.
|
117 | QSqlQuery q(m_db); |
| 65 |
1/1✓ Branch 8 → 9 taken 117 times.
|
117 | q.prepare(QStringLiteral( |
| 66 | "SELECT uid FROM mutation_journal WHERE account_id=:account AND folder_id=:fid " | ||
| 67 | "AND kind=:move AND phase NOT IN (:confirmed, :failed, :quarantined)")); | ||
| 68 |
1/1✓ Branch 16 → 17 taken 117 times.
|
234 | q.bindValue(QStringLiteral(":account"), account); |
| 69 |
1/1✓ Branch 25 → 26 taken 117 times.
|
234 | q.bindValue(QStringLiteral(":fid"), folderId); |
| 70 |
1/1✓ Branch 34 → 35 taken 117 times.
|
234 | q.bindValue(QStringLiteral(":move"), int(MutationKind::Move)); |
| 71 |
1/1✓ Branch 43 → 44 taken 117 times.
|
234 | q.bindValue(QStringLiteral(":confirmed"), int(MutationPhase::Confirmed)); |
| 72 |
1/1✓ Branch 52 → 53 taken 117 times.
|
234 | q.bindValue(QStringLiteral(":failed"), int(MutationPhase::Failed)); |
| 73 |
1/1✓ Branch 61 → 62 taken 117 times.
|
234 | q.bindValue(QStringLiteral(":quarantined"), int(MutationPhase::Quarantined)); |
| 74 | 117 | QSet<qint64> result; | |
| 75 |
2/3✓ Branch 66 → 67 taken 117 times.
✗ Branch 67 → 68 not taken.
✓ Branch 67 → 69 taken 117 times.
|
117 | if (!q.exec()) return result; |
| 76 |
6/6✓ Branch 70 → 71 taken 33 times.
✓ Branch 71 → 72 taken 33 times.
✓ Branch 72 → 73 taken 33 times.
✓ Branch 75 → 76 taken 150 times.
✓ Branch 76 → 70 taken 33 times.
✓ Branch 76 → 77 taken 117 times.
|
150 | while (q.next()) result.insert(q.value(0).toLongLong()); |
| 77 |
4/4✓ Branch 77 → 78 taken 57 times.
✓ Branch 77 → 82 taken 60 times.
✓ Branch 78 → 79 taken 57 times.
✓ Branch 79 → 80 taken 57 times.
|
117 | if (ok) *ok = !q.lastError().isValid(); |
| 78 | 117 | return result; | |
| 79 | 117 | } | |
| 80 | |||
| 81 | 14 | bool MutationJournalStore::hasPendingSeenMutation(const QString &account, qint64 folderId) const { | |
| 82 |
1/1✓ Branch 2 → 3 taken 14 times.
|
14 | QSqlQuery q(m_db); |
| 83 |
1/1✓ Branch 6 → 7 taken 14 times.
|
14 | q.prepare(QStringLiteral("SELECT 1 FROM mutation_journal WHERE account_id=? " |
| 84 | "AND folder_id=? AND kind=? AND phase IN (?,?,?) LIMIT 1")); | ||
| 85 |
1/1✓ Branch 11 → 12 taken 14 times.
|
14 | q.addBindValue(account); |
| 86 |
1/1✓ Branch 15 → 16 taken 14 times.
|
14 | q.addBindValue(folderId); |
| 87 |
1/1✓ Branch 19 → 20 taken 14 times.
|
14 | q.addBindValue(int(MutationKind::Seen)); |
| 88 |
1/1✓ Branch 23 → 24 taken 14 times.
|
14 | q.addBindValue(int(MutationPhase::Queued)); |
| 89 |
1/1✓ Branch 27 → 28 taken 14 times.
|
14 | q.addBindValue(int(MutationPhase::Dispatched)); |
| 90 |
1/1✓ Branch 31 → 32 taken 14 times.
|
14 | q.addBindValue(int(MutationPhase::AwaitingVerification)); |
| 91 |
5/6✓ Branch 33 → 34 taken 14 times.
✓ Branch 34 → 35 taken 14 times.
✗ Branch 34 → 38 not taken.
✓ Branch 35 → 36 taken 14 times.
✓ Branch 36 → 37 taken 3 times.
✓ Branch 36 → 38 taken 11 times.
|
28 | return q.exec() && q.next(); |
| 92 | 14 | } | |
| 93 | |||
| 94 | std::optional<qint64> | ||
| 95 | 390 | MutationJournalStore::enqueueMutation(const MutationRecord &record) { | |
| 96 |
5/5✓ Branch 2 → 3 taken 390 times.
✓ Branch 5 → 6 taken 388 times.
✓ Branch 5 → 20 taken 1 time.
✓ Branch 7 → 8 taken 387 times.
✓ Branch 7 → 20 taken 1 time.
|
1167 | if (!m_db.isOpen() || !record.isValid() || |
| 97 |
4/4✓ Branch 9 → 10 taken 386 times.
✓ Branch 9 → 20 taken 1 time.
✓ Branch 11 → 12 taken 385 times.
✓ Branch 11 → 20 taken 1 time.
|
1161 | record.mailId.accountId.size() > 256 || record.folderPath.size() > 1024 || |
| 98 |
4/4✓ Branch 13 → 14 taken 384 times.
✓ Branch 13 → 20 taken 1 time.
✓ Branch 15 → 16 taken 383 times.
✓ Branch 15 → 20 taken 1 time.
|
1155 | record.flag.size() > 256 || record.targetFolder.size() > 1024 || |
| 99 |
2/2✓ Branch 17 → 18 taken 382 times.
✓ Branch 17 → 20 taken 1 time.
|
767 | record.messageId.size() > 2048 || |
| 100 |
6/6✓ Branch 3 → 4 taken 389 times.
✓ Branch 3 → 20 taken 1 time.
✓ Branch 19 → 20 taken 1 time.
✓ Branch 19 → 21 taken 381 times.
✓ Branch 22 → 23 taken 9 times.
✓ Branch 22 → 32 taken 381 times.
|
1162 | record.uidPlusEvidence.size() > 1024 || record.error.size() > 2048) { |
| 101 | 9 | m_lastError = QStringLiteral("Invalid or oversized mutation journal entry"); | |
| 102 | 9 | return std::nullopt; | |
| 103 | } | ||
| 104 | |||
| 105 |
1/1✓ Branch 32 → 33 taken 381 times.
|
381 | ScopedWriteUnit unit(m_db, &m_lastError); |
| 106 |
1/2✗ Branch 34 → 35 not taken.
✓ Branch 34 → 38 taken 381 times.
|
381 | if (!unit.started()) return std::nullopt; |
| 107 | |||
| 108 | 381 | const qint64 now = QDateTime::currentSecsSinceEpoch(); | |
| 109 |
2/2✓ Branch 39 → 40 taken 348 times.
✓ Branch 39 → 212 taken 33 times.
|
381 | if (record.replayClass == MutationReplayClass::DesiredState) { |
| 110 |
1/1✓ Branch 40 → 41 taken 348 times.
|
348 | QSqlQuery existing(m_db); |
| 111 |
1/1✓ Branch 44 → 45 taken 348 times.
|
348 | existing.prepare(QStringLiteral( |
| 112 | "SELECT token FROM mutation_journal WHERE account_id = :account " | ||
| 113 | "AND folder_id = :folder AND uid = :uid AND kind = :kind " | ||
| 114 | "AND flag_name = :flag AND replay_class = :class " | ||
| 115 | "AND phase = :queued " | ||
| 116 | "ORDER BY token DESC LIMIT 1")); | ||
| 117 |
1/1✓ Branch 52 → 53 taken 348 times.
|
696 | existing.bindValue(QStringLiteral(":account"), record.mailId.accountId); |
| 118 |
1/1✓ Branch 61 → 62 taken 348 times.
|
696 | existing.bindValue(QStringLiteral(":folder"), record.mailId.folderId); |
| 119 |
1/1✓ Branch 70 → 71 taken 348 times.
|
696 | existing.bindValue(QStringLiteral(":uid"), record.mailId.uid); |
| 120 |
1/1✓ Branch 79 → 80 taken 348 times.
|
696 | existing.bindValue(QStringLiteral(":kind"), static_cast<int>(record.kind)); |
| 121 |
1/1✓ Branch 88 → 89 taken 348 times.
|
696 | existing.bindValue(QStringLiteral(":flag"), record.flag); |
| 122 |
1/1✓ Branch 97 → 98 taken 348 times.
|
696 | existing.bindValue(QStringLiteral(":class"), |
| 123 | static_cast<int>(MutationReplayClass::DesiredState)); | ||
| 124 |
1/1✓ Branch 106 → 107 taken 348 times.
|
696 | existing.bindValue(QStringLiteral(":queued"), |
| 125 | static_cast<int>(MutationPhase::Queued)); | ||
| 126 |
3/3✓ Branch 110 → 111 taken 348 times.
✓ Branch 111 → 112 taken 1 time.
✓ Branch 111 → 120 taken 347 times.
|
348 | if (!existing.exec()) { |
| 127 |
2/2✓ Branch 112 → 113 taken 1 time.
✓ Branch 113 → 114 taken 1 time.
|
1 | m_lastError = existing.lastError().text(); |
| 128 | 1 | return std::nullopt; | |
| 129 | } | ||
| 130 |
3/3✓ Branch 120 → 121 taken 347 times.
✓ Branch 121 → 122 taken 62 times.
✓ Branch 121 → 206 taken 285 times.
|
347 | if (existing.next()) { |
| 131 |
2/2✓ Branch 122 → 123 taken 62 times.
✓ Branch 123 → 124 taken 62 times.
|
62 | const qint64 token = existing.value(0).toLongLong(); |
| 132 |
1/1✓ Branch 125 → 126 taken 62 times.
|
62 | QSqlQuery update(m_db); |
| 133 |
1/1✓ Branch 129 → 130 taken 62 times.
|
62 | update.prepare(QStringLiteral( |
| 134 | "UPDATE mutation_journal SET desired_state = :desired, " | ||
| 135 | "folder_path = :path, uidvalidity = :validity, phase = :phase, " | ||
| 136 | "error = '', uidplus_evidence = '', updated_at = :updated " | ||
| 137 | "WHERE token = :token")); | ||
| 138 |
1/1✓ Branch 137 → 138 taken 62 times.
|
124 | update.bindValue(QStringLiteral(":desired"), record.desiredState); |
| 139 |
1/1✓ Branch 146 → 147 taken 62 times.
|
124 | update.bindValue(QStringLiteral(":path"), record.folderPath); |
| 140 |
1/1✓ Branch 155 → 156 taken 62 times.
|
124 | update.bindValue(QStringLiteral(":validity"), record.uidValidity); |
| 141 |
1/1✓ Branch 164 → 165 taken 62 times.
|
124 | update.bindValue(QStringLiteral(":phase"), |
| 142 | static_cast<int>(MutationPhase::Queued)); | ||
| 143 |
1/1✓ Branch 173 → 174 taken 62 times.
|
124 | update.bindValue(QStringLiteral(":updated"), now); |
| 144 |
1/1✓ Branch 182 → 183 taken 62 times.
|
124 | update.bindValue(QStringLiteral(":token"), token); |
| 145 |
3/3✓ Branch 186 → 187 taken 62 times.
✓ Branch 187 → 188 taken 1 time.
✓ Branch 187 → 196 taken 61 times.
|
62 | if (!update.exec()) { |
| 146 |
2/2✓ Branch 188 → 189 taken 1 time.
✓ Branch 189 → 190 taken 1 time.
|
1 | m_lastError = update.lastError().text(); |
| 147 | 1 | return std::nullopt; | |
| 148 | } | ||
| 149 |
2/3✓ Branch 196 → 197 taken 61 times.
✗ Branch 197 → 198 not taken.
✓ Branch 197 → 201 taken 61 times.
|
61 | if (!unit.commit(&m_lastError)) return std::nullopt; |
| 150 | 61 | return token; | |
| 151 | 62 | } | |
| 152 | 348 | } | |
| 153 | |||
| 154 | // S106-115: fail closed like the batch path — an unreadable usage is no | ||
| 155 | // free capacity. | ||
| 156 | 318 | int count = 0; | |
| 157 | 318 | qint64 used = 0; | |
| 158 |
2/3✓ Branch 212 → 213 taken 318 times.
✗ Branch 213 → 214 not taken.
✓ Branch 213 → 223 taken 318 times.
|
318 | if (!journalUsage(&count, &used)) { |
| 159 | ✗ | m_lastError = QStringLiteral("Mutation journal usage could not be read"); | |
| 160 | ✗ | return std::nullopt; | |
| 161 | } | ||
| 162 |
4/4✓ Branch 223 → 224 taken 317 times.
✓ Branch 223 → 226 taken 1 time.
✓ Branch 228 → 229 taken 2 times.
✓ Branch 228 → 238 taken 316 times.
|
635 | if (count >= m_mutationJournalMaxEntries || |
| 163 |
2/2✓ Branch 225 → 226 taken 1 time.
✓ Branch 225 → 227 taken 316 times.
|
317 | estimatedMutationBytes(record) > m_mutationJournalMaxBytes - used) { |
| 164 | 2 | m_lastError = QStringLiteral("Mutation journal capacity exceeded"); | |
| 165 | 2 | return std::nullopt; | |
| 166 | } | ||
| 167 | |||
| 168 |
1/1✓ Branch 238 → 239 taken 316 times.
|
316 | QSqlQuery insert(m_db); |
| 169 |
1/1✓ Branch 242 → 243 taken 316 times.
|
316 | insert.prepare(QStringLiteral( |
| 170 | "INSERT INTO mutation_journal (account_id, folder_id, folder_path, uid, " | ||
| 171 | "kind, replay_class, phase, flag_name, desired_state, target_folder, " | ||
| 172 | "message_id, uidvalidity, uidplus_evidence, error, created_at, updated_at) " | ||
| 173 | "VALUES (:account, :folder, :path, :uid, :kind, :class, :phase, :flag, " | ||
| 174 | ":desired, :target, :message, :validity, :uidplus, :error, :created, " | ||
| 175 | ":updated)")); | ||
| 176 |
1/1✓ Branch 250 → 251 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":account"), record.mailId.accountId); |
| 177 |
1/1✓ Branch 259 → 260 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":folder"), record.mailId.folderId); |
| 178 |
1/1✓ Branch 268 → 269 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":path"), record.folderPath); |
| 179 |
1/1✓ Branch 277 → 278 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":uid"), record.mailId.uid); |
| 180 |
1/1✓ Branch 286 → 287 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":kind"), static_cast<int>(record.kind)); |
| 181 |
1/1✓ Branch 295 → 296 taken 316 times.
|
316 | insert.bindValue(QStringLiteral(":class"), |
| 182 | 316 | static_cast<int>(record.replayClass)); | |
| 183 |
1/1✓ Branch 304 → 305 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":phase"), static_cast<int>(record.phase)); |
| 184 |
1/1✓ Branch 313 → 314 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":flag"), record.flag); |
| 185 |
1/1✓ Branch 322 → 323 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":desired"), record.desiredState); |
| 186 |
1/1✓ Branch 331 → 332 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":target"), record.targetFolder); |
| 187 |
1/1✓ Branch 340 → 341 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":message"), record.messageId); |
| 188 |
1/1✓ Branch 349 → 350 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":validity"), record.uidValidity); |
| 189 |
1/1✓ Branch 358 → 359 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":uidplus"), record.uidPlusEvidence); |
| 190 |
1/1✓ Branch 367 → 368 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":error"), record.error); |
| 191 |
1/1✓ Branch 376 → 377 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":created"), now); |
| 192 |
1/1✓ Branch 385 → 386 taken 316 times.
|
632 | insert.bindValue(QStringLiteral(":updated"), now); |
| 193 |
3/3✓ Branch 389 → 390 taken 316 times.
✓ Branch 390 → 391 taken 2 times.
✓ Branch 390 → 399 taken 314 times.
|
316 | if (!insert.exec()) { |
| 194 |
2/2✓ Branch 391 → 392 taken 2 times.
✓ Branch 392 → 393 taken 2 times.
|
2 | m_lastError = insert.lastError().text(); |
| 195 | 2 | return std::nullopt; | |
| 196 | } | ||
| 197 |
2/2✓ Branch 399 → 400 taken 314 times.
✓ Branch 400 → 401 taken 314 times.
|
314 | const qint64 token = insert.lastInsertId().toLongLong(); |
| 198 |
1/2✗ Branch 402 → 403 not taken.
✓ Branch 402 → 412 taken 314 times.
|
314 | if (token <= 0) { |
| 199 | ✗ | m_lastError = QStringLiteral("Mutation journal did not return a token"); | |
| 200 | ✗ | return std::nullopt; | |
| 201 | } | ||
| 202 |
2/3✓ Branch 412 → 413 taken 314 times.
✗ Branch 413 → 414 not taken.
✓ Branch 413 → 417 taken 314 times.
|
314 | if (!unit.commit(&m_lastError)) return std::nullopt; |
| 203 | 314 | return token; | |
| 204 | 381 | } | |
| 205 | |||
| 206 | 20 | std::optional<qint64> MutationJournalStore::enqueueAppendMutation(const MutationRecord &record, | |
| 207 | const QByteArray &message, const QString &flags, | ||
| 208 | const QByteArray &precomputedSha256) { | ||
| 209 |
5/7✓ Branch 2 → 3 taken 20 times.
✓ Branch 4 → 5 taken 20 times.
✗ Branch 4 → 26 not taken.
✓ Branch 6 → 7 taken 19 times.
✓ Branch 6 → 26 taken 1 time.
✓ Branch 8 → 9 taken 19 times.
✗ Branch 8 → 26 not taken.
|
59 | if (!m_db.isOpen() || record.kind != MutationKind::Append || message.isEmpty() || |
| 210 |
1/2✓ Branch 10 → 11 taken 19 times.
✗ Branch 10 → 26 not taken.
|
38 | message.size() > MaximumAppendMessageBytes || flags.size() > 256 || |
| 211 |
3/4✓ Branch 12 → 13 taken 5 times.
✓ Branch 12 → 15 taken 14 times.
✓ Branch 14 → 15 taken 5 times.
✗ Branch 14 → 26 not taken.
|
19 | (!precomputedSha256.isEmpty() && precomputedSha256.size() != 32) || |
| 212 |
10/13✓ Branch 3 → 4 taken 20 times.
✗ Branch 3 → 26 not taken.
✓ Branch 17 → 18 taken 19 times.
✓ Branch 18 → 19 taken 18 times.
✓ Branch 18 → 26 taken 1 time.
✓ Branch 21 → 22 taken 18 times.
✓ Branch 22 → 23 taken 18 times.
✗ Branch 22 → 26 not taken.
✓ Branch 24 → 25 taken 18 times.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 18 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 28 → 38 taken 18 times.
|
40 | flags.contains(QLatin1Char('\r')) || flags.contains(QLatin1Char('\n')) || flags.contains(QChar::Null)) { |
| 213 | 2 | m_lastError = QStringLiteral("Invalid or oversized append payload"); | |
| 214 | 2 | return std::nullopt; | |
| 215 | } | ||
| 216 |
2/3✓ Branch 38 → 39 taken 18 times.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 43 taken 18 times.
|
18 | if (!beginWriteTransaction(m_db, &m_lastError)) return std::nullopt; |
| 217 | 21 | auto rollback = qScopeGuard([this] { m_db.rollback(); }); | |
| 218 |
1/1✓ Branch 44 → 45 taken 18 times.
|
18 | QSqlQuery capacity(m_db); |
| 219 |
5/13✓ Branch 48 → 49 taken 18 times.
✓ Branch 49 → 50 taken 18 times.
✗ Branch 49 → 52 not taken.
✓ Branch 54 → 55 taken 18 times.
✗ Branch 54 → 56 not taken.
✓ Branch 56 → 57 taken 18 times.
✗ Branch 56 → 58 not taken.
✗ Branch 58 → 59 not taken.
✓ Branch 58 → 67 taken 18 times.
✗ Branch 184 → 185 not taken.
✗ Branch 184 → 186 not taken.
✗ Branch 188 → 189 not taken.
✗ Branch 188 → 190 not taken.
|
54 | if (!capacity.exec(QStringLiteral("SELECT COALESCE(SUM(length(message)), 0) FROM append_payloads")) || |
| 220 |
2/3✓ Branch 50 → 51 taken 18 times.
✗ Branch 51 → 52 not taken.
✓ Branch 51 → 53 taken 18 times.
|
18 | !capacity.next()) { |
| 221 | ✗ | m_lastError = capacity.lastError().text(); | |
| 222 | ✗ | return std::nullopt; | |
| 223 | } | ||
| 224 |
4/4✓ Branch 67 → 68 taken 18 times.
✓ Branch 68 → 69 taken 18 times.
✓ Branch 71 → 72 taken 1 time.
✓ Branch 71 → 81 taken 17 times.
|
18 | if (capacity.value(0).toLongLong() > MaximumPendingAppendBytes - message.size()) { |
| 225 | 1 | m_lastError = QStringLiteral("Pending message copies exceed the storage limit"); | |
| 226 | 1 | return std::nullopt; | |
| 227 | } | ||
| 228 |
3/5✓ Branch 83 → 84 taken 17 times.
✓ Branch 86 → 87 taken 17 times.
✓ Branch 86 → 88 taken 17 times.
✗ Branch 202 → 203 not taken.
✗ Branch 202 → 204 not taken.
|
34 | const auto tokens = enqueueMutationsInTransaction({record}); |
| 229 |
5/6✓ Branch 89 → 90 taken 16 times.
✓ Branch 89 → 93 taken 1 time.
✗ Branch 92 → 93 not taken.
✓ Branch 92 → 94 taken 16 times.
✓ Branch 95 → 96 taken 1 time.
✓ Branch 95 → 99 taken 16 times.
|
17 | if (!tokens || tokens->size() != 1) return std::nullopt; |
| 230 |
1/1✓ Branch 99 → 100 taken 16 times.
|
16 | QSqlQuery insert(m_db); |
| 231 |
1/1✓ Branch 103 → 104 taken 16 times.
|
16 | insert.prepare(QStringLiteral("INSERT INTO append_payloads(token, message, flags, sha256) " |
| 232 | "VALUES(:token, :message, :flags, :sha)")); | ||
| 233 |
2/2✓ Branch 110 → 111 taken 16 times.
✓ Branch 111 → 112 taken 16 times.
|
16 | insert.bindValue(":token", tokens->first()); |
| 234 |
2/2✓ Branch 116 → 117 taken 16 times.
✓ Branch 117 → 118 taken 16 times.
|
16 | insert.bindValue(":message", message); |
| 235 |
6/8✓ Branch 122 → 123 taken 2 times.
✓ Branch 122 → 126 taken 14 times.
✓ Branch 128 → 129 taken 16 times.
✓ Branch 129 → 130 taken 16 times.
✓ Branch 133 → 134 taken 2 times.
✓ Branch 133 → 135 taken 14 times.
✗ Branch 239 → 240 not taken.
✗ Branch 239 → 241 not taken.
|
18 | insert.bindValue(":flags", flags.isNull() ? QStringLiteral("") : flags); |
| 236 |
2/2✓ Branch 142 → 143 taken 16 times.
✓ Branch 143 → 144 taken 16 times.
|
32 | insert.bindValue(":sha", precomputedSha256.isEmpty() |
| 237 |
3/3✓ Branch 137 → 138 taken 11 times.
✓ Branch 137 → 140 taken 5 times.
✓ Branch 139 → 141 taken 11 times.
|
32 | ? QCryptographicHash::hash(message, QCryptographicHash::Sha256) |
| 238 | : precomputedSha256); | ||
| 239 |
3/3✓ Branch 147 → 148 taken 16 times.
✓ Branch 148 → 149 taken 1 time.
✓ Branch 148 → 157 taken 15 times.
|
16 | if (!insert.exec()) { |
| 240 |
2/2✓ Branch 149 → 150 taken 1 time.
✓ Branch 150 → 151 taken 1 time.
|
1 | m_lastError = insert.lastError().text(); |
| 241 | 1 | return std::nullopt; | |
| 242 | } | ||
| 243 |
2/3✓ Branch 157 → 158 taken 15 times.
✗ Branch 158 → 159 not taken.
✓ Branch 158 → 167 taken 15 times.
|
15 | if (!m_db.commit()) { |
| 244 | ✗ | m_lastError = m_db.lastError().text(); | |
| 245 | ✗ | return std::nullopt; | |
| 246 | } | ||
| 247 | 15 | rollback.dismiss(); | |
| 248 | 15 | return tokens->first(); | |
| 249 | 35 | } | |
| 250 | |||
| 251 | 25 | std::optional<MutationJournalStore::AppendPayload> MutationJournalStore::appendPayload(qint64 token, | |
| 252 | const QString &accountId, QString *readError) const { | ||
| 253 |
2/2✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 4 taken 20 times.
|
25 | if (readError) readError->clear(); |
| 254 |
1/1✓ Branch 4 → 5 taken 25 times.
|
25 | QSqlQuery query(m_db); |
| 255 |
1/1✓ Branch 8 → 9 taken 25 times.
|
25 | query.prepare(QStringLiteral("SELECT p.message, p.flags, p.sha256 FROM append_payloads p " |
| 256 | "JOIN mutation_journal j ON j.token = p.token " | ||
| 257 | "WHERE p.token = :token AND j.account_id = :account AND j.kind = :kind " | ||
| 258 | "AND length(p.message) > 0 AND length(p.message) <= :limit AND length(p.flags) <= 256")); | ||
| 259 |
2/2✓ Branch 13 → 14 taken 25 times.
✓ Branch 14 → 15 taken 25 times.
|
25 | query.bindValue(":token", token); |
| 260 |
2/2✓ Branch 19 → 20 taken 25 times.
✓ Branch 20 → 21 taken 25 times.
|
25 | query.bindValue(":account", accountId); |
| 261 |
2/2✓ Branch 25 → 26 taken 25 times.
✓ Branch 26 → 27 taken 25 times.
|
25 | query.bindValue(":kind", static_cast<int>(MutationKind::Append)); |
| 262 |
2/2✓ Branch 31 → 32 taken 25 times.
✓ Branch 32 → 33 taken 25 times.
|
25 | query.bindValue(":limit", MaximumAppendMessageBytes); |
| 263 |
7/8✓ Branch 35 → 36 taken 25 times.
✓ Branch 36 → 37 taken 25 times.
✗ Branch 36 → 39 not taken.
✓ Branch 37 → 38 taken 25 times.
✓ Branch 38 → 39 taken 7 times.
✓ Branch 38 → 40 taken 18 times.
✓ Branch 41 → 42 taken 7 times.
✓ Branch 41 → 58 taken 18 times.
|
25 | if (!query.exec() || !query.next()) { |
| 264 |
8/12✓ Branch 42 → 43 taken 2 times.
✓ Branch 42 → 47 taken 5 times.
✓ Branch 43 → 44 taken 2 times.
✓ Branch 44 → 45 taken 2 times.
✗ Branch 45 → 46 not taken.
✓ Branch 45 → 47 taken 2 times.
✓ Branch 48 → 49 taken 2 times.
✓ Branch 48 → 50 taken 5 times.
✗ Branch 50 → 51 not taken.
✓ Branch 50 → 57 taken 7 times.
✗ Branch 130 → 131 not taken.
✗ Branch 130 → 132 not taken.
|
7 | if (readError && query.lastError().isValid()) |
| 265 | ✗ | *readError = query.lastError().text(); | |
| 266 | 7 | return std::nullopt; | |
| 267 | } | ||
| 268 |
4/6✓ Branch 58 → 59 taken 18 times.
✓ Branch 59 → 60 taken 18 times.
✓ Branch 60 → 61 taken 18 times.
✓ Branch 61 → 62 taken 18 times.
✗ Branch 141 → 142 not taken.
✗ Branch 141 → 143 not taken.
|
18 | AppendPayload payload{query.value(0).toByteArray(), query.value(1).toString()}; |
| 269 |
6/14✓ Branch 65 → 66 taken 18 times.
✓ Branch 67 → 68 taken 18 times.
✓ Branch 85 → 86 taken 18 times.
✗ Branch 85 → 87 not taken.
✓ Branch 87 → 88 taken 18 times.
✗ Branch 87 → 89 not taken.
✓ Branch 89 → 90 taken 1 time.
✓ Branch 89 → 91 taken 17 times.
✗ Branch 152 → 153 not taken.
✗ Branch 152 → 154 not taken.
✗ Branch 157 → 158 not taken.
✗ Branch 157 → 159 not taken.
✗ Branch 161 → 162 not taken.
✗ Branch 161 → 163 not taken.
|
54 | if (QCryptographicHash::hash(payload.message, QCryptographicHash::Sha256) != query.value(2).toByteArray() || |
| 270 |
7/9✓ Branch 64 → 65 taken 18 times.
✓ Branch 69 → 70 taken 17 times.
✓ Branch 69 → 81 taken 1 time.
✓ Branch 72 → 73 taken 17 times.
✓ Branch 73 → 74 taken 17 times.
✗ Branch 73 → 81 not taken.
✓ Branch 76 → 77 taken 17 times.
✓ Branch 77 → 78 taken 17 times.
✗ Branch 77 → 81 not taken.
|
71 | payload.flags.contains(QLatin1Char('\r')) || payload.flags.contains(QLatin1Char('\n')) || |
| 271 |
3/5✓ Branch 79 → 80 taken 17 times.
✗ Branch 80 → 81 not taken.
✓ Branch 80 → 82 taken 17 times.
✓ Branch 83 → 84 taken 18 times.
✗ Branch 83 → 85 not taken.
|
36 | payload.flags.contains(QChar::Null)) return std::nullopt; |
| 272 | 17 | return payload; | |
| 273 | 25 | } | |
| 274 | |||
| 275 | 6 | qint64 MutationJournalStore::appendPayloadBytes() const { | |
| 276 |
1/1✓ Branch 2 → 3 taken 6 times.
|
6 | QSqlQuery query(m_db); |
| 277 |
5/13✓ Branch 6 → 7 taken 6 times.
✓ Branch 7 → 8 taken 6 times.
✗ Branch 7 → 10 not taken.
✓ Branch 12 → 13 taken 6 times.
✗ Branch 12 → 14 not taken.
✓ Branch 14 → 15 taken 6 times.
✗ Branch 14 → 16 not taken.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 6 times.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 32 not taken.
|
18 | if (!query.exec(QStringLiteral("SELECT COALESCE(SUM(length(message)), 0) FROM append_payloads")) || |
| 278 |
2/3✓ Branch 8 → 9 taken 6 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 6 times.
|
6 | !query.next()) return -1; |
| 279 |
2/2✓ Branch 18 → 19 taken 6 times.
✓ Branch 19 → 20 taken 6 times.
|
6 | return query.value(0).toLongLong(); |
| 280 | 6 | } | |
| 281 | |||
| 282 | std::optional<QList<qint64>> | ||
| 283 | 21 | MutationJournalStore::enqueueMutations(const QList<MutationRecord> &records) { | |
| 284 |
3/3✓ Branch 2 → 3 taken 21 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 11 taken 20 times.
|
21 | if (!m_db.isOpen()) { |
| 285 | 1 | m_lastError = QStringLiteral("Mutation journal database is not open"); | |
| 286 | 1 | return std::nullopt; | |
| 287 | } | ||
| 288 |
2/2✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 17 taken 19 times.
|
20 | if (records.isEmpty()) return QList<qint64>{}; |
| 289 |
3/3✓ Branch 17 → 18 taken 19 times.
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 20 taken 18 times.
|
19 | if (!beginWriteTransaction(m_db, &m_lastError)) return std::nullopt; |
| 290 |
1/1✓ Branch 20 → 21 taken 18 times.
|
18 | const auto tokens = enqueueMutationsInTransaction(records); |
| 291 |
2/2✓ Branch 22 → 23 taken 14 times.
✓ Branch 22 → 25 taken 4 times.
|
18 | if (!tokens) { |
| 292 |
1/1✓ Branch 23 → 24 taken 14 times.
|
14 | m_db.rollback(); |
| 293 | 14 | return std::nullopt; | |
| 294 | } | ||
| 295 |
2/3✓ Branch 25 → 26 taken 4 times.
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 34 taken 4 times.
|
4 | if (!m_db.commit()) { |
| 296 | ✗ | m_lastError = m_db.lastError().text(); | |
| 297 | ✗ | m_db.rollback(); | |
| 298 | ✗ | return std::nullopt; | |
| 299 | } | ||
| 300 | // A newly journaled Move is the first pending phase. Indexed thread pages | ||
| 301 | // hide the row from this signal; they must not depend on the (possibly | ||
| 302 | // unloaded) flat model mirroring the optimistic removal. | ||
| 303 |
5/6✓ Branch 42 → 43 taken 7 times.
✓ Branch 42 → 47 taken 4 times.
✓ Branch 45 → 46 taken 7 times.
✗ Branch 45 → 47 not taken.
✓ Branch 48 → 35 taken 7 times.
✓ Branch 48 → 49 taken 4 times.
|
11 | for (int i = 0; i < records.size() && i < tokens->size(); ++i) { |
| 304 | 7 | const auto &record = records.at(i); | |
| 305 |
2/2✓ Branch 36 → 37 taken 2 times.
✓ Branch 36 → 40 taken 5 times.
|
7 | if (record.kind == MutationKind::Move) |
| 306 |
1/1✓ Branch 39 → 40 taken 2 times.
|
2 | m_notify.mutationPhaseChanged(record.mailId.accountId, record.mailId.folderId, |
| 307 | 2 | tokens->at(i), record.mailId.uid, true); | |
| 308 | } | ||
| 309 |
1/1✓ Branch 49 → 50 taken 4 times.
|
4 | return tokens; |
| 310 | 18 | } | |
| 311 | |||
| 312 | 181 | std::optional<QList<qint64>> MutationJournalStore::enqueueMutationsInTransaction( | |
| 313 | const QList<MutationRecord> &records, bool *capacityExceeded) { | ||
| 314 |
2/2✓ Branch 2 → 3 taken 91 times.
✓ Branch 2 → 4 taken 90 times.
|
181 | if (capacityExceeded) *capacityExceeded = false; |
| 315 | 6101 | const auto validRecord = [](const MutationRecord &record) { | |
| 316 |
4/4✓ Branch 5 → 6 taken 6099 times.
✓ Branch 5 → 19 taken 1 time.
✓ Branch 7 → 8 taken 6098 times.
✓ Branch 7 → 19 taken 1 time.
|
18300 | return record.isValid() && record.mailId.accountId.size() <= 256 && |
| 317 |
4/4✓ Branch 9 → 10 taken 6097 times.
✓ Branch 9 → 19 taken 1 time.
✓ Branch 11 → 12 taken 6096 times.
✓ Branch 11 → 19 taken 1 time.
|
18294 | record.folderPath.size() <= 1024 && record.flag.size() <= 256 && |
| 318 |
2/2✓ Branch 13 → 14 taken 6095 times.
✓ Branch 13 → 19 taken 1 time.
|
12193 | record.targetFolder.size() <= 1024 && |
| 319 |
2/2✓ Branch 15 → 16 taken 6094 times.
✓ Branch 15 → 19 taken 1 time.
|
12191 | record.messageId.size() <= 2048 && |
| 320 |
4/4✓ Branch 3 → 4 taken 6100 times.
✓ Branch 3 → 19 taken 1 time.
✓ Branch 17 → 18 taken 6093 times.
✓ Branch 17 → 19 taken 1 time.
|
18296 | record.uidPlusEvidence.size() <= 1024 && record.error.size() <= 2048; |
| 321 | }; | ||
| 322 |
4/5✓ Branch 4 → 5 taken 181 times.
✓ Branch 5 → 6 taken 181 times.
✗ Branch 5 → 10 not taken.
✓ Branch 12 → 13 taken 8 times.
✓ Branch 12 → 20 taken 173 times.
|
362 | if (!m_db.isOpen() || |
| 323 |
3/3✓ Branch 8 → 9 taken 181 times.
✓ Branch 9 → 10 taken 8 times.
✓ Branch 9 → 11 taken 173 times.
|
181 | std::any_of(records.cbegin(), records.cend(), |
| 324 | 6101 | [&validRecord](const MutationRecord &record) { | |
| 325 | 6101 | return !validRecord(record); | |
| 326 | })) { | ||
| 327 | 8 | m_lastError = QStringLiteral("Invalid or oversized mutation journal entry"); | |
| 328 | 8 | return std::nullopt; | |
| 329 | } | ||
| 330 |
1/2✗ Branch 21 → 22 not taken.
✓ Branch 21 → 26 taken 173 times.
|
173 | if (records.isEmpty()) |
| 331 | ✗ | return QList<qint64>{}; | |
| 332 | 9 | const auto fail = [this, capacityExceeded](const QString &error, bool limited = false) | |
| 333 | -> std::optional<QList<qint64>> { | ||
| 334 | 9 | m_lastError = error; | |
| 335 |
2/2✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 7 times.
|
9 | if (capacityExceeded) *capacityExceeded = limited; |
| 336 | 9 | return std::nullopt; | |
| 337 | 173 | }; | |
| 338 |
1/1✓ Branch 26 → 27 taken 173 times.
|
173 | QSqlQuery capacity(m_db); |
| 339 |
3/8✓ Branch 38 → 39 taken 173 times.
✗ Branch 38 → 40 not taken.
✓ Branch 40 → 41 taken 2 times.
✓ Branch 40 → 47 taken 171 times.
✗ Branch 419 → 420 not taken.
✗ Branch 419 → 421 not taken.
✗ Branch 423 → 424 not taken.
✗ Branch 423 → 425 not taken.
|
346 | if (!capacity.exec(QStringLiteral( |
| 340 | "SELECT COUNT(*), COALESCE(SUM(128 + 2 * (length(account_id) + " | ||
| 341 | "length(folder_path) + length(flag_name) + length(target_folder) + " | ||
| 342 | "length(message_id) + length(uidplus_evidence) + length(error))), 0) " | ||
| 343 |
4/5✓ Branch 30 → 31 taken 173 times.
✓ Branch 31 → 32 taken 171 times.
✓ Branch 31 → 34 taken 2 times.
✓ Branch 36 → 37 taken 173 times.
✗ Branch 36 → 38 not taken.
|
517 | "FROM mutation_journal")) || |
| 344 |
2/3✓ Branch 32 → 33 taken 171 times.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 171 times.
|
171 | !capacity.next()) { |
| 345 |
2/2✓ Branch 41 → 42 taken 2 times.
✓ Branch 42 → 43 taken 2 times.
|
2 | return fail(capacity.lastError().text()); |
| 346 | } | ||
| 347 |
2/2✓ Branch 47 → 48 taken 171 times.
✓ Branch 48 → 49 taken 171 times.
|
171 | int currentCount = capacity.value(0).toInt(); |
| 348 |
2/2✓ Branch 50 → 51 taken 171 times.
✓ Branch 51 → 52 taken 171 times.
|
171 | qint64 currentBytes = capacity.value(1).toLongLong(); |
| 349 | |||
| 350 |
1/1✓ Branch 53 → 54 taken 171 times.
|
171 | QSqlQuery existing(m_db); |
| 351 |
1/1✓ Branch 57 → 58 taken 171 times.
|
171 | existing.prepare(QStringLiteral( |
| 352 | "SELECT token, 128 + 2 * (length(account_id) + length(folder_path) + " | ||
| 353 | "length(flag_name) + length(target_folder) + length(message_id) + " | ||
| 354 | "length(uidplus_evidence) + length(error)) " | ||
| 355 | "FROM mutation_journal WHERE account_id = :account " | ||
| 356 | "AND folder_id = :folder AND uid = :uid AND kind = :kind " | ||
| 357 | "AND flag_name = :flag AND replay_class = :class " | ||
| 358 | "AND phase = :queued ORDER BY token DESC LIMIT 1")); | ||
| 359 |
1/1✓ Branch 60 → 61 taken 171 times.
|
171 | QSqlQuery update(m_db); |
| 360 |
1/1✓ Branch 64 → 65 taken 171 times.
|
171 | update.prepare(QStringLiteral( |
| 361 | "UPDATE mutation_journal SET desired_state = :desired, " | ||
| 362 | "folder_path = :path, uidvalidity = :validity, phase = :phase, " | ||
| 363 | "error = '', uidplus_evidence = '', updated_at = :updated " | ||
| 364 | "WHERE token = :token")); | ||
| 365 |
1/1✓ Branch 67 → 68 taken 171 times.
|
171 | QSqlQuery insert(m_db); |
| 366 |
1/1✓ Branch 71 → 72 taken 171 times.
|
171 | insert.prepare(QStringLiteral( |
| 367 | "INSERT INTO mutation_journal (account_id, folder_id, folder_path, uid, " | ||
| 368 | "kind, replay_class, phase, flag_name, desired_state, target_folder, " | ||
| 369 | "message_id, uidvalidity, uidplus_evidence, error, created_at, updated_at) " | ||
| 370 | "VALUES (:account, :folder, :path, :uid, :kind, :class, :phase, :flag, " | ||
| 371 | ":desired, :target, :message, :validity, :uidplus, :error, :created, " | ||
| 372 | ":updated)")); | ||
| 373 | |||
| 374 | 171 | const qint64 now = QDateTime::currentSecsSinceEpoch(); | |
| 375 | 171 | QList<qint64> tokens; | |
| 376 |
1/1✓ Branch 76 → 77 taken 171 times.
|
171 | tokens.reserve(records.size()); |
| 377 |
2/2✓ Branch 409 → 79 taken 6091 times.
✓ Branch 409 → 410 taken 164 times.
|
6255 | for (const MutationRecord &record : records) { |
| 378 |
2/2✓ Branch 80 → 81 taken 311 times.
✓ Branch 80 → 233 taken 5780 times.
|
6091 | if (record.replayClass == MutationReplayClass::DesiredState) { |
| 379 |
1/1✓ Branch 86 → 87 taken 311 times.
|
622 | existing.bindValue(QStringLiteral(":account"), record.mailId.accountId); |
| 380 |
1/1✓ Branch 95 → 96 taken 311 times.
|
622 | existing.bindValue(QStringLiteral(":folder"), record.mailId.folderId); |
| 381 |
1/1✓ Branch 104 → 105 taken 311 times.
|
622 | existing.bindValue(QStringLiteral(":uid"), record.mailId.uid); |
| 382 |
1/1✓ Branch 113 → 114 taken 311 times.
|
622 | existing.bindValue(QStringLiteral(":kind"), static_cast<int>(record.kind)); |
| 383 |
1/1✓ Branch 122 → 123 taken 311 times.
|
622 | existing.bindValue(QStringLiteral(":flag"), record.flag); |
| 384 |
1/1✓ Branch 131 → 132 taken 311 times.
|
622 | existing.bindValue(QStringLiteral(":class"), |
| 385 | static_cast<int>(MutationReplayClass::DesiredState)); | ||
| 386 |
1/1✓ Branch 140 → 141 taken 311 times.
|
622 | existing.bindValue(QStringLiteral(":queued"), |
| 387 | static_cast<int>(MutationPhase::Queued)); | ||
| 388 |
2/3✓ Branch 144 → 145 taken 311 times.
✗ Branch 145 → 146 not taken.
✓ Branch 145 → 152 taken 311 times.
|
311 | if (!existing.exec()) |
| 389 | ✗ | return fail(existing.lastError().text()); | |
| 390 |
3/3✓ Branch 152 → 153 taken 311 times.
✓ Branch 153 → 154 taken 13 times.
✓ Branch 153 → 233 taken 298 times.
|
311 | if (existing.next()) { |
| 391 |
2/2✓ Branch 154 → 155 taken 13 times.
✓ Branch 155 → 156 taken 13 times.
|
13 | const qint64 token = existing.value(0).toLongLong(); |
| 392 | 13 | currentBytes += estimatedMutationBytes(record) - | |
| 393 |
2/2✓ Branch 158 → 159 taken 13 times.
✓ Branch 159 → 160 taken 13 times.
|
13 | existing.value(1).toLongLong(); |
| 394 |
2/2✓ Branch 161 → 162 taken 1 time.
✓ Branch 161 → 169 taken 12 times.
|
13 | if (currentBytes > m_mutationJournalMaxBytes) |
| 395 | 1 | return fail(QStringLiteral("Mutation journal capacity exceeded"), true); | |
| 396 |
1/1✓ Branch 174 → 175 taken 12 times.
|
24 | update.bindValue(QStringLiteral(":desired"), record.desiredState); |
| 397 |
1/1✓ Branch 183 → 184 taken 12 times.
|
24 | update.bindValue(QStringLiteral(":path"), record.folderPath); |
| 398 |
1/1✓ Branch 192 → 193 taken 12 times.
|
24 | update.bindValue(QStringLiteral(":validity"), record.uidValidity); |
| 399 |
1/1✓ Branch 201 → 202 taken 12 times.
|
24 | update.bindValue(QStringLiteral(":phase"), |
| 400 | static_cast<int>(MutationPhase::Queued)); | ||
| 401 |
1/1✓ Branch 210 → 211 taken 12 times.
|
24 | update.bindValue(QStringLiteral(":updated"), now); |
| 402 |
1/1✓ Branch 219 → 220 taken 12 times.
|
24 | update.bindValue(QStringLiteral(":token"), token); |
| 403 |
3/3✓ Branch 223 → 224 taken 12 times.
✓ Branch 224 → 225 taken 1 time.
✓ Branch 224 → 231 taken 11 times.
|
12 | if (!update.exec()) |
| 404 |
2/2✓ Branch 225 → 226 taken 1 time.
✓ Branch 226 → 227 taken 1 time.
|
1 | return fail(update.lastError().text()); |
| 405 |
1/1✓ Branch 231 → 232 taken 11 times.
|
11 | tokens.append(token); |
| 406 | 11 | continue; | |
| 407 | 11 | } | |
| 408 | } | ||
| 409 | |||
| 410 | 6078 | ++currentCount; | |
| 411 | 6078 | currentBytes += estimatedMutationBytes(record); | |
| 412 |
2/2✓ Branch 234 → 235 taken 6075 times.
✓ Branch 234 → 236 taken 3 times.
|
6078 | if (currentCount > m_mutationJournalMaxEntries || |
| 413 |
1/2✗ Branch 235 → 236 not taken.
✓ Branch 235 → 243 taken 6075 times.
|
6075 | currentBytes > m_mutationJournalMaxBytes) |
| 414 | 3 | return fail(QStringLiteral("Mutation journal capacity exceeded"), true); | |
| 415 |
1/1✓ Branch 248 → 249 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":account"), record.mailId.accountId); |
| 416 |
1/1✓ Branch 257 → 258 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":folder"), record.mailId.folderId); |
| 417 |
1/1✓ Branch 266 → 267 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":path"), record.folderPath); |
| 418 |
1/1✓ Branch 275 → 276 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":uid"), record.mailId.uid); |
| 419 |
1/1✓ Branch 284 → 285 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":kind"), static_cast<int>(record.kind)); |
| 420 |
1/1✓ Branch 293 → 294 taken 6075 times.
|
6075 | insert.bindValue(QStringLiteral(":class"), |
| 421 | 6075 | static_cast<int>(record.replayClass)); | |
| 422 |
1/1✓ Branch 302 → 303 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":phase"), static_cast<int>(record.phase)); |
| 423 |
1/1✓ Branch 311 → 312 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":flag"), record.flag); |
| 424 |
1/1✓ Branch 320 → 321 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":desired"), record.desiredState); |
| 425 |
1/1✓ Branch 329 → 330 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":target"), record.targetFolder); |
| 426 |
1/1✓ Branch 338 → 339 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":message"), record.messageId); |
| 427 |
1/1✓ Branch 347 → 348 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":validity"), record.uidValidity); |
| 428 |
1/1✓ Branch 356 → 357 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":uidplus"), record.uidPlusEvidence); |
| 429 |
1/1✓ Branch 365 → 366 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":error"), record.error); |
| 430 |
1/1✓ Branch 374 → 375 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":created"), now); |
| 431 |
1/1✓ Branch 383 → 384 taken 6075 times.
|
12150 | insert.bindValue(QStringLiteral(":updated"), now); |
| 432 |
3/3✓ Branch 387 → 388 taken 6075 times.
✓ Branch 388 → 389 taken 2 times.
✓ Branch 388 → 395 taken 6073 times.
|
6075 | if (!insert.exec()) |
| 433 |
2/2✓ Branch 389 → 390 taken 2 times.
✓ Branch 390 → 391 taken 2 times.
|
2 | return fail(insert.lastError().text()); |
| 434 |
2/2✓ Branch 395 → 396 taken 6073 times.
✓ Branch 396 → 397 taken 6073 times.
|
6073 | const qint64 token = insert.lastInsertId().toLongLong(); |
| 435 |
1/2✗ Branch 398 → 399 not taken.
✓ Branch 398 → 406 taken 6073 times.
|
6073 | if (token <= 0) |
| 436 | ✗ | return fail(QStringLiteral("Mutation journal did not return a token")); | |
| 437 |
1/1✓ Branch 406 → 407 taken 6073 times.
|
6073 | tokens.append(token); |
| 438 | } | ||
| 439 | 164 | return tokens; | |
| 440 | 173 | } | |
| 441 | |||
| 442 | QList<MutationRecord> | ||
| 443 | 4057 | MutationJournalStore::pendingMutations(const QString &accountId) const { | |
| 444 | 4057 | QList<MutationRecord> records; | |
| 445 |
1/1✓ Branch 2 → 3 taken 4057 times.
|
4057 | QSqlQuery query(m_db); |
| 446 | 8114 | QString sql = QStringLiteral("SELECT %1 FROM mutation_journal ").arg( | |
| 447 |
2/2✓ Branch 8 → 9 taken 4057 times.
✓ Branch 9 → 10 taken 4057 times.
|
4057 | QString::fromLatin1(MutationColumns)); |
| 448 |
2/2✓ Branch 14 → 15 taken 4052 times.
✓ Branch 14 → 22 taken 5 times.
|
4057 | if (!accountId.isEmpty()) |
| 449 |
1/1✓ Branch 18 → 19 taken 4052 times.
|
4052 | sql += QStringLiteral("WHERE account_id = :account "); |
| 450 |
1/1✓ Branch 25 → 26 taken 4057 times.
|
4057 | sql += QStringLiteral("ORDER BY token"); |
| 451 |
1/1✓ Branch 28 → 29 taken 4057 times.
|
4057 | query.prepare(sql); |
| 452 |
2/2✓ Branch 30 → 31 taken 4052 times.
✓ Branch 30 → 41 taken 5 times.
|
4057 | if (!accountId.isEmpty()) |
| 453 |
1/1✓ Branch 36 → 37 taken 4052 times.
|
8104 | query.bindValue(QStringLiteral(":account"), accountId); |
| 454 |
3/3✓ Branch 41 → 42 taken 4057 times.
✓ Branch 42 → 43 taken 2 times.
✓ Branch 42 → 44 taken 4055 times.
|
4057 | if (!query.exec()) |
| 455 | 2 | return records; | |
| 456 |
3/3✓ Branch 49 → 50 taken 2947624 times.
✓ Branch 50 → 45 taken 2943569 times.
✓ Branch 50 → 51 taken 4055 times.
|
2947624 | while (query.next()) |
| 457 |
2/2✓ Branch 45 → 46 taken 2943569 times.
✓ Branch 46 → 47 taken 2943569 times.
|
2943569 | records.append(mutationFromQuery(query)); |
| 458 | 4055 | return records; | |
| 459 | 4057 | } | |
| 460 | |||
| 461 | 23 | std::optional<QList<MutationRecord>> MutationJournalStore::pendingAppendMutations(const QString &accountId, int limit) const { | |
| 462 |
4/8✓ Branch 3 → 4 taken 23 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 23 times.
✗ Branch 4 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 23 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 23 times.
|
23 | if (accountId.isEmpty() || limit < 1 || limit > 100) return std::nullopt; |
| 463 |
1/1✓ Branch 10 → 11 taken 23 times.
|
23 | QSqlQuery query(m_db); |
| 464 |
1/1✓ Branch 18 → 19 taken 23 times.
|
69 | query.prepare(QStringLiteral("SELECT %1 FROM mutation_journal WHERE account_id = :account " |
| 465 | "AND kind = :kind AND phase != :confirmed ORDER BY token LIMIT :limit") | ||
| 466 |
2/2✓ Branch 16 → 17 taken 23 times.
✓ Branch 17 → 18 taken 23 times.
|
69 | .arg(QString::fromLatin1(MutationColumns))); |
| 467 |
2/2✓ Branch 25 → 26 taken 23 times.
✓ Branch 26 → 27 taken 23 times.
|
23 | query.bindValue(":account", accountId); |
| 468 |
2/2✓ Branch 31 → 32 taken 23 times.
✓ Branch 32 → 33 taken 23 times.
|
23 | query.bindValue(":kind", static_cast<int>(MutationKind::Append)); |
| 469 |
2/2✓ Branch 37 → 38 taken 23 times.
✓ Branch 38 → 39 taken 23 times.
|
23 | query.bindValue(":confirmed", static_cast<int>(MutationPhase::Confirmed)); |
| 470 |
2/2✓ Branch 43 → 44 taken 23 times.
✓ Branch 44 → 45 taken 23 times.
|
23 | query.bindValue(":limit", limit); |
| 471 |
2/3✓ Branch 47 → 48 taken 23 times.
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 50 taken 23 times.
|
23 | if (!query.exec()) return std::nullopt; |
| 472 | 23 | QList<MutationRecord> rows; | |
| 473 |
5/5✓ Branch 51 → 52 taken 27 times.
✓ Branch 52 → 53 taken 27 times.
✓ Branch 55 → 56 taken 50 times.
✓ Branch 56 → 51 taken 27 times.
✓ Branch 56 → 57 taken 23 times.
|
50 | while (query.next()) rows.append(mutationFromQuery(query)); |
| 474 | 23 | return rows; | |
| 475 | 23 | } | |
| 476 | |||
| 477 | 1 | bool MutationJournalStore::requeueFailedAppend(qint64 token, const QString &accountId) { | |
| 478 |
1/1✓ Branch 2 → 3 taken 1 time.
|
1 | QSqlQuery query(m_db); |
| 479 |
1/1✓ Branch 6 → 7 taken 1 time.
|
1 | query.prepare(QStringLiteral("UPDATE mutation_journal SET phase = :queued, error = '', updated_at = :now " |
| 480 | "WHERE token = :token AND account_id = :account AND kind = :kind AND phase = :failed " | ||
| 481 | "AND EXISTS (SELECT 1 FROM append_payloads p WHERE p.token = :token " | ||
| 482 | "AND length(p.message) > 0 AND length(p.message) <= :limit " | ||
| 483 | "AND length(p.sha256) = 32 AND length(p.flags) <= 256)")); | ||
| 484 |
2/2✓ Branch 11 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
|
1 | query.bindValue(":queued", static_cast<int>(MutationPhase::Queued)); |
| 485 |
2/2✓ Branch 18 → 19 taken 1 time.
✓ Branch 19 → 20 taken 1 time.
|
1 | query.bindValue(":now", QDateTime::currentSecsSinceEpoch()); |
| 486 |
2/2✓ Branch 24 → 25 taken 1 time.
✓ Branch 25 → 26 taken 1 time.
|
1 | query.bindValue(":token", token); |
| 487 |
2/2✓ Branch 30 → 31 taken 1 time.
✓ Branch 31 → 32 taken 1 time.
|
1 | query.bindValue(":account", accountId); |
| 488 |
2/2✓ Branch 36 → 37 taken 1 time.
✓ Branch 37 → 38 taken 1 time.
|
1 | query.bindValue(":kind", static_cast<int>(MutationKind::Append)); |
| 489 |
2/2✓ Branch 42 → 43 taken 1 time.
✓ Branch 43 → 44 taken 1 time.
|
1 | query.bindValue(":failed", static_cast<int>(MutationPhase::Failed)); |
| 490 |
2/2✓ Branch 48 → 49 taken 1 time.
✓ Branch 49 → 50 taken 1 time.
|
1 | query.bindValue(":limit", MaximumAppendMessageBytes); |
| 491 |
4/6✓ Branch 52 → 53 taken 1 time.
✓ Branch 53 → 54 taken 1 time.
✗ Branch 53 → 57 not taken.
✓ Branch 54 → 55 taken 1 time.
✓ Branch 55 → 56 taken 1 time.
✗ Branch 55 → 57 not taken.
|
2 | return query.exec() && query.numRowsAffected() == 1; |
| 492 | 1 | } | |
| 493 | |||
| 494 | 5074 | std::optional<MutationRecord> MutationJournalStore::mutation(qint64 token) const { | |
| 495 |
1/1✓ Branch 2 → 3 taken 5074 times.
|
5074 | QSqlQuery query(m_db); |
| 496 |
1/1✓ Branch 10 → 11 taken 5074 times.
|
15222 | query.prepare(QStringLiteral("SELECT %1 FROM mutation_journal WHERE token = " |
| 497 | ":token") | ||
| 498 |
2/2✓ Branch 8 → 9 taken 5074 times.
✓ Branch 9 → 10 taken 5074 times.
|
15222 | .arg(QString::fromLatin1(MutationColumns))); |
| 499 |
1/1✓ Branch 20 → 21 taken 5074 times.
|
10148 | query.bindValue(QStringLiteral(":token"), token); |
| 500 |
8/8✓ Branch 24 → 25 taken 5074 times.
✓ Branch 25 → 26 taken 5072 times.
✓ Branch 25 → 28 taken 2 times.
✓ Branch 26 → 27 taken 5072 times.
✓ Branch 27 → 28 taken 54 times.
✓ Branch 27 → 29 taken 5018 times.
✓ Branch 30 → 31 taken 56 times.
✓ Branch 30 → 32 taken 5018 times.
|
5074 | if (!query.exec() || !query.next()) |
| 501 | 56 | return std::nullopt; | |
| 502 |
1/1✓ Branch 32 → 33 taken 5018 times.
|
5018 | return mutationFromQuery(query); |
| 503 | 5074 | } | |
| 504 | |||
| 505 | 63 | bool MutationJournalStore::claimMutationForDispatch(const MutationRecord &expected) { | |
| 506 |
6/9✓ Branch 2 → 3 taken 63 times.
✓ Branch 3 → 4 taken 63 times.
✗ Branch 3 → 8 not taken.
✓ Branch 4 → 5 taken 63 times.
✗ Branch 4 → 8 not taken.
✓ Branch 6 → 7 taken 63 times.
✗ Branch 6 → 8 not taken.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 62 times.
|
126 | if (!m_db.isOpen() || expected.token <= 0 || !expected.isValid() || |
| 507 |
2/2✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 62 times.
|
63 | expected.phase != MutationPhase::Queued) |
| 508 | 1 | return false; | |
| 509 | // One SQLite write statement arbitrates with worker-owned cancellation. | ||
| 510 | // Compare execution fields, not timestamps (which have second resolution). | ||
| 511 |
1/1✓ Branch 12 → 13 taken 62 times.
|
62 | QSqlQuery query(m_db); |
| 512 |
1/1✓ Branch 16 → 17 taken 62 times.
|
62 | query.prepare(QStringLiteral( |
| 513 | "UPDATE mutation_journal SET phase=:dispatched, updated_at=:now " | ||
| 514 | "WHERE token=:token AND phase=:queued AND account_id=:account " | ||
| 515 | "AND folder_id=:folder AND folder_path=:path AND uid=:uid " | ||
| 516 | "AND kind=:kind AND replay_class=:class AND uidvalidity=:validity " | ||
| 517 | "AND COALESCE(flag_name,'')=:flag AND desired_state=:desired " | ||
| 518 | "AND COALESCE(target_folder,'')=:target AND COALESCE(message_id,'')=:message " | ||
| 519 | "AND (:append OR EXISTS (SELECT 1 FROM folders f WHERE f.id=:folder " | ||
| 520 | "AND f.account=:account AND f.path=:path " | ||
| 521 | "AND (:validity=0 OR f.uidvalidity=0 OR f.uidvalidity=:validity)))")); | ||
| 522 |
2/2✓ Branch 21 → 22 taken 62 times.
✓ Branch 22 → 23 taken 62 times.
|
62 | query.bindValue(":dispatched", static_cast<int>(MutationPhase::Dispatched)); |
| 523 |
2/2✓ Branch 28 → 29 taken 62 times.
✓ Branch 29 → 30 taken 62 times.
|
62 | query.bindValue(":now", QDateTime::currentSecsSinceEpoch()); |
| 524 |
2/2✓ Branch 34 → 35 taken 62 times.
✓ Branch 35 → 36 taken 62 times.
|
62 | query.bindValue(":token", expected.token); |
| 525 |
2/2✓ Branch 40 → 41 taken 62 times.
✓ Branch 41 → 42 taken 62 times.
|
62 | query.bindValue(":queued", static_cast<int>(MutationPhase::Queued)); |
| 526 |
2/2✓ Branch 46 → 47 taken 62 times.
✓ Branch 47 → 48 taken 62 times.
|
62 | query.bindValue(":account", expected.mailId.accountId); |
| 527 |
2/2✓ Branch 52 → 53 taken 62 times.
✓ Branch 53 → 54 taken 62 times.
|
62 | query.bindValue(":folder", expected.mailId.folderId); |
| 528 |
2/2✓ Branch 58 → 59 taken 62 times.
✓ Branch 59 → 60 taken 62 times.
|
62 | query.bindValue(":path", expected.folderPath); |
| 529 |
2/2✓ Branch 64 → 65 taken 62 times.
✓ Branch 65 → 66 taken 62 times.
|
62 | query.bindValue(":uid", expected.mailId.uid); |
| 530 |
2/2✓ Branch 70 → 71 taken 62 times.
✓ Branch 71 → 72 taken 62 times.
|
62 | query.bindValue(":kind", static_cast<int>(expected.kind)); |
| 531 |
2/2✓ Branch 76 → 77 taken 62 times.
✓ Branch 77 → 78 taken 62 times.
|
62 | query.bindValue(":class", static_cast<int>(expected.replayClass)); |
| 532 |
2/2✓ Branch 82 → 83 taken 62 times.
✓ Branch 83 → 84 taken 62 times.
|
62 | query.bindValue(":validity", expected.uidValidity); |
| 533 |
6/8✓ Branch 88 → 89 taken 51 times.
✓ Branch 88 → 92 taken 11 times.
✓ Branch 94 → 95 taken 62 times.
✓ Branch 95 → 96 taken 62 times.
✓ Branch 99 → 100 taken 51 times.
✓ Branch 99 → 101 taken 11 times.
✗ Branch 246 → 247 not taken.
✗ Branch 246 → 248 not taken.
|
113 | query.bindValue(":flag", expected.flag.isNull() ? QStringLiteral("") : expected.flag); |
| 534 |
2/2✓ Branch 103 → 104 taken 62 times.
✓ Branch 104 → 105 taken 62 times.
|
62 | query.bindValue(":desired", expected.desiredState); |
| 535 |
6/8✓ Branch 109 → 110 taken 2 times.
✓ Branch 109 → 113 taken 60 times.
✓ Branch 115 → 116 taken 62 times.
✓ Branch 116 → 117 taken 62 times.
✓ Branch 120 → 121 taken 2 times.
✓ Branch 120 → 122 taken 60 times.
✗ Branch 267 → 268 not taken.
✗ Branch 267 → 269 not taken.
|
64 | query.bindValue(":target", expected.targetFolder.isNull() ? QStringLiteral("") : expected.targetFolder); |
| 536 |
6/8✓ Branch 124 → 125 taken 2 times.
✓ Branch 124 → 128 taken 60 times.
✓ Branch 130 → 131 taken 62 times.
✓ Branch 131 → 132 taken 62 times.
✓ Branch 135 → 136 taken 2 times.
✓ Branch 135 → 137 taken 60 times.
✗ Branch 281 → 282 not taken.
✗ Branch 281 → 283 not taken.
|
64 | query.bindValue(":message", expected.messageId.isNull() ? QStringLiteral("") : expected.messageId); |
| 537 |
2/2✓ Branch 139 → 140 taken 62 times.
✓ Branch 140 → 141 taken 62 times.
|
62 | query.bindValue(":append", expected.kind == MutationKind::Append); |
| 538 |
2/3✓ Branch 143 → 144 taken 62 times.
✗ Branch 144 → 145 not taken.
✓ Branch 144 → 151 taken 62 times.
|
62 | if (!query.exec()) { |
| 539 | ✗ | m_lastError = query.lastError().text(); | |
| 540 | ✗ | return false; | |
| 541 | } | ||
| 542 |
1/1✓ Branch 151 → 152 taken 62 times.
|
62 | return query.numRowsAffected() == 1; |
| 543 | 62 | } | |
| 544 | |||
| 545 | 109 | bool MutationJournalStore::updateMutationPhase(qint64 token, MutationPhase phase, | |
| 546 | const QString &error, | ||
| 547 | const QString &uidPlusEvidence) { | ||
| 548 |
6/6✓ Branch 3 → 4 taken 108 times.
✓ Branch 3 → 6 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 107 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 107 times.
|
109 | if (error.size() > 2048 || uidPlusEvidence.size() > 1024) |
| 549 | 2 | return false; | |
| 550 | // P90.16: the phase drives pending-move admission sets; owners need the | ||
| 551 | // transition to refresh their projection exclusions even when the folder | ||
| 552 | // revision is unchanged. | ||
| 553 |
1/1✓ Branch 10 → 11 taken 107 times.
|
107 | QSqlQuery owner(m_db); |
| 554 |
1/1✓ Branch 14 → 15 taken 107 times.
|
107 | owner.prepare(QStringLiteral("SELECT account_id, folder_id, kind, uid FROM mutation_journal WHERE token=:token")); |
| 555 |
1/1✓ Branch 22 → 23 taken 107 times.
|
214 | owner.bindValue(QStringLiteral(":token"), token); |
| 556 |
6/6✓ Branch 26 → 27 taken 107 times.
✓ Branch 27 → 28 taken 105 times.
✓ Branch 27 → 31 taken 2 times.
✓ Branch 28 → 29 taken 105 times.
✓ Branch 29 → 30 taken 104 times.
✓ Branch 29 → 31 taken 1 time.
|
107 | const bool hasOwner = owner.exec() && owner.next(); |
| 557 |
6/8✓ Branch 32 → 33 taken 104 times.
✓ Branch 32 → 35 taken 3 times.
✓ Branch 33 → 34 taken 104 times.
✓ Branch 34 → 36 taken 104 times.
✓ Branch 36 → 37 taken 104 times.
✓ Branch 36 → 38 taken 3 times.
✗ Branch 150 → 151 not taken.
✗ Branch 150 → 152 not taken.
|
107 | const QString account = hasOwner ? owner.value(0).toString() : QString{}; |
| 558 |
6/8✓ Branch 38 → 39 taken 104 times.
✓ Branch 38 → 42 taken 3 times.
✓ Branch 39 → 40 taken 104 times.
✓ Branch 40 → 41 taken 104 times.
✓ Branch 43 → 44 taken 104 times.
✓ Branch 43 → 45 taken 3 times.
✗ Branch 154 → 155 not taken.
✗ Branch 154 → 156 not taken.
|
107 | const qint64 folderId = hasOwner ? owner.value(1).toLongLong() : qint64(-1); |
| 559 |
2/2✓ Branch 45 → 46 taken 104 times.
✓ Branch 45 → 50 taken 3 times.
|
211 | const bool mayChangeMoveExclusions = hasOwner && |
| 560 |
6/8✓ Branch 46 → 47 taken 104 times.
✓ Branch 47 → 48 taken 104 times.
✓ Branch 48 → 49 taken 40 times.
✓ Branch 48 → 50 taken 64 times.
✓ Branch 51 → 52 taken 104 times.
✓ Branch 51 → 53 taken 3 times.
✗ Branch 158 → 159 not taken.
✗ Branch 158 → 160 not taken.
|
211 | owner.value(2).toInt() == int(MutationKind::Move); |
| 561 |
6/8✓ Branch 53 → 54 taken 40 times.
✓ Branch 53 → 57 taken 67 times.
✓ Branch 54 → 55 taken 40 times.
✓ Branch 55 → 56 taken 40 times.
✓ Branch 58 → 59 taken 40 times.
✓ Branch 58 → 60 taken 67 times.
✗ Branch 162 → 163 not taken.
✗ Branch 162 → 164 not taken.
|
107 | const qint64 uid = mayChangeMoveExclusions ? owner.value(3).toLongLong() : qint64(0); |
| 562 |
1/1✓ Branch 60 → 61 taken 107 times.
|
107 | owner.finish(); |
| 563 |
1/1✓ Branch 61 → 62 taken 107 times.
|
107 | QSqlQuery query(m_db); |
| 564 |
1/1✓ Branch 65 → 66 taken 107 times.
|
107 | query.prepare(QStringLiteral( |
| 565 | "UPDATE mutation_journal SET phase = :phase, error = :error, " | ||
| 566 | "uidplus_evidence = :uidplus, updated_at = :updated WHERE token = " | ||
| 567 | ":token")); | ||
| 568 |
1/1✓ Branch 73 → 74 taken 107 times.
|
214 | query.bindValue(QStringLiteral(":phase"), static_cast<int>(phase)); |
| 569 |
1/1✓ Branch 82 → 83 taken 107 times.
|
214 | query.bindValue(QStringLiteral(":error"), error); |
| 570 |
1/1✓ Branch 91 → 92 taken 107 times.
|
214 | query.bindValue(QStringLiteral(":uidplus"), uidPlusEvidence); |
| 571 |
1/1✓ Branch 101 → 102 taken 107 times.
|
214 | query.bindValue(QStringLiteral(":updated"), |
| 572 | QDateTime::currentSecsSinceEpoch()); | ||
| 573 |
1/1✓ Branch 110 → 111 taken 107 times.
|
214 | query.bindValue(QStringLiteral(":token"), token); |
| 574 |
6/6✓ Branch 114 → 115 taken 107 times.
✓ Branch 115 → 116 taken 105 times.
✓ Branch 115 → 119 taken 2 times.
✓ Branch 116 → 117 taken 105 times.
✓ Branch 117 → 118 taken 104 times.
✓ Branch 117 → 119 taken 1 time.
|
107 | const bool updated = query.exec() && query.numRowsAffected() == 1; |
| 575 |
5/6✓ Branch 120 → 121 taken 104 times.
✓ Branch 120 → 129 taken 3 times.
✓ Branch 121 → 122 taken 104 times.
✗ Branch 121 → 129 not taken.
✓ Branch 122 → 123 taken 40 times.
✓ Branch 122 → 129 taken 64 times.
|
107 | if (updated && folderId >= 0 && mayChangeMoveExclusions) |
| 576 |
1/1✓ Branch 128 → 129 taken 40 times.
|
40 | m_notify.mutationPhaseChanged(account, folderId, token, uid, |
| 577 |
6/6✓ Branch 123 → 124 taken 28 times.
✓ Branch 123 → 127 taken 12 times.
✓ Branch 124 → 125 taken 24 times.
✓ Branch 124 → 127 taken 4 times.
✓ Branch 125 → 126 taken 23 times.
✓ Branch 125 → 127 taken 1 time.
|
40 | phase != MutationPhase::Confirmed && phase != MutationPhase::Failed && |
| 578 | phase != MutationPhase::Quarantined); | ||
| 579 | 107 | return updated; | |
| 580 | 107 | } | |
| 581 | |||
| 582 | 3213 | bool MutationJournalStore::removeMutation(qint64 token, QList<qint64> *deferred) { | |
| 583 |
1/1✓ Branch 2 → 3 taken 3213 times.
|
3213 | QSqlQuery query(m_db); |
| 584 |
1/1✓ Branch 6 → 7 taken 3213 times.
|
3213 | query.prepare( |
| 585 | 6426 | QStringLiteral("DELETE FROM mutation_journal WHERE token = :token")); | |
| 586 |
1/1✓ Branch 14 → 15 taken 3213 times.
|
6426 | query.bindValue(QStringLiteral(":token"), token); |
| 587 |
6/6✓ Branch 18 → 19 taken 3213 times.
✓ Branch 19 → 20 taken 3210 times.
✓ Branch 19 → 23 taken 3 times.
✓ Branch 20 → 21 taken 3210 times.
✓ Branch 21 → 22 taken 3209 times.
✓ Branch 21 → 23 taken 1 time.
|
3213 | const bool removed = query.exec() && query.numRowsAffected() == 1; |
| 588 |
5/5✓ Branch 24 → 25 taken 3209 times.
✓ Branch 24 → 28 taken 4 times.
✓ Branch 25 → 26 taken 3148 times.
✓ Branch 25 → 28 taken 61 times.
✓ Branch 26 → 27 taken 3148 times.
|
3213 | if (removed && deferred) deferred->append(token); |
| 589 |
3/3✓ Branch 28 → 29 taken 61 times.
✓ Branch 28 → 30 taken 4 times.
✓ Branch 29 → 30 taken 61 times.
|
65 | else if (removed) m_notify.mutationRemoved(token); |
| 590 | 3213 | return removed; | |
| 591 | 3213 | } | |
| 592 | |||
| 593 | 18 | QList<qint64> MutationJournalStore::retireSourceFlagMutations(const MailId &source, | |
| 594 | quint32 uidValidity) { | ||
| 595 | 18 | QList<qint64> tokens; | |
| 596 |
5/6✓ Branch 3 → 4 taken 18 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 17 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 17 times.
|
18 | if (!source.isValid() || uidValidity == 0) return tokens; |
| 597 |
1/1✓ Branch 9 → 10 taken 17 times.
|
17 | QSqlQuery query(m_db); |
| 598 |
1/1✓ Branch 13 → 14 taken 17 times.
|
17 | query.prepare(QStringLiteral( |
| 599 | "DELETE FROM mutation_journal WHERE account_id = :account " | ||
| 600 | "AND folder_id = :folder AND uid = :uid AND uidvalidity = :validity " | ||
| 601 | "AND replay_class = :class AND kind IN (:seen, :flagged, :keyword) " | ||
| 602 | "RETURNING token")); | ||
| 603 |
1/1✓ Branch 21 → 22 taken 17 times.
|
34 | query.bindValue(QStringLiteral(":account"), source.accountId); |
| 604 |
1/1✓ Branch 30 → 31 taken 17 times.
|
34 | query.bindValue(QStringLiteral(":folder"), source.folderId); |
| 605 |
1/1✓ Branch 39 → 40 taken 17 times.
|
34 | query.bindValue(QStringLiteral(":uid"), source.uid); |
| 606 |
1/1✓ Branch 48 → 49 taken 17 times.
|
34 | query.bindValue(QStringLiteral(":validity"), uidValidity); |
| 607 |
1/1✓ Branch 57 → 58 taken 17 times.
|
34 | query.bindValue(QStringLiteral(":class"), static_cast<int>(MutationReplayClass::DesiredState)); |
| 608 |
1/1✓ Branch 66 → 67 taken 17 times.
|
34 | query.bindValue(QStringLiteral(":seen"), static_cast<int>(MutationKind::Seen)); |
| 609 |
1/1✓ Branch 75 → 76 taken 17 times.
|
34 | query.bindValue(QStringLiteral(":flagged"), static_cast<int>(MutationKind::Flagged)); |
| 610 |
1/1✓ Branch 84 → 85 taken 17 times.
|
34 | query.bindValue(QStringLiteral(":keyword"), static_cast<int>(MutationKind::Keyword)); |
| 611 |
2/3✓ Branch 88 → 89 taken 17 times.
✗ Branch 89 → 90 not taken.
✓ Branch 89 → 107 taken 17 times.
|
17 | if (!query.exec()) { |
| 612 | ✗ | qCWarning(lcCache) << "Could not retire moved-mail flag verification:" << query.lastError(); | |
| 613 | ✗ | return tokens; | |
| 614 | } | ||
| 615 |
6/6✓ Branch 108 → 109 taken 4 times.
✓ Branch 109 → 110 taken 4 times.
✓ Branch 110 → 111 taken 4 times.
✓ Branch 113 → 114 taken 21 times.
✓ Branch 114 → 108 taken 4 times.
✓ Branch 114 → 115 taken 17 times.
|
21 | while (query.next()) tokens.append(query.value(0).toLongLong()); |
| 616 |
1/1✓ Branch 115 → 116 taken 17 times.
|
17 | query.finish(); |
| 617 |
5/5✓ Branch 116 → 117 taken 17 times.
✓ Branch 117 → 118 taken 17 times.
✓ Branch 120 → 121 taken 4 times.
✓ Branch 123 → 119 taken 4 times.
✓ Branch 123 → 124 taken 17 times.
|
21 | for (qint64 token : tokens) m_notify.mutationRemoved(token); |
| 618 | 17 | return tokens; | |
| 619 | 17 | } | |
| 620 | |||
| 621 | 470 | bool MutationJournalStore::journalUsage(int *count, qint64 *bytes) const { | |
| 622 |
1/1✓ Branch 2 → 3 taken 470 times.
|
470 | QSqlQuery query(m_db); |
| 623 |
3/8✓ Branch 14 → 15 taken 470 times.
✗ Branch 14 → 16 not taken.
✓ Branch 16 → 17 taken 6 times.
✓ Branch 16 → 18 taken 464 times.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
|
940 | if (!query.exec(QStringLiteral( |
| 624 | "SELECT COUNT(*), COALESCE(SUM(128 + 2 * (length(account_id) + " | ||
| 625 | "length(folder_path) + length(flag_name) + length(target_folder) + " | ||
| 626 | "length(message_id) + length(uidplus_evidence) + length(error))), 0) " | ||
| 627 |
4/5✓ Branch 6 → 7 taken 470 times.
✓ Branch 7 → 8 taken 464 times.
✓ Branch 7 → 10 taken 6 times.
✓ Branch 12 → 13 taken 470 times.
✗ Branch 12 → 14 not taken.
|
1404 | "FROM mutation_journal")) || |
| 628 |
2/3✓ Branch 8 → 9 taken 464 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 464 times.
|
464 | !query.next()) |
| 629 | 6 | return false; | |
| 630 |
2/2✓ Branch 18 → 19 taken 464 times.
✓ Branch 19 → 20 taken 464 times.
|
464 | *count = query.value(0).toInt(); |
| 631 |
2/2✓ Branch 21 → 22 taken 464 times.
✓ Branch 22 → 23 taken 464 times.
|
464 | *bytes = query.value(1).toLongLong(); |
| 632 | 464 | return true; | |
| 633 | 470 | } | |
| 634 | |||
| 635 | 144 | int MutationJournalStore::mutationCount() const { | |
| 636 | 144 | int count = 0; | |
| 637 | 144 | qint64 bytes = 0; | |
| 638 |
3/3✓ Branch 2 → 3 taken 144 times.
✓ Branch 3 → 4 taken 141 times.
✓ Branch 3 → 5 taken 3 times.
|
288 | return journalUsage(&count, &bytes) ? count : 0; |
| 639 | } | ||
| 640 | |||
| 641 | 8 | qint64 MutationJournalStore::mutationJournalBytes() const { | |
| 642 | 8 | int count = 0; | |
| 643 | 8 | qint64 bytes = 0; | |
| 644 |
3/3✓ Branch 2 → 3 taken 8 times.
✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 5 taken 3 times.
|
16 | return journalUsage(&count, &bytes) ? bytes : 0; |
| 645 | } | ||
| 646 |